AVAssetImageGenerator devuelve a veces la misma image de 2 frameworks sucesivos

Actualmente estoy extrayendo cada fotogtwig de un video con AVAssetImageGenerator , pero a veces me devuelve sucesivamente 2 veces casi la misma image (no tienen el mismo "time de fotogtwig"). Lo curioso es que siempre sucede (en mi video de testing) cada 5 cuadros.

Aquí y aquí están las dos imágenes (ábrase cada una en una nueva pestaña y luego cambie las tabs para ver las diferencias).

Aquí está mi código:

 //setting up generator & compositor self.generator = [AVAssetImageGenerator assetImageGeneratorWithAsset:asset]; generator.appliesPrefernetworkingTrackTransform = YES; self.composition = [AVVideoComposition videoCompositionWithPropertiesOfAsset:asset]; NSTimeInterval duration = CMTimeGetSeconds(asset.duration); NSTimeInterval frameDuration = CMTimeGetSeconds(composition.frameDuration); CGFloat totalFrames = round(duration/frameDuration); NSMutableArray * times = [NSMutableArray array]; for (int i=0; i<totalFrames; i++) { NSValue * time = [NSValue valueWithCMTime:CMTimeMakeWithSeconds(i*frameDuration, composition.frameDuration.timescale)]; [times addObject:time]; } AVAssetImageGeneratorCompletionHandler handler = ^(CMTime requestedTime, CGImageRef im, CMTime actualTime, AVAssetImageGeneratorResult result, NSError *error){ // If actualTime is not equal to requestedTime image is ignonetworking if(CMTimeCompare(actualTime, requestedTime) == 0) { if (result == AVAssetImageGeneratorSucceeded) { NSLog(@"%.02f %.02f", CMTimeGetSeconds(requestedTime), CMTimeGetSeconds(actualTime)); // Each log have differents actualTimes. // frame extraction is here... } } }; generator.requestedTimeToleranceBefore = kCMTimeZero; generator.requestedTimeToleranceAfter = kCMTimeZero; [generator generateCGImagesAsynchronouslyForTimes:times completionHandler:handler]; 

¿Alguna idea de dónde podría venir?

Consulte las siguientes properties de AVAssetImageGenerator. Debe establecer kCMTimeZero para ambas properties para get los cuadros exactos.

 /* The actual time of the generated images will be within the range [requestedTime-toleranceBefore, requestedTime+toleranceAfter] and may differ from the requested time for efficiency. Pass kCMTimeZero for both toleranceBefore and toleranceAfter to request frame-accurate image generation; this may incur additional decoding delay. Default is kCMTimePositiveInfinity. */ @property (nonatomic) CMTime requestedTimeToleranceBefore NS_AVAILABLE(10_7, 5_0); @property (nonatomic) CMTime requestedTimeToleranceAfter NS_AVAILABLE(10_7, 5_0); 

Antes de configurar kCMTimeZero para ambas properties, obtuve algunas mismas imágenes para el time de request diferente que experimentó. Solo testing el siguiente código.

 self.imageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:myAsset]; self.imageGenerator.requestedTimeToleranceBefore = kCMTimeZero; self.imageGenerator.requestedTimeToleranceAfter = kCMTimeZero; 

Tuve el mismo problema contigo, pero mucho más evidente, la duplicación sucedía cuando el intervalo entre los dos fotogtwigs era inferior a 1.0 segundo y me di count de que dependía de la escala de time que estaba usando para generar valores de CMTime.

antes de

 CMTime requestTime = CMTimeMakeWithSeconds(imageTime, 1); 

Después

 CMTime requestTime = CMTimeMakeWithSeconds(imageTime, playerItem.asset.duration.timescale); 

… y Boom, no más duplicación 🙂

Entonces, tal vez puedas tratar de boost, duplicar quizás la escala de time, usando tu código:

 NSValue * time = [NSValue valueWithCMTime:CMTimeMakeWithSeconds(i*frameDuration, composition.frameDuration.timescale*2)]; // *2 at the end 

Para futuras references, aquí está mi código:

  playerItem = [AVPlayerItem playerItemWithURL:item.movieUrl]; imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:playerItem.asset]; imageGenerator.requestedTimeToleranceAfter = kCMTimeZero; imageGenerator.requestedTimeToleranceBefore = kCMTimeZero; float duration = item.duration; float interval = item.interval; NSLog(@"\nItem info:\n%f \n%f", duration,interval); NSString *srcPath = nil; NSString *zipPath = nil; srcPath = [item.path stringByAppendingPathComponent:@"info.json"]; zipPath = [NSString stringWithFormat:@"/%@/info.json",galleryID]; [zip addFileToZip:srcPath newname:zipPath level:0]; NSTimeInterval frameNum = item.duration / item.interval; for (int i=0; i<=frameNum; i++) { NSArray* cachePathArray = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); NSString* cachePath = [cachePathArray lastObject]; srcPath = [cachePath stringByAppendingPathComponent:@"export-tmp.jpg"]; zipPath = [NSString stringWithFormat:@"/%@/%d.jpg",galleryID,i]; float imageTime = ( i * interval ); NSError *error = nil; CMTime requestTime = CMTimeMakeWithSeconds(imageTime, playerItem.asset.duration.timescale); CMTime actualTime; CGImageRef imageRef = [imageGenerator copyCGImageAtTime:requestTime actualTime:&actualTime error:&error]; if (error == nil) { float req = ((float)requestTime.value/requestTime.timescale); float real = ((float)actualTime.value/actualTime.timescale); float diff = fabsf(req-real); NSLog(@"copyCGImageAtTime: %.2f, %.2f, %f",req,real,diff); } else { NSLog(@"copyCGImageAtTime: error: %@",error.localizedDescription); } // consider using CGImageDestination -> http://stackoverflow.com/questions/1320988/saving-cgimageref-to-a-png-file UIImage *img = [UIImage imageWithCGImage:imageRef]; CGImageRelease(imageRef); // CGImageRef won't be released by ARC [UIImageJPEGRepresentation(img, 100) writeToFile:srcPath atomically:YES]; if (srcPath != nil && zipPath!= nil) { [zip addFileToZip:srcPath newname:zipPath level:0]; // 0 = no compression. everything is a jpg image unlink([srcPath UTF8String]); } 

Estaba usando una forma ligeramente diferente para calcular la request de CMTime, y parecía funcionar. Aquí está el código (suponiendo iOS):

 -(void)extractImagesFromMovie { // set the asset NSString* path = [[NSBundle mainBundle] pathForResource:@"myMovie" ofType:@"MOV"]; NSURL* movURL = [NSURL fileURLWithPath:path]; NSMutableDictionary* myDict = [NSMutableDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES] , AVURLAssetPreferPreciseDurationAndTimingKey , [NSNumber numberWithInt:0], AVURLAssetReferenceRestrictionsKey, nil]; AVURLAsset* movie = [[AVURLAsset alloc] initWithURL:movURL options:myDict]; // set the generator AVAssetImageGenerator* generator = [[AVAssetImageGenerator assetImageGeneratorWithAsset:movie] retain]; generator.requestedTimeToleranceBefore = kCMTimeZero; generator.requestedTimeToleranceAfter = kCMTimeZero; // look for the video track AVAssetTrack* videoTrack; bool foundTrack = NO; for (AVAssetTrack* track in movie.tracks) { if ([track.mediaType isEqualToString:@"vide"]) { if (foundTrack) {NSLog (@"Error - - - more than one video tracks"); return(-1);} else { videoTrack = track; foundTrack = YES; } } } if (foundTrack == NO) {NSLog (@"Error - - No Video Tracks at all"); return(-1);} // set the number of frames in the movie int frameRate = videoTrack.nominalFrameRate; float value = movie.duration.value; float timeScale = movie.duration.timescale; float totalSeconds = value / timeScale; int totalFrames = totalSeconds * frameRate; NSLog (@"total frames %d", totalFrames); int timeValuePerFrame = movie.duration.timescale / frameRate; NSMutableArray* allFrames = [[NSMutableArray new] retain]; // get each frame for (int k=0; k< totalFrames; k++) { int timeValue = timeValuePerFrame * k; CMTime frameTime; frameTime.value = timeValue; frameTime.timescale = movie.duration.timescale; frameTime.flags = movie.duration.flags; frameTime.epoch = movie.duration.epoch; CMTime gotTime; CGImageRef myRef = [generator copyCGImageAtTime:frameTime actualTime:&gotTime error:nil]; [allFrames addObject:[UIImage imageWithCGImage:myRef]]; if (gotTime.value != frameTime.value) NSLog (@"requested %lld got %lld for k %d", frameTime.value, gotTime.value, k) } NSLog (@"got %d images in the array", [allFrames count]); // do something with images here... } `