使用 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 SIGNAL
s。
在我的测试中,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();
}
有时我从 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 SIGNAL
s。
在我的测试中,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();
}