使用QAudioProbe从原始数据绘制波形

时间:2017-10-26 06:56:16

标签: 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();
}

那么,我应该如何解析原始数据以绘制正确的波形?

1 个答案:

答案 0 :(得分:0)

首先检查缓冲区是否具有您期望的样本类型,然后检查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);
}

现在你应该迭代向量并除以函数返回的峰值,这将给出[-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,您将找到如何绘制波形,频谱图,音高和信号的不同属性的完整示例。 / p>