学习课题:逐步构建开发播放器【QT5 + FFmpeg6 + SDL2】
前言
创建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 reference
val = 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帧队列类
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
//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();
}