【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
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!
2019-04-26 Ubuntu 必装软件及安装教程
2019-04-26 把当前ubuntu系统做成镜像
2019-04-26 Windows下安装Ubuntu16.04双系统