Qt6 重构了多媒体模块,一些接口也随之变动,本文记录用到的部分音频相关类的变化。
(PS:Qt6.2 该模块才回归,模块名未变,依旧是 multimedia)
1.输入输出设备枚举在 Qt5 中用 QAudioDeviceInfo 的静态函数来查询音频输入输出设备,一个实例表示一个设备:
//获取当前可用的音频输入/输出设备列表
//QAudio::AudioOutput 输出
//QAudio::AudioInput 输入
static QList QAudioDeviceInfo::availableDevices(QAudio::Mode mode);
//默认音频输入设备
static QAudioDeviceInfo QAudioDeviceInfo::defaultInputDevice();
//默认音频输出设备
static QAudioDeviceInfo QAudioDeviceInfo::defaultOutputDevice();
Qt5 这个接口不能多个线程同时访问,否则程序会挂掉。而且 Qt5 没有提供输入输出设备变更的信号,得自己去轮询,或者捕获系统的设备插拔事件后去查询。在 Windows 下,因为使用了两个音频后端插件,所以设备名会重复两次,可以移除这个插件,或者根据支持的采样率等信息来过滤其中一个插件的结果,有一个插件只支持高采样。
在 Qt6 中,设备枚举由 QMediaDevices 进行,同时新增了输入输出变更的信号,设备信息由 QAudioDevice 实例表示:
//音频输入设备列表
static QList QMediaDevices::audioInputs();
//音频输出设备列表
static QList QMediaDevices::audioOutputs();
//默认音频输入设备
static QAudioDevice QMediaDevices::defaultAudioInput();
//默认音频输出设备
static QAudioDevice QMediaDevices::defaultAudioOutput();
//QMediaDevices 增加输入输出设备变更的信号
void audioInputsChanged();
void audioOutputsChanged();
Qt6 测试多线程获取设备信息没有崩溃。Qt6 Windows 只保留了一个音频后端插件,解决了之前的设备名重复的问题。
2.音频输入(录制)Qt5 中通过输入设备 QAudioDeviceInfo 和音频格式 QAudioFormat 来构建一个 QAudioInput 进行录制,数据通过 QODevice 的读写接口回调输入:
QAudioInput(const QAudioDeviceInfo &audioDevice,
const QAudioFormat &format = QAudioFormat(),
QObject *parent = nullptr)
输入有推拉两种模式:
//pull
void QAudioInput::start(QIODevice *device);
//push
QIODevice* QAudioInput::start();
如果是 pull 模式,则继承 QIOdevice,重写 writeData (外部写)接口用于接收数据。如果是 push 模式,关联 start() 返回的 QIODevice 的 readyRead 信号去取数据。这里借用示例的代码:
//切换推拉模式
void InputTest::toggleMode()
{
m_audioInput->stop();
toggleSuspend();
// Change bewteen pull and push modes
if (m_pullMode) {
m_modeButton->setText(tr("Enable push mode"));
m_audioInput->start(m_audioInfo.data());
} else {
m_modeButton->setText(tr("Enable pull mode"));
auto io = m_audioInput->start();
connect(io, &QIODevice::readyRead,
[&, io]() {
qint64 len = m_audioInput->bytesReady();
const int BufferSize = 4096;
if (len > BufferSize)
len = BufferSize;
QByteArray buffer(len, 0);
qint64 l = io->read(buffer.data(), len);
if (l > 0)
m_audioInfo->write(buffer.constData(), l);
});
}
m_pullMode = !m_pullMode;
}
Qt6 中该操作改为 QAudioSource 来完成,接口差不多,但是没有了 notify() 信号。
3.音频输出(播放)Qt5 中通过输出设备 QAudioDeviceInfo 和音频格式 QAudioFormat 来构建一个 QAudioOutput 进行播放,数据通过 QODevice 的读写接口回调输出:
QAudioOutput(const QAudioDeviceInfo &audioDevice,
const QAudioFormat &format = QAudioFormat(),
QObject *parent = nullptr)
输出有推拉两种模式:
//pull
void QAudioOutput::start(QIODevice *device);
//push
QIODevice* QAudioOutput::start();
如果是 pull 模式,则继承 QIOdevice,重写 readData (外部读)接口用于输出数据。如果是 push 模式,用定时器给 start() 返回的 QIODevice 写数据。这里借用示例的代码:
//切换推拉模式
void AudioTest::toggleMode()
{
m_pushTimer->stop();
m_audioOutput->stop();
toggleSuspendResume();
if (m_pullMode) {
//switch to pull mode (QAudioOutput pulls from Generator as needed)
m_modeButton->setText(tr("Enable push mode"));
m_audioOutput->start(m_generator.data());
} else {
//switch to push mode (periodically push to QAudioOutput using a timer)
m_modeButton->setText(tr("Enable pull mode"));
auto io = m_audioOutput->start();
m_pushTimer->disconnect();
connect(m_pushTimer, &QTimer::timeout, [this, io]() {
if (m_audioOutput->state() == QAudio::StoppedState)
return;
QByteArray buffer(32768, 0);
int chunks = m_audioOutput->bytesFree() / m_audioOutput->periodSize();
while (chunks) {
const qint64 len = m_generator->read(buffer.data(), m_audioOutput->periodSize());
if (len)
io->write(buffer.data(), len);
if (len != m_audioOutput->periodSize())
break;
--chunks;
}
});
m_pushTimer->start(20);
}
m_pullMode = !m_pullMode;
}
Qt6 中该操作改为 QAudioSink 来完成,接口差不多,但是没有了 notify() 信号。
4.参考Qt Creator 中搜索 audio 查询到的相关示例
Qt5 文档:https://doc.qt.io/qt-5/qtmultimedia-modules.html
Qt6 文档:https://doc.qt.io/qt-6/qtmultimedia-modules.html