先附上參考資料
http://www.lxweimin.com/p/16cb14f53933
https://developer.apple.com/library/content/samplecode/AVSimpleEditoriOS/Introduction/Intro.html
https://github.com/objcio/VideoCaptureDemo
https://github.com/gsixxxx/DTSmallVideo
https://github.com/AndyFightting/VideoRecord
卷首吐槽語(yǔ)
這還是第一次接觸自定義界面錄制視頻,包括各種參數(shù)的設(shè)置,不得不說,錄制視頻這塊,各種類,各種方法,蠻復(fù)雜的,網(wǎng)上的資料也是各種雜亂,想要弄清楚還真是得費(fèi)一番功夫,我參考了大量資料,根據(jù)自己的思路整理了一遍,按照我的思路來,保證你看一遍就會(huì),我這里只是簡(jiǎn)單的錄制,壓縮,剪裁,導(dǎo)出等功能,不設(shè)計(jì)濾鏡,添加背景音樂,合并,字幕等等,重要的是這個(gè)流程,主流程會(huì)了,其他也就是錦上添花了。
先附上dome demo地址
我的blog看更加方便,左側(cè)有目錄
點(diǎn)擊進(jìn)入我的blog文章地址
腦圖
方便大家對(duì)三中錄制方式有一個(gè)大概的了解,看一下這張圖片。

第一種采用系統(tǒng)的錄制較為簡(jiǎn)單,詳細(xì)介紹后面兩種。
效果圖






demo中把三種方式單獨(dú)分開,便于學(xué)習(xí)。支持閃光燈,切換鏡頭,錄制不同尺寸的視頻等。
1.UIImagePickerController
這種方式只能設(shè)置一些簡(jiǎn)單參數(shù),自定義程度不高,只能自定義界面上的操作按鈕,還有視頻的畫質(zhì)等。
- (void)viewDidLoad
{
[super viewDidLoad];
if ([self isVideoRecordingAvailable]) {
return;
}
self.sourceType = UIImagePickerControllerSourceTypeCamera;
self.mediaTypes = @[(NSString *)kUTTypeMovie];
self.delegate = self;
//隱藏系統(tǒng)自帶UI
self.showsCameraControls = NO;
//設(shè)置攝像頭
[self switchCameraIsFront:NO];
//設(shè)置視頻畫質(zhì)類別
self.videoQuality = UIImagePickerControllerQualityTypeMedium;
//設(shè)置散光燈類型
self.cameraFlashMode = UIImagePickerControllerCameraFlashModeAuto;
//設(shè)置錄制的最大時(shí)長(zhǎng)
self.videoMaximumDuration = 20;
}
- (BOOL)isVideoRecordingAvailable
{
if([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]){
NSArray *availableMediaTypes = [UIImagePickerController availableMediaTypesForSourceType:UIImagePickerControllerSourceTypeCamera];
if([availableMediaTypes containsObject:(NSString *)kUTTypeMovie]){
return YES;
}
}
return NO;
}
- (void)switchCameraIsFront:(BOOL)front
{
if (front) {
if([UIImagePickerController isCameraDeviceAvailable:UIImagePickerControllerCameraDeviceFront]){
[self setCameraDevice:UIImagePickerControllerCameraDeviceFront];
}
} else {
if([UIImagePickerController isCameraDeviceAvailable:UIImagePickerControllerCameraDeviceRear]){
[self setCameraDevice:UIImagePickerControllerCameraDeviceRear];
}
}
}
2.AVCaptureSession+AVCaptureMovieFileOutput
流程:
1. 創(chuàng)建捕捉會(huì)話
2. 設(shè)置視頻的輸入
3. 設(shè)置音頻的輸入
4. 輸出源設(shè)置,這里視頻,音頻數(shù)據(jù)會(huì)合并到一起輸出,在代理方法中國(guó)也可以單獨(dú)拿到視頻或者音頻數(shù)據(jù),給AVCaptureMovieFileOutput指定路徑,開始錄制之后就會(huì)向這個(gè)路徑寫入數(shù)據(jù)
5. 添加視頻預(yù)覽層
6. 開始采集數(shù)據(jù),這個(gè)時(shí)候還沒有寫入數(shù)據(jù),用戶點(diǎn)擊錄制后就可以開始寫入數(shù)據(jù)
0. 創(chuàng)建捕捉會(huì)話
self.session = [[AVCaptureSession alloc] init];
if ([_session canSetSessionPreset:AVCaptureSessionPreset640x480]) {//設(shè)置分辨率
_session.sessionPreset=AVCaptureSessionPreset640x480;
}
1. 視頻的輸入
- (void)setUpVideo
{
// 1.1 獲取視頻輸入設(shè)備(攝像頭)
AVCaptureDevice *videoCaptureDevice=[self getCameraDeviceWithPosition:AVCaptureDevicePositionBack];//取得后置攝像頭
// 視頻 HDR (高動(dòng)態(tài)范圍圖像)
// videoCaptureDevice.videoHDREnabled = YES;
// 設(shè)置最大,最小幀速率
//videoCaptureDevice.activeVideoMinFrameDuration = CMTimeMake(1, 60);
// 1.2 創(chuàng)建視頻輸入源
NSError *error=nil;
self.videoInput= [[AVCaptureDeviceInput alloc] initWithDevice:videoCaptureDevice error:&error];
// 1.3 將視頻輸入源添加到會(huì)話
if ([self.session canAddInput:self.videoInput]) {
[self.session addInput:self.videoInput];
}
}
2. 音頻的輸入
// 2.1 獲取音頻輸入設(shè)備
AVCaptureDevice *audioCaptureDevice=[[AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio] firstObject];
NSError *error=nil;
// 2.2 創(chuàng)建音頻輸入源
self.audioInput = [[AVCaptureDeviceInput alloc] initWithDevice:audioCaptureDevice error:&error];
// 2.3 將音頻輸入源添加到會(huì)話
if ([self.session canAddInput:self.audioInput]) {
[self.session addInput:self.audioInput];
}
3.輸出源設(shè)置
- (void)setUpFileOut
{
// 3.1初始化設(shè)備輸出對(duì)象,用于獲得輸出數(shù)據(jù)
self.FileOutput=[[AVCaptureMovieFileOutput alloc]init];
// 3.2設(shè)置輸出對(duì)象的一些屬性
AVCaptureConnection *captureConnection=[self.FileOutput connectionWithMediaType:AVMediaTypeVideo];
//設(shè)置防抖
//視頻防抖 是在 iOS 6 和 iPhone 4S 發(fā)布時(shí)引入的功能。到了 iPhone 6,增加了更強(qiáng)勁和流暢的防抖模式,被稱為影院級(jí)的視頻防抖動(dòng)。相關(guān)的 API 也有所改動(dòng) (目前為止并沒有在文檔中反映出來,不過可以查看頭文件)。防抖并不是在捕獲設(shè)備上配置的,而是在 AVCaptureConnection 上設(shè)置。由于不是所有的設(shè)備格式都支持全部的防抖模式,所以在實(shí)際應(yīng)用中應(yīng)事先確認(rèn)具體的防抖模式是否支持:
if ([captureConnection isVideoStabilizationSupported ]) {
captureConnection.preferredVideoStabilizationMode=AVCaptureVideoStabilizationModeAuto;
}
//預(yù)覽圖層和視頻方向保持一致
captureConnection.videoOrientation = [self.previewlayer connection].videoOrientation;
// 3.3將設(shè)備輸出添加到會(huì)話中
if ([_session canAddOutput:_FileOutput]) {
[_session addOutput:_FileOutput];
}
}
4. 視頻預(yù)覽層
一進(jìn)入視頻錄制界面,這個(gè)時(shí)候 session就已經(jīng)在采集數(shù)據(jù)了,并把數(shù)據(jù)顯示在預(yù)覽層上,用戶選擇錄制后,再將采集到的數(shù)據(jù)寫入文件。
- (void)setUpPreviewLayerWithType:(FMVideoViewType )type
{
CGRect rect = CGRectZero;
switch (type) {
case Type1X1:
rect = CGRectMake(0, 0, kScreenWidth, kScreenWidth);
break;
case Type4X3:
rect = CGRectMake(0, 0, kScreenWidth, kScreenWidth*4/3);
break;
case TypeFullScreen:
rect = [UIScreen mainScreen].bounds;
break;
default:
rect = [UIScreen mainScreen].bounds;
break;
}
self.previewlayer.frame = rect;
[_superView.layer insertSublayer:self.previewlayer atIndex:0];
}
5. 開始采集畫面
[self.session startRunning];
6.開始錄制
- (void)writeDataTofile
{
NSString *videoPath = [self createVideoFilePath];
self.videoUrl = [NSURL fileURLWithPath:videoPath];
[self.FileOutput startRecordingToOutputFileURL:self.videoUrl recordingDelegate:self];
}
3.AVCaptureSession+AVAssetWriter
流程:
1. 創(chuàng)建捕捉會(huì)話
2. 設(shè)置視頻的輸入 和 輸出
3. 設(shè)置音頻的輸入 和 輸出
4. 添加視頻預(yù)覽層
5. 開始采集數(shù)據(jù),這個(gè)時(shí)候還沒有寫入數(shù)據(jù),用戶點(diǎn)擊錄制后就可以開始寫入數(shù)據(jù)
6. 初始化AVAssetWriter, 我們會(huì)拿到視頻和音頻的數(shù)據(jù)流,用AVAssetWriter寫入文件,這一步需要我們自己實(shí)現(xiàn)。
1. 創(chuàng)建捕捉會(huì)話
需要確保在同一個(gè)隊(duì)列,最好隊(duì)列只創(chuàng)建一次
self.session = [[AVCaptureSession alloc] init];
if ([_session canSetSessionPreset:AVCaptureSessionPreset640x480]) {//設(shè)置分辨率
_session.sessionPreset=AVCaptureSessionPreset640x480;
}
2.設(shè)置視頻的輸入 和 輸出
- (void)setUpVideo
{
// 2.1 獲取視頻輸入設(shè)備(攝像頭)
AVCaptureDevice *videoCaptureDevice=[self getCameraDeviceWithPosition:AVCaptureDevicePositionBack];//取得后置攝像頭
// 2.2 創(chuàng)建視頻輸入源
NSError *error=nil;
self.videoInput= [[AVCaptureDeviceInput alloc] initWithDevice:videoCaptureDevice error:&error];
// 2.3 將視頻輸入源添加到會(huì)話
if ([self.session canAddInput:self.videoInput]) {
[self.session addInput:self.videoInput];
}
self.videoOutput = [[AVCaptureVideoDataOutput alloc] init];
self.videoOutput.alwaysDiscardsLateVideoFrames = YES; //立即丟棄舊幀,節(jié)省內(nèi)存,默認(rèn)YES
[self.videoOutput setSampleBufferDelegate:self queue:self.videoQueue];
if ([self.session canAddOutput:self.videoOutput]) {
[self.session addOutput:self.videoOutput];
}
}
3. 設(shè)置音頻的輸入 和 輸出
- (void)setUpAudio
{
// 2.2 獲取音頻輸入設(shè)備
AVCaptureDevice *audioCaptureDevice=[[AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio] firstObject];
NSError *error=nil;
// 2.4 創(chuàng)建音頻輸入源
self.audioInput = [[AVCaptureDeviceInput alloc] initWithDevice:audioCaptureDevice error:&error];
// 2.6 將音頻輸入源添加到會(huì)話
if ([self.session canAddInput:self.audioInput]) {
[self.session addInput:self.audioInput];
}
self.audioOutput = [[AVCaptureAudioDataOutput alloc] init];
[self.audioOutput setSampleBufferDelegate:self queue:self.videoQueue];
if([self.session canAddOutput:self.audioOutput]) {
[self.session addOutput:self.audioOutput];
}
}
4. 添加視頻預(yù)覽層
- (void)setUpPreviewLayerWithType:(FMVideoViewType )type
{
CGRect rect = CGRectZero;
switch (type) {
case Type1X1:
rect = CGRectMake(0, 0, kScreenWidth, kScreenWidth);
break;
case Type4X3:
rect = CGRectMake(0, 0, kScreenWidth, kScreenWidth*4/3);
break;
case TypeFullScreen:
rect = [UIScreen mainScreen].bounds;
break;
default:
rect = [UIScreen mainScreen].bounds;
break;
}
self.previewlayer.frame = rect;
[_superView.layer insertSublayer:self.previewlayer atIndex:0];
}
5. 開始采集畫面
[self.session startRunning];
6. 初始化AVAssetWriter
AVAssetWriter 寫入數(shù)據(jù)的過程需要在子線程中執(zhí)行,并且每次寫入數(shù)據(jù)都需要保證在同一個(gè)線程。
- (void)setUpWriter
{
self.videoUrl = [[NSURL alloc] initFileURLWithPath:[self createVideoFilePath]];
self.writeManager = [[AVAssetWriteManager alloc] initWithURL:self.videoUrl viewType:_viewType];
self.writeManager.delegate = self;
}
7.拿到數(shù)據(jù)流后處理
視頻數(shù)據(jù)和音頻數(shù)據(jù)需要分開處理
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
@autoreleasepool {
//視頻
if (connection == [self.videoOutput connectionWithMediaType:AVMediaTypeVideo]) {
if (!self.writeManager.outputVideoFormatDescription) {
@synchronized(self) {
CMFormatDescriptionRef formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer);
self.writeManager.outputVideoFormatDescription = formatDescription;
}
} else {
@synchronized(self) {
if (self.writeManager.writeState == FMRecordStateRecording) {
[self.writeManager appendSampleBuffer:sampleBuffer ofMediaType:AVMediaTypeVideo];
}
}
}
}
//音頻
if (connection == [self.audioOutput connectionWithMediaType:AVMediaTypeAudio]) {
if (!self.writeManager.outputAudioFormatDescription) {
@synchronized(self) {
CMFormatDescriptionRef formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer);
self.writeManager.outputAudioFormatDescription = formatDescription;
}
}
@synchronized(self) {
if (self.writeManager.writeState == FMRecordStateRecording) {
[self.writeManager appendSampleBuffer:sampleBuffer ofMediaType:AVMediaTypeAudio];
}
}
}
}
}
我們拿到最原始的數(shù)據(jù)以后,可以對(duì)其進(jìn)行各種參數(shù)的設(shè)置
- (void)setUpWriter
{
self.assetWriter = [AVAssetWriter assetWriterWithURL:self.videoUrl fileType:AVFileTypeMPEG4 error:nil];
//寫入視頻大小
NSInteger numPixels = self.outputSize.width * self.outputSize.height;
//每像素比特
CGFloat bitsPerPixel = 6.0;
NSInteger bitsPerSecond = numPixels * bitsPerPixel;
// 碼率和幀率設(shè)置
NSDictionary *compressionProperties = @{ AVVideoAverageBitRateKey : @(bitsPerSecond),
AVVideoExpectedSourceFrameRateKey : @(30),
AVVideoMaxKeyFrameIntervalKey : @(30),
AVVideoProfileLevelKey : AVVideoProfileLevelH264BaselineAutoLevel };
//視頻屬性
self.videoCompressionSettings = @{ AVVideoCodecKey : AVVideoCodecH264,
AVVideoScalingModeKey : AVVideoScalingModeResizeAspectFill,
AVVideoWidthKey : @(self.outputSize.height),
AVVideoHeightKey : @(self.outputSize.width),
AVVideoCompressionPropertiesKey : compressionProperties };
_assetWriterVideoInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:self.videoCompressionSettings];
//expectsMediaDataInRealTime 必須設(shè)為yes,需要從capture session 實(shí)時(shí)獲取數(shù)據(jù)
_assetWriterVideoInput.expectsMediaDataInRealTime = YES;
_assetWriterVideoInput.transform = CGAffineTransformMakeRotation(M_PI / 2.0);
// 音頻設(shè)置
self.audioCompressionSettings = @{ AVEncoderBitRatePerChannelKey : @(28000),
AVFormatIDKey : @(kAudioFormatMPEG4AAC),
AVNumberOfChannelsKey : @(1),
AVSampleRateKey : @(22050) };
_assetWriterAudioInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio outputSettings:self.audioCompressionSettings];
_assetWriterAudioInput.expectsMediaDataInRealTime = YES;
if ([_assetWriter canAddInput:_assetWriterVideoInput]) {
[_assetWriter addInput:_assetWriterVideoInput];
}else {
NSLog(@"AssetWriter videoInput append Failed");
}
if ([_assetWriter canAddInput:_assetWriterAudioInput]) {
[_assetWriter addInput:_assetWriterAudioInput];
}else {
NSLog(@"AssetWriter audioInput Append Failed");
}
self.writeState = FMRecordStateRecording;
}
設(shè)置好參數(shù)以后,就可以寫入文件了。AVAssetWriter數(shù)據(jù)寫入的過程有點(diǎn)復(fù)雜,demo中我新建AVAssetWriteManager分離出AVAssetWriter,單獨(dú)處理寫數(shù)據(jù),這樣邏輯會(huì)清晰一點(diǎn)。
fileOut和writer的相同點(diǎn)和不同點(diǎn)
從上面的兩個(gè)流程大致可以看出來,
相同點(diǎn):數(shù)據(jù)采集都在AVCaptureSession中進(jìn)行,視頻和音頻的輸入都一樣,畫面的預(yù)覽一致。
不同點(diǎn):
輸出不一致, AVCaptureMovieFileOutput 只需要一個(gè)輸出即可,指定一個(gè)文件路后,視頻和音頻會(huì)寫入到指定路徑,不需要其他復(fù)雜的操作。
AVAssetWriter 需要 AVCaptureVideoDataOutput 和 AVCaptureAudioDataOutput 兩個(gè)單獨(dú)的輸出,拿到各自的輸出數(shù)據(jù)后,然后自己進(jìn)行相應(yīng)的處理。
可配參數(shù)不一致,AVAssetWriter可以配置更多的參數(shù)。
視頻剪裁不一致,AVCaptureMovieFileOutput 如果要剪裁視頻,因?yàn)橄到y(tǒng)已經(jīng)把數(shù)據(jù)寫到文件中了,我們需要從文件中獨(dú)到一個(gè)完整的視頻,然后處理;而AVAssetWriter我們拿到的是數(shù)據(jù)流,還沒有合成視頻,對(duì)數(shù)據(jù)流進(jìn)行處理,所以兩則剪裁方式也是不一樣。
其他添加背景音樂,水印等也是不一樣的,這里沒有涉及就不介紹了。到這里也差不多了,文章也有點(diǎn)長(zhǎng)了。這些是我自己整理資料總結(jié)出來的,不排除會(huì)有一些錯(cuò)誤之處,供大家學(xué)習(xí)參考,希望有所收獲。如果方便,還請(qǐng)為我star一個(gè),也算是對(duì)我的支持。