【FFmpeg视频播放器开发】加入线程以解码音频、视频(六)

一、前言

前面只是开了个异步线程来同时解码音频和视频,音频和视频的解码没有分开,同时将这个异步线程封装成类。这里实现三个类:

  • XAudioThread:音频解码线程
  • XVideoThread:视频解码线程
  • XDemuxThread:“生产者” 线程,生产 AVPacket 添加到音频或视频队列,进而让 XAudioThread 和 XVideoThread 来进一步解码

二、XAudioThread类的实现(音频解码线程)

新创建个工程 。然后我们先看下 XAudioThread 的头文件:

class XAudioThread:public QThread
{
public:
	XAudioThread();
	virtual ~XAudioThread();
	
	virtual bool open(AVCodecParameters *para,int sampleRate,int channels); // 打开,不管成功与否都清理
	virtual void push(AVPacket *pkt); // 将AVPacket加入到队列中等待解码转换
	void run();
	
	// 最大队列
	int m_maxList = 100;
	bool m_isExit = false;

protected:
	std::list <AVPacket *> m_packs;
	std::mutex m_mutex;
	XDecode *m_decode = 0;
	XAudioPlay *m_audioPlay = 0;
	XResample *m_resample = 0;
};

2.1 open():打开

// 打开
bool XAudioThread::open(AVCodecParameters* para, int sampleRate, int channels)
{
	if (!para)return false;
	m_mutex.lock();
	if (!m_decode) m_decode = new XDecode();
	if (!m_resample) m_resample = new XResample();
	if (!m_audioPlay) m_audioPlay = XAudioPlay::Get();
	bool nRet = true;
	if (!m_resample->open(para, false))
	{
		cout << "XResample open failed!" << endl;
		nRet = false;
	}
	m_audioPlay->m_sampleRate = sampleRate;
	m_audioPlay->m_channels = channels;
	if (!m_audioPlay->open())
	{
		nRet = false;
		cout << "XAudioPlay open failed!" << endl;
	}
	if (!m_decode->open(para))
	{
		cout << "audio XDecode open failed!" << endl;
		nRet = false;
	}
	m_mutex.unlock();
	cout << "XAudioThread::Open :" << nRet << endl;
	return nRet;
}

2.2 push():将AVPacket加入到队列中等待解码转换

// 将AVPacket加入到队列中等待解码转换
void XAudioThread::push(AVPacket *pkt)
{
	if (!pkt) return; 
	// 阻塞
	while (!m_isExit)
	{
		m_mutex.lock();
		if (m_packs.size() < m_maxList)
		{
			m_packs.push_back(pkt);
			m_mutex.unlock();
			break;
		}
		m_mutex.unlock();
		msleep(1);
	}
}

2.3 run():从队列中获取AVPacket进行解码

// 从队列中获取AVPacket进行解码
void XAudioThread::run()
{
	unsigned char *pcm = new unsigned char[1024 * 1024 * 10];
	while (!m_isExit)
	{
		m_mutex.lock();

		//没有数据
		if (m_packs.empty() || !m_decode || !m_resample || !m_audioPlay)
		{
			m_mutex.unlock();
			msleep(1);
			continue;
		}

		AVPacket *pkt = m_packs.front();
		m_packs.pop_front();
		bool nRet = m_decode->send(pkt);
		if (!nRet)
		{
			m_mutex.unlock();
			msleep(1);
			continue;
		}
		//一次send 多次recv
		while (!m_isExit)
		{
			AVFrame * frame = m_decode->recv();
			if (!frame) break;
			//重采样 
			int size = m_resample->resample(frame, pcm);
			//播放音频
			while (!m_isExit)
			{
				if (size <= 0) break;
				//缓冲未播完,空间不够
				if (m_audioPlay->getFree() < size)
				{
					msleep(1);
					continue;
				}
				m_audioPlay->write(pcm, size);
				break;
			}
		}
		m_mutex.unlock();
	}
	delete pcm;
}

三、XVideoThread类的实现(视频解码线程)

先看下类的声明:

class XVideoThread:public QThread
{
public:	
	virtual bool open(AVCodecParameters *para, IVideoCall *call, int width,int height); // 打开,不管成功与否都清理
	virtual void push(AVPacket *pkt); // 将AVPacket加入到队列中等待解码转换
	void run();

	XVideoThread();
	virtual ~XVideoThread();
	
	int maxList = 100; // 最大队列
	bool isExit = false; // 线程是否存在

protected:
	std::list <AVPacket *> m_pktList; // AVPacket队列
	std::mutex m_mutex;
	XDecode *decode = 0; // 解码器
	IVideoCall *call = 0;
};

3.1 open():打开

// 打开,不管成功与否都清理
bool XVideoThread::open(AVCodecParameters *para, IVideoCall *call,int width,int height)
{
	if (!para)return false;
	m_mutex.lock();

	// 初始化显示窗口
	this->call = call;
	if (call)
	{
		call->init(width, height);
	}

	// 打开解码器
	if (!decode) decode = new XDecode();
	int nRet = true;
	if (!decode->open(para))
	{
		cout << "audio XDecode open failed!" << endl;
		nRet = false;
	}
	m_mutex.unlock();
	cout << "XAudioThread::Open :" << nRet << endl;
	return nRet;
}

3.2 push():将AVPacket加入到队列中等待解码转换

// 将AVPacket加入到队列中等待解码转换
void XVideoThread::push(AVPacket *pkt)
{
	if (!pkt) return;

	// 阻塞
	while (!isExit)
	{
		m_mutex.lock();
		if (m_pktList.size() < maxList)
		{
			m_pktList.push_back(pkt);
			m_mutex.unlock();
			break;
		}
		m_mutex.unlock();
		msleep(1);
	}
}

3.3 run():从队列中获取AVPacket进行解码

// 从队列中获取AVPacket进行解码
void XVideoThread::run()
{
	while (!isExit)
	{
		m_mutex.lock();

		// 没有数据
		if (m_pktList.empty() || !decode)
		{
			m_mutex.unlock();
			msleep(1);
			continue;
		}

		AVPacket *pkt = m_pktList.front();
		m_pktList.pop_front();
		bool nRet = decode->send(pkt);
		if (!nRet)
		{
			m_mutex.unlock();
			msleep(1);
			continue;
		}

		// 一次send 多次recv
		while (!isExit)
		{
			AVFrame * frame = decode->recv();
			if (!frame) break;
			// 显示视频
			if (call)
			{
				call->myRepaint(frame);
			}
		}
		m_mutex.unlock();
	}
}

四、XDemuxThread类的实现(生产者线程)

先看下类的声明:

class XDemuxThread:public QThread
{
public:
	XDemuxThread();
	virtual ~XDemuxThread();	
	virtual bool open(const char *url, IVideoCall *call); // 创建对象并打开
	void run();
	virtual void start(); // 启动所有线程

	bool m_isExit = false;

protected:
	std::mutex m_mutex;
	XDemux *m_demux = 0;
	XVideoThread *m_vThread = 0;
	XAudioThread *m_aThread = 0;
};

4.1 open():创建对象并打开

// 创建对象并打开
bool XDemuxThread::open(const char* url, IVideoCall* call)
{
	if (url == 0 || url[0] == '\0')
		return false;

	m_mutex.lock();
	if (!m_demux) m_demux = new XDemux();
	if (!m_vThread) m_vThread = new XVideoThread();
	if (!m_aThread) m_aThread = new XAudioThread();

	// 打开解封装
	bool nRet = m_demux->open(url);
	if (!nRet)
	{
		cout << "demux->Open(url) failed!" << endl;
		return false;
	}
	// 打开视频解码器和处理线程
	if (!m_vThread->open(m_demux->copyVPara(), call, m_demux->getVideoInfo().width, m_demux->getVideoInfo().height))
	{
		nRet = false;
		cout << "vt->Open failed!" << endl;
	}
	// 打开音频解码器和处理线程
	if (!m_aThread->open(m_demux->copyAPara(), m_demux->getVideoInfo().sampleRate, m_demux->getVideoInfo().channels))
	{
		nRet = false;
		cout << "at->Open failed!" << endl;
	}
	m_mutex.unlock();
	cout << "XDemuxThread::Open " << nRet << endl;
	return nRet;
}

4.2 run():运行

void XDemuxThread::run()
{
	while (!m_isExit)
	{
		m_mutex.lock();
		if (!m_demux)
		{
			m_mutex.unlock();
			msleep(5);
			continue;
		}
		AVPacket *pkt = m_demux->read();
		if (!pkt)
		{
			m_mutex.unlock();
			msleep(5);
			continue;
		}
		// 判断数据是音频
		if (m_demux->isAudio(pkt))
		{
			if(m_aThread)m_aThread->push(pkt);
		}
		else // 视频
		{
			if (m_vThread)m_vThread->push(pkt);
		}

		m_mutex.unlock();
	}
}

4.3 start():启动所有线程

// 启动所有线程
void XDemuxThread::start()
{
	m_mutex.lock();
	// 启动当前线程
	QThread::start();
	if (m_vThread)m_vThread->start();
	if (m_aThread)m_aThread->start();
	m_mutex.unlock();
}

五、客户端实现

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

	// 初始化显示
	XPlayer w;
	w.show();

	// XDemuxThread线程播放
	XDemuxThread dt;
	dt.open("dove_640x360.mp4", w.video);
	dt.start();

	return a.exec();
}

六、代码下载

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


posted @ 2021-04-26 18:55  fengMisaka  阅读(389)  评论(0编辑  收藏  举报