SDVideoCamera:仿抖音(视频录制、视频剪辑、视频合成)

闲话不多讲,先上项目Github传送门。

SDVideoCamera传送门


前言


又是好久没有更新博客了,哈哈哈,由于近来从公司离职,再加上近来要结婚的缘故,所以有大量充足的时间来整理以前写的一个仿写抖音录制的三方库。但是讲真的,写了将近一个月的时间,还有有很多细节没有实现,只是实现其核心功能,还是有很多地方可以优化的。另外抖音是使用FFmpeg来实现音视频处理的业务逻辑的,相比于AVFoundation,具有跨平台、执行效率高的特点。所以作为一个菜鸡,只能使用AVFoundation来实现视频处理了。。。

下面我就SDVideoCamera分部分介绍使用方法、功能分解等部分。


使用方法


由于SDVideoCamera使用了原生的系统相机,麦克风以及相册。所以我们需要在Info.plist配置如下信息。

    <key>NSCameraUsageDescription</key>
    <string>该项目想访问你的摄像头</string>
    <key>NSMicrophoneUsageDescription</key>
    <string>该项目想访问你的麦克风</string>
    <key>NSPhotoLibraryUsageDescription</key>
    <string>该项目想访问你的相册</string>

SDVideoCamera 中 所有配置项都是由SDVideoConfig这个类来完成的,在这个类中,你可以根据自己项目来配置一些适合场景的参数信息。如果你想修改SDVideoCamera的源码来适应你的项目,你也可以通过这个类来快速找到修改入口。

这里我猜想有几个属性是你绝对想要设置的。


合成视频返回时会调用的Block,在这个Block中,会返回合成视频的路径。

@property(nonatomic,copy)CameraReturnBlock returnBlock;

合成视频之后需要返回到那个控制器,这个属性是必须要设置的。

@property(nonatomic,weak)UIViewController *returnViewController;

该三方的主色,很多按钮或者控件都会受到该属性的影响。

@property(nonatomic,copy)NSString *tintColor;

配乐的数组,如果想要配乐,可以设置这个,如果感觉UI可能不符合你的项目需要,你可以自己通过这个属性来找到修改的位置入口(SDVideoPreviewMusicView),进行修改。

@property(nonatomic,copy)NSArray <SDVideoMusicModel *>*recommendMusicList;

想要给视频合成贴纸?需要设置这个贴纸图片名称数组。

@property(nonatomic,copy)NSArray <NSString *>*previewTagImageNameArray;

上面介绍了 SDVideoConfig 这个配置类,接下来我们就看一下我们该如何启动SDVideoCamera,以SDVideoCameraDemo为例,我们只需要配置好 SDVideoConfig 这个类,然后presentViewController即可,示例代码如下所示。

    SDVideoConfig *config = [[SDVideoConfig alloc] init];
    config.returnViewController = self;
    config.returnBlock = ^(NSString *mergeVideoPathString) {
        NSLog(@"合成路径 %@",mergeVideoPathString);
    };

    SDVideoMusicModel *musicModel = [[SDVideoMusicModel alloc] init];
    musicModel.musicTitle = @"你是人间四月天";
    musicModel.musicImageName = @"music_image.jpg";
    musicModel.musicWebURL = @"https://zhike-oss.oss-cn-beijing.aliyuncs.com/tmp/test_music.mp3";
    
    SDVideoMusicModel *otherMusicModel = [[SDVideoMusicModel alloc] init];
    otherMusicModel.musicTitle = @"郊外静音";
    otherMusicModel.musicImageName = @"music_image.jpg";
    otherMusicModel.musicWebURL = @"https://zhike-oss.oss-cn-beijing.aliyuncs.com/tmp/test_music2.mp3";
    
    config.recommendMusicList = @[musicModel,otherMusicModel];
    
    config.previewTagImageNameArray = @[@"circle_add_friend_icon",@"circle_apply_friend_icon"];
    
    SDVideoController *videoController = [[SDVideoController alloc] initWithCameraConfig:config];
    [self presentViewController:videoController animated:YES completion:nil];

上面我们对 SDVideoCamera 的接入有了一定的了解,接下来我就来分部分来说明不同功能模块。


录制功能


不叨叨别的。直接上效果图,我们来看一下实现的效果,主要是就分段录制,分段删除,长按录制,短按录制等。

视频的录制功能没有采用GPUImage 这个库,本来一开始想使用的,但是一想打包起来将近10M的静态库,所以就暂时放弃了,至于想实现美颜功能的童鞋,请根据下面的说明自行进行添加修改。

我们看一下该模块下所有相关的类。该功能主要存在于 CameraHelpers 目录下,所有相关类如下图所示。

Camera目录
Helpers目录

接下来,我们看一下每一个类的说明介绍,如下列表所示。

类名 功能说明
SDVideoCameraController 该类是录制的主控制器,也是SDVideoController的下属控制器
SDVideoCameraHeader 定义了一下常用的宏设定和枚举,例如宽高信息
SDVideoCameraMenuView 录制界面的菜单视图,包括左边菜单以及下部按钮的所有控件。
SDVideoCameraPlayButton 录制按钮的控件,由于录制按钮功能较多,对应的就是需要添加的手势较多,所以这里需自定义控件来实现功能。
SDVideoMenuDataModel 侧边按钮的数据模型,用户可以在SDVideoConfig中对menuDataArray,进行自定义即可,可以修改菜单按钮的顺序,显示等。
SDVideoCameraAuthoView 相机和麦克风权限的请求视图
SDVideoCaptureManager 是摄像头硬件管理类,包括相机、麦克风权限类型,录制管理等。
SDVideoDataManager 录制的数据管理类
SDVideoDataModel 该类是分段视频的数据模型类

视频录制功能的核心是分段录制,视频录制是没有使用到视频合成的,首先我们要了解合成一个视频其实是一个比较耗时的操作,所以我们能不进行合成操作尽量不要合成视频。在分段录制过程中的所有视频数据是以 SDVideoDataModel 模型存储的。这个模型中存储了分段录制完成的视频路径,时长进度,时长,时长权重,同时还有一个删除视频文件的方法。这里需要对时长权重进行说明,时长权重就是该视频占有总视频时长的百分比。因为我们需要对进度条进行删除动画处理,所以我们需要这个值来判断我们需要删除的长度。该类如下所示。

@interface SDVideoDataModel : NSObject

// SDVideoDataModel 是分段视频的数据模型

@property(nonatomic,strong)NSURL *pathURL;
@property(nonatomic,assign)float duration;//时长进度
@property(nonatomic,assign)float progress;//进度
@property(nonatomic,assign)float durationWeight;//时长权重

- (void)deleteLocalVideoFileAction;

@end

在录制过程中所有的数据管理都是在 SDVideoDataManager的单例类中完成中,为什么使用单例类,这里做一下说明。在录制过程中存在各种状态的改变,例如当前录制时长,录制视频个数等等。而其他视图可以通过KVO观察者模式来进行其属性进行观察,通过新值的变化而改变对应的UI。其实普通类也是满足该需要,但是需要进行传递操作,为了避免传值操作,所以这里使用了单例类,其实没有性能上的优化,单纯就是习惯而已。大家在修改源码的时候可以自行根据习惯来修改。该类的属性如下所示。

@interface SDVideoDataManager : NSObject

// SDVideoDataManager 是视频数据管理单例类

+ (SDVideoDataManager *)defaultManager;

@property(nonatomic,strong)SDVideoConfig *config; // 用户的配置项
@property(nonatomic,assign)VideoCameraState cameraState;//相机的状态
@property(nonatomic,assign,readonly)float videoSecondTime;//最大录制时长,根据用户配置项获得
@property(nonatomic,assign)float totalVideoTime; //录制的总计时长
@property(nonatomic,assign)float progress; //进度
@property(nonatomic,assign)NSInteger videoNumber; //视频的个数,没法直接监听数组元素的变化
@property(nonatomic,strong,readonly)NSMutableArray <SDVideoDataModel *>*videoDataArray;

/// 添加一条新的分段视频
- (void)addVideoModel:(SDVideoDataModel *)videoModel;

/// 删除最后的分段视频
- (void)deleteLastVideoModel;

/// 删除所有的分段视频
- (void)deleteAllVideoModel;

@end

在上面说到我没有使用GPUImage三方库来实现美颜滤镜效果,如果某位童鞋想在SDVideoCamera基础上添加美颜路径该怎么实现呢?这就需要对 SDVideoCaptureManager 这个设备管理类进行修改了。 SDVideoCaptureManager是骚栋基于系统相机写的视频录制管理类,如果需要添加美颜滤镜功能,需要修改这里面的代码。

至于该模块的UI部分,我就不过多进行诉述了。


视频剪辑


上一个模块我们简单的聊了一下视频录制,这个模块的入口是通过上传视频得到的,效果如下所示。

该模块涉及到的类主要存放于 AlbumCrop 以及 Helpers 中。

Album目录
Crop目录
Helpers目录

主要功能类功能简介表格如下所示。其他类似于Cell的类这里就不过多的叙述了。

类名 功能说明
SDVideoAlbumViewController 选取视频的主体控制器,选取模式有两种,一种是选取完成Push出裁剪控制器,另外一种是选取完成返回到上一个界面,并且回调协议方法。这两模式通过 isFinishReturn 属性来控制。
SDVideoCropViewController 视频选取、裁剪的主体控制器,相册的视频合成功能这个是个主入口。
SDVideoCropBottomView 多视频合成、裁剪的操作视图。包含 SDVideoCropFrameView 和 SDVideoCropItemListView 两部分
SDVideoCropFrameView 视频帧预览功能的主体视图,也是视频裁剪功能的主要操作区。
SDVideoCropItemListView 视频媒体列表展示的主体视图,通过这个我们对合成视频列表进行新增、删除,变换位置等操作。
️SDVideoUtils️ 视频合成操作工具类,这个类中有我们需要的各种视频处理方法,也算是整个三方的核心了。

OK,上面的基本情况我们已经介绍完成了,接下来我们就一起来看看如何实现多视频合成以及裁剪的。讲真的,这个模块本来是不想做的,因为相对于视频录制来说,功能比较多,而且抖音爸爸做的细节也比较多,每一个小细节都可能需要一两天甚至更多的时间,但是一想反正有时间,不如就做吧,所以就把这块给做了。

但是做的过程中遇到了不少的坑,例如合成的视频大小不一致、视频帧图获取需要时间等等问题。这里我就这些问题来说说视频剪辑。让想做这块的童鞋降低踩坑的风险。该类童鞋可以着重的看一下视频处理类 SDVideoUtils 中的实现。

首先,该模块和视频录制模块不同,虽然都是多视频合成,但是其实有不同的,因为在这个模式下,相册中的视频并不一定都是录制得来的,这就会造成一个问题,那就是合成录制视频可能不同,如果我们还是按照网上的那种粗糙的方式进行合成系统就会以一个视频的尺寸为基准进行合成,从而造成合成视频尺寸不正常。网上的资料也不是很多,这里参考了iOS 不同尺寸、比例、方向的视频拼接播放来实现的该功能。

这里对其进行说明,首先我们要是到不管是视频还是音频都是由多条轨信息组成,一般的情况,视频会有一条视频轨和一条音频轨,但是我们如果想解决视频尺寸大小不一致的问题,我们需要指定renderSize,同时根据Apple官方提示,我们需要添加两条视频轨,然后 用 交替 添加的形式来进行添加分段视频的视频轨,这样就能彻底的解决视频大小不一致造成的视频拉伸问题,然后问题又来了,那就是两条视频轨,我们如何让播放器知道什么时刻播放哪条轨,什么时刻进行视频轨的交替播放呢?这个就需要定义了 AVMutableVideoComposition 类来实现了。而且在播放的时候,我们只需要添加提供该信息即可。

结合上面的问题,我来对视频合成操作方法进行说明解释。该方法如下所示,是SDVideoUtils私有静态方法。

+ (void)loadMeidaCompostion:(AVMutableComposition *)composition
           videoComposition:(AVMutableVideoComposition *)videoComposition
                   audioMix:(AVMutableAudioMix *)audioMix
                 assetArray:(NSArray <AVAsset *>*)assetArray
            selectTimeRange:(CMTimeRange)selectTimeRange
               bgAudioAsset:(AVAsset *)bgAudioAsset
             originalVolume:(float)originalVolume
              bgAudioVolume:(float)bgAudioVolume;

先对参数进行说明一下 AVMutableComposition 是视频轨和音频轨管理类。videoComposition 是可以让视频源也就是视频轨进行切换的信息类。AVMutableAudioMix 是混音管理类,用来管理合成音频以及伴奏的声音大小。assetArray是需要合成的视频数据类。selectTimeRange是时间截取结构体。

首先我们通过外部传入的方式,把对应的参数传入到该方法中,然后我们需要情况来创建不同的音频轨和视频轨。同时指定 renderSize 的大小,如下所示。

    // 初始化视频轨和音频轨,伴奏音频轨
    videoComposition.frameDuration = CMTimeMake(1, 30);
    videoComposition.renderSize = SDVideoSize;
    // 创建两条视频轨,处理不同尺寸视频合成问题
    AVMutableCompositionTrack *firstVideoTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
    AVMutableCompositionTrack *secondVideoTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
    NSArray *videoTrackArray = @[firstVideoTrack,secondVideoTrack];

    AVMutableCompositionTrack *audioTrack = nil;
    if (originalVolume > 0) {
        audioTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
    }
    AVMutableCompositionTrack *bgAudioTrack = nil;
    if (bgAudioVolume > 0) {
        bgAudioTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
    }

对于 selectTimeRange 这个参数主要是用来截取视频用的,有很多童鞋可能刚刚了解这块,我建议看一下CMTimeCMTimeRange 两个结构体的使用与操作方法,这里就不过多叙述了。所以我们在截取视频的时候需要知道从哪个视频数据的那个时间点开始,那个视频的那个时间点结束。这样我们合成的时候只需要添加对应的时间位置和时间长度的视频源即可。代码如下。

    CMTime firstAssetStartTime = kCMTimeZero;
    CMTime endAssetDuration = assetArray.lastObject.duration;
    NSInteger startIndex = 0;
    NSInteger endIndex = assetArray.count - 1;
    
    // 如果是没有设置时间区间,那么直接认定全部选中
    if (!CMTimeRangeEqual(selectTimeRange, kCMTimeRangeZero)) {
        startIndex = -1;
        endIndex = -1;
        CMTime assetTotalTime = kCMTimeZero;
        CMTime videoTotalTime = CMTimeAdd(selectTimeRange.start, selectTimeRange.duration);
        for (int i = 0; i < assetArray.count; i++) {
            AVAsset *asset = assetArray[i];
            assetTotalTime = CMTimeAdd(assetTotalTime, asset.duration);
            if (CMTIME_COMPARE_INLINE(CMTimeSubtract(assetTotalTime,selectTimeRange.start), >, selectTimeRange.start) && startIndex == -1) {
                startIndex = i;
                firstAssetStartTime = CMTimeSubtract(asset.duration, CMTimeSubtract(assetTotalTime,selectTimeRange.start));
            }
            if (CMTIME_COMPARE_INLINE(assetTotalTime, >=, videoTotalTime) && startIndex != -1 && endIndex == -1) {
                endIndex = i;
                endAssetDuration = CMTimeSubtract(asset.duration, CMTimeSubtract(assetTotalTime,videoTotalTime));
            }
        }
    }

获取到时间截取信息之后,我们就从 startIndex 开始遍历,到 endIndex 结束 交替的往两条视频轨添加视频轨,同时需要判断是否需要添加视频原生音频轨信息。如果有原声音频轨信息,还需要添加混音信息。 同时需要存储每个分段视频在整个视频中的timeRange信息。 整体代码如下所示。

    NSMutableArray *audioMixArray = [NSMutableArray arrayWithCapacity:16];
    for (NSInteger i = startIndex; i <= endIndex; i++) {
        AVAsset *asset = assetArray[i];
        AVMutableCompositionTrack *videoTrack = videoTrackArray[i % 2];
        AVAssetTrack *videoAssetTrack = [asset tracksWithMediaType:AVMediaTypeVideo].firstObject;
        if (videoAssetTrack == nil) {
            continue;
        }
        CMTimeRange timeRange = CMTimeRangeMake(kCMTimeZero, asset.duration);
        
        if (i == startIndex) {
            timeRange = CMTimeRangeMake(firstAssetStartTime, CMTimeSubtract(asset.duration, firstAssetStartTime));
        }
        if (i == endIndex) {
            timeRange = CMTimeRangeMake(kCMTimeZero, endAssetDuration);
        }

        [videoTrack insertTimeRange:timeRange ofTrack:videoAssetTrack atTime:startTime error:nil];
        passThroughTimeRanges[i] = CMTimeRangeMake(startTime, timeRange.duration);
        
        if (originalVolume > 0) {
            [audioTrack insertTimeRange:timeRange ofTrack:[asset tracksWithMediaType:AVMediaTypeAudio][0] atTime:startTime error:nil];
            AVMutableAudioMixInputParameters *audioTrackParameters = [AVMutableAudioMixInputParameters audioMixInputParametersWithTrack:audioTrack];
            [audioTrackParameters setVolume:originalVolume atTime:timeRange.duration];
            [audioMixArray addObject:audioTrackParameters];
        }
        startTime = CMTimeAdd(startTime,timeRange.duration);
    }

从上一个模块中获取到的视频分段信息数组 passThroughTimeRanges 将用来修改视频尺寸数据。这里仿写了上面文章提到的修改视频尺寸方法,整体代码如下所示。

    NSMutableArray *instructions = [NSMutableArray arrayWithCapacity:16];
    
    for (NSInteger i = startIndex; i <= endIndex; i++) {
        
        AVMutableCompositionTrack *videoTrack = videoTrackArray[i % 2];
        AVMutableVideoCompositionInstruction * passThroughInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
        passThroughInstruction.timeRange = passThroughTimeRanges[i];
        AVMutableVideoCompositionLayerInstruction * passThroughLayer = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoTrack];
        [SDVideoUtils changeVideoSizeWithAsset:assetArray[i] passThroughLayer:passThroughLayer];
        passThroughInstruction.layerInstructions = @[passThroughLayer];
        [instructions addObject:passThroughInstruction];
    }
    videoComposition.instructions = instructions;
// 处理视频尺寸大小
+ (void)changeVideoSizeWithAsset:(AVAsset *)asset passThroughLayer:(AVMutableVideoCompositionLayerInstruction *)passThroughLayer {
    
    AVAssetTrack *videoAssetTrack = [asset tracksWithMediaType:AVMediaTypeVideo].firstObject;
    if (videoAssetTrack == nil) {
        return;
    }
    CGSize naturalSize = videoAssetTrack.naturalSize;
    
    if ([SDVideoUtils videoDegressWithVideoAsset:asset] == 90) {
        naturalSize = CGSizeMake(naturalSize.height, naturalSize.width);
    }
    
    if ((int)naturalSize.width % 2 != 0) {
        naturalSize = CGSizeMake(naturalSize.width + 1.0, naturalSize.height);
    }
    
    CGSize videoSize = SDVideoSize;
    
    if ([SDVideoUtils videoDegressWithVideoAsset:asset] == 90) {
        CGFloat height = videoSize.width * naturalSize.height / naturalSize.width;
        CGAffineTransform translateToCenter = CGAffineTransformMakeTranslation(videoSize.width, videoSize.height/2.0 - height/2.0);
        CGAffineTransform scaleTransform = CGAffineTransformScale(translateToCenter, videoSize.width/naturalSize.width, height/naturalSize.height);
        CGAffineTransform mixedTransform = CGAffineTransformRotate(scaleTransform, M_PI_2);
        [passThroughLayer setTransform:mixedTransform atTime:kCMTimeZero];
    } else {
        CGFloat height = videoSize.width * naturalSize.height / naturalSize.width;
        CGAffineTransform translateToCenter = CGAffineTransformMakeTranslation(0, videoSize.height/2.0 - height/2.0);
        CGAffineTransform scaleTransform = CGAffineTransformScale(translateToCenter, videoSize.width/naturalSize.width, height/naturalSize.height);
        [passThroughLayer setTransform:scaleTransform atTime:kCMTimeZero];
    }
}

到了这一步,视频尺寸大小不一致问题基本上就算解决完成了,接下来我们就根据情况看是否需要合成伴奏信息,同时对混音信息进行添加,整体代码如下。

    // 插入伴奏
    if (bgAudioAsset != nil && bgAudioVolume > 0) {
        AVAssetTrack *assetAudioTrack = [[bgAudioAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0];
        [bgAudioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, startTime) ofTrack:assetAudioTrack atTime:kCMTimeZero error:nil];
        AVMutableAudioMixInputParameters *bgAudioTrackParameters = [AVMutableAudioMixInputParameters audioMixInputParametersWithTrack:bgAudioTrack];
        [bgAudioTrackParameters setVolume:bgAudioVolume atTime:startTime];
        [audioMixArray addObject:bgAudioTrackParameters];
    }
    audioMix.inputParameters = audioMixArray;

OK,到这里很多童鞋说接下来我们是不是需要把这些信息合成一个视频文件导出即可,NONONO,这里我想说,我们有这些信息就可以播放了,我们只需要创建一个 AVPlayerItem,让它携带这些信息即可。这些代码则在 mergeMediaPlayerItemActionWithAssetArray 方法中体现的。

    AVPlayerItem *playerItem = [[AVPlayerItem alloc] initWithAsset:composition];
    playerItem.videoComposition = videoComposition;
    playerItem.audioMix = audioMix;

合成的这块逻辑基本说完了,接下来我讲讲第二个坑,就是关于视频帧图片问题,由于视频帧图片获取需要时间,同时如果需要符合抖音的话,我们需要将视频帧图片与视频的时长进行对应,也就说视频帧图片可以不一定都是一样大小。在这里我做了部分优化,例如存储视频的帧图片信息,如果以前有该时间的视频帧图片,直接取出,不再进行重新获取等等,但是还是没有做到抖音那样流畅程度,所以这里如果有大佬可以指导一下,骚栋不胜感激了。


视频合成


不管是视频录制还是视频剪辑说真的都是没有用到视频合成,主要是视频合成太耗时,而且像视频剪辑,每一次都合成会造成相当的卡顿的。视频合成在视频界面的 下一步 按钮才会有体现。其他位置都没有使用视频合成,借此我们就先看一下视频预览的效果图。

该模块涉及到的类主要存放于 Preview 目录中。

在预览模块中除了合成背景音乐外还有另外的一个功能就是添加贴纸或者添加文字,这就需要用到 SDVideoUtils 中的如下静态方法。

/// 给视频添加贴图信息
/// @param composition 视频合成器
/// @param size 视频尺寸
/// @param layerArray 图层数组
+ (void)applyVideoEffectsWithComposition:(AVMutableVideoComposition *)composition
                                    size:(CGSize)size
                              layerArray:(NSArray <CALayer *>*)layerArray;

方法中的操作比较简单,我直接上代码了。

    if (layerArray.count == 0) {
        return;
    }
    
    CALayer *overlayLayer = [CALayer layer];
    overlayLayer.frame = CGRectMake(0, 0, size.width, size.height);
    overlayLayer.masksToBounds = YES;
    for (CALayer *subLayer in layerArray) {
        [overlayLayer addSublayer:subLayer];
    }
    
    CALayer *parentLayer = [CALayer layer];
    CALayer *videoLayer = [CALayer layer];
    parentLayer.frame = CGRectMake(0, 0, size.width, size.height);
    videoLayer.frame = CGRectMake(0, 0, size.width, size.height);
    [parentLayer addSublayer:videoLayer];
    [parentLayer addSublayer:overlayLayer];
    
    composition.animationTool = [AVVideoCompositionCoreAnimationTool videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:videoLayer inLayer:parentLayer];

在视频合成的时候,操作和视频剪辑调用的方法是一直的,只是需要创建一个 AVAssetExportSession 对象,用来导出视频文件,这个操作是在 mergeMediaActionWithAssetArray 方法中,整体代码如下所示。

    AVAssetExportSession *exporterSession = [[AVAssetExportSession alloc] initWithAsset:composition presetName:AVAssetExportPresetMediumQuality];
    exporterSession.outputFileType = AVFileTypeMPEG4;
    exporterSession.outputURL = [NSURL fileURLWithPath:mergePath]; //如果文件已存在,将造成导出失败
    exporterSession.videoComposition = videoComposition;
    exporterSession.audioMix = audioMix;
    exporterSession.shouldOptimizeForNetworkUse = YES; //用于互联网传输
    dispatch_semaphore_t sema = dispatch_semaphore_create(0);
    [exporterSession exportAsynchronouslyWithCompletionHandler:^{
        switch (exporterSession.status) {
            case AVAssetExportSessionStatusUnknown:
                NSLog(@"exporter Unknow");
                break;
            case AVAssetExportSessionStatusCancelled:
                NSLog(@"exporter Canceled");
                break;
            case AVAssetExportSessionStatusFailed:
                NSLog(@"exporter Failed");
                break;
            case AVAssetExportSessionStatusWaiting:
                NSLog(@"exporter Waiting");
                break;
            case AVAssetExportSessionStatusExporting:
                NSLog(@"exporter Exporting");
                break;
            case AVAssetExportSessionStatusCompleted:
                NSLog(@"exporter Completed");
                break;
        }
        dispatch_semaphore_signal(sema);
    }];
    dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);


结语


前前后后写了将近一个月的时间才把这个一期版本写完,如果喜欢欢迎给个Star,感谢阅读,如果有任何问题,欢迎评论区指导批评。最后再次附上SDVideoCamera地址。

SDVideoCamera传送门


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342