1

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

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.

Heres my code:

#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?

user1496491
  • 453
  • 2
  • 11
  • 23

1 Answers1

0

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

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

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.

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

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.

mohabouje
  • 3,867
  • 2
  • 14
  • 28