学习课题:逐步构建开发播放器【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();
}