Ошибка вращения AVAssetExportSession, когда видео с камеры

Я пытаюсь преобразовать видео .mov в .mp4 и заодно исправить ориентацию. Код, который я использую ниже, отлично работает при записи видео с помощью UIImagePickerController, однако, если видео выбрано из фотопленки, я получаю эту ошибку, и я не понимаю, почему:

Ошибка экспорта: Операция остановлена: Ошибка Домен = Код AVFoundationErrorDomain = -11841 «Операция остановлена» UserInfo = 0x1815ca50 {NSLocalizedDescription = Операция остановлена, NSLocalizedFailureReason = Не удалось создать видео.}

Я попытался сначала сохранить видео в другой файл, но это не помогло.

Вот код, который я использую для конвертации видео:

- (void)convertVideoToLowQuailtyAndFixRotationWithInputURL:(NSURL*)inputURL handler:(void (^)(NSURL *outURL))handler
{
    if ([[inputURL pathExtension] isEqualToString:@"MOV"])
    {
        NSURL *outputURL = [inputURL URLByDeletingPathExtension];
        outputURL = [outputURL URLByAppendingPathExtension:@"mp4"];

        AVURLAsset *avAsset = [AVURLAsset URLAssetWithURL:inputURL options:nil];

        AVAssetTrack *sourceVideoTrack = [[avAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
        AVAssetTrack *sourceAudioTrack = [[avAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0];

        AVMutableComposition* composition = [AVMutableComposition composition];

        AVMutableCompositionTrack *compositionVideoTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo
                                                                                    preferredTrackID:kCMPersistentTrackID_Invalid];
        [compositionVideoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, avAsset.duration)
                                       ofTrack:sourceVideoTrack
                                        atTime:kCMTimeZero error:nil];
        [compositionVideoTrack setPreferredTransform:sourceVideoTrack.preferredTransform];

        AVMutableCompositionTrack *compositionAudioTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio
                                                                                    preferredTrackID:kCMPersistentTrackID_Invalid];
        [compositionAudioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, avAsset.duration)
                                       ofTrack:sourceAudioTrack
                                        atTime:kCMTimeZero error:nil];

        AVMutableVideoComposition *videoComposition = [self getVideoComposition:avAsset];

        NSArray *compatiblePresets = [AVAssetExportSession exportPresetsCompatibleWithAsset:avAsset];
        if ([compatiblePresets containsObject:AVAssetExportPresetMediumQuality])
        {
            AVAssetExportSession *exportSession = [[AVAssetExportSession alloc]initWithAsset:composition presetName:AVAssetExportPresetMediumQuality];
            exportSession.outputURL = outputURL;
            exportSession.outputFileType = AVFileTypeMPEG4;
            exportSession.shouldOptimizeForNetworkUse = YES;
            exportSession.videoComposition = videoComposition;
            [exportSession exportAsynchronouslyWithCompletionHandler:^{

                switch ([exportSession status])
                {
                    case AVAssetExportSessionStatusFailed:
                        NSLog(@"Export failed: %@ : %@", [[exportSession error] localizedDescription], [exportSession error]);
                        handler(nil);

                        break;
                    case AVAssetExportSessionStatusCancelled:

                        NSLog(@"Export canceled");
                        handler(nil);

                        break;
                    default:

                        handler(outputURL);

                        break;

                }
            }];
        }

    } else {
        handler(inputURL);
    }
}

- (AVMutableVideoComposition *)getVideoComposition:(AVAsset *)asset
{
    AVAssetTrack *videoTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
    AVMutableComposition *composition = [AVMutableComposition composition];
    AVMutableVideoComposition *videoComposition = [AVMutableVideoComposition videoComposition];
    CGSize videoSize = videoTrack.naturalSize;
    BOOL isPortrait_ = [self isVideoPortrait:asset];
    if(isPortrait_) {
//        NSLog(@"video is portrait ");
        videoSize = CGSizeMake(videoSize.height, videoSize.width);
    }
    composition.naturalSize     = videoSize;
    videoComposition.renderSize = videoSize;
    videoComposition.frameDuration = CMTimeMakeWithSeconds( 1 / videoTrack.nominalFrameRate, 600);

    AVMutableCompositionTrack *compositionVideoTrack;
    compositionVideoTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
    [compositionVideoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, asset.duration) ofTrack:videoTrack atTime:kCMTimeZero error:nil];
    AVMutableVideoCompositionLayerInstruction *layerInst;
    layerInst = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoTrack];
    [layerInst setTransform:videoTrack.preferredTransform atTime:kCMTimeZero];
    AVMutableVideoCompositionInstruction *inst = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
    inst.timeRange = CMTimeRangeMake(kCMTimeZero, asset.duration);
    inst.layerInstructions = [NSArray arrayWithObject:layerInst];
    videoComposition.instructions = [NSArray arrayWithObject:inst];
    return videoComposition;
}

person Darren    schedule 26.11.2013    source источник


Ответы (3)


Константа ошибки AVFoundation -11841 означает, что у вас недопустимая композиция видео. См. эту ссылку, если вам нужна дополнительная информация о константах ошибок: https://developer.apple.com/library/ios/documentation/AVFoundation/Reference/AVFoundation_ErrorConstants/Reference/reference.html

Хотя у меня сразу не всплывает никаких серьезных ошибок, я могу предложить следующие способы сузить источник вашей проблемы.

Во-первых, вместо передачи nil для параметра error в этих вызовах:

[compositionVideoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, avAsset.duration)
                                       ofTrack:sourceVideoTrack
                                        atTime:kCMTimeZero error:nil];

создайте объект NSError и передайте ссылку на него следующим образом:

NSError *error = nil;
[compositionVideoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, avAsset.duration)
                                       ofTrack:sourceVideoTrack
                                        atTime:kCMTimeZero error:&error];

Изучите ошибку, чтобы убедиться, что ваши видео- и аудиодорожки правильно вставлены в дорожку композиции. Ошибка должна быть nil, если все пойдет хорошо.

if(error)
    NSLog(@"Insertion error: %@", error);

Вы также можете проверить свойства composable, exportable и hasProtectedContent вашего AVAsset. Если это не ДА, ДА и НЕТ соответственно, у вас могут возникнуть проблемы при создании нового видеофайла.

Иногда я сталкивался с проблемой, когда создание временного диапазона для звуковой дорожки не соответствовало временной шкале 600 при использовании в композиции с видеодорожкой. Вы можете создать новый CMTime для продолжительности (avAsset.duration) в

CMTimeRangeMake(kCMTimeZero, avAsset.duration) 

только для вставки звуковой дорожки. В новом CMTime используйте шкалу времени 44100 (или любую другую частоту дискретизации звуковой дорожки). То же самое касается вашего videoComposition.frameDuration. В зависимости от nominalFrameRate вашей видеодорожки ваше время может быть представлено неправильно с временной шкалой 600.

Наконец, Apple предлагает полезный инструмент для отладки видеокомпозиций:

https://developer.apple.com/library/mac/samplecode/AVCompositionDebugViewer/Introduction/Intro.html

Это дает визуальное представление о вашей композиции, и вы можете увидеть, где все выглядит не так, как должно.

person jlw    schedule 02.12.2013
comment
Спасибо за ваш очень подробный ответ. Я до сих пор не могу заставить его работать :( composable, exportable и hasProtectedContent все 4, как вы говорите. Из вашего первого предложения нет ошибок. Я попытался изменить звуковую дорожку, чтобы использовать 44 100. Если я закомментирую exportSession.videoComposition = videoComposition;, все работает нормально однако видео не переворачивается.Так что проблема должна быть где-то там.Спасибо - person Darren; 03.12.2013
comment
Я заметил, что вы создаете AVMutableComposition в -(AVMutableVideoComposition *)getVideoComposition и создаете AVMutableCompositionTrack из этого. Однако в - (void)convertVideoToLowQuailtyAndFixRotationWithInputURL:handler: у вас есть другой AVMutableComposition, который вы передаете в AVExportSession. Попробуйте передать эту композицию в getVideoComposition, чтобы создать из нее AVMutableCompositionTracks, чтобы остался только один. - person jlw; 03.12.2013
comment
Я только что попытался переместить AVMutableVideoComposition с AVMutableVideoCompositionLayerInstruction в основной метод преобразования, поэтому у него есть только 1 AVMutableComposition, но все те же результаты. При выборе видео из рулона камеры он сжимает его перед передачей, не уверен, что это имеет значение. - person Darren; 04.12.2013
comment
Сжатие не должно иметь значения, если ресурс все еще можно экспортировать и т. д. Вы удалили лишние дорожки? В вашей видеокомпозиции должно быть только два AVMutableVideoCompositionTracks. Один для аудио, другой для видео. Вы должны создать AVMutableVideoCompositionLayerInstruction на основе sourceVideoTrack и попытаться установить преобразование только в инструкции. Показывает ли класс AVCompositionDebugViewer то, что вы ожидаете, когда используете его в своем проекте? - person jlw; 04.12.2013
comment
Спасибо за вашу помощь. Я обнаружил, что это прекрасно работает stackoverflow.com/questions/19660250/ Не знаю, почему, но я доволен этим. Я награжу тебя наградой за всю твою помощь. Мой следующий шаг — установить переменный битрейт, чтобы размер файла не превышал установленного предела. Если у вас есть подсказки? - person Darren; 04.12.2013
comment
Я признателен за это! AVAssetExportSession дает вам ограниченное количество пресетов, влияющих на качество и размер файла. Вы можете заглянуть в AVAssetWriterAVAssetWriterInput/ AVAssetReaderTrackOutput), где вы можете указать средний битрейт и т. д. Настройки, которые вы можете использовать, перечислены в developer.apple.com/library/iOS/documentation/AVFoundation/ в настройках видео. - person jlw; 04.12.2013
comment
Я обнаружил, что ошибка будет assets.during.timeScale == assets.videotrack.naturalTimeScale . Успех с активом.during.timeScale == assets.audiotrack.naturalTimeScale - person zszen; 21.08.2017

Вам обязательно следует использовать метод isValidForAsset:timeRange:validationDelegate: из AVVideoCompostion, он диагностирует любую проблему с композицией вашего видео. У меня была та же проблема, и решение для меня состояло в том, чтобы создать layerInstruction с AVMutableCompositionTrack вместо исходной дорожки:

layerInst = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:compositionVideoTrack];
person nikos974    schedule 18.02.2015
comment
Вы, сэр, чемпион. Застрял на этом в течение нескольких часов, и это было исправлением. - person Kacy; 01.01.2021

Попробуйте прокомментировать строку ниже и запустить свой проект

exportSession.videoComposition = videoComposition;
person Amit Kumar    schedule 08.01.2015
comment
Это удалит любые преобразования, обрезки и т. д., добавленные в изменяемую видеокомпозицию, поэтому в этом случае бесполезно. - person Luke Smith; 11.05.2016
comment
Это сработало и для меня. Хотя мне интересно, почему. Кто-нибудь знает? - person Anton Holmberg; 18.10.2017