iOS AVFoundation 视频暂停 多视频合成 流程
AVCaptureSession 只有开始和结束 编码的方法 。他并没有暂停的接口 。
所以我们要做暂停就有两种思路 。
- 点击暂停,就执行 stopRunning 方法 。恢复录制的时候重新录制下一个 。这样就录制了多个小视频 。然后手动把他们拼接起来 。
- 点击暂停的时候,CMSampleBufferRef 不写入 。恢复录制的时候,再继续写入 。
两种方法对应的功能点:
- 多段视频的拼接
- 时间偏移量(就是暂停的时候)的计算
音视频中的时间 CMTime
一个c结构体 ,包括:
typedef int64_t CMTimeValue : 分子
typedef int32_t CMTimeScale : 分母 (必须为正,vidio文件的推荐范围是movie files range from 600 to 90000)
typedef int64_t CMTimeEpoch : 类似循环次数(eg, loop number)加减法必须在同一个 epoch内
-
uint32_t, CMTimeFlags :标记位 (一个枚举值)
kCMTimeFlags_Valid = 1UL<<0,(必须设置 ,否则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
表示时间范围的一个数据类型 结构体:- CMTime : start起始时间
- CMTime : duration 持续时间
创建
- CMTimeRange timeRange1 = CMTimeRangeMake(start, duration);
- CMTimeRange timeRange2 = CMTimeRangeFromTimeToTime(t4, t3);
计算连个时间段的交集并集
- 交叉时间范围
CMTimeRange intersectionRange = CMTimeRangeGetIntersection(timeRange2, timeRange1);
- 总和时间范围
CMTimeRange unionRange = CMTimeRangeGetUnion(timeRange1, timeRange2);
--
(一)时间偏移量(就是暂停的时候)的计算
首先暂停的时候设置标志 discont = YES .
然后我们的操作都是在 CMSampleBufferRef 的编码回调中进行
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection;
-
第一种,如果是暂停的时候,进来的buffer 全部丢弃
if (self.discont) { if (isVideo) return; }
-
如果暂停结束,恢复录制 ,就要把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); } // 取得现在的时间 和 上一次时间 的时间差 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];
//调整媒体数据的时间 - (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 的持续时间
CMTime dur = CMSampleBufferGetDuration(sampleBuffer);
if (dur.value > 0) {
// 得到这个buffer 的结束时间,记录下来
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(), ^{ //如果转换成功 if ( exporter.status == AVAssetExportSessionStatusCompleted) { if ([_delegate respondsToSelector:@selector(videoRecorder:didFinishMergingVideosToOutPutFileAtURL:)]) { [_delegate videoRecorder:self didFinishMergingVideosToOutPutFileAtURL:mergeFileURL]; } } }); }];