播放器开发(四):多线程解复用与解码模块实现

学习课题:逐步构建开发播放器【QT5 + FFmpeg6 + SDL2】

前言

根据第一章内容,我们首先可以先把解复用和解码模块完成,其中需要使用到多线程以及队列,还需要使用FFmpeg进行解复用和解码动作的实现。

创建BaseQueue基类

BaseQueue.h

#include <condition_variable>
#include <mutex>
#include <queue>using namespace std;template<class T>
class BaseQueue {
public:/*** 唤醒所有等待线程,设置异常标识为1*/void abort() {m_abort = 1;m_cond.notify_all();}/*** push进m_queue** @param val 需要push的值* @return  1是成功 or -1是m_abort==1可能有异常*/int push(T val) {lock_guard<mutex> lock(m_mutex);if (m_abort == 1) {return -1;}m_queue.push(val);m_cond.notify_one();return 0;}/*** 从基类 front 到 val 并执行基类std::queue.pop** @param val 存放front值的地址引用* @param timeout 持有锁的上限时间(ms)* @return 1是成功 or -1是m_abort==1可能有异常 or 是m_queue为空*/int pop(T &val, int timeout = 0) {unique_lock<mutex> lock(m_mutex);if (m_queue.empty()) {m_cond.wait_for(lock, chrono::microseconds(timeout), [this] {return !m_queue.empty() | m_abort;});}if (m_abort == 1) {return -1;}if (m_queue.empty()) {return -2;}// there is address referenceval = m_queue.front();m_queue.pop();return 0;}/*** 从基类std::queue.front 获取值保存到引用地址val** @param val 存放front值的地址引用* @return 1是成功 or -1是m_abort==1可能有异常 or 是m_queue为空*/int front(T &val) {lock_guard<mutex> lock(m_mutex);if (m_abort == 1) {return -1;}if (m_queue.empty()) {return -2;}val = m_queue.front();return 0;}int size() {lock_guard<mutex> lock(m_mutex);return m_queue.size();}private:int m_abort = 0;// 是否中止mutex m_mutex;  // 锁condition_variable m_cond;queue<T> m_queue;
};

创建AVPacketQueue包队列类和AVFrameQueue帧队列类

在AVPacketQueue和AVFrameQueue中分别实现模版BaseQueue基类,并且可以添加一些错误的信息打印。


我们可以新建一个头文件FFmpegHeader.h用来存放导入ffmpeg的一些代码,后面用的时候导入这个文件就可以了

FFmpegHeader.h

extern "C" {
#include "libavcodec/avcodec.h"
#include "libavfilter/avfilter.h"
#include "libavformat/avformat.h"
#include "libavutil/audio_fifo.h"
#include "libavutil/avassert.h"
#include "libavutil/ffversion.h"
#include "libavutil/frame.h"
#include "libavutil/imgutils.h"
#include "libavutil/opt.h"
#include "libavutil/pixdesc.h"
#include "libavutil/time.h"
#include "libswresample/swresample.h"
#include "libswscale/swscale.h"#ifdef ffmpegdevice
#include "libavdevice/avdevice.h"
#endif
}#include "qdatetime.h"
#pragma execution_character_set("utf-8")#define TIMEMS qPrintable(QTime::currentTime().toString("HH:mm:ss zzz"))
#define TIME qPrintable(QTime::currentTime().toString("HH:mm:ss"))
#define QDATE qPrintable(QDate::currentDate().toString("yyyy-MM-dd"))
#define QTIME qPrintable(QTime::currentTime().toString("HH-mm-ss"))
#define DATETIME qPrintable(QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss"))
#define STRDATETIME qPrintable(QDateTime::currentDateTime().toString("yyyy-MM-dd-HH-mm-ss"))
#define STRDATETIMEMS qPrintable(QDateTime::currentDateTime().toString("yyyy-MM-dd-HH-mm-ss-zzz"))

AVPacketQueue

//AVPacketQueue.h
#include "BaseQueue.h"
#include "FFmpegHeader.h"
#include <cstdio>class AVPacketQueue {
public:~AVPacketQueue();int push(AVPacket *val);AVPacket *pop(int timeout);void release();int size();private:BaseQueue<AVPacket *> m_queue;
};//AVPacketQueue.cpp
#include "AVPacketQueue.h"AVPacketQueue::~AVPacketQueue() {release();m_queue.abort();
}int AVPacketQueue::push(AVPacket *val) {AVPacket *tmp_pkt = av_packet_alloc();av_packet_move_ref(tmp_pkt, val);return m_queue.push(tmp_pkt);
}
AVPacket *AVPacketQueue::pop(int timeout) {AVPacket *tmp_pkt = nullptr;int ret = m_queue.pop(tmp_pkt, timeout);if (ret == -1) {printf("AVPacketQueue::pop -->> m_abort==1可能有异常");}if (ret == -2) {printf("AVPacketQueue::pop -->> 队列为空");}return tmp_pkt;
}
void AVPacketQueue::release() {while (true) {AVPacket *pkt = nullptr;int ret = m_queue.pop(pkt, 1);if (ret < 0) {break;} else {av_packet_free(&pkt);continue;}}
}
int AVPacketQueue::size() {return m_queue.size();
}

AVFrameQueue

//AVFrameQueue.h
#include "BaseQueue.h"
#include "FFmpegHeader.h"
#include <cstdio>class AVFrameQueue {
public:~AVFrameQueue();int push(AVFrame *val);AVFrame *pop(int timeout);void release();int size();private:BaseQueue<AVFrame *> m_queue;
};//AVFrameQueue.cpp
#include "AVFrameQueue.h"
AVFrameQueue::~AVFrameQueue() {release();m_queue.abort();
}int AVFrameQueue::push(AVFrame *val) {AVFrame *tmp_frame = av_frame_alloc();av_frame_move_ref(tmp_frame, val);return m_queue.push(tmp_frame);
}
AVFrame *AVFrameQueue::pop(int timeout) {AVFrame *tmp_frame = nullptr;int ret = m_queue.pop(tmp_frame, timeout);if (ret == -1) {printf("AVFrameQueue::pop -->> m_abort==1可能有异常");}if (ret == -2) {printf("AVFrameQueue::pop -->> 队列为空");}return tmp_frame;
}
void AVFrameQueue::release() {while (true) {AVFrame *pkt = nullptr;int ret = m_queue.pop(pkt, 1);if (ret < 0) {break;} else {av_frame_free(&pkt);continue;}}
}
int AVFrameQueue::size() {return m_queue.size();
}

创建BaseThread抽象类

#include <QThread>/*** 因为我们后面可能需要用到qt信号传递是否暂停,所以使用QThread*/
class BaseThread : public QThread {Q_OBJECT
public:// 初始化virtual int init() = 0;// 创建线程 开始run工作virtual int start() = 0;// 停止线程 释放资源virtual void stop() {isStopped = true;if (m_thread) {if (m_thread->isRunning()) {m_thread->wait(-1);}delete m_thread;m_thread = nullptr;}};protected:bool isStopped = true; // 是否已经停止 停止时退出线程bool isPlaying = false;// 是否正在播放bool isPause = false;  // 是否暂停QThread *m_thread = nullptr;
};

创建DemuxThread解复用线程模块和DecodeThread解码线程模块

DemuxThread解复用线程:

1、打开视频文件,解封装操作。

2、读取流信息,并添加打印信息。

3、解复用(循环分离视频流和音频流)。

DemuxThread

//DemuxThread.h
enum class MediaType {Audio,Video
};class DemuxThread : public BaseThread {
private:QString m_url = nullptr;AVFormatContext *ic = nullptr;int m_videoStreamIndex = -1;int m_audioStreamIndex = -1;const AVCodec *m_videoCodec;const AVCodec *m_audioCodec;AVPacketQueue *m_audioQueue;AVPacketQueue *m_videoQueue;public:DemuxThread(AVPacketQueue *mAudioQueue, AVPacketQueue *mVideoQueue);DemuxThread(const QString &url, AVPacketQueue *mAudioQueue, AVPacketQueue *mVideoQueue);~DemuxThread() override;// 打开视频文件,读取信息int init() override;int start() override;void stop() override;void run() override;void setUrl(const QString &url);AVCodecParameters *getCodecParameters(MediaType type);AVRational *getStreamTimeBase(MediaType type);const AVCodec *getCodec(MediaType type);
};//DemuxThread.cpp
#include "DemuxThread.h"
DemuxThread::DemuxThread(AVPacketQueue *mAudioQueue, AVPacketQueue *mVideoQueue): m_audioQueue(mAudioQueue), m_videoQueue(mVideoQueue) {
}
DemuxThread::DemuxThread(const QString &url, AVPacketQueue *mAudioQueue, AVPacketQueue *mVideoQueue): m_url(url), m_audioQueue(mAudioQueue), m_videoQueue(mVideoQueue) {
}DemuxThread::~DemuxThread() {if (m_thread) {this->stop();}
}
int DemuxThread::init() {if (m_url == nullptr) {qDebug() << "没有设置文件链接";return -1;}ic = avformat_alloc_context();int ret;ret = avformat_open_input(&ic, m_url.toUtf8(), nullptr, nullptr);if (ret < 0) {qDebug() << "avformat_open_input 函数发送错误";return -1;}ret = avformat_find_stream_info(ic, nullptr);if (ret < 0) {qDebug() << "avformat_find_stream_info 函数发送错误";return -1;}m_videoStreamIndex = av_find_best_stream(ic, AVMEDIA_TYPE_VIDEO, -1, -1, &m_videoCodec, 0);if (m_videoStreamIndex < 0) {qDebug() << "没有找到视频流索引 av_find_best_stream error";return -1;}AVCodecParameters *codecParameters_video = ic->streams[m_videoStreamIndex]->codecpar;QString codecNameVideo = avcodec_get_name(codecParameters_video->codec_id);qDebug() << "视频流:" << codecNameVideo;m_audioStreamIndex = av_find_best_stream(ic, AVMEDIA_TYPE_AUDIO, -1, -1, &m_audioCodec, 0);if (m_audioStreamIndex < 0) {qDebug() << "没有找到音频流索引 av_find_best_stream error";return -1;}AVCodecParameters *codecParameters_audio = ic->streams[m_audioStreamIndex]->codecpar;QString codecNameAudio = avcodec_get_name(codecParameters_audio->codec_id);qDebug() << "音频流:" << codecNameAudio;return 1;
}
int DemuxThread::start() {if (init() != 1) {qDebug() << "打开文件失败,停止创建线程";return -1;}isStopped = false;isPlaying = true;QThread::start();if (!currentThread()) {qDebug() << "线程创建失败";return -1;}return 0;
}
void DemuxThread::stop() {BaseThread::stop();if (ic) {avformat_close_input(&ic);ic = nullptr;}if (m_videoCodec) {m_videoCodec = nullptr;}if (m_audioCodec) {m_audioCodec = nullptr;}
}
void DemuxThread::run() {int ret;AVPacket pkt;while (!isStopped) {//if (m_audioQueue->size() > 10 || m_videoQueue->size() > 10) {//                        qDebug()<<"解复用线程等待 " <<"videoSize "<< m_videoQueue->size()<<"audioSize "<<m_audioQueue->size();msleep(10);//            std::this_thread::sleep_for(std::chrono::milliseconds(10));continue;}ret = av_read_frame(ic, &pkt);if (ret < 0) {qDebug() << "帧读完";break;}if (pkt.stream_index == m_audioStreamIndex) {m_audioQueue->push(&pkt);//            qDebug() << "audio pkt queue size:" << m_audioQueue->size();} else if (pkt.stream_index == m_videoStreamIndex) {m_videoQueue->push(&pkt);//            qDebug() << "video pkt queue size:" << m_videoQueue->size();} else {av_packet_unref(&pkt);}}
}void DemuxThread::setUrl(const QString &url) {m_url = url;
}
AVCodecParameters *DemuxThread::getCodecParameters(MediaType type) {switch (type) {case MediaType::Audio:if (m_audioStreamIndex != -1) {return ic->streams[m_audioStreamIndex]->codecpar;} else {return nullptr;}case MediaType::Video:if (m_videoStreamIndex != -1) {return ic->streams[m_videoStreamIndex]->codecpar;} else {return nullptr;}}
}
AVRational *DemuxThread::getStreamTimeBase(MediaType type) {switch (type) {case MediaType::Audio:if (m_audioStreamIndex != -1) {return &ic->streams[m_audioStreamIndex]->time_base;} else {return nullptr;}case MediaType::Video:if (m_videoStreamIndex != -1) {return &ic->streams[m_videoStreamIndex]->time_base;} else {return nullptr;}}
}
const AVCodec *DemuxThread::getCodec(MediaType type) {switch (type) {case MediaType::Audio:return m_audioCodec;case MediaType::Video:return m_videoCodec;}
}

DecodeThread

//DecodeThread.h
#include "BaseThread.h"
#include "FFmpegHeader.h"
#include "queue/AVFrameQueue.h"
#include "queue/AVPacketQueue.h"
#include <QDebug>class DecodeThread : public BaseThread {
private:const AVCodec *m_codec = nullptr;AVCodecParameters *m_par = nullptr;AVPacketQueue *m_packetQueue = nullptr;AVFrameQueue *m_frameQueue = nullptr;public:AVCodecContext *dec_ctx = nullptr;DecodeThread(const AVCodec *mCodec, AVCodecParameters *mPar, AVPacketQueue *mPacketQueue, AVFrameQueue *mFrameQueue);~DecodeThread() override;int init() override;int start() override;void stop() override;void run() override;
};//DecodeThread.cpp
#include "DecodeThread.h"
DecodeThread::DecodeThread(const AVCodec *mCodec, AVCodecParameters *mPar, AVPacketQueue *mPacketQueue, AVFrameQueue *mFrameQueue): m_codec(mCodec), m_par(mPar), m_packetQueue(mPacketQueue), m_frameQueue(mFrameQueue) {
}
DecodeThread::~DecodeThread() {stop();
}
int DecodeThread::init() {if (!m_par) {qDebug() << "AVCodecParameters 为空";return -1;}dec_ctx = avcodec_alloc_context3(nullptr);int ret = avcodec_parameters_to_context(dec_ctx, m_par);if (ret < 0) {qDebug() << "avcodec_parameters_to_context error";}ret = avcodec_open2(dec_ctx, m_codec, nullptr);if (ret < 0) {qDebug() << "avcodec_open2 error";}return 0;
}
void DecodeThread::run() {AVFrame *frame = av_frame_alloc();while (!isStopped) {if (m_frameQueue->size() > 10) {//                        qDebug()<<"解码线程等待";msleep(10);//            std::this_thread::sleep_for(std::chrono::milliseconds(10));continue;}AVPacket *pkt = m_packetQueue->pop(5);if (pkt) {int ret = avcodec_send_packet(dec_ctx, pkt);av_packet_free(&pkt);if (ret < 0) {qDebug() << "avcodec_send_packet error";break;}while (true) {ret = avcodec_receive_frame(dec_ctx, frame);if (ret == 0) {m_frameQueue->push(frame);//                    qDebug()<<"m_frameQueue size:"<<m_frameQueue->size();continue;} else if (AVERROR(EAGAIN) == ret) {break;} else {isStopped = true;qDebug() << "avcodec_receive_frame error";break;}}} else {break;}}
}
int DecodeThread::start() {isStopped = false;isPlaying = true;QThread::start();if (!currentThread()) {qDebug() << "线程创建失败";return -1;}return 0;
}
void DecodeThread::stop() {BaseThread::stop();if (dec_ctx)avcodec_close(dec_ctx);
}

测试是否能够正常运行

现在解复用线程模块和解码线程模块都已经完成了,测试一下是否正常运行

main.cpp

#include <QApplication>
#include <QPushButton>
//-----------
#include "queue/AVFrameQueue.h"
#include "queue/AVPacketQueue.h"
#include "thread/DecodeThread.h"
#include "thread/DemuxThread.h"int main(int argc, char *argv[]) {QApplication a(argc, argv);QPushButton button("Hello world!", nullptr);button.resize(200, 100);button.show();// 解复用DemuxThread *demuxThread;DecodeThread *audioDecodeThread;DecodeThread *videoDecodeThread;// 解码-音频AVPacketQueue audioPacketQueue;AVFrameQueue audioFrameQueue;// 解码-视频AVPacketQueue videoPacketQueue;AVFrameQueue videoFrameQueue;demuxThread = new DemuxThread(&audioPacketQueue, &videoPacketQueue);demuxThread->setUrl("/Users/mac/Downloads/23.mp4");demuxThread->start();int ret;audioDecodeThread = new DecodeThread(demuxThread->getCodec(MediaType::Audio),demuxThread->getCodecParameters(MediaType::Audio),&audioPacketQueue,&audioFrameQueue);audioDecodeThread->init();audioDecodeThread->start();videoDecodeThread = new DecodeThread(demuxThread->getCodec(MediaType::Video),demuxThread->getCodecParameters(MediaType::Video),&videoPacketQueue,&videoFrameQueue);videoDecodeThread->init();videoDecodeThread->start();return QApplication::exec();
}

测试完成运行正常

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/215850.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于 STM32Cube.AI 的嵌入式人脸识别算法实现

本文介绍了如何使用 STM32Cube.AI 工具开发嵌入式人脸识别算法。首先&#xff0c;我们将简要介绍 STM32Cube.AI 工具和 STM32F系列单片机的特点。接下来&#xff0c;我们将详细讨论如何使用 STM32Cube.AI 工具链和相关库来进行人脸识别算法的开发和优化。最后&#xff0c;我们提…

C语言——输入一个4位正整数,输出其逆数。

#define _CRT_SECURE_NO_WARNINGS 1#include<stdio.h> int main() {int i,j 0;int a1,a2,a3,a4;printf("输入一个4位正整数&#xff1a;\n");scanf("%d",&i);a1 i/1000; a2 i/100%10; a3 i/10%10; a4 i%10; printf("千位a1%d,百位a…

Django 通过 Trunc(kind) 和 Extract(lookup_name) 参数进行潜在 SQL 注入 (CVE-2022-34265)

漏洞描述 Django 于 2022 年6月4 日发布了一个安全更新&#xff0c;修复了 Trunc&#xff08;&#xff09; 和 Extract&#xff08;&#xff09; 数据库函数中的 SQL 注入漏洞。 参考链接&#xff1a; Django security releases issued: 4.0.6 and 3.2.14 | Weblog | Djang…

第98步 深度学习图像目标检测:SSD建模

基于WIN10的64位系统演示 一、写在前面 本期开始&#xff0c;我们继续学习深度学习图像目标检测系列&#xff0c;SSD&#xff08;Single Shot MultiBox Detector&#xff09;模型。 二、SSD简介 SSD&#xff08;Single Shot MultiBox Detector&#xff09;是一种流行的目标检…

pairplot

Python可视化 | Seaborn5分钟入门(七)——pairplot - 知乎 (zhihu.com) Seaborn是基于matplotlib的Python可视化库。它提供了一个高级界面来绘制有吸引力的统计图形。Seaborn其实是在matplotlib的基础上进行了更高级的API封装&#xff0c;从而使得作图更加容易&#xff0c;不需…

人脑工作机制 基本工作原理 神经元 神经网络 学习和记忆 和身体的互动 模仿游戏

人脑的工作机制非常复杂&#xff0c;涉及多个层面的结构和功能。以下是一些关键点&#xff0c;用以概述人脑的基本工作原理&#xff1a; 基本单位 - 神经元&#xff1a; 人脑包含大约860亿个神经元。神经元是脑的基本工作和信号处理单位&#xff0c;通过树突接收信号&#xff0…

python pdf转txt文本、pdf转json

文章目录 一、前言二、实现方法1. 目录结构2. 代码 一、前言 此方法只能转文本格式的pdf&#xff0c;如果是图片格式的pdf需要用到ocr包&#xff0c;以后如果有这方面需求再加这个方法 二、实现方法 1. 目录结构 2. 代码 pdf2txt.py 代码如下 #!/usr/bin/env python # -*- …

优秀的时间追踪软件Timemator for Mac轻松管理时间!

在现代社会&#xff0c;时间管理成为了我们工作和生活中的一大挑战。如果你经常感到时间不够用&#xff0c;无法高效地完成任务&#xff0c;那么Timemator for Mac将成为你的得力助手。 Timemator for Mac是一款出色的时间追踪软件&#xff0c;它可以帮助你精确记录和管理你的…

Dreamview底层实现原理

1. Dreamview底层实现原理(3个模块) (1) HMI--可视化人机交互 a. HMIConfig: 1) 支持哪些模式&#xff1b;2)支持哪些地图&#xff1b;3)支持哪些车辆&#xff1b;4)HMIAction HMIMode: b.HMIStatus (2) SimControl (3) Monitor--监视自动驾驶行驶过程中软硬件状态 Referenc…

JMeter+Python 实现异步接口测试

当使用JMeter和Python来实现异步接口测试时&#xff0c;可以按照以下步骤进行操作&#xff1a; 1、安装JMeter和Java Development Kit&#xff08;JDK&#xff09;&#xff1a; 下载并安装JMeter&#xff08;https://jmeter.apache.org/download_jmeter.cgi&#xff09;和适用…

在 Ubuntu 上安装最新版的 Calibre

目录 前言 方法1&#xff1a;从 Ubuntu 的仓库安装 Calibre 卸载 Calibre 方法2&#xff1a;获取最新版本的 Calibre 卸载 Calibre 结语 前言 Calibre 是一款自由开源的电子书软件。下面介绍如何在 Ubuntu Linux 上安装它。 作为电子书管理的瑞士军刀&#xff0c;Calibre …

Cesium 展示——地球以及渲染数据导出(下载)为图片或 pdf

文章目录 需求分析新加需求分析第一种方式第二种方式需求 将 Cesium 球体以及渲染数据导出为 jpg/png/pdf 分析 获取场景 scene 信息,转为image 的 octet-stream 流 进行下载为图片 /*** @todo canvas 导出图片* @param {string} dataurl - 地址* @return {Blob}*/ functio…