使用QAudioProbe从原始数据绘制波形 [英] Draw waveform from raw data using QAudioProbe

查看:222
本文介绍了使用QAudioProbe从原始数据绘制波形的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

For some strange reason QAudioRecorder::audioInputs() returns twice as much devices that I actually have

它们似乎是重复的,但不是真正的-好像它们提供了不同的样本,因为当我尝试播放前两个设备的录制音频时,它的发音速度是后者的两倍,而后两个设备则正常播放.

They're seem to be duplicated but not really - looks like they giving different samples, because when I'm trying to play recorded audio from first two devices it sounds twice as fast, when second two devices sounds normally.

这里是我的代码:

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

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

So, how should I parse the raw data to draw correct waveform?

推荐答案

首先检查缓冲区是否具有所需的样本类型,然后检查QAudioFormat

Firs of all check that the buffer hast exactly the sample type that you expect, to do it, check the QAudioFormat sampleType function. There are 3 alternatives:

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

这应该有助于您确定给定样本的正确投射.就我而言,作为不同的Qt示例,我使用:

This should help you to decide the correct cast for the given samples. In my case, as the different Qt examples, I use:

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

您可以使用此功能轻松对其进行标准化:

And them you can normalise it easily using this function:

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数组中以进行绘制.

Now you should iterate over the vector and divide by the peak that the functions returns, this will give a range of samples from [-1, 1], so save it in a QVector array to plot it.

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

To plot it, you have different alternatives, Qt introduce his own QtCharts module but you can still use QCustomPlot or Qwt. An example with 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 ,您将找到有关如何绘制信号的波形,频谱图,音调和不同属性的完整示例.

You can find a complete example in the Qt examples or check my little GitHub project, LogoSpeech Studio, you will find complete example of how to plot the wave form, spectrogram, pitch and different properties of a signal.

这篇关于使用QAudioProbe从原始数据绘制波形的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆