使用 QAudioInput 从麦克风实时播放 QAudioOutput 欠载问题

QAudioOutput underrun issue on Realtime Play from Microphone with QAudioInput

有时我从 ALSA 库中得到 "underrun occured",这意味着音频输出没有及时得到播放的值。 Alsa 然后在扬声器上重复旧的缓冲区值。

如何避免 QAudioOuput 上运行不足? 我在 Debian 8 上使用 Qt5.9.1 和基于 ARM CPU 运行。

我尝试更改缓冲区大小:

audioOutput->setBufferSize(144000);
qDebug()<<"buffersize "<<audioOutput->bufferSize()<<" period size" . 
<<audioOutput->periodSize();

I get: buffersize 144000 period size 0

audiOutput->start()之后我得到:buffersize 19200 period size 3840

这是我正在做的事情:

audioOutput->setBufferSize(144000);
qDebug()<<"buffersize "<<audioOutput->bufferSize()<<" period size" . 
<<audioOutput->periodSize();
m_audioInput = audioInput->start();
m_audioOutput = audioOutput->start();

qDebug()<<"buffersize "<<audioOutput->bufferSize()<<" period size"< 
<<audioOutput->periodSize();
connect(m_audioInput, SIGNAL(readyRead()), SLOT(readBufferSlot()));

录制音频数据后,我将 QIODevice m_audioInput 中的值写入 QIODevice m_audioOutput

所以我认为我有时会遇到时间问题,两者的音频间隔在 start() 前后都是 1000 毫秒。 为什么我不能增加缓冲区大小?我怎样才能避免欠载?

根据我使用 QAudioOutput 的经验,它的缓冲区只是为了保持实时播放,例如,您不能将 1 分钟的声音直接放到 QIODevice 期望它得到buffered 顺序播放,但不是说不能buffer sound,只是需要自己做。

我在 "C-Style" 中制作了以下示例来制作一个一体化解决方案,它在播放之前缓冲 1000 毫秒(1 秒)的输入。

事件循环需要可用以处理 Qt SIGNALs。

在我的测试中,1 秒缓冲足以避免欠载。

#include <QtCore>
#include <QtMultimedia>

#define MAX_BUFFERED_TIME 1000

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

struct AudioContext
{
    QAudioInput *m_audio_input;
    QIODevice *m_input_device;

    QAudioOutput *m_audio_output;
    QIODevice *m_output_device;

    QByteArray m_buffer;

    QAudioDeviceInfo m_input_device_info;
    QAudioDeviceInfo m_output_device_info;
    QAudioFormat m_format;

    int m_time_to_buffer;
    int m_max_size_to_buffer;

    int m_size_to_buffer;

    bool m_buffer_requested = true; //Needed
    bool m_play_called = false;
};

void play(AudioContext *ctx)
{
    //Set that last async call was triggered
    ctx->m_play_called = false;

    if (ctx->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
        ctx->m_buffer_requested = true;
        return;
    }
    else if (ctx->m_buffer.size() < ctx->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 (ctx->m_buffer_requested)
            return;
    }
    else
    {
        //Buffer is ready and data can be played
        ctx->m_buffer_requested = false;
    }

    int readlen = ctx->m_audio_output->periodSize();

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

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

        //Write data to the output device after the volume was applied
        if (len)
        {
            ctx->m_output_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--;
    }
}

void preplay(AudioContext *ctx)
{
    //Verify if exists a pending call to play function
    //If not, call the play function async
    if (!ctx->m_play_called)
    {
        ctx->m_play_called = true;
        QTimer::singleShot(0, [=]{play(ctx);});
    }
}

void init(AudioContext *ctx)
{
    /***** INITIALIZE INPUT *****/

    //Check if format is supported by the choosen input device
    if (!ctx->m_input_device_info.isFormatSupported(ctx->m_format))
    {
        qDebug() << "Format not supported by the input device";
        return;
    }

    //Initialize the audio input device
    ctx->m_audio_input = new QAudioInput(ctx->m_input_device_info, ctx->m_format, qApp);

    ctx->m_input_device = ctx->m_audio_input->start();

    if (!ctx->m_input_device)
    {
        qDebug() << "Failed to open input audio device";
        return;
    }

    //Call the readyReadPrivate function when data are available in the input device
    QObject::connect(ctx->m_input_device, &QIODevice::readyRead, [=]{
        //Read sound samples from input device to buffer
        ctx->m_buffer.append(ctx->m_input_device->readAll());
        preplay(ctx);
    });

    /***** INITIALIZE INPUT *****/

    /***** INITIALIZE OUTPUT *****/

    //Check if format is supported by the choosen output device
    if (!ctx->m_output_device_info.isFormatSupported(ctx->m_format))
    {
        qDebug() << "Format not supported by the output device";
        return;
    }

    int internal_buffer_size;

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

    //Initialize the audio output device
    ctx->m_audio_output = new QAudioOutput(ctx->m_output_device_info, ctx->m_format, qApp);
    //Increase the buffer size to enable higher sample rates
    ctx->m_audio_output->setBufferSize(internal_buffer_size);

    //Compute the size in bytes to be buffered based on the current format
    ctx->m_size_to_buffer = int(timeToSize(ctx->m_time_to_buffer, ctx->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
    ctx->m_max_size_to_buffer = ctx->m_size_to_buffer + int(timeToSize(MAX_BUFFERED_TIME, ctx->m_format));

    ctx->m_output_device = ctx->m_audio_output->start();

    if (!ctx->m_output_device)
    {
        qDebug() << "Failed to open output audio device";
        return;
    }

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

    //Timer that checks for too old data in the buffer
    QTimer *timer_verifier = new QTimer(qApp);
    QObject::connect(timer_verifier, &QTimer::timeout, [=]{
        if (ctx->m_buffer.size() >= ctx->m_max_size_to_buffer)
            ctx->m_buffer.clear();
    });
    timer_verifier->start(qMax(ctx->m_time_to_buffer, 10));

    /***** INITIALIZE OUTPUT *****/

    qDebug() << "Playing...";
}

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

    AudioContext ctx;

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

    ctx.m_format = format;

    ctx.m_input_device_info = QAudioDeviceInfo::defaultInputDevice();
    ctx.m_output_device_info = QAudioDeviceInfo::defaultOutputDevice();

    ctx.m_time_to_buffer = 1000;

    init(&ctx);

    return a.exec();
}