1

有时我从 ALSA 库中“发生欠载”,这意味着音频输出没有按时获得值来播放。然后 Alsa 在扬声器上重复旧的缓冲区值。

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

我试图改变缓冲区大小:

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

我得到:buffersize 144000 周期大小 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_audioOutput来自 QIODevice m_audioInput 的值。

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

4

1 回答 1

1

根据我的经验QAudioOutput,它的缓冲区旨在保持实时播放,例如,您不能将 1 分钟的声音直接放到QIODevice期望它被缓冲并按顺序播放,但这并不意味着您不能缓冲声音,只是意味着你需要自己做。

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

事件循环需要可用于处理 Qt SIGNAL

在我的测试中,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();
}
于 2018-10-24T07:39:54.857 回答