【问题标题】:Draw waveform from raw data using QAudioProbe使用 QAudioProbe 从原始数据中绘制波形
【发布时间】:2017-10-26 18:45:47
【问题描述】:

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

它们似乎是重复的,但不是真的 - 看起来它们提供了不同的样本,因为当我尝试从前两个设备播放录制的音频时,它听起来快两倍,而后两个设备听起来正常。

这是我的代码:

#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();
}

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

【问题讨论】:

    标签: c++ qt audio


    【解决方案1】:

    首先检查缓冲区是否具有您期望的样本类型,要做到这一点,请检查 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,您将找到有关如何绘制信号的波形、频谱图、音高和不同属性的完整示例。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-06-29
      • 2011-06-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多