Исходя из моего опыта работы с QAudioOutput
, его буфер предназначен только для воспроизведения в реальном времени, например, вы не можете, например, сбросить 1 минуту звука прямо на QIODevice
, ожидая, что он буферизуется и воспроизводится последовательно, ноэто не означает, что вы не можете сохранить звук, просто означает, что вам нужно сделать это самостоятельно.
Я сделал следующий пример в «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();
}