22_播放器之使用SDL顯示YUV視頻

簡介

使用SDL實(shí)現(xiàn)簡單的YUV播放器。
這里還需要使用到像素格式和計(jì)算圖片大小,這兩個(gè)我們直接使用ffmpeg來實(shí)現(xiàn),因此需要使用ffmpeg的libavutil/avutil.hlibavutil/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);
    }
}

代碼鏈接

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容