c++ - 使用 QAudioProbe 从原始数据中绘制波形

标签 c++ qt audio

出于某些奇怪的原因,QAudioRecorder::audioInputs() 返回的设备数量是我实际拥有设备数量的两倍 devices list

它们似乎是重复的,但实际上并非如此 - 看起来它们提供了不同的样本,因为当我尝试播放前两台设备录制的音频时,当后两台设备正常播放时,它的速度是原来的两倍。

这是我的代码:

#include "MicrophoneWidget.h"

#include <QLayout>
#include <sndfile.h>

MicrophoneWidget::MicrophoneWidget(QWidget *parent) : QWidget(parent)
{
    QAudioEncoderSettings settings;
    settings.setCodec("audio/PCM");
    settings.setQuality(QMultimedia::HighQuality);
    settings.setChannelCount(1);

    recorder = new QAudioRecorder(this);
    recorder->setEncodingSettings(settings);

    button = new QPushButton();
    button->setCheckable(true);

    devicesBox = new QComboBox();
    connect(devicesBox, SIGNAL(currentIndexChanged(int)), this, SLOT(onDeviceChanged(int)));
    for(const QString& device : recorder->audioInputs()) devicesBox->addItem(device, QVariant(device));

    label = new QLabel();

    connect(button, SIGNAL(toggled(bool)), this, SLOT(onButtonToggled(bool)));

    QVBoxLayout* layout = new QVBoxLayout();
    layout->addWidget(devicesBox);
    layout->addWidget(button);
    layout->addWidget(label);

    setLayout(layout);

    probe = new QAudioProbe();
    probe->setSource(recorder);
    connect(probe, SIGNAL(audioBufferProbed(QAudioBuffer)), this, SLOT(onAudioBufferProbed(QAudioBuffer)));

}

void MicrophoneWidget::resizeEvent(QResizeEvent*)
{
    pixmap = QPixmap(label->size());
}

void MicrophoneWidget::onAudioBufferProbed(QAudioBuffer buffer)
{
    qDebug() << buffer.byteCount() / buffer.sampleCount();

    const qint32 *data = buffer.constData<qint32>();

    pixmap.fill(Qt::transparent);
    painter.begin(&pixmap);

    int count = buffer.sampleCount() / 2;
    float xScale = (float)label->width() / count;
    float center = (float)label->height() / 2;

    for(int i = 0; i < count; i++) samples.push_back(data[i]);

    for(int i = 1; i < count; i++)
    {
        painter.drawLine(
            (i - 1) * xScale,
            center + ((float)(data[i-1]) / INT_MAX * center),
            i * xScale,
            center + ((float)(data[i]) / INT_MAX * center)
        );

    }

    painter.end();
    label->setPixmap(pixmap);
}

void MicrophoneWidget::onButtonToggled(bool toggled)
{
    if(toggled)
    {
        samples.clear();
        recorder->record();
    }
    else
    {
        recorder->stop();

        SF_INFO sndFileInfo;
        sndFileInfo.channels = 1;
        sndFileInfo.samplerate = 44100;
        sndFileInfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_32;

        QString filePath = "customWAV-" + QString::number(QDateTime::currentMSecsSinceEpoch()) + ".wav";

        SNDFILE* sndFile = sf_open(filePath.toStdString().c_str(), SFM_WRITE, &sndFileInfo);

        if(sndFile != nullptr)
        {
            sf_count_t count = sf_write_int(sndFile, samples.data(), samples.size());
            qDebug() << "Written " << count << " items; " << (samples.size() / sndFileInfo.samplerate) << " seconds";
        }

        sf_close(sndFile);
    }
}

void MicrophoneWidget::onDeviceChanged(int index)
{
    recorder->stop();
    recorder->setAudioInput(devicesBox->itemData(index).toString());
    if(button->isChecked())recorder->record();
}

那么,我该如何解析原始数据才能绘制出正确的波形呢?

最佳答案

首先检查缓冲区是否具有您期望的样本类型,为此,检查 QAudioFormat sampleType功能。有 3 种选择:

QAudioFormat::SignedInt,
QAudioFormat::UnSignedInt,      
QAudioFormat::Float

这应该可以帮助您确定给定样本的正确转换。就我而言,作为不同的 Qt 示例,我使用:

const qint16 *data = buffer.data<qint16>();

你可以使用这个函数轻松地将它们归一化:

qreal getPeakValue(const QAudioFormat& format)
{
    // Note: Only the most common sample formats are supported
    if (!format.isValid())
        return qreal(0);

    if (format.codec() != "audio/pcm")
        return qreal(0);

    switch (format.sampleType()) {
    case QAudioFormat::Unknown:
        break;
    case QAudioFormat::Float:
        if (format.sampleSize() != 32) // other sample formats are not supported
            return qreal(0);
        return qreal(1.00003);
    case QAudioFormat::SignedInt:
        if (format.sampleSize() == 32)
#ifdef Q_OS_WIN
            return qreal(INT_MAX);
#endif
#ifdef Q_OS_UNIX
            return qreal(SHRT_MAX);
#endif
        if (format.sampleSize() == 16)
            return qreal(SHRT_MAX);
        if (format.sampleSize() == 8)
            return qreal(CHAR_MAX);
        break;
    case QAudioFormat::UnSignedInt:
        if (format.sampleSize() == 32)
            return qreal(UINT_MAX);
        if (format.sampleSize() == 16)
            return qreal(USHRT_MAX);
        if (format.sampleSize() == 8)
            return qreal(UCHAR_MAX);
        break;
    }

    return qreal(0);
}

现在您应该遍历 vector 并除以函数返回的峰值,这将给出 [-1, 1] 范围内的样本,因此将其保存在 QVector 数组中以绘制它。

要绘制它,您有不同的选择,Qt 引入了自己的 QtCharts 模块,但您仍然可以使用 QCustomPlot 或 Qwt。 QCustomPlot 的示例:

QCPGraph myPlot =  ui->chart->addGraph();
myPlot->setData(xAxys.data(), recorded.data()); // init an X vector from 0 to the size of Y or whatever you want 
ui->chart->yAxis->setRange(QCPRange(-1,1)); // set the range
ui->chart->replot();

您可以在 Qt 示例中找到完整示例或查看我的 GitHub 小项目 LogoSpeech Studio ,您将找到有关如何绘制信号的波形、频谱图、音调和不同属性的完整示例。

关于c++ - 使用 QAudioProbe 从原始数据中绘制波形,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46947668/

相关文章:

c++ - QT QTextEdit setText 崩溃

javascript - 在页面上连续播放随机声音

audio - 使用数据估计音频中的时间位置?

javascript - Nedd 通过单击 HTML5 模板中的一个按钮来播放音频剪辑和图像动画

c++ - 无法在 VS2017 中包含 rapidjson 库

c++ - 进程的内存分析

javascript - 删除 QML 网格的子项

c++ - 调用模板祖 parent 的成员函数

c++ - 什么是构造函数继承?

c++ - 在 Qt 中打开新文件时,如何使应用程序不运行新实例?