VLCMediaThumbnailer.m 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  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. {
  27. void * _internalLibVLCInstance;
  28. }
  29. - (void)didFetchThumbnail;
  30. - (void)notifyDelegate;
  31. - (void)fetchThumbnail;
  32. - (void)startFetchingThumbnail;
  33. @property (readonly, assign) void *dataPointer;
  34. @property (readonly, assign) BOOL shouldRejectFrames;
  35. @end
  36. static void *lock(void *opaque, void **pixels)
  37. {
  38. VLCMediaThumbnailer *thumbnailer = opaque;
  39. *pixels = [thumbnailer dataPointer];
  40. assert(*pixels);
  41. return NULL;
  42. }
  43. static const size_t kDefaultImageWidth = 320;
  44. static const size_t kDefaultImageHeight = 240;
  45. static const float kSnapshotPosition = 0.3;
  46. static const long long kStandardStartTime = 150000;
  47. void unlock(void *opaque, void *picture, void *const *p_pixels)
  48. {
  49. VLCMediaThumbnailer *thumbnailer = opaque;
  50. assert(!picture);
  51. assert([thumbnailer dataPointer] == *p_pixels);
  52. // We may already have a thumbnail if we are receiving picture after the first one.
  53. // Just ignore.
  54. if ([thumbnailer thumbnail] || [thumbnailer shouldRejectFrames])
  55. return;
  56. [thumbnailer performSelectorOnMainThread:@selector(didFetchThumbnail) withObject:nil waitUntilDone:YES];
  57. }
  58. @implementation VLCMediaThumbnailer
  59. @synthesize media=_media;
  60. @synthesize delegate=_delegate;
  61. @synthesize thumbnail=_thumbnail;
  62. @synthesize dataPointer=_data;
  63. @synthesize thumbnailWidth=_thumbnailWidth;
  64. @synthesize thumbnailHeight=_thumbnailHeight;
  65. @synthesize snapshotPosition=_snapshotPosition;
  66. @synthesize shouldRejectFrames=_shouldRejectFrames;
  67. + (VLCMediaThumbnailer *)thumbnailerWithMedia:(VLCMedia *)media andDelegate:(id<VLCMediaThumbnailerDelegate>)delegate
  68. {
  69. id obj = [[[self class] alloc] init];
  70. [obj setMedia:media];
  71. [obj setDelegate:delegate];
  72. [obj setLibVLCinstance:[VLCLibrary sharedInstance]];
  73. return [obj autorelease];
  74. }
  75. + (VLCMediaThumbnailer *)thumbnailerWithMedia:(VLCMedia *)media delegate:(id<VLCMediaThumbnailerDelegate>)delegate andVLCLibrary:(VLCLibrary *)library
  76. {
  77. id obj = [[[self class] alloc] init];
  78. [obj setMedia:media];
  79. [obj setDelegate:delegate];
  80. if (library)
  81. [obj setLibVLCinstance:library.instance];
  82. else
  83. [obj setLibVLCinstance:[VLCLibrary sharedInstance]];
  84. return [obj autorelease];
  85. }
  86. - (void)dealloc
  87. {
  88. NSAssert(!_thumbnailingTimeoutTimer, @"Timer not released");
  89. NSAssert(!_parsingTimeoutTimer, @"Timer not released");
  90. NSAssert(!_data, @"Data not released");
  91. NSAssert(!_mp, @"Not properly retained");
  92. if (_thumbnail)
  93. CGImageRelease(_thumbnail);
  94. [_media release];
  95. if (_internalLibVLCInstance)
  96. libvlc_release(_internalLibVLCInstance);
  97. [super dealloc];
  98. }
  99. - (void)setLibVLCinstance:(void *)libVLCinstance
  100. {
  101. _internalLibVLCInstance = libVLCinstance;
  102. libvlc_retain(_internalLibVLCInstance);
  103. }
  104. - (void *)libVLCinstance
  105. {
  106. return _internalLibVLCInstance;
  107. }
  108. - (void)fetchThumbnail
  109. {
  110. NSAssert(!_data, @"We are already fetching a thumbnail");
  111. [self retain]; // Balanced in -notifyDelegate
  112. if (![_media isParsed]) {
  113. [_media addObserver:self forKeyPath:@"parsed" options:0 context:NULL];
  114. [_media synchronousParse];
  115. NSAssert(!_parsingTimeoutTimer, @"We already have a timer around");
  116. _parsingTimeoutTimer = [[NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(mediaParsingTimedOut) userInfo:nil repeats:NO] retain];
  117. return;
  118. }
  119. [self startFetchingThumbnail];
  120. }
  121. - (void)startFetchingThumbnail
  122. {
  123. NSArray *tracks = [_media tracksInformation];
  124. // Find the video track
  125. NSDictionary *videoTrack = nil;
  126. for (NSDictionary *track in tracks) {
  127. NSString *type = track[VLCMediaTracksInformationType];
  128. if ([type isEqualToString:VLCMediaTracksInformationTypeVideo]) {
  129. videoTrack = track;
  130. break;
  131. }
  132. }
  133. unsigned imageWidth = _thumbnailWidth > 0 ? _thumbnailWidth : kDefaultImageWidth;
  134. unsigned imageHeight = _thumbnailHeight > 0 ? _thumbnailHeight : kDefaultImageHeight;
  135. float snapshotPosition = _snapshotPosition > 0 ? _snapshotPosition : kSnapshotPosition;
  136. if (!videoTrack) {
  137. VKLog(@"WARNING: Can't find video track info, skipping file");
  138. [_parsingTimeoutTimer invalidate];
  139. [_parsingTimeoutTimer release];
  140. _parsingTimeoutTimer = nil;
  141. [self mediaThumbnailingTimedOut];
  142. return;
  143. } else {
  144. int videoHeight = [videoTrack[VLCMediaTracksInformationVideoHeight] intValue];
  145. int videoWidth = [videoTrack[VLCMediaTracksInformationVideoWidth] intValue];
  146. // Constraining to the aspect ratio of the video.
  147. double ratio;
  148. if ((double)imageWidth / imageHeight < (double)videoWidth / videoHeight)
  149. ratio = (double)imageHeight / videoHeight;
  150. else
  151. ratio = (double)imageWidth / videoWidth;
  152. int newWidth = round(videoWidth * ratio);
  153. int newHeight = round(videoHeight * ratio);
  154. imageWidth = newWidth > 0 ? newWidth : imageWidth;
  155. imageHeight = newHeight > 0 ? newHeight : imageHeight;
  156. }
  157. _numberOfReceivedFrames = 0;
  158. NSAssert(!_shouldRejectFrames, @"Are we still running?");
  159. _effectiveThumbnailHeight = imageHeight;
  160. _effectiveThumbnailWidth = imageWidth;
  161. _data = calloc(1, imageWidth * imageHeight * 4);
  162. NSAssert(_data, @"Can't create data");
  163. NSAssert(!_mp, @"We are already fetching a thumbnail");
  164. _mp = libvlc_media_player_new(self.libVLCinstance);
  165. libvlc_media_add_option([_media libVLCMediaDescriptor], "no-audio");
  166. libvlc_media_player_set_media(_mp, [_media libVLCMediaDescriptor]);
  167. libvlc_video_set_format(_mp, "RGBA", imageWidth, imageHeight, 4 * imageWidth);
  168. libvlc_video_set_callbacks(_mp, lock, unlock, NULL, self);
  169. if (snapshotPosition == kSnapshotPosition) {
  170. int length = _media.length.intValue;
  171. if (length < kStandardStartTime) {
  172. VKLog(@"short file detected");
  173. if (length > 1000) {
  174. VKLog(@"attempting seek to %is", (length * 25 / 100000));
  175. libvlc_media_add_option([_media libVLCMediaDescriptor], [[NSString stringWithFormat:@"start-time=%i", (length * 25 / 100000)] UTF8String]);
  176. }
  177. } else
  178. libvlc_media_add_option([_media libVLCMediaDescriptor], [[NSString stringWithFormat:@"start-time=%lli", (kStandardStartTime / 1000)] UTF8String]);
  179. }
  180. libvlc_media_player_play(_mp);
  181. NSAssert(!_thumbnailingTimeoutTimer, @"We already have a timer around");
  182. _thumbnailingTimeoutTimer = [[NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(mediaThumbnailingTimedOut) userInfo:nil repeats:NO] retain];
  183. }
  184. - (void)mediaParsingTimedOut
  185. {
  186. VKLog(@"WARNING: media thumbnailer media parsing timed out");
  187. [_media removeObserver:self forKeyPath:@"parsed"];
  188. [self startFetchingThumbnail];
  189. }
  190. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
  191. {
  192. if (object == _media && [keyPath isEqualToString:@"parsed"]) {
  193. if ([_media isParsed]) {
  194. [_parsingTimeoutTimer invalidate];
  195. [_parsingTimeoutTimer release];
  196. _parsingTimeoutTimer = nil;
  197. [_media removeObserver:self forKeyPath:@"parsed"];
  198. [self startFetchingThumbnail];
  199. }
  200. return;
  201. }
  202. return [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
  203. }
  204. - (void)didFetchThumbnail
  205. {
  206. if (_shouldRejectFrames)
  207. return;
  208. // The video thread is blocking on us. Beware not to do too much work.
  209. _numberOfReceivedFrames++;
  210. float position = libvlc_media_player_get_position(_mp);
  211. long long length = libvlc_media_player_get_length(_mp);
  212. // Make sure we are getting the right frame
  213. if (position < self.snapshotPosition && _numberOfReceivedFrames < 2) {
  214. libvlc_media_player_set_position(_mp, self.snapshotPosition);
  215. return;
  216. }
  217. if ((length < kStandardStartTime * 2 && _numberOfReceivedFrames < 5) && self.snapshotPosition == kSnapshotPosition) {
  218. libvlc_media_player_set_position(_mp, kSnapshotPosition);
  219. return;
  220. }
  221. if ((position <= 0.05 && _numberOfReceivedFrames < 8) && length > 1000) {
  222. // Arbitrary choice to work around broken files.
  223. libvlc_media_player_set_position(_mp, kSnapshotPosition);
  224. return;
  225. }
  226. // it isn't always best what comes first
  227. if (_numberOfReceivedFrames < 4)
  228. return;
  229. NSAssert(_data, @"We have no data");
  230. CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
  231. const CGFloat width = _effectiveThumbnailWidth;
  232. const CGFloat height = _effectiveThumbnailHeight;
  233. const CGFloat pitch = 4 * width;
  234. CGContextRef bitmap = CGBitmapContextCreate(_data,
  235. width,
  236. height,
  237. 8,
  238. pitch,
  239. colorSpace,
  240. kCGImageAlphaNoneSkipLast);
  241. CGColorSpaceRelease(colorSpace);
  242. NSAssert(bitmap, @"Can't create bitmap");
  243. // Create the thumbnail image
  244. //NSAssert(!_thumbnail, @"We already have a thumbnail");
  245. if (_thumbnail)
  246. CGImageRelease(_thumbnail);
  247. _thumbnail = CGBitmapContextCreateImage(bitmap);
  248. // Put a new context there.
  249. CGContextRelease(bitmap);
  250. // Make sure we don't block the video thread now
  251. [self performSelector:@selector(notifyDelegate) withObject:nil afterDelay:0];
  252. }
  253. - (void)stopAsync
  254. {
  255. if (_mp) {
  256. libvlc_media_player_stop(_mp);
  257. libvlc_media_player_release(_mp);
  258. _mp = NULL;
  259. }
  260. // Now release data
  261. if (_data)
  262. free(_data);
  263. _data = NULL;
  264. _shouldRejectFrames = NO;
  265. }
  266. - (void)endThumbnailing
  267. {
  268. _shouldRejectFrames = YES;
  269. [_thumbnailingTimeoutTimer invalidate];
  270. [_thumbnailingTimeoutTimer release];
  271. _thumbnailingTimeoutTimer = nil;
  272. [self performSelectorInBackground:@selector(stopAsync) withObject:nil];
  273. [self autorelease]; // Balancing -fetchThumbnail
  274. }
  275. - (void)notifyDelegate
  276. {
  277. [self endThumbnailing];
  278. // Call delegate
  279. [_delegate mediaThumbnailer:self didFinishThumbnail:_thumbnail];
  280. }
  281. - (void)mediaThumbnailingTimedOut
  282. {
  283. VKLog(@"WARNING: media thumbnailer media thumbnailing timed out");
  284. [self endThumbnailing];
  285. // Call delegate
  286. [_delegate mediaThumbnailerDidTimeOut:self];
  287. }
  288. @end