2018-09-13 13:23:06 +00:00
|
|
|
#include "sdl.h"
|
2018-06-28 08:44:43 +00:00
|
|
|
|
2018-06-24 01:46:59 +00:00
|
|
|
#include <Limelight.h>
|
|
|
|
#include <SDL.h>
|
|
|
|
|
2018-09-13 14:46:01 +00:00
|
|
|
#include <QAudioDeviceInfo>
|
2018-09-13 13:23:06 +00:00
|
|
|
#include <QtGlobal>
|
|
|
|
|
2018-07-16 03:17:08 +00:00
|
|
|
#define MIN_QUEUED_FRAMES 2
|
|
|
|
#define MAX_QUEUED_FRAMES 4
|
2018-07-16 04:43:43 +00:00
|
|
|
#define STOP_THE_WORLD_LIMIT 20
|
2018-07-16 03:17:08 +00:00
|
|
|
#define DROP_RATIO_DENOM 32
|
2018-06-24 01:46:59 +00:00
|
|
|
|
2018-09-13 14:46:01 +00:00
|
|
|
// Detecting this with SDL is quite problematic, so we'll use Qt's
|
|
|
|
// multimedia framework to do so. It appears to be actually
|
|
|
|
// accurate on Linux and macOS, unlike using SDL and relying
|
|
|
|
// on a channel change in the format received.
|
2018-09-13 13:23:06 +00:00
|
|
|
int SdlAudioRenderer::detectAudioConfiguration()
|
2018-06-24 01:46:59 +00:00
|
|
|
{
|
2018-09-13 14:46:01 +00:00
|
|
|
int preferredChannelCount = QAudioDeviceInfo::defaultOutputDevice().preferredFormat().channelCount();
|
2018-06-24 01:46:59 +00:00
|
|
|
|
|
|
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
2018-09-13 14:46:01 +00:00
|
|
|
"Audio output device prefers %d channel configuration",
|
|
|
|
preferredChannelCount);
|
2018-06-24 01:46:59 +00:00
|
|
|
|
2018-09-13 14:46:01 +00:00
|
|
|
// We can better downmix 5.1 to quad than we can upmix stereo
|
|
|
|
if (preferredChannelCount > 2) {
|
|
|
|
return AUDIO_CONFIGURATION_51_SURROUND;
|
2018-06-24 01:46:59 +00:00
|
|
|
}
|
|
|
|
else {
|
2018-09-13 14:46:01 +00:00
|
|
|
return AUDIO_CONFIGURATION_STEREO;
|
2018-06-24 01:46:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-13 13:23:06 +00:00
|
|
|
bool SdlAudioRenderer::testAudio(int audioConfiguration)
|
2018-08-31 04:09:31 +00:00
|
|
|
{
|
|
|
|
SDL_AudioSpec want, have;
|
|
|
|
SDL_AudioDeviceID dev;
|
|
|
|
|
|
|
|
SDL_zero(want);
|
|
|
|
want.freq = 48000;
|
|
|
|
want.format = AUDIO_S16;
|
|
|
|
want.samples = SAMPLES_PER_FRAME;
|
|
|
|
|
|
|
|
switch (audioConfiguration) {
|
|
|
|
case AUDIO_CONFIGURATION_STEREO:
|
|
|
|
want.channels = 2;
|
|
|
|
break;
|
|
|
|
case AUDIO_CONFIGURATION_51_SURROUND:
|
|
|
|
want.channels = 6;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
SDL_assert(false);
|
2018-09-13 14:46:01 +00:00
|
|
|
return false;
|
2018-08-31 04:09:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Test audio device for functionality
|
|
|
|
dev = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0);
|
|
|
|
if (dev == 0) {
|
|
|
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
|
|
"Audio test - Failed to open audio device: %s",
|
|
|
|
SDL_GetError());
|
2018-09-13 14:46:01 +00:00
|
|
|
return false;
|
2018-08-31 04:09:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
SDL_CloseAudioDevice(dev);
|
|
|
|
|
|
|
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
|
|
"Audio test - Successful with %d channels",
|
|
|
|
want.channels);
|
2018-09-13 14:46:01 +00:00
|
|
|
return true;
|
2018-08-31 04:09:31 +00:00
|
|
|
}
|
|
|
|
|
2018-09-13 13:23:06 +00:00
|
|
|
SdlAudioRenderer::SdlAudioRenderer()
|
|
|
|
: m_AudioDevice(0),
|
|
|
|
m_ChannelCount(0),
|
|
|
|
m_PendingDrops(0),
|
|
|
|
m_PendingHardDrops(0),
|
|
|
|
m_SampleIndex(0),
|
|
|
|
m_BaselinePendingData(0)
|
|
|
|
{
|
2018-07-21 01:15:46 +00:00
|
|
|
SDL_assert(!SDL_WasInit(SDL_INIT_AUDIO));
|
|
|
|
if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) {
|
|
|
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
|
|
"SDL_InitSubSystem(SDL_INIT_AUDIO) failed: %s",
|
|
|
|
SDL_GetError());
|
2018-09-13 14:46:01 +00:00
|
|
|
SDL_assert(SDL_WasInit(SDL_INIT_AUDIO));
|
2018-07-21 01:15:46 +00:00
|
|
|
}
|
2018-09-13 14:46:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool SdlAudioRenderer::prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* opusConfig)
|
|
|
|
{
|
|
|
|
SDL_AudioSpec want, have;
|
2018-07-21 01:15:46 +00:00
|
|
|
|
2018-06-24 01:46:59 +00:00
|
|
|
SDL_zero(want);
|
|
|
|
want.freq = opusConfig->sampleRate;
|
|
|
|
want.format = AUDIO_S16;
|
|
|
|
want.channels = opusConfig->channelCount;
|
2018-07-16 03:17:08 +00:00
|
|
|
|
|
|
|
// This is supposed to be a power of 2, but our
|
|
|
|
// frames contain a non-power of 2 number of samples,
|
|
|
|
// so the slop would require buffering another full frame.
|
|
|
|
// Specifying non-Po2 seems to work for our supported platforms.
|
|
|
|
want.samples = SAMPLES_PER_FRAME;
|
2018-06-24 01:46:59 +00:00
|
|
|
|
2018-09-13 13:23:06 +00:00
|
|
|
m_AudioDevice = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0);
|
|
|
|
if (m_AudioDevice == 0) {
|
2018-06-24 01:46:59 +00:00
|
|
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
|
|
"Failed to open audio device: %s",
|
|
|
|
SDL_GetError());
|
2018-07-21 01:15:46 +00:00
|
|
|
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
2018-09-13 14:46:01 +00:00
|
|
|
return false;
|
2018-06-24 01:46:59 +00:00
|
|
|
}
|
|
|
|
|
2018-07-16 04:43:43 +00:00
|
|
|
// SDL counts pending samples in the queued
|
|
|
|
// audio size using the WASAPI backend. This
|
|
|
|
// includes silence, which can throw off our
|
|
|
|
// pending data count. Get a baseline so we
|
|
|
|
// can exclude that data.
|
2018-09-13 13:23:06 +00:00
|
|
|
m_BaselinePendingData = 0;
|
2018-07-22 00:00:09 +00:00
|
|
|
#ifdef Q_OS_WIN32
|
2018-07-16 04:43:43 +00:00
|
|
|
for (int i = 0; i < 100; i++) {
|
2018-09-13 13:23:06 +00:00
|
|
|
m_BaselinePendingData = qMax(m_BaselinePendingData, SDL_GetQueuedAudioSize(m_AudioDevice));
|
2018-07-16 04:43:43 +00:00
|
|
|
SDL_Delay(10);
|
|
|
|
}
|
|
|
|
#endif
|
2018-09-13 13:23:06 +00:00
|
|
|
m_BaselinePendingData *= 2;
|
2018-07-16 04:43:43 +00:00
|
|
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
|
|
"Baseline pending audio data: %d bytes",
|
2018-09-13 13:23:06 +00:00
|
|
|
m_BaselinePendingData);
|
2018-07-16 04:43:43 +00:00
|
|
|
|
2018-09-13 13:23:06 +00:00
|
|
|
m_ChannelCount = opusConfig->channelCount;
|
|
|
|
m_SampleIndex = 0;
|
|
|
|
m_PendingDrops = m_PendingHardDrops = 0;
|
2018-06-24 01:46:59 +00:00
|
|
|
|
2018-09-13 13:23:06 +00:00
|
|
|
// Start playback
|
|
|
|
SDL_PauseAudioDevice(m_AudioDevice, 0);
|
2018-07-28 23:06:26 +00:00
|
|
|
|
2018-09-13 14:46:01 +00:00
|
|
|
return true;
|
2018-06-24 01:46:59 +00:00
|
|
|
}
|
|
|
|
|
2018-09-13 13:23:06 +00:00
|
|
|
SdlAudioRenderer::~SdlAudioRenderer()
|
2018-06-24 01:46:59 +00:00
|
|
|
{
|
2018-09-13 14:46:01 +00:00
|
|
|
if (m_AudioDevice != 0) {
|
|
|
|
// Stop playback
|
|
|
|
SDL_PauseAudioDevice(m_AudioDevice, 1);
|
|
|
|
SDL_CloseAudioDevice(m_AudioDevice);
|
|
|
|
}
|
2018-07-21 01:15:46 +00:00
|
|
|
|
|
|
|
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
|
|
|
SDL_assert(!SDL_WasInit(SDL_INIT_AUDIO));
|
2018-06-24 01:46:59 +00:00
|
|
|
}
|
|
|
|
|
2018-09-13 13:23:06 +00:00
|
|
|
void SdlAudioRenderer::submitAudio(short* audioBuffer, int audioSize)
|
2018-06-24 01:46:59 +00:00
|
|
|
{
|
2018-09-13 13:23:06 +00:00
|
|
|
m_SampleIndex++;
|
2018-07-16 03:17:08 +00:00
|
|
|
|
2018-09-13 21:09:03 +00:00
|
|
|
Uint32 queuedAudio = qMax(SDL_GetQueuedAudioSize(m_AudioDevice) - m_BaselinePendingData, 0U);
|
2018-09-13 13:23:06 +00:00
|
|
|
Uint32 framesQueued = queuedAudio / (SAMPLES_PER_FRAME * m_ChannelCount * sizeof(short));
|
2018-07-16 03:17:08 +00:00
|
|
|
|
2018-08-15 00:39:29 +00:00
|
|
|
// We must check this prior to the below checks to ensure we don't
|
2018-09-13 13:23:06 +00:00
|
|
|
// underflow if framesQueued - m_PendingHardDrops < 0.
|
2018-08-15 00:39:29 +00:00
|
|
|
if (framesQueued <= MIN_QUEUED_FRAMES) {
|
2018-09-13 13:23:06 +00:00
|
|
|
m_PendingDrops = m_PendingHardDrops = 0;
|
2018-08-15 00:39:29 +00:00
|
|
|
}
|
2018-09-13 21:09:03 +00:00
|
|
|
// Pend enough drops to get us back to MIN_QUEUED_FRAMES, checking first
|
|
|
|
// to ensure we don't underflow.
|
|
|
|
else if (framesQueued > m_PendingHardDrops &&
|
|
|
|
framesQueued - m_PendingHardDrops > STOP_THE_WORLD_LIMIT) {
|
2018-09-13 13:23:06 +00:00
|
|
|
m_PendingHardDrops = framesQueued - MIN_QUEUED_FRAMES;
|
2018-07-16 04:43:43 +00:00
|
|
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
|
|
"Pending hard drop of %u audio frames",
|
2018-09-13 13:23:06 +00:00
|
|
|
m_PendingHardDrops);
|
2018-07-16 04:43:43 +00:00
|
|
|
}
|
2018-09-13 21:09:03 +00:00
|
|
|
// If we're under the stop the world limit, we can drop samples
|
|
|
|
// gracefully over the next little while.
|
|
|
|
else if (framesQueued > m_PendingHardDrops + m_PendingDrops &&
|
|
|
|
framesQueued - m_PendingHardDrops - m_PendingDrops > MAX_QUEUED_FRAMES) {
|
2018-09-13 13:23:06 +00:00
|
|
|
m_PendingDrops = framesQueued - MIN_QUEUED_FRAMES;
|
2018-07-16 03:17:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Determine if this frame should be dropped
|
2018-09-13 13:23:06 +00:00
|
|
|
if (m_PendingHardDrops != 0) {
|
2018-07-16 04:43:43 +00:00
|
|
|
// Hard drops happen all at once to forcefully
|
|
|
|
// resync with the source.
|
2018-09-13 13:23:06 +00:00
|
|
|
m_PendingHardDrops--;
|
2018-07-16 04:43:43 +00:00
|
|
|
return;
|
2018-07-16 03:17:08 +00:00
|
|
|
}
|
2018-09-13 13:23:06 +00:00
|
|
|
else if (m_PendingDrops != 0 && m_SampleIndex % DROP_RATIO_DENOM == 0) {
|
2018-07-16 04:43:43 +00:00
|
|
|
// Normal drops are interspersed with the audio data
|
|
|
|
// to hide the glitches.
|
2018-09-13 13:23:06 +00:00
|
|
|
m_PendingDrops--;
|
2018-07-16 03:17:08 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-09-13 13:23:06 +00:00
|
|
|
if (SDL_QueueAudio(m_AudioDevice, audioBuffer, audioSize) < 0) {
|
|
|
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
|
|
"Failed to queue audio sample: %s",
|
|
|
|
SDL_GetError());
|
2018-06-24 01:46:59 +00:00
|
|
|
}
|
|
|
|
}
|