Qt - 音视频播放
一、音频播放
-
QSound:用于播放未压缩的音频文件(如WAV文件),简单易用,但不支持压缩格式。
-
QMediaPlayer:功能强大的媒体播放类,支持音频和视频,可以播放多种格式的压缩音频文件。
-
QAudioOutput:用于播放原始PCM音频数据,适合需要实时处理音频数据的场景。
在 Qt 中,有多种音频播放类可供选择,每个类都有不同的用途和特点。以下是 Qt 中常用的音频播放类及其详细说明:
1. QSound - 简单音效播放
QSound类提供了一种播放未压缩音频文件(主要是WAV文件)的简单方法。它通常用于播放短小的音效,因为它在播放时不会阻塞主线程。
特点和用途
-
最简单的音频播放类
-
适合播放短音效、提示音
-
支持 WAV 格式
-
异步播放,不阻塞主线程
使用示例
#include <QSound>
// 播放系统提示音
QSound::play(":/sounds/beep.wav");
// 或者创建对象控制播放
QSound *sound = new QSound(":/sounds/notification.wav", this);
sound->play();
sound->setLoops(3); // 设置循环次数
// 检查是否正在播放
if (sound->isFinished()) {
sound->play();
}
2. QMediaPlayer - 功能完整的媒体播放器
QMediaPlayer是一个高级媒体播放类,它支持多种音频和视频格式。它使用平台底层的多媒体框架,因此支持的格式取决于平台。它提供了播放控制(播放、暂停、停止)、音量控制、播放进度控制等功能。
特点和用途
-
功能最全面的播放器
-
支持多种音频格式(MP3、WAV、AAC、OGG等)
-
支持网络流媒体
-
提供播放控制、进度、音量等功能
-
支持播放列表
使用示例
#include <QMediaPlayer>
#include <QAudioOutput>
// 基本播放
QMediaPlayer *player = new QMediaPlayer(this);
QAudioOutput *audioOutput = new QAudioOutput(this);
player->setAudioOutput(audioOutput);
player->setSource(QUrl::fromLocalFile("/path/to/audio.mp3"));
player->play();
// 控制功能
player->pause();
player->stop();
player->setPosition(30000); // 跳到30秒位置
audioOutput->setVolume(0.8); // 设置音量
// 信号连接
connect(player, &QMediaPlayer::positionChanged, [](qint64 position) {
qDebug() << "Current position:" << position;
});
connect(player, &QMediaPlayer::playbackStateChanged, [](QMediaPlayer::PlaybackState state) {
switch (state) {
case QMediaPlayer::PlayingState:
qDebug() << "Playing";
break;
case QMediaPlayer::PausedState:
qDebug() << "Paused";
break;
case QMediaPlayer::StoppedState:
qDebug() << "Stopped";
break;
}
});
播放列表功能
#include <QMediaPlayer>
#include <QAudioOutput>
#include <QMediaPlaylist>
QMediaPlayer *player = new QMediaPlayer(this);
QAudioOutput *audioOutput = new QAudioOutput(this);
QMediaPlaylist *playlist = new QMediaPlaylist(this);
player->setAudioOutput(audioOutput);
player->setPlaylist(playlist);
// 添加多个文件
playlist->addMedia(QUrl::fromLocalFile("/path/to/song1.mp3"));
playlist->addMedia(QUrl::fromLocalFile("/path/to/song2.mp3"));
playlist->addMedia(QUrl("http://example.com/stream.mp3"));
// 设置播放模式
playlist->setPlaybackMode(QMediaPlaylist::Loop); // 循环播放
// QMediaPlaylist::Sequential - 顺序播放
// QMediaPlaylist::CurrentItemInLoop - 单曲循环
// QMediaPlaylist::Random - 随机播放
player->play();
3. QAudioOutput - 原始PCM数据播放
QAudioOutput用于播放原始PCM音频数据。它需要应用程序提供音频数据,通常与QIODevice一起使用。适合需要实时生成或处理音频数据的场景,如语音通话、音频流播放等。
特点和用途
-
播放原始PCM音频数据
-
适合实时音频流、语音通话
-
低延迟播放
-
需要手动管理音频数据
使用示例
#include <QAudioOutput>
#include <QAudioDevice>
#include <QBuffer>
class RawAudioPlayer : public QObject
{
Q_OBJECT
public:
RawAudioPlayer(QObject *parent = nullptr) : QObject(parent)
{
setupAudioFormat();
setupAudioOutput();
}
void playPcmData(const QByteArray &pcmData)
{
if (m_outputDevice) {
m_outputDevice->write(pcmData);
}
}
void setVolume(float volume)
{
if (m_audioOutput) {
m_audioOutput->setVolume(volume);
}
}
private:
void setupAudioFormat()
{
m_format.setSampleRate(44100);
m_format.setChannelCount(2);
m_format.setSampleSize(16);
m_format.setCodec("audio/pcm");
m_format.setByteOrder(QAudioFormat::LittleEndian);
m_format.setSampleType(QAudioFormat::SignedInt);
}
void setupAudioOutput()
{
QAudioDevice outputDevice = QMediaDevices::defaultAudioOutput();
if (!outputDevice.isFormatSupported(m_format)) {
m_format = outputDevice.nearestFormat(m_format);
}
m_audioOutput = new QAudioOutput(outputDevice, m_format, this);
m_outputDevice = m_audioOutput->start();
}
QAudioFormat m_format;
QAudioOutput *m_audioOutput;
QIODevice *m_outputDevice;
};
4. QSoundEffect - 低延迟音效播放
特点和用途
-
专门用于游戏音效、UI反馈音
-
比 QSound 更低的延迟
-
支持循环播放和音量控制
-
适合需要快速响应的短音效
使用示例
#include <QSoundEffect>
#include <QAudioDevice>
class SoundEffectPlayer : public QObject
{
Q_OBJECT
public:
SoundEffectPlayer(QObject *parent = nullptr) : QObject(parent)
{
// 预加载音效
m_clickEffect.setSource(QUrl::fromLocalFile(":/sounds/click.wav"));
m_notificationEffect.setSource(QUrl::fromLocalFile(":/sounds/notification.wav"));
// 设置音量
m_clickEffect.setVolume(0.7f);
m_notificationEffect.setVolume(1.0f);
// 设置循环(适合背景音)
m_backgroundEffect.setSource(QUrl::fromLocalFile(":/sounds/background.wav"));
m_backgroundEffect.setLoopCount(QSoundEffect::Infinite); // 无限循环
}
void playClick() { m_clickEffect.play(); }
void playNotification() { m_notificationEffect.play(); }
void startBackground() { m_backgroundEffect.play(); }
void stopBackground() { m_backgroundEffect.stop(); }
private:
QSoundEffect m_clickEffect;
QSoundEffect m_notificationEffect;
QSoundEffect m_backgroundEffect;
};
二、视频播放
对于音频原始PCM,我们可以使用QAudioOutput,那么对于原始YUV视频数据,我们可以使用Qt的多媒体模块,但是注意,Qt多媒体模块通常用于播放压缩的视频文件(如MP4、AVI等)。对于原始视频数据(YUV、RGB),我们需要自己处理并显示。
在Qt中,视频播放主要依赖于Multimedia模块。以下是一些常用的视频播放类及其用途:
-
QMediaPlayer:用于播放音频和视频文件。它可以与QVideoWidget或QGraphicsVideoItem一起使用来显示视频。
-
QVideoWidget:一个简单的视频显示组件,用于在Qt应用程序中显示视频。
-
QGraphicsVideoItem:可以在QGraphicsView框架中显示视频的图形项。
在Qt5中,视频播放通常使用QMediaPlayer和QVideoWidget。
在 Qt 中,视频播放主要通过 Multimedia 模块提供。以下是 Qt 中常用的视频播放类及其详细说明:
1. QMediaPlayer - 核心媒体播放器
特点和用途
-
功能最全面的媒体播放器
-
支持音频和视频播放
-
支持多种格式(MP4、AVI、MKV、MOV等)
-
提供播放控制、进度、音量等功能
-
支持网络流媒体
基本使用示例
#include <QMediaPlayer>
#include <QVideoWidget>
#include <QAudioOutput>
class BasicVideoPlayer : public QWidget
{
Q_OBJECT
public:
BasicVideoPlayer(QWidget *parent = nullptr) : QWidget(parent)
{
setupUI();
setupPlayer();
}
void openFile(const QString &filePath)
{
m_mediaPlayer->setSource(QUrl::fromLocalFile(filePath));
m_mediaPlayer->play();
}
private:
void setupUI()
{
QVBoxLayout *layout = new QVBoxLayout(this);
m_videoWidget = new QVideoWidget(this);
m_videoWidget->setMinimumSize(640, 480);
layout->addWidget(m_videoWidget);
// 控制按钮
QHBoxLayout *controlLayout = new QHBoxLayout();
m_playButton = new QPushButton("播放", this);
m_pauseButton = new QPushButton("暂停", this);
m_stopButton = new QPushButton("停止", this);
controlLayout->addWidget(m_playButton);
controlLayout->addWidget(m_pauseButton);
controlLayout->addWidget(m_stopButton);
layout->addLayout(controlLayout);
connect(m_playButton, &QPushButton::clicked, this, &BasicVideoPlayer::play);
connect(m_pauseButton, &QPushButton::clicked, this, &BasicVideoPlayer::pause);
connect(m_stopButton, &QPushButton::clicked, this, &BasicVideoPlayer::stop);
}
void setupPlayer()
{
m_mediaPlayer = new QMediaPlayer(this);
m_audioOutput = new QAudioOutput(this);
// 设置视频输出和音频输出
m_mediaPlayer->setVideoOutput(m_videoWidget);
m_mediaPlayer->setAudioOutput(m_audioOutput);
// 连接信号
connect(m_mediaPlayer, &QMediaPlayer::playbackStateChanged,
this, &BasicVideoPlayer::onPlaybackStateChanged);
connect(m_mediaPlayer, &QMediaPlayer::positionChanged,
this, &BasicVideoPlayer::onPositionChanged);
connect(m_mediaPlayer, &QMediaPlayer::durationChanged,
this, &BasicVideoPlayer::onDurationChanged);
}
private slots:
void play() { m_mediaPlayer->play(); }
void pause() { m_mediaPlayer->pause(); }
void stop() { m_mediaPlayer->stop(); }
void onPlaybackStateChanged(QMediaPlayer::PlaybackState state)
{
qDebug() << "Playback state:" << state;
}
void onPositionChanged(qint64 position)
{
qDebug() << "Position:" << position;
}
void onDurationChanged(qint64 duration)
{
qDebug() << "Duration:" << duration;
}
private:
QMediaPlayer *m_mediaPlayer;
QVideoWidget *m_videoWidget;
QAudioOutput *m_audioOutput;
QPushButton *m_playButton, *m_pauseButton, *m_stopButton;
};
2. QVideoWidget - 视频显示组件
特点和用途
-
专门用于视频显示的Widget
-
可以嵌入到任何布局中
-
支持全屏显示
-
提供基本的视频渲染功能
高级功能示例
#include <QVideoWidget>
#include <QMediaPlayer>
#include <QMenu>
#include <QAction>
class AdvancedVideoWidget : public QVideoWidget
{
Q_OBJECT
public:
AdvancedVideoWidget(QWidget *parent = nullptr) : QVideoWidget(parent)
{
setupContextMenu();
}
protected:
void mouseDoubleClickEvent(QMouseEvent *event) override
{
// 双击切换全屏
if (isFullScreen()) {
showNormal();
} else {
showFullScreen();
}
QVideoWidget::mouseDoubleClickEvent(event);
}
void keyPressEvent(QKeyEvent *event) override
{
// ESC键退出全屏
if (event->key() == Qt::Key_Escape && isFullScreen()) {
showNormal();
event->accept();
} else {
QVideoWidget::keyPressEvent(event);
}
}
void contextMenuEvent(QContextMenuEvent *event) override
{
m_contextMenu->exec(event->globalPos());
}
private slots:
void toggleFullScreen()
{
if (isFullScreen()) {
showNormal();
} else {
showFullScreen();
}
}
void toggleAspectRatio()
{
QVideoWidget::AspectRatioMode mode = aspectRatioMode();
if (mode == QVideoWidget::AspectRatioMode::KeepAspectRatio) {
setAspectRatioMode(QVideoWidget::AspectRatioMode::KeepAspectRatioByExpanding);
} else {
setAspectRatioMode(QVideoWidget::AspectRatioMode::KeepAspectRatio);
}
}
private:
void setupContextMenu()
{
m_contextMenu = new QMenu(this);
QAction *fullScreenAction = new QAction("全屏", this);
connect(fullScreenAction, &QAction::triggered, this, &AdvancedVideoWidget::toggleFullScreen);
QAction *aspectRatioAction = new QAction("切换宽高比", this);
connect(aspectRatioAction, &QAction::triggered, this, &AdvancedVideoWidget::toggleAspectRatio);
m_contextMenu->addAction(fullScreenAction);
m_contextMenu->addAction(aspectRatioAction);
}
private:
QMenu *m_contextMenu;
};
3. QGraphicsVideoItem - 图形视图中的视频项
特点和用途
-
在 QGraphicsView 中显示视频
-
可以与其他图形项组合
-
支持变换、旋转、缩放等效果
-
适合复杂的UI布局和特效
使用示例
#include <QGraphicsVideoItem>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QMediaPlayer>
class GraphicsVideoPlayer : public QWidget
{
Q_OBJECT
public:
GraphicsVideoPlayer(QWidget *parent = nullptr) : QWidget(parent)
{
setupUI();
setupPlayer();
}
void openFile(const QString &filePath)
{
m_mediaPlayer->setSource(QUrl::fromLocalFile(filePath));
m_mediaPlayer->play();
}
private:
void setupUI()
{
QVBoxLayout *layout = new QVBoxLayout(this);
// 创建图形视图和场景
m_graphicsView = new QGraphicsView(this);
m_graphicsScene = new QGraphicsScene(this);
m_graphicsView->setScene(m_graphicsScene);
layout->addWidget(m_graphicsView);
}
void setupPlayer()
{
m_mediaPlayer = new QMediaPlayer(this);
m_audioOutput = new QAudioOutput(this);
m_videoItem = new QGraphicsVideoItem();
m_mediaPlayer->setAudioOutput(m_audioOutput);
m_mediaPlayer->setVideoOutput(m_videoItem);
// 将视频项添加到场景
m_graphicsScene->addItem(m_videoItem);
// 设置视频项位置和大小
m_videoItem->setPos(50, 50);
m_videoItem->setSize(QSizeF(640, 480));
// 添加其他图形项(示例:文字标签)
QGraphicsTextItem *textItem = m_graphicsScene->addText("视频播放器");
textItem->setPos(10, 10);
textItem->setDefaultTextColor(Qt::white);
// 设置视图大小
m_graphicsView->setSceneRect(0, 0, 740, 580);
}
private:
QMediaPlayer *m_mediaPlayer;
QAudioOutput *m_audioOutput;
QGraphicsVideoItem *m_videoItem;
QGraphicsView *m_graphicsView;
QGraphicsScene *m_graphicsScene;
};
4. 完整视频播放器
(1)常规视频文件播放(videoplayer.h)
#include <QWidget>
#include <QFileDialog>
#include <QMediaPlayer>
#include <QVideoWidget>
#include <QVBoxLayout>
#include <QPushButton>
#include <QSlider>
#include <QLabel>
class VideoPlayer : public QWidget
{
Q_OBJECT
private:
QMediaPlayer *mediaPlayer;
QVideoWidget *videoWidget;
QSlider *positionSlider;
QLabel *positionLabel;
public:
VideoPlayer(QWidget *parent = nullptr) : QWidget(parent)
{
setupUI();
setupConnections();
}
private slots:
void openFile()
{
QString fileName = QFileDialog::getOpenFileName(this, "打开视频文件",
"", "视频文件 (*.mp4 *.avi *.mkv *.mov *.wmv)");
if (!fileName.isEmpty()) {
mediaPlayer->setMedia(QUrl::fromLocalFile(fileName));
mediaPlayer->play();
}
}
void updatePosition(qint64 position)
{
positionSlider->setValue(position);
updatePositionLabel(position);
}
void setPosition(int position)
{
mediaPlayer->setPosition(position);
}
void updateDuration(qint64 duration)
{
positionSlider->setRange(0, duration);
updatePositionLabel(0);
}
private:
void setupUI()
{
QVBoxLayout *layout = new QVBoxLayout(this);
// 视频显示区域
videoWidget = new QVideoWidget(this);
layout->addWidget(videoWidget);
// 进度条
positionSlider = new QSlider(Qt::Horizontal, this);
positionLabel = new QLabel("00:00 / 00:00", this);
layout->addWidget(positionSlider);
layout->addWidget(positionLabel);
// 控制按钮
QHBoxLayout *controlLayout = new QHBoxLayout();
QPushButton *openButton = new QPushButton("打开文件", this);
QPushButton *playButton = new QPushButton("播放", this);
QPushButton *pauseButton = new QPushButton("暂停", this);
QPushButton *stopButton = new QPushButton("停止", this);
controlLayout->addWidget(openButton);
controlLayout->addWidget(playButton);
controlLayout->addWidget(pauseButton);
controlLayout->addWidget(stopButton);
layout->addLayout(controlLayout);
// 媒体播放器
mediaPlayer = new QMediaPlayer(this);
mediaPlayer->setVideoOutput(videoWidget);
connect(openButton, &QPushButton::clicked, this, &VideoPlayer::openFile);
connect(playButton, &QPushButton::clicked, mediaPlayer, &QMediaPlayer::play);
connect(pauseButton, &QPushButton::clicked, mediaPlayer, &QMediaPlayer::pause);
connect(stopButton, &QPushButton::clicked, mediaPlayer, &QMediaPlayer::stop);
}
void setupConnections()
{
connect(mediaPlayer, &QMediaPlayer::positionChanged, this, &VideoPlayer::updatePosition);
connect(mediaPlayer, &QMediaPlayer::durationChanged, this, &VideoPlayer::updateDuration);
connect(positionSlider, &QSlider::sliderMoved, this, &VideoPlayer::setPosition);
}
void updatePositionLabel(qint64 position)
{
qint64 duration = mediaPlayer->duration();
QString positionTime = formatTime(position);
QString durationTime = formatTime(duration);
positionLabel->setText(QString("%1 / %2").arg(positionTime, durationTime));
}
QString formatTime(qint64 time)
{
int seconds = time / 1000;
int minutes = seconds / 60;
seconds %= 60;
return QString("%1:%2").arg(minutes, 2, 10, QLatin1Char('0'))
.arg(seconds, 2, 10, QLatin1Char('0'));
}
};
(2)原始 YUV 数据播放(yuvplayer.h)
#ifndef YUVPLAYER_H
#define YUVPLAYER_H
#include <QWidget>
#include <QTimer>
#include <QVBoxLayout>
#include <QPushButton>
#include <QOpenGLWidget>
#include <QOpenGLFunctions>
#include <QOpenGLShaderProgram>
#include <QFileDialog>
class YUVWidget : public QOpenGLWidget, protected QOpenGLFunctions
{
Q_OBJECT
private:
QOpenGLShaderProgram *program;
GLuint textureY, textureU, textureV;
int vertexLocation, textureLocation;
QByteArray yuvData;
int videoWidth, videoHeight;
bool needUpdateTextures;
QTimer *timer;
public:
YUVWidget(QWidget *parent = nullptr)
: QOpenGLWidget(parent)
, textureY(0), textureU(0), textureV(0)
, videoWidth(0), videoHeight(0)
, needUpdateTextures(true)
{
// 设置更新定时器
timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, QOverload<>::of(&YUVWidget::update));
}
~YUVWidget() {
if (textureY) glDeleteTextures(1, &textureY);
if (textureU) glDeleteTextures(1, &textureU);
if (textureV) glDeleteTextures(1, &textureV);
}
void playYUVData(const QByteArray &data, int width, int height)
{
if (width != videoWidth || height != videoHeight) {
videoWidth = width;
videoHeight = height;
needUpdateTextures = true;
}
this->yuvData = data;
timer->start(33); // 约30fps
}
void stop()
{
timer->stop();
}
protected:
void initializeGL() override
{
initializeOpenGLFunctions();
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
// 创建着色器程序
program = new QOpenGLShaderProgram(this);
// 顶点着色器
if (!program->addShaderFromSourceCode(QOpenGLShader::Vertex,
"attribute vec4 vertexIn;\n"
"attribute vec2 textureIn;\n"
"varying vec2 textureOut;\n"
"void main(void)\n"
"{\n"
" gl_Position = vertexIn;\n"
" textureOut = textureIn;\n"
"}")) {
qDebug() << "Vertex shader compilation failed:" << program->log();
}
// 片段着色器 - YUV420P to RGB
if (!program->addShaderFromSourceCode(QOpenGLShader::Fragment,
"varying vec2 textureOut;\n"
"uniform sampler2D tex_y;\n"
"uniform sampler2D tex_u;\n"
"uniform sampler2D tex_v;\n"
"void main(void)\n"
"{\n"
" vec3 yuv;\n"
" vec3 rgb;\n"
" yuv.x = texture2D(tex_y, textureOut).r - 0.0625;\n"
" yuv.y = texture2D(tex_u, textureOut).r - 0.5;\n"
" yuv.z = texture2D(tex_v, textureOut).r - 0.5;\n"
" rgb = mat3(1.1643, 1.1643, 1.1643,\n"
" 0.0, -0.39173, 2.017,\n"
" 1.5958, -0.8129, 0.0) * yuv;\n"
" gl_FragColor = vec4(rgb, 1.0);\n"
"}")) {
qDebug() << "Fragment shader compilation failed:" << program->log();
}
if (!program->link()) {
qDebug() << "Shader program link failed:" << program->log();
}
if (!program->bind()) {
qDebug() << "Shader program bind failed:" << program->log();
}
// 获取属性位置
vertexLocation = program->attributeLocation("vertexIn");
textureLocation = program->attributeLocation("textureIn");
// 设置顶点和纹理坐标
static const GLfloat vertexVertices[] = {
-1.0f, -1.0f,
1.0f, -1.0f,
-1.0f, 1.0f,
1.0f, 1.0f,
};
static const GLfloat textureVertices[] = {
0.0f, 1.0f,
1.0f, 1.0f,
0.0f, 0.0f,
1.0f, 0.0f,
};
glVertexAttribPointer(vertexLocation, 2, GL_FLOAT, 0, 0, vertexVertices);
glEnableVertexAttribArray(vertexLocation);
glVertexAttribPointer(textureLocation, 2, GL_FLOAT, 0, 0, textureVertices);
glEnableVertexAttribArray(textureLocation);
}
void resizeGL(int w, int h) override
{
glViewport(0, 0, w, h);
}
void paintGL() override
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
if (yuvData.isEmpty() || videoWidth == 0 || videoHeight == 0)
return;
// 更新纹理
updateTextures();
// 绘制
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
private:
void updateTextures()
{
if (needUpdateTextures) {
// 删除旧纹理
if (textureY) glDeleteTextures(1, &textureY);
if (textureU) glDeleteTextures(1, &textureU);
if (textureV) glDeleteTextures(1, &textureV);
// 创建新纹理
glGenTextures(1, &textureY);
glGenTextures(1, &textureU);
glGenTextures(1, &textureV);
needUpdateTextures = false;
}
int ySize = videoWidth * videoHeight;
int uSize = ySize / 4;
// 上传Y数据
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, textureY);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, videoWidth, videoHeight, 0,
GL_LUMINANCE, GL_UNSIGNED_BYTE, yuvData.constData());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// 上传U数据
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, textureU);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, videoWidth/2, videoHeight/2, 0,
GL_LUMINANCE, GL_UNSIGNED_BYTE, yuvData.constData() + ySize);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// 上传V数据
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, textureV);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, videoWidth/2, videoHeight/2, 0,
GL_LUMINANCE, GL_UNSIGNED_BYTE, yuvData.constData() + ySize + uSize);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// 设置纹理单元
program->setUniformValue("tex_y", 0);
program->setUniformValue("tex_u", 1);
program->setUniformValue("tex_v", 2);
}
};
class YUVPlayer : public QWidget
{
Q_OBJECT
private:
YUVWidget *yuvWidget;
QByteArray yuvData;
bool useTestData;
public:
YUVPlayer(QWidget *parent = nullptr) : QWidget(parent), useTestData(true)
{
setupUI();
generateTestYUVData();
}
private slots:
void openFile()
{
QString fileName = QFileDialog::getOpenFileName(this, "打开YUV文件");
if (!fileName.isEmpty()) {
QFile file(fileName);
if (file.open(QIODevice::ReadOnly)) {
yuvData = file.readAll();
file.close();
useTestData = false;
// 根据文件大小判断分辨率
int width = 640, height = 480;
if (yuvData.size() == 640 * 480 * 3 / 2) {
width = 640; height = 480;
} else if (yuvData.size() == 1280 * 720 * 3 / 2) {
width = 1280; height = 720;
}
startPlayback(width, height);
}
}
}
void startPlayback(int width = 640, int height = 480)
{
if (useTestData) {
generateTestYUVData();
}
yuvWidget->playYUVData(yuvData, width, height);
}
void stopPlayback()
{
yuvWidget->stop();
}
private:
void setupUI()
{
QVBoxLayout *layout = new QVBoxLayout(this);
yuvWidget = new YUVWidget(this);
layout->addWidget(yuvWidget);
QHBoxLayout *controlLayout = new QHBoxLayout();
QPushButton *openButton = new QPushButton("打开YUV文件", this);
QPushButton *playButton = new QPushButton("播放YUV", this);
QPushButton *stopButton = new QPushButton("停止", this);
QPushButton *testButton = new QPushButton("测试数据", this);
controlLayout->addWidget(openButton);
controlLayout->addWidget(playButton);
controlLayout->addWidget(stopButton);
controlLayout->addWidget(testButton);
layout->addLayout(controlLayout);
connect(openButton, &QPushButton::clicked, this, &YUVPlayer::openFile);
connect(playButton, &QPushButton::clicked, this, [this]() { startPlayback(); });
connect(stopButton, &QPushButton::clicked, this, &YUVPlayer::stopPlayback);
connect(testButton, &QPushButton::clicked, this, [this]() {
useTestData = true;
generateTestYUVData();
startPlayback();
});
}
void generateTestYUVData()
{
int width = 640;
int height = 480;
int ySize = width * height;
int uvSize = ySize / 4;
yuvData.resize(ySize + uvSize * 2);
static int frameCount = 0;
frameCount++;
char *yData = yuvData.data();
char *uData = yData + ySize;
char *vData = uData + uvSize;
// Y分量:灰色背景
memset(yData, 128, ySize);
// 在Y分量上绘制移动的白色方块
int blockSize = 50;
int blockX = (frameCount * 5) % (width - blockSize);
int blockY = (height - blockSize) / 2;
for (int y = 0; y < blockSize; y++) {
for (int x = 0; x < blockSize; x++) {
int pos = (blockY + y) * width + (blockX + x);
if (pos < ySize) {
yData[pos] = 255; // 白色
}
}
}
// UV分量:设置为中性(灰色)
memset(uData, 128, uvSize);
memset(vData, 128, uvSize);
}
};
#endif // YUVPLAYER_H
(3)原始 RGB 数据播放rgbplayer.h
#ifndef RGBPLAYER_H
#define RGBPLAYER_H
#include <QtWidgets>
#include <QTimer>
#include <QFileDialog>
class RGBPlayer : public QWidget
{
Q_OBJECT
private:
QLabel *label;
QTimer *timer;
QByteArray rgbData;
QPushButton *openButton;
int frameWidth, frameHeight;
bool useTestData;
public:
RGBPlayer(QWidget *parent = nullptr) : QWidget(parent), frameWidth(640), frameHeight(480), useTestData(true)
{
setupUI();
setupTimer();
generateTestRGBData();
}
private slots:
void openFile()
{
QString fileName = QFileDialog::getOpenFileName(this, "打开RGB文件");
if (!fileName.isEmpty()) {
QFile file(fileName);
if (file.open(QIODevice::ReadOnly)) {
rgbData = file.readAll();
file.close();
useTestData = false;
// 简单判断文件大小来确定分辨率
if (rgbData.size() == 640 * 480 * 3) {
frameWidth = 640;
frameHeight = 480;
} else if (rgbData.size() == 1280 * 720 * 3) {
frameWidth = 1280;
frameHeight = 720;
} else {
// 默认使用640x480,截取数据
frameWidth = 640;
frameHeight = 480;
if (rgbData.size() < frameWidth * frameHeight * 3) {
rgbData.resize(frameWidth * frameHeight * 3);
}
}
label->setMinimumSize(frameWidth, frameHeight);
startPlayback();
}
}
}
void startPlayback()
{
timer->start(33); // 30fps
}
void stopPlayback()
{
timer->stop();
}
void updateFrame()
{
if (useTestData) {
updateRGBData();
}
// 创建QImage并显示
if (!rgbData.isEmpty() && rgbData.size() >= frameWidth * frameHeight * 3) {
QImage image((const uchar*)rgbData.constData(), frameWidth, frameHeight, QImage::Format_RGB888);
label->setPixmap(QPixmap::fromImage(image.scaled(label->size(), Qt::KeepAspectRatio)));
}
}
private:
void setupUI()
{
QVBoxLayout *layout = new QVBoxLayout(this);
label = new QLabel(this);
label->setAlignment(Qt::AlignCenter);
label->setMinimumSize(frameWidth, frameHeight);
label->setStyleSheet("background-color: black;");
layout->addWidget(label);
QHBoxLayout *controlLayout = new QHBoxLayout();
openButton = new QPushButton("打开RGB文件", this);
QPushButton *playButton = new QPushButton("播放RGB", this);
QPushButton *stopButton = new QPushButton("停止", this);
QPushButton *testButton = new QPushButton("测试数据", this);
controlLayout->addWidget(openButton);
controlLayout->addWidget(playButton);
controlLayout->addWidget(stopButton);
controlLayout->addWidget(testButton);
layout->addLayout(controlLayout);
connect(openButton, &QPushButton::clicked, this, &RGBPlayer::openFile);
connect(playButton, &QPushButton::clicked, this, &RGBPlayer::startPlayback);
connect(stopButton, &QPushButton::clicked, this, &RGBPlayer::stopPlayback);
connect(testButton, &QPushButton::clicked, this, [this]() {
useTestData = true;
generateTestRGBData();
startPlayback();
});
}
void setupTimer()
{
timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, &RGBPlayer::updateFrame);
}
void generateTestRGBData()
{
frameWidth = 640;
frameHeight = 480;
rgbData.resize(frameWidth * frameHeight * 3);
label->setMinimumSize(frameWidth, frameHeight);
}
void updateRGBData()
{
static int frameCount = 0;
frameCount++;
uchar *data = (uchar*)rgbData.data();
// 生成简单的动画RGB数据
for (int y = 0; y < frameHeight; y++) {
for (int x = 0; x < frameWidth; x++) {
int index = (y * frameWidth + x) * 3;
// 创建移动的颜色渐变
int r = (x + frameCount) % 256;
int g = (y + frameCount) % 256;
int b = (x + y + frameCount) % 256;
data[index] = r; // Red
data[index + 1] = g; // Green
data[index + 2] = b; // Blue
}
}
}
};
#endif // RGBPLAYER_H
main.cpp
#include <QApplication>
#include <QMainWindow>
#include <QTabWidget>
#include "rgbplayer.h"
#include "videoplayer.h"
#include "yuvplayer.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
// 创建主窗口和标签页
QMainWindow mainWindow;
QTabWidget *tabWidget = new QTabWidget(&mainWindow);
// 创建三种播放器
RGBPlayer *rgbPlayer = new RGBPlayer(tabWidget);
VideoPlayer *videoPlayer = new VideoPlayer(tabWidget);
YUVPlayer *yuvPlayer = new YUVPlayer(tabWidget);
// 添加到标签页
tabWidget->addTab(rgbPlayer, "RGB播放器");
tabWidget->addTab(videoPlayer, "视频播放器");
tabWidget->addTab(yuvPlayer, "YUV播放器");
mainWindow.setCentralWidget(tabWidget);
mainWindow.setWindowTitle("多媒体播放器");
mainWindow.resize(800, 600);
mainWindow.show();
return app.exec();
}
(4)项目配置.pro
QT += core gui widgets multimedia multimediawidgets opengl
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++11
# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
# You can also make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
main.cpp \
rgbplayer.cpp \
videoplayer.cpp \
yuvplayer.cpp
HEADERS += \
rgbplayer.h \
videoplayer.h \
yuvplayer.h
FORMS += \
widget.ui
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
(5)总结
-
常规视频播放:使用
QMediaPlayer+QVideoWidget,支持多种格式 -
YUV数据播放:使用
QOpenGLWidget和着色器进行硬件加速渲染 -
RGB数据播放:使用
QImage+QLabel,简单但效率较低
对于原始视频数据播放,推荐使用OpenGL方式,因为:
-
硬件加速,性能更好
-
支持YUV到RGB的转换
-
可以处理高分辨率视频
实际应用中,你需要根据数据源(文件、网络流等)来读取YUV/RGB数据,并实现相应的解码和缓冲机制。

浙公网安备 33010602011771号