mirror of
https://github.com/moonlight-stream/moonlight-qt.git
synced 2026-06-18 06:30:55 +00:00
Remove automatic audio configuration permanently due to brokenness and allow renderers to request reinitialization on demand
This commit is contained in:
@@ -194,7 +194,6 @@ StreamCommandLineParser::StreamCommandLineParser()
|
|||||||
{"borderless", StreamingPreferences::WM_FULLSCREEN_DESKTOP},
|
{"borderless", StreamingPreferences::WM_FULLSCREEN_DESKTOP},
|
||||||
};
|
};
|
||||||
m_AudioConfigMap = {
|
m_AudioConfigMap = {
|
||||||
{"auto", StreamingPreferences::AC_AUTO},
|
|
||||||
{"stereo", StreamingPreferences::AC_FORCE_STEREO},
|
{"stereo", StreamingPreferences::AC_FORCE_STEREO},
|
||||||
{"surround", StreamingPreferences::AC_FORCE_SURROUND},
|
{"surround", StreamingPreferences::AC_FORCE_SURROUND},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -406,10 +406,6 @@ ScrollView {
|
|||||||
textRole: "text"
|
textRole: "text"
|
||||||
model: ListModel {
|
model: ListModel {
|
||||||
id: audioListModel
|
id: audioListModel
|
||||||
ListElement {
|
|
||||||
text: "Autodetect"
|
|
||||||
val: StreamingPreferences.AC_AUTO
|
|
||||||
}
|
|
||||||
ListElement {
|
ListElement {
|
||||||
text: "Stereo"
|
text: "Stereo"
|
||||||
val: StreamingPreferences.AC_FORCE_STEREO
|
val: StreamingPreferences.AC_FORCE_STEREO
|
||||||
|
|||||||
@@ -52,23 +52,6 @@ bool Session::testAudio(int audioConfiguration)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Session::detectAudioConfiguration()
|
|
||||||
{
|
|
||||||
IAudioRenderer* audioRenderer;
|
|
||||||
|
|
||||||
audioRenderer = createAudioRenderer();
|
|
||||||
if (audioRenderer == nullptr) {
|
|
||||||
// Hope for the best
|
|
||||||
return AUDIO_CONFIGURATION_STEREO;
|
|
||||||
}
|
|
||||||
|
|
||||||
int audioConfig = audioRenderer->detectAudioConfiguration();
|
|
||||||
|
|
||||||
delete audioRenderer;
|
|
||||||
|
|
||||||
return audioConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
int Session::arInit(int /* audioConfiguration */,
|
int Session::arInit(int /* audioConfiguration */,
|
||||||
const POPUS_MULTISTREAM_CONFIGURATION opusConfig,
|
const POPUS_MULTISTREAM_CONFIGURATION opusConfig,
|
||||||
void* /* arContext */, int /* arFlags */)
|
void* /* arContext */, int /* arFlags */)
|
||||||
@@ -92,15 +75,10 @@ int Session::arInit(int /* audioConfiguration */,
|
|||||||
}
|
}
|
||||||
|
|
||||||
s_ActiveSession->m_AudioRenderer = s_ActiveSession->createAudioRenderer();
|
s_ActiveSession->m_AudioRenderer = s_ActiveSession->createAudioRenderer();
|
||||||
if (s_ActiveSession->m_AudioRenderer == nullptr) {
|
|
||||||
opus_multistream_decoder_destroy(s_ActiveSession->m_OpusDecoder);
|
|
||||||
return -2;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!s_ActiveSession->m_AudioRenderer->prepareForPlayback(opusConfig)) {
|
if (!s_ActiveSession->m_AudioRenderer->prepareForPlayback(opusConfig)) {
|
||||||
delete s_ActiveSession->m_AudioRenderer;
|
delete s_ActiveSession->m_AudioRenderer;
|
||||||
opus_multistream_decoder_destroy(s_ActiveSession->m_OpusDecoder);
|
opus_multistream_decoder_destroy(s_ActiveSession->m_OpusDecoder);
|
||||||
return -3;
|
return -2;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
@@ -123,16 +101,40 @@ void Session::arDecodeAndPlaySample(char* sampleData, int sampleLength)
|
|||||||
{
|
{
|
||||||
int samplesDecoded;
|
int samplesDecoded;
|
||||||
|
|
||||||
samplesDecoded = opus_multistream_decode(s_ActiveSession->m_OpusDecoder,
|
s_ActiveSession->m_AudioSampleCount++;
|
||||||
(unsigned char*)sampleData,
|
|
||||||
sampleLength,
|
if (s_ActiveSession->m_AudioRenderer != nullptr) {
|
||||||
s_ActiveSession->m_OpusDecodeBuffer,
|
samplesDecoded = opus_multistream_decode(s_ActiveSession->m_OpusDecoder,
|
||||||
SAMPLES_PER_FRAME,
|
(unsigned char*)sampleData,
|
||||||
0);
|
sampleLength,
|
||||||
if (samplesDecoded > 0) {
|
s_ActiveSession->m_OpusDecodeBuffer,
|
||||||
s_ActiveSession->m_AudioRenderer->submitAudio(s_ActiveSession->m_OpusDecodeBuffer,
|
SAMPLES_PER_FRAME,
|
||||||
static_cast<int>(sizeof(short) *
|
0);
|
||||||
samplesDecoded *
|
if (samplesDecoded > 0) {
|
||||||
s_ActiveSession->m_AudioConfig.channelCount));
|
if (!s_ActiveSession->m_AudioRenderer->submitAudio(s_ActiveSession->m_OpusDecodeBuffer,
|
||||||
|
static_cast<int>(
|
||||||
|
sizeof(short) *
|
||||||
|
samplesDecoded *
|
||||||
|
s_ActiveSession->m_AudioConfig.channelCount))) {
|
||||||
|
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Reinitializing audio renderer after failure");
|
||||||
|
|
||||||
|
delete s_ActiveSession->m_AudioRenderer;
|
||||||
|
s_ActiveSession->m_AudioRenderer = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,14 +74,14 @@ bool PortAudioRenderer::prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION*
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PortAudioRenderer::submitAudio(short* audioBuffer, int audioSize)
|
bool PortAudioRenderer::submitAudio(short* audioBuffer, int audioSize)
|
||||||
{
|
{
|
||||||
SDL_assert(audioSize == SAMPLES_PER_FRAME * m_ChannelCount * 2);
|
SDL_assert(audioSize == SAMPLES_PER_FRAME * m_ChannelCount * 2);
|
||||||
|
|
||||||
// Check if there is space for this sample in the buffer. Again, this can race
|
// Check if there is space for this sample in the buffer. Again, this can race
|
||||||
// but in the worst case, we'll not see the sample callback having consumed a sample.
|
// but in the worst case, we'll not see the sample callback having consumed a sample.
|
||||||
if (((m_WriteIndex + 1) % CIRCULAR_BUFFER_SIZE) == m_ReadIndex) {
|
if (((m_WriteIndex + 1) % CIRCULAR_BUFFER_SIZE) == m_ReadIndex) {
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_memcpy(&m_AudioBuffer[m_WriteIndex * CIRCULAR_BUFFER_STRIDE], audioBuffer, audioSize);
|
SDL_memcpy(&m_AudioBuffer[m_WriteIndex * CIRCULAR_BUFFER_STRIDE], audioBuffer, audioSize);
|
||||||
@@ -102,33 +102,13 @@ void PortAudioRenderer::submitAudio(short* audioBuffer, int audioSize)
|
|||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"Pa_StartStream() failed: %s",
|
"Pa_StartStream() failed: %s",
|
||||||
Pa_GetErrorText(error));
|
Pa_GetErrorText(error));
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_Started = true;
|
m_Started = true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
int PortAudioRenderer::detectAudioConfiguration() const
|
return true;
|
||||||
{
|
|
||||||
const PaDeviceInfo* deviceInfo = Pa_GetDeviceInfo(Pa_GetDefaultOutputDevice());
|
|
||||||
if (deviceInfo == nullptr) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Pa_GetDeviceInfo() failed");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// PulseAudio reports max output channels that don't
|
|
||||||
// correspond to any output devices (32 channels), so
|
|
||||||
// only use 5.1 surround sound if the output channel count
|
|
||||||
// is reasonable. Additionally, PortAudio doesn't do remixing
|
|
||||||
// for quadraphonic, so only use 5.1 if we have 6 or more channels.
|
|
||||||
if (deviceInfo->maxOutputChannels == 6 || deviceInfo->maxOutputChannels == 8) {
|
|
||||||
return AUDIO_CONFIGURATION_51_SURROUND;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return AUDIO_CONFIGURATION_STEREO;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int PortAudioRenderer::paStreamCallback(const void*, void* output, unsigned long frameCount, const PaStreamCallbackTimeInfo*, PaStreamCallbackFlags, void* userData)
|
int PortAudioRenderer::paStreamCallback(const void*, void* output, unsigned long frameCount, const PaStreamCallbackTimeInfo*, PaStreamCallbackFlags, void* userData)
|
||||||
|
|||||||
@@ -18,9 +18,7 @@ public:
|
|||||||
|
|
||||||
virtual bool prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* opusConfig);
|
virtual bool prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* opusConfig);
|
||||||
|
|
||||||
virtual void submitAudio(short* audioBuffer, int audioSize);
|
virtual bool submitAudio(short* audioBuffer, int audioSize);
|
||||||
|
|
||||||
virtual int detectAudioConfiguration() const;
|
|
||||||
|
|
||||||
static int paStreamCallback(const void *input,
|
static int paStreamCallback(const void *input,
|
||||||
void *output,
|
void *output,
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ public:
|
|||||||
|
|
||||||
virtual bool prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* opusConfig) = 0;
|
virtual bool prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* opusConfig) = 0;
|
||||||
|
|
||||||
virtual void submitAudio(short* audioBuffer, int audioSize) = 0;
|
// Return false if an unrecoverable error has occurred and the renderer must be reinitialized
|
||||||
|
virtual bool submitAudio(short* audioBuffer, int audioSize) = 0;
|
||||||
virtual int detectAudioConfiguration() const = 0;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,9 +17,7 @@ public:
|
|||||||
|
|
||||||
virtual bool prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* opusConfig);
|
virtual bool prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* opusConfig);
|
||||||
|
|
||||||
virtual void submitAudio(short* audioBuffer, int audioSize);
|
virtual bool submitAudio(short* audioBuffer, int audioSize);
|
||||||
|
|
||||||
virtual int detectAudioConfiguration() const;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SDL_AudioDeviceID m_AudioDevice;
|
SDL_AudioDeviceID m_AudioDevice;
|
||||||
|
|||||||
@@ -10,47 +10,6 @@
|
|||||||
#define STOP_THE_WORLD_LIMIT 20
|
#define STOP_THE_WORLD_LIMIT 20
|
||||||
#define DROP_RATIO_DENOM 32
|
#define DROP_RATIO_DENOM 32
|
||||||
|
|
||||||
// This isn't accurate on macOS and Linux (PulseAudio),
|
|
||||||
// since they both report supporting a large number of
|
|
||||||
// channels, regardless of the actual output device.
|
|
||||||
int SdlAudioRenderer::detectAudioConfiguration() const
|
|
||||||
{
|
|
||||||
SDL_AudioSpec want, have;
|
|
||||||
SDL_AudioDeviceID dev;
|
|
||||||
|
|
||||||
SDL_zero(want);
|
|
||||||
want.freq = 48000;
|
|
||||||
want.format = AUDIO_S16;
|
|
||||||
want.channels = 6;
|
|
||||||
want.samples = 1024;
|
|
||||||
|
|
||||||
// Try to open for 5.1 surround sound, but allow SDL to tell us that's
|
|
||||||
// not available.
|
|
||||||
dev = SDL_OpenAudioDevice(NULL, 0, &want, &have, SDL_AUDIO_ALLOW_CHANNELS_CHANGE);
|
|
||||||
if (dev == 0) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Failed to open audio device");
|
|
||||||
// We'll probably have issues during audio stream init, but we'll
|
|
||||||
// try anyway
|
|
||||||
return AUDIO_CONFIGURATION_STEREO;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_CloseAudioDevice(dev);
|
|
||||||
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Audio device has %d channels", have.channels);
|
|
||||||
|
|
||||||
if (have.channels > 2) {
|
|
||||||
// We don't support quadraphonic or 7.1 surround, but SDL
|
|
||||||
// should be able to downmix or upmix better from 5.1 than
|
|
||||||
// from stereo, so use 5.1 in non-stereo cases.
|
|
||||||
return AUDIO_CONFIGURATION_51_SURROUND;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return AUDIO_CONFIGURATION_STEREO;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SdlAudioRenderer::SdlAudioRenderer()
|
SdlAudioRenderer::SdlAudioRenderer()
|
||||||
: m_AudioDevice(0),
|
: m_AudioDevice(0),
|
||||||
m_ChannelCount(0),
|
m_ChannelCount(0),
|
||||||
@@ -141,7 +100,7 @@ SdlAudioRenderer::~SdlAudioRenderer()
|
|||||||
SDL_assert(!SDL_WasInit(SDL_INIT_AUDIO));
|
SDL_assert(!SDL_WasInit(SDL_INIT_AUDIO));
|
||||||
}
|
}
|
||||||
|
|
||||||
void SdlAudioRenderer::submitAudio(short* audioBuffer, int audioSize)
|
bool SdlAudioRenderer::submitAudio(short* audioBuffer, int audioSize)
|
||||||
{
|
{
|
||||||
m_SampleIndex++;
|
m_SampleIndex++;
|
||||||
|
|
||||||
@@ -181,13 +140,13 @@ void SdlAudioRenderer::submitAudio(short* audioBuffer, int audioSize)
|
|||||||
// Hard drops happen all at once to forcefully
|
// Hard drops happen all at once to forcefully
|
||||||
// resync with the source.
|
// resync with the source.
|
||||||
m_PendingHardDrops--;
|
m_PendingHardDrops--;
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
else if (m_PendingDrops != 0 && m_SampleIndex % DROP_RATIO_DENOM == 0) {
|
else if (m_PendingDrops != 0 && m_SampleIndex % DROP_RATIO_DENOM == 0) {
|
||||||
// Normal drops are interspersed with the audio data
|
// Normal drops are interspersed with the audio data
|
||||||
// to hide the glitches.
|
// to hide the glitches.
|
||||||
m_PendingDrops--;
|
m_PendingDrops--;
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SDL_QueueAudio(m_AudioDevice, audioBuffer, audioSize) < 0) {
|
if (SDL_QueueAudio(m_AudioDevice, audioBuffer, audioSize) < 0) {
|
||||||
@@ -195,4 +154,6 @@ void SdlAudioRenderer::submitAudio(short* audioBuffer, int audioSize)
|
|||||||
"Failed to queue audio sample: %s",
|
"Failed to queue audio sample: %s",
|
||||||
SDL_GetError());
|
SDL_GetError());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -288,7 +288,8 @@ Session::Session(NvComputer* computer, NvApp& app, StreamingPreferences *prefere
|
|||||||
m_DisplayOriginY(0),
|
m_DisplayOriginY(0),
|
||||||
m_PendingWindowedTransition(false),
|
m_PendingWindowedTransition(false),
|
||||||
m_OpusDecoder(nullptr),
|
m_OpusDecoder(nullptr),
|
||||||
m_AudioRenderer(nullptr)
|
m_AudioRenderer(nullptr),
|
||||||
|
m_AudioSampleCount(0)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -327,10 +328,6 @@ void Session::initialize()
|
|||||||
|
|
||||||
switch (m_Preferences->audioConfig)
|
switch (m_Preferences->audioConfig)
|
||||||
{
|
{
|
||||||
case StreamingPreferences::AC_AUTO:
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Autodetecting audio configuration");
|
|
||||||
m_StreamConfig.audioConfiguration = detectAudioConfiguration();
|
|
||||||
break;
|
|
||||||
case StreamingPreferences::AC_FORCE_STEREO:
|
case StreamingPreferences::AC_FORCE_STEREO:
|
||||||
m_StreamConfig.audioConfiguration = AUDIO_CONFIGURATION_STEREO;
|
m_StreamConfig.audioConfiguration = AUDIO_CONFIGURATION_STEREO;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -124,6 +124,7 @@ private:
|
|||||||
short m_OpusDecodeBuffer[MAX_CHANNELS * SAMPLES_PER_FRAME];
|
short m_OpusDecodeBuffer[MAX_CHANNELS * SAMPLES_PER_FRAME];
|
||||||
IAudioRenderer* m_AudioRenderer;
|
IAudioRenderer* m_AudioRenderer;
|
||||||
OPUS_MULTISTREAM_CONFIGURATION m_AudioConfig;
|
OPUS_MULTISTREAM_CONFIGURATION m_AudioConfig;
|
||||||
|
int m_AudioSampleCount;
|
||||||
|
|
||||||
static AUDIO_RENDERER_CALLBACKS k_AudioCallbacks;
|
static AUDIO_RENDERER_CALLBACKS k_AudioCallbacks;
|
||||||
static CONNECTION_LISTENER_CALLBACKS k_ConnCallbacks;
|
static CONNECTION_LISTENER_CALLBACKS k_ConnCallbacks;
|
||||||
|
|||||||
Reference in New Issue
Block a user