闲话不多讲,先上项目Github传送门。
又是好久没有升级博客了,哈哈哈,因为近来从公司离任,再加上近来要结婚的缘故,所以有大量充足的时间来整理以前写的一个仿写抖音录制的三方库。但是讲真的,写了将近一个月的时间,还有有很多细节没有实现,只是实现其核心功能,还是有很多地方可以优化的。另外抖音是使用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的静态库,所以就暂时放弃了,至于想实现美颜功能的童鞋,请根据下面的说明自行进行增加修改。
我们看一下该模块下所有相关的类。该功能主要存在于 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部分,我就不过多进行诉述了。
上一个模块我们简单的聊了一下视频录制,这个模块的入口是通过上传视频得到的,效果如下所示。
该模块涉及到的类主要存放于 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 这个参数主要是用来截取视频用的,有很多童鞋可能刚刚理解这块,我建议看一下CMTime 和 CMTimeRange 两个结构体的使用与操作方法,这里就不过多叙述了。所以我们在截取视频的时候需要知道从哪个视频数据的那个时间点开始,那个视频的那个时间点结束。这样我们合成的时候只要要增加对应的时间位置和时间长度的视频源就可。代码如下。
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地址。