从相机胶卷中导入的视频,AVAssetExportSession在旋转时出现错误

7
我正在尝试将.mov视频转换为.mp4,并同时纠正方向。当我使用UIImagePickerController录制视频时,我使用以下代码可以很好地工作,但是如果从相机胶卷中选择视频,则会出现以下错误,我不知道原因所在:

导出失败:操作停止:错误 Domain=AVFoundationErrorDomain Code=-11841 "Operation Stopped" UserInfo=0x1815ca50 {NSLocalizedDescription=Operation Stopped, NSLocalizedFailureReason=The video could not be composed.}

我已经尝试先将视频保存到另一个文件中,但没有任何区别。

以下是我用于转换视频的代码:

- (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;
}
3个回答

13

AVFoundation错误常量-11841表示您具有无效的视频合成。如果您想了解更多关于错误常量的信息,请参考以下链接:https://developer.apple.com/library/ios/documentation/AVFoundation/Reference/AVFoundation_ErrorConstants/Reference/reference.html

虽然一开始没有出现重大错误,但我可以建议以下方法来缩小您问题的来源:

首先,在这些调用中不要将error参数传递为nil

[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);

你可能还需要检查 AVAsset 的 composableexportablehasProtectedContent 属性。如果它们分别不是 YES、YES 和 NO,则在创建新的视频文件时可能会出现问题。

偶尔我会遇到这样的问题,即在将音频轨道与视频轨道合成时,为音频轨道创建时间范围时不喜欢 600 时间刻度。你可能想要为持续时间(avAsset.duration)创建一个新的 CMTime。

CMTimeRangeMake(kCMTimeZero, avAsset.duration) 

只用于插入音轨。在新的CMTime中,使用44100的时间刻度(或者与音频轨道采样率相同的时间刻度)。对于 videoComposition.frameDuration 也是如此。根据您的视频轨道的 nominalFrameRate ,您的时间可能不能正确地表示为600个时间刻度。

最后,苹果提供了一个有用的工具来调试视频组合:

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

它会以可视化的方式呈现您的组合,您可以看到哪些地方不像应该的那样。


非常感谢您详细的回复。但我仍然无法让它工作 :( composable, exportablehasProtectedContent 都是您所说的。您的第一个建议没有返回任何错误。我尝试将音频轨道更改为使用 44,100。 如果我注释掉 exportSession.videoComposition = videoComposition;,它可以正常工作,但视频不会旋转。因此问题必须出在那里。谢谢。 - Darren
我注意到你在 -(AVMutableVideoComposition *)getVideoComposition 中创建了一个 AVMutableComposition,并从中创建了 AVMutableCompositionTrack。然而,在 - (void)convertVideoToLowQuailtyAndFixRotationWithInputURL:handler: 中,你有一个 不同的 AVMutableComposition,你正在将其传递给 AVExportSession。尝试将此组合传递给 getVideoComposition 以从中创建 AVMutableCompositionTracks,这样就只有一个组合了。 - jlw
我刚试过将 AVMutableVideoCompositionAVMutableVideoCompositionLayerInstruction 向上移动到主要的转换方法中,这样就只有一个 AVMutableComposition,但结果仍然相同。从照片库选择视频时,在传递之前会对其进行压缩,不确定是否会有影响。 - Darren
如果资产仍然可以导出,压缩不应该有任何影响。你删除了额外的轨道吗?在你的videoComposition中,你应该只有两个AVMutableVideoCompositionTracks,一个用于音频,一个用于视频。你应该基于sourceVideoTrack创建AVMutableVideoCompositionLayerInstruction,并尝试仅在指令上设置变换。当你在项目中使用AVCompositionDebugViewer类时,它是否显示你期望的内容? - jlw
1
我很感激!AVAssetExportSession提供了有限的预设,影响质量和文件大小。你可能想要研究一下 AVAssetWriter(以及AVAssetWriterInput/AVAssetReaderTrackOutput),在那里你可以指定平均比特率等设置。你可以使用的设置列在https://developer.apple.com/library/iOS/documentation/AVFoundation/Reference/AVFoundation_Constants/Reference/reference.html 的视频设置中。 - jlw
显示剩余2条评论

3

您应该绝对使用AVVideoComposition的isValidForAsset:timeRange:validationDelegate:方法,它将诊断您的视频合成中的任何问题。我曾经遇到过同样的问题,对我来说解决方案是使用AVMutableCompositionTrack创建layerInstruction而不是原始轨道:

layerInst = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:compositionVideoTrack];

您真是个冠军。我被这个问题困扰了好几个小时,而这就是解决方法。 - Kacy

2
尝试注释掉下面的代码并运行您的项目。
exportSession.videoComposition = videoComposition;

3
这将会清除掉添加到可变视频合成中的任何转换、裁剪等内容,所以在这种情况下没有用处。 - Luke Smith
这对我也起作用了。但我很好奇为什么会这样。有人知道吗? - Anton Holmberg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接