收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人
都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
2、基于qt提供的QAudioFormat进行音频播放,QTOpenGL进行视频播放,及音视频同步问题。
3、qt界面管理设计了打开文件(考虑多次打开) ,暂停(考虑缓冲,音频存在三个缓冲 解码的缓冲队列,播放QAudioOutput的自带缓冲,QIODevice的硬件设备缓冲) ,以及滑动条的拖动(涉及qt自带滑动条点击每次只能移动一下进行重载实现点击在哪进度就移动到哪里)。
最后实际效果图
二、框架分析
1、隔离ffmpeg相关
将ffmpeg进行的音视频编解码及重采样要进行隔离要与qt播放显示部分隔开,则需要对他们进行封装成类。主要是三个部分解封装 解码 以及音频重采样
XDcode 负责音视频解码 到时候创建两个这样的对象分别解码音频包 和视频包
XDemux 解封装 进行ffmpeg前面的一系列操作注册 保存参数 获取数据包
XResample 对音频数据进行重采样
XDemux相关函数
//打开音视频文件 赋值成员变量 AVFormatContext \*ic 和一系列信息参数
virtual bool Open(const char \*url);
//读取一包数据
virtual AVPacket\* Read();
//只读取视频数据 这个与后面的seek操作有关
virtual AVPacket\* ReadVedio();
//返回视频流的解码器参数AVCodecParameters
virtual AVCodecParameters\* CopyVPara()
//返回音频流的解码器参数AVCodecParameters
virtual AVCodecParameters\* CopyAPara()
//移动关键帧根据视频移动 seek 位置 pos0.0-1.0 类似百分比的值
virtual bool Seek(double pos);
//清空读取缓存
virtual void Clear();
//关闭
virtual void Close();
//新增一个接口判断是音频还是视频
virtual bool IsAudio(AVPacket \*pkt);
//注册封装器
XDemux();
XDcode相关函数 就是获取解码上下文 发送pkt 接收frame
extern void XFreePacket(AVPacket \*\*pkt);
extern void XFreeFrame(AVFrame \*\*frame);
//传入解码器参数,设置成员AVCodecContext codec
virtual bool Open(AVCodecParameters \*para);
//将pkt发送到解码线程,不管成功与否都要释放pkt(包含两个空间对象和内部数据)
virtual bool Send(AVPacket \*pkt);
//从解码线程读取解码的数据帧 并返回帧 注意与ffmpeg一样 对应多次Send
virtual AVFrame\* Recv();
//清理关闭AVCodecContext
virtual void Close();
//清理缓冲
virtual void Clear();
XResample
//初始化 创建赋值SwrContext \*actx
virtual bool Open(AVCodecParameters \*para, bool isClearPara = false);
//进行重采样 数据采用后数据
virtual int Resample(AVFrame \*indata, unsigned char \*data);
//关闭
virtual void Close();
2、解码是多线程
XDemuxThread;解封装读包到对应解码器管理类的pkts的list中
XVideoThread;视频解码器进行取包解码显示
XAudioThread;音频解码器进行取包解码重采样播放
因为音视频的解码处理线程很多操作都是相同的则可以重新构建一下封装一个XDecodeThread基类,将共同操作提取到基类
XDemuxThread
成员有;
XDemux \*demux = 0;
XVideoThread \*vt = 0;
XAudioThread \*at = 0;
成员函数;
//将XDemux 、XVideoThread 、XAudioThread 都调用open操作
virtual bool Open(const char \*url, IVideoCall \*call);
//创建XDemux 、XVideoThread 、XAudioThread对象 并启动这三个线程run函数
virtual void Start();
//关闭线程等待清理 关闭vt at 并delete
virtual void Close();
//清理缓冲资源 demux vt at 三个都调用clear函数进行清理
virtual void Clear();
//设置状态暂停 以及 at vt都调用pause函数
void SetPause(bool isPause);
bool isPause = false;
//清理资源 调用都调用pause 解封装移动到seek位置 只读视频流 进行显示
virtual void Seek(double pos);
//音视频同步、读取包分别存放到at、vt管理的队列中去
void run();
XDecodeThread
成员
XDcode \*decode = 0;
std::list <AVPacket \*> packts;
成员函数
//产生
virtual void Push(AVPacket \*pkt);
//取出一帧数据并入栈,没有贼返回NULL
virtual AVPacket \*Pop();
//清理队列
virtual void Clear();
//关闭清理 要将XDcode关闭
virtual void Close();
XAudioThread
成员
XAudioPlay \*ap = 0; 音频播放的类
XResample \*res = 0; 重采样的类
成员函数
//decode ap res都调用Open操作
virtual bool Open(AVCodecParameters \*para,int sampleRate, int channels);
//停止线程 清理资源 ap res都要close
virtual void Close();
//音频播放有两部分缓冲 要重载Clear ap部分也要clear
virtual void Clear();
//读取包 解码 重采样 IO播放
void run();
//暂停操作 并调用ap的pause
void SetPause(bool isPause);
bool isPause = false;
//创建对象ap 、res
XAudioThread::XAudioThread()
XVideoThread
成员
IVideoCall \*call = NULL; //视频显示的类
成员函数
//创建窗口 decode打开
virtual bool Open(AVCodecParameters \*para,IVideoCall \*call,int width,int height);
//解码pts,如果接收到的解码数据pts大于seekPts return true 并显示
virtual bool RepaintPts(AVPacket \*Pkt, long long seekPts);
//音视频同步 取包 解码 显示
void run();
//设置视频处理暂停步骤
void SetPause(bool isPause);
bool isPause = false;
3、视频显示类设计 添加抽象接口进行封装
抽象出来一个接口给XVideoThread使用 在XVideoThread播放的时候使用接口类,而真正显示的类继承接口类传入进来,进行隔离封装,在视频编解码部分是不需要知道显示部分的,不管他用什么技术显示都不用管,我里面只管调用接口类的几个函数进行调用显示,至于内部具体什么操作这边是不需要管的进行了隔离。
//创建接口,则以后用其他显示的时候 videoThread这个类是不用变的
class IVideoCall
{
public:
virtual void Init(int width, int height) = 0;
virtual void Repaint(AVFrame \*frame) = 0;
};
XVideoWidget
使用qt的OPenGL进行显示
class XVideoWidget : public QOpenGLWidget, protected QOpenGLFunctions, public IVideoCall
则后面需要继承IVideoCall这个接口类,并实现接口中的方法
并且调用这两个接口可以完成视频的显示
4、音频播放类设计 工程模式进行封装
提供一个抽象类并实现一个Get的静态方法用来返回子类的实例化对象的,这样操作主要是将真正的播放类进行隔离与编解码这边没有任何关系,因为这边都看不到这个类。
class XAudioPlay
{
public:
//工程模式
static XAudioPlay \*Get();
....
}
XAudioPlay \*XAudioPlay::Get()
{
static CXAudiaoPlay play;//返回具体静态类
return &play;
}
使用qt的QAudioFormat进行音频播放的
class CXAudiaoPlay : public XAudioPlay
{
//重载里面的方法
};
5、界面相关处理
实现了双击放大还原、滑动条的拖动与点击位置播放、尺寸大小的变化
滑动条随视频播放移动是采用重载定时器事件的方式进行处理的
main函数中启动startTimer(40);
//重载定时器函数的方法 滑动条进行显示
void Widget::timerEvent(QTimerEvent \*e)
{
//如果滑动条按下的时候就不在计时
if(isSliderPress) return ;
long long total = dt.totalMs;
//播放的pts
if(total > 0)
{
double pos = (double)dt.pts/(double)total;
int v = ui->playPos->maximum() \* pos;
ui->playPos->setValue(v);
}
}
三、具体逻辑
只有static XDemuxThread dt;这个类与界面类进行交互的
在main函数中也是一开始就启动了dt.Start();线程
1、线程逻辑
void XDemuxThread::Start()
demux = new XDemux();
vt = new XVideoThread();
at = new XAudioThread();
QThread::start();
XDemuxThread::run()
//如果暂停则取消下面的解封装读pkt操作
//音视频同步
AVPacket \*pkt = demux->Read();
at->Push(pkt); or vt->Push(pkt);
vt->start();
//暂停状态
//音频同步
AVPacket \*pkt = Pop();
**收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。**


**[如果你需要这些资料,可以戳这里获取](https://bbs.youkuaiyun.com/topics/618679757)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人**
**都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**
24年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。**
[外链图片转存中...(img-SQYiGFUk-1715677438105)]
[外链图片转存中...(img-eR4fELjj-1715677438105)]
**[如果你需要这些资料,可以戳这里获取](https://bbs.youkuaiyun.com/topics/618679757)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人**
**都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**