在 音视频-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播放类,给予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 就可以实现了