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


    目录

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

    步骤

    VideoOutPut模块

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

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

    VideoWidget自定义Widget类

    1、定义内部变量

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

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

    主要代码

    分配缓存

    1. // 根据格式和视频宽高获取一张图像的字节数据大小
    2. int byte = av_image_get_buffer_size(AV_PIX_FMT_RGB32, videoWidth, videoHeight, 1);
    3. // 分配缓存空间
    4. buffer = (uint8_t *) av_malloc(byte * sizeof(uint8_t));
    5. // 类似于格式化已经申请的内存
    6. av_image_fill_arrays(playFrame->data, playFrame->linesize, buffer, AV_PIX_FMT_RGB32, videoWidth, videoHeight, 1);
    7. // 初始化分配并返回SwsContext
    8. 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窗口纹理渲染器

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

    SDL渲染

    1. int ret = 1;
    2. ret = SDL_RenderClear(sdlRenderer);
    3. if (ret != 0) {
    4. qDebug() << "SDL_RenderClear error";
    5. }
    6. // 帧数据更新到纹理
    7. ret = SDL_UpdateTexture(sdlTexture, &m_originalSDLRect, frame->data[0], frame->linesize[0]);
    8. // 如果这里的帧数据已经是YUV则需要使用下面的
    9. // ret = SDL_UpdateYUVTexture(sdlTexture, &sdlRect2, frame->data[0], frame->linesize[0], frame->data[1], frame->linesize[1], frame->data[2], frame->linesize[2]);
    10. if (ret != 0) {
    11. qDebug() << "SDL_UpdateYUVTexture error:" << SDL_GetError();
    12. }
    13. // 将纹理绘制到渲染器上
    14. ret = SDL_RenderCopy(sdlRenderer, sdlTexture, &m_originalSDLRect, nullptr);
    15. if (ret != 0) {
    16. qDebug() << "SDL_RenderCopy error";
    17. }
    18. // 刷新渲染器,将内容显示到窗口上
    19. SDL_RenderPresent(sdlRenderer);

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

    QPainter渲染

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

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

    完整模块

    VideoOutPut

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

    1. // VideoOutPut.h
    2. #include "FFmpegHeader.h"
    3. #include "SDL.h"
    4. #include "queue/AVFrameQueue.h"
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. class VideoOutPut : public QObject {
    11. Q_OBJECT
    12. private:
    13. std::thread *m_thread;
    14. bool isStopped = true; // 是否已经停止 停止时退出线程
    15. bool isPlaying = false;// 是否正在播放
    16. bool isPause = false; // 是否暂停
    17. uint8_t *buffer;//存储解码后图片buffer
    18. //图像缩放、颜色空间转换上下文
    19. SwsContext *swsContext;
    20. AVFrame *playFrame; //转换后的帧对象
    21. AVFrameQueue *frameQueue;//解码后的帧队列
    22. // 解码器上下文
    23. AVCodecContext *decCtx;// 音频解码器上下文
    24. public:
    25. VideoOutPut(AVCodecContext *dec_ctx, AVFrameQueue *frame_queue);
    26. int init();
    27. int start();
    28. void run();
    29. int videoWidth; //视频宽度
    30. int videoHeight;//视频高度
    31. Q_SIGNALS:
    32. void refreshImage(const SDL_Rect &sdlRect, AVFrame *frame);
    33. };
    34. // VideoOutPut.cpp
    35. #include "VideoOutPut.h"
    36. VideoOutPut::VideoOutPut(AVCodecContext *dec_ctx, AVFrameQueue *frame_queue)
    37. : decCtx(dec_ctx), frameQueue(frame_queue) {
    38. }
    39. int VideoOutPut::init() {
    40. //获取分辨率大小
    41. videoWidth = decCtx->width;
    42. videoHeight = decCtx->height;
    43. playFrame = av_frame_alloc();
    44. AVPixelFormat pixelFormat = decCtx->pix_fmt;
    45. qDebug() << av_get_pix_fmt_name(pixelFormat);
    46. int byte = av_image_get_buffer_size(AV_PIX_FMT_RGB32, videoWidth, videoHeight, 1);
    47. buffer = (uint8_t *) av_malloc(byte * sizeof(uint8_t));
    48. av_image_fill_arrays(playFrame->data, playFrame->linesize, buffer, AV_PIX_FMT_RGB32, videoWidth, videoHeight, 1);
    49. swsContext = sws_getContext(decCtx->width, decCtx->height, pixelFormat, videoWidth, videoHeight, AV_PIX_FMT_RGB32, SWS_FAST_BILINEAR, nullptr, nullptr, nullptr);
    50. return 1;
    51. }
    52. int VideoOutPut::start() {
    53. m_thread = new std::thread(&VideoOutPut::run, this);
    54. if (!m_thread->joinable()) {
    55. qDebug() << "VideoOutPut视频帧处理线程创建失败";
    56. return -1;
    57. }
    58. isStopped = false;
    59. isPlaying = true;
    60. return 0;
    61. }
    62. void VideoOutPut::run() {
    63. AVFrame *frame;
    64. while (!isStopped) {
    65. frame = frameQueue->pop(10);
    66. if (frame) {
    67. //图像缩放、颜色空间转换
    68. sws_scale(swsContext, (const uint8_t *const *) frame->data, frame->linesize, 0, decCtx->height, playFrame->data, playFrame->linesize);
    69. av_frame_unref(frame);
    70. //视频区域
    71. SDL_Rect sdlRect;
    72. sdlRect.x = 0;
    73. sdlRect.y = 0;
    74. sdlRect.w = decCtx->width;
    75. sdlRect.h = decCtx->height;
    76. //渲染到sdl窗口
    77. emit refreshImage(sdlRect, playFrame);
    78. av_usleep(39999);
    79. }
    80. }
    81. }

    VideoWidget

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


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

    1. //VideoWidget.h
    2. #include "FFmpegHeader.h"
    3. #include "SDL.h"
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. class VideoWidget : public QWidget {
    10. Q_OBJECT
    11. private:
    12. SDL_Rect m_originalSDLRect;
    13. AVFrame *frame = nullptr;
    14. SDL_Window *sdlWindow = nullptr;
    15. SDL_Renderer *sdlRenderer = nullptr;
    16. SDL_Texture *sdlTexture = nullptr;
    17. protected:
    18. void paintEvent(QPaintEvent *event) override;
    19. public:
    20. VideoWidget(QWidget *parent = 0);
    21. ~VideoWidget();
    22. void initSDL();
    23. public slots:
    24. void updateImage(const SDL_Rect &sdl_rect, AVFrame *frame);
    25. };
    26. //VideoWidget.cpp
    27. #include "VideoWidget.h"
    28. VideoWidget::VideoWidget(QWidget *parent)
    29. : QWidget(parent) {
    30. // 注册SDL类
    31. qRegisterMetaType("SDL_Rect");
    32. }
    33. VideoWidget::~VideoWidget() {
    34. if (frame)
    35. av_frame_free(&frame);
    36. if (sdlTexture)
    37. SDL_DestroyTexture(sdlTexture);
    38. if (sdlRenderer)
    39. SDL_DestroyRenderer(sdlRenderer);
    40. if (sdlWindow)
    41. SDL_DestroyWindow(sdlWindow);
    42. SDL_Quit();
    43. }
    44. void VideoWidget::initSDL() {
    45. // SDL init
    46. if (SDL_Init(SDL_INIT_VIDEO) != 0) {
    47. qDebug() << "SDL_INIT_VIDEO error";
    48. return;
    49. }
    50. // 这里必须要先SDL_CreateWindowFrom,然后在设置父类为this,否则会直接把父类当成参数作为背景
    51. sdlWindow = SDL_CreateWindowFrom((void *) winId());
    52. //渲染器
    53. sdlRenderer = SDL_CreateRenderer(sdlWindow, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
    54. //纹理 大小是视频大小
    55. sdlTexture = SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_BGRA32, SDL_TEXTUREACCESS_STREAMING, 1920, 1080);
    56. SDL_SetTextureScaleMode(sdlTexture, SDL_ScaleModeLinear);
    57. }
    58. void VideoWidget::updateImage(const SDL_Rect &sdl_rect, AVFrame *frame) {
    59. this->m_originalSDLRect = sdl_rect;
    60. this->frame = frame;
    61. this->update();
    62. }
    63. void VideoWidget::paintEvent(QPaintEvent *event) {
    64. if (!frame) {
    65. return;
    66. }
    67. #if 0//QPainter渲染
    68. QImage image((uchar *) frame->data[0], 1920, 1080, QImage::Format_RGB32);
    69. QPainter painter(this);
    70. painter.drawImage(this->rect(), image);
    71. #endif
    72. #if 1//SDL渲染
    73. int ret = 1;
    74. ret = SDL_RenderClear(sdlRenderer);
    75. if (ret != 0) {
    76. qDebug() << "SDL_RenderClear error";
    77. }
    78. // 创建SDL纹理并从表面创建
    79. ret = SDL_UpdateTexture(sdlTexture, &m_originalSDLRect, frame->data[0], frame->linesize[0]);
    80. // ret = SDL_UpdateYUVTexture(sdlTexture, &sdlRect2, frame->data[0], frame->linesize[0], frame->data[1], frame->linesize[1], frame->data[2], frame->linesize[2]);
    81. if (ret != 0) {
    82. qDebug() << "SDL_UpdateYUVTexture error:" << SDL_GetError();
    83. }
    84. // 将纹理绘制到渲染器上
    85. ret = SDL_RenderCopy(sdlRenderer, sdlTexture, &m_originalSDLRect, nullptr);
    86. if (ret != 0) {
    87. qDebug() << "SDL_RenderCopy error";
    88. }
    89. // 刷新渲染器,将内容显示到窗口上
    90. SDL_RenderPresent(sdlRenderer);
    91. #endif
    92. }

    PlayerMain

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

    1. //PlayerMain.h
    2. #include
    3. #include
    4. //-----------
    5. #include "queue/AVFrameQueue.h"
    6. #include "queue/AVPacketQueue.h"
    7. #include "thread/DecodeThread.h"
    8. #include "thread/DemuxThread.h"
    9. #include "widget/VideoWidget.h"
    10. #include "output/VideoOutPut.h"
    11. QT_BEGIN_NAMESPACE
    12. namespace Ui {
    13. class PlayerMain;
    14. }
    15. QT_END_NAMESPACE
    16. class PlayerMain : public QWidget {
    17. Q_OBJECT
    18. public:
    19. explicit PlayerMain(QWidget *parent = nullptr);
    20. ~PlayerMain() override;
    21. private:
    22. Ui::PlayerMain *ui;
    23. // 解复用
    24. DemuxThread *demuxThread;
    25. DecodeThread *audioDecodeThread;
    26. DecodeThread *videoDecodeThread;
    27. // 解码-音频
    28. AVPacketQueue audioPacketQueue;
    29. AVFrameQueue audioFrameQueue;
    30. // 解码-视频
    31. AVPacketQueue videoPacketQueue;
    32. AVFrameQueue videoFrameQueue;
    33. VideoOutPut *videoOutPut;
    34. };
    35. //PlayerMain.cpp
    36. #include "PlayerMain.h"
    37. #include "ui_PlayerMain.h"
    38. PlayerMain::PlayerMain(QWidget *parent)
    39. : QWidget(parent), ui(new Ui::PlayerMain) {
    40. ui->setupUi(this);
    41. // 解复用
    42. demuxThread = new DemuxThread(&audioPacketQueue, &videoPacketQueue);
    43. demuxThread->setUrl("/Users/mac/Downloads/0911超前派对:于文文孟佳爆笑猜词 王源欧阳靖脑洞大开.mp4");
    44. // demuxThread->setUrl("/Users/mac/Downloads/23.mp4");
    45. demuxThread->start();
    46. int ret;
    47. // 解码-音频
    48. audioDecodeThread = new DecodeThread(
    49. demuxThread->getCodec(MediaType::Audio),
    50. demuxThread->getCodecParameters(MediaType::Audio),
    51. &audioPacketQueue,
    52. &audioFrameQueue);
    53. audioDecodeThread->init();
    54. audioDecodeThread->start();
    55. // 解码-视频
    56. videoDecodeThread = new DecodeThread(
    57. demuxThread->getCodec(MediaType::Video),
    58. demuxThread->getCodecParameters(MediaType::Video),
    59. &videoPacketQueue,
    60. &videoFrameQueue);
    61. videoDecodeThread->init();
    62. videoDecodeThread->start();
    63. // video output
    64. this->resize(1920/2,1080/2);
    65. videoOutPut = new VideoOutPut(videoDecodeThread->dec_ctx, &videoFrameQueue);
    66. videoOutPut->init();
    67. VideoWidget *videoWidget = new VideoWidget(this);
    68. connect(videoOutPut, &VideoOutPut::refreshImage, videoWidget, &VideoWidget::updateImage);
    69. videoWidget->show();
    70. videoWidget->initSDL();
    71. videoOutPut->start();
    72. // videoWidget->setParent(this);
    73. }
    74. PlayerMain::~PlayerMain() {
    75. delete ui;
    76. }

    测试运行结果

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

  • 相关阅读:
    vue3导出表格(导出成Execl表)
    #力扣:70. 爬楼梯@FDDLC
    聊聊数据库事务内嵌TCP连接
    Redis运行为什么快
    什么是轻量应用服务器?腾讯云轻量服务器可以干什么?
    .NET开源功能强大的串口调试工具
    Java面试中的常问的多线程问题
    Mac电脑录屏软件 Screen Recorder by Omi 中文最新
    JWT令牌实现登陆校验
    【Linux】权限
  • 原文地址:https://blog.csdn.net/xyl192960/article/details/134616968