aboutsummaryrefslogtreecommitdiffstats
path: root/ahl-binding/ahl-deviceenum.c
diff options
context:
space:
mode:
Diffstat (limited to 'ahl-binding/ahl-deviceenum.c')
-rw-r--r--ahl-binding/ahl-deviceenum.c331
1 files changed, 331 insertions, 0 deletions
diff --git a/ahl-binding/ahl-deviceenum.c b/ahl-binding/ahl-deviceenum.c
new file mode 100644
index 0000000..7e67d7a
--- /dev/null
+++ b/ahl-binding/ahl-deviceenum.c
@@ -0,0 +1,331 @@
+/*
+ * Copyright (C) 2017 "Audiokinetic Inc"
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <string.h>
+#include <alsa/asoundlib.h>
+#include <alsa/pcm.h>
+#include <json-c/json.h>
+#include "wrap-json.h"
+
+#include "ahl-binding.h"
+#include "ahl-policy.h"
+
+extern AHLCtxT g_AHLCtx;
+
+// TODO: Hash from endpoint ID information instead
+static endpointID_t CreateNewSourceID()
+{
+ endpointID_t newID = g_AHLCtx.nextSourceEndpointID;
+ g_AHLCtx.nextSourceEndpointID++;
+ return newID;
+}
+
+// TODO: Hash from endpoint ID information instead
+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,AHL_DOMAIN_ALSA) == 0);
+}
+
+static int IsPulseDomain(const char * in_pDomainStr)
+{
+ return (int) (strcasecmp(in_pDomainStr,AHL_DOMAIN_PULSE) == 0);
+}
+
+static int IsGStreamerDomain(const char * in_pDomainStr)
+{
+ return (int) (strcasecmp(in_pDomainStr,AHL_DOMAIN_GSTREAMER) == 0);
+}
+
+static int IsExternalDomain(const char * in_pDomainStr)
+{
+ return (int) (strcasecmp(in_pDomainStr,AHL_DOMAIN_EXTERNAL) == 0);
+}
+
+static int FillALSAPCMInfo( snd_pcm_t * in_pPcmHandle, EndpointInfoT * out_pEndpointInfo )
+{
+ g_assert_nonnull(in_pPcmHandle);
+ g_assert_nonnull(out_pEndpointInfo);
+ snd_pcm_type_t pcmType = 0;
+ snd_pcm_info_t * pPcmInfo = NULL;
+ int iAlsaRet = 0;
+ const char * pCardName = NULL;
+ int retVal = 0;
+ snd_ctl_t * ctlHandle = NULL;
+ snd_ctl_card_info_t * ctlInfo = NULL;
+
+ snd_pcm_info_alloca(&pPcmInfo);
+ snd_ctl_card_info_alloca(&ctlInfo);
+
+ // 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;
+ case SND_PCM_TYPE_PLUG:
+ out_pEndpointInfo->deviceURIType = DEVICEURITYPE_ALSA_PLUG;
+ break;
+ default:
+ out_pEndpointInfo->deviceURIType = DEVICEURITYPE_ALSA_OTHER;
+ break;
+ }
+
+ iAlsaRet = snd_pcm_info(in_pPcmHandle,pPcmInfo);
+ if (iAlsaRet < 0)
+ {
+ AFB_WARNING("Error retrieving PCM device info");
+ return 1;
+ }
+
+ // get card number
+ out_pEndpointInfo->alsaInfo.cardNum = snd_pcm_info_get_card(pPcmInfo);
+ if ( out_pEndpointInfo->alsaInfo.cardNum < 0 )
+ {
+ AFB_WARNING("No Alsa card number available");
+ return 1;
+ }
+
+ // get device number
+ out_pEndpointInfo->alsaInfo.deviceNum = snd_pcm_info_get_device(pPcmInfo);
+ if ( out_pEndpointInfo->alsaInfo.deviceNum < 0 )
+ {
+ AFB_WARNING("No Alsa device number available");
+ return 1;
+ }
+
+ // get sub-device number
+ out_pEndpointInfo->alsaInfo.subDeviceNum = snd_pcm_info_get_subdevice(pPcmInfo);
+ if ( out_pEndpointInfo->alsaInfo.subDeviceNum < 0 )
+ {
+ AFB_WARNING("No Alsa subdevice number available");
+ return 1;
+ }
+
+ char cardName[32];
+ sprintf(cardName, "hw:%d", out_pEndpointInfo->alsaInfo.cardNum);
+ iAlsaRet = snd_ctl_open(&ctlHandle, cardName, 0);
+ if ( iAlsaRet < 0 )
+ {
+ AFB_WARNING("Could not open ALSA card control");
+ return 1;
+ }
+
+ iAlsaRet = snd_ctl_card_info(ctlHandle, ctlInfo);
+ if ( iAlsaRet < 0 )
+ {
+ AFB_WARNING("Could not retrieve ALSA card info");
+ snd_ctl_close(ctlHandle);
+ return 1;
+ }
+
+ // Populate unique target card name
+ pCardName = snd_ctl_card_info_get_id(ctlInfo);
+ if (pCardName == NULL)
+ {
+ AFB_WARNING("No Alsa card name available");
+ snd_ctl_close(ctlHandle);
+ return 1;
+ }
+ out_pEndpointInfo->gsDeviceName = g_strdup(pCardName);
+
+ snd_ctl_close(ctlHandle);
+
+ return retVal;
+}
+
+EndpointInfoT * InitEndpointInfo()
+{
+ EndpointInfoT * pEndpointInfo = (EndpointInfoT*) malloc(sizeof(EndpointInfoT));
+ memset(pEndpointInfo,0,sizeof(EndpointInfoT));
+ pEndpointInfo->endpointID = AHL_UNDEFINED;
+ pEndpointInfo->type = ENDPOINTTYPE_MAXVALUE;
+ pEndpointInfo->deviceURIType = DEVICEURITYPE_MAXVALUE;
+ pEndpointInfo->alsaInfo.cardNum = AHL_UNDEFINED;
+ pEndpointInfo->alsaInfo.deviceNum = AHL_UNDEFINED;
+ pEndpointInfo->alsaInfo.subDeviceNum = AHL_UNDEFINED;
+ pEndpointInfo->format.sampleRate = AHL_UNDEFINED;
+ pEndpointInfo->format.numChannels = AHL_UNDEFINED;
+ pEndpointInfo->format.sampleType = AHL_FORMAT_UNKNOWN;
+ pEndpointInfo->pPropTable = g_hash_table_new(g_str_hash, g_str_equal);
+ return pEndpointInfo;
+}
+
+void TermEndpointInfo( EndpointInfoT * out_pEndpointInfo )
+{
+ #define SAFE_FREE(__ptr__) if(__ptr__) g_free(__ptr__); __ptr__ = NULL;
+ SAFE_FREE(out_pEndpointInfo->gsDeviceName);
+ SAFE_FREE(out_pEndpointInfo->gsDisplayName);
+ SAFE_FREE(out_pEndpointInfo->gsDeviceDomain);
+ SAFE_FREE(out_pEndpointInfo->pRoleName);
+ SAFE_FREE(out_pEndpointInfo->gsDeviceURI);
+ SAFE_FREE(out_pEndpointInfo->gsHALAPIName);
+
+ if (out_pEndpointInfo->pPropTable) {
+ // Free json_object for all property values
+ GHashTableIter iter;
+ gpointer key, value;
+ g_hash_table_iter_init (&iter, out_pEndpointInfo->pPropTable);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ if (value)
+ json_object_put(value);
+ }
+ g_hash_table_remove_all(out_pEndpointInfo->pPropTable);
+ g_hash_table_destroy(out_pEndpointInfo->pPropTable);
+ out_pEndpointInfo->pPropTable = NULL;
+ }
+ // GLib automatically frees item when removed from the array
+}
+
+// For a given audio role
+int EnumerateDevices(json_object * in_jDeviceArray, char * in_pAudioRole, EndpointTypeT in_deviceType, GPtrArray * out_pEndpointArray) {
+
+ g_assert_nonnull(in_jDeviceArray);
+ int iNumberDevices = json_object_array_length(in_jDeviceArray);
+
+ // Parse and validate list of available devices
+ for (int i = 0; i < iNumberDevices; i++)
+ {
+ char * pDeviceURIDomain = NULL;
+ char * pFullDeviceURI = NULL;
+ char * pDeviceURIPCM = NULL;
+ int err = 0;
+
+ json_object * jDevice = json_object_array_get_idx(in_jDeviceArray,i);
+ if (jDevice == NULL) {
+ AFB_WARNING("Invalid device array -> %s",json_object_to_json_string(in_jDeviceArray));
+ continue;
+ }
+ // strip domain name from URI
+ pFullDeviceURI = (char *)json_object_get_string(jDevice);
+ char * pFullDeviceURICopy = g_strdup(pFullDeviceURI); // strtok is destructive
+ err = SeparateDomainFromDeviceURI(pFullDeviceURICopy,&pDeviceURIDomain,&pDeviceURIPCM);
+ if (err)
+ {
+ AFB_WARNING("Invalid device URI string -> %s",pFullDeviceURICopy);
+ continue;
+ }
+
+ EndpointInfoT * pEndpointInfo = InitEndpointInfo();
+ g_assert_nonnull(pEndpointInfo);
+
+ // 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
+ pEndpointInfo->gsDeviceName = g_strdup(pDeviceURIPCM);
+ pEndpointInfo->gsDeviceDomain = g_strdup(pDeviceURIDomain);
+ pEndpointInfo->gsDeviceURI = g_strdup(pDeviceURIPCM);
+ pEndpointInfo->pRoleName = g_strdup(in_pAudioRole);
+
+ g_free(pFullDeviceURICopy);
+ pFullDeviceURICopy = NULL;
+ pDeviceURIDomain = NULL; //Derived from above mem
+ pDeviceURIPCM = NULL; //Derived from above mem
+
+ if (IsAlsaDomain(pEndpointInfo->gsDeviceDomain))
+ {
+ // 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)
+
+ // Get PCM handle
+ snd_pcm_t * pPcmHandle = NULL;
+ snd_pcm_stream_t streamType = in_deviceType == ENDPOINTTYPE_SOURCE ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK;
+ err = snd_pcm_open(&pPcmHandle, pEndpointInfo->gsDeviceURI, streamType, 0);
+ if (err < 0)
+ {
+ AFB_NOTICE("Alsa PCM device was not found -> %s", pEndpointInfo->gsDeviceURI);
+ continue;
+ }
+
+ err = FillALSAPCMInfo(pPcmHandle,pEndpointInfo);
+ if (err) {
+ AFB_WARNING("Unable to retrieve PCM information for PCM -> %s",pEndpointInfo->gsDeviceURI);
+ snd_pcm_close(pPcmHandle);
+ continue;
+ }
+
+ snd_pcm_close(pPcmHandle);
+ }
+ else if (IsPulseDomain(pEndpointInfo->gsDeviceDomain)) {
+ // Pulse domain
+ // For now display name is device URI directly, could extrapolated using more heuristics or even usins Pulse API later on
+ pEndpointInfo->deviceURIType = DEVICEURITYPE_NOT_ALSA;
+ }
+ else if (IsGStreamerDomain(pEndpointInfo->gsDeviceDomain)){
+ // GStreamer domain
+ // For now display name is device URI directly, could extrapolated using more heuristics or even usins GStreamer API later on
+ pEndpointInfo->deviceURIType = DEVICEURITYPE_NOT_ALSA;
+ }
+ else if (IsExternalDomain(pEndpointInfo->gsDeviceDomain)){
+ // External domain
+ pEndpointInfo->deviceURIType = DEVICEURITYPE_NOT_ALSA;
+ }
+ else {
+ // Unknown domain
+ AFB_WARNING("Unknown domain in device URI string -> %s",pFullDeviceURI);
+ continue;
+ }
+
+ pEndpointInfo->endpointID = in_deviceType == ENDPOINTTYPE_SOURCE ? CreateNewSourceID() : CreateNewSinkID();
+ pEndpointInfo->type = in_deviceType;
+ // Assigned by policy initialization
+ pEndpointInfo->gsDisplayName = malloc(AHL_STR_MAX_LENGTH*sizeof(char));
+ memset(pEndpointInfo->gsDisplayName,0,AHL_STR_MAX_LENGTH*sizeof(char));
+ pEndpointInfo->gsHALAPIName = malloc(AHL_STR_MAX_LENGTH*sizeof(char));
+ memset(pEndpointInfo->gsDisplayName,0,AHL_STR_MAX_LENGTH*sizeof(char));
+
+ // add to structure to list of available devices
+ g_ptr_array_add(out_pEndpointArray, pEndpointInfo);
+
+ } // for all devices
+
+ AFB_DEBUG ("Audio high-level - Enumerate devices done");
+ return 0;
+} \ No newline at end of file