【问题标题】:iOS - AVAssestExportSession can only export maximum 8 tracks after playing with AVPlayeriOS - AVAssestExportSession 使用 AVPlayer 播放后最多只能导出 8 首曲目
【发布时间】:2016-05-30 06:03:18
【问题描述】:

我正在尝试循环播放录制视频的一些片段并将它们合并到一个视频中。 我已经成功地合并和导出了多达 16 首曲目的作品。但是当我尝试在合并前使用AVPlayer 播放乐曲时,我最多只能导出 8 首曲目。

首先,我创建AVCompositionAVVideoComposition

    +(void)previewUserClipDanceWithAudio:(NSURL*)videoURL audioURL:(NSURL*)audioFile loop:(NSArray*)loopTime slowMotion:(NSArray*)slowFactor showInViewController:(UIViewController*)viewController completion:(void(^)(BOOL success, AVVideoComposition* videoComposition, AVComposition* composition))completion{

AVMutableComposition* mixComposition = [[AVMutableComposition alloc] init];
NSMutableArray *arrayInstruction = [[NSMutableArray alloc] init];
AVMutableVideoCompositionInstruction *videoCompositionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];

AVURLAsset  *audioAsset = [[AVURLAsset alloc]initWithURL:audioFile options:nil];
//NSLog(@"audio File %@",audioFile);

CMTime duration = kCMTimeZero;

AVAsset *currentAsset = [AVAsset assetWithURL:videoURL];
BOOL  isCurrentAssetPortrait  = YES;

for(NSInteger i=0;i< [loopTime count]; i++) {

    //handle looptime array
    NSInteger loopDur = [[loopTime objectAtIndex:i] intValue];
    NSInteger value = labs(loopDur);
    //NSLog(@"loopInfo %d value %d",loopInfo,value);
    //handle slowmotion array
    double slowInfo = [[slowFactor objectAtIndex:i] doubleValue];
    double videoScaleFactor = fabs(slowInfo);

    AVMutableCompositionTrack *currentTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
    AVMutableCompositionTrack *audioTrack;
    audioTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeAudio
                                             preferredTrackID:kCMPersistentTrackID_Invalid];
    if (i==0) {
        [currentTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, currentAsset.duration) ofTrack:[[currentAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0] atTime:duration error:nil];

        [audioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, currentAsset.duration) ofTrack:[[currentAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0] atTime:duration error:nil];

    } else {

        [currentTrack insertTimeRange:CMTimeRangeMake(CMTimeSubtract(currentAsset.duration, CMTimeMake(value, 10)), CMTimeMake(value, 10)) ofTrack:[[currentAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0] atTime:duration error:nil];

        if (videoScaleFactor==1) {

            [audioTrack insertTimeRange:CMTimeRangeMake(CMTimeSubtract(currentAsset.duration, CMTimeMake(value, 10)), CMTimeMake(value, 10)) ofTrack:[[currentAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0] atTime:duration error:nil];
        }
        //slow motion here
        if (videoScaleFactor!=1) {

            [currentTrack scaleTimeRange:CMTimeRangeMake(CMTimeSubtract(currentAsset.duration, CMTimeMake(value, 10)), CMTimeMake(value, 10))
                              toDuration:CMTimeMake(value*videoScaleFactor, 10)];
            NSLog(@"slowmo %f",value*videoScaleFactor);
        }
    }

    AVMutableVideoCompositionLayerInstruction *currentAssetLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:currentTrack];
    AVAssetTrack *currentAssetTrack = [[currentAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];

    BOOL  isCurrentAssetPortrait  = YES;
    //CGFloat assetScaleToFitRatio;
    //assetScaleToFitRatio = [self getScaleToFitRatioCurrentTrack:currentTrack];

    if(isCurrentAssetPortrait){
        //NSLog(@"portrait");
        if (slowInfo<0) {
            CGRect screenRect = [[UIScreen mainScreen] bounds];
            CGFloat ratio = screenRect.size.height / screenRect.size.width;

            // we have to adjust the ratio for 16:9 screens
            if (ratio == 1.775) ratio = 1.77777777777778;

            CGFloat complimentSize = (currentAssetTrack.naturalSize.height*ratio);
            CGFloat tx = (currentAssetTrack.naturalSize.width-complimentSize)/2;

            // invert translation because of portrait
            tx *= -1;
            // t1: rotate and position video since it may have been cropped to screen ratio
            CGAffineTransform t1 = CGAffineTransformTranslate(currentAssetTrack.preferredTransform, tx, 0);
            // t2/t3: mirror video vertically

            CGAffineTransform t2 = CGAffineTransformTranslate(t1, currentAssetTrack.naturalSize.width, 0);
            CGAffineTransform t3 = CGAffineTransformScale(t2, -1, 1);

            [currentAssetLayerInstruction setTransform:t3 atTime:duration];

        } else if (loopDur<0) {
            CGRect screenRect = [[UIScreen mainScreen] bounds];
            CGFloat ratio = screenRect.size.height / screenRect.size.width;

            // we have to adjust the ratio for 16:9 screens
            if (ratio == 1.775) ratio = 1.77777777777778;

            CGFloat complimentSize = (currentAssetTrack.naturalSize.height*ratio);
            CGFloat tx = (currentAssetTrack.naturalSize.width-complimentSize)/2;

            // invert translation because of portrait
            tx *= -1;
            // t1: rotate and position video since it may have been cropped to screen ratio
            CGAffineTransform t1 = CGAffineTransformTranslate(currentAssetTrack.preferredTransform, tx, 0);
            // t2/t3: mirror video horizontally
            CGAffineTransform t2 = CGAffineTransformTranslate(t1, 0, currentAssetTrack.naturalSize.height);
            CGAffineTransform t3 = CGAffineTransformScale(t2, 1, -1);

            [currentAssetLayerInstruction setTransform:t3 atTime:duration];

        } else {

            [currentAssetLayerInstruction setTransform:currentAssetTrack.preferredTransform atTime:duration];

        }
    }else{
        //            CGFloat translateAxisX = (currentTrack.naturalSize.width > MAX_WIDTH )?(0.0):0.0;// if use <, 640 video will be moved left by 10px. (float)(MAX_WIDTH - currentTrack.naturalSize.width)/(float)4.0
        //            CGAffineTransform FirstAssetScaleFactor = CGAffineTransformMakeScale(assetScaleToFitRatio,assetScaleToFitRatio);
        //            [currentAssetLayerInstruction setTransform:
        //             CGAffineTransformConcat(CGAffineTransformConcat(currentAssetTrack.preferredTransform, FirstAssetScaleFactor),CGAffineTransformMakeTranslation(translateAxisX, 0)) atTime:duration];
    }
    if (i==0) {
        duration=CMTimeAdd(duration, currentAsset.duration);
    } else  {
        if (videoScaleFactor!=1) {
            duration=CMTimeAdd(duration, CMTimeMake(value*videoScaleFactor, 10));
        } else {
            duration=CMTimeAdd(duration, CMTimeMake(value, 10));
        }
    }

    [currentAssetLayerInstruction setOpacity:0.0 atTime:duration];
    [arrayInstruction addObject:currentAssetLayerInstruction];
}

AVMutableCompositionTrack *AudioBGTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
[AudioBGTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, audioAsset.duration) ofTrack:[[audioAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0] atTime:CMTimeSubtract(duration, audioAsset.duration) error:nil];

videoCompositionInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, duration);
videoCompositionInstruction.layerInstructions = arrayInstruction;

CGSize naturalSize;
if(isCurrentAssetPortrait){
    naturalSize = CGSizeMake(MAX_HEIGHT,MAX_WIDTH);//currentAssetTrack.naturalSize.height,currentAssetTrack.naturalSize.width);
} else {
    naturalSize = CGSizeMake(MAX_WIDTH,MAX_HEIGHT);//currentAssetTrack.naturalSize;
}

AVMutableVideoComposition *videoComposition = [AVMutableVideoComposition videoComposition];
videoComposition.instructions = [NSArray arrayWithObject:videoCompositionInstruction];
videoComposition.frameDuration = CMTimeMake(1, 30);
videoComposition.renderSize = CGSizeMake(naturalSize.width,naturalSize.height);
NSLog(@"prepared");

AVVideoComposition *composition = [videoComposition copy];
AVComposition *mixedComposition = [mixComposition copy];
completion(YES, composition, mixedComposition);
}

然后,我设置AVPlayer

    -(void)playVideoWithComposition:(AVVideoComposition*)videoComposition inMutableComposition:(AVComposition*)composition{

MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hud.label.text = myLanguage(@"kMergeClip");

savedComposition = [composition copy];
savedVideoComposition = [videoComposition copy];
playerItem = [AVPlayerItem playerItemWithAsset:composition];
playerItem.videoComposition = videoComposition;

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(repeatVideo:) name:AVPlayerItemDidPlayToEndTimeNotification object:playerItem];

if (!player) {
    player = [AVPlayer playerWithPlayerItem:playerItem];
    layer = [AVPlayerLayer playerLayerWithPlayer:player];
    layer.frame = [UIScreen mainScreen].bounds;
    [self.ibPlayerView.layer insertSublayer:layer atIndex:0];
    NSLog(@"create new player");
}

if (player.currentItem != playerItem ) {
    [player replaceCurrentItemWithPlayerItem:playerItem];
}
player.actionAtItemEnd = AVPlayerActionAtItemEndNone;
//[player seekToTime:kCMTimeZero];

[playerItem addObserver:self
             forKeyPath:@"status"
                options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
                context:@"AVPlayerStatus"];
}

当用户预览他们想要的所有视频并点击保存时。我用这个方法导出

    +(void)mergeUserCLip:(AVVideoComposition*)videoComposition withAsset:(AVComposition*)mixComposition showInViewController:(UIViewController*)viewController completion:(void(^)(BOOL success, NSURL *fileURL))completion{

MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:viewController.view animated:YES];
hud.mode = MBProgressHUDModeDeterminateHorizontalBar;
hud.label.text = myLanguage(@"kMergeClip");

//Name merge clip using beat name
//NSString* beatName = [[[NSString stringWithFormat:@"%@",audioFile] lastPathComponent] stringByDeletingPathExtension];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *tmpDir = [[documentsDirectory stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"tmp"];
NSString *myPathDocs =  [tmpDir stringByAppendingPathComponent:[NSString stringWithFormat:@"merge-beat.mp4"]];
//Not remove here, will remove when call previewPlayVC
[[NSFileManager defaultManager] removeItemAtPath:myPathDocs error:nil];

// 1 - set up the overlay
CALayer *overlayLayer = [CALayer layer];
UIImage *overlayImage = [UIImage imageNamed:@"watermark.png"];

[overlayLayer setContents:(id)[overlayImage CGImage]];
overlayLayer.frame = CGRectMake(720-221, 1280-109, 181, 69);
[overlayLayer setMasksToBounds:YES];

//    aLayer  = [CALayer layer];
//    [aLayer addSublayer:labelLogo.layer];
//    aLayer.frame = CGRectMake(MAX_WIDTH- labelLogo.width - 10.0, MAX_HEIGHT-50.0, 20.0, 20.0);
//    aLayer.opacity = 1;

// 2 - set up the parent layer
CALayer *parentLayer = [CALayer layer];
CALayer *videoLayer = [CALayer layer];
parentLayer.frame = CGRectMake(0, 0, MAX_HEIGHT,MAX_WIDTH);
videoLayer.frame = CGRectMake(0, 0, MAX_HEIGHT,MAX_WIDTH);
[parentLayer addSublayer:videoLayer];
[parentLayer addSublayer:overlayLayer];

// 3 - apply magic
AVMutableVideoComposition *mutableVideoComposition = [videoComposition copy];
mutableVideoComposition.animationTool = [AVVideoCompositionCoreAnimationTool
                                  videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:videoLayer inLayer:parentLayer];

NSURL *url = [NSURL fileURLWithPath:myPathDocs];
myLog(@"Path: %@", myPathDocs);
AVAssetExportSession *exporter = [[AVAssetExportSession alloc] initWithAsset:mixComposition presetName:AVAssetExportPreset1280x720];
exporter.outputURL = url;
exporter.outputFileType = AVFileTypeMPEG4;
exporter.videoComposition = mutableVideoComposition;
exporter.shouldOptimizeForNetworkUse = NO;

[exporter exportAsynchronouslyWithCompletionHandler:^ {
    //NSLog(@"exporting");
    switch (exporter.status) {
        case AVAssetExportSessionStatusCompleted: {
            NSURL *url = [NSURL fileURLWithPath:myPathDocs];
            hud.progress = 1.0f;
            dispatch_async(dispatch_get_main_queue(), ^{
                [MBProgressHUD hideHUDForView:viewController.view animated:YES];
            });
            [self checkTmpSize];
            if (completion) {
                completion(YES, url);
            }
        }
            break;
        case AVAssetExportSessionStatusExporting:
            myLog(@"Exporting!");
            break;
        case AVAssetExportSessionStatusWaiting:
            myLog(@"Waiting");
            break;
        default:
            break;
    }
}];
}

如果选择选项循环少于 8 次,上面的代码可以正常工作。 如果选择选项超过 8 次,导出会话将冻结并显示 export.progress = 0.0000000 如果我删除这一行

    playerItem.videoComposition = videoComposition;

然后我无法预览混合的视频,但可以正常导出(最多 16 个轨道)。

或者如果我删除导出代码中的行:

    exporter.videoComposition = mutableVideoComposition;

然后就可以预览混合的视频,并在没有视频合成的情况下正常导出。

所以我猜AVVideoComposition 和/或我实现它的方式有问题。

如果有任何建议,我将不胜感激。 非常感谢。

我非常怀疑这是因为使用AVPlayer 预览视频会以某种方式阻碍AVAssetExportSession,如下面的帖子所述:

iOS 5: Error merging 3 videos with AVAssetExportSession

AVPlayerItem fails with AVStatusFailed and error code “Cannot Decode”

【问题讨论】:

    标签: ios avfoundation avassetexportsession avcomposition


    【解决方案1】:

    我在尝试连接 N 个视频时遇到了这个问题,同时我在 UICollectionView 中播放了最多 3 个视频 I AVPlayer 实例。在您链接的 Stack Overflow question 中已经讨论过 iOS 只能处理这么多 AVPlayer 的实例。每个实例都使用了一个“渲染管道”。我发现AVMutableCompositionTrack 的每个实例也使用了这些渲染管道之一。

    因此,如果您使用过多的AVPlayer 实例或尝试使用过多的AVMutableCompositionTrack 轨道创建AVMutableComposition,您可能会耗尽解码H264 的资源,您将收到“无法解码”错误。我能够通过仅使用AVMutableCompositionTrack 的两个实例来解决这个问题。这样,我可以“重叠”视频片段,同时还可以应用过渡(这需要两个视频轨道同时“播放”)。

    简而言之:尽量减少对AVMutableCompositionTrackAVPlayer 的使用。您可以查看 Apple 提供的 AVCustomEdit 示例代码作为示例。具体来说,查看APLSimpleEditor 类中的buildTransitionComposition 方法。

    【讨论】:

      【解决方案2】:

      试试这个,在导出前清除播放器项目

      [self.player replaceCurrentItemWithPlayerItem:nil];
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多