今天看啥  ›  专栏  ›  仙人掌D

AVFoundation合并多个音视频文件(七)

仙人掌D  · 简书  ·  · 2020-10-11 09:11

前言

从本文开始逐渐学习iOS自带的多媒体处理框架,例如AVFoundation,VideoToolbox,CoreMedia,CoreVideo实现多媒体的处理,并且将实现方式以及效果和ffmpeg的方式做对比

给一个视频添加音乐,将多段音视频文件合并为一个文件是很常见的需求,AVFoundation就提供了这样的接口。

本文的目的:
合并两个MP4文件为一个

音视频合并相关流程

image.png

上图介绍了AVFoundation框架中关于合并音视频文件的相关的对象关系图,可以看到使用AVFoundation合并音视频还是相对比较简单的。

相关对象及函数介绍

  • 1、AVURLAsset
    容器对象,代表了要操作的容器。封装,解封装,音视频播放,以及音视频合并等等操作的基础都涉及到这个对象。

  • 2、AVAssetTrack
    音视频轨道对象,代表了文件中的一路音频流或者一路视频流,它作为每一个要被合并的音频或者视频流被添加到组合对象中最终进行合并

  • 3、AVMutableCompositionTrack
    组合轨道对象,它作为音视频合并的基础,通过它添加要合并的音频流或者视频流,分为两种类型:音频组合轨道对象和视频组合轨道对象,音频组合轨道对象只能添加音频流,视频组合轨道对象只能添加视频流

  • 4、AVMutableComposition
    组合对象,通过它构建组合轨道对象

  • 5、AVAssetExportSession
    执行合并操作并导出为文件对象,该对象内部应该是封装了合并多个音频流或者视频流的操作和封装操作

实现代码

#import <Foundation/Foundation.h>

@interface AVMYComposition : NSObject

/** 合并任意两个相同容器类型容器文件的功能;合并后的文件分辨率取最低的文件分辨率,像素格式及颜色范围取第一文件的。编码方式则
 */
- (void)mergeFile:(NSURL*)filstURL twoURL:(NSURL*)twoURL dst:(NSURL*)dsturl;
@end
import "AVMYComposition.h"
#import <AVFoundation/AVFoundation.h>

@implementation AVMYComposition
{
    dispatch_semaphore_t processSemaphore;
}
- (void)mergeFile:(NSURL*)filstURL twoURL:(NSURL*)twoURL dst:(NSURL*)dsturl
{
    NSLog(@"开始");
    processSemaphore = dispatch_semaphore_create(0);
    
    // 创建组合对象
    AVMutableComposition *composition = [AVMutableComposition composition];
    
    // 为组合对象添加组合对象音频轨道,用于合并所有音频
    AVMutableCompositionTrack *audioComTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
    // 为组合对象添加组合对象视频轨道,用于合并所有视频
    AVMutableCompositionTrack *videoComTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
    
    // 准备第一个容器文件的所有音频和视频轨道
    AVURLAsset *asset1 = [[AVURLAsset alloc] initWithURL:filstURL options:nil];
    NSArray *audioTracks1 = [asset1 tracksWithMediaType:AVMediaTypeAudio];
    NSArray *videoTracks1 = [asset1 tracksWithMediaType:AVMediaTypeVideo];
    AVAssetTrack *audioTrack1 = audioTracks1?audioTracks1[0]:nil;
    AVAssetTrack *videoTrack1 = videoTracks1?videoTracks1[0]:nil;
    
    
    // 准备第二个文件的所有音频和视频轨道
    AVURLAsset *asset2 = [[AVURLAsset alloc] initWithURL:twoURL options:nil];
    NSArray *audioTracks2 = [asset2 tracksWithMediaType:AVMediaTypeAudio];
    NSArray *videoTracks2 = [asset2 tracksWithMediaType:AVMediaTypeVideo];
    AVAssetTrack *audioTrack2 = audioTracks2?audioTracks2[0]:nil;
    AVAssetTrack *videoTrack2 = videoTracks2?videoTracks2[0]:nil;
    
    // 将解析出的每个文件的各个轨道添加到组合对象的对应的用于编辑的音视频轨道对象中
    CMTimeRange audioTmeRange1 = kCMTimeRangeZero;
    CMTimeRange videoTmeRange1 = kCMTimeRangeZero;
    NSError *error = nil;
    if (audioTrack1) {
        audioTmeRange1 = CMTimeRangeMake(kCMTimeZero, asset1.duration);
        // 组合对象音频轨道添加音频轨道
        [audioComTrack insertTimeRange:audioTmeRange1 ofTrack:audioTrack1 atTime:kCMTimeZero error:&error];
        if (error) {
            NSLog(@"audio1 error %@",error);
            return;;
        }
    }
    
    if (videoTrack1) {
        videoTmeRange1 = CMTimeRangeMake(kCMTimeZero, asset1.duration);
        // 组合对象音频轨道添加音频轨道
        [videoComTrack insertTimeRange:videoTmeRange1 ofTrack:videoTrack1 atTime:kCMTimeZero error:&error];
        if (error) {
            NSLog(@"video1 error %@",error);
            return;;
        }
    }
    
    // 处理第二个文件
    CMTimeRange audioTmeRange2 = kCMTimeRangeZero;
    CMTimeRange videoTmeRange2 = kCMTimeRangeZero;
    if (audioTrack2) {
        audioTmeRange2 = CMTimeRangeMake(kCMTimeZero, asset2.duration);
        [audioComTrack insertTimeRange:audioTmeRange2 ofTrack:audioTrack2 atTime:asset1.duration error:&error];
        if (error) {
            NSLog(@"audio2 error %@",error);
            return;;
        }
    }
    if (videoTrack2) {
        videoTmeRange2 = CMTimeRangeMake(kCMTimeZero, asset2.duration);
        [videoComTrack insertTimeRange:videoTmeRange2 ofTrack:videoTrack2 atTime:asset1.duration error:&error];
        if (error) {
            NSLog(@"video2 error %@",error);
            return;;
        }
    }
    
    // 执行合并
    if ([[NSFileManager defaultManager] fileExistsAtPath:dsturl.path]) {
        [[NSFileManager defaultManager] removeItemAtURL:dsturl error:nil];
    }
    
    // 执行合并轨道对象会话
    AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:composition presetName:AVAssetExportPresetHighestQuality];
    exportSession.outputURL = dsturl;
    exportSession.outputFileType = AVFileTypeMPEG4;
    
    [exportSession exportAsynchronouslyWithCompletionHandler:^{
        NSLog(@"over");
        if (exportSession.status != AVAssetExportSessionStatusCompleted) {
            NSLog(@"error %@",exportSession.error);
        }
        
        dispatch_semaphore_signal(self->processSemaphore);
    }];
    
    dispatch_semaphore_wait(self->processSemaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"结束了");
}

遇到问题

项目地址

https://github.com/nldzsz/ffmpeg-demo

位于AVFoundation目录下文件AVMYComposition.h/AVMYComposition.m中




原文地址:访问原文地址
快照地址: 访问文章快照