[AE/PulseAudio] fix for volume, device selection #1868

Closed
wants to merge 3 commits into
from
@@ -30,6 +30,7 @@
#include <pulse/pulseaudio.h>
#include <pulse/simple.h>
#include "guilib/LocalizeStrings.h"
+#include "settings/GUISettings.h"
/* Static helpers */
static const char *ContextStateToString(pa_context_state s)
@@ -353,7 +354,7 @@ static void SinkInfo(pa_context *c, const pa_sink_info *i, int eol, void *userda
{
CStdString desc, sink;
desc.Format("%s (PulseAudio)", i->description);
- sink.Format("pulse:%s@default", i->name);
+ sink.Format("%s", i->name);
sinkStruct->list->push_back(AEDevice(desc, sink));
CLog::Log(LOGDEBUG, "PulseAudio: Found %s with devicestring %s", desc.c_str(), sink.c_str());
}
@@ -375,7 +376,7 @@ void CPulseAE::EnumerateOutputDevices(AEDeviceList &devices, bool passthrough)
sinkStruct.list = &devices;
CStdString def;
def.Format("%s (PulseAudio)",g_localizeStrings.Get(409).c_str());
- devices.push_back(AEDevice(def, "pulse:default@default"));
+ devices.push_back(AEDevice(def, "default"));
WaitForOperation(pa_context_get_sink_info_list(m_Context,
SinkInfo, &sinkStruct), m_MainLoop, "EnumerateAudioSinks");
@@ -409,4 +410,18 @@ void CPulseAE::SetMute(const bool enabled)
m_muted = enabled;
}
+/*
+ Return audio device name set within XBMC GUI. If passthrough is set to true
+ audio passthrough device name will be returned.
+*/
+const char* CPulseAE::GetAudioDevice(bool passthrough)
+{
+ std::string m_outputDevice;
+ if (passthrough)
+ m_outputDevice = g_guiSettings.GetString("audiooutput.passthroughdevice");
+ else
+ m_outputDevice = g_guiSettings.GetString("audiooutput.audiodevice");
+ return m_outputDevice.c_str();
+}
+
#endif
@@ -69,6 +69,7 @@ class CPulseAE : public IAE
virtual void SetMute(const bool enabled);
virtual bool IsMuted() { return m_muted; }
virtual void SetSoundMode(const int mode) {}
+ static const char* GetAudioDevice(bool passthrough);
#if PA_CHECK_VERSION(1,0,0)
virtual bool SupportsRaw() { return true; }
#endif
@@ -22,7 +22,9 @@
#ifdef HAS_PULSEAUDIO
#include "PulseAESound.h"
+#include "PulseAE.h"
#include "AEFactory.h"
+#include "Utils/AEUtil.h"
#include "utils/log.h"
#include "MathUtils.h"
#include "StringUtils.h"
@@ -81,7 +83,7 @@ bool CPulseAESound::Initialize()
m_maxVolume = CAEFactory::GetEngine()->GetVolume();
m_volume = 1.0f;
- pa_volume_t paVolume = pa_sw_volume_from_linear((double)(m_volume * m_maxVolume));
+ pa_volume_t paVolume = CAEUtil::PercentToPulseVolume((double)(m_volume * m_maxVolume));
pa_cvolume_set(&m_chVolume, m_sampleSpec.channels, paVolume);
pa_threaded_mainloop_lock(m_mainLoop);
@@ -135,7 +137,13 @@ void CPulseAESound::Play()
/* we only keep the most recent operation as it is the only one needed for IsPlaying to function */
if (m_op)
pa_operation_unref(m_op);
- m_op = pa_context_play_sample(m_context, m_pulseName.c_str(), NULL, PA_VOLUME_INVALID, NULL, NULL);
+ m_maxVolume = CAEFactory::GetEngine()->GetVolume();
+ pa_volume_t paVolume = CAEUtil::PercentToPulseVolume((double)(m_volume * m_maxVolume));
+ std::string m_outputDevice = CPulseAE::GetAudioDevice(false);
+ if (m_outputDevice == "default")
+ m_op = pa_context_play_sample(m_context, m_pulseName.c_str(), NULL, paVolume, NULL, NULL);
+ else
+ m_op = pa_context_play_sample(m_context, m_pulseName.c_str(), m_outputDevice.c_str(), paVolume, NULL, NULL);
pa_threaded_mainloop_unlock(m_mainLoop);
}
@@ -22,8 +22,10 @@
#ifdef HAS_PULSEAUDIO
#include "PulseAEStream.h"
+#include "PulseAE.h"
#include "AEFactory.h"
#include "Utils/AEUtil.h"
+#include "Utils/AEUtil.h"
#include "utils/log.h"
#include "utils/MathUtils.h"
#include "threads/SingleLock.h"
@@ -63,6 +65,8 @@ CPulseAEStream::CPulseAEStream(pa_context *context, pa_threaded_mainloop *mainLo
m_channelLayout = channelLayout;
m_options = options;
+ bool m_passthrough = false;
+
m_DrainOperation = NULL;
m_slave = NULL;
@@ -138,7 +142,7 @@ CPulseAEStream::CPulseAEStream(pa_context *context, pa_threaded_mainloop *mainLo
m_MaxVolume = CAEFactory::GetEngine()->GetVolume();
m_Volume = 1.0f;
- pa_volume_t paVolume = pa_sw_volume_from_linear((double)(m_Volume * m_MaxVolume));
+ pa_volume_t paVolume = CAEUtil::PercentToPulseVolume((double)(m_Volume * m_MaxVolume));
pa_cvolume_set(&m_ChVolume, m_SampleSpec.channels, paVolume);
#if PA_CHECK_VERSION(1,0,0)
@@ -155,6 +159,8 @@ CPulseAEStream::CPulseAEStream(pa_context *context, pa_threaded_mainloop *mainLo
pa_format_info_set_channels (info[0], m_SampleSpec.channels);
pa_format_info_set_sample_format(info[0], m_SampleSpec.format);
m_Stream = pa_stream_new_extended(m_Context, "audio stream", info, 1, NULL);
+ if (!info[0]->encoding == PA_ENCODING_PCM)
+ m_passthrough = true;
pa_format_info_free(info[0]);
#else
m_Stream = pa_stream_new(m_Context, "audio stream", &m_SampleSpec, &map);
@@ -177,7 +183,14 @@ CPulseAEStream::CPulseAEStream(pa_context *context, pa_threaded_mainloop *mainLo
if (options && AESTREAM_FORCE_RESAMPLE)
flags |= PA_STREAM_VARIABLE_RATE;
- if (pa_stream_connect_playback(m_Stream, NULL, NULL, (pa_stream_flags)flags, &m_ChVolume, NULL) < 0)
+ int pa_state;
+ std::string m_outputDevice = CPulseAE::GetAudioDevice(m_passthrough);
+ if (m_outputDevice == "default")
+ pa_state = pa_stream_connect_playback(m_Stream, NULL, NULL, (pa_stream_flags)flags, &m_ChVolume, NULL);
+ else
+ pa_state = pa_stream_connect_playback(m_Stream, m_outputDevice.c_str(), NULL, (pa_stream_flags)flags, &m_ChVolume, NULL);
+
+ if (pa_state < 0)
{
CLog::Log(LOGERROR, "PulseAudio: Failed to connect stream to output");
pa_threaded_mainloop_unlock(m_MainLoop);
@@ -208,6 +221,7 @@ CPulseAEStream::CPulseAEStream(pa_context *context, pa_threaded_mainloop *mainLo
m_Initialized = true;
CLog::Log(LOGINFO, "PulseAEStream::Initialized");
+ CLog::Log(LOGINFO, " Sink Output : %s", m_outputDevice.c_str());
CLog::Log(LOGINFO, " Sample Rate : %d", m_sampleRate);
CLog::Log(LOGINFO, " Sample Format : %s", CAEUtil::DataFormatToStr(m_format));
CLog::Log(LOGINFO, " Channel Count : %d", m_channelLayout.Count());
@@ -419,7 +433,7 @@ void CPulseAEStream::SetVolume(float volume)
if (volume > 0.f)
{
m_Volume = volume;
- pa_volume_t paVolume = pa_sw_volume_from_linear((double)(m_Volume * m_MaxVolume));
+ pa_volume_t paVolume = CAEUtil::PercentToPulseVolume((double)(m_Volume * m_MaxVolume));
pa_cvolume_set(&m_ChVolume, m_SampleSpec.channels, paVolume);
}
@@ -24,6 +24,10 @@
#include "PlatformDefs.h"
#include <math.h>
+#if defined(HAS_PULSEAUDIO)
+#include <pulse/pulseaudio.h>
+#endif
+
#ifdef TARGET_WINDOWS
#if _M_IX86_FP>0 && !defined(__SSE__)
#define __SSE__
@@ -81,6 +85,26 @@ class CAEUtil
{
return pow(10.0f, dB/20);
}
+
+ #if defined(HAS_PULSEAUDIO)
+ /*
+ Hardware volume [0,1] will be converted to dB using PercentToGain() and then converted to scale by
+ GainToScale() before reaching PulseAudio. However the underlying math assumes 60dB volume range which
+ contradicts (-inf,0] volume range used by pulseaudio 2.0 and above. To fix this we will reverse the
+ process of GainToScale() and PercentToGain() to get hardware volume then using our own linear converter to
+ get valid pulseaudio volume [0,PA_VOLUME_NORM].
+ */
+ static inline const int PercentToPulseVolume(const float value)
+ {
+ float hardware_volume = 0.f;
+ // This is to reverse "pow(10.0f, dB/20)" in GainToScale() and "(value - 1)*db_range" in PercentToGain()
+ if ( value > 0.f )
+ hardware_volume = (double)( (20 * log10(value)) / 60 + 1 );
+ // This is to simulate pa_sw_volume_from_linear() which convert a linear hardware volume rage [0,1]
+ // to pulseaudio volume rage [0, PA_VOLUME_NORM]
+ return (int)( hardware_volume * PA_VOLUME_NORM );
+ }
+ #endif
#ifdef __SSE__
static void SSEMulArray (float *data, const float mul, uint32_t count);