AVAssetExportSession перестает развиваться
У меня проблема с AVAssetExportSession, где прогресс останавливается, но статус все еще говорит о том, что он экспортирует. Это на самом деле довольно редкое явление, оно работает безупречно около 99,99% времени, но я все равно хочу исправить проблему.
Итак, я запускаю экспорт:
exportSession = [[AVAssetExportSession alloc] initWithAsset:composition presetName:AVAssetExportPresetMediumQuality];
exportSession.videoComposition = videoComposition;
exportSession.outputFileType = @"com.apple.quicktime-movie";
exportSession.outputURL = outputURL;
[exportSession exportAsynchronouslyWithCompletionHandler:^{
...
}];
Затем выполните таймер, проверяющий ход:
AVAssetExportSessionStatus status = [exportSession status];
float progress = 0;
if (status == AVAssetExportSessionStatusExporting) {
progress = [exportSession progress];
} else if (status == AVAssetExportSessionStatusCompleted) {
progress = 1;
}
NSLog(@"%d %f", status, progress);
[delegate processor:self didProgress:progress];
И результат будет выглядеть следующим образом:
2012-05-23 14:28:59.494 **********[1899:707] 2 0.125991
2012-05-23 14:28:59.994 **********[1899:707] 2 0.185280
2012-05-23 14:29:00.494 **********[1899:707] 2 0.259393
2012-05-23 14:29:00.994 **********[1899:707] 2 0.326093
2012-05-23 14:29:01.494 **********[1899:707] 2 0.400206
2012-05-23 14:29:01.995 **********[1899:707] 2 0.481729
2012-05-23 14:29:02.495 **********[1899:707] 2 0.541019
2012-05-23 14:29:02.997 **********[1899:707] 2 0.622542
2012-05-23 14:29:03.493 **********[1899:707] 2 0.681832
2012-05-23 14:29:03.995 **********[1899:707] 2 0.763355
2012-05-23 14:29:04.494 **********[1899:707] 2 0.822645
2012-05-23 14:29:04.994 **********[1899:707] 2 0.880082
2012-05-23 14:29:05.493 **********[1899:707] 2 0.880082
2012-05-23 14:29:05.994 **********[1899:707] 2 0.880082
...
2012-05-23 14:43:22.994 **********[1899:707] 2 0.880082
2012-05-23 14:43:23.493 **********[1899:707] 2 0.880082
2012-05-23 14:43:23.994 **********[1899:707] 2 0.880082
2012-05-23 14:43:24.494 **********[1899:707] 2 0.880082
(Примечание: он не останавливается с одинаковым процентом каждый раз, его полностью случайным)
Как вы можете видеть из временных меток, для выполнения первых 88% потребовалось 5 секунд, а затем я пропустил еще 13 минут (полная обработка видео обычно занимает не более 10 секунд) без изменений в прогресс.
В настоящее время мой единственный вариант - проверить, не проделан ли прогресс за последние X секунд, и просто сообщите об этом пользователю и попробуйте еще раз.
У кого-нибудь есть идеи?
Ответы
Ответ 1
В моем случае эта проблема возникла из mp4s, которые были закодированы с необычными конфигурациями. Но в конечном итоге ошибка в коде Apple вызывает проблему.
В частности, я работал с mp4s на Facebook. Похоже, что когда facebook кодирует видеоролики, он уменьшает или сдвигает первый кадр видеодорожки. Изучение видео с помощью ffprobe показало:
"r_frame_rate": "722/25",
"avg_frame_rate": "722/25",
"time_base": "1/28880",
"start_pts": 1000,
"start_time": "0.034626",
0.034626 - это ровно время для кадра 2. Звуковая дорожка, однако, начиналась с 0.
К сожалению, API Apple неправильно сообщают об этом времени запуска для трека. После получения AVAssetTrack из AVAsset и проверки его timeRange, он сообщил о времени начала как 0. Возможно, я неправильно понимаю характер отслеживания дорожек и активов, но это похоже на ошибку.
Конечно, при построении видеороликов для сеанса экспорта я использовал неправильный интервал времени трека. Я подозреваю, что когда сессия экспорта ищет данные в треке в момент времени 0 и не находит ничего путающего, а затем, как мы видели, просто зависает и даже не сообщает об ошибке - еще одна ошибка. Даже создание композиции с помощью videoCompositionWithPropertiesOfAsset: не помогает.
Я еще не пытался использовать AVAssetReader и AVAssetWriter вместо AVAssetExportSession, поэтому я не знаю, встретите ли вы там ту же ситуацию. Пока у меня не будет больше времени, чтобы изучить, что я придумал этот хак в качестве решения:
Reset временной интервал, используемый для вставки дорожки и видеоинструкции для начала в 1 или 2 кадрах:
CMTimeRange videoTimeRange = videoTrack.timeRange;
videoTimeRange.start = CMTimeMake(1, ceil(videoTrack.nominalFrameRate));
Очевидно, что этот взлом применяется только к конкретной ситуации, когда время начала видео трека не равно 0. Я бы предположил, что в целом эта ошибка возникает из-за необычных кодировок мультимедиа, которые не ожидаются AVAssetExportSession и связанные с ним классы.
Ответ 2
Я нашел это!
Вам нужно установить правильные команды layerInstructions:
AVMutableCompositionTrack *track = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
AVMutableVideoCompositionLayerInstruction * layerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:track];
AVMutableVideoCompositionInstruction * MainInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
MainInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, allTime);
MainInstruction.layerInstructions = [NSArray arrayWithObject:layerInstruction];
В противном случае он просто застрял.
Ответ 3
Мне интересно, если это проблема с потоками - если таймер и экспортер попадают в разные потоки. (Руководство AVFoundation говорит, что экспортер не может работать в любом конкретном потоке.)
Вы пытались использовать наблюдение за ключом вместо таймера?
Пример Barebones:
// register for the notification
[exportSession addObserver:someProcessor forKeyPath:@"progress" options:NSKeyValueObservingOptionNew context:NULL];
Затем в вашем процессоре:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
// add some checks here
NSNumber *processNumber = [change objectForKey:NSKeyValueChangeNewKey];
float process = [processNumber floatValue];
[delegate processor:self didProgress:progress];
}
Ответ 4
У меня была та же проблема, и ни одно из других решений (например, установка layerInstructions) не помогло мне.
Я изменил порядок работы DispatchQueues, и проблема, похоже, устранилась. В частности, AVAssetExportSessions создавались для нескольких разных (одновременных) DispatchQueues с одним и тем же AVAsset. Исправление состояло в том, чтобы очередь, которая создала AVAsset, также (всегда) была очередью, которая создает AVAssetExportSessions. Я обнаружил, что вызов AVAssetExportSession.exportAsynchronously() из глобального DispatchQueue - это нормально - и необходимо, если вы хотите иметь таймер, который в конечном итоге истекает в случае сбоя.