Skip to content

Commit

Permalink
Merge pull request xbmc#4451 from fritsch/pa-volume-fix-mainline
Browse files Browse the repository at this point in the history
PulseAudio: Rework Volume
  • Loading branch information
t-nelson committed Mar 27, 2014
2 parents 1bb64ef + e1350d0 commit c927f64
Show file tree
Hide file tree
Showing 2 changed files with 139 additions and 26 deletions.
161 changes: 135 additions & 26 deletions xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "utils/log.h"
#include "Util.h"
#include "guilib/LocalizeStrings.h"
#include "Application.h"

using namespace std;

Expand Down Expand Up @@ -223,12 +224,29 @@ struct SinkInfoStruct
bool isHWDevice;
bool device_found;
pa_threaded_mainloop *mainloop;
int samplerate;
SinkInfoStruct()
{
list = NULL;
isHWDevice = false;
device_found = true;
mainloop = NULL;
samplerate = 0;
}
};

struct SinkInputInfoStruct
{
bool is_valid;
int mute;
int index;
pa_cvolume volume;
pa_threaded_mainloop *mainloop;
SinkInputInfoStruct()
{
is_valid = false;
mute = 0;
mainloop = NULL;
}
};

Expand All @@ -240,11 +258,25 @@ static void SinkInfoCallback(pa_context *c, const pa_sink_info *i, int eol, void
if (i->flags && (i->flags & PA_SINK_HARDWARE))
sinkStruct->isHWDevice = true;

sinkStruct->samplerate = i->sample_spec.rate;
sinkStruct->device_found = true;
}
pa_threaded_mainloop_signal(sinkStruct->mainloop, 0);
}

static void SinkInputInfoCallback(pa_context *c, const pa_sink_input_info *i, int eol, void *userdata)
{
SinkInputInfoStruct *siiStruct = (SinkInputInfoStruct *)userdata;
if(i && i->has_volume)
{
siiStruct->is_valid = true;
siiStruct->volume = i->volume;
siiStruct->mute = i->mute;
siiStruct->index = i->index;
}
pa_threaded_mainloop_signal(siiStruct->mainloop, 0);
}

static AEChannel PAChannelToAEChannel(pa_channel_position_t channel)
{
AEChannel ae_channel;
Expand Down Expand Up @@ -483,6 +515,31 @@ bool CAESinkPULSE::Initialize(AEAudioFormat &format, std::string &device)
}
m_Channels = format.m_channelLayout.Count();

// store information about current sink
SinkInfoStruct sinkStruct;
sinkStruct.mainloop = m_MainLoop;
sinkStruct.device_found = false;

// get real sample rate of the device we want to open - to avoid resampling
bool isDefaultDevice = (device == "Default");
WaitForOperation(pa_context_get_sink_info_by_name(m_Context, isDefaultDevice ? NULL : device.c_str(), SinkInfoCallback, &sinkStruct), m_MainLoop, "Get Sink Info");
if (sinkStruct.device_found)
{
if (!m_passthrough)
format.m_sampleRate = sinkStruct.samplerate;
}
else
{
CLog::Log(LOGERROR, "PulseAudio: Sink %s not found", device.c_str());
pa_threaded_mainloop_unlock(m_MainLoop);
Deinitialize();
return false;
}

// Pulse can resample everything between 1 hz and 192000 hz
// Make sure we are in the range that we originally added
format.m_sampleRate = std::max(5512U, std::min(format.m_sampleRate, 192000U));

pa_format_info *info[1];
info[0] = pa_format_info_new();
info[0]->encoding = AEFormatToPulseEncoding(format.m_dataFormat);
Expand All @@ -493,10 +550,6 @@ bool CAESinkPULSE::Initialize(AEAudioFormat &format, std::string &device)
}
pa_format_info_set_channels(info[0], m_Channels);

// Pulse can resample everything between 1 hz and 192000 hz
// Make sure we are in the range that we originally added
format.m_sampleRate = std::max(5512U, std::min(format.m_sampleRate, 192000U));

// PA requires m_encodedRate in order to do EAC3
unsigned int samplerate;
if (m_passthrough)
Expand Down Expand Up @@ -558,29 +611,7 @@ bool CAESinkPULSE::Initialize(AEAudioFormat &format, std::string &device)
pa_stream_set_write_callback(m_Stream, StreamRequestCallback, m_MainLoop);
pa_stream_set_latency_update_callback(m_Stream, StreamLatencyUpdateCallback, m_MainLoop);

bool isDefaultDevice = (device == "Default");

pa_buffer_attr buffer_attr;
SinkInfoStruct sinkStruct;
sinkStruct.mainloop = m_MainLoop;
sinkStruct.isHWDevice = false;
sinkStruct.device_found = true; // needed to get default device opened

if (!isDefaultDevice)
{
// we need to check if the device we want to open really exists
// default device is handled in a special manner
sinkStruct.device_found = false; // if sink is valid it will be set true in pa_context_get_sink_info_by_name
WaitForOperation(pa_context_get_sink_info_by_name(m_Context, device.c_str(),SinkInfoCallback, &sinkStruct), m_MainLoop, "Get Sink Info");
}

if(!sinkStruct.device_found) // ActiveAE will open us again with a valid device name
{
CLog::Log(LOGERROR, "PulseAudio: Sink %s not found", device.c_str());
pa_threaded_mainloop_unlock(m_MainLoop);
Deinitialize();
return false;
}

// 200ms max latency
// 50ms min packet size
Expand Down Expand Up @@ -620,6 +651,31 @@ bool CAESinkPULSE::Initialize(AEAudioFormat &format, std::string &device)
return false;
}

//update local volume if we are in non passthrough mode
if (!m_passthrough)
{
unsigned int devidx = pa_stream_get_index(m_Stream);
SinkInputInfoStruct sii;
sii.mainloop = m_MainLoop;
bool success = WaitForOperation(pa_context_get_sink_input_info(m_Context, devidx, SinkInputInfoCallback, &sii), m_MainLoop, "Get Sink Input Info");
if(success && sii.is_valid)
{
// we don't have per channel values so average them
pa_volume_t p_vol = pa_cvolume_avg(&sii.volume);
// store it internally
m_Volume = sii.volume;
float sValue = std::min(p_vol / (float) PA_VOLUME_NORM, 1.0f);
CLog::Log(LOGDEBUG, "Restored Stream value to %f", sValue);
sValue = std::min(sValue, 1.0f);
g_application.SetVolume(sValue, false);
if (sii.mute && sValue > 0)
{
CLog::Log(LOGDEBUG, "PulseAudio: Stream is muted - perhaps was a user wish - if volume is changed we unmute");
}
}

}

const pa_buffer_attr *a;

if (!(a = pa_stream_get_buffer_attr(m_Stream)))
Expand Down Expand Up @@ -754,6 +810,59 @@ void CAESinkPULSE::Drain()
pa_threaded_mainloop_unlock(m_MainLoop);
}

void CAESinkPULSE::SetVolume(float volume)
{
if (m_IsAllocated && !m_passthrough)
{
pa_threaded_mainloop_lock(m_MainLoop);
bool external_change = false;
//check if internal volume does not match sink volume
unsigned int devidx = pa_stream_get_index(m_Stream);
SinkInputInfoStruct sii;
sii.mainloop = m_MainLoop;
bool success = WaitForOperation(pa_context_get_sink_input_info(m_Context, devidx, SinkInputInfoCallback, &sii), m_MainLoop, "Get Sink Input Info");
float sValue = 0.0f;
if(success && sii.is_valid)
{
// we don't have per channel values so average them
pa_volume_t n_vol = pa_cvolume_avg(&sii.volume);
pa_volume_t o_vol = pa_cvolume_avg(&m_Volume);
sValue = std::min(n_vol / (float) PA_VOLUME_NORM, 1.0f);
if (n_vol != o_vol)
{
external_change = true;
// update internal volume
m_Volume = sii.volume;
CLog::Log(LOGDEBUG, "Restored Volume cause of external change to value to %f", sValue);
g_application.SetVolume(sValue, false);
}
}
// unmute if we should not be muted
if (sii.mute && sValue > 0)
{
pa_operation *op = pa_context_set_sink_input_mute(m_Context, sii.index, 0, NULL, NULL);
if (op == NULL)
CLog::Log(LOGERROR, "PulseAudio: Failed to unmute the stream");
else
pa_operation_unref(op);
}
if (!external_change)
{
pa_volume_t pavolume = pa_sw_volume_from_linear(volume);
if ( pavolume <= 0 )
pa_cvolume_mute(&m_Volume, m_Channels);
else
pa_cvolume_set(&m_Volume, m_Channels, pavolume);
pa_operation *op = pa_context_set_sink_input_volume(m_Context, devidx, &m_Volume, NULL, NULL);
if (op == NULL)
CLog::Log(LOGERROR, "PulseAudio: Failed to set volume");
else
pa_operation_unref(op);
}
pa_threaded_mainloop_unlock(m_MainLoop);
}
}

void CAESinkPULSE::EnumerateDevicesEx(AEDeviceInfoList &list, bool force)
{
pa_context *context;
Expand Down
4 changes: 4 additions & 0 deletions xbmc/cores/AudioEngine/Sinks/AESinkPULSE.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ class CAESinkPULSE : public IAESink
virtual unsigned int AddPackets (uint8_t *data, unsigned int frames, bool hasAudio, bool blocking = false);
virtual void Drain ();

virtual bool HasVolume() { return true; };
virtual void SetVolume(float volume);

static void EnumerateDevicesEx(AEDeviceInfoList &list, bool force = false);
bool IsInitialized();
CCriticalSection m_sec;
Expand All @@ -61,6 +64,7 @@ class CAESinkPULSE : public IAESink
unsigned int m_Channels;

pa_stream *m_Stream;
pa_cvolume m_Volume;

pa_context *m_Context;
pa_threaded_mainloop *m_MainLoop;
Expand Down

0 comments on commit c927f64

Please sign in to comment.