22_播放器之使用SDL显示YUV视频
简介
使用SDL实现简单的YUV播放器。
这里还需要使用到像素格式和计算图片大小,这两个我们直接使用ffmpeg来实现,因此需要使用ffmpeg的libavutil/avutil.h
和libavutil/imgutils.h
初始化Video子系统
main.cpp
这里我们把SDL的初始化和退出都写在main函数里。
int main(int argc, char *argv[]){ // 初始化Video子系统 if (SDL_Init(SDL_INIT_VIDEO)) { qDebug() << "SDL_Init error" << SDL_GetError(); return 0; } QApplication a(argc, argv); MainWindow w; w.show(); int ret = a.exec(); SDL_Quit(); return ret; }
创建播放器YuvPlayer类
这里我们先创建YuvPlayer类,然后向往提供一些方法。
yuvplayer.h
#ifndef YUVPLAYER_H #define YUVPLAYER_H #include <QWidget> #include <SDL2/SDL.h> #include <QFile> extern "C"{ #include <libavutil/avutil.h> #include <libavutil/imgutils.h> } typedef struct{ const char *filename; int width; int height; AVPixelFormat pixelFormat; int fps; }Yuv; class YuvPlayer : public QWidget{ Q_OBJECT public: // 状态 typedef enum{ Stopped = 0, Playing, Paused, Finished } State; explicit YuvPlayer(QWidget *parent = nullptr); ~YuvPlayer(); void play(); void pause(); void stop(); bool isPlaying(); void setYuv(Yuv &yuv); State getState(); signals: private: // 窗口 SDL_Window *_window = nullptr; // 渲染上下文 SDL_Renderer *_renderer = nullptr; // 纹理(直接跟特定驱动程序相关的像素数据) SDL_Texture *_texture = nullptr; QFile _file; int _timerId = 0; State _state = Stopped; Yuv _yuv; bool _playing; void timerEvent(QTimerEvent *event); }; #endif // YUVPLAYER_H
mainwindow.cpp
在MainWindow类中调用播放器YuvPlayer的一些方法
#include "mainwindow.h" #include "ui_mainwindow.h" #include <QDebug> #ifdef Q_OS_WIN #define FILENAME "../test/out.yuv" #else #define FILENAME "/Users/zuojie/QtProjects/audio-video-dev/test/out.yuv" #endif MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow){ ui->setupUi(this); // 创建播放器 _player = new YuvPlayer(this); // 设置播放器的位置和尺寸 int w = 640; int h = 480; int x = (width() - w) >> 1; int y = (height() - h) >> 1; _player->setGeometry(x, y, w, h); // 设置需要播放的文件 Yuv yuv = { FILENAME, 848,480, AV_PIX_FMT_YUV420P, 30 }; _player->setYuv(yuv); } MainWindow::~MainWindow(){ delete ui; } void MainWindow::on_playButton_clicked(){ if(_player->isPlaying()){// 正在播放 _player->pause(); ui->playButton->setText("播放"); }else{// 没有正在播放 _player->play(); ui->playButton->setText("暂停"); } } void MainWindow::on_stopButton_clicked(){ _player->stop(); }
实现播放器YuvPlayer的方法
yuvplayer.cpp
#include "yuvplayer.h" #include <QDebug> #define RET(judge, func) \ if (judge) { \ qDebug() << #func << "error" << SDL_GetError(); \ return; \ } static const std::map<AVPixelFormat, SDL_PixelFormatEnum> PIXEL_FORMAT_MAP = { {AV_PIX_FMT_YUV420P, SDL_PIXELFORMAT_IYUV}, {AV_PIX_FMT_YUYV422, SDL_PIXELFORMAT_YUY2}, {AV_PIX_FMT_NONE, SDL_PIXELFORMAT_UNKNOWN} }; YuvPlayer::YuvPlayer(QWidget *parent) : QWidget(parent){ // 创建窗口 _window = SDL_CreateWindowFrom((void *)winId()); RET(!_window, SDL_CreateWindow); // 创建渲染上下文(默认的渲染目标是window) _renderer = SDL_CreateRenderer(_window, // 要初始化的渲染设备的索引,设置 -1 则初始化第一个支持 flags 的设备 -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); if (!_renderer) { // 说明开启硬件加速失败 _renderer = SDL_CreateRenderer(_window, -1, 0); } RET(!_renderer, SDL_CreateRenderer); } YuvPlayer::~YuvPlayer(){ _file.close(); SDL_DestroyTexture(_texture); SDL_DestroyRenderer(_renderer); SDL_DestroyWindow(_window); } void YuvPlayer::play(){ // 开启定时器 _timerId = startTimer(1000 / _yuv.fps); _state = YuvPlayer::Playing; } void YuvPlayer::pause(){ if(_timerId){ killTimer(_timerId); } _state = YuvPlayer::Paused; } void YuvPlayer::stop(){ if(_timerId){ killTimer(_timerId); } _state = YuvPlayer::Stopped; } bool YuvPlayer::isPlaying(){ return _state == YuvPlayer::Playing; } YuvPlayer::State YuvPlayer::getState(){ return _state; } void YuvPlayer::setYuv(Yuv &yuv){ _yuv = yuv; // 创建纹理 _texture = SDL_CreateTexture(_renderer, //显示的像素数据格式,我们显示的YUV图片像素格式是yuv420p, //其实SDL_PIXELFORMAT_IYUV就是yuv420p像素格式 PIXEL_FORMAT_MAP.find(yuv.pixelFormat)->second, //之前我们把同一个texture在窗口绘制多次时,我们设置的是SDL_TEXTUREACCESS_TARGET, //这里我们设置SDL_TEXTUREACCESS_STATIC,当然设置成SDL_TEXTUREACCESS_STREAMING也可以 SDL_TEXTUREACCESS_STREAMING, yuv.width,yuv.height); RET(!_texture, SDL_CreateTexture); // 打开文件 _file.setFileName(yuv.filename); if(!_file.open(QFile::ReadOnly)){ qDebug() << "file open error" << yuv.filename; } } void YuvPlayer::timerEvent(QTimerEvent *event){ // 图片大小 int imgSize = av_image_get_buffer_size(_yuv.pixelFormat, _yuv.width,_yuv.height, 1); char data[imgSize]; // 每次读取一帧图像 if(_file.read(data,imgSize) > 0){ // 将YUV的像素数据填充到texture RET(SDL_UpdateTexture(_texture, nullptr,// SDL_Rect:更新像素的矩形区域,传nullptr表示更新整个纹理区域 data,// 原始像素数据 _yuv.width),// 一行像素数据的字节数,这里传图片宽度即可 SDL_UpdateTexture); // 渲染 // 设置绘制颜色(这里随便设置了一个颜色:黑色) RET(SDL_SetRenderDrawColor(_renderer,0,0,0,SDL_ALPHA_OPAQUE),SDL_SetRenderDrawColor); // 用DrawColor清除渲染目标 RET(SDL_RenderClear(_renderer),SDL_RenderClear); // 复制纹理到渲染目标上(默认是window)可以使用SDL_SetRenderTarget()修改渲染目标 // srcrect源矩形框,dstrect目标矩形框,两者都传nullptr表示整个纹理渲染到整个目标上去 RET(SDL_RenderCopy(_renderer,_texture,nullptr,nullptr),SDL_RenderCopy); // 将此前的所有需要渲染的内容更新到屏幕上 SDL_RenderPresent(_renderer); }else{ // 文件数据已经读取完毕 killTimer(_timerId); } }
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!