播放器开发(五):视频帧处理并用SDL渲染播放

目录

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

步骤

VideoOutPut模块

1、初始化【分配缓存、读取信息】

2、开始线程工作【从队列读帧->缩放->发送渲染信号到窗口】

VideoWidget自定义Widget类

1、定义内部变量

2、如果使用SDL,需要进行初始化

3、接收到信号后需要执行槽函数进行渲染

主要代码

分配缓存

cpp 复制代码
    // 根据格式和视频宽高获取一张图像的字节数据大小
    int byte = av_image_get_buffer_size(AV_PIX_FMT_RGB32, videoWidth, videoHeight, 1);
    // 分配缓存空间
    buffer = (uint8_t *) av_malloc(byte * sizeof(uint8_t));
    // 类似于格式化已经申请的内存
    av_image_fill_arrays(playFrame->data, playFrame->linesize, buffer, AV_PIX_FMT_RGB32, videoWidth, videoHeight, 1);
    // 初始化分配并返回SwsContext
    swsContext = sws_getContext(decCtx->width, decCtx->height, pixelFormat, videoWidth, videoHeight, AV_PIX_FMT_RGB32, SWS_FAST_BILINEAR, nullptr, nullptr, nullptr);

执行缩放

cpp 复制代码
sws_scale(swsContext, (const uint8_t *const *) frame->data, frame->linesize, 0, decCtx->height, playFrame->data, playFrame->linesize);

创建SDL窗口纹理渲染器

cpp 复制代码
// 这里必须要先SDL_CreateWindowFrom,然后在设置父类为this,否则会直接把父类当成参数作为背景
    sdlWindow = SDL_CreateWindowFrom((void *) winId());
    //渲染器
    sdlRenderer = SDL_CreateRenderer(sdlWindow, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
    //纹理 大小是视频大小
    sdlTexture = SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_BGRA32, SDL_TEXTUREACCESS_STREAMING, 1920, 1080);
    SDL_SetTextureScaleMode(sdlTexture, SDL_ScaleModeLinear);

SDL渲染

cpp 复制代码
    int ret = 1;
    ret = SDL_RenderClear(sdlRenderer);
    if (ret != 0) {
        qDebug() << "SDL_RenderClear error";
    }
    // 帧数据更新到纹理
    ret = SDL_UpdateTexture(sdlTexture, &m_originalSDLRect, frame->data[0], frame->linesize[0]);
    // 如果这里的帧数据已经是YUV则需要使用下面的
    //    ret = SDL_UpdateYUVTexture(sdlTexture, &sdlRect2, frame->data[0], frame->linesize[0], frame->data[1], frame->linesize[1], frame->data[2], frame->linesize[2]);
    if (ret != 0) {
        qDebug() << "SDL_UpdateYUVTexture error:" << SDL_GetError();
    }
    // 将纹理绘制到渲染器上
    ret = SDL_RenderCopy(sdlRenderer, sdlTexture, &m_originalSDLRect, nullptr);
    if (ret != 0) {
        qDebug() << "SDL_RenderCopy error";
    }
    //     刷新渲染器,将内容显示到窗口上
    SDL_RenderPresent(sdlRenderer);

SDL窗口渲染在mac系统上好像有点问题,缩放的时候图像会很模糊,暂时不知道是什么情况。望知道的朋友告知原因。

QPainter渲染

使用Qt的QPainter进行渲染可以原画像显示,不会有模糊的情况。

cpp 复制代码
// 注意这里需要使用RGB格式
QImage image((uchar *) frame->data[0], 1920, 1080, QImage::Format_RGB32);
QPainter painter(this);
painter.drawImage(this->rect(), image);

完整模块

VideoOutPut

1、run函数实现内为什么需要av_usleep(39999):

在run函数中是进行循环读取帧,然后缩放,最后发送信号进行渲染,如果我们不进行sleep,哪么就会在极短的时间内循环读取完所有的帧,渲染过快导致无法看清图像,因此我们需要进行sleep,这个时间对于单个视频流来说,一般是FPS ,就是画面每秒传输帧数,通俗来讲就是指动画或视频的画面数。这个值一般是可以通过视频流的一些参数计算出来的,我们放到同步部分在说。

cpp 复制代码
// VideoOutPut.h
#include "FFmpegHeader.h"
#include "SDL.h"
#include "queue/AVFrameQueue.h"
#include <QDebug>
#include <QObject>
#include <QtGui>
#include <QtWidgets>
#include <thread>

class VideoOutPut : public QObject {
    Q_OBJECT
private:
    std::thread *m_thread;
    bool isStopped = true; // 是否已经停止 停止时退出线程
    bool isPlaying = false;// 是否正在播放
    bool isPause = false;  // 是否暂停

    uint8_t *buffer;//存储解码后图片buffer

    //图像缩放、颜色空间转换上下文
    SwsContext *swsContext;
    AVFrame *playFrame;      //转换后的帧对象
    AVFrameQueue *frameQueue;//解码后的帧队列

    // 解码器上下文
    AVCodecContext *decCtx;// 音频解码器上下文
public:
    VideoOutPut(AVCodecContext *dec_ctx, AVFrameQueue *frame_queue);
    int init();
    int start();
    void run();

    int videoWidth; //视频宽度
    int videoHeight;//视频高度
Q_SIGNALS:
    void refreshImage(const SDL_Rect &sdlRect, AVFrame *frame);
};


// VideoOutPut.cpp
#include "VideoOutPut.h"
VideoOutPut::VideoOutPut(AVCodecContext *dec_ctx, AVFrameQueue *frame_queue)
    : decCtx(dec_ctx), frameQueue(frame_queue) {
}
int VideoOutPut::init() {

    //获取分辨率大小
    videoWidth = decCtx->width;
    videoHeight = decCtx->height;
    playFrame = av_frame_alloc();

    AVPixelFormat pixelFormat = decCtx->pix_fmt;
    qDebug() << av_get_pix_fmt_name(pixelFormat);

    int byte = av_image_get_buffer_size(AV_PIX_FMT_RGB32, videoWidth, videoHeight, 1);
    buffer = (uint8_t *) av_malloc(byte * sizeof(uint8_t));
    av_image_fill_arrays(playFrame->data, playFrame->linesize, buffer, AV_PIX_FMT_RGB32, videoWidth, videoHeight, 1);
    swsContext = sws_getContext(decCtx->width, decCtx->height, pixelFormat, videoWidth, videoHeight, AV_PIX_FMT_RGB32, SWS_FAST_BILINEAR, nullptr, nullptr, nullptr);

    return 1;
}
int VideoOutPut::start() {
    m_thread = new std::thread(&VideoOutPut::run, this);
    if (!m_thread->joinable()) {
        qDebug() << "VideoOutPut视频帧处理线程创建失败";
        return -1;
    }
    isStopped = false;
    isPlaying = true;
    return 0;
}

void VideoOutPut::run() {
    AVFrame *frame;
    while (!isStopped) {
        frame = frameQueue->pop(10);
        if (frame) {
            //图像缩放、颜色空间转换
            sws_scale(swsContext, (const uint8_t *const *) frame->data, frame->linesize, 0, decCtx->height, playFrame->data, playFrame->linesize);
            av_frame_unref(frame);

            //视频区域
            SDL_Rect sdlRect;
            sdlRect.x = 0;
            sdlRect.y = 0;
            sdlRect.w = decCtx->width;
            sdlRect.h = decCtx->height;
            //渲染到sdl窗口
            emit refreshImage(sdlRect, playFrame);
            av_usleep(39999);
        }
    }
}

VideoWidget

我们这里使用到QWidget,直接把SDL封装进去,做一个自定义类,之后使用qt creator 提升一下就行了。


注意:在使用时,如果要把VideoWidget嵌入到某个widget内部,需要在new 之后在单独setParent,不然会把整个父类主窗口当作sdl窗口。

cpp 复制代码
//VideoWidget.h
#include "FFmpegHeader.h"
#include "SDL.h"
#include <QApplication>
#include <QDebug>
#include <QMetaType>
#include <QWidget>
#include <QtGui>

class VideoWidget : public QWidget {
    Q_OBJECT
private:
    SDL_Rect m_originalSDLRect;
    AVFrame *frame = nullptr;
    SDL_Window *sdlWindow = nullptr;
    SDL_Renderer *sdlRenderer = nullptr;
    SDL_Texture *sdlTexture = nullptr;

protected:
    void paintEvent(QPaintEvent *event) override;

public:
    VideoWidget(QWidget *parent = 0);
    ~VideoWidget();
    void initSDL();
public slots:
    void updateImage(const SDL_Rect &sdl_rect, AVFrame *frame);
};



//VideoWidget.cpp
#include "VideoWidget.h"
VideoWidget::VideoWidget(QWidget *parent)
    : QWidget(parent) {
    // 注册SDL类
    qRegisterMetaType<SDL_Rect>("SDL_Rect");
}
VideoWidget::~VideoWidget() {
    if (frame)
        av_frame_free(&frame);
    if (sdlTexture)
        SDL_DestroyTexture(sdlTexture);
    if (sdlRenderer)
        SDL_DestroyRenderer(sdlRenderer);
    if (sdlWindow)
        SDL_DestroyWindow(sdlWindow);
    SDL_Quit();
}
void VideoWidget::initSDL() {
    // SDL init
    if (SDL_Init(SDL_INIT_VIDEO) != 0) {
        qDebug() << "SDL_INIT_VIDEO error";
        return;
    }

    // 这里必须要先SDL_CreateWindowFrom,然后在设置父类为this,否则会直接把父类当成参数作为背景
    sdlWindow = SDL_CreateWindowFrom((void *) winId());
    //渲染器
    sdlRenderer = SDL_CreateRenderer(sdlWindow, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
    //纹理 大小是视频大小
    sdlTexture = SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_BGRA32, SDL_TEXTUREACCESS_STREAMING, 1920, 1080);
    SDL_SetTextureScaleMode(sdlTexture, SDL_ScaleModeLinear);
}
void VideoWidget::updateImage(const SDL_Rect &sdl_rect, AVFrame *frame) {
    this->m_originalSDLRect = sdl_rect;
    this->frame = frame;
    this->update();
}
void VideoWidget::paintEvent(QPaintEvent *event) {
    if (!frame) {
        return;
    }
#if 0//QPainter渲染
        QImage image((uchar *) frame->data[0], 1920, 1080, QImage::Format_RGB32);
        QPainter painter(this);
        painter.drawImage(this->rect(), image);
#endif

#if 1//SDL渲染
    int ret = 1;
    ret = SDL_RenderClear(sdlRenderer);
    if (ret != 0) {
        qDebug() << "SDL_RenderClear error";
    }
    // 创建SDL纹理并从表面创建
    ret = SDL_UpdateTexture(sdlTexture, &m_originalSDLRect, frame->data[0], frame->linesize[0]);
    //    ret = SDL_UpdateYUVTexture(sdlTexture, &sdlRect2, frame->data[0], frame->linesize[0], frame->data[1], frame->linesize[1], frame->data[2], frame->linesize[2]);
    if (ret != 0) {
        qDebug() << "SDL_UpdateYUVTexture error:" << SDL_GetError();
    }
    // 将纹理绘制到渲染器上
    ret = SDL_RenderCopy(sdlRenderer, sdlTexture, &m_originalSDLRect, nullptr);
    if (ret != 0) {
        qDebug() << "SDL_RenderCopy error";
    }
    //     刷新渲染器,将内容显示到窗口上
    SDL_RenderPresent(sdlRenderer);
#endif
}

PlayerMain

因为我们使用到了QT,所以先简单创建一个qt的ui,并且在内调用播放视频的函数,测试是否能够看见画面

cpp 复制代码
//PlayerMain.h
#include <QWidget>
#include <QtCore>
//-----------
#include "queue/AVFrameQueue.h"
#include "queue/AVPacketQueue.h"
#include "thread/DecodeThread.h"
#include "thread/DemuxThread.h"
#include "widget/VideoWidget.h"
#include "output/VideoOutPut.h"


QT_BEGIN_NAMESPACE
namespace Ui {
    class PlayerMain;
}
QT_END_NAMESPACE

class PlayerMain : public QWidget {
    Q_OBJECT

public:
    explicit PlayerMain(QWidget *parent = nullptr);
    ~PlayerMain() override;

private:
    Ui::PlayerMain *ui;

    // 解复用
    DemuxThread *demuxThread;
    DecodeThread *audioDecodeThread;
    DecodeThread *videoDecodeThread;

    // 解码-音频
    AVPacketQueue audioPacketQueue;
    AVFrameQueue audioFrameQueue;
    // 解码-视频
    AVPacketQueue videoPacketQueue;
    AVFrameQueue videoFrameQueue;
    VideoOutPut *videoOutPut;
};



//PlayerMain.cpp
#include "PlayerMain.h"
#include "ui_PlayerMain.h"


PlayerMain::PlayerMain(QWidget *parent)
    : QWidget(parent), ui(new Ui::PlayerMain) {
    ui->setupUi(this);


    // 解复用
    demuxThread = new DemuxThread(&audioPacketQueue, &videoPacketQueue);
    demuxThread->setUrl("/Users/mac/Downloads/0911超前派对:于文文孟佳爆笑猜词 王源欧阳靖脑洞大开.mp4");
    //    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();

    // video output
    this->resize(1920/2,1080/2);
    videoOutPut = new VideoOutPut(videoDecodeThread->dec_ctx, &videoFrameQueue);
    videoOutPut->init();
    VideoWidget *videoWidget = new VideoWidget(this);
    connect(videoOutPut, &VideoOutPut::refreshImage, videoWidget, &VideoWidget::updateImage);
    videoWidget->show();
    videoWidget->initSDL();
    videoOutPut->start();
//    videoWidget->setParent(this);
}

PlayerMain::~PlayerMain() {
    delete ui;
}

测试运行结果

播放器开发(五):视频帧处理并用SDL渲染播放 结果

相关推荐
vortex516 分钟前
解决 VSCode 中 C/C++ 编码乱码问题的两种方法
c语言·c++·vscode
对愁眠1 小时前
【鸣潮,原神PC端启动器】仿二次元手游PC端游戏启动器,以鸣潮为例。
qt·c/c++
清风fu杨柳2 小时前
centos7 arm版本编译qt5.6.3详细说明
开发语言·arm开发·qt
清风fu杨柳2 小时前
麒麟服务器工作站SP1 arm环境qt5.6.3源码编译
服务器·arm开发·qt
醉颜凉2 小时前
【NOIP提高组】潜伏者
java·c语言·开发语言·c++·算法
hunandede2 小时前
FFmpeg 4.3 音视频-多路H265监控录放C++开发十三.2:avpacket中包含多个 NALU如何解析头部分析
c++·ffmpeg·音视频
声网2 小时前
「人眼视觉不再是视频消费的唯一形式」丨智能编解码和 AI 视频生成专场回顾@RTE2024
人工智能·音视频
爱学习的大牛1232 小时前
通过vmware虚拟机安装和调试编译好的 ReactOS
c++·windows内核
tumu_C4 小时前
C++模板特化实战:在使用开源库boost::geometry::index::rtree时,用特化来让其支持自己的数据类型
c++·开源
杜若南星4 小时前
保研考研机试攻略(满分篇):第二章——满分之路上(1)
数据结构·c++·经验分享·笔记·考研·算法·贪心算法