Пустой кадр при объединении видео с использованием AVMutableComposition

Этот вопрос задавали уже много раз, но мне ничего не помогло. Я объединяю несколько видео, используя AVMutableComposition. После объединения видео я получаю пустые кадры между 30-40% видео. Остальные сливаются нормально. Я просто играю композицию напрямую, используя AVPlayer как AVPlayerItem. Код ниже:

AVMutableComposition *mutableComposition = [AVMutableComposition composition];
    AVMutableCompositionTrack *videoCompositionTrack = [mutableComposition addMutableTrackWithMediaType:AVMediaTypeVideo
                                                                                       preferredTrackID:kCMPersistentTrackID_Invalid];
    AVMutableCompositionTrack *audioCompositionTrack = [mutableComposition addMutableTrackWithMediaType:AVMediaTypeAudio
                                                                                       preferredTrackID:kCMPersistentTrackID_Invalid];

    NSMutableArray *instructions = [NSMutableArray new];
    CGSize size = CGSizeZero;

    CMTime time = kCMTimeZero;
    for (AVURLAsset *asset in assets)
    {
        AVAssetTrack *assetTrack;
        assetTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
        AVAssetTrack *audioAssetTrack = [asset tracksWithMediaType:AVMediaTypeAudio].firstObject;

        NSError *error;
        [videoCompositionTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, assetTrack.timeRange.duration )
                                       ofTrack:assetTrack
                                        atTime:time
                                         error:&error];


        if (error) {
            NSLog(@"asset url :: %@",assetTrack.asset);
            NSLog(@"Error - %@", error.debugDescription);
        }

        [audioCompositionTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, assetTrack.timeRange.duration)
                                       ofTrack:audioAssetTrack
                                        atTime:time
                                         error:&error];


        if (error) {
            NSLog(@"Error - %@", error.debugDescription);
        }
        AVMutableVideoCompositionInstruction *videoCompositionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
        videoCompositionInstruction.timeRange = CMTimeRangeMake(time, assetTrack.timeRange.duration);
        videoCompositionInstruction.layerInstructions = @[[AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoCompositionTrack]];
        [instructions addObject:videoCompositionInstruction];

        time = CMTimeAdd(time, assetTrack.timeRange.duration);

        if (CGSizeEqualToSize(size, CGSizeZero)) {
            size = assetTrack.naturalSize;;
        }
    }

    AVMutableVideoComposition *mutableVideoComposition = [AVMutableVideoComposition videoComposition];
    mutableVideoComposition.instructions = instructions;
    mutableVideoComposition.frameDuration = CMTimeMake(1, 30);
    mutableVideoComposition.renderSize = size;

    playerItem = [AVPlayerItem playerItemWithAsset:mutableComposition];
    playerItem.videoComposition = mutableVideoComposition;

person blancos    schedule 21.05.2015    source источник
comment
Ваш layerInstructions не является неправильным, посмотрите, прокомментировав последнюю строку: playerItem.videoComposition = mutableVideoComposition;   -  person Elf Sundae    schedule 28.05.2015
comment
Вы имеете в виду не правильно? Что не так в инструкции? После того, как я прокомментировал эту строку, я получаю черный кадр между всеми видео.   -  person blancos    schedule 29.05.2015


Ответы (1)


Насколько я знаю, AVMutableVideoCompositionLayerInstruction нельзя просто «добавить» или «добавить» в ваш код.

Судя по вашему коду, я предполагаю, что вы хотите сохранить информацию о видеоинструкциях при объединении видеоресурсов, но инструкции нельзя «скопировать» напрямую.

Если вы хотите это сделать, см. Документацию для AVVideoCompositionLayerInstruction, например

    getTransformRampForTime:startTransform:endTransform:timeRange:
    setTransformRampFromStartTransform:toEndTransform:timeRange:
    setTransform:atTime:

    getOpacityRampForTime:startOpacity:endOpacity:timeRange:
    setOpacityRampFromStartOpacity:toEndOpacity:timeRange:
    setOpacity:atTime:

    getCropRectangleRampForTime:startCropRectangle:endCropRectangle:timeRange:
    setCropRectangleRampFromStartCropRectangle:toEndCropRectangle:timeRange:
    setCropRectangle:atTime:

Вы должны использовать getFoo... методы на исходной дорожке, затем вычислить insertTime или timeRange для последней дорожки, затем setFoo..., а затем добавить к слоям инструкций конечной композиции видео.

ДА, немного сложновато ... Кроме того, самое главное, вы не можете получить все видеоэффекты, примененные к исходному активу.

Так в чем твоя цель? А чем обеспечен ваш исходный актив?

Если вы просто хотите объединить несколько файлов mp4 / mov, просто зацикливайте треки и добавляйте их в AVMutableCompositionTrack, но не videoComposition. И я проверил ваш код, он работает.

Если вы хотите объединить AVAsset с видеоинструкциями, см. Объяснение выше и документы. И моя лучшая практика - перед объединением сохранить эти AVAsset в файл с помощью AVAssetExportSession, а затем просто объединить видеофайлы.

p.s. Возможно, есть какие-то проблемы с вашими тестовыми файлами или исходными активами.

Код из моего проекта вроде Vine:

    - (BOOL)generateComposition
    {
            [self cleanComposition];

            NSUInteger segmentsCount = self.segmentsCount;
            if (0 == segmentsCount) {
                    return NO;
            }

            AVMutableComposition *composition = [AVMutableComposition composition];
            AVMutableVideoComposition *videoComposition = nil;
            AVMutableVideoCompositionInstruction *videoCompositionInstruction = nil;
            AVMutableVideoCompositionLayerInstruction *videoCompositionLayerInstruction = nil;
            AVMutableAudioMix *audioMix = nil;

            AVMutableCompositionTrack *videoTrack = nil;
            AVMutableCompositionTrack *audioTrack = nil;
            AVMutableCompositionTrack *musicTrack = nil;
            CMTime currentTime = kCMTimeZero;

            for (MVRecorderSegment *segment in self.segments) {
                    AVURLAsset *asset = segment.asset;
                    NSArray *videoAssetTracks = [asset tracksWithMediaType:AVMediaTypeVideo];
                    NSArray *audioAssetTracks = [asset tracksWithMediaType:AVMediaTypeAudio];

                    CMTime maxBounds = kCMTimeInvalid;

                    CMTime videoTime = currentTime;
                    for (AVAssetTrack *videoAssetTrack in videoAssetTracks) {
                            if (!videoTrack) {
                                    videoTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
                                    videoTrack.preferredTransform = CGAffineTransformIdentity;

                                    videoCompositionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
                                    videoCompositionLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoTrack];
                            }

                            /* Fix orientation */
                            CGAffineTransform transform = videoAssetTrack.preferredTransform;
                            if (AVCaptureDevicePositionFront == segment.cameraPosition) {
                                    transform = CGAffineTransformMakeTranslation(self.config.videoSize, 0);
                                    transform = CGAffineTransformScale(transform, -1.0, 1.0);
                            } else if (AVCaptureDevicePositionBack == segment.cameraPosition) {

                            }
                            [videoCompositionLayerInstruction setTransform:transform atTime:videoTime];

                            /* Append track */
                            videoTime = [MVHelper appendAssetTrack:videoAssetTrack toCompositionTrack:videoTrack atTime:videoTime withBounds:maxBounds];
                            maxBounds = videoTime;
                    }

                    if (self.sessionConfiguration.originalVoiceOn) {
                            CMTime audioTime = currentTime;
                            for (AVAssetTrack *audioAssetTrack in audioAssetTracks) {
                                    if (!audioTrack) {
                                            audioTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
                                    }
                                    audioTime = [MVHelper appendAssetTrack:audioAssetTrack toCompositionTrack:audioTrack atTime:audioTime withBounds:maxBounds];
                            }
                    }

                    currentTime = composition.duration;
            }

            if (videoCompositionInstruction && videoCompositionLayerInstruction) {
                    videoCompositionInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, composition.duration);
                    videoCompositionInstruction.layerInstructions = @[videoCompositionLayerInstruction];

                    videoComposition = [AVMutableVideoComposition videoComposition];
                    videoComposition.renderSize = CGSizeMake(self.config.videoSize, self.config.videoSize);
                    videoComposition.frameDuration = CMTimeMake(1, self.config.videoFrameRate);
                    videoComposition.instructions = @[videoCompositionInstruction];
            }


            // 添加背景音乐 musicTrack
            NSURL *musicFileURL = self.sessionConfiguration.musicFileURL;
            if (musicFileURL && musicFileURL.isFileExists) {
                    AVAsset *musicAsset = [AVAsset assetWithURL:musicFileURL];
                    AVAssetTrack *musicAssetTrack = [musicAsset tracksWithMediaType:AVMediaTypeAudio].firstObject;
                    if (musicAssetTrack) {
                            musicTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
                            if (CMTIME_COMPARE_INLINE(musicAsset.duration, >=, composition.duration)) {
                                    // 如果背景音乐时长大于视频总时长, 则直接添加
                                    [musicTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, composition.duration) ofTrack:musicAssetTrack atTime:kCMTimeZero error:NULL];
                            } else {
                                    // 否则, 循环背景音乐
                                    CMTime musicTime = kCMTimeZero;
                                    CMTime bounds = composition.duration;
                                    while (true) {
                                            musicTime = [MVHelper appendAssetTrack:musicAssetTrack toCompositionTrack:musicTrack atTime:musicTime withBounds:bounds];
                                            if (CMTIME_COMPARE_INLINE(musicTime, >=, composition.duration)) {
                                                    break;
                                            }
                                    }
                            }
                    }
            }

            // 处理音频
            if (musicTrack) {
                    AVMutableAudioMixInputParameters *audioMixParameters = [AVMutableAudioMixInputParameters audioMixInputParametersWithTrack:musicTrack];

                    /* 背景音乐添加淡入淡出 */
                    AVAsset *musicAsset = musicTrack.asset;
                    CMTime crossfadeDuration = CMTimeMake(15, 10); // 前后都是1.5秒
                    CMTime halfDuration = CMTimeMultiplyByFloat64(musicAsset.duration, 0.5);
                    crossfadeDuration = CMTimeMinimum(crossfadeDuration, halfDuration);
                    CMTimeRange crossfadeRangeBegin = CMTimeRangeMake(kCMTimeZero, crossfadeDuration);
                    CMTimeRange crossfadeRangeEnd = CMTimeRangeMake(CMTimeSubtract(musicAsset.duration, crossfadeDuration), crossfadeDuration);
                    [audioMixParameters setVolumeRampFromStartVolume:0.0 toEndVolume:self.sessionConfiguration.musicVolume timeRange:crossfadeRangeBegin];
                    [audioMixParameters setVolumeRampFromStartVolume:self.sessionConfiguration.musicVolume toEndVolume:0.0 timeRange:crossfadeRangeEnd];

                    audioMix = [AVMutableAudioMix audioMix];
                    [audioMix setInputParameters:@[audioMixParameters]];
            }

            _composition = composition;
            _videoComposition = videoComposition;
            _audioMix = audioMix;

            return YES;
    }


    - (AVPlayerItem *)playerItem
    {
            AVPlayerItem *playerItem = nil;
            if (self.composition) {
                    playerItem = [AVPlayerItem playerItemWithAsset:self.composition];
                    if (!self.videoComposition.animationTool) {
                            playerItem.videoComposition = self.videoComposition;
                    }
                    playerItem.audioMix = self.audioMix;
            }
            return playerItem;
    }

    ///=============================================
    /// MVHelper
    ///=============================================

    + (CMTime)appendAssetTrack:(AVAssetTrack *)track toCompositionTrack:(AVMutableCompositionTrack *)compositionTrack atTime:(CMTime)atTime withBounds:(CMTime)bounds
    {
            CMTimeRange timeRange = track.timeRange;
            atTime = CMTimeAdd(atTime, timeRange.start);

            if (!track || !compositionTrack) {
                    return atTime;
            }

            if (CMTIME_IS_VALID(bounds)) {
                    CMTime currentBounds = CMTimeAdd(atTime, timeRange.duration);
                    if (CMTIME_COMPARE_INLINE(currentBounds, >, bounds)) {
                            timeRange = CMTimeRangeMake(timeRange.start, CMTimeSubtract(timeRange.duration, CMTimeSubtract(currentBounds, bounds)));
                    }
            }
            if (CMTIME_COMPARE_INLINE(timeRange.duration, >, kCMTimeZero)) {
                    NSError *error = nil;
                    [compositionTrack insertTimeRange:timeRange ofTrack:track atTime:atTime error:&error];
                    if (error) {
                            MVLog(@"Failed to append %@ track: %@", compositionTrack.mediaType, error);
                    }
                    return CMTimeAdd(atTime, timeRange.duration);
            }

            return atTime;
    }
person Elf Sundae    schedule 29.05.2015