以音频线程为例,解封装模块调用音频线程模块,音频线程模块模块将声音播放模块,解码模块,重采样模块结合起来,用音频线程模块去调用其他模块
1.xaudiothread.h
#include<qthread.h>
#include"xdecode.h"
#include"xaudioplay.h"
#include"xresample.h"
#include<mutex>
class QThread;
struct AVCodecParameter;
class xaudiothread:public QThread
{
public:
xaudiothread();
virtual ~xaudiothread();
void run();
virtual bool open(AVCodecParameters* par, int samplerate, int channels);
virtual void push(AVPacket* pkt);
int maxlist = 100;//防止队列中packet没有被读取造成内存泄漏
bool isexit = false;//控制线程退出
protected:
std::mutex mux;
std::list<AVPacket*>packs;//生产者消费者模式:调用者生产packet,在队列中消费packet
xdecode* adecode=0;
xaudioplay* audioplay=0;
xresample* resample=0;
};
2.virtual bool open(AVCodecParameters* par, int samplerate, int channels);
使用该函数时使用 at->open(demux->CopyAPara(), demux->sampletrate, demux->channels),第一个参数即为AVCodecParameters* 类型,该函数做为解封装线程与音频线程的桥梁
bool xaudiothread::open(AVCodecParameters* par,int samplerate,int channels)
{
if (!par)return false;
mux.lock();
if (!adecode)adecode = new xdecode();
if (!resample)resample = new xresample();
if (!audioplay)audioplay = xaudioplay::get();
bool re=true;
if (!adecode->open(par, false))
{
cout << "音频解码打开失败" << endl;
re = false;
}
if (!resample->Open(par, false))
{
cout << "重采样打开失败" << endl;
re = false;
}
audioplay->samplerate = samplerate;
cout << "audioplay->samplerate" << audioplay->samplerate << endl;
audioplay->channels = channels;
cout << "audioplay->samplesize" << audioplay->samplesize << endl;
if (!audioplay->open())
{
cout << "音频播放打开失败" << endl;
re = false;
}
avcodec_parameters_free(&par);
mux.unlock();
cout << "xaudiothread打开(1成功0失败):" <<re<< endl;
return re;
传进来的par需要作为解码,重采样的构造函数的参数,但这些功能的open中会清理掉par
因此为这些open加一个参数,判断是否要清理,这里就选择不清理,由该代码最后调用清理:avcodec_parameters_free(&par);
因此当某个功能没有打开时,不需要解锁,也不需要有个返回值
3.~xaudiothread()
xaudiothread::~xaudiothread()
{
//等待线程退出
isexit = true;
wait();
cout << "音频线程退出" << endl;
}
当释放该线程类的时候,将bool型成员isexit设为true,通过这样来中断run()和push()
4.virtual void push(AVPacket* pkt);
int maxlist = 100;//防止队列中packet没有被读取造成内存泄漏
使用生产者消费者模式的思想
void xaudiothread::push(AVPacket* pkt)
{
if(!pkt)return;
while (!isexit)
{
mux.lock();
if (packs.size() < maxlist)
{
packs.push_back(pkt);
cout << "push" << endl;
mux.unlock();
break;
}
mux.unlock();
msleep(1);
}
}
读取速度高于解码速度,队列经常放满,因此还未放进队列的pakcet不能丢弃,这里我们采用msleep(1)等待一秒再进入下次循环并判断,只有当队列packs的长度小于最大长度时,才往队列里面塞pkt,塞一次之后就break出循环。
生产者消费者模式:
在实际的软件开发过程中,经常会碰到如下场景:某个模块负责产生数据,这些数据由另一个模块来负责处理(此处的模块是广义的,可以是类、函数、线程、进程等)。产生数据的模块,就形象地称为生产者;而处理数据的模块,就称为消费者。
单单抽象出生产者和消费者,还够不上是生产者/消费者模式。该模式还需要有一个缓冲区处于生产者和消费者之间,作为一个中介。生产者把数据放入缓冲区,而消费者从缓冲区取出数据。大概的结构如下图。
假设生产者和消费者分别是两个类。如果让生产者直接调用消费者的某个方法,那么生产者对于消费者就会产生依赖(也就是耦合)。将来如果消费者的代码发生变化,可能会影响到生产者。而如果两者都依赖于某个缓冲区,两者之间不直接依赖,耦合也就相应降低了。
详细:https://blog.csdn.net/u011109589/article/details/80519863
5.run()
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;
}
while (!isexit)
{
AVFrame* frame = adecode->receive();
if (!frame)break;
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;
}
1.当isexit为false时,进入循环,队列为空或者没有初始化成功则解锁该线程,等待数据或者初始化,等1秒再判断一次
2.当队列非空并且初始化成功之后,从队列头部弹出一个packet发给解码线程
3. 可能一次send多次receive,因此一次send之后当调用析构函数使isexit=false之后或者 adecode->receive()将解码队列读干净后才进入到下次send()
4. 对接收到的frame进行重采样得到pcm,并获得重采样后的大小size,当播放器中空余空间大于size时将pcm进行播放
6.xvideothread
与音频线程类似:
xvideothread.h
#pragma once
#include<qthread.h>
#include"xvideowidget.h"
#include"xvideothread.h"
#include"xdecode.h"
#include<mutex>
#include"ivideocall.h"
class QThread;
class xvideothread:public QThread
{
public:
xvideothread();
virtual ~xvideothread();
virtual bool open(AVCodecParameters*par,ivideocall*call,int width,int height);
void run();
virtual bool push(AVPacket* pkt);
int maxlist = 100;
bool isexit = false;
protected:
xdecode* vdecode=0;
xvideowidget* video=0;
ivideocall* call = 0;
std::list<AVPacket*>packs;
std::mutex mux;
};
增加了videocall* call = 0;
class ivideocall
{
public:
virtual void Init(int width,int height) = 0;
virtual void Repaint(AVFrame*)=0;
};
该类只有纯虚函数,为抽象类,让播放控件xvideowidget 继承,因此这两个纯虚函数的实现靠的是xvideowidget中的函数定义,此时可以用父类指针(call)来调用子类(xvideowidget)中 的方法:
call->Init(width, height);,call->Repaint(frame);
为什么这么做,而不是直接将xvideowidget做为参数?
原因:
int main(int argc, char *argv[])
{
testtread tt;
QApplication a(argc, argv);
Xplay2 w;
w.show();
//w.ui.openGLWidget->Init(tt.demux.width, tt.demux.height);
tt.video = w.ui.openGLWidget;
tt.init();
tt.start();
return a.exec();
}
原本是先通过视频线程来初始化控件openGLWidget,然而视频线程的初始化也需要控件OpenGLwidget(不然视频没有地方显示),为了解决这个矛盾,将控件openglwidget的初始化放在视频线程中,将待初始化的控件做为参数传递进去,因为传进去的时候还没有初始化(实例化)该控件,所以将其抽象类ivideocall传进去
xvideothread.cpp
#include "xvideothread.h"
#include<iostream>
using namespace std;
xvideothread::xvideothread()
{
}
xvideothread::~xvideothread()
{ //等待线程退出
isexit = true;
wait();
cout << "视频线程退出" << endl;
}
bool xvideothread::open(AVCodecParameters* par, ivideocall* call, int width, int height)
{
if (!par)return false;
//初始化显示窗口
this->call = call;
if(call)
{
call->Init(width, height);
}
bool re = true;
//打开解码器
if (!vdecode)vdecode = new xdecode();
if (!vdecode->open(par))
{
cout << "video decode open failed!" << endl;
re = false;
}
cout << "xvideothread open:(0失败1成功)" << re << endl;
return re;
}
bool xvideothread::push(AVPacket* pkt)
{
if (!pkt)return false;
while (!isexit)
{
mux.lock();
if (packs.size() < maxlist)
{
packs.push_back(pkt);
cout << "push" << endl;
mux.unlock();
break;
}
mux.unlock();
msleep(1);
}
}
void xvideothread::run()
{
cout << "开始视频线程" << endl;
while (!isexit)
{
mux.lock();
//没数据或者没有初始化成功则解锁该线程,等待数据或者初始化,等1秒再判断一次
if (packs.empty() || !vdecode)
{
mux.unlock();
msleep(1);
continue;
}
AVPacket* pkt = packs.front();
packs.pop_front();
bool re = vdecode->send(pkt);
if (!re)
{
mux.unlock();
msleep(1);
continue;
}
//可能一次send多次receive
while (!isexit)
{
AVFrame* frame = vdecode->receive();
if (!frame)break;
if(call)
{
call->Repaint(frame);
}
}
mux.unlock();
}
}