前言
最近想做个简单的带滤镜功能的录制器,于是乎就想到GPUImage
这个远古且强大的滤镜库,其中GPUImage
的GPUImageMovieWriter
可以实现录制功能,用起来比较简单,对于我这种OpenGL小白十分友好。
然而,开发过程中,发现这个录制器的暂停功能是有问题的。
这种情况,最好是换一个库,毕竟GPUImage
已经很久没有更新了,以后问题估计会越来越多,但是此时录制相关的逻辑都写得差不多了,这沉没成本有点大。。。
最终决定,专门针对这个录制器进行修复吧。
最终效果
Demo地址:JPMovieWriter_Demo,可暂停、续录、定时、带滤镜美颜的录制器,另附GIF制作。
暂停问题
GPUImageMovieWriter
目前存在的问题是:暂停又重录会黑屏或者有画面没声音。
经过调试发现:
-
多次暂停恢复后,GPUImageVideoCamera的
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
这个方法会采集不了音频。 -
经百度谷歌发现这篇文章也是发现这个问题,但是注释了文章提到的那段代码后,虽然音频能继续采集,但是多次暂停恢复,偏差会越来越大,最终导致音画不同步(有时候声音卡了,有时候是画面卡了,最后连声音也没了)。
// 文中提及注释的地方
if (offsetTime.value > 0) {
CFRelease(audioBuffer);
audioBuffer = [self adjustTime:audioBuffer by:offsetTime];
CFRetain(audioBuffer);
}
解决方案
GPUImageMovieWriter
内部是通过AVAssetWriter
实现的录制功能,但AVAssetWriter
原本就没有暂停的功能,而GPUImageMovieWriter
是通过一些延时算法来实现,然而并不靠谱,多次暂停后偏差会越来越离谱。
继续百度谷歌还有ChatGPT的询问后,最好办法就是每次暂停后,换一个新的AVAssetWriter
重新录制,这是最保险的解决方案。
由于GPUImageMovieWriter
除了能录制,内部还有对视频帧进行滤镜渲染,为了不影响这部分的逻辑,我的做法就是完全拷贝一个新的GPUImageMovieWriter
文件,然后只针对AVAssetWriter
的部分进行改造:移除【暂停】这个功能,只能【完成】录制,不同的是,【完成】录制后,可以【再次】录制。
这就是JPMovieWriter的由来,一个可以重复录制,并且带滤镜渲染功能的录制器。
新的特性
- 可重复、重新录制:每次录制都是一个全新的AVAssetWriter,因此每次”暂停“都是新的一个视频,也可以清空已录制部分重新开始录制,且在内部实现,无需重新设置滤镜渲染链(重新设置页面会明显卡顿)。
- 可设置最大录制时长:录制过程中会不断监听录制的时间,达到最大值会自动停止录制。
JPMovieWriter
可以完全代替GPUImageMovieWriter
,因为除了AVAssetWriter
的部分,其余地方几乎没有改动,并且也做了大量的代码结构性优化,更加面向对象化,方便阅读和调试。
注意点
虽然说能重录,但每次录制都是新的一个视频,所以最终结束录制时,会返回一个存放在Temp文件夹的视频URL数组,鉴于以便应付不同的需求,内部并没有将多段视频合成一个视频的实现,数据将原汁原味保留(合成的逻辑Demo中有实现)。
遵守JPMovieWriterDelegate
协议,监听录制过程,录制结束后合成并导出:
// MARK: - <JPMovieWriterDelegate>
extension RecordViewController: JPMovieWriterDelegate {
// AVAssetWriter重置错误的回调(例如目标路径已存在文件)
func movieWriter(_ writer: JPMovieWriter, resetFailed recordedURLs: [URL], error: Error) {
JPProgressHUD.showError(withStatus: (error as NSError).localizedDescription, userInteractionEnabled: true)
}
// 录制进度的回调
func movieWriter(_ writer: JPMovieWriter, recording recordDuration: TimeInterval, totalDuration: TimeInterval) {
let progress = recordDuration / totalDuration
controlBtn.setProgress(progress, animated: false)
}
// 录制即将结束的回调
func movieWriter(_ writer: JPMovieWriter, recordWillDone error: Error?) {
JPProgressHUD.show(withStatus: error == nil ? "正在结束录制" : "录制发生错误")
controlBar.isEnabled = false
}
// 录制已经结束的回调
func movieWriter(_ writer: JPMovieWriter, recordDone recordedURLs: [URL], error: Error?) {
if let error = error as? NSError {
// 录制发送错误
return
}
// 录制完成,将视频碎片合成并导出
VideoTool.mergeVideos(recordedURLs, videoSize: UIConfig.videoSize, contentMode: .scaleAspectFit, maxDuration: CMTime(value: Int64(RecordConfig.videoMaxDuration), timescale: 1)) { mergedFilePath in
// mergedFilePath:合成好的路径 -> 缓存
} faild: { kError in
// 合成或导出失败
}
}
}
Demo介绍
JPMovieWriter_Demo:可暂停、续录、定时、带滤镜美颜的录制器,另附GIF制作。没什么技术含量,不过可以玩一下。
Feature:
✅ 可暂停、定时、续录;
✅ 多种滤镜选择;
✅ 基础美颜功能;
✅ 支持相册视频的导入和拼接;
✅ GIF制作;
TODO:
🔘 更多的滤镜和特效;
🔘 更加定制化的GIF制作;
🔘 视频编辑。
美颜滤镜
GIF制作
生成图:
其他
- 首页的瀑布流布局:WaterfallLayoutDemo
- 封面编辑裁剪:JPCrop
- 视频GIF制作:JPImageresizerView
最后
原本想做一个GIF制作(用于微信斗图)、同时也能随时随地记录心情的App,但是最近有更重要的事情要先去做,还有很多页面和功能都没写完,这个项目只能搁置了。