VLCMediaThumbnailer.m 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. /*****************************************************************************
  2. * VLCKit: VLCMediaThumbnailer
  3. *****************************************************************************
  4. * Copyright (C) 2010-2012 Pierre d'Herbemont and VideoLAN
  5. *
  6. * Authors: Pierre d'Herbemont
  7. *
  8. * This program is free software; you can redistribute it and/or modify it
  9. * under the terms of the GNU Lesser General Public License as published by
  10. * the Free Software Foundation; either version 2.1 of the License, or
  11. * (at your option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU Lesser General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU Lesser General Public License
  19. * along with this program; if not, write to the Free Software Foundation,
  20. * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
  21. *****************************************************************************/
  22. #import <vlc/vlc.h>
  23. #import "VLCMediaThumbnailer.h"
  24. #import "VLCLibVLCBridging.h"
  25. @interface VLCMediaThumbnailer ()
  26. - (void)didFetchThumbnail;
  27. - (void)notifyDelegate;
  28. - (void)fetchThumbnail;
  29. - (void)startFetchingThumbnail;
  30. @property (readonly, assign) void *dataPointer;
  31. @property (readonly, assign) BOOL shouldRejectFrames;
  32. @end
  33. static void *lock(void *opaque, void **pixels)
  34. {
  35. VLCMediaThumbnailer *thumbnailer = opaque;
  36. *pixels = [thumbnailer dataPointer];
  37. assert(*pixels);
  38. return NULL;
  39. }
  40. static const size_t kDefaultImageWidth = 320;
  41. static const size_t kDefaultImageHeight = 240;
  42. static const float kSnapshotPosition = 0.5;
  43. void unlock(void *opaque, void *picture, void *const *p_pixels)
  44. {
  45. VLCMediaThumbnailer *thumbnailer = opaque;
  46. assert(!picture);
  47. assert([thumbnailer dataPointer] == *p_pixels);
  48. // We may already have a thumbnail if we are receiving picture after the first one.
  49. // Just ignore.
  50. if ([thumbnailer thumbnail] || [thumbnailer shouldRejectFrames])
  51. return;
  52. [thumbnailer performSelectorOnMainThread:@selector(didFetchThumbnail) withObject:nil waitUntilDone:YES];
  53. }
  54. @implementation VLCMediaThumbnailer
  55. @synthesize media=_media;
  56. @synthesize delegate=_delegate;
  57. @synthesize thumbnail=_thumbnail;
  58. @synthesize dataPointer=_data;
  59. @synthesize thumbnailWidth=_thumbnailWidth;
  60. @synthesize thumbnailHeight=_thumbnailHeight;
  61. @synthesize snapshotPosition=_snapshotPosition;
  62. @synthesize shouldRejectFrames=_shouldRejectFrames;
  63. + (VLCMediaThumbnailer *)thumbnailerWithMedia:(VLCMedia *)media andDelegate:(id<VLCMediaThumbnailerDelegate>)delegate
  64. {
  65. id obj = [[[self class] alloc] init];
  66. [obj setMedia:media];
  67. [obj setDelegate:delegate];
  68. return [obj autorelease];
  69. }
  70. - (void)dealloc
  71. {
  72. NSAssert(!_thumbnailingTimeoutTimer, @"Timer not released");
  73. NSAssert(!_parsingTimeoutTimer, @"Timer not released");
  74. NSAssert(!_data, @"Data not released");
  75. NSAssert(!_mp, @"Not properly retained");
  76. if (_thumbnail)
  77. CGImageRelease(_thumbnail);
  78. [_media release];
  79. [super dealloc];
  80. }
  81. - (void)fetchThumbnail
  82. {
  83. NSAssert(!_data, @"We are already fetching a thumbnail");
  84. [self retain]; // Balanced in -notifyDelegate
  85. if (![_media isParsed]) {
  86. [_media addObserver:self forKeyPath:@"parsed" options:0 context:NULL];
  87. [_media synchronousParse];
  88. NSAssert(!_parsingTimeoutTimer, @"We already have a timer around");
  89. _parsingTimeoutTimer = [[NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(mediaParsingTimedOut) userInfo:nil repeats:NO] retain];
  90. return;
  91. }
  92. [self startFetchingThumbnail];
  93. }
  94. - (void)startFetchingThumbnail
  95. {
  96. NSArray *tracks = [_media tracksInformation];
  97. // Find the video track
  98. NSDictionary *videoTrack = nil;
  99. for (NSDictionary *track in tracks) {
  100. NSString *type = track[VLCMediaTracksInformationType];
  101. if ([type isEqualToString:VLCMediaTracksInformationTypeVideo]) {
  102. videoTrack = track;
  103. break;
  104. }
  105. }
  106. unsigned imageWidth = _thumbnailWidth > 0 ? _thumbnailWidth : kDefaultImageWidth;
  107. unsigned imageHeight = _thumbnailHeight > 0 ? _thumbnailHeight : kDefaultImageHeight;
  108. unsigned snapshotPosition = _snapshotPosition > 0 ? _snapshotPosition : kSnapshotPosition;
  109. if (!videoTrack) {
  110. VKLog(@"WARNING: Can't find video track info, skipping file");
  111. [self mediaThumbnailingTimedOut];
  112. return;
  113. } else {
  114. int videoHeight = [videoTrack[VLCMediaTracksInformationVideoHeight] intValue];
  115. int videoWidth = [videoTrack[VLCMediaTracksInformationVideoWidth] intValue];
  116. // Constraining to the aspect ratio of the video.
  117. double ratio;
  118. if ((double)imageWidth / imageHeight < (double)videoWidth / videoHeight)
  119. ratio = (double)imageHeight / videoHeight;
  120. else
  121. ratio = (double)imageWidth / videoWidth;
  122. int newWidth = round(videoWidth * ratio);
  123. int newHeight = round(videoHeight * ratio);
  124. imageWidth = newWidth > 0 ? newWidth : imageWidth;
  125. imageHeight = newHeight > 0 ? newHeight : imageHeight;
  126. }
  127. _numberOfReceivedFrames = 0;
  128. NSAssert(!_shouldRejectFrames, @"Are we still running?");
  129. _effectiveThumbnailHeight = imageHeight;
  130. _effectiveThumbnailWidth = imageWidth;
  131. _data = calloc(1, imageWidth * imageHeight * 4);
  132. NSAssert(_data, @"Can't create data");
  133. NSAssert(!_mp, @"We are already fetching a thumbnail");
  134. _mp = libvlc_media_player_new([VLCLibrary sharedInstance]);
  135. libvlc_media_add_option([_media libVLCMediaDescriptor], "no-audio");
  136. libvlc_media_add_option([_media libVLCMediaDescriptor], "avcodec-threads=1");
  137. libvlc_media_add_option([_media libVLCMediaDescriptor], "avcodec-skip-frame=4");
  138. libvlc_media_add_option([_media libVLCMediaDescriptor], "avcodec-skip-idct=2");
  139. libvlc_media_player_set_media(_mp, [_media libVLCMediaDescriptor]);
  140. libvlc_video_set_format(_mp, "RGBA", imageWidth, imageHeight, 4 * imageWidth);
  141. libvlc_video_set_callbacks(_mp, lock, unlock, NULL, self);
  142. libvlc_media_player_play(_mp);
  143. libvlc_media_player_set_position(_mp, snapshotPosition);
  144. NSAssert(!_thumbnailingTimeoutTimer, @"We already have a timer around");
  145. _thumbnailingTimeoutTimer = [[NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(mediaThumbnailingTimedOut) userInfo:nil repeats:NO] retain];
  146. }
  147. - (void)mediaParsingTimedOut
  148. {
  149. VKLog(@"WARNING: media thumbnailer media parsing timed out");
  150. [_media removeObserver:self forKeyPath:@"parsed"];
  151. [self startFetchingThumbnail];
  152. }
  153. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
  154. {
  155. if (object == _media && [keyPath isEqualToString:@"parsed"]) {
  156. if ([_media isParsed]) {
  157. [_parsingTimeoutTimer invalidate];
  158. [_parsingTimeoutTimer release];
  159. _parsingTimeoutTimer = nil;
  160. [_media removeObserver:self forKeyPath:@"parsed"];
  161. [self startFetchingThumbnail];
  162. }
  163. return;
  164. }
  165. return [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
  166. }
  167. - (void)didFetchThumbnail
  168. {
  169. if (_shouldRejectFrames)
  170. return;
  171. // The video thread is blocking on us. Beware not to do too much work.
  172. _numberOfReceivedFrames++;
  173. // Make sure we are getting the right frame
  174. if (libvlc_media_player_get_position(_mp) < kSnapshotPosition / 2 &&
  175. // Arbitrary choice to work around broken files.
  176. libvlc_media_player_get_length(_mp) > 1000 &&
  177. _numberOfReceivedFrames < 10)
  178. return;
  179. NSAssert(_data, @"We have no data");
  180. CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
  181. const CGFloat width = _effectiveThumbnailWidth;
  182. const CGFloat height = _effectiveThumbnailHeight;
  183. const CGFloat pitch = 4 * width;
  184. CGContextRef bitmap = CGBitmapContextCreate(_data,
  185. width,
  186. height,
  187. 8,
  188. pitch,
  189. colorSpace,
  190. kCGImageAlphaNoneSkipLast);
  191. CGColorSpaceRelease(colorSpace);
  192. NSAssert(bitmap, @"Can't create bitmap");
  193. // Create the thumbnail image
  194. //NSAssert(!_thumbnail, @"We already have a thumbnail");
  195. if (_thumbnail)
  196. CGImageRelease(_thumbnail);
  197. _thumbnail = CGBitmapContextCreateImage(bitmap);
  198. // Put a new context there.
  199. CGContextRelease(bitmap);
  200. // Make sure we don't block the video thread now
  201. [self performSelector:@selector(notifyDelegate) withObject:nil afterDelay:0];
  202. }
  203. - (void)stopAsync
  204. {
  205. if (_mp) {
  206. libvlc_media_player_stop(_mp);
  207. libvlc_media_player_release(_mp);
  208. _mp = NULL;
  209. }
  210. // Now release data
  211. if (_data)
  212. free(_data);
  213. _data = NULL;
  214. _shouldRejectFrames = NO;
  215. }
  216. - (void)endThumbnailing
  217. {
  218. _shouldRejectFrames = YES;
  219. [_thumbnailingTimeoutTimer invalidate];
  220. [_thumbnailingTimeoutTimer release];
  221. _thumbnailingTimeoutTimer = nil;
  222. [self performSelectorInBackground:@selector(stopAsync) withObject:nil];
  223. [self autorelease]; // Balancing -fetchThumbnail
  224. }
  225. - (void)notifyDelegate
  226. {
  227. [self endThumbnailing];
  228. // Call delegate
  229. [_delegate mediaThumbnailer:self didFinishThumbnail:_thumbnail];
  230. }
  231. - (void)mediaThumbnailingTimedOut
  232. {
  233. VKLog(@"WARNING: media thumbnailer media thumbnailing timed out");
  234. [self endThumbnailing];
  235. // Call delegate
  236. [_delegate mediaThumbnailerDidTimeOut:self];
  237. }
  238. @end