AVFoundation 『入門』-- 以微信小視屏為例

仿微信小視屏 - 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ī)錄取視頻只能用 AVFoundationAVCaptureSession 來捕捉。
  • 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)擊這里查看。

錄制前的準(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;    
}); 
  • 第(2/5)步,你得有將 攝像頭話筒 兩個(gè)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)步,你需要有一個(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)擊這里
  • 拿到的視頻大概 8S15.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 以下,但是還是存在以下問題:
  • 總之還有更好的辦法。

優(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ù)幀來操作我們可以利用AVCaptureVideoDataOutputAVCaptureAudioDataOutput 來實(shí)時(shí)的處理。
    • fiter 則可以使用 ffmpegGPUImageCoreImage 來處理。(暫時(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"/>
  • 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 看。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容