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 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()
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 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及其派生类中,有两种常见的数据读取和写入方式: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_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;
}

 音量实时显示条

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类

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

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

#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 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。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

作为一个27岁的人,学习单片机然后进入这行的可能性大吗?

作为一个27岁的人&#xff0c;学习单片机然后进入这行的可能性大吗&#xff1f;有c语言基础。&#xff1f; 在开始前我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「c语言的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“…

python-分享篇-小猪佩奇

文章目录 代码效果 代码 # coding:utf-8 import turtle as tt.pensize(4) t.hideturtle() t.colormode(255) t.color((255,155,192),"pink") t.setup(840,500) t.speed(10)#鼻子 t.pu() t.goto(-100,100) t.pd() t.seth(-30) t.begin_fill() a0.4 for i in range(12…

python爬虫概念及介绍

1. 什么是互联网爬虫&#xff1f; 解释 1 &#xff1a;通过一个程序&#xff0c;根据 Url ( http : // www . taobao . com ) 进行爬取网页&#xff0c;获取有用信息 解释 2&#xff1a;使用程序模拟浏览器&#xff0c;去向服务器发送请求&#xff0c;获取响应信息 2. 爬虫核…

C++病毒【永久性】

我最近发现&#xff0c;我2024年后就再也没有更新过 C#沙雕程序了。 今天我想通了&#xff0c;我要再更几期关于C#沙雕程序的文章。 开始做&#xff01; 这一次就直接上代码蚌&#xff01; 不用任何特定头文件。 #include <bits/stdc.h> #include <iostream> #…

【Java基础_02】Java变量

【Java基础_02】Java变量、运算符、程序控制结构 文章目录 1 变量1.1 程序中“”号的使用1.2 数据类型1.3 整数类型1.3.1 整数类型的分类1.3.2 整型的使用细节 1.4 浮点类型1.4.1 浮点型的分类1.4.2 浮点类型使用细节 1.5 字符类型1.5.1 字符类型使用细节1.5.2 字符类型本质1.5…

幻兽帕鲁专用服务器,多人游戏(专用服务器)搭建

玩转幻兽帕鲁服务器&#xff0c;阿里云推出新手0基础一键部署幻兽帕鲁服务器教程&#xff0c;傻瓜式一键部署&#xff0c;3分钟即可成功创建一台Palworld专属服务器&#xff0c;成本仅需26元&#xff0c;阿里云服务器网aliyunfuwuqi.com分享2024年新版基于阿里云搭建幻兽帕鲁服…

网络安全产品之准入控制系统

文章目录 一、什么是准入控制系统二、准入控制系统的主要功能1. 接入设备的身份认证2. 接入设备的安全性检查 三、准入控制系统的工作原理四、准入控制系统的特点五、准入控制系统的部署方式1. 网关模式2. 控制旁路模式 六、准入控制系统的应用场景七、企业如何利用准入控制系统…

程序员为什么不喜欢关电脑,这回答很霸道!

在大家的生活中&#xff0c;经常会发现这样一个现象&#xff1a;程序员经常不关电脑。 至于程序员不关电脑的原因&#xff0c;众说纷纭。 其中这样的一个程序员&#xff0c;他的回答很霸道&#xff1a; “因为我是程序员&#xff0c;我有权选择不关电脑。我需要在任何时候都能够…

python创建udf函数步骤

一、目标 实现一个函数&#xff0c;传入两个datetime类型的参数&#xff0c;返回double类型的工作日天数 二、思路 如何计算差值&#xff1f; 如果开始时间和结束时间在同一天&#xff1a;实现同 datediff(end, start, ‘ss’) / 86400.0 如果开始时间和结束时间在不同天&am…

【教程】Python代码混淆工具,Python源代码保密、加密、混淆

引言 Python作为一种高级脚本语言&#xff0c;便捷的语法和丰富的库使它成为众多开发者的首选。然而&#xff0c;有时候我们希望保护我们的Python源代码&#xff0c;避免被他人轻易获取和篡改。为了实现这一目标&#xff0c;我们可以采取代码混淆的技术手段。本文将介绍Python…

vue3项目打包移除console.log()打印

一、安装terser&#xff08;不安装打包会报错&#xff0c;安装过后无需引入直接使用&#xff09; npm install terser --save-dev二、在vite.config.ts里面使用 build: {minify: "terser",terserOptions:{compress:{drop_console: true,drop_debugger: true,}}}

Vue3.0(三):Vue组件化深入理解

Vue组件化深入理解 生命周期 每个组件都可能经历 创建、挂载、更新、卸载等一系列过程 在每个阶段&#xff0c;我们可能会添加一些属于自己的逻辑代码 在Vue中&#xff0c;生命周期通过生命周期函数实现 生命周期函数实际上就是回调函数&#xff0c;在某个时间会被Vue源码调…

零售的这个销售模式,太震撼了!

随着科技的飞速发展&#xff0c;新零售模式正逐渐改变着传统零售业的面貌。在这个数字化时代&#xff0c;自动售货机作为新零售的一部分&#xff0c;正以其便捷、智能的特性&#xff0c;为商家和消费者带来全新的购物体验。 客户案例 智能便利店 传统便利店运营面临高租金、人…

【STM32+HAL库+CubeMX】UART轮询收发、中断收发、DMA收发方法及空闲中断详解

&#xff08;转载&#xff09;原文链接&#xff1a;https://blog.csdn.net/qq_39344192/article/details/131470735 1. 什么是UART&#xff1f; UART是一种异步串行通信接口&#xff0c;常用于通过串口与外部设备进行通信。它通过发送和接收数据帧来实现数据传输&#xff0c;使…

保护个人信息安全,避免成为“互联网中的裸泳者”

⚽️ 一、互联网中的裸泳者&#x1f3c0; 二、代理 IP 的应用 - 解锁无限可能⚾️ 三、代理 ip 的几种类型 3.1 动态住宅代理&#xff08;Rotating Residential Proxy&#xff09;3.2 静态住宅代理&#xff08;Static Residential Proxy&#xff09;3.3 动态长效ISP&#xff08…

Nucleosome, Recombinant Human, H2BK120ub1 dNuc, Biotinylated

EpiCypher&#xff08;国内授权代理商欣博盛生物&#xff09;是一家为表观遗传学和染色质生物学研究提供高质量试剂和工具的专业制造商。EpiCypher生产的在E. coli中表达的重组人单核小体(组蛋白H2A、H2B、H3和H4各2个;accession numbers:H2A-P04908;H2B-O60814;H3.1-P68431;H4…

Python实现排序算法

目录 一&#xff1a;快速排序 二&#xff1a;合并排序 三&#xff1a;冒泡排序 四&#xff1a;插入排序 五&#xff1a;选择排序 一&#xff1a;快速排序 def quicksort(arr): if len(arr) < 1: return arr pivot arr[len(arr) // 2] le…

【Docker】入门到精通(常用命令解读)

一、准备工作 1.配置Docker的yum库 首先要安装一个yum工具 yum install -y yum-utils安装成功后&#xff0c;执行命令&#xff0c;配置Docker的yum源&#xff1a; yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo2.安装Docker 执…

python创建pdf文件

目录 一&#xff1a;使用reportlab库 二&#xff1a;使用使pdf库 在Python中生成PDF文件可以使用多种库&#xff0c;其中最常用的是reportlab和fpdf。以下是使用这两个库生成PDF文件的示例代码&#xff1a; 一&#xff1a;使用reportlab库 1&#xff1a;写入文字信息 from r…

keil边框的背景色更改

网上有很多keil换背景色的帖子&#xff0c;效果如图&#xff1a; 可以看到&#xff0c;虽然代码区背景色设置为了黑色&#xff0c;但是上方、左侧边、下方的颜色并没有改变&#xff0c;看起来还是很不舒服。 机缘巧合&#xff0c;我想把Windows系统颜色设置为护眼的颜色&#x…