之前已经完成了音频和视频的播放,但是发现音频的播放速度要明显快于视频。由于音频和视频解码时间、渲染时间的不同,所以要进行音视频同步。
由于人耳的听觉灵敏度是要高度视觉的。假如音频少一帧的话是能够明显感觉到的。但是视频少一帧是很难察觉到的。所以所以视频同步的关键是以音频播放为准,视频不断的去减少两者之间的相对播放时间。
一、记录音频的相对播放时间
1、声明变量clock(相对开始的播放时间),这个clock值是不断变化的。这里的重新赋值实在音频转pcm数据的方法中进行的。代码如下:
/**
* 转换为pcm数据
*/
int AudioChannel::changeFrameToPcm() {
...//省略代码
//数量*单位 pts是数量
clock = frame->pts*av_q2d(time_base);
break;
}
releaseAvFrame(frame);
return data_size;
}
2、这里的pts与dts需要了解一下:
pts:显示时间戳,指从packet解码出来的数据的显示顺序
dts:解码时间戳,告诉解码器packet的解码顺序
音频中两者是相同的,但是视频有图B帧的存在,会造成解码顺序与显示顺序并不相同,也就是pts与dts不一定相同。
单位:av_q2d(time_base)计算出来相当于帧率,单位是时间。计算得到的clock是实际的相对于之前一帧的进度时间。
3、由于是以音频为准。所以在视频代码中需要进行播放的精确延时。代码如下:
void VideoChannel2::goPlayFrame() {
...//省略代码
while (isPlaying) {
...//省略代码
//帧率,解码速度,渲染速度都会影响音视频同步
//根据帧率求得延迟时间
double fps_delay = 1 / fps;
//渲染时间
double decode_delay = frame->repeat_pict / (2 * fps);
//总的延迟时间 解码时间也要算进去 配置差的手机解码比较慢
double totalDelay = fps_delay + decode_delay;
//视频的相对时间
clock = frame->pts * av_q2d(time_base);
//音频的相对时间
double audioClock = audioChannel->clock;
//视频时间刻度-音频时间刻度
double diff = clock - audioClock;
LOGE("音视频时间差--%d", diff);
if (clock > audioClock) {
//视频超前 休眠时间长一点 不然会卡很久
if (diff > 1) {
//相差比较大,延迟时间加大,加快同步时间
av_usleep((totalDelay * 2) * 1000000);
} else {
//相差较小
av_usleep((totalDelay + diff) * 1000000);
}
} else {
//视频延后 音频超前
if (diff > 1) {
//休眠
} else if (diff >= 0.05) {
//视频需要追赶,丢非关键帧 同步
releaseAvFrame(frame);
//丢frame队列 调用该方法直接调用dropFrame()方法
frame_queue.sync();
} else {
}
}
//16ms
//av_usleep(frame_delay * 1000 * 1000);
//释放frame
releaseAvFrame(frame);
}
//资源释放
av_free(&dst_data[0]);
isPlaying = false;
releaseAvFrame(frame);
sws_freeContext(swsContext);
}
4、这里通过计算得到视频和音频的相对播放时间,判断两者的差异,差异较小时可以通过延迟的方法进行追赶。但是如果视频落后音频很多时,就需要采用丢帧的形式经行追赶。丢帧的方式有两种。丢packet和丢frame。两者对比如下:
由于视频有I帧和P,B帧。丢帧不能丢I帧,否则导致后面的画面看不到。所以需要进行判断。另外丢packet帧有时候是没有效果的。当我们的frame队列是满的时候。无论怎么操作packet队列都不会影响结果。因为直接和视频播放相关的是frame队列。所以这里采用丢frame帧的策略。
5:丢帧代码如下:
VideoChannel2::VideoChannel2(int id, JavaCallHelper *javaCallHelper,
AVCodecContext *avCodecContext, AVRational time_base) : BaseChannel(id,
javaCallHelper,
avCodecContext,
time_base) {
//设置丢帧策略
frame_queue.setReleaseHandle(releaseAvFrame);
frame_queue.setSyncHandle(dropFrame);
}
/**
* 丢真可分为两种 丢packet 丢frame
* 丢packet不能丢关键帧 假如丢packet帧,但是frame队列还是满的,f因为rame队列是直接与渲染相关的,丢packet不会有效果
* 选择丢frame帧 不需要判断I,P,B帧,并且直接有效
* @param q
*/
void dropFrame(queue<AVFrame *> &q) {
if (!q.empty()) {
AVFrame *frame = q.front();
q.pop();
BaseChannel::releaseAvFrame(frame);
}
}
完成以后步骤,就可以达到音视频的同步了。