仿微信小視屏 - iOS 技術(shù)路線實(shí)踐筆記【錄制篇】
一周之前拿到這個(gè)需求時(shí),我當(dāng)時(shí)是懵逼的,因?yàn)樽约簩?duì) 視頻 這一塊幾乎可以說是一無所知。在斷斷續(xù)續(xù)一周的研究過程之后,準(zhǔn)備寫點(diǎn)筆記記錄一下。
需求分析
-
對(duì)于一個(gè)類似微信小視屏的功能,大致需要完成的功能無非就是兩塊:
- 視頻錄制
- 視頻播放
先講講視頻錄制 - 技術(shù)路線
(因?yàn)樽约簩?duì)視頻是個(gè)小白,只能借助谷歌來搜索一些相關(guān)技術(shù),一定有什么不對(duì)的地方)
-
在 iOS 中與視頻錄制相關(guān)的技術(shù)大概有三種:
- UIImagePickerController:這是系統(tǒng)相機(jī)的控制器,使用很簡(jiǎn)單,但是可定制程度幾乎為零。
- AVFoundation:是一個(gè)可以用來使用和創(chuàng)建基于時(shí)間的視聽媒體的框架,它提供了一個(gè)能使用基于時(shí)間的視聽數(shù)據(jù)的接口。
- ffmpeg:一套可以用來記錄、轉(zhuǎn)換數(shù)字音頻、視頻,并能將其轉(zhuǎn)化為流的開源計(jì)算機(jī)程序。
看上去很懵逼是不是,其實(shí)我也是懵逼的。更甚至于AVFoundation 和 ffmpeg 兩者關(guān)系我最開始都摸不透。如果你和我一樣懵逼可以看一下。我寫的AVFoundation和視頻捕捉相關(guān)的總結(jié)。ffmpeg 則 需要去看雷神的博客了,很詳細(xì),也很入門。
- 對(duì)于以上三種,首先UIImagePickerController肯定不在考慮范圍之內(nèi)了,可定制化太低。
- 對(duì)于利用相機(jī)錄取視頻只能用 AVFoundation 的 AVCaptureSession 來捕捉。
- ffmpeg 技術(shù)更注重于后期處理技術(shù)。關(guān)于后期處理,ffmpeg 應(yīng)該是目前最強(qiáng)大的視頻處理技術(shù)了,利用CPU做視頻的編碼和解碼,俗稱為軟編軟解,目前很火的直播技術(shù)應(yīng)該都是用的ffmpeg。
- 此外,對(duì)于AVFoundation 而言,因?yàn)槭翘O果自己提供的視頻處理庫,也可以用于視頻后期處理而且還支持硬件編碼。
廢話不多說,上代碼。
對(duì)于 AVFoundation 捕捉只是還不是很清楚的可以點(diǎn)擊這里查看。
- Demo1 下載鏈接
錄制前的準(zhǔn)備工作
- 第(1/5)步,你得有一個(gè) AVCaptureSession? 對(duì)象,作為 輸入、輸出的 中間件。
@property (nonatomic, strong) AVCaptureSession *captureSession;/**< 捕捉會(huì)話 */
self.captureSession = ({
// 分辨率設(shè)置
AVCaptureSession *session = [[AVCaptureSession alloc] init];
if ([session canSetSessionPreset:AVCaptureSessionPresetHigh]) {
[session setSessionPreset:AVCaptureSessionPresetHigh];
}
session;
});
/// 初始化 捕捉輸入
- (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)步,你需要有一個(gè)視頻輸出 AVCaptureMovieFileOutput ?用于從AVCaptureDevice獲得的數(shù)據(jù)輸出到文件中。
//初始化設(shè)備輸出對(duì)象,用于獲得輸出數(shù)據(jù)
self.captureMovieFileOutput = ({
AVCaptureMovieFileOutput *output = [[AVCaptureMovieFileOutput alloc]init];
// 設(shè)置錄制模式
AVCaptureConnection *captureConnection=[output connectionWithMediaType:AVMediaTypeVideo];
if ([captureConnection isVideoStabilizationSupported ]) {
captureConnection.preferredVideoStabilizationMode = AVCaptureVideoStabilizationModeAuto;
}
//將設(shè)備輸出添加到會(huì)話中
if ([self.captureSession canAddOutput:output]) {
[self.captureSession addOutput:output];
}
output;
});
- 第(4/5)步,你得有一個(gè) AVCaptureVideoPreviewLayer ?的視圖,用于預(yù)覽 AVCaptureDevice 拿到的界面。
@property (nonatomic, strong) AVCaptureVideoPreviewLayer *captureVideoPreviewLayer; /**< 相機(jī)拍攝預(yù)覽圖層 */
//創(chuàng)建視頻預(yù)覽層,用于實(shí)時(shí)展示攝像頭狀態(tài)
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)步,現(xiàn)在你調(diào)用
[self.captureSession startRunning];
真機(jī)運(yùn)行就可以看到一個(gè)錄制畫面了。
錄制視頻
用 AVCaptureMovieFileOutput 錄制視頻很簡(jiǎn)單。代碼如下。
-(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];//停止錄制
}
}
查看錄制視頻
- 關(guān)于如何查看沙盒內(nèi)容可以點(diǎn)擊這里
- 拿到的視頻大概 8S。15.9 M 左右。Excuse me ?小視屏,15.9M?
- 莫急,可以壓縮嘛。
壓縮視頻
- 壓縮大概花了不到0.05秒,但是視頻減少了10倍左右,在 1M 以內(nèi)了。
-(void)videoCompression{
NSLog(@"begin");
NSURL *tempurl = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:@"temp.mov"]];
//加載視頻資源
AVAsset *asset = [AVAsset assetWithURL:tempurl];
//創(chuàng)建視頻資源導(dǎo)出會(huì)話
AVAssetExportSession *session = [[AVAssetExportSession alloc] initWithAsset:asset presetName:AVAssetExportPresetMediumQuality];
//創(chuàng)建導(dǎo)出視頻的URL
session.outputURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:@"tempLow.mov"]];
//必須配置輸出屬性
session.outputFileType = @"com.apple.quicktime-movie";
//導(dǎo)出視頻
[session exportAsynchronouslyWithCompletionHandler:^{
NSLog(@"end");
}];
}
ok!利用 AVFoundation 模仿小視屏功能就這么實(shí)現(xiàn)了~ 總結(jié)一下,如圖
哈哈哈,那是不可能的
- 雖然說,我們已經(jīng)利用攝像頭,能錄制視頻,且壓縮到1M 以下,但是還是存在以下問題:
- 我們選擇的尺寸不符合小視屏的尺寸。微信視頻的尺寸比例大概是4:3。可選預(yù)設(shè)
- 據(jù)iOS微信小視頻優(yōu)化心得說這樣很耗時(shí)。所有對(duì)視頻的處理都需要在錄制完成之后來做。
- 總之還有更好的辦法。
優(yōu)化方案
- 就前一種方案存在的不足主要有幾個(gè)方面:
- 1.可選的分辨率很少,而且如果設(shè)置低分辨率的話拍攝過程中也會(huì)比較模糊。
- 2.對(duì)于封邊率問題雖然可以在 壓縮過程中利用 AVMutableComposition 來實(shí)現(xiàn),但是存在一個(gè)問題是只有視頻錄制完成以后才能處理。大概需要的步驟是 錄制 -> 濾鏡 -> 碼率壓縮。而加濾鏡的過程中,還是需要取出視頻再按幀處理,再存入視頻。
- 完全可以設(shè)計(jì)采用一種,AVCapture 拿到 一幀,交給 fiter 處理,再利用 writer 根據(jù) setting 寫入文件。這也是iOS微信小視頻優(yōu)化心得所提供的思路。
- 關(guān)于根據(jù)幀來操作我們可以利用AVCaptureVideoDataOutput 和 AVCaptureAudioDataOutput 來實(shí)時(shí)的處理。
- 而 fiter 則可以使用 ffmpeg 、 GPUImage 、 CoreImage 來處理。(暫時(shí)先不處理,只提供思路)
- 最后就設(shè)置好參數(shù),利用writer 來處理。
總體分析
因?yàn)榇a比較多,就不貼出來了,需要的可以在這里下載
- 根據(jù)上面的分析,對(duì)于視頻錄制部分大致先分成三部分,一部分是讀(DWVideoRecoder)、一部分是寫(DWVideoWriter)、一部分是預(yù)覽(DWPreviewView).如下圖:
DWPreviewView
- 主要是一個(gè)預(yù)覽層,同時(shí)還需要處理 用戶 與 Session 之間的交互
DWVideoRecoder
- Session 的配置與控制
- Device 的控制與配置
DWVideoWriter
- 設(shè)置videoSetting 和 audioSetting 的參數(shù),將每一幀通過幀壓縮與濾鏡過濾之后,寫入文件中
- 視頻具體的參數(shù)設(shè)置
- VideoOutputSettings
Key | |
---|---|
AVVideoCodecKey | 編碼格式,一般選h264,硬件編碼 |
AVVideoScalingModeKey | 填充模式,AVVideoScalingModeResizeAspectFill拉伸填充 |
AVVideoWidthKey | 視頻寬度,以手機(jī)水平,home 在右邊的方向 |
AVVideoHeightKey | 視頻高度,以手機(jī)水平,home 在右邊的方向 |
AVVideoCompressionPropertiesKey | 壓縮參數(shù) |
- AVVideoCompressionPropertiesKey
Key | |
---|---|
AVVideoAverageBitRateKey | 視頻尺寸*比率 比率10.1相當(dāng)于AVCaptureSessionPresetHigh數(shù)值越大越精細(xì) |
AVVideoMaxKeyFrameIntervalKey | 關(guān)鍵幀最大間隔,1為每個(gè)都是關(guān)鍵幀,數(shù)值越大壓縮率越高 |
AVVideoProfileLevelKey | 默認(rèn)選擇 AVVideoProfileLevelH264BaselineAutoLevel |
- 對(duì)于壓縮 只需要控制比率就可以了
后記
- iOS 開發(fā)真的是越來越簡(jiǎn)單了。最開始搜怎么實(shí)現(xiàn)的時(shí)候直接出現(xiàn)了好幾個(gè) SDK,大概就是直接導(dǎo)入照著文檔寫兩下就能用的那種。可能自己覺得這樣太 low 所以決定自己嘗試一下去實(shí)現(xiàn),覺得有很多收獲,視頻開發(fā)算是入門了吧,寫下這篇總結(jié)希望能給大家一點(diǎn)幫助,也給自己一個(gè)技術(shù)沉淀。
番外篇
<a name="AVFoundation"/>
<a name="AVFoundation1"/>
關(guān)于AVFoundation 捕捉
<a name="AVCaptureSession"/>
-
AVCaptureSession 捕捉會(huì)話
- AVCaptureSession 從捕捉設(shè)備(物理)得到數(shù)據(jù)流,比如攝像頭、麥克風(fēng),輸出到一個(gè)或多個(gè)目的地。
- AVCaptureSession 可以動(dòng)態(tài)配置輸入輸出的線路,在會(huì)話進(jìn)行中按需重新配置捕捉環(huán)境。
- AVCaptureSession 可以額外配置一個(gè)會(huì)話預(yù)設(shè)值(session preset),用來控制捕捉數(shù)據(jù)的格式和質(zhì)量。會(huì)話預(yù)設(shè)值默認(rèn)為AVCaptureSessionPresetHigh。
<a name="AVCaptureDevice"/>
-
AVCaptureDevice 捕捉設(shè)備
- AVCaptureDevice 針對(duì)物理硬件設(shè)備定義了大量的控制方法,比如控制攝像頭的對(duì)焦、曝光、白平衡和閃光燈。
<a name="AVCaptureDeviceInput"/>
-
AVCaptureDeviceInput 捕捉設(shè)備的輸入
- 在使用捕捉設(shè)備進(jìn)行處理之前,需要將它添加到捕捉會(huì)話的輸入。不過一個(gè)設(shè)備不能直接添加到AVCaptureSession 中,需要利用 AVCaptureDeviceInput 的一個(gè)實(shí)例封裝起來添加。
<a name="AVCaptureOutput"/>
-
AVCaptureOutput 捕捉設(shè)備的輸出
- 如上文所提,AVCaptureSession 會(huì)從AVCaptureDevice拿數(shù)據(jù)流,并輸出到一個(gè)或者多個(gè)目的地,這個(gè)目的地就是 AVCaptureOutput。
- 首先 AVCaptureOutput 是一個(gè)基類,AVFoundation 為我們提供了 四個(gè) 擴(kuò)展類。
- AVCaptureStillImageOutput 捕捉靜態(tài)照片(拍照)
- AVCaptureMovieFileOutput 捕捉視頻(視頻 + 音頻)
- AVCaptureVideoDataOutput 視頻錄制數(shù)據(jù)流
- AVCaptureAudioDataOutput 音頻錄制數(shù)據(jù)流
- AVCaptureVideoDataOutput 和 AVCaptureAudioDataOutput 可以更好的音頻視頻實(shí)時(shí)處理
對(duì)于以上四者的關(guān)系,類似于 AVCaptureSession 是過濾器,AVCaptureDevice 是“原始”材料,AVCaptureDeviceInput 是 AVCaptureDevice 的收集器,AVCaptureOutput 就是產(chǎn)物了。
<a name="AVCaptureConnection"/>
-
AVCaptureConnection 捕捉連接
- 那么問題來了,上面四者之間的“導(dǎo)管”是什么呢?那就是 AVCaptureConnection。利用AVCaptureConnection 可以很好的將這幾個(gè)獨(dú)立的功能件很好的連接起來。
<a name="AVCaptureVideoPreviewLayer"/>
- 那么問題來了,上面四者之間的“導(dǎo)管”是什么呢?那就是 AVCaptureConnection。利用AVCaptureConnection 可以很好的將這幾個(gè)獨(dú)立的功能件很好的連接起來。
-
AVCaptureVideoPreviewLayer 捕捉預(yù)覽
- 以上所有的數(shù)據(jù)處理,都是在代碼中執(zhí)行的,用戶無法看到AVCaptureSession到底在做什么事情。所以AVFoundation 為我們提供了一個(gè)叫做 AVCaptureVideoPreviewLayer 的東西,提供實(shí)時(shí)預(yù)覽。
- AVCaptureVideoPreviewLayer 是 CoreAnimation 的 CALayer 的子類。
- 關(guān)于預(yù)覽層的填充模式有 AVLayerVideoGravityResizeAspect、AVLayerVideoGravityResizeAspectFill、AVLayerVideoGravityResize三種
<a name="SandBox"/>
如何查看真機(jī)沙盒里面的文件
-
Xcode -> Window -> Devices
-
選中真機(jī),再右邊選中你要導(dǎo)出沙盒的項(xiàng)目,然后點(diǎn)擊最下面的設(shè)置按鈕,然后Download Container.
<a name="SessionPreset"/>
可選預(yù)設(shè)
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;
參考文章:
Android & IOS視頻錄制技術(shù)方案
iOS微信小視頻優(yōu)化心得
iOS仿微信小視頻功能開發(fā)優(yōu)化記錄
在 iOS 上捕獲視頻
參考書本:
《AV Foundation 開發(fā)秘籍》
Github:
SCRecorder
VideoCaptureDemo
更多
工作之余,寫了點(diǎn)筆記,如果需要可以在我的 GitHub 看。