簡介
使用SDL實(shí)現(xiàn)簡單的YUV播放器。
這里還需要使用到像素格式和計(jì)算圖片大小,這兩個(gè)我們直接使用ffmpeg來實(shí)現(xiàn),因此需要使用ffmpeg的libavutil/avutil.h和libavutil/imgutils.h
初始化Video子系統(tǒng)
main.cpp
這里我們把SDL的初始化和退出都寫在main函數(shù)里。
int main(int argc, char *argv[]){
// 初始化Video子系統(tǒng)
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;
}
創(chuàng)建播放器YuvPlayer類
這里我們先創(chuàng)建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:
// 狀態(tài)
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;
// 紋理(直接跟特定驅(qū)動程序相關(guān)的像素?cái)?shù)據(jù))
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類中調(diào)用播放器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);
// 創(chuàng)建播放器
_player = new YuvPlayer(this);
// 設(shè)置播放器的位置和尺寸
int w = 640;
int h = 480;
int x = (width() - w) >> 1;
int y = (height() - h) >> 1;
_player->setGeometry(x, y, w, h);
// 設(shè)置需要播放的文件
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();
}
實(shí)現(xiàn)播放器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){
// 創(chuàng)建窗口
_window = SDL_CreateWindowFrom((void *)winId());
RET(!_window, SDL_CreateWindow);
// 創(chuàng)建渲染上下文(默認(rèn)的渲染目標(biāo)是window)
_renderer = SDL_CreateRenderer(_window,
// 要初始化的渲染設(shè)備的索引,設(shè)置 -1 則初始化第一個(gè)支持 flags 的設(shè)備
-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(){
// 開啟定時(shí)器
_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;
// 創(chuàng)建紋理
_texture = SDL_CreateTexture(_renderer,
//顯示的像素?cái)?shù)據(jù)格式,我們顯示的YUV圖片像素格式是yuv420p,
//其實(shí)SDL_PIXELFORMAT_IYUV就是yuv420p像素格式
PIXEL_FORMAT_MAP.find(yuv.pixelFormat)->second,
//之前我們把同一個(gè)texture在窗口繪制多次時(shí),我們設(shè)置的是SDL_TEXTUREACCESS_TARGET,
//這里我們設(shè)置SDL_TEXTUREACCESS_STATIC,當(dāng)然設(shè)置成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的像素?cái)?shù)據(jù)填充到texture
RET(SDL_UpdateTexture(_texture,
nullptr,// SDL_Rect:更新像素的矩形區(qū)域,傳nullptr表示更新整個(gè)紋理區(qū)域
data,// 原始像素?cái)?shù)據(jù)
_yuv.width),// 一行像素?cái)?shù)據(jù)的字節(jié)數(shù),這里傳圖片寬度即可
SDL_UpdateTexture);
// 渲染
// 設(shè)置繪制顏色(這里隨便設(shè)置了一個(gè)顏色:黑色)
RET(SDL_SetRenderDrawColor(_renderer,0,0,0,SDL_ALPHA_OPAQUE),SDL_SetRenderDrawColor);
// 用DrawColor清除渲染目標(biāo)
RET(SDL_RenderClear(_renderer),SDL_RenderClear);
// 復(fù)制紋理到渲染目標(biāo)上(默認(rèn)是window)可以使用SDL_SetRenderTarget()修改渲染目標(biāo)
// srcrect源矩形框,dstrect目標(biāo)矩形框,兩者都傳nullptr表示整個(gè)紋理渲染到整個(gè)目標(biāo)上去
RET(SDL_RenderCopy(_renderer,_texture,nullptr,nullptr),SDL_RenderCopy);
// 將此前的所有需要渲染的內(nèi)容更新到屏幕上
SDL_RenderPresent(_renderer);
}else{
// 文件數(shù)據(jù)已經(jīng)讀取完畢
killTimer(_timerId);
}
}