0
点赞
收藏
分享

微信扫一扫

RK3588平台开发系列讲解(视频篇)H.264码流结构介绍

天天天蓝loveyou 2024-02-05 阅读 17

 

目录

 一、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类

2.QAudioSink代替QAudioOutput类

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

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

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

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

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

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

举报

相关推荐

0 条评论