目录
本人实际是要完成一个类似于对讲机的通话小Demo,并且支持安卓,当然QT就是跨平台的,安卓的内容就不在这里叙述,后面可能会记录,功能就是两台客户端,通过网络websocket传递音频数据,做到实时通话。需要使用到QT的音频输入输出。但是网络上对QT6的音频输入输出不详细,故写此篇。
想寻找QT5实现的可以参考这些文章:
QT应用编程: 基于Qt设计的跨平台录音机功能 - 知乎 (zhihu.com)
一、QT6音频调用与QT5的区别
QT5的音频输入输出调用网络上还是蛮多介绍的,这里详细介绍以及实战一下QT6的音频调用输入输出,网络上很少提到,问一些AI它们也都只会QT5的调用方法,于是还是通过自己查找资料和看官方文档,慢慢整理出来并且实现一个对讲机功能的应用,下面就先看看官方控制输出与输入的类的变化,以及范例。
1.QAudioSource代替QAudioInput类
QAudioSource Class
QAudioSource类提供了一个接口,用于从音频输入设备接收音频数据。
|-----------|----------------------------------------------------------------------------------------------------------|
| Header: | #include <QAudioSource> |
| CMake: | find_package(Qt6 REQUIRED COMPONENTS Multimedia) target_link_libraries(mytarget PRIVATE Qt6::Multimedia) |
| qmake: | QT += multimedia |
| Inherits: | QObject |公共函数
|---------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| | QAudioSource (const QAudioFormat &format = QAudioFormat(), QObject *parent = nullptr) |
| | QAudioSource (const QAudioDevice &audioDevice , const QAudioFormat &format = QAudioFormat(), QObject *parent = nullptr) |
| virtual | ~QAudioSource() |
| qsizetype | bufferSize() const |
| qsizetype | bytesAvailable() const |
| qint64 | elapsedUSecs() const |
| QAudio::Error | error() const |
| QAudioFormat | format() const |
| bool | isNull() const |
| qint64 | processedUSecs() const |
| void | reset() |
| void | resume() |
| void | setBufferSize (qsizetype value) |
| void | setVolume (qreal volume) |
| void | start (QIODevice *device) |
| QIODevice * | start() |
| QAudio::State | state() const |
| void | stop() |
| void | suspend() |
| qreal | volume() const |信号
|------|------------------------------------------------------------------------------------------------------------------|
| void | stateChanged (QAudio::State state) |详细说明:
您可以使用系统的默认音频输入设备构建音频输入。也可以使用特定的QAudioDevice创建QAudioSource。创建音频输入时,还应发送用于录制的QAudioFormat(有关详细信息,请参阅QAudioFormat类描述)。
要录制到文件,请执行以下操作:
QAudioSource允许您使用音频输入设备录制音频。此类的默认构造函数将使用系统默认音频设备,但您也可以为特定设备指定QAudioDevice。您还需要传入要录制的QAudioFormat。启动QAudioSource只需在打开QIODevice的情况下调用start()
cpp QFile destinationFile; // Class member QAudioSource* audio; // Class member { destinationFile.setFileName("/tmp/test.raw"); destinationFile.open( QIODevice::WriteOnly | QIODevice::Truncate ); QAudioFormat format; // Set up the desired format, for example: format.setSampleRate(8000); format.setChannelCount(1); format.setSampleFormat(QAudioFormat::UInt8); QAudioDevice info = QMediaDevices::defaultAudioInput(); if (!info.isFormatSupported(format)) { qWarning() << "Default format not supported, trying to use the nearest."; } audio = new QAudioSource(format, this); connect(audio, &QAudioSource::stateChanged, this, &AudioInputExample::handleStateChanged); QTimer::singleShot(3000, this, &AudioInputExample::stopRecording); audio->start(&destinationFile); // Records audio for 3000ms }
如果输入设备支持指定的格式,这将开始录制(您可以使用QAudioDevice::isFormatSupported()进行检查。如果出现任何问题,请使用error()函数检查出了什么问题。我们在stopRecording()插槽中停止录制。
cppvoid AudioInputExample::stopRecording() { audio->stop(); destinationFile.close(); delete audio; }
在任何时间点,QAudioSource都将处于四种状态之一:活动、挂起、停止或空闲。这些状态由QAudio::State枚举指定。您可以直接通过suspend()、resume(),stop(),reset()和start()请求状态更改。当前状态由state()报告。当状态发生变化时,QAudioSink也会向您发出信号(stateChanged())。
QAudioSource提供了几种测量录制开始()后经过的时间的方法。processedUSecs()函数返回以微秒为单位写入的流的长度,即,它忽略了音频输入暂停或空闲的时间。elapsedUSecs()函数返回自调用start()以来经过的时间,无论QAudioSource处于何种状态。
如果出现错误,可以使用error()获取其原因。可能的错误原因由QAudio::error枚举描述。遇到错误时,QAudioSource将进入StoppedState。连接到stateChanged()信号以处理错误:
cppvoid AudioInputExample::handleStateChanged(QAudio::State newState) { switch (newState) { case QAudio::StoppedState: if (audio->error() != QAudio::NoError) { // Error handling } else { // Finished recording } break; case QAudio::ActiveState: // Started recording - read from IO device break; default: // ... other cases as appropriate break; } }
2.QAudioSink代替QAudioOutput类
QAudioSink Class
QAudioSink类提供了一个接口,用于将音频数据发送到音频输出设备。
|-----------|----------------------------------------------------------------------------------------------------------|
| Header: | #include <QAudioSink> |
| CMake: | find_package(Qt6 REQUIRED COMPONENTS Multimedia) target_link_libraries(mytarget PRIVATE Qt6::Multimedia) |
| qmake: | QT += multimedia |
| Inherits: | QObject |公共函数
|---------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| | QAudioSink (const QAudioFormat &format = QAudioFormat(), QObject *parent = nullptr) |
| | QAudioSink (const QAudioDevice &audioDevice , const QAudioFormat &format = QAudioFormat(), QObject *parent = nullptr) |
| virtual | ~QAudioSink() |
| qsizetype | bufferSize() const |
| qsizetype | bytesFree() const |
| qint64 | elapsedUSecs() const |
| QAudio::Error | error() const |
| QAudioFormat | format() const |
| bool | isNull() const |
| qint64 | processedUSecs() const |
| void | reset() |
| void | resume() |
| void | setBufferSize (qsizetype value) |
| void | setVolume (qreal volume) |
| void | start (QIODevice *device) |
| QIODevice * | start() |
| QAudio::State | state() const |
| void | stop() |
| void | suspend() |
| qreal | volume() const |信号
|------|----------------------------------------------------------------------------------------------------------------|
| void | stateChanged (QAudio::State state) |详细描述
您可以使用系统的默认音频输出设备构建音频输出。也可以使用特定的QAudioDevice创建QAudioSink。创建音频输出时,还应发送用于播放的QAudioFormat(有关详细信息,请参阅QAudioFormat类描述)。
播放文件:
开始播放音频流只需使用QIODevice调用start()即可。然后,QAudioSink将从io设备中获取所需的数据。因此,播放音频文件非常简单:
cppQFile sourceFile; // class member. QAudioSink* audio; // class member. { sourceFile.setFileName("/tmp/test.raw"); sourceFile.open(QIODevice::ReadOnly); QAudioFormat format; // Set up the format, eg. format.setSampleRate(8000); format.setChannelCount(1); format.setSampleFormat(QAudioFormat::UInt8); QAudioDevice info(QMediaDevices::defaultAudioOutput()); if (!info.isFormatSupported(format)) { qWarning() << "Raw audio format not supported by backend, cannot play audio."; return; } audio = new QAudioSink(format, this); connect(audio, QAudioSink::stateChanged, this, &AudioInputExample::handleStateChanged); audio->start(&sourceFile); }
假设音频系统和输出设备支持该文件,则该文件将开始播放。如果运气不好,请检查error()函数的情况。
文件播放完毕后,我们需要停止设备:
cppvoid AudioOutputExample::stopAudioOutput() { audio->stop(); sourceFile.close(); delete audio; }
在任何给定时间,QAudioSink都将处于四种状态之一:活动、暂停、停止或空闲。这些状态由QAudio::State枚举描述。状态变化通过stateChanged()信号报告。例如,您可以使用此信号来更新应用程序的GUI;这里常见的例子是更改播放/暂停按钮的状态。您可以使用suspend()、stop()、reset()、resume()和start()直接请求状态更改。
如果发生错误,可以使用error()函数获取错误类型。有关报告的可能错误的描述,请参阅QAudio::Error枚举。当遇到QAudio::UnderrunError时,状态将变为QAudio::IdleState,当遇到另一个错误时,状态变为QAaudio::StoppedState。您可以通过连接到stateChanged()信号来检查错误:
cppvoid AudioOutputExample::handleStateChanged(QAudio::State newState) { switch (newState) { case QAudio::IdleState: // Finished playing (no more data) AudioOutputExample::stopAudioOutput(); break; case QAudio::StoppedState: // Stopped for other reasons if (audio->error() != QAudio::NoError) { // Error handling } break; default: // ... other cases as appropriate break; } }
同样可以看到这两个在上面两个类中的运用,可以自行去看看QAudioSource and QAudioDevice.
看到这里有些同志已经会了,上面的范例主要就是示范对于音频文件的输入输出,加载在设备上就有了录音和读文件的功能。但是我要实现的实时对讲机不是这样的,不需要记录为文件,所以我要生成pcm格式的二进制数据然后传入传出,这里如果想了解音频格式的,或者是对音频格式有要求的可以去了解一下这些方面。
QT生成的音频数据格式
QT播放音频文件
FFMPEG音频库的引入
FFMPEG库对音频数据的转码
二、音频操作中Push和Pull的区别
网络上很多博主都没有说清楚甚至没有说其实输入和输出都有两种方法,就是Push和Pull方式,要根据实际功能选择使用,而且不要弄混了,我在项目中使用的时候,输入是用的push方式,输出用的pull方式,实现的是实时对讲机,它们有以下区别:
在Qt的QIODevice
及其派生类中,有两种常见的数据读取和写入方式:push
和pull
。这两种方式是用于描述数据流如何被传输的。
Push 模式:
- 概念: 在 Push 模式中,数据的生产者(producer)主动推送数据到消费者(consumer)。生产者生成数据并将其推送到消费者。
- 例子: 一个网络套接字(
QTcpSocket
)可以使用 Push 模式,当有新数据到达时,套接字发射readyRead
信号,告知应用程序有数据可读。- 使用场景: 当数据的生成速率相对较快或者生产者的数据产生是不规律的时候,Push 模式通常更为合适。
Pull 模式:
- 概念: 在 Pull 模式中,数据的消费者主动从数据源拉取(pull)数据。消费者主动发起请求以获取数据。
- 例子: 文件I/O 操作通常是 Pull 模式,你需要调用
read
函数来从文件中拉取数据。- 使用场景: 当数据的生成速率相对较慢或者数据生成是规律的时候,Pull 模式通常更为合适。
在 Qt 中,QIODevice
的 read
和 write
方法是 Pull 模式的典型例子,而 QIODevice
的 readyRead
信号则是 Push 模式的例子。QIODevice
实际上可以同时支持 Push 和 Pull 操作。
在 Qt 中,QIODevice
是一个抽象类,而具体的实现类如 QFile
、QTcpSocket
等,根据其用途,可能更倾向于其中一种方式。你在使用这些类时,可以根据具体的需求选择适当的模式。
三、依托于Websocket实现实时对讲机
效果图:
WebSocket部分主要就是通过QT自带的WebSocket然后利用网络服务器帮着传输音频数据,就不贴出来了,可以用其他任何方式替代,主要是对音频处理的代码我会贴出。
1.AudioIputDevices类
实现了对音频输入的设备数据控制。
cpp
#include <QAudioSource>
#include <QMediaDevices>
#include <QComboBox>
#include <QPushButton>
#include <QSlider>
#include <QWidget>
#include <QPixmap>
#include <QByteArray>
#include <QScopedPointer>
class AudioIputDevices : public QIODevice
{
Q_OBJECT
public:
AudioIputDevices(const QAudioFormat &format);
void start();
void stop();
qreal level() const { return m_level; }
qint64 readData(char *data, qint64 maxlen) override;
qint64 writeData(const char *data, qint64 len) override;
qreal calculateLevel(const char *data, qint64 len) const;
signals:
void levelChanged(qreal level);
void signalInputAudioBytearrayData(const char *data, qint64 len);
private:
const QAudioFormat m_format;
qreal m_level = 0.0; // 0.0 <= m_level <= 1.0
};
#include <QAudioDevice>
#include <QAudioSource>
#include <QDateTime>
#include <QDebug>
#include <QLabel>
#include <QPainter>
#include <QVBoxLayout>
#include <QtEndian>
#if QT_CONFIG(permissions)
#include <QCoreApplication>
#include <QPermission>
#endif
#include <math.h>
#include <stdlib.h>
AudioIputDevices::AudioIputDevices(const QAudioFormat &format) : m_format(format) { }
void AudioIputDevices::start()
{
open(QIODevice::WriteOnly);
}
void AudioIputDevices::stop()
{
close();
}
qint64 AudioIputDevices::readData(char * /* data */, qint64 /* maxlen */)
{
return 0;
}
qreal AudioIputDevices::calculateLevel(const char *data, qint64 len) const
{
const int channelBytes = m_format.bytesPerSample();
const int sampleBytes = m_format.bytesPerFrame();
const int numSamples = len / sampleBytes;
float maxValue = 0;
auto *ptr = reinterpret_cast<const unsigned char *>(data);
for (int i = 0; i < numSamples; ++i) {
for (int j = 0; j < m_format.channelCount(); ++j) {
float value = m_format.normalizedSampleValue(ptr);
maxValue = qMax(value, maxValue);
ptr += channelBytes;
}
}
return maxValue;
}
qint64 AudioIputDevices::writeData(const char *data, qint64 len)
{
m_level = calculateLevel(data, len);
emit signalInputAudioBytearrayData(data, len);
emit levelChanged(m_level);
return len;
}
音量实时显示条
cpp
class RenderArea : public QWidget
{
Q_OBJECT
public:
explicit RenderArea(QWidget *parent = nullptr);
public slots:
void setLevel(qreal value);
protected:
void paintEvent(QPaintEvent *event) override;
private:
qreal m_level = 0;
};
RenderArea::RenderArea(QWidget *parent) : QWidget(parent)
{
setBackgroundRole(QPalette::Base);
setAutoFillBackground(true);
setMinimumHeight(30);
setMinimumWidth(200);
}
void RenderArea::paintEvent(QPaintEvent * /* event */)
{
QPainter painter(this);
painter.setPen(Qt::black);
const QRect frame = painter.viewport() - QMargins(10, 10, 10, 10);
painter.drawRect(frame);
if (m_level == 0.0)
return;
const int pos = qRound(qreal(frame.width() - 1) * m_level);
painter.fillRect(frame.left() + 1, frame.top() + 1, pos, frame.height() - 1, Qt::red);
}
void RenderArea::setLevel(qreal value)
{
m_level = value;
update();
}
2.AudioOutputDevices类
实现对音频输出的设备数据控制。
cpp
#include <QAudioSink>
#include <QByteArray>
#include <QComboBox>
#include <QIODevice>
#include <QLabel>
#include <QMainWindow>
#include <QMediaDevices>
#include <QObject>
#include <QPushButton>
#include <QScopedPointer>
#include <QSlider>
#include <QTimer>
class AudioOutputDevices : public QIODevice
{
Q_OBJECT
public:
AudioOutputDevices(const QAudioFormat &format, qint64 durationUs, int sampleRate);
void start();
void stop();
qint64 readData(char *data, qint64 maxlen) override;
qint64 writeData(const char *data, qint64 len) override;
qint64 bytesAvailable() const override;
qint64 size() const override { return m_buffer.size(); }
private:
void generateData(const QAudioFormat &format, qint64 durationUs, int sampleRate);
private:
qint64 m_pos = 0;
QByteArray m_buffer;
};
#include <QAudioDevice>
#include <QAudioSink>
#include <QDebug>
#include <QVBoxLayout>
#include <QtEndian>
#include <QtMath>
AudioOutputDevices::AudioOutputDevices(const QAudioFormat &format, qint64 durationUs, int sampleRate)
{
if (format.isValid())
generateData(format, durationUs, sampleRate);
}
void AudioOutputDevices::start()
{
open(QIODevice::ReadOnly);
}
void AudioOutputDevices::stop()
{
m_pos = 0;
close();
}
void AudioOutputDevices::generateData(const QAudioFormat &format, qint64 durationUs, int sampleRate)
{
const int channelBytes = format.bytesPerSample();
const int sampleBytes = format.channelCount() * channelBytes;
qint64 length = format.bytesForDuration(durationUs);
Q_ASSERT(length % sampleBytes == 0);
Q_UNUSED(sampleBytes); // suppress warning in release builds
m_buffer.resize(length);
unsigned char *ptr = reinterpret_cast<unsigned char *>(m_buffer.data());
int sampleIndex = 0;
while (length) {
// Produces value (-1..1)
const qreal x = qSin(2 * M_PI * sampleRate * qreal(sampleIndex++ % format.sampleRate())
/ format.sampleRate());
for (int i = 0; i < format.channelCount(); ++i) {
switch (format.sampleFormat()) {
case QAudioFormat::UInt8:
*reinterpret_cast<quint8 *>(ptr) = static_cast<quint8>((1.0 + x) / 2 * 255);
break;
case QAudioFormat::Int16:
*reinterpret_cast<qint16 *>(ptr) = static_cast<qint16>(x * 32767);
break;
case QAudioFormat::Int32:
*reinterpret_cast<qint32 *>(ptr) =
static_cast<qint32>(x * std::numeric_limits<qint32>::max());
break;
case QAudioFormat::Float:
*reinterpret_cast<float *>(ptr) = x;
break;
default:
break;
}
ptr += channelBytes;
length -= channelBytes;
}
}
}
qint64 AudioOutputDevices::readData(char *data, qint64 len)
{
qint64 total = 0;
// if (!m_buffer.isEmpty()) {
// // qDebug() << "!m_buffer.isEmpty()" << m_buffer ;
// while (len - total > 0) {
// const qint64 chunk = qMin((m_buffer.size() - m_pos), len - total);
// memcpy(data + total, m_buffer.constData() + m_pos, chunk);
// m_pos = (m_pos + chunk) % m_buffer.size();
// total += chunk;
// }
// }
return total;
}
qint64 AudioOutputDevices::writeData(const char *data, qint64 len)
{
Q_UNUSED(data);
Q_UNUSED(len);
return 0;
}
qint64 AudioOutputDevices::bytesAvailable() const
{
return m_buffer.size() + QIODevice::bytesAvailable();
}
3.实现的AudioHandler类完整内容
这个类处理了开始讲话和停止讲话,接收WebSocket传来的音频数据
cpp
#ifndef AUDIOHANDLER_H
#define AUDIOHANDLER_H
#include <QObject>
#include <QCoreApplication>
#include <QtWebSockets/QWebSocket>
#include <QBuffer>
#include <QAudio> //这五个是QT处理音频的库
#include <QAudioFormat>
#include <QAudioInput>
#include <QAudioOutput>
#include <QIODevice>
#include <QThread>
#include <QAudioSource>
#include <QAudioSink>
#include <QTimer>
#include <QFile>
#include <QMediaDevices>
#include "datahandle.h"
#include "audioiputdevices.h"
#include "audiooutputdevices.h"
class AudioHandler : public QObject
{
Q_OBJECT
public:
explicit AudioHandler(QObject *parent = nullptr);
~AudioHandler();
public slots:
void onStartTalking();
void onStopTalking();
// void deviceChanged(QAudioDevice device, int index);
void onAudioDataformWebsocket(const QByteArray& audioOutputData);
private slots:
void processAudioData(const QByteArray &data);
void onInputNotify();
void onOutputNotify();
private:
void initializeInputAudio(const QAudioDevice &deviceInfo);
void initializeOutPutAudio(const QAudioDevice &deviceInfo);
void startAudioInput();
void stopAudioInput();
void startAudioOutput();
void stopAudioOutput();
private:
QScopedPointer<AudioIputDevices> inputDevice;
QScopedPointer<AudioOutputDevices> outputDevice;
QIODevice *m_output;
QAudioFormat inputAudioFormat;
QAudioFormat outputAudioFormat;
QScopedPointer<QAudioSource> audioInputsource;
QScopedPointer<QAudioSink> audioOutputsource;
// QAudioDevice inputDevice;
// QAudioDevice outputAudioDevice;
QMediaDevices *m_inputMediaDevices;
QMediaDevices *m_outputMediaDevices;
QTimer *m_inputTimer;
QTimer *m_outputTimer;
QTimer *m_pushTimer;
QBuffer *m_audioBuffer;
DataHandle m_dataHandle;
QByteArray m_audioByteArrayData;
QByteArray m_audioOutputByteArryaData;
signals:
void signalSendData(const QByteArray& data);
void signalRequestTalk(CMDTYPE cmdtype);
void signalAudioLevel(qreal value);
};
#endif // AUDIOHANDLER_H
cpp
#include "audiohandler.h"
#include <QDebug>
AudioHandler::AudioHandler(QObject *parent)
:QObject(parent), audioInputsource(nullptr), audioOutputsource(nullptr)
,m_outputMediaDevices(new QMediaDevices(this)), m_inputTimer(new QTimer(this))
, m_outputTimer(new QTimer(this)),m_pushTimer(new QTimer(this))
{
for(int i = 0; i < QMediaDevices::audioInputs().count(); ++i)
{
auto aa = QMediaDevices::audioInputs().at(i);
qDebug() << "音频输入:" << aa.description();
}
for(int i = 0; i < QMediaDevices::audioOutputs().count(); ++i)
{
auto aa = QMediaDevices::audioOutputs().at(i);
qDebug() << "音频输出:" << aa.description();
}
initializeInputAudio(QMediaDevices::defaultAudioInput());
// initializeOutPutAudio(m_outputMediaDevices->audioOutputs().at(2));
initializeOutPutAudio(m_outputMediaDevices->defaultAudioOutput());
connect(m_inputTimer, &QTimer::timeout, this, &AudioHandler::onInputNotify);
connect(inputDevice.data(), &AudioIputDevices::levelChanged, [=](qreal value){
// qDebug() << "emit signalAudioLevel(value);" << value;
emit signalAudioLevel(value);
});
connect(m_outputTimer, &QTimer::timeout, this, &AudioHandler::onOutputNotify);
onStopTalking();
}
AudioHandler::~AudioHandler()
{
if(m_outputTimer)
{
m_outputTimer->deleteLater();
m_outputTimer = nullptr;
}
if(m_inputTimer)
{
m_inputTimer->deleteLater();
m_inputTimer = nullptr;
}
}
void AudioHandler::initializeInputAudio(const QAudioDevice &deviceInfo)
{
//设置录音的格式
inputAudioFormat.setSampleRate(8000); //设置采样率以对赫兹采样。 以秒为单位,每秒采集多少声音数据的频率.
inputAudioFormat.setChannelCount(1); //将通道数设置为通道。
// audioFormat.setSampleSize(16); /*将样本大小设置为指定的sampleSize(以位为单位)通常为8或16,但是某些系统可能支持更大的样本量。*/
inputAudioFormat.setSampleFormat(QAudioFormat::Int16);
// audioFormat.setCodec("audio/pcm"); //设置编码格式
// audioFormat.setByteOrder(QAudioFormat::LittleEndian); //样本是小端字节顺序
// audioFormat.setSampleType(QAudioFormat::SignedInt); //样本类型
// ChannelConfigStereo is 2, Int16 is 2
qDebug("sampleRate: %d, channelCount: %d, sampleFormat: %d",
inputAudioFormat.sampleRate(), inputAudioFormat.channelCount(), inputAudioFormat.sampleFormat()
);
inputDevice.reset(new AudioIputDevices(inputAudioFormat));
audioInputsource.reset(new QAudioSource(deviceInfo, inputAudioFormat));
connect(inputDevice.data(), &AudioIputDevices::signalInputAudioBytearrayData, [=](const char *data, qint64 len){
qDebug() << "m_audioByteArrayData start:" << "m_audioByteArrayData size:" << m_audioByteArrayData.size() ;
QByteArray aa(data, len);
m_audioByteArrayData.append(aa);
qDebug() << "m_audioByteArrayData final:" << "m_audioByteArrayData size:" << m_audioByteArrayData.size() ;
});
}
void AudioHandler::initializeOutPutAudio(const QAudioDevice &deviceInfo)
{
qDebug() << "outputAudioDevice音频输出:" << deviceInfo.description();
outputAudioFormat = deviceInfo.preferredFormat();
outputAudioFormat.setSampleRate(8000); //设置采样率以对赫兹采样。 以秒为单位,每秒采集多少声音数据的频率.
outputAudioFormat.setChannelCount(1); //将通道数设置为通道。
// audioFormat.setSampleSize(16); /*将样本大小设置为指定的sampleSize(以位为单位)通常为8或16,但是某些系统可能支持更大的样本量。*/
outputAudioFormat.setSampleFormat(QAudioFormat::Int16);
if(!deviceInfo.isFormatSupported(outputAudioFormat))
{
qWarning() << "not Support fromat";
}
qDebug("sampleRate: %d, channelCount: %d, sampleFormat: %d",
outputAudioFormat.sampleRate(), outputAudioFormat.channelCount(), outputAudioFormat.sampleFormat()
);
const int durationSeconds = 1;
const int toneSampleRateHz = 600;
outputDevice.reset(new AudioOutputDevices(outputAudioFormat, durationSeconds * 1000000, toneSampleRateHz));
audioOutputsource.reset(new QAudioSink(deviceInfo, outputAudioFormat));
}
void AudioHandler::processAudioData(const QByteArray &data)
{
emit signalSendData(data);
}
void AudioHandler::onOutputNotify()
{
if(m_audioOutputByteArryaData.size() == 0)
return;
qDebug() << "m_audioOutputByteArryaData大小:" << m_audioOutputByteArryaData.size();
// auto io = audioOutputsource->start();
// int len = audioOutputsource->bytesFree();
// qDebug() << "len:" << len;
// len = io->write(m_audioOutputByteArryaData.data(), m_audioOutputByteArryaData.size());
int len = audioOutputsource->bytesFree();
qDebug() << "len:" << len;
len = m_output->write(m_audioOutputByteArryaData.data(), m_audioOutputByteArryaData.size());
m_audioOutputByteArryaData.clear();
}
void AudioHandler::onInputNotify()
{
// Read available audio input data and send it
if (inputDevice)
{
// QByteArray audioData = inputDevice->readAll();
// qDebug() << "audioData" << audioData;
// QFile file("output.pcm");
// if(!file.open(QIODevice::WriteOnly | QIODevice::Append))
// {
// qDebug() << "unable to open file";
// }
// qDebug() << "file.write";
// file.write("aaaaaaaaa");
// file.write(audioData.data(), audioData.size());
// file.close();
QByteArray adtsHeaders = m_dataHandle.createADTSHeader(m_audioByteArrayData.size() + 7);
qDebug() << "onInputNotify" << "m_audioByteArrayData size:" << m_audioByteArrayData.size() ;
processAudioData(m_audioByteArrayData);
m_audioByteArrayData.clear();
}
}
void AudioHandler::onStartTalking()
{
qDebug() << "onStartTalking";
stopAudioOutput();
startAudioInput();
emit signalRequestTalk(CMDTYPE::CMDTALK);
}
void AudioHandler::onStopTalking()
{
qDebug() << "onStopTalking";
stopAudioInput();
startAudioOutput();
emit signalRequestTalk(CMDTYPE::CMDSTOPTALK);
// if(m_timer)
// {
// m_timer->stop();
// delete m_timer;
// m_timer = nullptr;
// }
}
void AudioHandler::onAudioDataformWebsocket(const QByteArray &audioOutputData)
{
m_audioOutputByteArryaData.append(audioOutputData);
qDebug() << "onAudioDataformWebsocket" << "m_audioByteArrayData size:" << m_audioOutputByteArryaData.size() ;
}
void AudioHandler::startAudioInput()
{
if (audioInputsource)
{
inputDevice->start();
audioInputsource->start(inputDevice.data());
m_inputTimer->start(10);
// connect(inputDevice, &QIODevice::readyRead, this, &AudioHandler::onNotify);
}
}
void AudioHandler::stopAudioInput()
{
if (audioInputsource)
{
m_inputTimer->stop();
audioInputsource->stop();
}
}
void AudioHandler::startAudioOutput()
{
qDebug() << "onstartListening";
outputDevice->start();
m_output = audioOutputsource->start();
m_outputTimer->start(10);
// QFile file("clip_0002.wav");
// audioOutputsource->start(&file);
}
void AudioHandler::stopAudioOutput()
{
qDebug() << "onstopListening";
m_outputTimer->stop();
outputDevice->stop();
audioOutputsource->stop();
}
// void AudioHandler::deviceChanged(QAudioDevice device, int index)
// {
// outputDevice->stop();
// audioOutputsource->stop();
// audioOutputsource->disconnect(this);
// initializeOutPutAudio(device);
// }
以上就是AudioHandler类通过调用AudioIputDevices和AudioOutputDevices两个类来完成音频输入和输出的核心类代码,这样大家应该就知道该如何在QT6中使用了。
若有疑问可留言评论。thanks。