Qt - 音视频采集

Qt 提供了多媒体模块(Qt Multimedia)来处理音视频的采集、播放等。但是需要注意的是,Qt 的音视频采集功能可能在不同的平台上有不同的支持程度,并且可能不如专业的音视频库(如 FFmpeg)功能全面。不过,对于基本的音视频采集,Qt 提供了较为方便的 API。

下面我们将介绍如何使用 Qt 实现音视频采集。

一. 音频采集

1. QIODevice - 输入输出设备基类

基本概念

QIODevice 是 Qt 中所有输入输出设备的抽象基类,它定义了读写数据的通用接口。

主要用途

  • 数据读写:提供统一的 read()、write() 方法

  • 设备抽象:将不同的数据源统一为"设备"概念

  • 信号机制:通过信号通知数据可用性

常用子类

cpp
QFile          // 文件
QBuffer        // 内存缓冲区
QTcpSocket     // TCP网络套接字
QUdpSocket     // UDP网络套接字
QProcess       // 进程通信

在音频处理中的使用

cpp
// 创建内存缓冲区作为音频数据容器
QBuffer *audioBuffer = new QBuffer;
audioBuffer->open(QIODevice::ReadWrite);

// 写入音频数据
QByteArray pcmData = ...; // 从麦克风获取的数据
audioBuffer->write(pcmData);

// 读取音频数据
audioBuffer->seek(0);
QByteArray dataToPlay = audioBuffer->readAll();

// 连接数据可读信号
connect(audioBuffer, &QIODevice::readyRead, [=]() {
    QByteArray newData = audioBuffer->readAll();
    // 处理新到达的音频数据
});

关键方法

cpp
// 打开设备
bool open(QIODevice::OpenMode mode)

// 读取数据
qint64 read(char *data, qint64 maxSize)
QByteArray read(qint64 maxSize)
QByteArray readAll()

// 写入数据
qint64 write(const char *data, qint64 maxSize)
qint64 write(const QByteArray &data)

// 定位
bool seek(qint64 pos)
qint64 pos() const

// 状态查询
qint64 bytesAvailable() const  // 可读字节数
qint64 bytesToWrite() const    // 等待写入的字节数
bool atEnd() const             // 是否到达末尾

2. QAudioInput - 音频输入类

基本概念

QAudioInput 用于从音频输入设备(如麦克风)捕获原始音频数据。

主要用途

  • 音频采集:从麦克风捕获PCM音频数据

  • 实时录音:实现录音功能

  • 语音输入:语音识别、语音通话的输入部分

工作流程

text
麦克风 → QAudioInput → QIODevice → 应用程序

基本使用

cpp
#include <QAudioInput>
#include <QAudioDeviceInfo>

// 1. 设置音频格式
QAudioFormat format;
format.setSampleRate(44100);        // 采样率
format.setChannelCount(2);          // 声道数
format.setSampleSize(16);           // 采样位数
format.setCodec("audio/pcm");       // 编码格式
format.setByteOrder(QAudioFormat::LittleEndian);
format.setSampleType(QAudioFormat::SignedInt);

// 2. 检查设备支持
QAudioDeviceInfo inputDevice = QAudioDeviceInfo::defaultInputDevice();
if (!inputDevice.isFormatSupported(format)) {
    format = inputDevice.nearestFormat(format);
}

// 3. 创建音频输入对象
QAudioInput *audioInput = new QAudioInput(inputDevice, format, this);

// 4. 创建数据存储设备
QBuffer *audioBuffer = new QBuffer(this);
audioBuffer->open(QIODevice::WriteOnly);

// 5. 开始录音
audioInput->start(audioBuffer);

// 6. 停止录音
audioInput->stop();

实时音频采集示例

cpp
class AudioRecorder : public QObject
{
    Q_OBJECT

public:
    AudioRecorder(QObject *parent = nullptr) : QObject(parent)
    {
        setupAudioFormat();
        setupAudioInput();
    }

    void startRecording()
    {
        m_audioBuffer = new QBuffer(this);
        m_audioBuffer->open(QIODevice::WriteOnly);
        
        // 连接数据可读信号
        connect(m_audioBuffer, &QIODevice::readyRead, 
                this, &AudioRecorder::onAudioDataReady);
        
        m_audioInput->start(m_audioBuffer);
    }

    void stopRecording()
    {
        if (m_audioInput) {
            m_audioInput->stop();
        }
        if (m_audioBuffer) {
            m_audioBuffer->close();
            delete m_audioBuffer;
            m_audioBuffer = nullptr;
        }
    }

private slots:
    void onAudioDataReady()
    {
        QByteArray audioData = m_audioBuffer->readAll();
        if (!audioData.isEmpty()) {
            // 处理音频数据:保存、发送、分析等
            processAudioData(audioData);
        }
    }

private:
    void setupAudioFormat()
    {
        m_format.setSampleRate(16000);
        m_format.setChannelCount(1);
        m_format.setSampleSize(16);
        m_format.setCodec("audio/pcm");
        m_format.setByteOrder(QAudioFormat::LittleEndian);
        m_format.setSampleType(QAudioFormat::SignedInt);
    }

    void setupAudioInput()
    {
        QAudioDeviceInfo inputDevice = QAudioDeviceInfo::defaultInputDevice();
        if (inputDevice.isFormatSupported(m_format)) {
            m_audioInput = new QAudioInput(inputDevice, m_format, this);
        } else {
            qWarning() << "Default format not supported, trying nearest format";
            QAudioFormat nearestFormat = inputDevice.nearestFormat(m_format);
            m_audioInput = new QAudioInput(inputDevice, nearestFormat, this);
        }
        
        // 设置缓冲区大小(影响延迟)
        m_audioInput->setBufferSize(4096); // 4KB缓冲区
    }

    void processAudioData(const QByteArray &data)
    {
        // 在这里处理音频数据
        // 例如:发送到网络、保存到文件、实时分析等
        qDebug() << "Received audio data:" << data.size() << "bytes";
        
        // 示例:计算音频电平
        qint16 maxAmplitude = 0;
        const qint16 *samples = reinterpret_cast<const qint16*>(data.constData());
        int sampleCount = data.size() / 2;
        
        for (int i = 0; i < sampleCount; ++i) {
            qint16 amplitude = qAbs(samples[i]);
            if (amplitude > maxAmplitude) {
                maxAmplitude = amplitude;
            }
        }
        
        float level = (float)maxAmplitude / 32768.0f;
        emit audioLevelChanged(level);
    }

signals:
    void audioLevelChanged(float level);

private:
    QAudioFormat m_format;
    QAudioInput *m_audioInput;
    QBuffer *m_audioBuffer;
};

3. QAudioOutput - 音频输出类

基本概念

QAudioOutput 用于将原始PCM音频数据播放到音频输出设备(如扬声器、耳机)。

主要用途

  • 音频播放:播放PCM格式的音频数据

  • 实时播放:语音通话、实时音频流

  • 音频提示:系统提示音、游戏音效

工作流程

text
应用程序 → QIODevice → QAudioOutput → 扬声器

基本使用

cpp
#include <QAudioOutput>
#include <QAudioDeviceInfo>

// 1. 设置音频格式(必须与输入格式匹配)
QAudioFormat format;
format.setSampleRate(44100);
format.setChannelCount(2);
format.setSampleSize(16);
format.setCodec("audio/pcm");
format.setByteOrder(QAudioFormat::LittleEndian);
format.setSampleType(QAudioFormat::SignedInt);

// 2. 检查设备支持
QAudioDeviceInfo outputDevice = QAudioDeviceInfo::defaultOutputDevice();
if (!outputDevice.isFormatSupported(format)) {
    format = outputDevice.nearestFormat(format);
}

// 3. 创建音频输出对象
QAudioOutput *audioOutput = new QAudioOutput(outputDevice, format, this);

// 4. 准备音频数据
QBuffer *audioBuffer = new QBuffer(this);
QByteArray pcmData = ...; // 从文件或网络获取的PCM数据
audioBuffer->setData(pcmData);
audioBuffer->open(QIODevice::ReadOnly);

// 5. 开始播放
audioOutput->start(audioBuffer);

// 6. 连接播放结束信号
connect(audioOutput, &QAudioOutput::stateChanged, [=](QAudio::State state) {
    if (state == QAudio::IdleState) {
        // 播放完成
        audioOutput->stop();
        audioBuffer->close();
    }
});

实时音频播放示例

cpp
class AudioPlayer : public QObject
{
    Q_OBJECT

public:
    AudioPlayer(QObject *parent = nullptr) : QObject(parent)
    {
        setupAudioFormat();
        setupAudioOutput();
    }

    void playAudioData(const QByteArray &audioData)
    {
        if (!m_audioOutput || !m_outputDevice) {
            return;
        }

        // 写入音频数据到输出设备
        qint64 bytesWritten = m_outputDevice->write(audioData);
        if (bytesWritten != audioData.size()) {
            qWarning() << "Audio write incomplete";
        }
        
        // 监控缓冲区状态
        qint64 bytesFree = m_audioOutput->bytesFree();
        qint64 bufferSize = m_audioOutput->bufferSize();
        float bufferUsage = (float)(bufferSize - bytesFree) / bufferSize;
        
        qDebug() << "Buffer usage:" << bufferUsage * 100 << "%";
    }

    void stop()
    {
        if (m_audioOutput) {
            m_audioOutput->stop();
        }
    }

private:
    void setupAudioFormat()
    {
        m_format.setSampleRate(16000);
        m_format.setChannelCount(1);
        m_format.setSampleSize(16);
        m_format.setCodec("audio/pcm");
        m_format.setByteOrder(QAudioFormat::LittleEndian);
        m_format.setSampleType(QAudioFormat::SignedInt);
    }

    void setupAudioOutput()
    {
        QAudioDeviceInfo outputDevice = QAudioDeviceInfo::defaultOutputDevice();
        if (outputDevice.isFormatSupported(m_format)) {
            m_audioOutput = new QAudioOutput(outputDevice, m_format, this);
        } else {
            qWarning() << "Default format not supported, trying nearest format";
            QAudioFormat nearestFormat = outputDevice.nearestFormat(m_format);
            m_audioOutput = new QAudioOutput(outputDevice, nearestFormat, this);
        }
        
        // 设置合适的缓冲区大小
        int bytesPerSecond = m_format.sampleRate() * m_format.channelCount() * 
                           (m_format.sampleSize() / 8);
        m_audioOutput->setBufferSize(bytesPerSecond / 10); // 100ms缓冲区
        
        // 启动输出设备
        m_outputDevice = m_audioOutput->start();
        
        // 连接状态变化信号
        connect(m_audioOutput, &QAudioOutput::stateChanged,
                this, &AudioPlayer::onAudioStateChanged);
    }

private slots:
    void onAudioStateChanged(QAudio::State state)
    {
        switch (state) {
        case QAudio::ActiveState:
            qDebug() << "Audio output active";
            break;
        case QAudio::SuspendedState:
            qDebug() << "Audio output suspended";
            break;
        case QAudio::StoppedState:
            qDebug() << "Audio output stopped";
            break;
        case QAudio::IdleState:
            qDebug() << "Audio output idle (buffer empty)";
            break;
        }
    }

private:
    QAudioFormat m_format;
    QAudioOutput *m_audioOutput;
    QIODevice *m_outputDevice;
};

 

4. 关键要点总结

QIODevice

  • 角色:数据通道的抽象

  • 在音频中:作为音频数据的容器和传输媒介

  • 常用子类:QBuffer(内存)、QFile(文件)

QAudioInput

  • 角色:音频采集器

  • 数据流:麦克风 → QAudioInput → QIODevice

  • 关键方法:start(QIODevice*)、stop()

QAudioOutput

  • 角色:音频播放器

  • 数据流:QIODevice → QAudioOutput → 扬声器

  • 关键方法:start(QIODevice*)、stop()

 

二. 视频采集

在Qt中,视频的采集和播放与音频的采集和播放在概念上是相似的,但使用的类不同。视频采集通常使用QCamera类,而视频播放则使用QMediaPlayer或QVideoWidget等类。

视频采集(摄像头):

  • 主要类:QCamera, QCameraViewfinder, QVideoProbe, QMediaRecorder

  • 流程:通过QCamera获取摄像头视频流,可以通过QCameraViewfinder实时显示,通过QVideoProbe获取每一帧数据,或者通过QMediaRecorder录制视频。

视频播放:

  • 主要类:QMediaPlayer, QVideoWidget, QGraphicsVideoItem

  • 流程:使用QMediaPlayer加载视频,然后通过QVideoWidget或QGraphicsVideoItem显示视频。

然而,与音频的QAudioInput和QAudioOutput直接处理原始PCM数据不同,视频的采集和播放通常处理的是已经编码的视频帧(如YUV、RGB)或视频流(如H.264)。在Qt中,视频采集和播放更倾向于使用高级的类,而不是直接处理原始视频数据。

但是,如果你需要处理原始视频数据(例如,从摄像头获取每一帧进行图像处理,或者将处理后的帧显示出来),那么你可以使用QVideoProbe来捕获视频帧,或者使用QAbstractVideoSurface来接收和处理视频帧。

1. 视频采集类

(1)QCamera - 摄像头控制

#include <QCamera>
#include <QCameraInfo>

基本概念

QCamera是Qt中用于访问系统摄像头设备的类。它提供了对摄像头硬件的抽象,允许开发者控制摄像头的各种功能,如启动、停止、调整分辨率、对焦、曝光等。

主要用途

  • 摄像头控制:启动、停止、暂停摄像头。

  • 参数设置:设置分辨率、帧率、对焦模式、曝光补偿等。

  • 取景器显示:将摄像头捕获的视频流显示在QCameraViewfinder或自定义的QAbstractVideoSurface上。

  • 图像捕获:与QCameraImageCapture配合,用于静态图片捕获。

  • 视频录制:与QMediaRecorder配合,用于录制视频。

常用方法

  • start(): 启动摄像头。

  • stop(): 停止摄像头。

  • setViewfinder(QVideoWidget *): 设置取景器显示。

  • setCaptureMode(QCamera::CaptureMode): 设置捕获模式(静态图像或视频)。

 

(2)QVideoProbe - 视频帧探测

#include <QVideoProbe>

基本概念

QVideoProbe类允许你在不干扰正常视频流的情况下,监视摄像头或媒体播放器输出的视频帧。

主要用途

  • 视频帧监视:在不影响视频显示或录制的情况下,获取视频帧进行额外处理。

  • 非侵入式分析:例如,实时分析视频内容(如运动检测、颜色分析)而不改变原有流程。

常用方法

  • setSource(QMediaObject *): 设置监视源(如QCamera或QMediaPlayer)。

  • videoFrameProbed(QVideoFrame): 信号,当有视频帧时发出。

使用场景

  • 在视频录制或显示的同时,进行实时分析。

  • 需要同时进行多个处理(如显示、录制和分析)而互不干扰。

 

(3)QAbstractVideoSurface - 自定义视频渲染表面

#include <QAbstractVideoSurface>

基本概念

QAbstractVideoSurface是一个抽象基类,用于接收和处理视频帧。你可以继承这个类并实现其纯虚函数,以自定义处理视频帧的方式。

主要用途

  • 自定义视频渲染:例如,将视频帧渲染到自定义的OpenGL窗口或进行图像处理。

  • 视频帧捕获:获取每一帧视频数据,用于保存、分析或传输。

需要实现的方法

  • present(const QVideoFrame &frame): 处理视频帧,这是必须实现的。

  • supportedPixelFormats(QAbstractVideoBuffer::HandleType type): 返回支持的像素格式。

使用场景

当需要直接访问视频帧数据时,例如:

  • 实时图像处理(如滤镜、人脸识别)。

  • 将视频帧数据发送到编码器。

  • 在非标准界面上显示视频(如OpenGL纹理)。

 

三者的关系和区别:

  • QCamera是摄像头控制的核心类,负责管理摄像头的生命周期和参数。

  • QAbstractVideoSurface是用于接收视频帧的抽象接口,需要子类化以实现自定义处理。

  • QVideoProbe是一种非侵入式的监视工具,可以连接到QCamera(或其他媒体对象)并获取视频帧的副本,而不影响原有的视频流。

使用选择:

  • 如果你只需要显示摄像头视频,使用QCamera和QCameraViewfinder即可。

  • 如果你需要自定义渲染视频(例如在OpenGL中),则继承QAbstractVideoSurface。

  • 如果你需要在不断开原有视频流的情况下获取视频帧(例如同时显示和分析),使用QVideoProbe

 

1.1视频采集详细实现

1. 基本的摄像头采集(QVideoProbe)

cpp
// CameraCapture.h
#ifndef CAMERACAPTURE_H
#define CAMERACAPTURE_H

#include <QObject>
#include <QCamera>
#include <QCameraInfo>
#include <QVideoProbe>
#include <QVideoFrame>
#include <QImage>

class CameraCapture : public QObject
{
    Q_OBJECT

public:
    explicit CameraCapture(QObject *parent = nullptr);
    ~CameraCapture();

    bool startCapture();
    void stopCapture();
    QList<QCameraInfo> availableCameras() const;
    void setCamera(const QCameraInfo &cameraInfo);

signals:
    void frameCaptured(const QImage &image);
    void frameDataCaptured(const QVideoFrame &frame);

private slots:
    void onFrameProbed(const QVideoFrame &frame);
    void onCameraError(QCamera::Error error);

private:
    QCamera *m_camera;
    QVideoProbe *m_videoProbe;
    QCameraInfo m_currentCamera;
};

#endif // CAMERACAPTURE_H
cpp
// CameraCapture.cpp
#include "CameraCapture.h"
#include <QDebug>

CameraCapture::CameraCapture(QObject *parent)
    : QObject(parent)
    , m_camera(nullptr)
    , m_videoProbe(new QVideoProbe(this))
{
    connect(m_videoProbe, &QVideoProbe::videoFrameProbed,
            this, &CameraCapture::onFrameProbed);
}

CameraCapture::~CameraCapture()
{
    stopCapture();
}

QList<QCameraInfo> CameraCapture::availableCameras() const
{
    return QCameraInfo::availableCameras();
}

void CameraCapture::setCamera(const QCameraInfo &cameraInfo)
{
    m_currentCamera = cameraInfo;
}

bool CameraCapture::startCapture()
{
    if (m_camera) {
        stopCapture();
    }

    if (m_currentCamera.isNull()) {
        // 使用默认摄像头
        m_camera = new QCamera(this);
    } else {
        m_camera = new QCamera(m_currentCamera, this);
    }

    connect(m_camera, QOverload<QCamera::Error>::of(&QCamera::error),
            this, &CameraCapture::onCameraError);

    // 设置视频探测
    if (!m_videoProbe->setSource(m_camera)) {
        qWarning() << "Failed to set video probe source";
        delete m_camera;
        m_camera = nullptr;
        return false;
    }

    // 启动摄像头
    m_camera->start();

    return true;
}

void CameraCapture::stopCapture()
{
    if (m_camera) {
        m_camera->stop();
        delete m_camera;
        m_camera = nullptr;
    }
}

void CameraCapture::onFrameProbed(const QVideoFrame &frame)
{
    // 处理视频帧
    QVideoFrame cloneFrame(frame);
    
    if (cloneFrame.map(QAbstractVideoBuffer::ReadOnly)) {
        // 发射原始帧数据信号
        emit frameDataCaptured(cloneFrame);
        
        // 转换为QImage并发射
        QImage image = qt_imageFromVideoFrame(cloneFrame);
        if (!image.isNull()) {
            emit frameCaptured(image);
        }
        
        cloneFrame.unmap();
    }
}

void CameraCapture::onCameraError(QCamera::Error error)
{
    qWarning() << "Camera error:" << error << m_camera->errorString();
}

2. 自定义视频表面(更底层的采集)

cpp
// CustomVideoSurface.h
#ifndef CUSTOMVIDEOSURFACE_H
#define CUSTOMVIDEOSURFACE_H

#include <QAbstractVideoSurface>
#include <QVideoFrame>
#include <QImage>

class CustomVideoSurface : public QAbstractVideoSurface
{
    Q_OBJECT

public:
    CustomVideoSurface(QObject *parent = nullptr);
    
    // 必须实现的虚函数
    QList<QVideoFrame::PixelFormat> supportedPixelFormats(
        QAbstractVideoBuffer::HandleType handleType = QAbstractVideoBuffer::NoHandle) const override;
    
    bool present(const QVideoFrame &frame) override;

signals:
    void frameReceived(const QVideoFrame &frame);
    void imageReceived(const QImage &image);

private:
    QImage videoFrameToImage(const QVideoFrame &frame);
};

#endif // CUSTOMVIDEOSURFACE_H
cpp
// CustomVideoSurface.cpp
#include "CustomVideoSurface.h"
#include <QDebug>

CustomVideoSurface::CustomVideoSurface(QObject *parent)
    : QAbstractVideoSurface(parent)
{
}

QList<QVideoFrame::PixelFormat> CustomVideoSurface::supportedPixelFormats(
    QAbstractVideoBuffer::HandleType handleType) const
{
    Q_UNUSED(handleType);
    
    // 支持的像素格式列表
    return QList<QVideoFrame::PixelFormat>()
        << QVideoFrame::Format_ARGB32
        << QVideoFrame::Format_ARGB32_Premultiplied
        << QVideoFrame::Format_RGB32
        << QVideoFrame::Format_RGB24
        << QVideoFrame::Format_RGB565
        << QVideoFrame::Format_RGB555
        << QVideoFrame::Format_YUYV
        << QVideoFrame::Format_NV12
        << QVideoFrame::Format_NV21;
}

bool CustomVideoSurface::present(const QVideoFrame &frame)
{
    if (!frame.isValid()) {
        return false;
    }

    // 发射原始帧
    emit frameReceived(frame);
    
    // 转换为QImage并发射
    QImage image = videoFrameToImage(frame);
    if (!image.isNull()) {
        emit imageReceived(image);
    }
    
    return true;
}

QImage CustomVideoSurface::videoFrameToImage(const QVideoFrame &frame)
{
    QVideoFrame cloneFrame(frame);
    
    if (!cloneFrame.map(QAbstractVideoBuffer::ReadOnly)) {
        return QImage();
    }
    
    QImage image;
    QImage::Format imageFormat = QImage::Format_Invalid;
    
    switch (cloneFrame.pixelFormat()) {
    case QVideoFrame::Format_ARGB32:
        imageFormat = QImage::Format_ARGB32;
        break;
    case QVideoFrame::Format_ARGB32_Premultiplied:
        imageFormat = QImage::Format_ARGB32_Premultiplied;
        break;
    case QVideoFrame::Format_RGB32:
        imageFormat = QImage::Format_RGB32;
        break;
    case QVideoFrame::Format_RGB24:
        imageFormat = QImage::Format_RGB888;
        break;
    case QVideoFrame::Format_RGB565:
        imageFormat = QImage::Format_RGB16;
        break;
    case QVideoFrame::Format_RGB555:
        imageFormat = QImage::Format_RGB555;
        break;
    default:
        // 不支持的格式
        cloneFrame.unmap();
        return QImage();
    }
    
    image = QImage(cloneFrame.bits(),
                   cloneFrame.width(),
                   cloneFrame.height(),
                   cloneFrame.bytesPerLine(),
                   imageFormat);
    
    cloneFrame.unmap();
    
    // 如果需要,转换颜色空间
    if (frame.pixelFormat() == QVideoFrame::Format_YUYV) {
        // 这里需要实现YUV到RGB的转换
        // 实际项目中可能需要使用libyuv或OpenCV
    }
    
    return image.copy(); // 返回深拷贝
}

 

2. 视频播放类

QMediaPlayer - 媒体播放器(支持音视频)

cpp
#include <QMediaPlayer>

QVideoWidget - 视频显示组件

cpp
#include <QVideoWidget>

QGraphicsVideoItem - 在 QGraphicsView 中显示视频

cpp
#include <QGraphicsVideoItem>

 

2.1视频播放详细实现

1. 使用 QMediaPlayer + QVideoWidget

cpp
// VideoPlayer.h
#ifndef VIDEOPLAYER_H
#define VIDEOPLAYER_H

#include <QWidget>
#include <QMediaPlayer>
#include <QVideoWidget>
#include <QSlider>
#include <QPushButton>
#include <QLabel>

class VideoPlayer : public QWidget
{
    Q_OBJECT

public:
    explicit VideoPlayer(QWidget *parent = nullptr);

public slots:
    void openFile();
    void play();
    void pause();
    void stop();
    void setPosition(int position);

private slots:
    void onStateChanged(QMediaPlayer::State state);
    void onPositionChanged(qint64 position);
    void onDurationChanged(qint64 duration);
    void onErrorOccurred(QMediaPlayer::Error error, const QString &errorString);

private:
    void setupUI();
    
    QMediaPlayer *m_mediaPlayer;
    QVideoWidget *m_videoWidget;
    QSlider *m_positionSlider;
    QPushButton *m_playButton;
    QPushButton *m_pauseButton;
    QPushButton *m_stopButton;
    QLabel *m_positionLabel;
    QLabel *m_durationLabel;
};

#endif // VIDEOPLAYER_H
cpp
// VideoPlayer.cpp
#include "VideoPlayer.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QFileDialog>
#include <QTime>
#include <QDebug>

VideoPlayer::VideoPlayer(QWidget *parent)
    : QWidget(parent)
{
    setupUI();
    
    // 创建媒体播放器
    m_mediaPlayer = new QMediaPlayer(this);
    m_mediaPlayer->setVideoOutput(m_videoWidget);
    
    // 连接信号
    connect(m_mediaPlayer, &QMediaPlayer::stateChanged,
            this, &VideoPlayer::onStateChanged);
    connect(m_mediaPlayer, &QMediaPlayer::positionChanged,
            this, &VideoPlayer::onPositionChanged);
    connect(m_mediaPlayer, &QMediaPlayer::durationChanged,
            this, &VideoPlayer::onDurationChanged);
    connect(m_mediaPlayer, QOverload<QMediaPlayer::Error>::of(&QMediaPlayer::error),
            this, &VideoPlayer::onErrorOccurred);
    
    connect(m_positionSlider, &QSlider::sliderMoved,
            this, &VideoPlayer::setPosition);
}

void VideoPlayer::setupUI()
{
    QVBoxLayout *mainLayout = new QVBoxLayout(this);
    
    // 视频显示区域
    m_videoWidget = new QVideoWidget(this);
    m_videoWidget->setMinimumSize(640, 480);
    mainLayout->addWidget(m_videoWidget);
    
    // 控制区域
    QHBoxLayout *controlLayout = new QHBoxLayout();
    
    m_playButton = new QPushButton("播放", this);
    m_pauseButton = new QPushButton("暂停", this);
    m_stopButton = new QPushButton("停止", this);
    
    connect(m_playButton, &QPushButton::clicked, this, &VideoPlayer::play);
    connect(m_pauseButton, &QPushButton::clicked, this, &VideoPlayer::pause);
    connect(m_stopButton, &QPushButton::clicked, this, &VideoPlayer::stop);
    
    m_positionSlider = new QSlider(Qt::Horizontal, this);
    m_positionSlider->setRange(0, 0);
    
    m_positionLabel = new QLabel("00:00", this);
    m_durationLabel = new QLabel("00:00", this);
    
    controlLayout->addWidget(m_playButton);
    controlLayout->addWidget(m_pauseButton);
    controlLayout->addWidget(m_stopButton);
    controlLayout->addWidget(m_positionLabel);
    controlLayout->addWidget(m_positionSlider);
    controlLayout->addWidget(m_durationLabel);
    
    mainLayout->addLayout(controlLayout);
}

void VideoPlayer::openFile()
{
    QString fileName = QFileDialog::getOpenFileName(this,
        "打开视频文件", "", "视频文件 (*.mp4 *.avi *.mkv *.mov *.wmv)");
    
    if (!fileName.isEmpty()) {
        m_mediaPlayer->setSource(QUrl::fromLocalFile(fileName));
        play();
    }
}

void VideoPlayer::play()
{
    m_mediaPlayer->play();
}

void VideoPlayer::pause()
{
    m_mediaPlayer->pause();
}

void VideoPlayer::stop()
{
    m_mediaPlayer->stop();
}

void VideoPlayer::setPosition(int position)
{
    m_mediaPlayer->setPosition(position);
}

void VideoPlayer::onStateChanged(QMediaPlayer::State state)
{
    switch (state) {
    case QMediaPlayer::PlayingState:
        m_playButton->setEnabled(false);
        m_pauseButton->setEnabled(true);
        m_stopButton->setEnabled(true);
        break;
    case QMediaPlayer::PausedState:
        m_playButton->setEnabled(true);
        m_pauseButton->setEnabled(false);
        m_stopButton->setEnabled(true);
        break;
    case QMediaPlayer::StoppedState:
        m_playButton->setEnabled(true);
        m_pauseButton->setEnabled(false);
        m_stopButton->setEnabled(false);
        break;
    }
}

void VideoPlayer::onPositionChanged(qint64 position)
{
    m_positionSlider->setValue(position);
    
    QTime currentTime(0, 0, 0, 0);
    currentTime = currentTime.addMSecs(position);
    m_positionLabel->setText(currentTime.toString("mm:ss"));
}

void VideoPlayer::onDurationChanged(qint64 duration)
{
    m_positionSlider->setRange(0, duration);
    
    QTime totalTime(0, 0, 0, 0);
    totalTime = totalTime.addMSecs(duration);
    m_durationLabel->setText(totalTime.toString("mm:ss"));
}

void VideoPlayer::onErrorOccurred(QMediaPlayer::Error error, const QString &errorString)
{
    qWarning() << "Media player error:" << error << "-" << errorString;
}

2. 自定义视频渲染

cpp
// CustomVideoRenderer.h
#ifndef CUSTOMVIDEORENDERER_H
#define CUSTOMVIDEORENDERER_H

#include <QWidget>
#include <QMediaPlayer>
#include <QVideoFrame>
#include <QPainter>

class CustomVideoRenderer : public QWidget
{
    Q_OBJECT

public:
    explicit CustomVideoRenderer(QWidget *parent = nullptr);
    void setMediaPlayer(QMediaPlayer *player);

protected:
    void paintEvent(QPaintEvent *event) override;

private slots:
    void onVideoFrameChanged(const QVideoFrame &frame);

private:
    QMediaPlayer *m_mediaPlayer;
    QVideoFrame m_currentFrame;
    QImage m_currentImage;
};

#endif // CUSTOMVIDEORENDERER_H
cpp
// CustomVideoRenderer.cpp
#include "CustomVideoRenderer.h"
#include <QVideoProbe>
#include <QDebug>

CustomVideoRenderer::CustomVideoRenderer(QWidget *parent)
    : QWidget(parent)
    , m_mediaPlayer(nullptr)
{
    setAttribute(Qt::WA_OpaquePaintEvent);
}

void CustomVideoRenderer::setMediaPlayer(QMediaPlayer *player)
{
    if (m_mediaPlayer) {
        // 断开之前的连接
    }
    
    m_mediaPlayer = player;
    
    if (m_mediaPlayer) {
        QVideoProbe *probe = new QVideoProbe(this);
        if (probe->setSource(m_mediaPlayer)) {
            connect(probe, &QVideoProbe::videoFrameProbed,
                    this, &CustomVideoRenderer::onVideoFrameChanged);
        }
    }
}

void CustomVideoRenderer::paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event);
    
    QPainter painter(this);
    
    if (m_currentImage.isNull()) {
        // 没有视频帧时显示黑色背景
        painter.fillRect(rect(), Qt::black);
    } else {
        // 绘制视频帧,保持宽高比
        QImage scaledImage = m_currentImage.scaled(size(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
        QRect imageRect(QPoint(0, 0), scaledImage.size());
        imageRect.moveCenter(rect().center());
        
        painter.drawImage(imageRect, scaledImage);
    }
}

void CustomVideoRenderer::onVideoFrameChanged(const QVideoFrame &frame)
{
    QVideoFrame cloneFrame(frame);
    
    if (cloneFrame.map(QAbstractVideoBuffer::ReadOnly)) {
        // 转换为QImage
        QImage::Format format = QImage::Format_Invalid;
        
        switch (cloneFrame.pixelFormat()) {
        case QVideoFrame::Format_RGB32:
            format = QImage::Format_RGB32;
            break;
        case QVideoFrame::Format_ARGB32:
            format = QImage::Format_ARGB32;
            break;
        case QVideoFrame::Format_RGB24:
            format = QImage::Format_RGB888;
            break;
        default:
            // 不支持的格式
            break;
        }
        
        if (format != QImage::Format_Invalid) {
            m_currentImage = QImage(cloneFrame.bits(),
                                   cloneFrame.width(),
                                   cloneFrame.height(),
                                   cloneFrame.bytesPerLine(),
                                   format);
        }
        
        cloneFrame.unmap();
        
        // 触发重绘
        update();
    }
}

 

三. 音视频采集和播放的对比

 
特性 音频 视频
采集类 QAudioInput QCamera, QVideoProbe
播放类 QAudioOutput QMediaPlayer, QVideoWidget
数据格式 PCM采样格式 像素格式(RGB, YUV等)
数据单位 音频帧(样本) 视频帧(图像)
缓冲机制 QIODevice缓冲 QVideoFrame缓冲
实时性 低延迟重要 帧率稳定重要
同步需求 单独处理 通常需要与音频同步

 

1.相关类介绍

知识补充

【QCamera、QCameraInfo、QCameraExposure、QCameraFocus、QCameraFocusZone、QCameraViewfinder、QCameraViewfinderSettinfs】

QCamera:系统摄像头设备接口。

QCameraInfo:有关相机设备的一般信息。

QCameraExposure:曝光相关相机设置界面。

QCameraFocus:用于对焦和变焦相关相机设置的界面。

QCameraFocusZone:有关用于相机自动对焦区域的信息。

QCameraViewfinder:用于显示摄像头实时预览。

QCameraViewfinderSettings:取景器设置。

【QCameraImageCapture、QImageEncoderSettings、QCameraImageProcessing】

QCameraImageCapture:拍摄照片。

QImageEncoderSettings:图像编码器设置。

QCameraImageProcessing:图像处理相关相机设置接口。

【QMediaRecoder、QAudioEncoderSettings、QVideoEncoderSettings】

QMediaRecoder:音视频录像。

QAudioEncoderSettings:音频编码器设置。

QVideoEncoderSettings:视频编码器设置。

 

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