Add framework for implementing Linux AudioManager
Source outputs must all be moved to new default source. Reference topic https://askubuntu.com/a/72076 * Adds microphone controls on Linux via PulseAudio (PA) for several points in https://github.com/OpenVR-Advanced-Settings/OpenVR-AdvancedSettings/issues/43. It can not be overstated how unsuited the public AudioManager API is for using the PA API. It can also not be overstated how undocumented and horrible the PA API is. The PA way of doing audio is very unlike the Windows way of doing audio, which the AudioManager is heavily influenced by. Since I don't have a Linux machine I have not been able to personally test this. https://github.com/feilen/ has graciously helped with testing. PulseAudio is very undocumented and not very widely used for what we need outside of official utilities. As such there's most likely a better way of doing things than I've done. Additional information can be found at https://github.com/username223/pulseaudio-device-changing. The "complex" PA API is asynchronous. Since our current AudioManager is running synchronously and would not work asynchronously (functions are expected to return valid data on function return) it is necessary to massage the PA API until it runs synchronously. This is done with a general workflow of 1. Register a callback function (`pa_context_get_source_info_list()`, for example). 2. Run the PA loop through `customPulseLoop()`. This will trigger any registered callbacks. Sometimes a `void*` is passed through the callback that can be used for custom data. `successCallback()` uses this to set the success variable. 3. Run the callback function and do required work. 4. Return to original function, do work or return values. It is not possible to run a `customPulseLoop()` inside of an already running loop (inside a callback), as this will lead to a PA assert error on runtime.
This commit is contained in:
@@ -6,6 +6,8 @@
|
||||
#include "../settings/settings_object.h"
|
||||
#ifdef _WIN32
|
||||
# include "audiomanager/AudioManagerWindows.h"
|
||||
#elif __linux__
|
||||
# include "audiomanager/AudioManagerPulse.h"
|
||||
#else
|
||||
# include "audiomanager/AudioManagerDummy.h"
|
||||
#endif
|
||||
@@ -18,6 +20,8 @@ void AudioTabController::initStage1()
|
||||
vr::EVRSettingsError vrSettingsError;
|
||||
#ifdef _WIN32
|
||||
audioManager.reset( new AudioManagerWindows() );
|
||||
#elif __linux__
|
||||
audioManager.reset( new AudioManagerPulse() );
|
||||
#else
|
||||
audioManager.reset( new AudioManagerDummy() );
|
||||
#endif
|
||||
@@ -453,29 +457,51 @@ int AudioTabController::getPlaybackDeviceCount()
|
||||
return static_cast<int>( m_playbackDevices.size() );
|
||||
}
|
||||
|
||||
bool indexIsValid( const int index,
|
||||
const std::vector<AudioDevice>& devices,
|
||||
const std::string& functionName ) noexcept
|
||||
{
|
||||
if ( index < 0 )
|
||||
{
|
||||
LOG( ERROR ) << functionName
|
||||
<< " called with index below zero: " << index;
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( static_cast<size_t>( index ) >= devices.size() )
|
||||
{
|
||||
LOG( ERROR ) << functionName
|
||||
<< " called with index greater than size of "
|
||||
"devices '"
|
||||
<< devices.size() << "', index '" << index << "'.";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QString AudioTabController::getPlaybackDeviceName( int index )
|
||||
{
|
||||
if ( index >= 0 && static_cast<size_t>( index ) < m_playbackDevices.size() )
|
||||
if ( indexIsValid( index, m_playbackDevices, "getPlaybackDeviceName" ) )
|
||||
{
|
||||
return QString::fromStdString(
|
||||
m_playbackDevices[static_cast<size_t>( index )].name() );
|
||||
m_playbackDevices.at( static_cast<size_t>( index ) ).name() );
|
||||
}
|
||||
else
|
||||
{
|
||||
return "<ERROR>";
|
||||
return "<PBN:ERROR>";
|
||||
}
|
||||
}
|
||||
|
||||
std::string AudioTabController::getPlaybackDeviceID( int index )
|
||||
{
|
||||
if ( index >= 0 && static_cast<size_t>( index ) < m_playbackDevices.size() )
|
||||
if ( indexIsValid( index, m_playbackDevices, "getPlaybackDeviceID" ) )
|
||||
{
|
||||
return m_playbackDevices[static_cast<size_t>( index )].id();
|
||||
return m_playbackDevices.at( static_cast<size_t>( index ) ).id();
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG( ERROR ) << "Playback Device does not have a unique id";
|
||||
return "<ERROR>";
|
||||
return "<PBI:ERROR>";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -491,27 +517,26 @@ int AudioTabController::getRecordingDeviceCount()
|
||||
|
||||
std::string AudioTabController::getRecordingDeviceID( int index )
|
||||
{
|
||||
if ( index >= 0 && static_cast<size_t>( index ) < m_playbackDevices.size() )
|
||||
if ( indexIsValid( index, m_recordingDevices, "getRecordingDeviceID" ) )
|
||||
{
|
||||
return m_recordingDevices[static_cast<size_t>( index )].id();
|
||||
return m_recordingDevices.at( static_cast<size_t>( index ) ).id();
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG( ERROR ) << "Recording Device does not have a unique id";
|
||||
return "<ERROR>";
|
||||
return "<RCI:ERROR>";
|
||||
}
|
||||
}
|
||||
|
||||
QString AudioTabController::getRecordingDeviceName( int index )
|
||||
{
|
||||
if ( index >= 0 && static_cast<size_t>( index ) < m_playbackDevices.size() )
|
||||
if ( indexIsValid( index, m_recordingDevices, "getRecordingDeviceName" ) )
|
||||
{
|
||||
return QString::fromStdString(
|
||||
m_recordingDevices[static_cast<size_t>( index )].name() );
|
||||
m_recordingDevices.at( static_cast<size_t>( index ) ).name() );
|
||||
}
|
||||
else
|
||||
{
|
||||
return "<ERROR>";
|
||||
return "<RCN:ERROR>";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -628,6 +653,7 @@ void AudioTabController::setMicDeviceIndex( int index, bool notify )
|
||||
// code to just change Mic
|
||||
audioManager->setMicDevice(
|
||||
m_recordingDevices[static_cast<size_t>( index )].id(), notify );
|
||||
m_recordingDeviceIndex = index;
|
||||
if ( tempProx )
|
||||
{
|
||||
setMicProximitySensorCanMute( true );
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
#include <easylogging++.h>
|
||||
#include "AudioManagerPulse.h"
|
||||
#include "AudioManagerPulse_internal.h"
|
||||
#include "../AudioTabController.h"
|
||||
|
||||
// Used to get the compiler to shut up about C4100: unreferenced formal
|
||||
// parameter. The cast is to get GCC to shut up about it.
|
||||
#define UNREFERENCED_PARAMETER( P ) static_cast<void>( ( P ) )
|
||||
|
||||
// application namespace
|
||||
namespace advsettings
|
||||
{
|
||||
AudioManagerPulse::~AudioManagerPulse()
|
||||
{
|
||||
restorePulseAudioState();
|
||||
}
|
||||
|
||||
void AudioManagerPulse::init( AudioTabController* controller )
|
||||
{
|
||||
m_controller = controller;
|
||||
|
||||
initializePulseAudio();
|
||||
}
|
||||
|
||||
void AudioManagerPulse::setPlaybackDevice( const std::string& id, bool notify )
|
||||
{
|
||||
setPlaybackDeviceInternal( id );
|
||||
|
||||
if ( notify )
|
||||
{
|
||||
m_controller->onNewPlaybackDevice();
|
||||
}
|
||||
}
|
||||
|
||||
std::string AudioManagerPulse::getPlaybackDevName()
|
||||
{
|
||||
return getCurrentDefaultPlaybackDeviceName();
|
||||
}
|
||||
|
||||
std::string AudioManagerPulse::getPlaybackDevId()
|
||||
{
|
||||
return getCurrentDefaultPlaybackDeviceId();
|
||||
}
|
||||
|
||||
void AudioManagerPulse::setMirrorDevice( const std::string& id, bool notify )
|
||||
{
|
||||
// noop
|
||||
UNREFERENCED_PARAMETER( id );
|
||||
UNREFERENCED_PARAMETER( notify );
|
||||
}
|
||||
|
||||
bool AudioManagerPulse::isMirrorValid()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string AudioManagerPulse::getMirrorDevName()
|
||||
{
|
||||
return "dummy";
|
||||
}
|
||||
|
||||
std::string AudioManagerPulse::getMirrorDevId()
|
||||
{
|
||||
return "dummy";
|
||||
}
|
||||
|
||||
float AudioManagerPulse::getMirrorVolume()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool AudioManagerPulse::setMirrorVolume( float value )
|
||||
{
|
||||
UNREFERENCED_PARAMETER( value );
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AudioManagerPulse::getMirrorMuted()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioManagerPulse::setMirrorMuted( bool value )
|
||||
{
|
||||
UNREFERENCED_PARAMETER( value );
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AudioManagerPulse::isMicValid()
|
||||
{
|
||||
return isMicrophoneValid();
|
||||
}
|
||||
|
||||
void AudioManagerPulse::setMicDevice( const std::string& id, bool notify )
|
||||
{
|
||||
setMicrophoneDevice( id );
|
||||
|
||||
if ( notify )
|
||||
{
|
||||
m_controller->onNewRecordingDevice();
|
||||
}
|
||||
}
|
||||
|
||||
std::string AudioManagerPulse::getMicDevName()
|
||||
{
|
||||
return getCurrentDefaultRecordingDeviceName();
|
||||
}
|
||||
|
||||
std::string AudioManagerPulse::getMicDevId()
|
||||
{
|
||||
return getCurrentDefaultRecordingDeviceId();
|
||||
}
|
||||
|
||||
float AudioManagerPulse::getMicVolume()
|
||||
{
|
||||
return getMicrophoneVolume();
|
||||
}
|
||||
|
||||
bool AudioManagerPulse::setMicVolume( float value )
|
||||
{
|
||||
if ( value > 1.0f )
|
||||
{
|
||||
value = 1.0f;
|
||||
}
|
||||
else if ( value < 0.0f )
|
||||
{
|
||||
value = 0.0f;
|
||||
}
|
||||
|
||||
return setMicrophoneVolume( value );
|
||||
}
|
||||
|
||||
bool AudioManagerPulse::getMicMuted()
|
||||
{
|
||||
return getMicrophoneMuted();
|
||||
}
|
||||
|
||||
bool AudioManagerPulse::setMicMuted( bool value )
|
||||
{
|
||||
return setMicMuteState( value );
|
||||
}
|
||||
|
||||
std::vector<AudioDevice> AudioManagerPulse::getRecordingDevices()
|
||||
{
|
||||
return returnRecordingDevices();
|
||||
}
|
||||
|
||||
std::vector<AudioDevice> AudioManagerPulse::getPlaybackDevices()
|
||||
{
|
||||
return returnPlaybackDevices();
|
||||
}
|
||||
|
||||
} // namespace advsettings
|
||||
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include "AudioManager.h"
|
||||
#include <pulse/pulseaudio.h>
|
||||
|
||||
namespace advsettings
|
||||
{
|
||||
class AudioManagerPulse : public AudioManager
|
||||
{
|
||||
public:
|
||||
virtual void init( AudioTabController* controller ) override;
|
||||
~AudioManagerPulse() override;
|
||||
|
||||
virtual void setPlaybackDevice( const std::string& id,
|
||||
bool notify = true ) override;
|
||||
virtual std::string getPlaybackDevName() override;
|
||||
virtual std::string getPlaybackDevId() override;
|
||||
|
||||
virtual void setMirrorDevice( const std::string& id,
|
||||
bool notify = true ) override;
|
||||
virtual bool isMirrorValid() override;
|
||||
virtual std::string getMirrorDevName() override;
|
||||
virtual std::string getMirrorDevId() override;
|
||||
virtual float getMirrorVolume() override;
|
||||
virtual bool setMirrorVolume( float value ) override;
|
||||
virtual bool getMirrorMuted() override;
|
||||
virtual bool setMirrorMuted( bool value ) override;
|
||||
|
||||
virtual bool isMicValid() override;
|
||||
virtual void setMicDevice( const std::string& id,
|
||||
bool notify = true ) override;
|
||||
virtual std::string getMicDevName() override;
|
||||
virtual std::string getMicDevId() override;
|
||||
virtual float getMicVolume() override;
|
||||
virtual bool setMicVolume( float value ) override;
|
||||
virtual bool getMicMuted() override;
|
||||
virtual bool setMicMuted( bool value ) override;
|
||||
|
||||
virtual std::vector<AudioDevice> getRecordingDevices() override;
|
||||
virtual std::vector<AudioDevice> getPlaybackDevices() override;
|
||||
|
||||
private:
|
||||
AudioTabController* m_controller;
|
||||
};
|
||||
|
||||
} // namespace advsettings
|
||||
@@ -0,0 +1,649 @@
|
||||
#pragma once
|
||||
#include <pulse/pulseaudio.h>
|
||||
#include <string>
|
||||
#include <easylogging++.h>
|
||||
#include "AudioManager.h"
|
||||
|
||||
// Used to get the compiler to shut up about C4100: unreferenced formal
|
||||
// parameter. The cast is to get GCC to shut up about it.
|
||||
#define UNREFERENCED_PARAMETER( P ) static_cast<void>( ( P ) )
|
||||
|
||||
namespace advsettings
|
||||
{
|
||||
enum class PulseAudioIsLastMeaning
|
||||
{
|
||||
Error,
|
||||
RealDevice,
|
||||
PreviousDeviceWasLastReal,
|
||||
};
|
||||
|
||||
enum class PulseAudioLoopControl
|
||||
{
|
||||
Stop,
|
||||
Run,
|
||||
};
|
||||
|
||||
struct
|
||||
{
|
||||
pa_mainloop* mainLoop;
|
||||
pa_mainloop_api* api;
|
||||
pa_context* context;
|
||||
} pulseAudioPointers;
|
||||
|
||||
struct
|
||||
{
|
||||
std::string defaultSinkOutputDeviceId;
|
||||
std::string defaultSourceInputDeviceId;
|
||||
|
||||
std::string originalDefaultOutputDeviceId;
|
||||
std::string originalDefaultInputDeviceId;
|
||||
|
||||
float originalDefaultOutputDeviceVolume;
|
||||
float originalDefaultInputDeviceVolume;
|
||||
|
||||
std::vector<AudioDevice> sinkOutputDevices;
|
||||
std::vector<AudioDevice> sourceInputDevices;
|
||||
|
||||
pa_sink_info currentDefaultSinkInfo;
|
||||
pa_source_info currentDefaultSourceInfo;
|
||||
} pulseAudioData;
|
||||
|
||||
static PulseAudioLoopControl loopControl = PulseAudioLoopControl::Run;
|
||||
|
||||
void customPulseLoop()
|
||||
{
|
||||
while ( loopControl == PulseAudioLoopControl::Run )
|
||||
{
|
||||
constexpr auto noReturnValue = nullptr;
|
||||
constexpr auto blockForEvents = 1;
|
||||
pa_mainloop_iterate(
|
||||
pulseAudioPointers.mainLoop, blockForEvents, noReturnValue );
|
||||
}
|
||||
|
||||
loopControl = PulseAudioLoopControl::Run;
|
||||
}
|
||||
|
||||
// Error function
|
||||
void dumpPulseAudioState()
|
||||
{
|
||||
LOG( ERROR ) << "____";
|
||||
|
||||
LOG( ERROR ) << "Dumping PulseAudio state: ";
|
||||
LOG( ERROR ) << "mainLoop: " << pulseAudioPointers.mainLoop;
|
||||
LOG( ERROR ) << "api: " << pulseAudioPointers.api;
|
||||
LOG( ERROR ) << "context: " << pulseAudioPointers.context;
|
||||
|
||||
LOG( ERROR ) << "";
|
||||
|
||||
LOG( ERROR ) << "Data:";
|
||||
LOG( ERROR ) << "\tdefaultSinkOutputDeviceId: "
|
||||
<< pulseAudioData.defaultSinkOutputDeviceId;
|
||||
LOG( ERROR ) << "\tdefaultSourceInputDeviceId: "
|
||||
<< pulseAudioData.defaultSourceInputDeviceId;
|
||||
|
||||
LOG( ERROR ) << "";
|
||||
|
||||
LOG( ERROR ) << "\tcurrentDefaultSinkInfo name: "
|
||||
<< pulseAudioData.currentDefaultSinkInfo.name;
|
||||
LOG( ERROR ) << "\tcurrentDefaultSourceInfo name: "
|
||||
<< pulseAudioData.currentDefaultSourceInfo.name;
|
||||
|
||||
LOG( ERROR ) << "";
|
||||
|
||||
LOG( ERROR ) << "sinkOutputDevices: ";
|
||||
LOG_IF( pulseAudioData.sinkOutputDevices.size() == 0, ERROR )
|
||||
<< "\tOutput devices size zero.";
|
||||
for ( const auto& device : pulseAudioData.sinkOutputDevices )
|
||||
{
|
||||
LOG( ERROR ) << "\tDevice Name: " << device.name();
|
||||
LOG( ERROR ) << "\tDevice Id: " << device.id();
|
||||
}
|
||||
|
||||
LOG( ERROR ) << "";
|
||||
|
||||
LOG( ERROR ) << "sourceInputDevices: ";
|
||||
LOG_IF( pulseAudioData.sourceInputDevices.size() == 0, ERROR )
|
||||
<< "\tInput devices size zero.";
|
||||
for ( const auto& device : pulseAudioData.sourceInputDevices )
|
||||
{
|
||||
LOG( ERROR ) << "\tDevice Name: " << device.name();
|
||||
LOG( ERROR ) << "\tDevice Id: " << device.id();
|
||||
}
|
||||
|
||||
LOG( ERROR ) << "____";
|
||||
}
|
||||
|
||||
PulseAudioIsLastMeaning getIsLastMeaning( const int isLast ) noexcept
|
||||
{
|
||||
if ( isLast < 0 )
|
||||
{
|
||||
LOG( ERROR ) << "Error in isLast.";
|
||||
dumpPulseAudioState();
|
||||
return PulseAudioIsLastMeaning::Error;
|
||||
}
|
||||
|
||||
if ( isLast > 0 )
|
||||
{
|
||||
return PulseAudioIsLastMeaning::PreviousDeviceWasLastReal;
|
||||
}
|
||||
|
||||
return PulseAudioIsLastMeaning::RealDevice;
|
||||
}
|
||||
|
||||
std::string getDeviceName( pa_proplist* p )
|
||||
{
|
||||
if ( !p )
|
||||
{
|
||||
LOG( ERROR ) << "proplist not valid.";
|
||||
}
|
||||
|
||||
constexpr auto deviceDescription = "device.description";
|
||||
if ( !pa_proplist_contains( p, deviceDescription ) )
|
||||
{
|
||||
LOG( ERROR ) << "proplist does not contain '" << deviceDescription
|
||||
<< "'.";
|
||||
return "ERROR";
|
||||
}
|
||||
|
||||
std::string s;
|
||||
s.assign( pa_proplist_gets( p, deviceDescription ) );
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
template <class T> void deviceCallback( const T* i, const int isLast )
|
||||
{
|
||||
static_assert(
|
||||
std::is_same<pa_source_info, T>::value
|
||||
|| std::is_same<pa_sink_info, T>::value,
|
||||
"Function should only be used with pa_source_info or pa_sink_info." );
|
||||
|
||||
const auto deviceState = getIsLastMeaning( isLast );
|
||||
if ( deviceState == PulseAudioIsLastMeaning::PreviousDeviceWasLastReal )
|
||||
{
|
||||
loopControl = PulseAudioLoopControl::Stop;
|
||||
return;
|
||||
}
|
||||
else if ( deviceState == PulseAudioIsLastMeaning::Error )
|
||||
{
|
||||
LOG( ERROR ) << "Error in deviceCallback function.";
|
||||
dumpPulseAudioState();
|
||||
loopControl = PulseAudioLoopControl::Stop;
|
||||
return;
|
||||
}
|
||||
|
||||
if constexpr ( std::is_same<pa_source_info, T>::value )
|
||||
{
|
||||
if ( i->name == pulseAudioData.defaultSourceInputDeviceId )
|
||||
{
|
||||
pulseAudioData.currentDefaultSourceInfo = *i;
|
||||
}
|
||||
|
||||
LOG( DEBUG ) << "Adding device to input: '" << i->name << "', '"
|
||||
<< getDeviceName( i->proplist ) << "'.";
|
||||
pulseAudioData.sourceInputDevices.push_back(
|
||||
AudioDevice( i->name, getDeviceName( i->proplist ) ) );
|
||||
}
|
||||
|
||||
else if constexpr ( std::is_same<pa_sink_info, T>::value )
|
||||
{
|
||||
if ( i->name == pulseAudioData.defaultSinkOutputDeviceId )
|
||||
{
|
||||
pulseAudioData.currentDefaultSinkInfo = *i;
|
||||
}
|
||||
|
||||
LOG( DEBUG ) << "Adding device to output: '" << i->name << "', '"
|
||||
<< getDeviceName( i->proplist ) << "'.";
|
||||
pulseAudioData.sinkOutputDevices.push_back(
|
||||
AudioDevice( i->name, getDeviceName( i->proplist ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
void setInputDevicesCallback( pa_context* c,
|
||||
const pa_source_info* i,
|
||||
int isLast,
|
||||
void* userdata )
|
||||
{
|
||||
UNREFERENCED_PARAMETER( userdata );
|
||||
UNREFERENCED_PARAMETER( c );
|
||||
|
||||
deviceCallback( i, isLast );
|
||||
}
|
||||
|
||||
void setOutputDevicesCallback( pa_context* c,
|
||||
const pa_sink_info* i,
|
||||
int isLast,
|
||||
void* userdata )
|
||||
{
|
||||
UNREFERENCED_PARAMETER( userdata );
|
||||
UNREFERENCED_PARAMETER( c );
|
||||
|
||||
deviceCallback( i, isLast );
|
||||
}
|
||||
|
||||
void getDefaultDevicesCallback( pa_context* c,
|
||||
const pa_server_info* i,
|
||||
void* userdata )
|
||||
{
|
||||
UNREFERENCED_PARAMETER( c );
|
||||
UNREFERENCED_PARAMETER( userdata );
|
||||
|
||||
if ( !i )
|
||||
{
|
||||
LOG( ERROR ) << "i == 0";
|
||||
pulseAudioData.defaultSinkOutputDeviceId = "DDO:ERROR";
|
||||
pulseAudioData.defaultSourceInputDeviceId = "DDI:ERROR";
|
||||
return;
|
||||
}
|
||||
|
||||
// Copy because we don't know how long the pa_server_info* lives for
|
||||
pulseAudioData.defaultSinkOutputDeviceId.assign( i->default_sink_name );
|
||||
pulseAudioData.defaultSourceInputDeviceId.assign( i->default_source_name );
|
||||
|
||||
loopControl = PulseAudioLoopControl::Stop;
|
||||
|
||||
LOG( DEBUG ) << "getDefaultDevicesCallback done with sink output device: '"
|
||||
<< pulseAudioData.defaultSinkOutputDeviceId
|
||||
<< "' and source input '"
|
||||
<< pulseAudioData.defaultSourceInputDeviceId << "'.";
|
||||
}
|
||||
|
||||
void stateCallbackFunction( pa_context* c, void* userdata )
|
||||
{
|
||||
UNREFERENCED_PARAMETER( c );
|
||||
UNREFERENCED_PARAMETER( userdata );
|
||||
|
||||
switch ( pa_context_get_state( c ) )
|
||||
{
|
||||
case PA_CONTEXT_TERMINATED:
|
||||
LOG( ERROR ) << "PA_CONTEXT_TERMINATED in stateCallbackFunction";
|
||||
dumpPulseAudioState();
|
||||
return;
|
||||
case PA_CONTEXT_CONNECTING:
|
||||
LOG( DEBUG ) << "PA_CONTEXT_CONNECTING";
|
||||
return;
|
||||
case PA_CONTEXT_AUTHORIZING:
|
||||
LOG( DEBUG ) << "PA_CONTEXT_AUTHORIZING";
|
||||
return;
|
||||
case PA_CONTEXT_SETTING_NAME:
|
||||
LOG( DEBUG ) << "PA_CONTEXT_SETTING_NAME";
|
||||
return;
|
||||
case PA_CONTEXT_UNCONNECTED:
|
||||
LOG( DEBUG ) << "PA_CONTEXT_UNCONNECTED";
|
||||
return;
|
||||
case PA_CONTEXT_FAILED:
|
||||
LOG( DEBUG ) << "PA_CONTEXT_FAILED";
|
||||
return;
|
||||
|
||||
case PA_CONTEXT_READY:
|
||||
LOG( DEBUG ) << "PA_CONTEXT_READY";
|
||||
loopControl = PulseAudioLoopControl::Stop;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void updateAllPulseData()
|
||||
{
|
||||
constexpr auto noCustomUserdata = nullptr;
|
||||
|
||||
pa_context_get_server_info( pulseAudioPointers.context,
|
||||
getDefaultDevicesCallback,
|
||||
noCustomUserdata );
|
||||
customPulseLoop();
|
||||
|
||||
pulseAudioData.sinkOutputDevices.clear();
|
||||
pa_context_get_sink_info_list( pulseAudioPointers.context,
|
||||
setOutputDevicesCallback,
|
||||
noCustomUserdata );
|
||||
customPulseLoop();
|
||||
|
||||
pulseAudioData.sourceInputDevices.clear();
|
||||
pa_context_get_source_info_list(
|
||||
pulseAudioPointers.context, setInputDevicesCallback, noCustomUserdata );
|
||||
customPulseLoop();
|
||||
|
||||
LOG( DEBUG ) << "updateAllPulseData done.";
|
||||
}
|
||||
|
||||
void successCallback( pa_context* c, int success, void* successVariable )
|
||||
{
|
||||
UNREFERENCED_PARAMETER( c );
|
||||
|
||||
if ( successVariable != nullptr )
|
||||
{
|
||||
*static_cast<bool*>( successVariable ) = success;
|
||||
}
|
||||
|
||||
if ( !success )
|
||||
{
|
||||
LOG( ERROR ) << "Non successful callback operation.";
|
||||
dumpPulseAudioState();
|
||||
}
|
||||
|
||||
loopControl = PulseAudioLoopControl::Stop;
|
||||
}
|
||||
|
||||
void setPlaybackDeviceInternal( const std::string& id )
|
||||
{
|
||||
updateAllPulseData();
|
||||
|
||||
auto success = false;
|
||||
pa_context_set_default_sink(
|
||||
pulseAudioPointers.context, id.c_str(), successCallback, &success );
|
||||
|
||||
customPulseLoop();
|
||||
|
||||
if ( !success )
|
||||
{
|
||||
LOG( ERROR ) << "setPlaybackDeviceInternal failed to set default sink "
|
||||
"for device '"
|
||||
<< id << "'.";
|
||||
}
|
||||
|
||||
LOG( DEBUG ) << "setPlaybackDeviceInternal done with id: " << id;
|
||||
}
|
||||
|
||||
std::string getCurrentDefaultPlaybackDeviceName()
|
||||
{
|
||||
updateAllPulseData();
|
||||
|
||||
for ( const auto& dev : pulseAudioData.sinkOutputDevices )
|
||||
{
|
||||
if ( dev.id() == pulseAudioData.defaultSinkOutputDeviceId )
|
||||
{
|
||||
LOG( DEBUG ) << "getCurrentDefaultPlaybackDeviceName done with "
|
||||
<< dev.name();
|
||||
return dev.name();
|
||||
}
|
||||
}
|
||||
LOG( ERROR ) << "Unable to find default playback device.";
|
||||
|
||||
return "ERROR";
|
||||
}
|
||||
|
||||
std::string getCurrentDefaultPlaybackDeviceId()
|
||||
{
|
||||
updateAllPulseData();
|
||||
|
||||
LOG( DEBUG ) << "getCurrentDefaultPlaybackDeviceId done with "
|
||||
<< pulseAudioData.defaultSinkOutputDeviceId;
|
||||
|
||||
return pulseAudioData.defaultSinkOutputDeviceId;
|
||||
}
|
||||
|
||||
std::string getCurrentDefaultRecordingDeviceName()
|
||||
{
|
||||
updateAllPulseData();
|
||||
|
||||
for ( const auto& dev : pulseAudioData.sourceInputDevices )
|
||||
{
|
||||
if ( dev.id() == pulseAudioData.defaultSourceInputDeviceId )
|
||||
{
|
||||
LOG( DEBUG ) << "getCurrentDefaultRecordingDeviceName done with: "
|
||||
<< dev.name();
|
||||
return dev.name();
|
||||
}
|
||||
}
|
||||
LOG( ERROR ) << "Unable to find default playback device.";
|
||||
|
||||
return "ERROR";
|
||||
}
|
||||
|
||||
std::string getCurrentDefaultRecordingDeviceId()
|
||||
{
|
||||
updateAllPulseData();
|
||||
|
||||
LOG( DEBUG ) << "getCurrentDefaultRecordingDeviceId done with "
|
||||
<< pulseAudioData.defaultSourceInputDeviceId;
|
||||
|
||||
return pulseAudioData.defaultSourceInputDeviceId;
|
||||
}
|
||||
|
||||
std::vector<AudioDevice> returnRecordingDevices()
|
||||
{
|
||||
updateAllPulseData();
|
||||
|
||||
return pulseAudioData.sourceInputDevices;
|
||||
}
|
||||
|
||||
std::vector<AudioDevice> returnPlaybackDevices()
|
||||
{
|
||||
updateAllPulseData();
|
||||
|
||||
return pulseAudioData.sinkOutputDevices;
|
||||
}
|
||||
|
||||
bool isMicrophoneValid()
|
||||
{
|
||||
updateAllPulseData();
|
||||
|
||||
const auto valid = pulseAudioData.defaultSourceInputDeviceId != "";
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
float getMicrophoneVolume()
|
||||
{
|
||||
updateAllPulseData();
|
||||
|
||||
const auto linearVolume = pa_sw_volume_to_linear(
|
||||
pa_cvolume_avg( &pulseAudioData.currentDefaultSourceInfo.volume ) );
|
||||
|
||||
return static_cast<float>( linearVolume );
|
||||
}
|
||||
|
||||
bool getMicrophoneMuted()
|
||||
{
|
||||
updateAllPulseData();
|
||||
|
||||
return pulseAudioData.currentDefaultSourceInfo.mute;
|
||||
}
|
||||
|
||||
void sourceOutputCallback( pa_context* c,
|
||||
const pa_source_output_info* i,
|
||||
int isLast,
|
||||
void* success )
|
||||
{
|
||||
const auto deviceState = getIsLastMeaning( isLast );
|
||||
if ( deviceState == PulseAudioIsLastMeaning::PreviousDeviceWasLastReal )
|
||||
{
|
||||
loopControl = PulseAudioLoopControl::Stop;
|
||||
return;
|
||||
}
|
||||
else if ( deviceState == PulseAudioIsLastMeaning::Error )
|
||||
{
|
||||
LOG( ERROR ) << "Error in sourceOutputCallback function.";
|
||||
dumpPulseAudioState();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto sourceOutputIndex = i->index;
|
||||
const auto sourceIndex = pulseAudioData.currentDefaultSourceInfo.index;
|
||||
|
||||
LOG( DEBUG ) << "Attempting to move sourceOutputIndex: '"
|
||||
<< sourceOutputIndex << "' to sourceIndex '" << sourceIndex
|
||||
<< "' with source output name " << i->name << ".";
|
||||
|
||||
pa_context_move_source_output_by_index(
|
||||
c, sourceOutputIndex, sourceIndex, successCallback, &success );
|
||||
}
|
||||
|
||||
void setMicrophoneDevice( const std::string& id )
|
||||
{
|
||||
LOG( DEBUG ) << "setMicrophoneDevice called with 'id': " << id;
|
||||
|
||||
updateAllPulseData();
|
||||
|
||||
auto success = false;
|
||||
pa_context_set_default_source(
|
||||
pulseAudioPointers.context, id.c_str(), successCallback, &success );
|
||||
|
||||
customPulseLoop();
|
||||
|
||||
if ( !success )
|
||||
{
|
||||
LOG( ERROR ) << "Error setting microphone device for '" << id << "'.";
|
||||
}
|
||||
|
||||
updateAllPulseData();
|
||||
|
||||
pa_context_get_source_output_info_list(
|
||||
pulseAudioPointers.context, sourceOutputCallback, &success );
|
||||
|
||||
customPulseLoop();
|
||||
|
||||
if ( !success )
|
||||
{
|
||||
LOG( ERROR ) << "Error in moving source outputs to new source.";
|
||||
}
|
||||
|
||||
updateAllPulseData();
|
||||
|
||||
LOG( DEBUG ) << "setMicrophoneDevice done.";
|
||||
}
|
||||
|
||||
bool setPlaybackVolume( const float volume )
|
||||
{
|
||||
LOG( DEBUG ) << "setPlaybackVolume called with 'volume': " << volume;
|
||||
|
||||
updateAllPulseData();
|
||||
|
||||
auto pulseVolume = pulseAudioData.currentDefaultSinkInfo.volume;
|
||||
const auto vol = pa_sw_volume_from_linear( static_cast<double>( volume ) );
|
||||
|
||||
pa_cvolume_set( &pulseVolume, pulseVolume.channels, vol );
|
||||
|
||||
auto success = false;
|
||||
pa_context_set_sink_volume_by_name(
|
||||
pulseAudioPointers.context,
|
||||
pulseAudioData.defaultSinkOutputDeviceId.c_str(),
|
||||
&pulseVolume,
|
||||
successCallback,
|
||||
&success );
|
||||
|
||||
customPulseLoop();
|
||||
|
||||
if ( !success )
|
||||
{
|
||||
LOG( ERROR ) << "setPlaybackVolume failed to set volume '" << volume
|
||||
<< "' for device '"
|
||||
<< pulseAudioData.defaultSinkOutputDeviceId << "'.";
|
||||
}
|
||||
|
||||
LOG( DEBUG ) << "setPlaybackVolume done with 'success': " << success;
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool setMicrophoneVolume( const float volume )
|
||||
{
|
||||
LOG( DEBUG ) << "setMicrophoneVolume called with 'volume': " << volume;
|
||||
|
||||
updateAllPulseData();
|
||||
|
||||
auto pulseVolume = pulseAudioData.currentDefaultSourceInfo.volume;
|
||||
const auto vol = pa_sw_volume_from_linear( static_cast<double>( volume ) );
|
||||
|
||||
pa_cvolume_set( &pulseVolume, pulseVolume.channels, vol );
|
||||
|
||||
auto success = false;
|
||||
pa_context_set_source_volume_by_name(
|
||||
pulseAudioPointers.context,
|
||||
pulseAudioData.defaultSourceInputDeviceId.c_str(),
|
||||
&pulseVolume,
|
||||
successCallback,
|
||||
&success );
|
||||
|
||||
customPulseLoop();
|
||||
|
||||
if ( !success )
|
||||
{
|
||||
LOG( ERROR ) << "seMicrophoneVolume failed to set volume '" << volume
|
||||
<< "' for device '"
|
||||
<< pulseAudioData.defaultSourceInputDeviceId << "'.";
|
||||
}
|
||||
|
||||
LOG( DEBUG ) << "setMicrophoneVolume done with 'success': " << success;
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool setMicMuteState( const bool muted )
|
||||
{
|
||||
LOG( DEBUG ) << "setMicMuteState called with 'muted': " << muted;
|
||||
bool success = false;
|
||||
|
||||
pa_context_set_source_mute_by_name(
|
||||
pulseAudioPointers.context,
|
||||
pulseAudioData.defaultSourceInputDeviceId.c_str(),
|
||||
muted,
|
||||
successCallback,
|
||||
&success );
|
||||
|
||||
customPulseLoop();
|
||||
|
||||
if ( !success )
|
||||
{
|
||||
LOG( ERROR ) << "setMicMuteState failed to set muted '" << muted
|
||||
<< "' for device '"
|
||||
<< pulseAudioData.defaultSourceInputDeviceId << "'.";
|
||||
}
|
||||
|
||||
LOG( DEBUG ) << "setMicMuteState done with 'success': " << success;
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
void restorePulseAudioState()
|
||||
{
|
||||
LOG( DEBUG ) << "restorePulseAudioState called.";
|
||||
|
||||
setPlaybackDeviceInternal( pulseAudioData.originalDefaultOutputDeviceId );
|
||||
setPlaybackVolume( pulseAudioData.originalDefaultOutputDeviceVolume );
|
||||
|
||||
setMicrophoneDevice( pulseAudioData.originalDefaultInputDeviceId );
|
||||
setMicrophoneVolume( pulseAudioData.originalDefaultInputDeviceVolume );
|
||||
|
||||
LOG( DEBUG ) << "restorePulseAudioState done.";
|
||||
}
|
||||
|
||||
void initializePulseAudio()
|
||||
{
|
||||
LOG( DEBUG ) << "initializePulseAudio called.";
|
||||
|
||||
pulseAudioPointers.mainLoop = pa_mainloop_new();
|
||||
|
||||
pulseAudioPointers.api = pa_mainloop_get_api( pulseAudioPointers.mainLoop );
|
||||
|
||||
pulseAudioPointers.context
|
||||
= pa_context_new( pulseAudioPointers.api, "openvr-advanced-settings" );
|
||||
|
||||
constexpr auto noCustomUserdata = nullptr;
|
||||
pa_context_set_state_callback(
|
||||
pulseAudioPointers.context, stateCallbackFunction, noCustomUserdata );
|
||||
|
||||
constexpr auto useDefaultServer = nullptr;
|
||||
constexpr auto useDefaultSpawnApi = nullptr;
|
||||
pa_context_connect( pulseAudioPointers.context,
|
||||
useDefaultServer,
|
||||
PA_CONTEXT_NOFLAGS,
|
||||
useDefaultSpawnApi );
|
||||
customPulseLoop();
|
||||
|
||||
pulseAudioData.originalDefaultInputDeviceId
|
||||
= getCurrentDefaultRecordingDeviceId();
|
||||
|
||||
pulseAudioData.originalDefaultInputDeviceVolume
|
||||
= static_cast<float>( pa_sw_volume_to_linear( pa_cvolume_avg(
|
||||
&pulseAudioData.currentDefaultSourceInfo.volume ) ) );
|
||||
|
||||
pulseAudioData.originalDefaultOutputDeviceId
|
||||
= getCurrentDefaultPlaybackDeviceId();
|
||||
|
||||
pulseAudioData.originalDefaultOutputDeviceVolume
|
||||
= static_cast<float>( pa_sw_volume_to_linear(
|
||||
pa_cvolume_avg( &pulseAudioData.currentDefaultSinkInfo.volume ) ) );
|
||||
|
||||
LOG( DEBUG ) << "initializePulseAudio finished.";
|
||||
}
|
||||
} // namespace advsettings
|
||||
Reference in New Issue
Block a user