Как нарисовать всю Waveform аудио сразу в Qt? - PullRequest
1 голос
/ 07 ноября 2019

Я пытаюсь нарисовать форму волны из аудио. До сих пор мне удавалось нарисовать его с помощью этого кода:

probe = new QAudioProbe;
probe->setSource(mediaPlayer);

connect(probe, SIGNAL(audioBufferProbed(QAudioBuffer)), this, SLOT(onAudioBufferProbed(QAudioBuffer)));

В моем слоте onAudioBufferProbed(QAudioBuffer):

...
const qint16 *data = audioBuffer.constData<qint16>();
int cnt = audioBuffer.sampleCount()/2;
qreal peak = GetPeakValue(audioBuffer.format());

for (int i = 0; i<cnt; i++)
    {
        double val = data[i]/peak;
        samples.append(val);
    }

QVector<double> x(samples.size());

    for (int i = 0; i<x.size(); i++)
        x[i] = i;

DrawWaveform(x, samples);

Когда я нажимаю кнопку воспроизведения, он рисует время от времениво время воспроизведения звука, следуя этому сигналу ofc, но как я могу его нарисовать немедленно (чтобы увидеть форму сигнала до того, как я воспроизведу аудио)?

1 Ответ

0 голосов
/ 07 ноября 2019

Вы можете использовать QAudioDecoder . Вы должны создать декодер и вызвать setSourceFileName . Затем позвоните start . Подключите bufferReady () к вашему слоту, затем read () буфер и делайте все до конца. Вы также можете объединить полученные фрагменты в QByteArray и воссоздать один QAudioBuffer для всего аудио. Затем вы можете нарезать буфер и получить значение выбора для желаемой длительности на основе вычисленных байтов в секунду.

Вот краткая реализация, которая по умолчанию задает продолжительность кадрами, полученными QAudioDecoder . Таким образом, в этом коде отсутствует механизм рефрейминга. Эти три метода (getBufferLevels как две перегрузки и getPeakValue) скопированы из Пример аудиозаписи из Qt и Я не включаю их здесь

//waveformgenerator.h
#ifndef WAVEFORMGENERATOR_H
#define WAVEFORMGENERATOR_H

#include <QObject>
#include <QAudioDecoder>
#include <QFile>
#include <QBuffer>

class WaveformGenerator : public QObject
{
    Q_OBJECT
public:
    explicit WaveformGenerator(QObject *parent = nullptr);
    template<class T>
    static QVector<qreal> getBufferLevels(const T *buffer, int frames, int channels);
    static QVector<qreal> getBufferLevels(const QAudioBuffer &buffer);
    static qreal getPeakValue(const QAudioFormat &format);

private:
    QAudioDecoder m_decoder;
    QFile file;
    QVector<QVector<qreal>> bufferLevels;

signals:
    void bufferLevelsReady(QVector<QVector<qreal>> bufferLevels);

public slots:
    void start(const QString& filepath);

private slots:
    void bufferReady();
    void sendBufferLevels();
    void handleError(QAudioDecoder::Error error);
};

#endif // WAVEFORMGENERATOR_H
//waveformgenerator.cpp
#include "waveformgenerator.h"

WaveformGenerator::WaveformGenerator(QObject *parent) : QObject(parent){
    connect(&m_decoder,&QAudioDecoder::bufferReady,this,&WaveformGenerator::bufferReady);
    connect(&m_decoder,&QAudioDecoder::finished,this,&WaveformGenerator::sendBufferLevels);
    connect(&m_decoder, QOverload<QAudioDecoder::Error>::of(&QAudioDecoder::error),this,&WaveformGenerator::handleError);
}

void WaveformGenerator::start(const QString &filepath){
    if (m_decoder.state()==QAudioDecoder::DecodingState)
        m_decoder.stop();
    bufferLevels.clear();

    file.setFileName(filepath);
    if (file.open(QFile::ReadOnly)){
        m_decoder.setSourceDevice(&file);
        m_decoder.start();
    }
    else
    {
        //TODO: handle this error where file could not be opened
    }
}

void WaveformGenerator::bufferReady(){
    auto current_buff = m_decoder.read();
    auto current_buff_level = getBufferLevels(current_buff);
    bufferLevels.append(current_buff_level);
}

void WaveformGenerator::sendBufferLevels(){
    file.close();
    emit bufferLevelsReady(bufferLevels);
    //Will emit a QVector of QVectors which every inner QVector
    //has two values for each channel from 0~1.0 as level
    //these pairs of values are generate for each frame
    //you can do your stuff like drawing by connecting an appropriate
    //slots to bufferLevelsReady signal
}

void WaveformGenerator::handleError(QAudioDecoder::Error error)
{
    if(error!=QAudioDecoder::NoError){
        file.close();
        bufferLevels.clear();
    }
}
//using
auto waveformView = new WaveformView (); // your fancy waveform view
auto wfg = new WaveFormGenerate();
//TODO: connect bufferLevelsReady signal to the object to draw those data
wfg.start(":/sample.mp3");
...