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]; } } }); }];