iOS AVFoundation 視頻暫停 多視頻合成 流程

iOS AVFoundation 視頻暫停 多視頻合成 流程

AVCaptureSession 只有開始和結(jié)束 編碼的方法 。他并沒有暫停的接口 。

所以我們要做暫停就有兩種思路 。

  • 點擊暫停驻子,就執(zhí)行 stopRunning 方法 。恢復錄制的時候重新錄制下一個 哄酝。這樣就錄制了多個小視頻 。然后手動把他們拼接起來 祷膳。
  • 點擊暫停的時候陶衅,CMSampleBufferRef 不寫入 ≈背浚恢復錄制的時候搀军,再繼續(xù)寫入 。

兩種方法對應的功能點:

  • 多段視頻的拼接
  • 時間偏移量(就是暫停的時候)的計算

音視頻中的時間 CMTime

一個c結(jié)構(gòu)體 勇皇,包括:

  • typedef int64_t CMTimeValue : 分子

  • typedef int32_t CMTimeScale : 分母 (必須為正罩句,vidio文件的推薦范圍是movie files range from 600 to 90000)

  • typedef int64_t CMTimeEpoch : 類似循環(huán)次數(shù)(eg, loop number)加減法必須在同一個 epoch內(nèi)

  • uint32_t, CMTimeFlags :標記位 (一個枚舉值)

          kCMTimeFlags_Valid = 1UL<<0,(必須設(shè)置 ,否則CMTime無效)
          kCMTimeFlags_HasBeenRounded = 1UL<<1,
          kCMTimeFlags_PositiveInfinity = 1UL<<2,(正無窮)
          kCMTimeFlags_NegativeInfinity = 1UL<<3,(負無窮)
          kCMTimeFlags_Indefinite = 1UL<<4,(時間未定的時候敛摘,如實況直播的時候)
          kCMTimeFlags_ImpliedValueFlagsMask = 
          kCMTimeFlags_PositiveInfinity |     
          kCMTimeFlags_NegativeInfinity |     
          kCMTimeFlags_Indefinite
    

second=value/timescale

  • 加法
    CMTime t3 = CMTimeAdd(t1, t2);

  • 減法
    CMTime t4 = CMTimeSubtract(t3, t1);

  • 獲取second

Float64 CMTimeGetSeconds(CMTime time)

  • CMTimeRange
    表示時間范圍的一個數(shù)據(jù)類型 結(jié)構(gòu)體:

    • CMTime : start起始時間
    • CMTime : duration 持續(xù)時間
創(chuàng)建
  • CMTimeRange timeRange1 = CMTimeRangeMake(start, duration);
  • CMTimeRange timeRange2 = CMTimeRangeFromTimeToTime(t4, t3);

計算連個時間段的交集并集

  • 交叉時間范圍
    CMTimeRange intersectionRange = CMTimeRangeGetIntersection(timeRange2, timeRange1);
  • 總和時間范圍
    CMTimeRange unionRange = CMTimeRangeGetUnion(timeRange1, timeRange2);

--

(一)時間偏移量(就是暫停的時候)的計算

首先暫停的時候設(shè)置標志 discont = YES .

然后我們的操作都是在 CMSampleBufferRef 的編碼回調(diào)中進行

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection;

  • 第一種门烂,如果是暫停的時候,進來的buffer 全部丟棄

    if (self.discont) 
    {
        if (isVideo) 
            return;
    }
    
  • 如果暫停結(jié)束兄淫,恢復錄制 屯远,就要把buffer 傳輸給AVAssetWriter

    // 得到當前buffer 的時間
            CMTime pts = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
            CMTime last = isVideo ? _lastVideo : _lastAudio;
            if (last.flags & kCMTimeFlags_Valid) {
                
                // 一開始錄制的時候  _timeOffset = CMTimeMake(0, 0);
    
                // kCMTimeFlags_Valid 是否有效
                if (_timeOffset.flags & kCMTimeFlags_Valid) {
                    // CMTime 減法
                    pts = CMTimeSubtract(pts, _timeOffset);
                }
                // 取得現(xiàn)在的時間 和 上一次時間 的時間差
                CMTime offset = CMTimeSubtract(pts, last);
                // 賦值給 _timeOfSet
                if (_timeOffset.value == 0) {
                    _timeOffset = offset;
                }else {
                    _timeOffset = CMTimeAdd(_timeOffset, offset);
                }
            }
            // 清空記錄
            _lastVideo.flags = 0;
            _lastAudio.flags = 0;
        }
        
            sampleBuffer = [self adjustTime:sampleBuffer by:_timeOffset];
        
    
    //調(diào)整媒體數(shù)據(jù)的時間
    - (CMSampleBufferRef)adjustTime:(CMSampleBufferRef)sample by:(CMTime)offset {
    CMItemCount count;
    CMSampleBufferGetSampleTimingInfoArray(sample, 0, nil, &count);
    CMSampleTimingInfo* pInfo = malloc(sizeof(CMSampleTimingInfo) * count);
    CMSampleBufferGetSampleTimingInfoArray(sample, count, pInfo, &count);
    for (CMItemCount i = 0; i < count; i++) {
        pInfo[i].decodeTimeStamp = CMTimeSubtract(pInfo[i].decodeTimeStamp, offset);
        pInfo[i].presentationTimeStamp = CMTimeSubtract(pInfo[i].presentationTimeStamp, offset);
    }
    CMSampleBufferRef sout;
    CMSampleBufferCreateCopyWithNewTiming(nil, sample, count, pInfo, &sout);
    free(pInfo);
    return sout;
    

}

```

//記錄住這次buffer 的時間       

```
//sampleBuffer的起點時間
    CMTime pts = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
    //sampleBuffer 的持續(xù)時間
    CMTime dur = CMSampleBufferGetDuration(sampleBuffer);
    if (dur.value > 0) {
        // 得到這個buffer 的結(jié)束時間,記錄下來
        pts = CMTimeAdd(pts, dur);
    }
    if (isVideo) {
        _lastVideo = pts;
    }else {
        _lastAudio = pts;
    }

```     

最后就可以存儲了捕虽。       

(二)多段視頻的拼接

  • 獲取 錄制好的一組 媒體資源 AVAsset

    for (NSURL *fileURL in fileURLArray) {
            AVAsset *asset = [AVAsset assetWithURL:fileURL];
            
            if (!asset) {
                continue;
            }
            
            [assetArray addObject:asset];
     }
    
    
  • 對多個資源的操作需要用到 AVMutableComposition

    // 組成
        AVMutableComposition *mixComposition = [[AVMutableComposition alloc] init];
    
  • AVMutableComposition給每一段資源生成對應的 音頻軌道和視頻軌道 慨丐,把資源添加進軌道

    for (int i = 0; i < [assetArray count] ; i++) {
            
            AVAsset *asset = [assetArray objectAtIndex:i];
            AVAssetTrack *assetTrack = [assetTrackArray objectAtIndex:i];
            
            //一個 audio 軌道
            //AVMutableCompositionTrack provides a convenient interface for insertion, removals, and scaling of track
            //合成音頻軌道    進行插入、縮放泄私、刪除
            AVMutableCompositionTrack *audioTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
            //把第一段錄制的 audio 插入到 AVMutableCompositionTrack
            [audioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, asset.duration)
                                ofTrack:[[asset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0]
                                 atTime:totalDuration
                                  error:nil];
            //合成視頻軌道
            AVMutableCompositionTrack *videoTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
            //把錄制的第一段 視頻軌道插入到 AVMutableCompositionTrack
            [videoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, asset.duration)
                                ofTrack:assetTrack
                                 atTime:totalDuration
                                  error:&error];
                                  
    }                                  
    
  • 合成 需要使用AVAssetExportSession

    
        AVAssetExportSession *exporter = [[AVAssetExportSession alloc] initWithAsset:mixComposition presetName:AVAssetExportPresetMediumQuality];
        exporter.outputURL = mergeFileURL;
        exporter.outputFileType = AVFileTypeMPEG4;
        exporter.shouldOptimizeForNetworkUse = YES;
        [exporter exportAsynchronouslyWithCompletionHandler:^{
            dispatch_async(dispatch_get_main_queue(), ^{
                //如果轉(zhuǎn)換成功
                if ( exporter.status == AVAssetExportSessionStatusCompleted)
                {
                    if ([_delegate respondsToSelector:@selector(videoRecorder:didFinishMergingVideosToOutPutFileAtURL:)]) {
                        [_delegate videoRecorder:self didFinishMergingVideosToOutPutFileAtURL:mergeFileURL];
                    }
    
                }
            });
        }];
    
    
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末房揭,一起剝皮案震驚了整個濱河市备闲,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌崩溪,老刑警劉巖浅役,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異伶唯,居然都是意外死亡觉既,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門乳幸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來瞪讼,“玉大人,你說我怎么就攤上這事粹断》罚” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵瓶埋,是天一觀的道長希柿。 經(jīng)常有香客問我,道長养筒,這世上最難降的妖魔是什么曾撤? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮晕粪,結(jié)果婚禮上挤悉,老公的妹妹穿的比我還像新娘。我一直安慰自己巫湘,他們只是感情好装悲,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著尚氛,像睡著了一般诀诊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上怠褐,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天畏梆,我揣著相機與錄音,去河邊找鬼奈懒。 笑死,一個胖子當著我的面吹牛宪巨,可吹牛的內(nèi)容都是我干的磷杏。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼捏卓,長吁一口氣:“原來是場噩夢啊……” “哼极祸!你這毒婦竟也來了慈格?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤遥金,失蹤者是張志新(化名)和其女友劉穎浴捆,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體稿械,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡选泻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了美莫。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片页眯。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖厢呵,靈堂內(nèi)的尸體忽然破棺而出窝撵,到底是詐尸還是另有隱情,我是刑警寧澤襟铭,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布碌奉,位于F島的核電站,受9級特大地震影響寒砖,放射性物質(zhì)發(fā)生泄漏赐劣。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一入撒、第九天 我趴在偏房一處隱蔽的房頂上張望隆豹。 院中可真熱鬧,春花似錦茅逮、人聲如沸璃赡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽碉考。三九已至,卻和暖如春挺身,著一層夾襖步出監(jiān)牢的瞬間侯谁,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工章钾, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留墙贱,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓贱傀,卻偏偏與公主長得像惨撇,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子府寒,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

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