目录
音视频同步的几种方案
- 音频同步到视频
- 视频同步到音频
- 音视频都同步到外部时钟
各个方案的比较
第一种方案,势必会出现音频的卡顿或加速,非常影响观感体验
第二种方案比第一种更加合理,因为音频一般和外部时钟是相同的,而且考虑到人对声音的敏感度要强于视频,所以一般会以音频时钟为参考时钟,视频同步到音频上
第三种方案,通常在播放网络流或者视频源没有音频时钟的情况下使用,是更加通用的一种方案
考虑到现阶段实现的是本地播放器,而大部分视频源都是有音频时钟的,所以采用了第二种方案
下面记录怎么获取视频和音频时钟
视频时钟的计算方式
在视频解码线程中,先从AVPacket中获取包,查看解码时间戳
第一个if判断中的含义是:
如果AVPacket的H.264流没有解码时间戳,而AVFrame的YUV流中有时间戳,视频时钟就赋值为YUV流中的时间戳
如果AVPacket的H.264流有解码时间戳,就赋值给它H.264流的解码时间戳
如果都没有赋值为0
//计算视频时钟
if (packet->dts == AV_NOPTS_VALUE &&
pFrame->opaque &&
*(uint64_t*)pFrame->opaque != AV_NOPTS_VALUE)
{
video_pts = *(uint64_t *) pFrame->opaque;
}
else if (packet->dts != AV_NOPTS_VALUE)
{
video_pts = packet->dts;
}
else
{
video_pts = 0;
}
//视频时钟补偿
video_pts *= 1000000 *av_q2d(is->video_st->time_base);
video_pts = synchronize_video(is, pFrame, video_pts);
//时间补偿函数--视频延时
double synchronize_video(VideoState *is, AVFrame *src_frame, double pts)
{
double frame_delay;
if (pts != 0) {
//如果传入的时间戳不为0 存到结构体里
is->video_clock = pts;
} else {
//如果传入的时间戳为0 把结构体里的时间戳赋值给它
pts = is->video_clock;
}
//重新计算时基
frame_delay = av_q2d(is->video_st->codec->time_base);
//
frame_delay += src_frame->repeat_pict * (frame_delay * 0.5);
is->video_clock += frame_delay;
return pts;
}
音频时钟的计算方式
在音频解码线程中
下一次音频时钟 = 上一次音频时钟 + 一帧数据的时间
一帧数据的时间 = 一帧音频的字节数(单位:位) / (单次采样字节数(单位:位)*采样频率(单位(1/s))
得到一帧数据的时间是秒数,与音频时钟统一单位,乘以1000000转换为微秒
视频同步到音频时钟的方法
在视频解码线程中添加下面这段代码
其含义是,如果视频时钟 > 音频时钟 计算两者差值delayTime,转换为毫秒,如果毫秒数大于5,视频延时5毫秒,如果不大于5,视频延时delayTime
当视频时钟 <= 音频时钟时跳出死循环
//视频同步到音频上
while(1)
{
//获取音频时钟,当 视频时钟 <= 音频时钟时 跳出播放
audio_pts = is->audio_clock;
if (video_pts <= audio_pts) break;
int delayTime = (video_pts - audio_pts) * 1000;
delayTime = delayTime > 5 ? 5:delayTime;
SDL_Delay(delayTime);
}
后续待补充音视频同步到外部时钟的内容
参考