zl程序教程

您现在的位置是:首页 >  前端

当前栏目

【FFmpeg视频播放器开发】音频重采样类和音频播放类的封装(五)

封装开发 视频 播放 FFMPEG 音频 播放器 采样
2023-09-27 14:29:13 时间

一、前言

在上一篇中我们只实现了 OpenGl 播放视频,现在我们实现播放音频功能,播放音频首先要实现音频重采样,然后通过 Qt 的 QAudioOutput 类实现播放音频。

二、XResample类的实现(重采样)

新创建个工程,命名为 XPlayer_4。然后我们看下 XDemux 类要实现哪些函数:

class XResample
{
public:
	XResample();
	~XResample();

	// 输出参数和输入参数一致除了采样格式,输出为S16 ,会释放para
	virtual bool Open(AVCodecParameters *para,bool isClearPara = false); // 打开
	virtual void Close(); // 关闭

	// 返回重采样后大小,不管成功与否都释放indata空间
	virtual int Resample(AVFrame *indata, unsigned char *data);
	
	int outFormat = 1; // 输出格式:AV_SAMPLE_FMT_S16

protected:
	std::mutex m_mutex; // 互斥锁
	SwrContext *actx = 0; // 上下文
};

2.1 Open():打开

// 输出参数和输入参数一致除了采样格式,输出为S16
bool XResample::Open(AVCodecParameters *para, bool isClearPara)
{
	if (!para) return false;
	std::unique_lock<std::mutex> guard(m_mutex);
	// 音频重采样 上下文初始化
	actx = swr_alloc_set_opts(actx,
		av_get_default_channel_layout(2),	// 输出格式
		(AVSampleFormat)outFormat,			// 输出样本格式 1 AV_SAMPLE_FMT_S16
		para->sample_rate,					// 输出采样率
		av_get_default_channel_layout(para->channels), // 输入格式
		(AVSampleFormat)para->format,
		para->sample_rate,
		0, 0
	);
	if(isClearPara)
		avcodec_parameters_free(&para);
	int nRet = swr_init(actx);
	if (nRet != 0)
	{
		char buf[1024] = { 0 };
		av_strerror(nRet, buf, sizeof(buf) - 1);
		cout << "swr_init  failed! :" << buf << endl;
		return false;
	}
	//unsigned char *pcm = NULL;
	return true;
}

2.2 Resample():返回重采样后大小

// 返回重采样后大小,不管成功与否都释放indata空间
int XResample::Resample(AVFrame *indata, unsigned char *d)
{
	if (!indata) return 0;
	if (!d)
	{
		av_frame_free(&indata);
		return 0;
	}
	uint8_t *data[2] = { 0 };
	data[0] = d;
	int nRet = swr_convert(actx,
		data, indata->nb_samples,		// 输出
		(const uint8_t**)indata->data, indata->nb_samples	// 输入
	);
	if (nRet <= 0)return nRet;
	int outSize = nRet * indata->channels * av_get_bytes_per_sample((AVSampleFormat)outFormat);
	return outSize;
}

2.3 Close():关闭

void XResample::Close()
{
	std::unique_lock<std::mutex> guard(m_mutex);
	if (actx)
		swr_free(&actx);
}

三、XAudioPlay类的实现(音频播放)

头文件如下:

class XAudioPlay
{
public:
	XAudioPlay();
	virtual ~XAudioPlay();

	static XAudioPlay* Get();
	//打开音频播放
	virtual bool Open() = 0;
	virtual void Close() = 0;
	//播放音频
	virtual bool Write(const unsigned char *data, int datasize) = 0;
	virtual int GetFree() = 0;

	int sampleRate = 44100;
	int sampleSize = 16;
	int channels = 2;
};

上面的成员函数留到派生类 CXAudioPlay 来实现:

class CXAudioPlay : public XAudioPlay
{
public:
	QAudioOutput *output = NULL;
	QIODevice *io = NULL;
	std::mutex mutex;
	virtual void Close()
	{
		mutex.lock();
		if (io)
		{
			io->close();
			io = NULL;
		}
		if (output)
		{
			output->stop();
			delete output;
			output = 0;
		}
		mutex.unlock();
	}
	virtual bool Open()
	{
		Close();
		QAudioFormat fmt;
		fmt.setSampleRate(sampleRate);
		fmt.setSampleSize(sampleSize);
		fmt.setChannelCount(channels);
		fmt.setCodec("audio/pcm");
		fmt.setByteOrder(QAudioFormat::LittleEndian);
		fmt.setSampleType(QAudioFormat::UnSignedInt);
		mutex.lock();
		output = new QAudioOutput(fmt);
		io = output->start(); //开始播放
		mutex.unlock();
		if(io)
			return true;
		return false;
	}
	virtual bool Write(const unsigned char *data, int datasize)
	{
		if (!data || datasize <= 0)return false;
		mutex.lock();
		if (!output || !io)
		{
			mutex.unlock();
			return false;
		}
		int size = io->write((char *)data, datasize);
		mutex.unlock();
		if (datasize != size)
			return false;
		return true;
	}

	virtual int GetFree()
	{
		mutex.lock();
		if (!output)
		{
			mutex.unlock();
			return 0;
		}
		int free = output->bytesFree();
		mutex.unlock();
		return free;
	}
};

四、客户端实现

int main(int argc, char* argv[])
{
	QApplication a(argc, argv);

	// 播放界面
	XPlayer w;
	w.show();

	//=================1、解封装测试====================
	XDemux demux;
	const char* url = "v1080.mp4";
	cout << "demux.Open = " << demux.open(url);
	demux.read();
	cout << "demux.Open = " << demux.open(url); // open一次的话,很久之后才开始播放
	cout << "CopyVPara = " << demux.copyVPara() << endl;
	cout << "CopyAPara = " << demux.copyAPara() << endl;

	// 初始化openGl窗口
	w.video->init(demux.getVideoInfo().width, demux.getVideoInfo().height);

	//=================2、解码测试====================
	XDecode vdecode;
	XDecode adecode;
	XResample resample; // 新添加
	cout << "vdecode.Open() = " << vdecode.Open(demux.copyVPara()) << endl;
	cout << "adecode.Open() = " << adecode.Open(demux.copyAPara()) << endl;
	// 【新添加音频】
	cout << "resample.Open = " << resample.Open(demux.copyAPara()) << endl;
	XAudioPlay::Get()->channels = demux.getVideoInfo().channels;
	XAudioPlay::Get()->sampleRate = demux.getVideoInfo().sampleRate;
	cout << "XAudioPlay::Get()->Open() = " << XAudioPlay::Get()->Open() << endl;
	unsigned char* pcm = new unsigned char[1024 * 1024];

	// 开辟异步线程进行解码播放,避免阻塞GUI
	auto futureLambda = std::async([&]() {		
		for (;;)
		{
			AVPacket* pkt = demux.read();
			if (!pkt)
			{
				// 异步线程退出后,才清空销毁
				demux.close();
				vdecode.Close();
				break;
			}
			if (demux.isAudio(pkt))
			{
				adecode.Send(pkt);
				AVFrame *frame = adecode.Recv();
				int len = resample.Resample(frame, pcm);
				//cout << "Audio:" << frame << endl;
				cout << "Resample:" << len << " ";
				while (len > 0)
				{
					if (XAudioPlay::Get()->GetFree() >= len)
					{
						XAudioPlay::Get()->Write(pcm, len);
						break;
					}
					QThread::msleep(1);
				}
			}
			else
			{
				vdecode.Send(pkt);
				AVFrame* frame = vdecode.Recv();
				// OpenGl子界面重绘
				w.video->myRepaint(frame);
				QThread::msleep(40); // 25帧播放
			}
		}
	});

	return a.exec();
}

测试发现,部分视频播放流畅,同时音画基本同步,但多数视频播放卡顿,且音画不同步。所以后面重点要实现真正的音视频同步(由于时间原因,还未实现)。

五、代码下载

下载链接:https://github.com/confidentFeng/FFmpeg/tree/master/XPlayer/XPlayer_4