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_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);
- 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系统上好像有点问题,缩放的时候图像会很模糊,暂时不知道是什么情况。望知道的朋友告知原因。
使用Qt的QPainter进行渲染可以原画像显示,不会有模糊的情况。
- // 注意这里需要使用RGB格式
- QImage image((uchar *) frame->data[0], 1920, 1080, QImage::Format_RGB32);
- QPainter painter(this);
- painter.drawImage(this->rect(), image);
1、run函数实现内为什么需要av_usleep(39999):
在run函数中是进行循环读取帧,然后缩放,最后发送信号进行渲染,如果我们不进行sleep,哪么就会在极短的时间内循环读取完所有的帧,渲染过快导致无法看清图像,因此我们需要进行sleep,这个时间对于单个视频流来说,一般是FPS,就是画面每秒传输帧数,通俗来讲就是指动画或视频的画面数。这个值一般是可以通过视频流的一些参数计算出来的,我们放到同步部分在说。
- // VideoOutPut.h
- #include "FFmpegHeader.h"
- #include "SDL.h"
- #include "queue/AVFrameQueue.h"
- #include
- #include
- #include
- #include
- #include
-
- 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);
- }
- }
- }
我们这里使用到QWidget,直接把SDL封装进去,做一个自定义类,之后使用qt creator 提升一下就行了。
注意:在使用时,如果要把VideoWidget嵌入到某个widget内部,需要在new 之后在单独setParent,不然会把整个父类主窗口当作sdl窗口。
- //VideoWidget.h
- #include "FFmpegHeader.h"
- #include "SDL.h"
- #include
- #include
- #include
- #include
- #include
-
- 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"); - }
- 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
- }
因为我们使用到了QT,所以先简单创建一个qt的ui,并且在内调用播放视频的函数,测试是否能够看见画面
- //PlayerMain.h
- #include
- #include
- //-----------
- #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渲染播放 结果