2018-09-29 23:18:46 +00:00
|
|
|
#include "../session.h"
|
2018-09-13 13:23:06 +00:00
|
|
|
#include "renderers/renderer.h"
|
2018-09-23 00:12:54 +00:00
|
|
|
|
2018-10-07 01:24:34 +00:00
|
|
|
#ifdef HAVE_SOUNDIO
|
2018-10-06 02:22:57 +00:00
|
|
|
#include "renderers/soundioaudiorenderer.h"
|
2018-09-23 00:12:54 +00:00
|
|
|
#endif
|
2018-09-13 13:23:06 +00:00
|
|
|
|
2019-03-23 06:08:10 +00:00
|
|
|
#ifdef HAVE_SLAUDIO
|
|
|
|
#include "renderers/slaud.h"
|
|
|
|
#endif
|
|
|
|
|
2019-01-06 21:28:05 +00:00
|
|
|
#include "renderers/sdl.h"
|
|
|
|
|
2018-09-13 13:23:06 +00:00
|
|
|
#include <Limelight.h>
|
|
|
|
|
|
|
|
IAudioRenderer* Session::createAudioRenderer()
|
|
|
|
{
|
2019-03-23 06:08:10 +00:00
|
|
|
#if defined(HAVE_SOUNDIO)
|
2019-01-06 21:28:05 +00:00
|
|
|
if (qgetenv("ML_AUDIO") == "SDL") {
|
|
|
|
return new SdlAudioRenderer();
|
|
|
|
}
|
|
|
|
|
2018-10-06 02:22:57 +00:00
|
|
|
return new SoundIoAudioRenderer();
|
2019-03-23 06:08:10 +00:00
|
|
|
#elif defined(HAVE_SLAUDIO)
|
|
|
|
return new SLAudioRenderer();
|
2018-09-23 00:12:54 +00:00
|
|
|
#else
|
2018-09-13 13:23:06 +00:00
|
|
|
return new SdlAudioRenderer();
|
2018-09-23 00:12:54 +00:00
|
|
|
#endif
|
2018-09-13 13:23:06 +00:00
|
|
|
}
|
|
|
|
|
2019-06-23 19:49:37 +00:00
|
|
|
int Session::getAudioRendererCapabilities()
|
|
|
|
{
|
|
|
|
IAudioRenderer* audioRenderer;
|
|
|
|
|
|
|
|
audioRenderer = createAudioRenderer();
|
|
|
|
if (audioRenderer == nullptr) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int caps = audioRenderer->getCapabilities();
|
|
|
|
|
|
|
|
delete audioRenderer;
|
|
|
|
|
|
|
|
return caps;
|
|
|
|
}
|
|
|
|
|
2018-09-13 13:23:06 +00:00
|
|
|
bool Session::testAudio(int audioConfiguration)
|
|
|
|
{
|
|
|
|
IAudioRenderer* audioRenderer;
|
|
|
|
|
|
|
|
audioRenderer = createAudioRenderer();
|
|
|
|
if (audioRenderer == nullptr) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-10-02 02:49:08 +00:00
|
|
|
// Build a fake OPUS_MULTISTREAM_CONFIGURATION to give
|
|
|
|
// the renderer the channel count and sample rate.
|
|
|
|
OPUS_MULTISTREAM_CONFIGURATION opusConfig = {};
|
|
|
|
opusConfig.sampleRate = 48000;
|
2019-05-04 22:46:11 +00:00
|
|
|
opusConfig.samplesPerFrame = 240;
|
2018-10-02 02:49:08 +00:00
|
|
|
|
|
|
|
switch (audioConfiguration)
|
|
|
|
{
|
|
|
|
case AUDIO_CONFIGURATION_STEREO:
|
|
|
|
opusConfig.channelCount = 2;
|
|
|
|
break;
|
|
|
|
case AUDIO_CONFIGURATION_51_SURROUND:
|
|
|
|
opusConfig.channelCount = 6;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
SDL_assert(false);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ret = audioRenderer->prepareForPlayback(&opusConfig);
|
2018-09-13 13:23:06 +00:00
|
|
|
|
|
|
|
delete audioRenderer;
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
int Session::arInit(int /* audioConfiguration */,
|
|
|
|
const POPUS_MULTISTREAM_CONFIGURATION opusConfig,
|
|
|
|
void* /* arContext */, int /* arFlags */)
|
|
|
|
{
|
|
|
|
int error;
|
|
|
|
|
|
|
|
SDL_memcpy(&s_ActiveSession->m_AudioConfig, opusConfig, sizeof(*opusConfig));
|
|
|
|
|
|
|
|
s_ActiveSession->m_OpusDecoder =
|
|
|
|
opus_multistream_decoder_create(opusConfig->sampleRate,
|
|
|
|
opusConfig->channelCount,
|
|
|
|
opusConfig->streams,
|
|
|
|
opusConfig->coupledStreams,
|
2018-10-02 08:09:13 +00:00
|
|
|
opusConfig->mapping,
|
2018-09-13 13:23:06 +00:00
|
|
|
&error);
|
|
|
|
if (s_ActiveSession->m_OpusDecoder == NULL) {
|
|
|
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
|
|
"Failed to create decoder: %d",
|
|
|
|
error);
|
2018-10-02 08:09:13 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
s_ActiveSession->m_AudioRenderer = s_ActiveSession->createAudioRenderer();
|
2018-09-13 14:46:01 +00:00
|
|
|
if (!s_ActiveSession->m_AudioRenderer->prepareForPlayback(opusConfig)) {
|
2018-09-13 13:23:06 +00:00
|
|
|
delete s_ActiveSession->m_AudioRenderer;
|
|
|
|
opus_multistream_decoder_destroy(s_ActiveSession->m_OpusDecoder);
|
2018-10-02 08:21:42 +00:00
|
|
|
return -2;
|
2018-09-13 13:23:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
|
|
"Audio stream has %d channels",
|
|
|
|
opusConfig->channelCount);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Session::arCleanup()
|
|
|
|
{
|
2018-10-01 01:09:12 +00:00
|
|
|
delete s_ActiveSession->m_AudioRenderer;
|
|
|
|
s_ActiveSession->m_AudioRenderer = nullptr;
|
2018-09-13 13:23:06 +00:00
|
|
|
|
|
|
|
opus_multistream_decoder_destroy(s_ActiveSession->m_OpusDecoder);
|
|
|
|
s_ActiveSession->m_OpusDecoder = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Session::arDecodeAndPlaySample(char* sampleData, int sampleLength)
|
|
|
|
{
|
|
|
|
int samplesDecoded;
|
|
|
|
|
2019-04-28 22:57:57 +00:00
|
|
|
#ifndef STEAM_LINK
|
|
|
|
// Set this thread to high priority to reduce the chance of missing
|
|
|
|
// our sample delivery time. On Steam Link, this causes starvation
|
|
|
|
// of other threads due to severely restricted CPU time available,
|
|
|
|
// so we will skip it on that platform.
|
2018-10-06 02:22:57 +00:00
|
|
|
if (s_ActiveSession->m_AudioSampleCount == 0) {
|
|
|
|
if (SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH) < 0) {
|
|
|
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
|
|
|
"Unable to set audio thread to high priority: %s",
|
|
|
|
SDL_GetError());
|
|
|
|
}
|
|
|
|
}
|
2019-04-28 22:57:57 +00:00
|
|
|
#endif
|
2018-10-06 02:22:57 +00:00
|
|
|
|
2019-02-15 06:32:54 +00:00
|
|
|
// See if we need to drop this sample
|
|
|
|
if (s_ActiveSession->m_DropAudioEndTime != 0) {
|
|
|
|
if (SDL_TICKS_PASSED(SDL_GetTicks(), s_ActiveSession->m_DropAudioEndTime)) {
|
|
|
|
// Avoid calling SDL_GetTicks() now
|
|
|
|
s_ActiveSession->m_DropAudioEndTime = 0;
|
|
|
|
|
|
|
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
|
|
"Audio drop window has ended");
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// We're still in the drop window
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-02 08:21:42 +00:00
|
|
|
s_ActiveSession->m_AudioSampleCount++;
|
|
|
|
|
|
|
|
if (s_ActiveSession->m_AudioRenderer != nullptr) {
|
2019-05-04 22:46:11 +00:00
|
|
|
int desiredSize = sizeof(short) * s_ActiveSession->m_AudioConfig.samplesPerFrame * s_ActiveSession->m_AudioConfig.channelCount;
|
2019-05-02 04:27:41 +00:00
|
|
|
void* buffer = s_ActiveSession->m_AudioRenderer->getAudioBuffer(&desiredSize);
|
|
|
|
if (buffer == nullptr) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-10-02 08:21:42 +00:00
|
|
|
samplesDecoded = opus_multistream_decode(s_ActiveSession->m_OpusDecoder,
|
|
|
|
(unsigned char*)sampleData,
|
|
|
|
sampleLength,
|
2019-05-02 04:27:41 +00:00
|
|
|
(short*)buffer,
|
|
|
|
desiredSize / sizeof(short) / s_ActiveSession->m_AudioConfig.channelCount,
|
2018-10-02 08:21:42 +00:00
|
|
|
0);
|
2019-05-12 02:09:59 +00:00
|
|
|
|
|
|
|
// Update desiredSize with the number of bytes actually populated by the decoding operation
|
2018-10-02 08:21:42 +00:00
|
|
|
if (samplesDecoded > 0) {
|
2019-05-02 04:27:41 +00:00
|
|
|
SDL_assert(desiredSize >= sizeof(short) * samplesDecoded * s_ActiveSession->m_AudioConfig.channelCount);
|
|
|
|
desiredSize = sizeof(short) * samplesDecoded * s_ActiveSession->m_AudioConfig.channelCount;
|
2019-05-12 02:09:59 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
desiredSize = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!s_ActiveSession->m_AudioRenderer->submitAudio(desiredSize)) {
|
|
|
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
|
|
|
"Reinitializing audio renderer after failure");
|
2018-10-02 08:21:42 +00:00
|
|
|
|
2019-05-12 02:09:59 +00:00
|
|
|
delete s_ActiveSession->m_AudioRenderer;
|
|
|
|
s_ActiveSession->m_AudioRenderer = nullptr;
|
2018-10-02 08:21:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Only try to recreate the audio renderer every 200 samples (1 second)
|
|
|
|
// to avoid thrashing if the audio device is unavailable. It is
|
|
|
|
// safe to reinitialize here because we can't be torn down while
|
|
|
|
// the audio decoder/playback thread is still alive.
|
|
|
|
if (s_ActiveSession->m_AudioRenderer == nullptr && (s_ActiveSession->m_AudioSampleCount % 200) == 0) {
|
2019-02-15 06:32:54 +00:00
|
|
|
// Since we're doing this inline and audio initialization takes time, we need
|
|
|
|
// to drop samples to account for the time we've spent blocking audio rendering
|
|
|
|
// so we return to real-time playback and don't accumulate latency.
|
|
|
|
Uint32 audioReinitStartTime = SDL_GetTicks();
|
|
|
|
|
2018-10-02 08:21:42 +00:00
|
|
|
s_ActiveSession->m_AudioRenderer = s_ActiveSession->createAudioRenderer();
|
|
|
|
if (!s_ActiveSession->m_AudioRenderer->prepareForPlayback(&s_ActiveSession->m_AudioConfig)) {
|
|
|
|
delete s_ActiveSession->m_AudioRenderer;
|
|
|
|
s_ActiveSession->m_AudioRenderer = nullptr;
|
|
|
|
}
|
2019-02-15 06:32:54 +00:00
|
|
|
|
|
|
|
Uint32 audioReinitStopTime = SDL_GetTicks();
|
|
|
|
|
|
|
|
s_ActiveSession->m_DropAudioEndTime = audioReinitStopTime + (audioReinitStopTime - audioReinitStartTime);
|
|
|
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
|
|
"Audio reinitialization took %d ms - starting drop window",
|
|
|
|
audioReinitStopTime - audioReinitStartTime);
|
2018-09-13 13:23:06 +00:00
|
|
|
}
|
|
|
|
}
|