1.概述
从视频的帧率及音频的采样率,即可知道视频/音频播放速度。声卡和显卡均是以一帧数据来作为播放单位,如果严格按照帧率及采样率来进行播放,在理想条件下,应该是同步的,不会出现偏差。
但实际情况下,如果用上面那种简单的方式,慢慢的就会出现音视频不同步的情况,要不是视频播放快了,要么是音频播放快了。可能的原因如下:
一帧的播放时间,难以精准控制。音视频解码及渲染的耗时不同,可能造成每一帧输出有一点细微差距,长久累计,不同步便越来越明显。(例如受限于性能,42ms才能输出一帧)
音频输出是线性的,而视频输出可能是非线性,从而导致有偏差。
所以,解决音视频同步问题,引入了时间戳:
首先选择一个参考时钟(要求参考时钟上的时间是线性递增的);
编码时依据参考时钟上的给每个音视频数据块都打上时间戳;
播放时,根据音视频时间戳及参考时钟,来调整播放。
所以,视频和音频的同步实际上是一个动态的过程,同步是暂时的,不同步则是常态。
因为人对声音的敏感度高于对画面的敏感度,并且声音为线性播放,视频帧为非线性播放, 因为通常采用视频同步音频的策略,即音频正常播放,如果视频播放快了则减少视频播放速率,慢了则加快播放速率
详细:https://blog.youkuaiyun.com/myvest/article/details/97416415
2.音频时钟
用解封装上下文ic和指向pkt的指针做为参数调用av_read_frame()去读取解封装的内容,将读取到的内容放到pkt中:
AVPacket* xdemux:: readfz()
{
mux.lock();
if (!ic)
{
mux.unlock();
return 0;
}
AVPacket* pkt=av_packet_alloc();//只是对象空间,并没有分配数据空间
int re=av_read_frame(ic, pkt);//分配数据空间
if (re!=0)//打开的话re是0
{
mux.unlock();
av_packet_free(&pkt);
return 0;
}
pkt->pts = pkt->pts * (1000 * (r2d(ic->streams[pkt->stream_index]->time_base)));
pkt->dts = pkt->dts * (1000 * (r2d(ic->streams[pkt->stream_index]->time_base)));
mux.unlock();
cout << " pkt->dts" << pkt->dts << endl;
return pkt;
}
可以看到pkt的dts是递增的,且有间隔,在解码时,不管是否有B帧,dts都是递增的,如果没有B帧的话dts与pts一致,如果有的话dts与pts就不同
在xdecode类中添加pts成员:
long long pts = 0;
表示当前解码到的pts
在xdecode类的receive()中添加pts = frame->pts;来更新当前解码到的pts,
(frame和pkt的pts区别?)
可以看到xaudiothread类与xdemux类隔绝开,其只能调用xdecode类,xresample类,xaudioplay类,因此其使用xdecode类的pts而不用xdemux里的pts
添加pts = adecode->pts-audioplay->GetNoPlayMs();
cout << “audio pts:” << pts << endl;
一次每次recevie或者receive到的pts之间都有间隔,这个pts是当前packet或者frame的最后一个pts,但是现在时间可能还没有走到最后一个pts,而是之前的pts,因此用上面的pts(从packet的pts复制而来,已转为毫秒)减去播放器中还未播放的时间,
void xaudiothread::run()
{
cout << "开始音频线程" << endl;
unsigned char* pcm = new unsigned char[1024 * 1024 * 10];
while (!isexit)
{
mux.lock();
if (packs.empty() || !adecode || !resample || !audioplay)
{
mux.unlock();
msleep(1);
continue;
}
AVPacket* pkt = packs.front();
packs.pop_front();
bool re = adecode->send(pkt);
if (!re)
{
mux.unlock();
msleep(1);
continue;
}
//可能一次send多次receive
while (!isexit)
{
AVFrame* frame = adecode->receive();
if (!frame)break;
pts = adecode->pts-audioplay->GetNoPlayMs();
cout << "audio pts:" << pts << endl;
int size = resample->Resample(frame, pcm);//Resample中会释放frame
while (!isexit)
{
if (size <= 0)break;
if (audioplay->Getfree() < size)
{
msleep(1);
continue;
}
audioplay->write(pcm, size);
break;
}
}
mux.unlock();
}
delete pcm;
}
在 xaudioplay的子类audioplay中添加获取还未播放的时间的功能:用还未播放的字节数除以一秒音频的字节大小,再乘以1000,获得还未播放的时间(毫秒)
virtual long long xaudioplay::GetNoPlayMs()
{
mux.lock();
if (!output)
{
mux.unlock();
return 0;
}
long long pts = 0;
//还未播放的字节数
double size = output->bufferSize() - output->bytesFree();
//算出一秒音频的字节大小
double secsize=samplerate* (samplesize / 8)* channels;
if (secsize <= 0)pts = 0;
pts = size / secsize * 1000;
mux.unlock();
return pts;
}
3.用视频同步音频
在xvideothread类中添加成员
long long synpts = 0;
每次open视频线程时置0,由外部解封装线程传通过音频线程的pts进行更新:
` if (vt && at)
{
vt->synpts = at->pts;
}`
再在视频线程的run中进行音视频同步:
当当前视频解码器获得的pts小于当前音频线程的pts时,解锁将线程资源交给其他线程,等待一秒再进行判断,以此来延缓视频的播放,使音频的播放追上视频的播放
if (synpts < vdecode->pts)
{
mux.unlock();
//msleep(1);
continue;
}