VLCMediaThumbnailer.m 8.3 KB

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