QT编程之PCM音频播放与采集
一、高级播放接口(未压缩编码的音频文件)
-
QMediaPlayer
- 支持MP3/WMA等压缩格式及网络流媒体播放,集成媒体控制(播放/暂停/进度调节)
- 需设置
QAudioOutput
指定输出设备,支持播放速度调节(setPlaybackRate) - 代码示例:
QMediaPlayer player; player.setSource(QUrl::fromLocalFile("audio.mp3")); player.play(); // 开始播放
- QSoundEffect
1.专为低延迟音效设计,适合游戏/UI反馈音,支持WAV格式
2.支持循环播放(setLoopCount)和实时音量调节
3.代码示例:QSoundEffect effect; effect.setSource(QUrl::fromLocalFile("beep.wav")); effect.play(); // 延迟低于50ms
二、底层音频控制
- QAudioOutput/QAudioSink
- 直接处理PCM数据流,适合FFmpeg解码后的原始音频播放
- Qt5使用
QAudioOutput
,Qt6重命名为QAudioSink
,需指定采样率/声道数等参数 - 典型应用:
QAudioFormat format; format.setSampleRate(44100); format.setChannelCount(2); QAudioSink sink(format); sink.start(data_device); // data_device提供PCM数据流
三、PCM音频播放
Qt5/Qt6通用方案
// 头文件包含
#include <QFile>
#include <QAudioFormat>
#include <QAudioOutput> // Qt5
#include <QAudioSink> // Qt6// 创建音频格式
QAudioFormat format;
format.setSampleRate(44100); // 采样率需与PCM文件一致
format.setChannelCount(2); // 声道数(1=单声道,2=立体声)
format.setSampleFormat(QAudioFormat::Int16); // 位深(必须与PCM编码匹配)// 打开PCM文件
QFile pcmFile("audio.pcm");
if(pcmFile.open(QIODevice::ReadOnly)) {// 检查设备支持QAudioDevice device = QMediaDevices::defaultAudioOutput();if(!device.isFormatSupported(format)) {qWarning() << "不支持的音频格式"; // 需调整参数重试return;}// 创建播放对象(Qt5用QAudioOutput, Qt6用QAudioSink)#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)QAudioOutput* audioOutput = new QAudioOutput(format);audioOutput->start(&pcmFile); // 启动播放#elseQAudioSink* audioSink = new QAudioSink(format);audioSink->start(&pcmFile); // Qt6新版API#endif
}
关键参数说明
参数 | 典型值 | 注意事项 |
---|---|---|
采样率 | 8000/44100/48000 Hz | 必须与PCM文件生成时一致 |
样本格式 | Int16/UInt8/Float | FFmpeg常用s16le对应Int16 |
缓冲区大小 | 4000-8192 bytes | 过小导致卡顿,过大会增加延迟 |
四、 PCM转WAV
// 添加WAV文件头(44字节)
struct WAVHeader {char riff[] = {'R','I','F','F'};uint32_t fileSize; // 文件总大小-8char wave[] = {'W','A','V','E'};// ... 其他字段根据参数填充
};QFile wavFile("audio.wav");
wavFile.open(QIODevice::WriteOnly);
wavFile.write((char*)&header, sizeof(WAVHeader)); // 写入头信息
wavFile.write(pcmData); // 追加PCM数据
五、PCM采集
Qt QAudioInput 实现 PCM 音频采集核心流程:
1. 参数配置
设置音频采集的格式参数,需定义 QAudioFormat
对象并指定以下关键参数:
QAudioFormat format;
format.setSampleRate(44100); // 采样率(如 44100Hz)
format.setChannelCount(1); // 声道数(单声道)
format.setSampleSize(16); // 样本大小(16-bit)
format.setCodec("audio/pcm"); // 编码格式为 PCM
format.setByteOrder(QAudioFormat::LittleEndian); // 字节序(小端)
format.setSampleType(QAudioFormat::SignedInt); // 样本类型(有符号整数)
2. 设备检查
验证默认输入设备是否支持设定的格式,若不支持则自动适配最接近的格式:
QAudioDeviceInfo device = QAudioDeviceInfo::defaultInputDevice();
if (!device.isFormatSupported(format)) {qWarning() << "默认格式不支持,尝试适配最接近格式";format = device.nearestFormat(format); // 自动适配
}
3. 启动采集
创建 QAudioInput
对象并启动音频输入设备:
QAudioInput* audioInput = new QAudioInput(format);
QIODevice* inputDevice = audioInput->start(); // 启动设备
4. 数据读取
通过 QIODevice
实时读取 PCM 数据:
// 通过信号槽机制实时读取数据(例如连接 readyRead 信号)
QObject::connect(inputDevice, &QIODevice::readyRead, :ml-search[=] {QByteArray buffer = inputDevice->readAll(); // 获取原始 PCM 数据// 此处处理或保存 buffer 数据(如发送到网络或写入文件)
});
5. 保存为 WAV 文件(可选)
若需保存为 WAV 文件,需在 PCM 数据前添加 WAV 头:
#include <QCoreApplication>
#include <QAudioInput>
#include <QFile>
#include <QDebug>// WAV文件头结构体(1字节对齐)
#pragma pack(push, 1)
struct WAVHeader {char riff = {'R', 'I', 'F', 'F'};quint32 fileSize;char wave = {'W', 'A', 'V', 'E'};char fmt = {'f', 'm', 't', ' '};quint32 fmtSize = 16;quint16 audioFormat = 1; // PCM格式quint16 channels;quint32 sampleRate;quint32 bytesPerSecond;quint16 blockAlign;quint16 bitsPerSample;char data = {'d', 'a', 't', 'a'};quint32 dataSize;
};
#pragma pack(pop)class AudioRecorder : public QObject {Q_OBJECT
public:explicit AudioRecorder(QObject *parent = nullptr) : QObject(parent) {}void startRecording(const QString &filename) {// 1. 设置音频格式QAudioFormat format;format.setSampleRate(44100);format.setChannelCount(1);format.setSampleSize(16);format.setCodec("audio/pcm");format.setByteOrder(QAudioFormat::LittleEndian);format.setSampleType(QAudioFormat::SignedInt);// 2. 检查设备支持QAudioDeviceInfo device = QAudioDeviceInfo::defaultInputDevice();if (!device.isFormatSupported(format)) {format = device.nearestFormat(format);qWarning() << "使用适配格式:" << format;}// 3. 创建WAV文件并写入头file.setFileName(filename);if (!file.open(QIODevice::WriteOnly)) {qCritical() << "无法创建文件:" << filename;return;}// 生成WAV头WAVHeader header;header.channels = format.channelCount();header.sampleRate = format.sampleRate();header.bitsPerSample = format.sampleSize();header.blockAlign = format.channelCount() * format.sampleSize() / 8;header.bytesPerSecond = format.sampleRate() * header.blockAlign;header.fileSize = sizeof(WAVHeader) - 8; // 后续更新header.dataSize = 0; // 后续更新file.write(reinterpret_cast<char*>(&header), sizeof(WAVHeader));// 4. 启动音频输入audioInput = new QAudioInput(format);audioInput->start(&file); // 直接将数据写入文件qDebug() << "开始录音...";}void stopRecording() {if (audioInput) {audioInput->stop();delete audioInput;audioInput = nullptr;// 更新WAV头信息quint32 fileSize = file.size() - 8;quint32 dataSize = fileSize - sizeof(WAVHeader) + 8;file.seek(4);file.write(reinterpret_cast<char*>(&fileSize), 4);file.seek(sizeof(WAVHeader) - 4);file.write(reinterpret_cast<char*>(&dataSize), 4);file.close();qDebug() << "录音已保存";}}private:QAudioInput *audioInput = nullptr;QFile file;
};// 使用示例
int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);AudioRecorder recorder;recorder.startRecording("recording.wav");// 按回车键停止录音QObject::connect(&a, &QCoreApplication::aboutToQuit, [&recorder] {recorder.stopRecording();});return a.exec();
}#include "main.moc"
注意事项
1)实时性处理:建议使用异步信号槽机制(如 readyRead
)避免阻塞主线程。
2)异常处理:需检查设备是否可用,并在结束时释放资源(调用 stop()
和 delete
)。
3)跨平台兼容性:代码在 Windows、Linux、Android 等系统均可运行,但需注意字节对齐问题(如 WAV 头结构体)。
3)参数调优:根据实际需求调整采样率、声道数等参数,以平衡音质和性能。
六、开发注意事项
-
跨平台兼容性
- Linux需安装
pulseaudio
或alsa-lib
驱动 - Windows/Mac需确认音频设备支持指定格式
- 结构体使用
#pragma pack(1)
避免对齐问题
- Linux需安装
-
实时音频处理
- 采集使用
QAudioInput
,与播放代码结构类似 - 网络传输时建议分块发送(每帧1024样本)
- 采集使用
-
性能优化
- 启用
QIODevice::Unbuffered
模式降低延迟 - 多线程处理:解码/采集与播放分离
- 启用