summaryrefslogtreecommitdiffstats
path: root/src/ahl-deviceenum.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/ahl-deviceenum.c')
-rw-r--r--src/ahl-deviceenum.c412
1 files changed, 403 insertions, 9 deletions
diff --git a/src/ahl-deviceenum.c b/src/ahl-deviceenum.c
index 6629de2..b866d52 100644
--- a/src/ahl-deviceenum.c
+++ b/src/ahl-deviceenum.c
@@ -18,23 +18,417 @@
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
-#include <time.h>
+#include <alsa/asoundlib.h>
+#include <alsa/pcm.h>
+#include <json-c/json.h>
+#include "wrap-json.h"
#include "ahl-binding.h"
-PUBLIC int EnumerateSources() {
-
- // TODO: Use lower level services to build a list of available source devices
+extern AHLCtxT g_AHLCtx;
+
+static endpointID_t CreateNewSourceID()
+{
+ endpointID_t newID = g_AHLCtx.nextSourceEndpointID;
+ g_AHLCtx.nextSourceEndpointID++;
+ return newID;
+}
+
+static endpointID_t CreateNewSinkID()
+{
+ endpointID_t newID = g_AHLCtx.nextSinkEndpointID;
+ g_AHLCtx.nextSinkEndpointID++;
+ return newID;
+}
+
+// Watchout: This function uses strtok and is destructive on the input string (use a copy)
+static int SeparateDomainFromDeviceURI( char * in_pDeviceURI, char ** out_pDomain, char ** out_pDevice)
+{
+ *out_pDomain = strtok(in_pDeviceURI, ".");
+ if (*out_pDomain == NULL)
+ {
+ AFB_ERROR("Error tokenizing device URI -> %s",in_pDeviceURI);
+ return 1;
+ }
+ // TODO: Validate domain is known string (e.g. ALSA,Pulse,GStreamer)
+ *out_pDevice = strtok(NULL, ".");
+ if (*out_pDevice == NULL)
+ {
+ AFB_ERROR("Error tokenizing device URI -> %s",in_pDeviceURI);
+ return 1;
+ }
+ return 0;
+}
+
+static int IsAlsaDomain(const char * in_pDomainStr)
+{
+ return (int) (strcasecmp(in_pDomainStr,AUDIOHL_DOMAIN_ALSA) == 0);
+}
+
+static int IsPulseDomain(const char * in_pDomainStr)
+{
+ return (int) (strcasecmp(in_pDomainStr,AUDIOHL_DOMAIN_PULSE) == 0);
+}
+
+static int IsGStreamerDomain(const char * in_pDomainStr)
+{
+ return (int) (strcasecmp(in_pDomainStr,AUDIOHL_DOMAIN_GSTREAMER) == 0);
+}
+
+static int IsExternalDomain(const char * in_pDomainStr)
+{
+ return (int) (strcasecmp(in_pDomainStr,AUDIOHL_DOMAIN_EXTERNAL) == 0);
+}
+
+static int FillALSAPCMInfo( snd_pcm_t * in_pPcmHandle, EndpointInfoT * out_pEndpointInfo )
+{
+ snd_pcm_type_t pcmType = 0;
+ snd_pcm_info_t * pPcmInfo = NULL;
+ int iAlsaRet = 0;
+ const char * pDeviceName = NULL;
+ int retVal = 0;
+
+ // retrieve PCM type
+ pcmType = snd_pcm_type(in_pPcmHandle);
+ switch (pcmType) {
+ case SND_PCM_TYPE_HW:
+ out_pEndpointInfo->deviceURIType = DEVICEURITYPE_ALSA_HW;
+ break;
+ case SND_PCM_TYPE_DMIX:
+ out_pEndpointInfo->deviceURIType = DEVICEURITYPE_ALSA_DMIX;
+ break;
+ case SND_PCM_TYPE_SOFTVOL:
+ out_pEndpointInfo->deviceURIType = DEVICEURITYPE_ALSA_SOFTVOL;
+ break;
+ default:
+ out_pEndpointInfo->deviceURIType = DEVICEURITYPE_ALSA_OTHER;
+ break;
+ }
+
+ iAlsaRet = snd_pcm_info_malloc(&pPcmInfo);
+ if (iAlsaRet < 0)
+ {
+ AFB_WARNING("Error allocating PCM info structure");
+ retVal = 1;
+ goto End;
+ }
+
+ iAlsaRet = snd_pcm_info(in_pPcmHandle,pPcmInfo);
+ if (iAlsaRet < 0)
+ {
+ AFB_WARNING("Error retrieving PCM device info");
+ retVal = 1;
+ goto End;
+ }
+
+ // Populate target device name (for application display)
+ pDeviceName = snd_pcm_info_get_name(pPcmInfo);
+ if (pDeviceName == NULL)
+ {
+ AFB_WARNING("No Alsa device name available");
+ retVal = 1;
+ goto End;
+ // Could potentially assign a "default" name and carry on with this device
+ }
+ strncpy(out_pEndpointInfo->deviceName,pDeviceName,AUDIOHL_MAX_DEVICE_NAME_LENGTH); // Overwritten by HAL name if available
+
+ // get card number
+ out_pEndpointInfo->cardNum = snd_pcm_info_get_card(pPcmInfo);
+ if ( out_pEndpointInfo->cardNum < 0 )
+ {
+ AFB_WARNING("No Alsa card number available");
+ retVal = 1;
+ goto End;
+ }
+ // get device number
+ out_pEndpointInfo->deviceNum = snd_pcm_info_get_device(pPcmInfo);
+ if ( out_pEndpointInfo->deviceNum < 0 )
+ {
+ AFB_WARNING("No Alsa device number available");
+ retVal = 1;
+ goto End;
+ }
+
+ // get sub-device number
+ out_pEndpointInfo->subDeviceNum = snd_pcm_info_get_subdevice(pPcmInfo);
+ if ( out_pEndpointInfo->subDeviceNum < 0 )
+ {
+ AFB_WARNING("No Alsa subdevice number available");
+ retVal = 1;
+ goto End;
+ }
+
+End:
+ if(pPcmInfo) {
+ snd_pcm_info_free(pPcmInfo);
+ pPcmInfo = NULL;
+ }
+
+ return retVal;
+}
+
+static int RetrieveAssociatedHALAPIName(EndpointInfoT * io_pEndpointInfo)
+{
+ json_object *j_response, *j_query = NULL;
+ int err;
+ err = afb_service_call_sync("alsacore", "hallist", j_query, &j_response);
+ if (err) {
+ AFB_ERROR("Could not retrieve list of HAL from ALSA core");
+ return 1;
+ }
+ AFB_DEBUG("ALSAcore hallist response=%s", json_object_to_json_string(j_response));
+
+ // Look through returned list for matching card
+ int found = 0;
+ json_object * jRespObj = NULL;
+ json_object_object_get_ex(j_response, "response", &jRespObj);
+ int iNumHAL = json_object_array_length(jRespObj);
+ for ( int i = 0 ; i < iNumHAL; i++)
+ {
+ json_object * jHAL = json_object_array_get_idx(jRespObj,i);
+ char * pDevIDStr = NULL;
+ char * pAPIName = NULL;
+ char * pShortName = NULL;
+
+ int err = wrap_json_unpack(jHAL, "{s:s,s:s,s:s}", "devid", &pDevIDStr,"api", &pAPIName,"shortname",&pShortName);
+ if (err) {
+ AFB_ERROR("Could not retrieve devid string=%s", json_object_get_string(jHAL));
+ return 1;
+ }
+
+ // Retrieve card number (e.g. hw:0)
+ int iCardNum = atoi(pDevIDStr+3);
+ if (iCardNum == io_pEndpointInfo->cardNum) {
+ strncpy(io_pEndpointInfo->halAPIName,pAPIName,AUDIOHL_MAX_AUDIOROLE_LENGTH);
+ strncpy(io_pEndpointInfo->deviceName,pShortName,AUDIOHL_MAX_DEVICE_NAME_LENGTH);
+ found = 1;
+ break;
+ }
+ }
+ return !found;
+}
+
+static int InitializeEndpointStates( EndpointInfoT * out_pEndpointInfo )
+{
+ //out_pEndpointInfo = g_array_sized_new(FALSE,TRUE,sizeof)
+ // for list of known states
+ return 0;
+}
+
+// For a given audio role
+PUBLIC int EnumerateSources(json_object * in_jSourceArray, int in_iRoleIndex, char * in_pRoleName) {
+
+ int iNumberDevices = json_object_array_length(in_jSourceArray);
+
+ // Parse and validate list of available devices
+ for (unsigned int i = 0; i < iNumberDevices; i++)
+ {
+ char fullDeviceURI[AUDIOHL_MAX_DEVICE_URI_LENGTH]; // strtok is destructive
+ char * pDeviceURIDomain = NULL;
+ char * pFullDeviceURI = NULL;
+ char * pDeviceURIPCM = NULL;
+ int err = 0;
+ EndpointInfoT endpointInfo;
+
+ json_object * jSource = json_object_array_get_idx(in_jSourceArray,i);
+
+ // strip domain name from URI
+ pFullDeviceURI = (char *)json_object_get_string(jSource);
+ strncpy(fullDeviceURI,pFullDeviceURI,AUDIOHL_MAX_DEVICE_URI_LENGTH);
+ err = SeparateDomainFromDeviceURI(fullDeviceURI,&pDeviceURIDomain,&pDeviceURIPCM);
+ if (err)
+ {
+ AFB_WARNING("Invalid device URI string -> %s",fullDeviceURI);
+ continue;
+ }
+
+ // non ALSA URI are simply passed to application (no validation) at this time
+ // In Non ALSA case devices in config are assumed to be available, if not application can fallback on explicit device selection
+ endpointInfo.cardNum = -1;
+ endpointInfo.deviceNum = -1;
+ endpointInfo.cardNum = -1;
+ strncpy(endpointInfo.deviceName,pDeviceURIPCM,AUDIOHL_MAX_DEVICE_NAME_LENGTH); // Overwritten by HAL name if available
+
+ if (IsAlsaDomain(pDeviceURIDomain))
+ {
+ // TODO: Missing support for loose name matching
+ // This will require using ALSA hints to get PCM names
+ // And would iterate over all available devices matching string (possibly all if no filtering is desired for a certain role)
+
+ snd_pcm_t * pPcmHandle = NULL;
+
+ // Get PCM handle
+ err = snd_pcm_open(&pPcmHandle, pDeviceURIPCM, SND_PCM_STREAM_CAPTURE, 0);
+ if (err < 0)
+ {
+ AFB_NOTICE("Alsa PCM device was not found -> %s", pDeviceURIPCM);
+ continue;
+ }
+
+ err = FillALSAPCMInfo(pPcmHandle,&endpointInfo);
+ if (err) {
+ AFB_WARNING("Unable to retrieve PCM information for PCM -> %s",pDeviceURIPCM);
+ snd_pcm_close(pPcmHandle);
+ continue;
+ }
+
+ snd_pcm_close(pPcmHandle);
+
+ // Retrieve HAL API name
+ err = RetrieveAssociatedHALAPIName(&endpointInfo);
+ if (err) {
+ AFB_WARNING("SetVolume will fail without HAL association ->%s",endpointInfo.deviceURI);
+ // Choose not to skip anyhow...
+ }
+ }
+ else if (IsPulseDomain(pDeviceURIDomain)) {
+ // Pulse domain
+ // For now display name is device URI directly, could extrapolated using more heuristics or even usins Pulse API later on
+ endpointInfo.deviceURIType = DEVICEURITYPE_PULSE;
+ }
+ else if (IsGStreamerDomain(pDeviceURIDomain)){
+ // GStreamer domain
+ // For now display name is device URI directly, could extrapolated using more heuristics or even usins GStreamer API later on
+ endpointInfo.deviceURIType = DEVICEURITYPE_GSTREAMER;
+ }
+ else if (IsExternalDomain(pDeviceURIDomain)){
+ // External domain
+ endpointInfo.deviceURIType = DEVICEURITYPE_EXTERNAL;
+ }
+ else {
+ // Unknown domain
+ AFB_WARNING("Unknown domain in device URI string -> %s",fullDeviceURI);
+ continue;
+ }
+
+ err = InitializeEndpointStates( &endpointInfo );
+ if (err) {
+ AFB_ERROR("Cannot initialize endpoint states for URI -> %s",fullDeviceURI);
+ continue;
+ }
+
+ strncpy(endpointInfo.deviceURI,pDeviceURIPCM,AUDIOHL_MAX_DEVICE_URI_LENGTH);
+ strncpy(endpointInfo.audioRole,in_pRoleName,AUDIOHL_MAX_AUDIOROLE_LENGTH);
+ endpointInfo.endpointID = CreateNewSourceID();
+ endpointInfo.type = ENDPOINTTYPE_SOURCE;
+
+ // add to structure to list of available source devices
+ GArray * pRoleSourceDeviceArray = g_ptr_array_index( g_AHLCtx.policyCtx.pSourceEndpoints, in_iRoleIndex );
+ g_array_append_val(pRoleSourceDeviceArray, endpointInfo);
+
+ } // for all devices
+
AFB_DEBUG ("Audio high-level - Enumerate sources done");
return 0;
}
-PUBLIC int EnumerateSinks() {
-
- // TODO: Use lower level services to build a list of available sink devices
-
+// For a given audio role
+PUBLIC int EnumerateSinks(json_object * in_jSinkArray, int in_iRoleIndex, char * in_pRoleName) {
+
+ int iNumberDevices = json_object_array_length(in_jSinkArray);
+
+ // Parse and validate list of available devices
+ for (unsigned int i = 0; i < iNumberDevices; i++)
+ {
+ char fullDeviceURI[AUDIOHL_MAX_DEVICE_URI_LENGTH]; // strtok is destructive
+ char * pDeviceURIDomain = NULL;
+ char * pFullDeviceURI = NULL;
+ char * pDeviceURIPCM = NULL;
+ int err = 0;
+ EndpointInfoT endpointInfo;
+
+ json_object * jSink = json_object_array_get_idx(in_jSinkArray,i);
+
+ // strip domain name from URI
+ pFullDeviceURI = (char*)json_object_get_string(jSink);
+ strncpy(fullDeviceURI,pFullDeviceURI,AUDIOHL_MAX_DEVICE_URI_LENGTH);
+ err = SeparateDomainFromDeviceURI(fullDeviceURI,&pDeviceURIDomain,&pDeviceURIPCM);
+ if (err)
+ {
+ AFB_WARNING("Invalid device URI string -> %s",fullDeviceURI);
+ continue;
+ }
+
+ // non ALSA URI are simply passed to application (no validation) at this time
+ // In Non ALSA case devices in config are assumed to be available, if not application can fallback on explicit device selection
+
+ endpointInfo.cardNum = -1;
+ endpointInfo.deviceNum = -1;
+ endpointInfo.cardNum = -1;
+ strncpy(endpointInfo.deviceName,pDeviceURIPCM,AUDIOHL_MAX_DEVICE_NAME_LENGTH);
+
+ if (IsAlsaDomain(pDeviceURIDomain))
+ {
+ // TODO: Missing support for loose name matching
+ // This will require using ALSA hints to get PCM names
+ // And would iterate over all available devices matching string (possibly all if no filtering is desired for a certain role)
+
+ snd_pcm_t * pPcmHandle = NULL;
+
+ // get PCM handle
+ err = snd_pcm_open(&pPcmHandle, pDeviceURIPCM, SND_PCM_STREAM_PLAYBACK, 0);
+ if (err < 0)
+ {
+ AFB_NOTICE("Alsa PCM device was not found -> %s", pDeviceURIPCM);
+ continue;
+ }
+
+ err = FillALSAPCMInfo(pPcmHandle,&endpointInfo);
+ if (err) {
+ AFB_WARNING("Unable to retrieve PCM information for PCM -> %s",pDeviceURIPCM);
+ snd_pcm_close(pPcmHandle);
+ continue;
+ }
+
+ snd_pcm_close(pPcmHandle);
+
+ // Retrieve HAL API name
+ err = RetrieveAssociatedHALAPIName(&endpointInfo);
+ if (err) {
+ //AFB_WARNING("SetVolume w fail without HAL association ->%s",endpointInfo.deviceURI);
+ continue;
+ }
+ }
+ else if (IsPulseDomain(pDeviceURIDomain)) {
+ // Pulse domain
+ // For now display name is device URI directly, could extrapolated using more heuristics or even usins Pulse API later on
+ endpointInfo.deviceURIType = DEVICEURITYPE_PULSE;
+
+ }
+ else if (IsGStreamerDomain(pDeviceURIDomain)){
+ // GStreamer domain
+ // For now display name is device URI directly, could extrapolated using more heuristics or even usins GStreamer API later on
+ endpointInfo.deviceURIType = DEVICEURITYPE_GSTREAMER;
+ }
+ else if (IsExternalDomain(pDeviceURIDomain)){
+ // External domain
+
+ endpointInfo.deviceURIType = DEVICEURITYPE_EXTERNAL;
+ }
+ else {
+ // Unknown domain
+ AFB_WARNING("Unknown domain in device URI string -> %s",fullDeviceURI);
+ continue;
+ }
+
+ err = InitializeEndpointStates( &endpointInfo );
+ if (err) {
+ AFB_ERROR("Cannot initialize endpoint states for URI -> %s",fullDeviceURI);
+ continue;
+ }
+
+ strncpy(endpointInfo.deviceURI,pDeviceURIPCM,AUDIOHL_MAX_DEVICE_URI_LENGTH);
+ strncpy(endpointInfo.audioRole,in_pRoleName,AUDIOHL_MAX_AUDIOROLE_LENGTH);
+ endpointInfo.endpointID = CreateNewSinkID();
+ endpointInfo.type = ENDPOINTTYPE_SINK;
+
+ // add to structure to list of available source devices
+ GArray * pRoleSinkDeviceArray = g_ptr_array_index( g_AHLCtx.policyCtx.pSinkEndpoints, in_iRoleIndex );
+ g_array_append_val(pRoleSinkDeviceArray, endpointInfo);
+
+ } // for all devices
+
AFB_DEBUG ("Audio high-level - Enumerate sinks done");
return 0;
}
-