QT6调用音频输入输出(超详细)

 

目录

 一、QT6音频调用与QT5的区别

1.QAudioSource代替QAudioInput类

2.QAudioSink代替QAudioOutput类

二、音频操作中Push和Pull的区别

三、依托于Websocket实现实时对讲机

1.AudioIputDevices类

2.AudioOutputDevices类

3.实现的AudioHandler类完整内容


 本人实际是要完成一个类似于对讲机的通话小Demo,并且支持安卓,当然QT就是跨平台的,安卓的内容就不在这里叙述,后面可能会记录,功能就是两台客户端,通过网络websocket传递音频数据,做到实时通话。需要使用到QT的音频输入输出。但是网络上对QT6的音频输入输出不详细,故写此篇。

想寻找QT5实现的可以参考这些文章:

http://t.csdnimg.cn/00ABs

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()
qsizetypebufferSize() const
qsizetypebytesAvailable() const
qint64elapsedUSecs() const
QAudio::Errorerror() const
QAudioFormatformat() const
boolisNull() const
qint64processedUSecs() const
voidreset()
voidresume()
voidsetBufferSize(qsizetype value)
voidsetVolume(qreal volume)
voidstart(QIODevice *device)
QIODevice *start()
QAudio::Statestate() const
voidstop()
voidsuspend()
qrealvolume() const

信号

voidstateChanged(QAudio::State state)

详细说明:

您可以使用系统的默认音频输入设备构建音频输入。也可以使用特定的QAudioDevice创建QAudioSource。创建音频输入时,还应发送用于录制的QAudioFormat(有关详细信息,请参阅QAudioFormat类描述)。

 

要录制到文件,请执行以下操作:

 

QAudioSource允许您使用音频输入设备录制音频。此类的默认构造函数将使用系统默认音频设备,但您也可以为特定设备指定QAudioDevice。您还需要传入要录制的QAudioFormat。启动QAudioSource只需在打开QIODevice的情况下调用start()

​
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()插槽中停止录制。

void 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()信号以处理错误:

void 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 devicebreak;default:// ... other cases as appropriatebreak;}
}

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()
qsizetypebufferSize() const
qsizetypebytesFree() const
qint64elapsedUSecs() const
QAudio::Errorerror() const
QAudioFormatformat() const
boolisNull() const
qint64processedUSecs() const
voidreset()
voidresume()
voidsetBufferSize(qsizetype value)
voidsetVolume(qreal volume)
voidstart(QIODevice *device)
QIODevice *start()
QAudio::Statestate() const
voidstop()
voidsuspend()
qrealvolume() const

信号

voidstateChanged(QAudio::State state)

详细描述


您可以使用系统的默认音频输出设备构建音频输出。也可以使用特定的QAudioDevice创建QAudioSink。创建音频输出时,还应发送用于播放的QAudioFormat(有关详细信息,请参阅QAudioFormat类描述)。
播放文件:
开始播放音频流只需使用QIODevice调用start()即可。然后,QAudioSink将从io设备中获取所需的数据。因此,播放音频文件非常简单:

QFile 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()函数的情况。
文件播放完毕后,我们需要停止设备:

void 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()信号来检查错误:

void AudioOutputExample::handleStateChanged(QAudio::State newState)
{switch (newState) {case QAudio::IdleState:// Finished playing (no more data)AudioOutputExample::stopAudioOutput();break;case QAudio::StoppedState:// Stopped for other reasonsif (audio->error() != QAudio::NoError) {// Error handling}break;default:// ... other cases as appropriatebreak;}
}

同样可以看到这两个在上面两个类中的运用,可以自行去看看 QAudioSource and QAudioDevice.

看到这里有些同志已经会了,上面的范例主要就是示范对于音频文件的输入输出,加载在设备上就有了录音和读文件的功能。但是我要实现的实时对讲机不是这样的,不需要记录为文件,所以我要生成pcm格式的二进制数据然后传入传出,这里如果想了解音频格式的,或者是对音频格式有要求的可以去了解一下这些方面。

QT生成的音频数据格式

QT播放音频文件

FFMPEG音频库的引入

FFMPEG库对音频数据的转码

二、音频操作中Push和Pull的区别

网络上很多博主都没有说清楚甚至没有说其实输入和输出都有两种方法,就是Push和Pull方式,要根据实际功能选择使用,而且不要弄混了,我在项目中使用的时候,输入是用的push方式,输出用的pull方式,实现的是实时对讲机,它们有以下区别:

在Qt的QIODevice及其派生类中,有两种常见的数据读取和写入方式:pushpull。这两种方式是用于描述数据流如何被传输的。

  1. Push 模式:

    • 概念: 在 Push 模式中,数据的生产者(producer)主动推送数据到消费者(consumer)。生产者生成数据并将其推送到消费者。
    • 例子: 一个网络套接字(QTcpSocket)可以使用 Push 模式,当有新数据到达时,套接字发射 readyRead 信号,告知应用程序有数据可读。
    • 使用场景: 当数据的生成速率相对较快或者生产者的数据产生是不规律的时候,Push 模式通常更为合适。
  2. Pull 模式:

    • 概念: 在 Pull 模式中,数据的消费者主动从数据源拉取(pull)数据。消费者主动发起请求以获取数据。
    • 例子: 文件I/O 操作通常是 Pull 模式,你需要调用 read 函数来从文件中拉取数据。
    • 使用场景: 当数据的生成速率相对较慢或者数据生成是规律的时候,Pull 模式通常更为合适。

在 Qt 中,QIODevicereadwrite 方法是 Pull 模式的典型例子,而 QIODevicereadyRead 信号则是 Push 模式的例子。QIODevice 实际上可以同时支持 Push 和 Pull 操作。

在 Qt 中,QIODevice 是一个抽象类,而具体的实现类如 QFileQTcpSocket 等,根据其用途,可能更倾向于其中一种方式。你在使用这些类时,可以根据具体的需求选择适当的模式。

三、依托于Websocket实现实时对讲机

效果图:

WebSocket部分主要就是通过QT自带的WebSocket然后利用网络服务器帮着传输音频数据,就不贴出来了,可以用其他任何方式替代,主要是对音频处理的代码我会贴出。

1.AudioIputDevices类

实现了对音频输入的设备数据控制。

#include <QAudioSource>
#include <QMediaDevices>#include <QComboBox>
#include <QPushButton>
#include <QSlider>
#include <QWidget>#include <QPixmap>#include <QByteArray>
#include <QScopedPointer>class AudioIputDevices : public QIODevice
{Q_OBJECTpublic: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;
}

 音量实时显示条

class RenderArea : public QWidget
{Q_OBJECTpublic: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类

实现对音频输出的设备数据控制。

#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_OBJECTpublic: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 buildsm_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传来的音频数据

#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
#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 2qDebug("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 itif (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。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/251562.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【学习笔记】详解换根法(换根DP)

一.换根DP的概念 1.换根DP是什么&#xff1f; 换根DP&#xff0c;又叫二次扫描&#xff0c;是树形DP的一种。 2.换根DP能解决什么问题&#xff1f; 换根DP能解决不指定根结点&#xff0c;并且根节点的变化会对一些值产生影响的问题。例如子结点深度和、点权和等。如果要 暴力…

腾讯云幻兽帕鲁Palworld服务器价格表,2024年2月最新

腾讯云幻兽帕鲁服务器价格32元起&#xff0c;4核16G12M配置32元1个月、96元3个月、156元6个月、312元一年&#xff0c;支持4-8个玩家&#xff1b;8核32G22M幻兽帕鲁服务器115元1个月、345元3个月&#xff0c;支持10到20人在线开黑。腾讯云百科txybk.com分享更多4核8G12M、16核6…

vue3+threejs+koa可视化项目——模型文件上传(第四步)

文章目录 ⭐前言&#x1f496;往期node系列文章&#x1f496;threejs系列相关文章&#x1f496;vue3threejs系列 ⭐koa后端文件上传(koa-body)&#x1f496;自动创建目录&#x1f496;自定义目录上传&#x1f496;apifox自测上传接口 ⭐vue3前端上传模型文件&#x1f496; axio…

docker 构建个人博客网站

1、项目地址 https://gitee.com/hhll/blog-hangliang.git 2、打包docker镜像并上传docker hub 【1】注册docker hub账号https://hub.docker.com/ 【2】在docker hub建对应的仓库 【3】登录docker hub并打包上传前后端镜像 sudo docker login -u xxxx 密码 xxxxxx 后端&am…

skywalking链路追踪

skywalking 1.简介1.1 skywalking介绍1.2 链路追踪框架对比1.3 Skywalking架构 2 环境构建2.1 windows环境2.1.1 启动skywalking服务和UI界面2.1.2 在IDEA启动项目中使用Skywalking2.1.3 skywalking持久化 2.2 linux环境 1.简介 微服务架构已经是一个很通用的系统架构&#xf…

WordPress可以做企业官网吗?如何用wordpress建公司网站?

我们在国内看到很多个人博客网站都是使用WordPress搭建&#xff0c;但是企业官网的相对少一些&#xff0c;那么WordPress可以做企业官网吗&#xff1f;如何用wordpress建公司网站呢&#xff1f;下面boke112百科就跟大家简单说一下。 WordPress是一款免费开源的内容管理系统&am…

MacBook有必要装清理软件吗?CleanMyMac X v4.14.6 直装特别版 附安装教程

MacBook是苹果公司的一款高端笔记本电脑&#xff0c;但是&#xff0c;随着使用时间的增长&#xff0c;MacBook也会出现一些问题&#xff0c;比如运行缓慢、卡顿、垃圾文件堆积、磁盘空间不足等。这些问题不仅影响了用户的使用体验&#xff0c;也可能对MacBook的寿命和安全性造成…

云原生数据库 GaiaDB 的核心技术演进和解析

导读 在越来越强调云原生的环境下&#xff0c;存算分离作为一种新的架构理念&#xff0c;已经是大势所趋。新的技术架构带来新的问题和挑战&#xff0c;百度智能云的云原生数据库 GaiaDB 采用 Quorum 分布式协议、高性能网络、高可靠分布式存储引擎等技术实现更高的性能和可用性…

React 中实现拖拽功能-插件 react-beautiful-dnd

拖拽功能在平时开发中是很常见的&#xff0c;这篇文章主要使用react-beautiful-dnd插件实现此功能。 非常好用&#xff0c;附上GitHub地址&#xff1a;https://github.com/atlassian/react-beautiful-dnd 安装及引入 // 1.引入 # yarn yarn add react-beautiful-dnd# npm npm…

2024美赛数学建模C题完整论文教学(含十几个处理后数据表格及python代码)

大家好呀&#xff0c;从发布赛题一直到现在&#xff0c;总算完成了数学建模美赛本次C题目Momentum in Tennis完整的成品论文。 本论文可以保证原创&#xff0c;保证高质量。绝不是随便引用一大堆模型和代码复制粘贴进来完全没有应用糊弄人的垃圾半成品论文。 C论文共49页&…

AI智能分析+明厨亮灶智慧管理平台助力“舌尖上的安全”

春节是中国最重要的传统节日之一&#xff0c;在春节期间&#xff0c;人们聚餐需求激增&#xff0c;餐饮业也迎来了高峰期。在这个时期&#xff0c;餐饮企业需要更加注重食品安全和卫生质量&#xff0c;以保证消费者的健康和权益&#xff0c;明厨亮灶智慧管理成为了餐饮业中备受…

【NTN 卫星通信】基于NTN的多3GPP连接应用场景

1 概述 同时聚合两条3GPP接入链路&#xff0c;其中一条为非地面网络&#xff0c;可以提供以下5G业务使能&#xff0c;尤其适用于带宽有限或接入链路不可靠的服务不足地区:   -扩展流动宽频   -超可靠的服务通信 如技术报告38.821所述&#xff0c;若干服务场景(例如在偏远地…

Unity 图片不改变比例适配屏幕

Unity 图片不改变比例适配屏幕 前言项目场景布置代码编写添加并设置脚本效果 前言 遇到一个要让图片适应相机大小&#xff0c;填满屏幕&#xff0c;但不改变图片比例的需求&#xff0c;记录一下。 项目 场景布置 代码编写 创建AdaptiveImageBackground脚本 using System.C…

电脑文件误删除怎么办?8个恢复软件解决电脑磁盘数据可能的误删

您是否刚刚发现您的电脑磁盘数据丢失了&#xff1f;不要绝望&#xff01;无论分区是否损坏、意外格式化或配置错误&#xff0c;存储在其上的文件都不一定会丢失到数字深渊。 我们已经卷起袖子&#xff0c;深入研究电脑分区恢复软件的广阔领域&#xff0c;为您带来一系列最有效…

精准医疗:DTAS尺寸公差仿真与尺寸链计算软件助力医疗器械制造的成功案例

随着医疗器械行业的不断发展和进步&#xff0c;对产品质量和安全性的要求也越来越高。而公差仿真软件作为一种能够帮助设计师和工程师优化产品设计并确保产品质量的工具&#xff0c;在医疗器械行业中的应用也变得越来越重要。 本文将探讨DTAS公差仿真软件在医疗器械中的应用&a…

【百度Apollo】探索创新之路:深入了解Apollo开放平台

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《linux深造日志》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! ⛳️ 推荐 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下…

前端面试题:二叉树广度和深度遍历

试题&#xff1a;有如下树形数据结构&#xff0c;通过JavaScript对二叉树实现深度遍历和广度遍历 广度遍历&#xff1a; 通过JavaScript数组模拟栈的方式实现&#xff0c;首先节点入栈&#xff0c;然后从栈顶取出节点&#xff0c;放入数组&#xff0c;然后对取出的节点进行遍历…

Haas 开发板连接阿里云上传温湿度和电池电压

目录 一、在阿里云上创建一个产品 二、开发环境的介绍 三、创建wifi示例 四、编写SI7006和ADC驱动 五、wifi配网 六、主要源码 七、查看实现结果 一、在阿里云上创建一个产品 登录自己的阿里云账号&#xff0c; 应该支付宝&#xff0c;淘宝账号都是可以的。 接着根据需求…

k8s学习-Kubernetes的包管理器Helm

1.1 为何需要Helm Kubernetes能够很好地组织和编排容器&#xff0c;但它缺少⼀个更高层次的应用打包工具&#xff0c;而Helm就是来干这件事的。 先来看个例子。 比如对于⼀个MySQL服务&#xff0c;Kubernetes需要部署下面这些对象&#xff1a; &#xff08;1&#xff09;Serv…

云计算、Docker、K8S问题

1 云计算 云计算作为一种新兴技术&#xff0c;已经在现代社会中得到了广泛应用。它以其高效、灵活和可扩展特性&#xff0c;成为了许多企业和组织在数据处理和存储方面的首选方案。 1.1 什么是云计算&#xff1f;它有哪些特点&#xff1f; 云计算是一种通过网络提供计算资源…