/* * Copyright (C) 2017, Microchip Technology Inc. and its subsidiaries. * Author Tobias Jahnke * * 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 #include "hal-interface.h" #include "wrap-json.h" #include "wrap_unicens.h" #include "wrap_volume.h" #define ALSA_CARD_NAME "ep016ch" #define ALSA_DEVICE_ID "hw:ep016ch" #define PCM_MAX_CHANNELS 6 /* Define few private tag for not standard functions */ #define PCM_Volume_Multimedia 1000 #define PCM_Volume_Navigation 1001 #define PCM_Volume_Emergency 1002 /* Default Values for MasterVolume Ramping */ STATIC halVolRampT volRampMaster= { .mode = RAMP_VOL_NORMAL, .slave = Master_Playback_Volume, .delay = 100*1000, // ramping delay in us .stepDown=1, .stepUp =1, }; /* Soft Volume Ramping Value can be customize by Audio Role and hardware card */ STATIC halVolRampT volRampEmergency= { .slave = Emergency_Playback_Volume, .delay = 50*1000, // ramping delay in us .stepDown= 2, .stepUp = 10, }; STATIC halVolRampT volRampWarning= { .slave = Warning_Playback_Volume, .delay = 50*1000, // ramping delay in us .stepDown= 2, .stepUp = 10, }; STATIC halVolRampT volRampCustomHigh= { .slave = CustomHigh_Playback_Volume, .delay = 50*1000, // ramping delay in us .stepDown= 2, .stepUp = 4, }; STATIC halVolRampT volRampPhone= { .slave = Phone_Playback_Volume, .delay = 50*1000, // ramping delay in us .stepDown= 2, .stepUp = 4, }; STATIC halVolRampT volRampNavigation= { .slave = Navigation_Playback_Volume, .delay = 50*1000, // ramping delay in us .stepDown= 2, .stepUp = 4, }; STATIC halVolRampT volRampCustomMedium= { .slave = CustomMedium_Playback_Volume, .delay = 50*1000, // ramping delay in us .stepDown= 2, .stepUp = 4, }; STATIC halVolRampT volRampVideo= { .slave = Video_Playback_Volume, .delay = 50*1000, // ramping delay in us .stepDown= 2, .stepUp = 4, }; STATIC halVolRampT volRampStreaming= { .slave = Streaming_Playback_Volume, .delay = 50*1000, // ramping delay in us .stepDown= 2, .stepUp = 4, }; STATIC halVolRampT volRampMultimedia= { .slave = Multimedia_Playback_Volume, .delay = 50*1000, // ramping delay in us .stepDown= 2, .stepUp = 4, }; STATIC halVolRampT volRampRadio= { .slave = Radio_Playback_Volume, .delay = 50*1000, // ramping delay in us .stepDown= 2, .stepUp = 4, }; STATIC halVolRampT volRampCustomLow= { .slave = CustomLow_Playback_Volume, .delay = 50*1000, // ramping delay in us .stepDown= 2, .stepUp = 4, }; STATIC halVolRampT volRampFallback= { .slave = Fallback_Playback_Volume, .delay = 50*1000, // ramping delay in us .stepDown= 2, .stepUp = 4, }; static int master_volume = 80; static json_bool master_switch; static int pcm_volume[PCM_MAX_CHANNELS] = {100,100,100,100,100,100}; static void unicens_apply_card_value(void *closure, struct json_object *j_obj){ int num_id = 0; int values[6]; /* apply master volume */ if (wrap_json_unpack(j_obj, "{s:i, s:[i!]}", "id", &num_id, "val", &values[0]) == 0) { if (num_id == 1) { AFB_NOTICE("unicens_apply_card_value: num_id: %d, value: %d", num_id, values[0]); wrap_volume_master(values[0]); return; } } if (wrap_json_unpack(j_obj, "{s:i, s:[iiiiii!]}", "id", &num_id, "val", &values[0], &values[1], &values[2], &values[3], &values[4], &values[5]) == 0) { if (num_id == 2) { AFB_NOTICE("unicens_apply_card_value: success, num_id: %d", num_id); wrap_volume_pcm(&values[0], 6); return; } } AFB_ERROR("unicens_apply_card_value: no card value applied"); } static void unicens_request_card_values_cb(void *closure, int status, struct json_object *j_response) { json_object *j_obj; AFB_INFO("unicens_request_card_values_cb: closure=%p status=%d, res=%s", closure, status, json_object_to_json_string(j_response)); if (json_object_object_get_ex(j_response, "response", &j_obj)) { wrap_json_optarray_for_all(j_obj, &unicens_apply_card_value, NULL); } } static __attribute__((unused)) void unicens_request_card_values(const char* dev_id) { int err; json_object *j_query = NULL; err = wrap_json_pack(&j_query, "{s:s, s:i}", "devid", dev_id, "mode", 0); if (err) { AFB_ERROR("Failed to call wrap_json_pack"); goto OnErrorExit; } afb_service_call("alsacore", "ctlget", j_query, &unicens_request_card_values_cb, NULL); OnErrorExit: return; } void unicens_master_vol_cb(halCtlsTagT tag, alsaHalCtlMapT *control, void* handle, json_object *j_obj) { const char *j_str = json_object_to_json_string(j_obj); if (wrap_json_unpack(j_obj, "[i!]", &master_volume) == 0) { AFB_NOTICE("master_volume: %s, value=%d", j_str, master_volume); wrap_volume_master(master_volume); } else { AFB_NOTICE("master_volume: INVALID STRING %s", j_str); } } void unicens_master_switch_cb(halCtlsTagT tag, alsaHalCtlMapT *control, void* handle, json_object *j_obj) { const char *j_str = json_object_to_json_string(j_obj); if (wrap_json_unpack(j_obj, "[b!]", &master_switch) == 0) { AFB_NOTICE("master_switch: %s, value=%d", j_str, master_switch); } else { AFB_NOTICE("master_switch: INVALID STRING %s", j_str); } } void unicens_pcm_vol_cb(halCtlsTagT tag, alsaHalCtlMapT *control, void* handle, json_object *j_obj) { const char *j_str = json_object_to_json_string(j_obj); if (wrap_json_unpack(j_obj, "[iiiiii!]", &pcm_volume[0], &pcm_volume[1], &pcm_volume[2], &pcm_volume[3], &pcm_volume[4], &pcm_volume[5]) == 0) { AFB_NOTICE("pcm_vol: %s", j_str); wrap_volume_pcm(pcm_volume, PCM_MAX_CHANNELS/*array size*/); } else { AFB_NOTICE("pcm_vol: INVALID STRING %s", j_str); } } /* declare ALSA mixer controls */ STATIC alsaHalMapT alsaHalMap[]= { { .tag=Master_Playback_Volume, .cb={.callback=unicens_master_vol_cb, .handle=&master_volume}, .info="Sets master playback volume", .ctl={.numid=CTL_AUTO, .type=SND_CTL_ELEM_TYPE_INTEGER, .count=1, .minval=0, .maxval=100, .step=1, .value=80, .name="Master Playback Volume"} }, /*{ .tag=Master_OnOff_Switch, .cb={.callback=unicens_master_switch_cb, .handle=&master_switch}, .info="Sets master playback switch", .ctl={.numid=CTL_AUTO, .type=SND_CTL_ELEM_TYPE_BOOLEAN, .count=1, .minval=0, .maxval=1, .step=1, .value=1, .name="Master Playback Switch"} },*/ { .tag=PCM_Playback_Volume, .cb={.callback=unicens_pcm_vol_cb, .handle=&pcm_volume}, .info="Sets PCM playback volume", .ctl={.numid=CTL_AUTO, .type=SND_CTL_ELEM_TYPE_INTEGER, .count=6, .minval=0, .maxval=100, .step=1, .value=100, .name="PCM Playback Volume"} }, // Sound card does not have hardware volume ramping { .tag=Master_Playback_Ramp, .cb={.callback=volumeRamp, .handle=&volRampMaster}, .info="RampUp Master Volume", .ctl={.numid=CTL_AUTO, .type=SND_CTL_ELEM_TYPE_INTEGER, .name="Master_Ramp", .count=1, .minval=0, .maxval=100, .step=1, .value=80 } }, // Implement Rampup Volume for Virtual Channels (0-100) { .tag=Emergency_Playback_Ramp, .cb={.callback=volumeRamp, .handle=&volRampEmergency}, .info="RampUp Emergency Volume", .ctl={.numid=CTL_AUTO, .type=SND_CTL_ELEM_TYPE_INTEGER,.name="Emergency_Ramp", .minval=0, .maxval=100, .step=1, .value=80 } }, { .tag=Warning_Playback_Ramp, .cb={.callback=volumeRamp, .handle=&volRampWarning}, .info="RampUp Warning Volume", .ctl={.numid=CTL_AUTO, .type=SND_CTL_ELEM_TYPE_INTEGER,.name="Warning_Ramp", .minval=0, .maxval=100, .step=1, .value=80 } }, { .tag=CustomHigh_Playback_Ramp, .cb={.callback=volumeRamp, .handle=&volRampCustomHigh}, .info="RampUp CustomHigh Volume", .ctl={.numid=CTL_AUTO, .type=SND_CTL_ELEM_TYPE_INTEGER,.name="CustomHigh_Ramp", .minval=0, .maxval=100, .step=1, .value=80 } }, { .tag=Phone_Playback_Ramp, .cb={.callback=volumeRamp, .handle=&volRampPhone}, .info="RampUp Phone Volume", .ctl={.numid=CTL_AUTO, .type=SND_CTL_ELEM_TYPE_INTEGER,.name="Phone_Ramp", .minval=0, .maxval=100, .step=1, .value=80 } }, { .tag=Navigation_Playback_Ramp, .cb={.callback=volumeRamp, .handle=&volRampNavigation}, .info="RampUp Navigation Volume", .ctl={.numid=CTL_AUTO, .type=SND_CTL_ELEM_TYPE_INTEGER,.name="Navigation_Ramp", .minval=0, .maxval=100, .step=1, .value=80 } }, { .tag=CustomMedium_Playback_Ramp, .cb={.callback=volumeRamp, .handle=&volRampCustomMedium}, .info="RampUp CustomMedium Volume", .ctl={.numid=CTL_AUTO, .type=SND_CTL_ELEM_TYPE_INTEGER,.name="CustomMedium_Ramp", .minval=0, .maxval=100, .step=1, .value=80 } }, { .tag=Video_Playback_Ramp, .cb={.callback=volumeRamp, .handle=&volRampVideo}, .info="RampUp Video Volume", .ctl={.numid=CTL_AUTO, .type=SND_CTL_ELEM_TYPE_INTEGER,.name="Video_Ramp", .minval=0, .maxval=100, .step=1, .value=80 } }, { .tag=Streaming_Playback_Ramp, .cb={.callback=volumeRamp, .handle=&volRampStreaming}, .info="RampUp Streaming Volume", .ctl={.numid=CTL_AUTO, .type=SND_CTL_ELEM_TYPE_INTEGER,.name="Streaming_Ramp", .minval=0, .maxval=100, .step=1, .value=80 } }, { .tag=Multimedia_Playback_Ramp, .cb={.callback=volumeRamp, .handle=&volRampMultimedia}, .info="RampUp Multimedia Volume", .ctl={.numid=CTL_AUTO, .type=SND_CTL_ELEM_TYPE_INTEGER,.name="Multimedia_Ramp", .minval=0, .maxval=100, .step=1, .value=80 } }, { .tag=Radio_Playback_Ramp, .cb={.callback=volumeRamp, .handle=&volRampRadio}, .info="RampUp Radio Volume", .ctl={.numid=CTL_AUTO, .type=SND_CTL_ELEM_TYPE_INTEGER,.name="Radio_Ramp", .minval=0, .maxval=100, .step=1, .value=80 } }, { .tag=CustomLow_Playback_Ramp, .cb={.callback=volumeRamp, .handle=&volRampCustomLow}, .info="RampUp CustomLow Volume", .ctl={.numid=CTL_AUTO, .type=SND_CTL_ELEM_TYPE_INTEGER,.name="CustomLow_Ramp", .minval=0, .maxval=100, .step=1, .value=80 } }, { .tag=Fallback_Playback_Ramp, .cb={.callback=volumeRamp, .handle=&volRampFallback}, .info="RampUp Fallback Volume", .ctl={.numid=CTL_AUTO, .type=SND_CTL_ELEM_TYPE_INTEGER,.name="Fallback_Ramp", .minval=0, .maxval=100, .step=1, .value=80 } }, // Bind with existing ones created by ALSA configuration (and linked to softvol) [0-255] { .tag=Emergency_Playback_Volume , .ctl={.name="Emergency_Volume",.numid=CTL_AUTO, .type=SND_CTL_ELEM_TYPE_INTEGER,.count=2, .maxval=255, .value=204 } }, { .tag=Warning_Playback_Volume , .ctl={.name="Warning_Volume",.numid=CTL_AUTO, .type=SND_CTL_ELEM_TYPE_INTEGER,.count=2, .maxval=255, .value=204 } }, { .tag=CustomHigh_Playback_Volume , .ctl={.name="CustomHigh_Volume",.numid=CTL_AUTO, .type=SND_CTL_ELEM_TYPE_INTEGER,.count=2, .maxval=255, .value=204 } }, { .tag=Phone_Playback_Volume , .ctl={.name="Phone_Volume",.numid=CTL_AUTO, .type=SND_CTL_ELEM_TYPE_INTEGER,.count=2, .maxval=255, .value=204 } }, { .tag=Navigation_Playback_Volume , .ctl={.name="Navigation_Volume",.numid=CTL_AUTO, .type=SND_CTL_ELEM_TYPE_INTEGER,.count=2, .maxval=255, .value=204 } }, { .tag=CustomMedium_Playback_Volume , .ctl={.name="CustomMedium_Volume",.numid=CTL_AUTO, .type=SND_CTL_ELEM_TYPE_INTEGER,.count=2, .maxval=255, .value=204 } }, { .tag=Video_Playback_Volume , .ctl={.name="Video_Volume",.numid=CTL_AUTO, .type=SND_CTL_ELEM_TYPE_INTEGER,.count=2, .maxval=255, .value=204 } }, { .tag=Streaming_Playback_Volume , .ctl={.name="Streaming_Volume",.numid=CTL_AUTO, .type=SND_CTL_ELEM_TYPE_INTEGER,.count=2, .maxval=255, .value=204 } }, { .tag=Multimedia_Playback_Volume , .ctl={.name="Multimedia_Volume",.numid=CTL_AUTO, .type=SND_CTL_ELEM_TYPE_INTEGER,.count=2, .maxval=255, .value=204 } }, { .tag=Radio_Playback_Volume , .ctl={.name="Radio_Volume",.numid=CTL_AUTO, .type=SND_CTL_ELEM_TYPE_INTEGER,.count=2, .maxval=255, .value=204 } }, { .tag=CustomLow_Playback_Volume , .ctl={.name="CustomLow_Volume",.numid=CTL_AUTO, .type=SND_CTL_ELEM_TYPE_INTEGER,.count=2, .maxval=255, .value=204 } }, { .tag=Fallback_Playback_Volume , .ctl={.name="Fallback_Volume",.numid=CTL_AUTO, .type=SND_CTL_ELEM_TYPE_INTEGER,.count=2, .maxval=255, .value=204 } }, { .tag=EndHalCrlTag} /* marker for end of the array */ } ; /* HAL sound card mapping info */ STATIC alsaHalSndCardT alsaHalSndCard = { .name = ALSA_CARD_NAME, /* WARNING: name MUST match with 'aplay -l' */ .info = "HAL for MICROCHIP MOST sound card controlled by UNICENS binding", .ctls = alsaHalMap, .volumeCB = NULL, /* use default volume normalization function */ }; /* initializes ALSA sound card, UNICENS API */ STATIC int unicens_service_init() { int err = 0; AFB_NOTICE("Initializing HAL-MOST-UNICENS-BINDING"); err = halServiceInit(afbBindingV2.api, &alsaHalSndCard); if (err) { AFB_ERROR("Cannot initialize ALSA soundcard."); goto OnErrorExit; } err= afb_daemon_require_api("UNICENS", 1); if (err) { AFB_ERROR("Failed to access UNICENS API"); goto OnErrorExit; } err = wrap_ucs_subscribe_sync(); if (err) { AFB_ERROR("Failed to subscribe to UNICENS binding"); goto OnErrorExit; } err = wrap_volume_init(); if (err) { AFB_ERROR("Failed to initialize wrapper for volume library"); goto OnErrorExit; } /* Step 1: Set output volume to pre-defined level */ /* in order to avoid muted volume to be persistent after boot. */ wrap_volume_master(80); wrap_volume_pcm(pcm_volume, PCM_MAX_CHANNELS/*array size*/); /* Step 2: risk to have influence on mixer controls init */ /* request of initial card values */ /* unicens_request_card_values(ALSA_DEVICE_ID); */ json_object *apiObject; wrap_json_pack(&apiObject, "{s:{s:s s:s s:s s:s s:s s:s s:i}}", "metadata", "api", "hal-most-unicens", "uid", "/dev/snd/by-id/usb-Microchip-SMSC_OS81210_0000-0000000C-02", "info", "External Hal for Unicens", "author", "Tobias Janhke", "version", "1.0", "date", "2018-06-14", "snd-dev-id", -1); json_object *returnedJ; if(afb_service_call_sync("4a-hal-manager", "load", apiObject, &returnedJ)) AFB_ERROR("An issue happen during registration to hal-manager : %s", json_object_get_string(returnedJ)); OnErrorExit: AFB_NOTICE("Initializing HAL-MOST-UNICENS-BINDING done.."); return err; } // This receive all event this binding subscribe to PUBLIC void unicens_event_cb(const char *evtname, json_object *j_event) { if (strncmp(evtname, "alsacore/", 9) == 0) { halServiceEvent(evtname, j_event); return; } if (strncmp(evtname, "UNICENS/", 8) == 0) { //AFB_NOTICE("unicens_event_cb: evtname=%s, event=%s", evtname, json_object_get_string(j_event)); if (strcmp(evtname, "UNICENS/node-availibility") == 0) { int node; int available; if (wrap_json_unpack(j_event, "{s:i,s:b}", "node", &node, "available", &available) == 0) { AFB_NOTICE("Node-Availability: node=0x%03X, available=%d", node, available); wrap_volume_node_avail(node, available); } } return; } AFB_NOTICE("unicens_event_cb: UNHANDLED EVENT, evtname=%s, event=%s", evtname, json_object_get_string(j_event)); } /* API prefix should be unique for each snd card */ PUBLIC const struct afb_binding_v2 afbBindingV2 = { .api = "hal-most-unicens", .init = unicens_service_init, .verbs = halServiceApi, .onevent = unicens_event_cb, };