【iOS】自定義相機(jī)(九)錄像進(jìn)階

Action

在前面【iOS】自定義相機(jī)(六)拍照錄像中伶棒,我們介紹了如何使用AVCaptureMovieFileOutput進(jìn)行視頻的錄制糯而。AVCaptureMovieFileOutput雖然十分方便,但是我們無法使用AVCaptureVideoDataOutput獲取幀圖片進(jìn)行處理坤候。因此這一篇文章就介紹一下直接使用AVCaptureVideoDataOutputAVCaptureAudioDataOutput進(jìn)行視頻的錄制的姿勢。

對應(yīng)的代碼在SCCamera中的SCMovieManager.mSCCameraController.m中歉胶。

AVCaptureVideoDataOutputSampleBufferDelegateAVCaptureAudioDataOutputSampleBufferDelegatecaptureOutput:didOutputSampleBuffer:fromConnection:中我們可以獲取到音視頻數(shù)據(jù)CMSampleBufferRef婿斥。通過使用AVAssetWriter我們就能達(dá)到劝篷,將每一幀的視頻和音頻寫入文件的目的。從而模擬出AVCaptureMovieFileOutput的效果民宿。

AVAssetWriter 使用

AVFoundation 中提供了直接讀寫視頻媒體樣本的類娇妓,分別為AVAssetReaderAVAssetWriterAVAssetReader 用于將資源從 AVAsset 實(shí)例中讀取媒體樣本活鹰,AVAssetWriter 用于對媒體資源進(jìn)行編碼并將其寫入到容器文件中哈恰。

AVAssetWriter是一個用于寫入資源的類,他由一個或多個AVAssetWriterInput組成志群,用于添加要寫入容器的特定的媒體樣本的CMSampleBuffer着绷。AVAssetWriterInput需要配置指定的媒體類型,比如音頻或視頻锌云。對于自定義相機(jī)的視頻錄制荠医,AVAssetWriter的使用如下:

AVAssetWriter 寫入過程

AVAssetWriter的創(chuàng)建十分簡單,只需要指定目標(biāo)容器的NSURL即可。重點(diǎn)需要注意的地方是AVAssetWriterInput

  • 創(chuàng)建AVAssetWriterInput需要的outputSettings
  • 音視頻樣本寫入模式

outputSettings

outputSettings是一個NSDictionary<NSString *, id>彬向,用鍵值對的方式表示音視頻編碼與壓縮相關(guān)信息豫喧,例子如下:

NSDictionary *outSettings = @{
    AVVideoCodecKey: AVVideoCodecH264,
    AVVideoWidthKey: @1280,
    AVVideoHeightKey: @720,
    AVVideoCompressionPropertiesKey: @{
        AVVideoMaxKeyFrameIntervalKey: @1,
        AVVideoAverageBitRateKey: @105000000,
        AVVideoProfileLevelKey: AVVideoProfileLevelH264Main31
    }
}

逐個鍵值對進(jìn)行設(shè)置可控性比較強(qiáng),但容易寫錯幢泼。方便起見紧显,我們可以使用AVCaptureVideoDataOutputAVCaptureAudioDataOutputrecommendedVideoSettingsForAssetWriterWithOutputFileType:方法。只需要傳入我們需要導(dǎo)出的文件類型缕棵,即可獲取一個當(dāng)前系統(tǒng)可以使用的一個settings字典孵班。

音視頻樣本寫入模式

我們都知道,在使用AVCaptureVideoOutputAVCaotureAudioOutput進(jìn)行視頻錄制中招驴,音頻數(shù)據(jù)和視頻數(shù)據(jù)是分開獲取的篙程。因此我們在使用AVAssetWriter進(jìn)行寫入視頻的時候需要注意數(shù)據(jù)的寫入順序。為了使數(shù)據(jù)有較高效率的排列别厘,存儲設(shè)備能更方便有效地讀取數(shù)據(jù)虱饿,并在播放和尋找資源時提高性能,我們在音視頻樣本獲取的時候需要采用交錯模式触趴。

交錯模式:音頻數(shù)據(jù)和視頻數(shù)據(jù)是逐個交錯排列的氮发。

AVAssetWriter中我們并不能直接設(shè)置寫入模式,但是在AVAssetWriterInput中的readyForMoreMediaData屬性可用于判斷該input是否可以進(jìn)行數(shù)據(jù)拼接冗懦。只需input在使用appendSampleBuffer:寫入數(shù)據(jù)之前進(jìn)行一層判斷爽冕,即可控制最后的音視頻樣本排列方式為交錯模式

錄制視頻步驟

為了方便編寫披蕉,我將關(guān)于AVAssetWriter的代碼都放進(jìn)了SCMovieManager中颈畸,完整代碼點(diǎn)這里。錄制視頻分為三個步驟没讲,分別是:

  • 錄制開始
  • 錄制過程
  • 錄制結(jié)束

錄制開始

錄制開始需要做的步驟有以下三步:

- (void)startRecordWithVideoSettings:(NSDictionary *)videoSettings
                       audioSettings:(NSDictionary *)audioSettings
                              handle:(void (^ _Nullable)(NSError * _Nonnull))handle {
    dispatch_async(self.movieQueue, ^{
        // 第一步:創(chuàng)建 AVAssetWriter
        // 第二步:創(chuàng)建視頻輸入
        // 第三步:創(chuàng)建音頻輸入
        self.recording = YES;
        self.firstSample = YES;
    });
}

PS: self.firstSample用于控制錄制第一幀必須是視頻幀眯娱。

第一步:創(chuàng)建 AVAssetWriter

NSError *error;
self.movieWriter = [AVAssetWriter assetWriterWithURL:self.movieURL fileType:AVFileTypeQuickTimeMovie error:&error];
if (!self.movieWriter || error) {
    NSLog(@"movieWriter error.");
    return;
}

PS:self.movieURL需要開發(fā)者自己管理好,最好在每次錄制之前保證該路徑下并沒有文件爬凑。

第二步:創(chuàng)建視頻輸入

self.movieVideoInput = [[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeVideo outputSettings:videoSettings];
// 針對實(shí)時性進(jìn)行優(yōu)化
self.movieVideoInput.expectsMediaDataInRealTime = YES;

if ([self.movieWriter canAddInput:self.movieVideoInput]) {
    [self.movieWriter addInput:self.movieVideoInput];
} else {
    NSLog(@"Unable to add video input.");
}

PS:如果應(yīng)用程序只支持一個視頻方向就需要根據(jù)當(dāng)前設(shè)備方向做圖像旋轉(zhuǎn)轉(zhuǎn)換徙缴,并設(shè)置self.movieVideoInput.transform屬性。

第三步:創(chuàng)建音頻輸入

self.movieAudioInput = [[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeAudio outputSettings:audioSettings];
// 針對實(shí)時性進(jìn)行優(yōu)化
self.movieAudioInput.expectsMediaDataInRealTime = YES;
if ([self.movieWriter canAddInput:self.movieAudioInput]) {
    [self.movieWriter addInput:self.movieAudioInput];
} else {
    NSLog(@"Unable to add audio input.");
}

錄制過程

使用AVCaptureVideoDataOutputAVCaptureAudioDataOutput進(jìn)行獲取數(shù)據(jù)的時候贰谣,我們都需要實(shí)現(xiàn)下面的代理方法并進(jìn)行樣本數(shù)據(jù)寫入:

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection{
    if (self.movieManager.isRecording) {
        [self.movieManager recordSampleBuffer:sampleBuffer];
    }
}

recordSampleBuffer:方法是錄制的核心方法娜搂,主要是根據(jù)mediaType區(qū)分音頻和視頻數(shù)據(jù)進(jìn)行相應(yīng)的寫入。

- (void)recordSampleBuffer:(CMSampleBufferRef)sampleBuffer {
    if (!self.isRecording) {
        return;
    }
    CMFormatDescriptionRef formatDesc = CMSampleBufferGetFormatDescription(sampleBuffer);
    CMMediaType mediaType = CMFormatDescriptionGetMediaType(formatDesc);
    
    if (mediaType == kCMMediaType_Video) {
        // 視頻數(shù)據(jù)處理
        CMTime timestamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
        if (self.isFirstSample) {
            if ([self.movieWriter startWriting]) {
                [self.movieWriter startSessionAtSourceTime: timestamp];
            } else {
                NSLog(@"Failed to start writing.");
            }
            self.firstSample = NO;
        }
        if (self.movieVideoInput.readyForMoreMediaData) {
            if (![self.movieVideoInput appendSampleBuffer:sampleBuffer]) {
                NSLog(@"Error appending video sample buffer.");
            }
        }
    } else if (!self.firstSample && mediaType == kCMMediaType_Audio) {
        // 音頻數(shù)據(jù)處理(已處理至少一個視頻數(shù)據(jù))
        if (self.movieAudioInput.readyForMoreMediaData) {
            if (![self.movieAudioInput appendSampleBuffer:sampleBuffer]) {
                NSLog(@"Error appending audio sample buffer.");
            }
        }
    }
}

錄制結(jié)束

錄制結(jié)束比較簡單吱抚,主要是就是調(diào)用self.movieWriterfinishWritingWithCompletionHandler:方式百宇,并根據(jù)self.movieWriter的狀態(tài)進(jìn)行后續(xù)的處理。

- (void)stopRecordWithCompletion:(void (^)(BOOL, NSURL * _Nullable))completion {
    self.recording = NO;
    dispatch_async(self.movieQueue, ^{
        [self.movieWriter finishWritingWithCompletionHandler:^{
            switch (self.movieWriter.status) {
                case AVAssetWriterStatusCompleted:{
                    self.firstSample = YES;
                    NSURL *fileURL = [self.movieWriter outputURL];
                    completion(YES, fileURL);
                    
                    // FIXME: - 測試用保存
                    [self saveMovieToCameraRoll:fileURL authHandle:^(BOOL success, PHAuthorizationStatus status) {
                        NSLog(@"相冊添加權(quán)限:%d, %ld", success, (long)status);
                    } completion:^(BOOL success, NSError * _Nullable error) {
                        NSLog(@"視頻添加結(jié)果:%d, %@", success, error);
                    }];
                    break;
                }
                default:
                    NSLog(@"Failed to write movie: %@", self.movieWriter.error);
                    break;
            }
        }];
    });
}

最后在fileURL中的視頻文件就是我們錄制的成果了秘豹。

由于錄制的全過程并不需要在主線程中進(jìn)行操作携御,因此,我們都需要在self.movieQueue這個串行隊(duì)列異步執(zhí)行

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末啄刹,一起剝皮案震驚了整個濱河市涮坐,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌誓军,老刑警劉巖袱讹,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異昵时,居然都是意外死亡捷雕,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進(jìn)店門壹甥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來救巷,“玉大人,你說我怎么就攤上這事句柠∑忠耄” “怎么了?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵溯职,是天一觀的道長精盅。 經(jīng)常有香客問我,道長缸榄,這世上最難降的妖魔是什么渤弛? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮甚带,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘佳头。我一直安慰自己鹰贵,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布康嘉。 她就那樣靜靜地躺著碉输,像睡著了一般。 火紅的嫁衣襯著肌膚如雪亭珍。 梳的紋絲不亂的頭發(fā)上敷钾,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天,我揣著相機(jī)與錄音肄梨,去河邊找鬼阻荒。 笑死,一個胖子當(dāng)著我的面吹牛众羡,可吹牛的內(nèi)容都是我干的侨赡。 我是一名探鬼主播,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼羊壹!你這毒婦竟也來了蓖宦?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤油猫,失蹤者是張志新(化名)和其女友劉穎稠茂,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體情妖,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡睬关,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了鲫售。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片共螺。...
    茶點(diǎn)故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖情竹,靈堂內(nèi)的尸體忽然破棺而出藐不,到底是詐尸還是另有隱情,我是刑警寧澤秦效,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布雏蛮,位于F島的核電站,受9級特大地震影響阱州,放射性物質(zhì)發(fā)生泄漏挑秉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一苔货、第九天 我趴在偏房一處隱蔽的房頂上張望犀概。 院中可真熱鬧,春花似錦夜惭、人聲如沸姻灶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽产喉。三九已至,卻和暖如春敢会,著一層夾襖步出監(jiān)牢的瞬間曾沈,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工鸥昏, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留塞俱,地道東北人。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓互广,卻偏偏與公主長得像敛腌,于是被迫代替她去往敵國和親卧土。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評論 2 360

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