美文网首页
音视频-SDL播放YUV(下)

音视频-SDL播放YUV(下)

作者: li_礼光 | 来源:发表于2021-08-15 22:07 被阅读0次

    音视频-SDL播放YUV(上) 最后

    while(infile.read(data, imgSize) > 0) {
            // 将YUV数据更新到render中
            renderUpdateRet = SDL_UpdateTexture(texture, nullptr, data, IMG_W);
            END(renderUpdateRet, SDL_RenderCopy);
    
            // 拷贝纹理数据到渲染目标(默认是window)
            renderCopyRet = SDL_RenderCopy(renderer, texture, nullptr, nullptr);
            END(renderCopyRet, SDL_RenderCopy);
    
            // 将此前的所有需要渲染的内容更新到屏幕上
            SDL_RenderPresent(renderer);
    }
    

    通过while循环的方式读取yuv裸流数据到SDL中渲染播放。这里只是作为一个简单的逻辑思路理解播放视频是怎么一回事。

    因为这里解决不了 1秒钟播放24帧的问题。



    现在,完整实现一个Yuvplayer来实现播放,暂停, 终止播放的播放器。

    yuvplayer

    创建一个YuvPlayer播放类,给予QWidget

    YuvPlayer.h

    #ifndef YUVPLAYER_H
    #define YUVPLAYER_H
    
    extern "C" {
    #define SDL_MAIN_HANDLED
    #include <SDL2/SDL.h>
    }
    
    #include <QWidget>
    #include <QFile>
    
    typedef struct  {
        const char *fileName; //文件路径A
        int width;  //分辨率-宽
        int height; //分辨率-高
        int framerate;  //帧率
        SDL_PixelFormatEnum pixelFormat;//像素格式
        int timeInterval; //跟定时器相关
    } YuvParams;
    
    class YuvPlayer : public QWidget {
        Q_OBJECT
    
    private:
        // 创建一个SDL Window窗口
        SDL_Window *_window = nullptr;
        // 像素数据
        SDL_Surface *_surface = nullptr;
        // 纹理(直接跟特定驱动程序相关的像素数据)
        SDL_Texture *_texture = nullptr;
        // 渲染上下文
        SDL_Renderer *_renderer = nullptr;
    
        // 加载的文件路径
        QFile _infile;
    
        QTimer *qTimer = nullptr;
    
        // 是否在播放中, 默认false
        bool _playing = false;
        // 是否暂停播放, 默认false
        bool _pause = false;
        // 是否终止播放, 默认true
        bool _stop = true;
    
        // yuv参数
        YuvParams _yuvParams;
        void loadYUVData();
    
    public:
        explicit YuvPlayer(QWidget *parent = nullptr);
        ~YuvPlayer();
    
        void play(YuvParams yuvParam);
        bool isPlaying();
    
        void pause();
        bool isPause();
    
        void stop();
        bool isStop();
    signals:
    
    };
    
    #endif // YUVPLAYER_H
    
    

    YuvPlayer.cpp

    #include "yuvplayer.h"
    #include <QDebug>
    #include <QTimer>
    #include <QDate>
    
    YuvPlayer::YuvPlayer(QWidget *parent) : QWidget(parent) {
        setAttribute(Qt::WA_StyledBackground);
        setStyleSheet("background: black");
    
        if(SDL_Init(SDL_INIT_VIDEO)) {
            qDebug() << "SDL_Init error : " << SDL_GetError();
            return;
        }
    
        _window = SDL_CreateWindowFrom((void *)this->winId());
        if(!_window) {
            qDebug() << "SDL_CreateWindow error : " << SDL_GetError();
            SDL_DestroyWindow(_window);
            return;
        }
    
        _renderer = SDL_CreateRenderer(_window, -1,  SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
        if (!_renderer) { // 说明开启硬件加速失败
            _renderer = SDL_CreateRenderer(_window, -1, 0);
        }
        if(!_renderer) {
            qDebug() << "SDL_CreateRenderer error : " << SDL_GetError();
            SDL_DestroyWindow(_window);
            SDL_DestroyRenderer(_renderer);
            return;
        }
    }
    
    YuvPlayer::~YuvPlayer() {
        SDL_DestroyWindow(_window);
        SDL_DestroyRenderer(_renderer);
        SDL_DestroyTexture(_texture);
        SDL_Quit();
    }
    
    // 播放
    void YuvPlayer::play(YuvParams yuvParam) {
        // 如果当前是播放的过程中, 直接跳过
        if(_playing && !_pause && !_stop) {
            return;
        }
    
        // 如果当前是暂停的过程中, 恢复播放
        if(!_playing && _pause && !_stop) {
            qTimer->start();
            _playing = true;
            _pause = false;
            return;
        }
    
        // 如果当前是初次播放, 创建定时器
        _playing = true;
        _pause = false;
        _stop = false;
    
        _yuvParams.fileName = yuvParam.fileName;
        _yuvParams.width = yuvParam.width;
        _yuvParams.height = yuvParam.height;
        _yuvParams.framerate = yuvParam.framerate;
        _yuvParams.pixelFormat = yuvParam.pixelFormat;
        _yuvParams.timeInterval = (1 * 1000 / _yuvParams.framerate);
    
        if (!qTimer) {
            _infile.setFileName(_yuvParams.fileName);
            // 打开文件
            if (!_infile.open(QFile::ReadOnly)) {
                qDebug() << "infile.open error : " << SDL_GetError();
                return;
            }
            _texture = SDL_CreateTexture(_renderer, _yuvParams.pixelFormat, SDL_TEXTUREACCESS_STREAMING, _yuvParams.width, _yuvParams.height);
            if (!_texture) {
                qDebug() << "SDL_CreateTexture error : " << SDL_GetError();
                return;
            }
    
            // 创建定时器
            qTimer = new QTimer();
            qTimer->setTimerType(Qt::PreciseTimer);
            qTimer->start(_yuvParams.timeInterval);
    
            connect(qTimer, &QTimer::timeout, this, [ = ]() {
                this->loadYUVData();
            });
        }
    }
    
    //暂停
    void YuvPlayer::pause() {
        if(_pause) {
            return;
        }
        _playing = false;
        _pause = true;
        _stop = false;
    
        // 暂时定时器
        qTimer->stop();
    }
    
    //终止
    void YuvPlayer::stop() {
        if(_stop) {
            return;
        }
        _playing = false;
        _pause = false;
        _stop = true;
    
        qTimer->stop();
        qTimer = nullptr;
    
        // 停止关闭文件
        _infile.close();
    
        if(_renderer) {
            SDL_SetRenderDrawColor(_renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
            SDL_RenderClear(_renderer);
            SDL_RenderPresent(_renderer);
        }
    }
    
    
    bool YuvPlayer::isPlaying() {
        return _playing;
    }
    bool YuvPlayer::isPause() {
        return _pause;
    }
    bool YuvPlayer::isStop() {
        return _stop;
    }
    
    // 每隔一段时间就会调用
    void YuvPlayer::loadYUVData() {
        qDebug() << "Time : " <<  QTime::currentTime();
        int imgSize = _yuvParams.width * _yuvParams.height * 1.5;
        char data[imgSize];
    
        // 创建一个定时器, 一秒获取固定帧的数据
        if( _infile.read(data, imgSize) > 0) {
            // 将YUV数据更新到render中
            if(SDL_UpdateTexture(_texture, nullptr, data, _yuvParams.width)) {
                qDebug() << "SDL_RenderCopy error : " << SDL_GetError();
            }
            if(SDL_SetRenderDrawColor(_renderer, 0, 0, 0, SDL_ALPHA_OPAQUE)) {
                qDebug() << "SDL_SetRenderDrawColor error : " << SDL_GetError();
            }
            if(SDL_RenderCopy(_renderer, _texture, nullptr, nullptr)) {
                qDebug() << "SDL_RenderCopy error : " << SDL_GetError();
            }
            // 将此前的所有需要渲染的内容更新到屏幕上
            SDL_RenderPresent(_renderer);
        } else {
            this->stop();
        }
    }
    

    在MainWindow中

    MainWindow.h

    #ifndef MAINWINDOW_H
    #define MAINWINDOW_H
    
    #include <QMainWindow>
    #include "yuvplayer.h"
    
    QT_BEGIN_NAMESPACE
    namespace Ui {
        class MainWindow;
    }
    QT_END_NAMESPACE
    
    class MainWindow : public QMainWindow {
        Q_OBJECT
    
    public:
        MainWindow(QWidget *parent = nullptr);
        ~MainWindow();
    
    private slots:
        void on_playBtn_clicked();
        void on_stopBtn_clicked();
    
    private:
        Ui::MainWindow *ui;
        YuvPlayer *_yuvPlayer = nullptr;
    
    };
    #endif // MAINWINDOW_H
    
    

    MainWindow.cpp

    #include "mainwindow.h"
    #include "ui_mainwindow.h"
    #include <QtDebug>
    
    
    #define FILENAME "G:/BigBuckBunny_CIF_24fps.yuv"
    #define PIXEL_FORMAT SDL_PIXELFORMAT_IYUV
    #define IMG_W 352
    #define IMG_H 288
    #define FRAME_RATE 24
    
    
    MainWindow::MainWindow(QWidget *parent)
        : QMainWindow(parent)
        , ui(new Ui::MainWindow) {
        ui->setupUi(this);
    
        // 主窗口大小 :800* 600
        this->setFixedSize(QSize(800, 600));
        _yuvPlayer = new YuvPlayer(this);
        _yuvPlayer->setGeometry(200, 50, 400, 400);
    }
    
    MainWindow::~MainWindow() {
        delete ui;
    }
    
    void MainWindow::on_playBtn_clicked() {
        // 播放, 接受一个YUV结构体
        YuvParams yuvparam;
        yuvparam.fileName = FILENAME;
        yuvparam.width = IMG_W;
        yuvparam.height = IMG_H;
        yuvparam.framerate = FRAME_RATE;
        yuvparam.pixelFormat = PIXEL_FORMAT;
    
        if (_yuvPlayer->isStop()) {
            ui->playBtn->setText("暂停");
            _yuvPlayer->play(yuvparam);
        } else {
            if(_yuvPlayer->isPlaying()) {
                ui->playBtn->setText("播放");
                _yuvPlayer->pause();
            } else {
                ui->playBtn->setText("暂停");
                _yuvPlayer->play(yuvparam);
            }
        }
    
    
    }
    
    void MainWindow::on_stopBtn_clicked() {
        ui->playBtn->setText("播放");
        _yuvPlayer->stop();
    }
    
    

    核心代码

    // 每隔一段时间就会调用
    void YuvPlayer::loadYUVData() {
        qDebug() << "Time : " <<  QTime::currentTime();
        int imgSize = _yuvParams.width * _yuvParams.height * 1.5;
        char data[imgSize];
    
        // 创建一个定时器, 一秒获取固定帧的数据
        if( _infile.read(data, imgSize) > 0) {
            // 将YUV数据更新到render中
            if(SDL_UpdateTexture(_texture, nullptr, data, _yuvParams.width)) {
                qDebug() << "SDL_RenderCopy error : " << SDL_GetError();
            }
            if(SDL_SetRenderDrawColor(_renderer, 0, 0, 0, SDL_ALPHA_OPAQUE)) {
                qDebug() << "SDL_SetRenderDrawColor error : " << SDL_GetError();
            }
            if(SDL_RenderCopy(_renderer, _texture, nullptr, nullptr)) {
                qDebug() << "SDL_RenderCopy error : " << SDL_GetError();
            }
            // 将此前的所有需要渲染的内容更新到屏幕上
            SDL_RenderPresent(_renderer);
        } else {
            this->stop();
        }
    }
    

    每隔一段时间去读取一帧的数据, 这一段的时间计算就是 间隔时间 = 1秒钟的时间 / 帧率, 比如24fps的视频内容, 1 * 1000 / 24 ≈ 41.667,也就是需要通过一个定时器, 间隔时间41.667的时间读取一帧的数据。

    // 创建定时器
    qTimer = new QTimer();
    qTimer->setTimerType(Qt::PreciseTimer);
    qTimer->start(_yuvParams.timeInterval);
    
    connect(qTimer, &QTimer::timeout, this, [ = ]() {
        this->loadYUVData();
    });
    

    其中 _yuvParams.timeInterval 就是间隔时间, _yuvParams.timeInterval = (1 * 1000 / _yuvParams.framerate);

    最后, 如果想做一个快进慢放的效果, 通过改变这个定时器的timeInterval 就可以实现了

    相关文章

      网友评论

          本文标题:音视频-SDL播放YUV(下)

          本文链接:https://www.haomeiwen.com/subject/gwchmltx.html