audio-orchestra/audio/orchestra/api/PulseDeviceList.cpp

364 lines
13 KiB
C++

/** @file
* @author Edouard DUPIN
* @copyright 2011, Edouard DUPIN, all right reserved
* @license APACHE v2.0 (see license file)
* @fork from RTAudio
*/
#if defined(ORCHESTRA_BUILD_PULSE)
extern "C" {
#include <stdio.h>
#include <string.h>
}
#include <pulse/pulseaudio.h>
#include <audio/orchestra/api/PulseDeviceList.hpp>
#include <audio/orchestra/debug.hpp>
#include <audio/Time.hpp>
#include <audio/Duration.hpp>
#include <audio/format.hpp>
#include <etk/stdTools.hpp>
// This callback gets called when our context changes state. We really only
// care about when it's ready or if it has failed
static void callbackStateMachine(pa_context* _contex, void *_userdata) {
pa_context_state_t state;
int *pulseAudioReady = static_cast<int*>(_userdata);
state = pa_context_get_state(_contex);
switch (state) {
// There are just here for reference
case PA_CONTEXT_UNCONNECTED:
ATA_VERBOSE("pulse state: PA_CONTEXT_UNCONNECTED");
break;
case PA_CONTEXT_CONNECTING:
ATA_VERBOSE("pulse state: PA_CONTEXT_CONNECTING");
break;
case PA_CONTEXT_AUTHORIZING:
ATA_VERBOSE("pulse state: PA_CONTEXT_AUTHORIZING");
break;
case PA_CONTEXT_SETTING_NAME:
ATA_VERBOSE("pulse state: PA_CONTEXT_SETTING_NAME");
break;
default:
ATA_VERBOSE("pulse state: default");
break;
case PA_CONTEXT_FAILED:
*pulseAudioReady = 2;
ATA_VERBOSE("pulse state: PA_CONTEXT_FAILED");
break;
case PA_CONTEXT_TERMINATED:
*pulseAudioReady = 2;
ATA_VERBOSE("pulse state: PA_CONTEXT_TERMINATED");
break;
case PA_CONTEXT_READY:
*pulseAudioReady = 1;
ATA_VERBOSE("pulse state: PA_CONTEXT_READY");
break;
}
}
static audio::format getFormatFromPulseFormat(enum pa_sample_format _format) {
switch (_format) {
case PA_SAMPLE_U8:
return audio::format_int8;
break;
case PA_SAMPLE_ALAW:
ATA_ERROR("Not supported: uint8_t a-law");
return audio::format_unknow;
case PA_SAMPLE_ULAW:
ATA_ERROR("Not supported: uint8_t mu-law");
return audio::format_unknow;
case PA_SAMPLE_S16LE:
return audio::format_int16;
break;
case PA_SAMPLE_S16BE:
return audio::format_int16;
break;
case PA_SAMPLE_FLOAT32LE:
return audio::format_float;
break;
case PA_SAMPLE_FLOAT32BE:
return audio::format_float;
break;
case PA_SAMPLE_S32LE:
return audio::format_int32;
break;
case PA_SAMPLE_S32BE:
return audio::format_int32;
break;
case PA_SAMPLE_S24LE:
return audio::format_int24;
break;
case PA_SAMPLE_S24BE:
return audio::format_int24;
break;
case PA_SAMPLE_S24_32LE:
return audio::format_int24_on_int32;
break;
case PA_SAMPLE_S24_32BE:
return audio::format_int24_on_int32;
break;
case PA_SAMPLE_INVALID:
case PA_SAMPLE_MAX:
ATA_ERROR("Not supported: invalid");
return audio::format_unknow;
}
ATA_ERROR("Not supported: UNKNOW flag...");
return audio::format_unknow;
}
static etk::Vector<audio::channel> getChannelOrderFromPulseChannel(const struct pa_channel_map& _map) {
etk::Vector<audio::channel> out;
for (int32_t iii=0; iii<_map.channels; ++iii) {
switch(_map.map[iii]) {
default:
case PA_CHANNEL_POSITION_MAX:
case PA_CHANNEL_POSITION_INVALID:
out.pushBack(audio::channel_unknow);
break;
case PA_CHANNEL_POSITION_MONO:
case PA_CHANNEL_POSITION_FRONT_CENTER:
out.pushBack(audio::channel_frontCenter);
break;
case PA_CHANNEL_POSITION_FRONT_LEFT:
out.pushBack(audio::channel_frontLeft);
break;
case PA_CHANNEL_POSITION_FRONT_RIGHT:
out.pushBack(audio::channel_frontRight);
break;
case PA_CHANNEL_POSITION_REAR_CENTER:
out.pushBack(audio::channel_rearCenter);
break;
case PA_CHANNEL_POSITION_REAR_LEFT:
out.pushBack(audio::channel_rearLeft);
break;
case PA_CHANNEL_POSITION_REAR_RIGHT:
out.pushBack(audio::channel_rearRight);
break;
case PA_CHANNEL_POSITION_LFE:
out.pushBack(audio::channel_lfe);
break;
case PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER:
out.pushBack(audio::channel_centerLeft);
break;
case PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER:
out.pushBack(audio::channel_centerRight);
break;
case PA_CHANNEL_POSITION_SIDE_LEFT:
out.pushBack(audio::channel_topCenterLeft);
break;
case PA_CHANNEL_POSITION_SIDE_RIGHT:
out.pushBack(audio::channel_topCenterRight);
break;
case PA_CHANNEL_POSITION_TOP_CENTER:
case PA_CHANNEL_POSITION_TOP_FRONT_CENTER:
out.pushBack(audio::channel_topFrontCenter);
break;
case PA_CHANNEL_POSITION_TOP_FRONT_LEFT:
out.pushBack(audio::channel_topFrontLeft);
break;
case PA_CHANNEL_POSITION_TOP_FRONT_RIGHT:
out.pushBack(audio::channel_topFrontRight);
break;
case PA_CHANNEL_POSITION_TOP_REAR_LEFT:
out.pushBack(audio::channel_topRearLeft);
break;
case PA_CHANNEL_POSITION_TOP_REAR_RIGHT:
out.pushBack(audio::channel_topRearRight);
break;
case PA_CHANNEL_POSITION_TOP_REAR_CENTER:
out.pushBack(audio::channel_topRearCenter);
break;
case PA_CHANNEL_POSITION_AUX0: out.pushBack(audio::channel_aux0); break;
case PA_CHANNEL_POSITION_AUX1: out.pushBack(audio::channel_aux1); break;
case PA_CHANNEL_POSITION_AUX2: out.pushBack(audio::channel_aux2); break;
case PA_CHANNEL_POSITION_AUX3: out.pushBack(audio::channel_aux3); break;
case PA_CHANNEL_POSITION_AUX4: out.pushBack(audio::channel_aux4); break;
case PA_CHANNEL_POSITION_AUX5: out.pushBack(audio::channel_aux5); break;
case PA_CHANNEL_POSITION_AUX6: out.pushBack(audio::channel_aux6); break;
case PA_CHANNEL_POSITION_AUX7: out.pushBack(audio::channel_aux7); break;
case PA_CHANNEL_POSITION_AUX8: out.pushBack(audio::channel_aux8); break;
case PA_CHANNEL_POSITION_AUX9: out.pushBack(audio::channel_aux9); break;
case PA_CHANNEL_POSITION_AUX10: out.pushBack(audio::channel_aux10); break;
case PA_CHANNEL_POSITION_AUX11: out.pushBack(audio::channel_aux11); break;
case PA_CHANNEL_POSITION_AUX12: out.pushBack(audio::channel_aux12); break;
case PA_CHANNEL_POSITION_AUX13: out.pushBack(audio::channel_aux13); break;
case PA_CHANNEL_POSITION_AUX14: out.pushBack(audio::channel_aux14); break;
case PA_CHANNEL_POSITION_AUX15: out.pushBack(audio::channel_aux15); break;
case PA_CHANNEL_POSITION_AUX16: out.pushBack(audio::channel_aux16); break;
case PA_CHANNEL_POSITION_AUX17: out.pushBack(audio::channel_aux17); break;
case PA_CHANNEL_POSITION_AUX18: out.pushBack(audio::channel_aux18); break;
case PA_CHANNEL_POSITION_AUX19: out.pushBack(audio::channel_aux19); break;
case PA_CHANNEL_POSITION_AUX20: out.pushBack(audio::channel_aux20); break;
case PA_CHANNEL_POSITION_AUX21: out.pushBack(audio::channel_aux21); break;
case PA_CHANNEL_POSITION_AUX22: out.pushBack(audio::channel_aux22); break;
case PA_CHANNEL_POSITION_AUX23: out.pushBack(audio::channel_aux23); break;
case PA_CHANNEL_POSITION_AUX24: out.pushBack(audio::channel_aux24); break;
case PA_CHANNEL_POSITION_AUX25: out.pushBack(audio::channel_aux25); break;
case PA_CHANNEL_POSITION_AUX26: out.pushBack(audio::channel_aux26); break;
case PA_CHANNEL_POSITION_AUX27: out.pushBack(audio::channel_aux27); break;
case PA_CHANNEL_POSITION_AUX28: out.pushBack(audio::channel_aux28); break;
case PA_CHANNEL_POSITION_AUX29: out.pushBack(audio::channel_aux29); break;
case PA_CHANNEL_POSITION_AUX30: out.pushBack(audio::channel_aux30); break;
case PA_CHANNEL_POSITION_AUX31: out.pushBack(audio::channel_aux31); break;
}
}
return out;
}
// Callback on getting data from pulseaudio:
static void callbackGetSinkList(pa_context* _contex, const pa_sink_info* _info, int _eol, void* _userdata) {
etk::Vector<audio::orchestra::DeviceInfo>* list = static_cast<etk::Vector<audio::orchestra::DeviceInfo>*>(_userdata);
// If eol is set to a positive number, you're at the end of the list
if (_eol > 0) {
return;
}
audio::orchestra::DeviceInfo info;
info.isCorrect = true;
info.input = false;
info.name = _info->name;
info.desc = _info->description;
info.sampleRates.pushBack(_info->sample_spec.rate);
info.nativeFormats.pushBack(getFormatFromPulseFormat(_info->sample_spec.format));
info.channels = getChannelOrderFromPulseChannel(_info->channel_map);
ATA_VERBOSE("plop=" << _info->index << " " << _info->name);
//ATA_DEBUG(" ports=" << _info->n_ports);
list->pushBack(info);
}
// allback to get data from pulseaudio:
static void callbackGetSourceList(pa_context* _contex, const pa_source_info* _info, int _eol, void* _userdata) {
etk::Vector<audio::orchestra::DeviceInfo>* list = static_cast<etk::Vector<audio::orchestra::DeviceInfo>*>(_userdata);
if (_eol > 0) {
return;
}
audio::orchestra::DeviceInfo info;
info.isCorrect = true;
info.input = true;
info.name = _info->name;
info.desc = _info->description;
info.sampleRates.pushBack(_info->sample_spec.rate);
info.nativeFormats.pushBack(getFormatFromPulseFormat(_info->sample_spec.format));
info.channels = getChannelOrderFromPulseChannel(_info->channel_map);
ATA_VERBOSE("plop=" << _info->index << " " << _info->name);
list->pushBack(info);
}
// to not update all the time ...
static etk::Vector<audio::orchestra::DeviceInfo> pulseAudioListOfDevice;
static audio::Time pulseAudioListOfDeviceTime;
etk::Vector<audio::orchestra::DeviceInfo> audio::orchestra::api::pulse::getDeviceList() {
audio::Duration delta = audio::Time::now() - pulseAudioListOfDeviceTime;
if (delta < audio::Duration(30,0)) {
return pulseAudioListOfDevice;
}
// Define our pulse audio loop and connection variables
pa_mainloop* pulseAudioMainLoop;
pa_mainloop_api* pulseAudioMainLoopAPI;
pa_operation* pulseAudioOperation;
pa_context* pulseAudioContex;
pa_context_flags_t pulseAudioFlags = PA_CONTEXT_NOAUTOSPAWN;
etk::Vector<audio::orchestra::DeviceInfo>& out = pulseAudioListOfDevice;
out.clear();
// We'll need these state variables to keep track of our requests
int state = 0;
int pulseAudioReady = 0;
// Create a mainloop API and connection to the default server
pulseAudioMainLoop = pa_mainloop_new();
pulseAudioMainLoopAPI = pa_mainloop_get_api(pulseAudioMainLoop);
pulseAudioContex = pa_context_new(pulseAudioMainLoopAPI, "orchestraPulseCount");
// If there's an error, the callback will set pulseAudioReady
pa_context_set_state_callback(pulseAudioContex, callbackStateMachine, &pulseAudioReady);
// This function connects to the pulse server
pa_context_connect(pulseAudioContex, NULL, pulseAudioFlags, NULL);
bool playLoop = true;
while (playLoop == true) {
// We can't do anything until PA is ready, so just iterate the mainloop
// and continue
if (pulseAudioReady == 0) {
pa_mainloop_iterate(pulseAudioMainLoop, 1, null);
continue;
}
// We couldn't get a connection to the server, so exit out
if (pulseAudioReady == 2) {
pa_context_disconnect(pulseAudioContex);
pa_context_unref(pulseAudioContex);
pa_mainloop_free(pulseAudioMainLoop);
ATA_ERROR("Pulse interface error: Can not connect to the pulseaudio iterface...");
return out;
}
// At this point, we're connected to the server and ready to make
// requests
switch (state) {
// State 0: we haven't done anything yet
case 0:
ATA_DEBUG("Request sink list");
pulseAudioOperation = pa_context_get_sink_info_list(pulseAudioContex,
callbackGetSinkList,
&out);
state++;
break;
case 1:
// Now we wait for our operation to complete. When it's
// complete our pa_output_devicelist is filled out, and we move
// along to the next state
if (pa_operation_get_state(pulseAudioOperation) == PA_OPERATION_DONE) {
pa_operation_unref(pulseAudioOperation);
ATA_DEBUG("Request sources list");
pulseAudioOperation = pa_context_get_source_info_list(pulseAudioContex,
callbackGetSourceList,
&out);
state++;
}
break;
case 2:
if (pa_operation_get_state(pulseAudioOperation) == PA_OPERATION_DONE) {
ATA_DEBUG("All is done");
// Now we're done, clean up and disconnect and return
pa_operation_unref(pulseAudioOperation);
pa_context_disconnect(pulseAudioContex);
pa_context_unref(pulseAudioContex);
pa_mainloop_free(pulseAudioMainLoop);
playLoop = false;
break;
}
break;
default:
// We should never see this state
ATA_ERROR("Error in getting the devices list ...");
return out;
}
// Iterate the main loop ..
if (playLoop == true) {
pa_mainloop_iterate(pulseAudioMainLoop, 1, null);
}
}
// TODO: need to do it better ...
// set default device:
int32_t idInput = -1;
int32_t idOutput = -1;
for (int32_t iii=0; iii<out.size(); ++iii) {
if (out[iii].input == true) {
if (idInput != -1) {
continue;
}
if (etk::end_with(out[iii].name, ".monitor", false) == false) {
idInput = iii;
out[iii].isDefault = true;
}
} else {
if (idOutput != -1) {
continue;
}
if (etk::end_with(out[iii].name, ".monitor", false) == false) {
idOutput = iii;
out[iii].isDefault = true;
}
}
}
return out;
}
#endif