Qt - 音视频采集
Qt 提供了多媒体模块(Qt Multimedia)来处理音视频的采集、播放等。但是需要注意的是,Qt 的音视频采集功能可能在不同的平台上有不同的支持程度,并且可能不如专业的音视频库(如 FFmpeg)功能全面。不过,对于基本的音视频采集,Qt 提供了较为方便的 API。
下面我们将介绍如何使用 Qt 实现音视频采集。
一. 音频采集
1. QIODevice - 输入输出设备基类
基本概念
QIODevice 是 Qt 中所有输入输出设备的抽象基类,它定义了读写数据的通用接口。
主要用途
-
数据读写:提供统一的 read()、write() 方法
-
设备抽象:将不同的数据源统一为"设备"概念
-
信号机制:通过信号通知数据可用性
常用子类
QFile // 文件
QBuffer // 内存缓冲区
QTcpSocket // TCP网络套接字
QUdpSocket // UDP网络套接字
QProcess // 进程通信
在音频处理中的使用
// 创建内存缓冲区作为音频数据容器
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();
// 处理新到达的音频数据
});
关键方法
// 打开设备
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音频数据
-
实时录音:实现录音功能
-
语音输入:语音识别、语音通话的输入部分
工作流程
麦克风 → QAudioInput → QIODevice → 应用程序
基本使用
#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();
实时音频采集示例
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格式的音频数据
-
实时播放:语音通话、实时音频流
-
音频提示:系统提示音、游戏音效
工作流程
应用程序 → QIODevice → QAudioOutput → 扬声器
基本使用
#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();
}
});
实时音频播放示例
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)
// 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
// 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. 自定义视频表面(更底层的采集)
// 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
// 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 - 媒体播放器(支持音视频)
#include <QMediaPlayer>
QVideoWidget - 视频显示组件
#include <QVideoWidget>
QGraphicsVideoItem - 在 QGraphicsView 中显示视频
#include <QGraphicsVideoItem>
2.1视频播放详细实现
1. 使用 QMediaPlayer + QVideoWidget
// 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
// 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. 自定义视频渲染
// 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
// 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:视频编码器设置。

浙公网安备 33010602011771号