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

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

前言

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

创建BaseQueue基类

BaseQueue.h

cpp 复制代码
#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帧队列类

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


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

FFmpegHeader.h

cpp 复制代码
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

cpp 复制代码
//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

cpp 复制代码
//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抽象类

cpp 复制代码
#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

cpp 复制代码
//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

cpp 复制代码
//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

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();
}

测试完成运行正常

相关推荐
南东山人1 小时前
一文说清:C和C++混合编程
c语言·c++
LNTON羚通2 小时前
摄像机视频分析软件下载LiteAIServer视频智能分析平台玩手机打电话检测算法技术的实现
算法·目标检测·音视频·监控·视频监控
Ysjt | 深4 小时前
C++多线程编程入门教程(优质版)
java·开发语言·jvm·c++
ephemerals__4 小时前
【c++丨STL】list模拟实现(附源码)
开发语言·c++·list
Microsoft Word4 小时前
c++基础语法
开发语言·c++·算法
一只小小汤圆5 小时前
opencascade源码学习之BRepOffsetAPI包 -BRepOffsetAPI_DraftAngle
c++·学习·opencascade
legend_jz5 小时前
【Linux】线程控制
linux·服务器·开发语言·c++·笔记·学习·学习方法
嘿BRE5 小时前
【C++】几个基本容器的模拟实现(string,vector,list,stack,queue,priority_queue)
c++
ö Constancy6 小时前
c++ 笔记
开发语言·c++