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:
Vasily
2020-04-08 22:44:12 +02:00
committed by username223
parent 9225956e8a
commit bb30a7c041
8 changed files with 924 additions and 22 deletions
+6
View File
@@ -54,6 +54,9 @@ update_and_install_prereqs: &update_and_install_prereqs
- run:
name: Install X11 packages
command: apt-get install libx11-dev libxt-dev libxtst-dev -y
- run:
name: Install pulse packages
command: apt-get install libpulse-dev -y
- run:
name: Install bear/clang-tidy
command: apt-get install bear clang-tidy -y
@@ -125,6 +128,9 @@ jobs:
- run:
name: Install X11 packages
command: sudo apt-get install libx11-dev libxt-dev libxtst-dev -y
- run:
name: Install pulse packages
command: sudo apt-get install libpulse-dev -y
- run:
name: Install bear/clang-tidy
command: sudo apt-get install bear clang-tidy -y
+1 -1
View File
@@ -57,7 +57,7 @@ install:
- sudo apt-get update -qq
- sudo apt-get install build-essential libgl1-mesa-dev
- sudo apt-get install qt512-meta-minimal qt512multimedia qt512declarative qt512quickcontrols2 qt512tools qt512base
- sudo apt-get install libx11-dev libxt-dev libxtst-dev
- sudo apt-get install libx11-dev libxt-dev libxtst-dev libpulse-dev
- sudo apt-get install bear clang-tidy
- qtchooser -install opt-qt512 /opt/qt512/bin/qmake
+12 -3
View File
@@ -37,7 +37,6 @@ HEADERS += src/overlaycontroller.h \
src/utils/Matrix.h \
src/utils/ChaperoneUtils.h \
src/quaternion/quaternion.h \
src/tabcontrollers/audiomanager/AudioManagerDummy.h \
src/openvr/openvr_init.h \
src/openvr/ivrinput_action.h \
src/openvr/ivrinput_manifest.h \
@@ -86,8 +85,18 @@ unix:!macx {
SOURCES += src/media_keys/media_keys_dummy.cpp
}
SOURCES += src/tabcontrollers/audiomanager/AudioManagerDummy.cpp
HEADERS += src/tabcontrollers/audiomanager/AudioManagerDummy.h
!noPulse {
message(PulseAudio features enabled.)
SOURCES += src/tabcontrollers/audiomanager/AudioManagerPulse.cpp
HEADERS += src/tabcontrollers/audiomanager/AudioManagerPulse.h \
src/tabcontrollers/audiomanager/AudioManagerPulse_internal.h
LIBS += -lpulse
}
else {
message(PulseAudio features disabled.)
SOURCES += src/tabcontrollers/audiomanager/AudioManagerDummy.cpp
HEADERS += src/tabcontrollers/audiomanager/AudioManagerDummy.h
}
}
macx {
+17 -4
View File
@@ -16,6 +16,7 @@
* [qtchooser and versions](#qtchooser-and-versions)
* [X11](#x11)
* [DBUS](#dbus)
* [PulseAudio](#pulseaudio)
* [SteamVR](#steamvr)
* [clang-tidy and bear](#clang-tidy-and-bear)
* [TL;DRs](#tldrs)
@@ -188,6 +189,12 @@ DBUS is needed for controlling media players from VR.
This feature and dependency can be disabled during compilation.
## PulseAudio
PulseAudio is needed for controlling audio devices through the Audio tab.
This feature and dependency can be disabled during compilation.
## SteamVR
On a completely fresh Ubuntu install the program also requires the following libraries to run.
@@ -226,7 +233,7 @@ sudo apt-get -y install build-essential libgl1-mesa-dev
# Install Qt
sudo apt-get -y install qt512-meta-full qtchooser
# Install additional features
sudo apt-get -y install libx11-dev libxt-dev libxtst-dev
sudo apt-get -y install libx11-dev libxt-dev libxtst-dev libpulse-dev
# Install necessary SteamVR libraries
sudo apt-get -y install libsdl2-dev libvulkan-dev
# Set up qtchooser
@@ -253,7 +260,7 @@ sudo apt-get -y install build-essential libgl1-mesa-dev
# Install Qt
sudo apt-get -y install qt512-meta-full qtchooser
# Install additional features
sudo apt-get -y install libx11-dev libxt-dev libxtst-dev
sudo apt-get -y install libx11-dev libxt-dev libxtst-dev libpulse-dev
# Install necessary SteamVR libraries
sudo apt-get -y install libsdl2-dev libvulkan-dev
# Set up qtchooser
@@ -277,7 +284,7 @@ sudo apt-get -y install build-essential libgl1-mesa-dev
# Install Qt
sudo apt-get -y install qt5-default qtmultimedia5-dev libqt5multimediawidgets5 libqt5multimedia5-plugins libqt5multimedia5 qml-module-qtquick-extras qml-module-qtquick-controls2 qml-module-qtquick-dialogs qtdeclarative5-dev qtchooser
# Install additional features
sudo apt-get -y install libx11-dev libxt-dev libxtst-dev
sudo apt-get -y install libx11-dev libxt-dev libxtst-dev libpulse-dev
# Install necessary SteamVR libraries
sudo apt-get -y install libsdl2-dev libvulkan-dev
# Set up qtchooser
@@ -300,7 +307,7 @@ sudo pacman --noconfirm -S base-devel mesa libudev0-shim
# Install Qt
sudo pacman --noconfirm -S qt5-declarative qt5-multimedia
# Install additional features
sudo pacman --noconfirm -S xorg-server dbus
sudo pacman --noconfirm -S xorg-server dbus libpulse-dev
# Compile
qmake
make -j2
@@ -334,6 +341,7 @@ The following values can be appended to the `CONFIG` internal variable in order
| ----- | ------- |
| `noX11` | Disables X11 specific features (VR to keyboard input). |
| `noDBUS` | Disables DBUS specific features (control media players). |
| `noPulse` | Disables PulseAudio specific features (change audio devices). |
| `debugSymbolsAndLogs` | Enables debug symbols and debug logging calls (while still having release optimizations). |
The values are case sensitive.
@@ -348,6 +356,11 @@ If you want to disable both X11 and DBUS features you would type
qmake CONFIG+=noX11 CONFIG+=noDBUS
```
If you want to disable X11, DBUS and PulseAudio features you would type
```bash
qmake CONFIG+=noX11 CONFIG+=noDBUS CONFIG+=noPulse
```
The location for `make install` can be configured in the `qmake` step.
This is done by setting the `PREFIX` variable.
```bash
+40 -14
View File
@@ -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