JNI——FFmpeg音视频同步(二)
使用OpenSL_ES播放声音
之前的播放声音是转码得到数据后主动放进AudioTrack进行播放,OpenSL就属于被动了,播放器开始播放先从缓冲区取数据,播放完了调用回调方法再去获取数据。
OpenSL_ES 跟JNI里的env 很类似,通过结构体实现对象的概念。
OpenSL ES的开发流程主要有如下6个步骤:
1、创建引擎对象
2、创建播放器(录音器)
3、设置缓冲队列和回调函数
4、设置播放状态
5、启动回调函数
其中创建播放器有需要设置混音器
其中3和4是播放PCM等数据格式的音频是需要用到的。
创建OpenSL_ES 对象有个生命周期的概念。
创建成功后,都进入 SL_OBJECT_STATE_UNREALIZED 状态,这种状态下,系统不会为它分配任何资源,直到调用 Realize 函数为止。
Realize 后的对象,就会进入 SL_OBJECT_STATE_REALIZED 状态,这是一种“可用”的状态,只有在这种状态下,对象的各个功能和资源才能正常地访问。
当调用对象的 Destroy 函数后,则会释放资源,并回到 SL_OBJECT_STATE_UNREALIZED 状态。
生命周期播放pcm:
第一步:创建播放引擎
第二步:创建播放器
创建播放器参数
channel可以设置为2。
创建播放器需要设置混音器
其他两个参数
真的要创建播放器了
第三步:设置缓冲队列和回调函数
第四步:设置播放状态
可以初始化音量控制对象,调节声音大小。
第五步:启动回调函数
播放URI
跟上面的差不多,就是设置SLDataSource这个参数的时候不一样,还不用调用回调方法。
音视频同步:
av_samples_get_buffer_size()可以得到一帧视频的大小size,采样率为什么44100,1秒的数据大小为44100*2*2(双通道16位),一帧的音频播放时间为size除以44100*2*2。
音视频的同步不是绝对的同步,以音频播放时间为基准(因为人对视频的感知比音频弱),视频或快或慢来同步,用音频帧播放时间减去视频帧播放时间大于某个值,就任务产生延迟了,视频播放通过USleep()来调整视频帧的播放速度达到同步效果。
在视音频流中的包中都含有DTS和PTS。DTS,Decoding Time Stamp,解码时间戳,告诉解码器packet的解码顺序;PTS,Presentation Time Stamp,显示时间戳,指示从packet中解码出来的数据的显示顺序。
视频的编码要比音频复杂一些,特别的是预测编码是视频编码的基本工具,这就会造成视频的DTS和PTS的不同。这样视频编码后会有三种不同类型的帧:
I帧 关键帧,包含了一帧的完整数据,解码时只需要本帧的数据,不需要 参考其他帧。
P帧 P是向前搜索,该帧的数据不完全的,解码时需要参考其前一帧的数 据。
B帧 B是双向搜索,解码这种类型的帧是最复杂,不但需要参考其一帧的 数据,还需要其后一帧的数据。
I帧的解码是最简单的,只需要本帧的数据;P帧也不是很复杂,值需要缓存上一帧的数据即可,总体来说都是线性,其解码顺序和显示顺序是一致的。B帧就比较复杂了,需要前后两帧的顺序,并且不是线性的,也是造成了DTS和PTS的不同的“元凶”,也是在解码后有可能得不到完整Frame的原因
假如一个视频序列,要这样显示I B B P,但是需要在B帧之前得到P帧的信息,因此帧可能以这样的顺序来存储I P B B,这样其解码顺序和显示的顺序就不同了,这也是DTS和PTS同时存在的原因。DTS指示解码顺序,PTS指示显示顺序。
在计算某一帧的显示时间之前,现来弄清楚FFmpeg中的时间单位:时间基(TIME BASE)。在FFmpeg中存在这多个不同的时间基,对应着视频处理的不同的阶段(分布于不同的结构体中)。在本文中使用的是AVStream的时间基,来指示Frame显示时的时间戳(timestamp)。
1、解码视频
解码视频2、计算当前帧播放的延迟时间延迟并不断纠正
计算当前帧播放的时间3、拿到视频显示时间和音频的播放时间进行比较,然后控制视频的延迟时间
4、得到正确的视频帧延迟时间,最后绘制
对于actual_delay的理解: