播放器实战21 音视频同步

1.概述

从视频的帧率及音频的采样率,即可知道视频/音频播放速度。声卡和显卡均是以一帧数据来作为播放单位,如果严格按照帧率及采样率来进行播放,在理想条件下,应该是同步的,不会出现偏差。

但实际情况下,如果用上面那种简单的方式,慢慢的就会出现音视频不同步的情况,要不是视频播放快了,要么是音频播放快了。可能的原因如下:

一帧的播放时间,难以精准控制。音视频解码及渲染的耗时不同,可能造成每一帧输出有一点细微差距,长久累计,不同步便越来越明显。(例如受限于性能,42ms才能输出一帧)

音频输出是线性的,而视频输出可能是非线性,从而导致有偏差。

所以,解决音视频同步问题,引入了时间戳:
首先选择一个参考时钟(要求参考时钟上的时间是线性递增的);
编码时依据参考时钟上的给每个音视频数据块都打上时间戳;
播放时,根据音视频时间戳及参考时钟,来调整播放。
所以,视频和音频的同步实际上是一个动态的过程,同步是暂时的,不同步则是常态。

因为人对声音的敏感度高于对画面的敏感度,并且声音为线性播放,视频帧为非线性播放, 因为通常采用视频同步音频的策略,即音频正常播放,如果视频播放快了则减少视频播放速率,慢了则加快播放速率

详细:https://blog.csdn.net/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;
		}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值