一、音视频数据编码同步合成FLV流
原始的音视频数据无法直接在网络上传输,推流需要编码后的音视频数据以合成的视频流,如flv, mov, asf流等,根据接收方需要的格式进行合成并传输,这就需要对音视频做同步,保证正确播放。
一般合成步骤如下图1-1所示:
图 1-1 音视频同步流程
对于iOS而言,我们可以通过底层API捕获视频帧与音频帧数据,捕获视频帧使用AVFoundation
框架中的AVCaptureSession
, 其实它同时也可以捕获音频数据,而因为我们想使用最低延时与最高音质的音频, 所以需要借助最底层的音频捕捉框架Audio Unit
,然后使用VideoToolbox
框架中的VTCompressionSessionRef
可以对视频数据进行编码,使用AudioConverter
可以对音频数据进行编码,我们在采集时可以将第一帧I帧产生时的系统时间作为音视频时间戳的一个起点,往后的视频说都基于此,由此可扩展做音视频同步方案,最终,我们将比那编码好的音视频数据通过FFmpeg进行合成,这里以asf流为例进行合成,并将生成好的asf流写入文件,以供测试. 生成好的asf流可直接用于网络传输.
主要步骤:
1、采集视频
- 创建
AVCaptureSession
对象 - 指定分辨率:
sessionPreset/activeFormat
,指定帧率setActiveVideoMinFrameDuration/setActiveVideoMaxFrameDuration
- 指定摄像头位置:
AVCaptureDevice
- 指定相机其他属性: 曝光,对焦,闪光灯,手电筒等等...
- 将摄像头数据源加入session
- 指定采集视频的格式:yuv,rgb....
kCVPixelBufferPixelFormatTypeKey
- 将输出源加入session
- 创建接收视频帧队列:
- (void)setSampleBufferDelegate:(nullable id<AVCaptureVideoDataOutputSampleBufferDelegate>)sampleBufferDelegate queue:(nullable dispatch_queue_t)sampleBufferCallbackQueue
- 将采集视频数据渲染到屏幕:
AVCaptureVideoPreviewLayer
- 在回调函数中获取视频帧数据:
CMSampleBufferRef
2、采集音频
- 配置音频格式ASBD: 采样率,声道数,采样位数,数据精度,每个包中字节数等等...
- 设置采样时间:
setPreferredIOBufferDuration
- 创建audio unit对象,指定分类.
AudioComponentInstanceNew
- 设置audio unit属性: 打开输入,禁止输出...
- 为接收的音频数据分配大小
kAudioUnitProperty_ShouldAllocateBuffer
- 设置接收数据的回调
- 开始audio unit:
AudioOutputUnitStart
- 在回调函数中获取音频数据:
AudioUnitRender
3、编码视频数据
- 指定编码器宽高类型回调并创建上下文对象:
VTCompressionSessionCreate
- 设置编码器属性:缓存帧数, 帧率, 平均码率, 最大码率, 实时编码, 是否重排序, 配置信息, 编码模式, I帧间隔时间等.
- 准备编码数据:
VTCompressionSessionPrepareToEncodeFrames
- 开始编码:
VTCompressionSessionEncodeFrame
- 回调函数中获取编码后的数据
CMBlockBufferRef
- 根据合成码流格式,这里是asf所以需要Annex B格式,自己组装sps,pps,start code.
4、编码音频数据
- 提供原始数据类型与编码后数据类型的ASBD
- 指定编码器类型
kAudioEncoderComponentType
- 创建编码器
AudioConverterNewSpecific
- 设置编码器属性: 比特率, 编码质量等
- 将1024个采样点原始PCM数据传入编码器
- 开始编码:
AudioConverterFillComplexBuffer
- 获取编码后的AAC数据
5、音视频同步
以编码的第一帧视频的系统时间作为音视频数据的基准时间戳,随后将采集到音视频数据中的时间戳减去该基准时间戳作为各自的时间戳, 同步有两种策略,一种是以音频时间戳为准, 即当出现错误时,让视频时间戳去追音频时间戳,这样做即会造成看到画面会快进或快退,二是以视频时间戳为准,即当出现错误时,让音频时间戳去追视时间戳,即声音可能会刺耳,不推荐.所以一般使用第一种方案,通过估计下一帧视频时间戳看看如果超出同步范围则进行同步。