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

这篇具有很好参考价值的文章主要介绍了播放器开发(五):视频帧处理并用SDL渲染播放。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

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

步骤

VideoOutPut模块

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

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

VideoWidget自定义Widget类

1、定义内部变量

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

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

主要代码

分配缓存

    // 根据格式和视频宽高获取一张图像的字节数据大小
    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);

执行缩放

sws_scale(swsContext, (const uint8_t *const *) frame->data, frame->linesize, 0, decCtx->height, playFrame->data, playFrame->linesize);

创建SDL窗口纹理渲染器

// 这里必须要先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渲染

    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进行渲染可以原画像显示,不会有模糊的情况。

// 注意这里需要使用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,就是画面每秒传输帧数,通俗来讲就是指动画或视频的画面数。这个值一般是可以通过视频流的一些参数计算出来的,我们放到同步部分在说。

// 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窗口。

//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,并且在内调用播放视频的函数,测试是否能够看见画面

//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渲染播放 结果文章来源地址https://www.toymoban.com/news/detail-814783.html

到了这里,关于播放器开发(五):视频帧处理并用SDL渲染播放的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 音视频项目—基于FFmpeg和SDL的音视频播放器解析(二十一)

    介绍 在本系列,我打算花大篇幅讲解我的 gitee 项目音视频播放器,在这个项目,您可以学到音视频解封装,解码,SDL渲染相关的知识。您对源代码感兴趣的话,请查看基于FFmpeg和SDL的音视频播放器 如果您不理解本文,可参考我的前一篇文章音视频项目—基于FFmpeg和SDL的音视

    2024年02月02日
    浏览(75)
  • ffmpeg SDL播放器--播放udp组播流

    c++调用ffmpeg api及SDL库播放播放udp组播流。 代码及工程见https://download.csdn.net/download/daqinzl/88168574 参考文档:https://blog.csdn.net/a53818742/article/details/109312740 开发工具:visual studio 2019 记得推送udp流,可采用ffmpeg工具集里的ffmpeg.exe推送, 执行命令 ffmpeg -f gdigrab -r 23 -i desktop -pkt_size

    2024年02月14日
    浏览(41)
  • Android开发之视频播放器

    今天教大家做一个简单的视频播放器hh 至此,结束!

    2023年04月09日
    浏览(53)
  • 【Harmony OS】【ArkUI】ets开发 简易视频播放器

    前言:这一次我们来使用ets 的Swiper 组件、List 组件和Video 组件制作一个简易的视频播放器。本篇是以HarmonyOS 官网的codelab 简易视频播放器(eTS )为基础进行编写。本篇最主要的内容就是一个主界面包括顶部的视频海报轮播,中部的视频播放列表,以及点击海报和播放列表进

    2023年04月27日
    浏览(40)
  • Qt音视频开发41-文件推流(支持网页和播放器播放并切换进度)

    本功能最初也是有一些人提过类似的需求,就是能不能将本地的音视频文件,通过纯Qt程序推流出去,然后用户可以直接在网页上播放,也可以用各种播放器播放,然后还可以任意切换播放进度,其实说白了就是个文件服务器,用户通过网络地址访问以后,告诉对方当前是媒

    2024年02月01日
    浏览(73)
  • 038-第三代软件开发-简易视频播放器-自定义Slider (二)

    : Qt 、 Qml 、 3 、 4 、 5 欢迎来到我们的 QML C++ 项目!这个项目结合了 QML(Qt Meta-Object Language)和 C++ 的强大功能,旨在开发出色的用户界面和高性能的后端逻辑。 在项目中,我们利用 QML 的声明式语法和可视化设计能力创建出现代化的用户界面。

    2024年02月07日
    浏览(39)
  • opencv+ffmpeg+QOpenGLWidget开发的音视频播放器demo

        本篇文档的demo包含了 1.使用OpenCV对图像进行处理,对图像进行置灰,旋转,抠图,高斯模糊,中值滤波,部分区域清除置黑,背景移除,边缘检测等操作;2.单纯使用opencv播放显示视频;3.使用opencv和openGL播放显示视频;4.在ffmpeg解码后,使用opencv显示视频,并支持对视

    2024年02月12日
    浏览(70)
  • 用Qt开发的ffmpeg流媒体播放器,支持截图、录像,支持音视频播放,支持本地文件播放、网络流播放

    本工程qt用的版本是5.8-32位,ffmpeg用的版本是较新的5.1版本。它支持TCP或UDP方式拉取实时流,实时流我采用的是监控摄像头的RTSP流。音频播放采用的是QAudioOutput,视频经ffmpeg解码并由YUV转RGB后是在QOpenGLWidget下进行渲染显示。本工程的代码有注释,可以通过本博客查看代码或者

    2024年02月03日
    浏览(105)
  • QT软件开发-基于FFMPEG设计视频播放器-软解图像(一)

    QT软件开发-基于FFMPEG设计视频播放器-CPU软解视频(一) https://xiaolong.blog.csdn.net/article/details/126832537 QT软件开发-基于FFMPEG设计视频播放器-GPU硬解视频(二) https://xiaolong.blog.csdn.net/article/details/126833434 QT软件开发-基于FFMPEG设计视频播放器-解码音频(三) https://xiaolong.blog.csdn.

    2023年04月08日
    浏览(51)
  • 【FFmpeg视频播放器开发】解封装解码流程、常用API和结构体简介(一)

    在正式编写 FFmpeg 播放器前,我们需要先简单了解下所要用到的 FFmpeg 库、播放与解码流程、函数和相关结构体。 库 介绍 avcodec 音视频编解码核心库 avformat 音视频容器格式的封装和解析 avutil 核心工具库 swscal 图像格式转换的模块 swresampel 音频重采样 avfilter 音视频滤镜库 如视

    2023年04月08日
    浏览(47)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包