QAudioInput из массива - PullRequest
0 голосов
/ 28 апреля 2018

У меня есть устройство с микрофонами, которое подключается к моему компьютеру через Ethernet, и Qt не может воспринимать его как аудиоустройство, поэтому я получаю от него пакеты и помещаю их в QByteArray. Мне нужно воспроизвести эти пакеты из потока. Где-то в интернете я нашел решение почти такой же проблемы, но там использовался внутренний микрофон.

#include <QApplication>

#include <iostream>
#include <cassert>

#include <QCoreApplication>
#include <QAudioInput>
#include <QAudioOutput>
#include <QBuffer>

int main(int argc, char *argv[]) {
    QCoreApplication app(argc, argv);

    QBuffer rdBuff;
    QBuffer wrBuff;
    wrBuff.open(QBuffer::WriteOnly);
    rdBuff.open(QBuffer::ReadOnly);

    QObject::connect(&wrBuff, &QIODevice::bytesWritten, [&wrBuff, &rdBuff](qint64)
    {
        rdBuff.buffer().remove(0, rdBuff.pos());

        // set pointer to the beginning of the unread data
        const auto res = rdBuff.seek(0);
        assert(res);

        // write new data
        rdBuff.buffer().append(wrBuff.buffer());

        // remove all data that was already written
        wrBuff.buffer().clear();
        wrBuff.seek(0);
    });

    const auto decideAudioFormat = [](const QAudioDeviceInfo& devInfo)
    {
        QAudioFormat format;
        format.setSampleRate(8000);
        format.setChannelCount(1);
        format.setSampleSize(16);
        format.setCodec("audio/pcm");
        format.setByteOrder(QAudioFormat::LittleEndian);
        format.setSampleType(QAudioFormat::SignedInt);

        if (devInfo.isFormatSupported(format))
        {
            return format;
        }
        else
        {
            std::cerr << "Raw audio format not supported by backend, cannot play audio.\n";
            throw 0;
        }
    };

    QAudioInput audioInput(decideAudioFormat(QAudioDeviceInfo::defaultInputDevice()));
    QAudioOutput audioOutput(decideAudioFormat(QAudioDeviceInfo::defaultOutputDevice()));

    audioInput.start(&wrBuff);
    audioOutput.start(&rdBuff);

    return app.exec();
}

Это работает довольно хорошо, но мне нужно установить QByteArray в качестве источника QAudioInput. Есть ли возможное решение?

Ответы [ 2 ]

0 голосов
/ 02 мая 2018

Хорошо. Я попытался применить код (пока только воспроизведение) к моей ситуации, но есть проблема, что он воспроизводит данные один раз, если не генерируется «MorePackets ()», или не воспроизводится вообще, если «MorePackets ()» "происходит.

micserver.h

#ifndef MICSERVER_H
#define MICSERVER_H
//-----------------------------//
#include <QObject>
#include <QDebug>
#include <QTcpServer>
#include <QTcpSocket>
#include <QAudioFormat>
#include <QAudioOutput>
#include <QFile>
#include <QBuffer>
#include <QByteArray>
#include <QDataStream>

#include <iostream>

#include "audiooutput.h"
//-----------------------------//
class MicServer : public QObject {
    Q_OBJECT
public:
    explicit MicServer(QObject *parent = nullptr);

    QTcpSocket* Socket;
private:
    QTcpServer* Server;
    QAudioFormat format;
    AudioOutput output;

    QByteArray GetPackets(QTcpSocket* ClientP, int PacketsNumP);
signals:
    void MorePackets();

public slots:
    void NewConnection();
    void Play();
};
//-----------------------------//
#endif

micserver.cpp

#include "micserver.h"
//-----------------------------//
QByteArray MicServer :: GetPackets(QTcpSocket* ClientP, int PacketsNumP) {
    QBuffer BufferL;
    BufferL.open(QBuffer :: WriteOnly);
    QDataStream InputL(&BufferL);
    InputL.setVersion(QDataStream::Qt_5_10);

    QByteArray TempArray;

    for (int i = 0; i < PacketsNumP; i++) {
        ClientP -> waitForReadyRead(3000);
        InputL << ClientP -> readAll();
    }

    for (int i = 0; i < PacketsNumP; i++) {
        TempArray.push_back(BufferL.buffer().mid(76 + i * 1172, 256));
    }

    BufferL.close();

    return TempArray;
}
//-----------------------------//
MicServer :: MicServer(QObject *parent) : QObject(parent) {
    Server = new QTcpServer;

    if (!Server -> listen(QHostAddress :: Any, 49112)) {
        qDebug() << "Failed to launch server!";
    } else {
        qDebug() << "Server launched!";
    }

    connect(Server, SIGNAL(newConnection()), this, SLOT(NewConnection()));
    connect(Server, SIGNAL(newConnection()), this, SLOT(Play()));
    connect(this, SIGNAL(MorePackets()), this, SLOT(Play()));

    format.setSampleRate(16000);
    format.setChannelCount(1);
    format.setSampleSize(16);
    format.setCodec("audio/pcm");
    format.setByteOrder(QAudioFormat::LittleEndian);
    format.setSampleType(QAudioFormat::SignedInt);

    output.start(QAudioDeviceInfo::defaultOutputDevice(), format);
}

void MicServer :: NewConnection() {
    Socket = Server -> nextPendingConnection();
    qDebug() << "New connection!";

    qDebug() << Socket -> localAddress().toString();
}

void MicServer :: Play() {
    QByteArray audio_data = GetPackets(Socket, 250);

    QTimer timer;

    QObject::connect(&timer, &QTimer::timeout, [&]{
        qDebug() << "Writting" << audio_data.size() << "bytes";
        output.write(audio_data);
    });

    qDebug() << "Writting" << audio_data.size() << "bytes";
    output.write(audio_data);

    timer.start(2000);

    emit(MorePackets());
}
0 голосов
/ 01 мая 2018

Не уверен, что я прямо отвечаю на ваш вопрос. Но возможным решением является подача выходного аудиоустройства вручную (режим push) при поступлении новых данных.

Вы также можете использовать пользовательский (унаследованный от QFile) класс для записи звука, а при поступлении звука передает как файл, так и выходное аудиоустройство.

Вот пример:

AudioOutput.h:

#ifndef AUDIOOUTPUT_H
#define AUDIOOUTPUT_H

#include <QtCore>
#include <QtMultimedia>

#define MAX_BUFFERED_TIME 10*1000

static inline int timeToSize(int ms, const QAudioFormat &format)
{
    return ((format.channelCount() * (format.sampleSize() / 8) * format.sampleRate()) * ms / 1000);
}

class AudioOutput : public QObject
{
    Q_OBJECT
public:
    explicit AudioOutput(QObject *parent = nullptr);

public slots:
    bool start(const QAudioDeviceInfo &devinfo,
               const QAudioFormat &format,
               int time_to_buffer);

    void write(const QByteArray &data);

private slots:
    void verifyBuffer();
    void preplay();
    void play();

private:
    bool m_initialized;
    QAudioOutput *m_audio_output;
    QIODevice *m_device;
    QByteArray m_buffer;
    bool m_buffer_requested;
    bool m_play_called;
    int m_size_to_buffer;
    int m_time_to_buffer;
    int m_max_size_to_buffer;
    QAudioFormat m_format;
};

#endif // AUDIOOUTPUT_H

AudioRecorder.h:

#ifndef AUDIORECORDER_H
#define AUDIORECORDER_H

#include <QtCore>
#include <QtMultimedia>

class AudioRecorder : public QFile
{
    Q_OBJECT
public:
    explicit AudioRecorder(const QString &name, const QAudioFormat &format, QObject *parent = nullptr);
    ~AudioRecorder();

    using QFile::open;

public slots:
    bool open();
    qint64 write(const QByteArray &data);
    void close();

private:
    void writeHeader();
    bool hasSupportedFormat();
    QAudioFormat format;
};

#endif // AUDIORECORDER_H

AudioOutput.cpp:

#include "audiooutput.h"

AudioOutput::AudioOutput(QObject *parent) : QObject(parent)
{
    m_initialized = false;
    m_audio_output = nullptr;
    m_device = nullptr;
    m_buffer_requested = true;
    m_play_called = false;
    m_size_to_buffer = 0;
    m_time_to_buffer = 0;
    m_max_size_to_buffer = 0;
}

bool AudioOutput::start(const QAudioDeviceInfo &devinfo,
                        const QAudioFormat &format,
                        int time_to_buffer)
{
    if (!devinfo.isFormatSupported(format))
    {
        qDebug() << "Format not supported by output device";
        return m_initialized;
    }

    m_format = format;

    int internal_buffer_size;

    //Adjust internal buffer size
    if (format.sampleRate() >= 44100)
        internal_buffer_size = (1024 * 10) * format.channelCount();
    else if (format.sampleRate() >= 24000)
        internal_buffer_size = (1024 * 6) * format.channelCount();
    else
        internal_buffer_size = (1024 * 4) * format.channelCount();

    //Initialize the audio output device
    m_audio_output = new QAudioOutput(devinfo, format, this);
    //Increase the buffer size to enable higher sample rates
    m_audio_output->setBufferSize(internal_buffer_size);

    m_time_to_buffer = time_to_buffer;
    //Compute the size in bytes to be buffered based on the current format
    m_size_to_buffer = timeToSize(m_time_to_buffer, m_format);
    //Define a highest size that the buffer are allowed to have in the given time
    //This value is used to discard too old buffered data
    m_max_size_to_buffer = m_size_to_buffer + timeToSize(MAX_BUFFERED_TIME, m_format);

    m_device = m_audio_output->start();

    if (!m_device)
    {
        qDebug() << "Failed to open output audio device";
        return m_initialized;
    }

    //Timer that helps to keep playing data while it's available on the internal buffer
    QTimer *timer_play = new QTimer(this);
    timer_play->setTimerType(Qt::PreciseTimer);
    connect(timer_play, &QTimer::timeout, this, &AudioOutput::preplay);
    timer_play->start(10);

    //Timer that checks for too old data in the buffer
    QTimer *timer_verifier = new QTimer(this);
    connect(timer_verifier, &QTimer::timeout, this, &AudioOutput::verifyBuffer);
    timer_verifier->start(qMax(m_time_to_buffer, 10));

    m_initialized = true;

    return m_initialized;
}

void AudioOutput::verifyBuffer()
{
    if (m_buffer.size() >= m_max_size_to_buffer)
        m_buffer.clear();
}

void AudioOutput::write(const QByteArray &data)
{
    m_buffer.append(data);
    preplay();
}

void AudioOutput::preplay()
{
    if (!m_initialized)
        return;

    //Verify if exists a pending call to play function
    //If not, call the play function async
    if (!m_play_called)
    {
        m_play_called = true;
        QMetaObject::invokeMethod(this, "play", Qt::QueuedConnection);
    }
}

void AudioOutput::play()
{
    //Set that last async call was triggered
    m_play_called = false;

    if (m_buffer.isEmpty())
    {
        //If data is empty set that nothing should be played
        //until the buffer has at least the minimum buffered size already set
        m_buffer_requested = true;
        return;
    }
    else if (m_buffer.size() < m_size_to_buffer)
    {
        //If buffer doesn't contains enough data,
        //check if exists a already flag telling that the buffer comes
        //from a empty state and should not play anything until have the minimum data size
        if (m_buffer_requested)
            return;
    }
    else
    {
        //Buffer is ready and data can be played
        m_buffer_requested = false;
    }

    int readlen = m_audio_output->periodSize();

    int chunks = m_audio_output->bytesFree() / readlen;

    //Play data while it's available in the output device
    while (chunks)
    {
        //Get chunk from the buffer
        QByteArray samples = m_buffer.mid(0, readlen);
        int len = samples.size();
        m_buffer.remove(0, len);

        //Write data to the output device
        if (len)
            m_device->write(samples);

        //If chunk is smaller than the output chunk size, exit loop
        if (len != readlen)
            break;

        //Decrease the available number of chunks
        chunks--;
    }
}

AudioRecorder.cpp:

#include "audiorecorder.h"

AudioRecorder::AudioRecorder(const QString &name, const QAudioFormat &format, QObject *parent) : QFile(name, parent), format(format)
{

}

AudioRecorder::~AudioRecorder()
{
    if (!isOpen())
        return;

    close();
}

bool AudioRecorder::hasSupportedFormat()
{
    return (format.sampleSize() == 8
            && format.sampleType() == QAudioFormat::UnSignedInt)
            || (format.sampleSize() > 8
                && format.sampleType() == QAudioFormat::SignedInt
                && format.byteOrder() == QAudioFormat::LittleEndian);
}

bool AudioRecorder::open()
{
    if (!hasSupportedFormat())
    {
        setErrorString("Wav PCM supports only 8-bit unsigned samples "
                       "or 16-bit (or more) signed samples (in little endian)");
        return false;
    }
    else
    {
        if (!QFile::open(ReadWrite | Truncate))
            return false;
        writeHeader();
        return true;
    }
}

qint64 AudioRecorder::write(const QByteArray &data)
{
    return QFile::write(data);
}

void AudioRecorder::writeHeader()
{
    QDataStream out(this);
    out.setByteOrder(QDataStream::LittleEndian);

    // RIFF chunk
    out.writeRawData("RIFF", 4);
    out << quint32(0); // Placeholder for the RIFF chunk size (filled by close())
    out.writeRawData("WAVE", 4);

    // Format description chunk
    out.writeRawData("fmt ", 4);
    out << quint32(16); // "fmt " chunk size (always 16 for PCM)
    out << quint16(1); // data format (1 => PCM)
    out << quint16(format.channelCount());
    out << quint32(format.sampleRate());
    out << quint32(format.sampleRate() * format.channelCount()
                   * format.sampleSize() / 8 ); // bytes per second
    out << quint16(format.channelCount() * format.sampleSize() / 8); // Block align
    out << quint16(format.sampleSize()); // Significant Bits Per Sample

    // Data chunk
    out.writeRawData("data", 4);
    out << quint32(0); // Placeholder for the data chunk size (filled by close())

    Q_ASSERT(pos() == 44); // Must be 44 for WAV PCM
}

void AudioRecorder::close()
{
    // Fill the header size placeholders
    quint32 fileSize = size();

    QDataStream out(this);
    // Set the same ByteOrder like in writeHeader()
    out.setByteOrder(QDataStream::LittleEndian);
    // RIFF chunk size
    seek(4);
    out << quint32(fileSize - 8);

    // data chunk size
    seek(40);
    out << quint32(fileSize - 44);

    QFile::close();
}

main.cpp:

#include <QtCore>
#include "audiooutput.h"
#include "audiorecorder.h"
#include <signal.h>

QByteArray tone_generator()
{
    //Tone generator from http://www.cplusplus.com/forum/general/129827/

    const unsigned int samplerate = 8000;
    const unsigned short channels = 1;

    const double pi = M_PI;
    const qint16 amplitude = std::numeric_limits<qint16>::max() * 0.5;

    const unsigned short n_frequencies = 8;
    const unsigned short n_seconds_each = 1;

    float frequencies[n_frequencies] = {55.0, 110.0, 220.0, 440.0, 880.0, 1760.0, 3520.0, 7040.0};

    const int n_samples = channels * samplerate * n_frequencies * n_seconds_each;

    QVector<qint16> data;
    data.resize(n_samples);

    int index = n_samples / n_frequencies;

    for (unsigned short i = 0; i < n_frequencies; ++i)
    {
        float freq = frequencies[i];
        double d = (samplerate / freq);
        int c = 0;

        for (int j = index * i; j < index * (i + 1); j += 2)
        {
            double deg = 360.0 / d;
            data[j] = data[j + (channels - 1)] = qSin((c++ * deg) * pi / 180.0) * amplitude;
        }
    }

    return QByteArray((char*)data.data(), data.size() * sizeof(qint16));
}

void signalHandler(int signum)
{
    qDebug().nospace() << "Interrupt signal (" << signum << ") received.";

    qApp->exit();
}

int main(int argc, char *argv[])
{
    //Handle console close to ensure destructors being called
#ifdef Q_OS_WIN
    signal(SIGBREAK, signalHandler);
#else
    signal(SIGHUP, signalHandler);
#endif
    signal(SIGINT, signalHandler);

    QCoreApplication a(argc, argv);

    QAudioFormat format;
    format.setSampleRate(8000);
    format.setChannelCount(1);
    format.setSampleSize(16);
    format.setCodec("audio/pcm");
    format.setByteOrder(QAudioFormat::LittleEndian);
    format.setSampleType(QAudioFormat::SignedInt);

    AudioOutput output;

    AudioRecorder file("tone.wav", format);

    if (!output.start(QAudioDeviceInfo::defaultOutputDevice(), format, 10 * 1000)) //10 seconds of buffer
        return a.exec();

    if (!file.open())
    {
        qDebug() << qPrintable(file.errorString());
        return a.exec();
    }

    qDebug() << "Started!";

    QByteArray audio_data = tone_generator();

    QTimer timer;

    QObject::connect(&timer, &QTimer::timeout, [&]{
        qDebug() << "Writting" << audio_data.size() << "bytes";
        output.write(audio_data);
        file.write(audio_data);
    });

    qDebug() << "Writting" << audio_data.size() << "bytes";
    output.write(audio_data);
    file.write(audio_data);

    timer.start(8000); //8 seconds because we generated 8 seconds of sound

    return a.exec();
}
...