QAudioOutput underrun issue on Realtime Play from Microphone with QAudioInput

自古美人都是妖i 提交于 2021-01-28 10:45:10

问题


Sometimes I am getting "underrun occured" from ALSA lib and that means the audioouput is not getting the values on time to play. Alsa then repeats the old buffer values on the speaker.

How can I avoid underruns on QAudioOuput? I am using Qt5.9.1 and ARM Based CPU running on Debian 8.

I tried to change the buffersize:

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

I get: buffersize 144000 period size 0

and after audiOutput->start() I get: buffersize 19200 period size 3840

Here is what I am doing:

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

Once audio data gets recorded I write to the QIODevice m_audioOutput the values from QIODevice m_audioInput.

So I think I have a timing issue sometimes and the audio interval for both is 1000ms before and after start(). Why cant I increase the buffer size? And how can I avoid underrun?


回答1:


Based on my experience with QAudioOutput, it's buffer is intended just to keep real-time playing, you can't for example drop 1 minute of sound directly to the QIODevice expecting it gets buffered and played sequentially, but it do not means that you can't buffer sound, just means that you need to do it by yourself.

I made the following example in "C-Style" to make an all-in-one solution, it buffers 1000 milliseconds (1 second) of the input before play it.

The event loop needs to be available to process the Qt SIGNALs.

In my tests, 1 second buffering is fairly enough to avoid under runs.

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


来源:https://stackoverflow.com/questions/52951545/qaudiooutput-underrun-issue-on-realtime-play-from-microphone-with-qaudioinput

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!