仿微信小视屏 iOS 技术路线实践笔记[录制篇]

1,646 阅读8分钟
原文链接: github.com

一周之前拿到这个需求时,我当时是懵逼的,因为自己对 视频 这一块几乎可以说是一无所知。在断断续续一周的研究过程之后,准备写点笔记记录一下。

需求分析

  • 对于一个类似微信小视屏的功能,大致需要完成的功能无非就是两块:

    • 视频录制
    • 视频播放

先讲讲视频录制 - 技术路线

(因为自己对视频是个小白,只能借助谷歌来搜索一些相关技术,一定有什么不对的地方)

  • 在 iOS 中与视频录制相关的技术大概有三种:

    • UIImagePickerController:这是系统相机的控制器,使用很简单,但是可定制程度几乎为零。
    • AVFoundation:是一个可以用来使用和创建基于时间的视听媒体的框架,它提供了一个能使用基于时间的视听数据的接口。
    • ffmpeg:一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。

看上去很懵逼是不是,其实我也是懵逼的。更甚至于AVFoundation 和 ffmpeg 两者关系我最开始都摸不透。如果你和我一样懵逼可以看一下。我写的AVFoundation和视频捕捉相关的总结。ffmpeg 则 需要去看 雷神的博客了,很详细,也很入门。

  • 对于以上三种,首先UIImagePickerController肯定不在考虑范围之内了,可定制化太低。
  • 对于利用相机录取视频只能用 AVFoundationAVCaptureSession 来捕捉。
  • ffmpeg 技术更注重于后期处理技术。关于后期处理,ffmpeg 应该是目前最强大的视频处理技术了,利用CPU做视频的编码和解码,俗称为软编软解,目前很火的直播技术应该都是用的ffmpeg
  • 此外,对于AVFoundation 而言,因为是苹果自己提供的视频处理库,也可以用于视频后期处理而且还支持硬件编码。

废话不多说,上代码。

对于 AVFoundation 捕捉只是还不是很清楚的可以点击这里查看。

录制前的准备工作

  • 第(1/5)步,你得有一个 AVCaptureSession? 对象,作为 输入、输出的 中间件
@property (nonatomic, strong) AVCaptureSession *captureSession;/**< 捕捉会话 */
self.captureSession = ({
    // 分辨率设置
  AVCaptureSession *session = [[AVCaptureSession alloc] init];
    if ([session canSetSessionPreset:AVCaptureSessionPresetHigh]) {
        [session setSessionPreset:AVCaptureSessionPresetHigh];
    }
    session;	
});	
  • 第(2/5)步,你得有将 摄像头话筒 两个AVCaptureDevice?添加到 AVCaptureSessionAVCaptureDeviceInput ?中。
/// 初始化 捕捉输入
- (BOOL)setupSessionInputs:(NSError **)error {
	
	// 添加 摄像头
	AVCaptureDeviceInput *videoInput = [AVCaptureDeviceInput deviceInputWithDevice:({
		
		[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
		
	}) error:error];
	
	if (!videoInput) { return NO; }
	
	if ([self.captureSession canAddInput:videoInput]) {
		[self.captureSession addInput:videoInput];
	}else{
		return NO;
	}
	
	// 添加 话筒
	AVCaptureDeviceInput *audioInput = [AVCaptureDeviceInput deviceInputWithDevice:({
		
		[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
		
	}) error:error];
	
	if (!audioInput)  { return NO; }
	
	if ([self.captureSession canAddInput:audioInput]) {
		[self.captureSession addInput:audioInput];
	}else{
		return NO;
	}
	
	return YES;
}
  • 第(3/5)步,你需要有一个视频输出 AVCaptureMovieFileOutput ?用于从AVCaptureDevice获得的数据输出到文件中。
    //初始化设备输出对象,用于获得输出数据
	self.captureMovieFileOutput = ({
		AVCaptureMovieFileOutput *output = [[AVCaptureMovieFileOutput alloc]init];
		// 设置录制模式
		AVCaptureConnection *captureConnection=[output connectionWithMediaType:AVMediaTypeVideo];
		if ([captureConnection isVideoStabilizationSupported ]) {
			captureConnection.preferredVideoStabilizationMode = AVCaptureVideoStabilizationModeAuto;
		}
		//将设备输出添加到会话中
		if ([self.captureSession canAddOutput:output]) {
			[self.captureSession addOutput:output];
		}
		output;
	});
  • 第(4/5)步,你得有一个 AVCaptureVideoPreviewLayer ?的视图,用于预览 AVCaptureDevice 拿到的界面。
@property (nonatomic, strong) AVCaptureVideoPreviewLayer *captureVideoPreviewLayer; /**< 相机拍摄预览图层 */
	//创建视频预览层,用于实时展示摄像头状态
	self.captureVideoPreviewLayer = ({
		AVCaptureVideoPreviewLayer *previewLayer = [[AVCaptureVideoPreviewLayer alloc]initWithSession:self.captureSession];
		previewLayer.frame=  CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height);
		previewLayer.videoGravity=AVLayerVideoGravityResizeAspectFill;//填充模式
		[self.view.layer addSublayer:previewLayer];
		self.view.layer.masksToBounds = YES;
		previewLayer;
		
	});
  • 第(5/5)步,现在你调用 [self.captureSession startRunning]; 真机运行就可以看到一个录制画面了。

录制视频

用 AVCaptureMovieFileOutput 录制视频很简单。代码如下。

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
	if (![self.captureMovieFileOutput isRecording]) {
		AVCaptureConnection *captureConnection=[self.captureMovieFileOutput connectionWithMediaType:AVMediaTypeVideo];
		captureConnection.videoOrientation=[self.captureVideoPreviewLayer connection].videoOrientation;
		[self.captureMovieFileOutput startRecordingToOutputFileURL:({
			// 录制 缓存地址。
			NSURL *url = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:@"temp.mov"]];
			if ([[NSFileManager defaultManager] fileExistsAtPath:url.path]) {
				[[NSFileManager defaultManager] removeItemAtURL:url error:nil];
			}
			url;
		}) recordingDelegate:self];
	}else{
		[self.captureMovieFileOutput stopRecording];//停止录制
	}
}

查看录制视频

  • 关于如何查看沙盒内容可以点击这里
  • 拿到的视频大概 8S15.9 M 左右。Excuse me ?小视屏,15.9M
  • 莫急,可以压缩嘛。

压缩视频

  • 压缩大概花了不到0.05秒,但是视频减少了10倍左右,在 1M 以内了。
-(void)videoCompression{
	
	NSLog(@"begin");
	NSURL *tempurl = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:@"temp.mov"]];
	//加载视频资源
	AVAsset *asset = [AVAsset assetWithURL:tempurl];
	//创建视频资源导出会话
	AVAssetExportSession *session = [[AVAssetExportSession alloc] initWithAsset:asset presetName:AVAssetExportPresetMediumQuality];
	//创建导出视频的URL
	session.outputURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:@"tempLow.mov"]];
	//必须配置输出属性
	session.outputFileType = @"com.apple.quicktime-movie";
	//导出视频
	[session exportAsynchronouslyWithCompletionHandler:^{
                NSLog(@"end");
	}];

}

ok!利用 AVFoundation 模仿小视屏功能就这么实现了~ 总结一下,如图

录制视频

哈哈哈,那是不可能的

  • 虽然说,我们已经利用摄像头,能录制视频,且压缩到1M 以下,但是还是存在以下问题:
    • 我们选择的尺寸不符合小视屏的尺寸。微信视频的尺寸比例大概是4:3。可选预设
    • iOS微信小视频优化心得说这样很耗时。所有对视频的处理都需要在录制完成之后来做。
  • 总之还有更好的办法。

优化方案

  • 就前一种方案存在的不足主要有几个方面:
    • 1.可选的分辨率很少,而且如果设置低分辨率的话拍摄过程中也会比较模糊。
    • 2.对于封边率问题虽然可以在 压缩过程中利用 AVMutableComposition 来实现,但是存在一个问题是只有视频录制完成以后才能处理。大概需要的步骤是 录制 -> 滤镜 -> 码率压缩。而加滤镜的过程中,还是需要取出视频再按帧处理,再存入视频。
  • 完全可以设计采用一种,AVCapture 拿到 一帧,交给 fiter 处理,再利用 writer 根据 setting 写入文件。这也是iOS微信小视频优化心得所提供的思路。
    • 关于根据帧来操作我们可以利用AVCaptureVideoDataOutputAVCaptureAudioDataOutput 来实时的处理。
    • fiter 则可以使用 ffmpegGPUImageCoreImage 来处理。(暂时先不处理,只提供思路)
    • 最后就设置好参数,利用writer 来处理。

总体分析

因为代码比较多,就不贴出来了,需要的可以在这里下载

  • 根据上面的分析,对于视频录制部分大致先分成三部分,一部分是读(DWVideoRecoder)、一部分是写(DWVideoWriter)、一部分是预览(DWPreviewView).如下图:

优化版

DWPreviewView

  • 主要是一个预览层,同时还需要处理 用户 与 Session 之间的交互

DWVideoRecoder

  • Session 的配置与控制
  • Device 的控制与配置

DWVideoWriter

  • 设置videoSetting 和 audioSetting 的参数,将每一帧通过帧压缩与滤镜过滤之后,写入文件中
  • 视频具体的参数设置
  • VideoOutputSettings
Key
AVVideoCodecKey 编码格式,一般选h264,硬件编码
AVVideoScalingModeKey 填充模式,AVVideoScalingModeResizeAspectFill拉伸填充
AVVideoWidthKey 视频宽度,以手机水平,home 在右边的方向
AVVideoHeightKey 视频高度,以手机水平,home 在右边的方向
AVVideoCompressionPropertiesKey 压缩参数
  • AVVideoCompressionPropertiesKey
Key
AVVideoAverageBitRateKey 视频尺寸*比率 比率10.1相当于AVCaptureSessionPresetHigh数值越大越精细
AVVideoMaxKeyFrameIntervalKey 关键帧最大间隔,1为每个都是关键帧,数值越大压缩率越高
AVVideoProfileLevelKey 默认选择 AVVideoProfileLevelH264BaselineAutoLevel
  • 对于压缩 只需要控制比率就可以了

##后记

  • iOS 开发真的是越来越简单了。最开始搜怎么实现的时候直接出现了好几个 SDK,大概就是直接导入照着文档写两下就能用的那种。可能自己觉得这样太 low 所以决定自己尝试一下去实现,觉得有很多收获,视频开发算是入门了吧,写下这篇总结希望能给大家一点帮助,也给自己一个技术沉淀。

##番外篇

### 关于AVFoundation 捕捉 - **AVCaptureSession** 捕捉会话 - AVCaptureSession 从捕捉设备(物理)得到数据流,比如摄像头、麦克风,输出到一个或多个目的地。 - AVCaptureSession 可以动态配置输入输出的线路,在会话进行中按需重新配置捕捉环境。 - AVCaptureSession 可以额外配置一个会话预设值(session preset),用来控制捕捉数据的格式和质量。会话预设值默认为AVCaptureSessionPresetHigh。 - **AVCaptureDevice** 捕捉设备 - AVCaptureDevice 针对物理硬件设备定义了大量的控制方法,比如控制摄像头的对焦、曝光、白平衡和闪光灯。 - **AVCaptureDeviceInput** 捕捉设备的输入 - 在使用捕捉设备进行处理之前,需要将它添加到捕捉会话的输入。不过一个设备不能直接添加到AVCaptureSession 中,需要利用 AVCaptureDeviceInput 的一个实例封装起来添加。 - **AVCaptureOutput** 捕捉设备的输出 - 如上文所提,AVCaptureSession 会从AVCaptureDevice拿数据流,并输出到一个或者多个目的地,这个目的地就是 AVCaptureOutput。 - 首先 AVCaptureOutput 是一个基类,AVFoundation 为我们提供了 四个 扩展类。 - AVCaptureStillImageOutput 捕捉静态照片(拍照) - AVCaptureMovieFileOutput 捕捉视频(视频 + 音频) - AVCaptureVideoDataOutput 视频录制数据流 - AVCaptureAudioDataOutput 音频录制数据流 - AVCaptureVideoDataOutput 和 AVCaptureAudioDataOutput 可以更好的音频视频实时处理
> 对于以上四者的关系,类似于 AVCaptureSession 是过滤器,AVCaptureDevice 是“原始”材料,AVCaptureDeviceInput 是 AVCaptureDevice 的收集器,AVCaptureOutput 就是产物了。
- **AVCaptureConnection** 捕捉连接 - 那么问题来了,上面四者之间的“导管”是什么呢?那就是 AVCaptureConnection。利用AVCaptureConnection 可以很好的将这几个独立的功能件很好的连接起来。 - **AVCaptureVideoPreviewLayer** 捕捉预览 - 以上所有的数据处理,都是在代码中执行的,用户无法看到AVCaptureSession到底在做什么事情。所以AVFoundation 为我们提供了一个叫做 AVCaptureVideoPreviewLayer 的东西,提供实时预览。 - AVCaptureVideoPreviewLayer 是 CoreAnimation 的 CALayer 的子类。 - 关于预览层的填充模式有 AVLayerVideoGravityResizeAspect、AVLayerVideoGravityResizeAspectFill、AVLayerVideoGravityResize三种 ### 如何查看真机沙盒里面的文件 - Xcode -> Window -> Devices ![](https://github.com/Damonvvong/DevNotes/blob/master/images/videorecoder_3.jpg) - 选中真机,再右边选中你要导出沙盒的项目,然后点击最下面的设置按钮,然后Download Container. ![](https://github.com/Damonvvong/DevNotes/blob/master/images/videorecoder_4.jpg)

可选预设

NSString *const  AVCaptureSessionPresetPhoto;
NSString *const  AVCaptureSessionPresetHigh;
NSString *const  AVCaptureSessionPresetMedium;
NSString *const  AVCaptureSessionPresetLow;
NSString *const  AVCaptureSessionPreset352x288;
NSString *const  AVCaptureSessionPreset640x480;
NSString *const  AVCaptureSessionPreset1280x720;
NSString *const  AVCaptureSessionPreset1920x1080;
NSString *const  AVCaptureSessionPresetiFrame960x540;
NSString *const  AVCaptureSessionPresetiFrame1280x720;
NSString *const  AVCaptureSessionPresetInputPriority;