Qt - 音视频播放

一、音频播放

  1. QSound:用于播放未压缩的音频文件(如WAV文件),简单易用,但不支持压缩格式。

  2. QMediaPlayer:功能强大的媒体播放类,支持音频和视频,可以播放多种格式的压缩音频文件。

  3. QAudioOutput:用于播放原始PCM音频数据,适合需要实时处理音频数据的场景。

 

在 Qt 中,有多种音频播放类可供选择,每个类都有不同的用途和特点。以下是 Qt 中常用的音频播放类及其详细说明:

1. QSound - 简单音效播放

QSound类提供了一种播放未压缩音频文件(主要是WAV文件)的简单方法。它通常用于播放短小的音效,因为它在播放时不会阻塞主线程。

特点和用途

  • 最简单的音频播放类

  • 适合播放短音效、提示音

  • 支持 WAV 格式

  • 异步播放,不阻塞主线程

使用示例

cpp
#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等)

  • 支持网络流媒体

  • 提供播放控制、进度、音量等功能

  • 支持播放列表

使用示例

cpp
#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;
    }
});

播放列表功能

cpp
#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音频数据

  • 适合实时音频流、语音通话

  • 低延迟播放

  • 需要手动管理音频数据

使用示例

cpp
#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 更低的延迟

  • 支持循环播放和音量控制

  • 适合需要快速响应的短音效

使用示例

cpp
#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模块。以下是一些常用的视频播放类及其用途:

  1. QMediaPlayer:用于播放音频和视频文件。它可以与QVideoWidget或QGraphicsVideoItem一起使用来显示视频。

  2. QVideoWidget:一个简单的视频显示组件,用于在Qt应用程序中显示视频。

  3. QGraphicsVideoItem:可以在QGraphicsView框架中显示视频的图形项。

在Qt5中,视频播放通常使用QMediaPlayer和QVideoWidget。

 

在 Qt 中,视频播放主要通过 Multimedia 模块提供。以下是 Qt 中常用的视频播放类及其详细说明:

1. QMediaPlayer - 核心媒体播放器

特点和用途

  • 功能最全面的媒体播放器

  • 支持音频和视频播放

  • 支持多种格式(MP4、AVI、MKV、MOV等)

  • 提供播放控制、进度、音量等功能

  • 支持网络流媒体

基本使用示例

cpp
#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

  • 可以嵌入到任何布局中

  • 支持全屏显示

  • 提供基本的视频渲染功能

高级功能示例

cpp
#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布局和特效

使用示例

cpp
#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)总结

  1. 常规视频播放:使用 QMediaPlayer + QVideoWidget,支持多种格式

  2. YUV数据播放:使用 QOpenGLWidget 和着色器进行硬件加速渲染

  3. RGB数据播放:使用 QImage + QLabel,简单但效率较低

对于原始视频数据播放,推荐使用OpenGL方式,因为:

  • 硬件加速,性能更好

  • 支持YUV到RGB的转换

  • 可以处理高分辨率视频

实际应用中,你需要根据数据源(文件、网络流等)来读取YUV/RGB数据,并实现相应的解码和缓冲机制。

 

 

 

 
posted @ 2025-09-23 15:15  [BORUTO]  阅读(40)  评论(0)    收藏  举报