diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/ahl-binding.c | 400 | ||||
-rw-r--r-- | src/ahl-binding.h | 42 | ||||
-rw-r--r-- | src/ahl-config.c | 34 | ||||
-rw-r--r-- | src/ahl-deviceenum.c | 81 | ||||
-rw-r--r-- | src/ahl-interface.h | 42 | ||||
-rw-r--r-- | src/ahl-policy.c | 1118 |
6 files changed, 1441 insertions, 276 deletions
diff --git a/src/ahl-binding.c b/src/ahl-binding.c index 3f8d71d..fe3dcee 100644 --- a/src/ahl-binding.c +++ b/src/ahl-binding.c @@ -24,11 +24,7 @@ #include "wrap-json.h" // Global high-level binding context -AHLCtxT g_AHLCtx; - -//afb_req_context_set ?? -// usr = afb_req_context_get(req); -// afb_req_context_clear(req); +AHLCtxT g_AHLCtx; static void AudioFormatStructToJSON(json_object **audioFormatJ, AudioFormatT * pAudioFormat) { @@ -42,10 +38,11 @@ static void AudioFormatStructToJSON(json_object **audioFormatJ, AudioFormatT * p static void EndpointInfoStructToJSON(json_object **endpointInfoJ, EndpointInfoT * pEndpointInfo) { json_object *formatInfoJ = NULL; - wrap_json_pack(endpointInfoJ, "{s:i,s:i,s:s,s:i}", + wrap_json_pack(endpointInfoJ, "{s:i,s:i,s:s,s:s,s:i}", "endpoint_id", pEndpointInfo->endpointID, "endpoint_type", pEndpointInfo->type, "device_name", pEndpointInfo->gsDeviceName->str, + "display_name", pEndpointInfo->gsDisplayName->str, "device_uri_type", pEndpointInfo->deviceURIType); AudioFormatStructToJSON(&formatInfoJ,&pEndpointInfo->format); json_object_object_add(*endpointInfoJ,"format",formatInfoJ); @@ -63,6 +60,27 @@ static void StreamInfoStructToJSON(json_object **streamInfoJ, StreamInfoT * pStr json_object_object_add(*streamInfoJ,"endpoint_info",endpointInfoJ); } +static streamID_t CreateNewStreamID() +{ + streamID_t newID = g_AHLCtx.nextStreamID; + g_AHLCtx.nextStreamID++; + return newID; +} + +static int FindRoleIndex( const char * in_pAudioRole) +{ + int index = -1; // Not found + for (int i = 0; i < g_AHLCtx.policyCtx.iNumberRoles; i++) + { + GString gs = g_array_index( g_AHLCtx.policyCtx.pAudioRoles, GString, i ); + if ( strcasecmp(gs.str,in_pAudioRole) == 0 ) + { + index = i; + break; + } + } + return index; +} static EndpointInfoT * GetEndpointInfo(endpointID_t in_endpointID, EndpointTypeT in_endpointType) { @@ -101,6 +119,46 @@ static StreamInfoT * GetActiveStream(streamID_t in_streamID) return pStreamInfo; } +static AHLClientCtxT * AllocateClientContext() +{ + AHLClientCtxT * pClientCtx = malloc(sizeof(AHLClientCtxT)); + pClientCtx->pEndpointAccessList = g_array_new(FALSE, TRUE, sizeof(endpointID_t)); + pClientCtx->pStreamAccessList = g_array_new(FALSE, TRUE, sizeof(streamID_t)); + return pClientCtx; +} + +static void TerminateClientContext(void * ptr) +{ + AHLClientCtxT * pClientCtx = (AHLClientCtxT *) ptr; + g_array_free( pClientCtx->pEndpointAccessList, TRUE); + g_array_free( pClientCtx->pStreamAccessList, TRUE); + free(ptr); +} + +static int CheckStreamAccessControl(AHLClientCtxT * pClientCtx, streamID_t streamID) +{ + int iAccessControl = AHL_ACCESS_CONTROL_DENIED; + for (int i = 0; i < pClientCtx->pStreamAccessList->len ; i++) { + streamID_t iID = g_array_index(pClientCtx->pStreamAccessList,streamID_t,i); + if (iID == streamID) { + iAccessControl = AHL_ACCESS_CONTROL_GRANTED; + } + } + return iAccessControl; +} + +static int CheckEndpointAccessControl(AHLClientCtxT * pClientCtx, endpointID_t endpointID) +{ + int iAccessControl = AHL_ACCESS_CONTROL_DENIED; + for (int i = 0; i < pClientCtx->pEndpointAccessList->len ; i++) { + endpointID_t iID = g_array_index(pClientCtx->pEndpointAccessList,endpointID_t,i); + if (iID == endpointID) { + iAccessControl = AHL_ACCESS_CONTROL_GRANTED; + } + } + return iAccessControl; +} + static int CreateEvents() { int err = 0; @@ -155,56 +213,29 @@ static void AhlBindingTerm() // TODO: Need to free g_strings in HAL list g_array_free(g_AHLCtx.pHALList,TRUE); + g_AHLCtx.pHALList = NULL; g_hash_table_remove_all(g_AHLCtx.policyCtx.pRolePriority); g_hash_table_destroy(g_AHLCtx.policyCtx.pRolePriority); + g_AHLCtx.policyCtx.pRolePriority = NULL; // TODO: Need to free g_strings in audio roles list g_array_free(g_AHLCtx.policyCtx.pAudioRoles,TRUE); + g_AHLCtx.policyCtx.pAudioRoles = NULL; g_array_free(g_AHLCtx.policyCtx.pInterruptBehavior,TRUE); + g_AHLCtx.policyCtx.pInterruptBehavior = NULL; g_array_free(g_AHLCtx.policyCtx.pActiveStreams,TRUE); + g_AHLCtx.policyCtx.pActiveStreams = NULL; AFB_INFO("Audio high-level Binding succesTermination"); } -static streamID_t CreateNewStreamID() -{ - streamID_t newID = g_AHLCtx.nextStreamID; - g_AHLCtx.nextStreamID++; - return newID; -} - -static int FindRoleIndex( const char * in_pAudioRole) -{ - int index = -1; // Not found - for (int i = 0; i < g_AHLCtx.policyCtx.iNumberRoles; i++) - { - GString gs = g_array_index( g_AHLCtx.policyCtx.pAudioRoles, GString, i ); - if ( strcasecmp(gs.str,in_pAudioRole) == 0 ) - index = i; - } - return index; -} - - // Binding initialization PUBLIC int AhlBindingInit() { int errcount = 0; - int err = 0; - - // This API uses services from low level audio - err = afb_daemon_require_api_v2("alsacore",1) ; - if( err != 0 ) - { - AFB_ERROR("Audio high level API requires alsacore API to be available"); - return 1; - } - - // Parse high-level binding JSON configuration file (will build device lists) - errcount += ParseHLBConfig(); atexit(AhlBindingTerm); - // This API uses services from low level audio + // This API uses services from low level audio. TODO: This dependency can be removed. errcount = afb_daemon_require_api_v2("alsacore",1) ; if( errcount != 0 ) { @@ -221,11 +252,10 @@ PUBLIC int AhlBindingInit() errcount += Policy_Init(); // Initialize list of active streams + g_AHLCtx.iNumActiveClients = 0; g_AHLCtx.policyCtx.pActiveStreams = g_array_new(FALSE,TRUE,sizeof(StreamInfoT)); - // TODO: Register for device changes ALSA low level audio service or HAL - - // TODO: Use AGL persistence framework to retrieve and set inital state/volumes/properties + // TODO: Use AGL persistence framework to retrieve and set initial volumes/properties AFB_DEBUG("Audio high-level Binding success errcount=%d", errcount); return errcount; @@ -233,23 +263,19 @@ PUBLIC int AhlBindingInit() PUBLIC void AhlOnEvent(const char *evtname, json_object *eventJ) { - // TODO: Implement handling events from HAL... AFB_DEBUG("AHL received event %s", evtname); + + //forward to policy to handle events + Policy_OnEvent(evtname, eventJ); } -// TODO: OnEventFunction when it actually subscribe to other binding events -// TODO: Dynamic device handling -// if HAL device availability change -> update source / sink list -// Call policy to attempt to assign role based on information (e.g. device name) -// Policy should also determine priority (insert device in right spot in the list) - PUBLIC void audiohlapi_get_sources(struct afb_req req) { json_object *sourcesJ = NULL; json_object *sourceJ = NULL; json_object *queryJ = NULL; char * audioRole = NULL; - + queryJ = afb_req_json(req); int err = wrap_json_unpack(queryJ, "{s:s}", "audio_role", &audioRole); if (err) { @@ -338,6 +364,15 @@ PUBLIC void audiohlapi_stream_open(struct afb_req req) } AFB_DEBUG("Parsed input arguments = audio_role:%s endpoint_type:%d endpoint_id:%d", audioRole,endpointType,endpointID); + // Check if there is already an existing context for this client + AHLClientCtxT * pClientCtx = afb_req_context_get(req); // Retrieve client-specific data structure + if (pClientCtx == NULL) + { + g_AHLCtx.iNumActiveClients++; + pClientCtx = AllocateClientContext(); + afb_req_context_set(req, pClientCtx, TerminateClientContext); + } + int iRoleIndex = FindRoleIndex(audioRole); if (iRoleIndex < 0) { afb_req_fail_f(req, "Invalid audio role", "Audio role was not found in configuration -> %s",audioRole); @@ -379,23 +414,23 @@ PUBLIC void audiohlapi_stream_open(struct afb_req req) } } + // Create stream + streamInfo.streamID = CreateNewStreamID(); // create new ID + streamInfo.streamState = STREAM_STATE_IDLE; + streamInfo.streamMute = STREAM_UNMUTED; + streamInfo.pEndpointInfo = pEndpointInfo; + // Call policy to verify whether creating a new audio stream is allowed in current context and possibly take other actions - policyAllowed = Policy_OpenStream(audioRole, endpointType, pEndpointInfo->endpointID); + policyAllowed = Policy_OpenStream(&streamInfo); if (policyAllowed == AHL_POLICY_REJECT) { afb_req_fail(req, "Audio policy violation", "Open stream not allowed in current context"); return; - } - - // Create stream - streamInfo.streamID = CreateNewStreamID(); // create new ID - streamInfo.streamState = STREAM_STATUS_READY; - streamInfo.streamMute = STREAM_UNMUTED; - streamInfo.pEndpointInfo = pEndpointInfo; + } char streamEventName[128]; snprintf(streamEventName,128,"ahl_streamstate_%d",streamInfo.streamID); - + streamInfo.streamStateEvent = afb_daemon_make_event(streamEventName); err = !afb_event_is_valid(streamInfo.streamStateEvent); if (err) { @@ -409,6 +444,10 @@ PUBLIC void audiohlapi_stream_open(struct afb_req req) return; } + // Add to client context stream ID and endpoint ID access rights + g_array_append_val(pClientCtx->pStreamAccessList, streamInfo.streamID); + g_array_append_val(pClientCtx->pEndpointAccessList, streamInfo.pEndpointInfo->endpointID); + // Push stream on active stream list g_array_append_val( g_AHLCtx.policyCtx.pActiveStreams, streamInfo ); @@ -431,10 +470,30 @@ PUBLIC void audiohlapi_stream_close(struct afb_req req) } AFB_DEBUG("Parsed input arguments = stream_id:%d", streamID); - // TODO: Validate that the application ID from which the stream close is coming is the one that opened it, otherwise fail and do nothing + // Check if there is already an existing context for this client + AHLClientCtxT * pClientCtx = afb_req_context_get(req); // Retrieve client-specific data structure + if (pClientCtx == NULL) + { + afb_req_fail(req, "No context associated with the request", "No context associated with the request"); + return; + } + + // Verify that this client can control the stream + int iStreamAccessControl = CheckStreamAccessControl( pClientCtx, streamID ); + if (iStreamAccessControl == AHL_ACCESS_CONTROL_DENIED) + { + afb_req_fail(req, "Access control denied", "Close stream not allowed in current client context"); + return; + } // Call policy to verify whether creating a new audio stream is allowed in current context and possibly take other actions - policyAllowed = Policy_CloseStream(streamID); + StreamInfoT * pStreamInfo = GetActiveStream(streamID); + if (pStreamInfo == NULL) { + afb_req_fail_f(req, "Stream not found", "Specified stream not currently active stream_id -> %d",streamID); + return; + } + + policyAllowed = Policy_CloseStream(pStreamInfo); if (policyAllowed == AHL_POLICY_REJECT) { afb_req_fail(req, "Audio policy violation", "Close stream not allowed in current context"); @@ -454,7 +513,7 @@ PUBLIC void audiohlapi_stream_close(struct afb_req req) if (iValid) { err = afb_req_unsubscribe(req,streamInfo.streamStateEvent); if (err) { - afb_req_fail(req, "Stream event subscription failure", "Could not subscribe to stream specific state change event"); + afb_req_fail(req, "Stream event subscription failure", "Could not unsubscribe to stream specific state change event"); return; } } @@ -474,6 +533,22 @@ PUBLIC void audiohlapi_stream_close(struct afb_req req) return; } + // Find index for cases where there are multiple streams per client + // Remove from client context stream ID and endpoint ID access rights + for (int i = 0; i < pClientCtx->pStreamAccessList->len ; i++) { + streamID_t iID = g_array_index(pClientCtx->pStreamAccessList,streamID_t,i); + if (iID == streamID) { + g_array_remove_index(pClientCtx->pStreamAccessList, i); + g_array_remove_index(pClientCtx->pEndpointAccessList, i); + } + } + + if (pClientCtx->pStreamAccessList->len == 0 && pClientCtx->pEndpointAccessList == 0) { + // If no more streams/endpoints owner, clear session + afb_req_context_clear(req); + g_AHLCtx.iNumActiveClients--; + } + afb_req_success(req, NULL, "Stream close completed"); } @@ -498,22 +573,29 @@ PUBLIC void audiohlapi_stream_close(struct afb_req req) return; } - policyAllowed = Policy_SetStreamState(streamID,streamState); - if (policyAllowed == AHL_POLICY_REJECT) + // Check if there is already an existing context for this client + AHLClientCtxT * pClientCtx = afb_req_context_get(req); // Retrieve client-specific data structure + if (pClientCtx == NULL) { - afb_req_fail(req, "Audio policy violation", "Change stream state not allowed in current context"); + afb_req_fail(req, "No context associated with the request", "No context associated with the request"); return; } - pStreamInfo->streamState = streamState; - // Package event data - json_object * eventDataJ = NULL; - err = wrap_json_pack(&eventDataJ,"{s:i,s:i}","mute",pStreamInfo->streamMute,"state",streamState); - if (err) { - afb_req_fail_f(req, "Invalid event data for stream state event", "Invalid event data for stream state event: %s",json_object_to_json_string(eventDataJ)); + // Verify that this client can control the stream + int iStreamAccessControl = CheckStreamAccessControl( pClientCtx, streamID ); + if (iStreamAccessControl == AHL_ACCESS_CONTROL_DENIED) + { + afb_req_fail(req, "Access control denied", "Set stream state not allowed in current client context"); return; } - afb_event_push(pStreamInfo->streamStateEvent,eventDataJ); + int AudioRoleIndex = FindRoleIndex(pStreamInfo->pEndpointInfo->gsAudioRole->str); + + policyAllowed = Policy_SetStreamState(pStreamInfo, AudioRoleIndex, streamState); + if (policyAllowed == AHL_POLICY_REJECT) + { + afb_req_fail(req, "Audio policy violation", "Change stream state not allowed in current context"); + return; + } afb_req_success(req, NULL, "Set stream state"); } @@ -539,28 +621,33 @@ PUBLIC void audiohlapi_stream_close(struct afb_req req) return; } - policyAllowed = Policy_SetStreamMute(streamID,muteState); - if (policyAllowed == AHL_POLICY_REJECT) + // Check if there is already an existing context for this client + AHLClientCtxT * pClientCtx = afb_req_context_get(req); // Retrieve client-specific data structure + if (pClientCtx == NULL) { - afb_req_fail(req, "Audio policy violation", "Mute stream not allowed in current context"); + afb_req_fail(req, "No context associated with the request", "No context associated with the request"); return; } - pStreamInfo->streamMute = muteState; + // Verify that this client can control the stream + int iStreamAccessControl = CheckStreamAccessControl( pClientCtx, streamID ); + if (iStreamAccessControl == AHL_ACCESS_CONTROL_DENIED) + { + afb_req_fail(req, "Access control denied", "Set stream mute state not allowed in current client context"); + return; + } - // Package event data - json_object * eventDataJ = NULL; - err = wrap_json_pack(&eventDataJ,"{s:i,s:i}","mute",muteState,"state",pStreamInfo->streamState); - if (err) { - afb_req_fail_f(req, "Invalid event data for stream state event", "Invalid event data for stream state event: %s",json_object_to_json_string(eventDataJ)); + policyAllowed = Policy_SetStreamMute(pStreamInfo,muteState); + if (policyAllowed == AHL_POLICY_REJECT) + { + afb_req_fail(req, "Audio policy violation", "Mute stream not allowed in current context"); return; } - afb_event_push(pStreamInfo->streamStateEvent,eventDataJ); afb_req_success(req, NULL, "Set stream mute completed"); } -PUBLIC void audiohlapi_get_stream_info(struct afb_req req) + PUBLIC void audiohlapi_get_stream_info(struct afb_req req) { json_object *queryJ = NULL; streamID_t streamID = AHL_UNDEFINED; @@ -601,18 +688,6 @@ PUBLIC void audiohlapi_set_volume(struct afb_req req) } AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d volume:%s", endpointType,endpointID,volumeStr); - // TODO: Parse volume string to support increment/absolute/percent notation (or delegate to action / policy layer to interpret) - int vol = atoi(volumeStr); - - // TODO: Policy needs way to set cached endpoint volume value (eg.) - policyAllowed = Policy_SetVolume(endpointType, endpointID, volumeStr); // TODO: Potentially retrieve modified value by policy (e.g. volume limit) - if (!policyAllowed) - { - afb_req_fail(req, "Audio policy violation", "Set volume not allowed in current context"); - return; - } - - // TODO: Cache HAL control name during device enumeration for efficiency EndpointInfoT * pEndpointInfo = GetEndpointInfo(endpointID,endpointType); if (pEndpointInfo == NULL) { @@ -620,37 +695,28 @@ PUBLIC void audiohlapi_set_volume(struct afb_req req) return; } - // Using audio role available from endpoint to target the right HAL control (build string based on convention) - GString * gsHALControlName = g_string_new(pEndpointInfo->gsAudioRole->str); - g_string_append(gsHALControlName,"_Ramp"); // Or _Vol for direct control (no ramping) - - // Set endpoint volume using HAL services (leveraging ramps etc.) - json_object *j_response, *j_query = NULL; - - // Package query - err = wrap_json_pack(&j_query,"{s:s,s:i}","label",gsHALControlName->str, "val",vol); - if (err) { - afb_req_fail_f(req, "Invalid query for HAL ctlset", "Invalid query for HAL ctlset: %s",json_object_to_json_string(j_query)); + // Check if there is already an existing context for this client + AHLClientCtxT * pClientCtx = afb_req_context_get(req); // Retrieve client-specific data structure + if (pClientCtx == NULL) + { + afb_req_fail(req, "No context associated with the request", "No context associated with the request"); return; } - // TODO: Move this to ref implmentation policy - // Set the volume using the HAL - err = afb_service_call_sync(pEndpointInfo->gsHALAPIName->str, "ctlset", j_query, &j_response); - if (err) { - afb_req_fail_f(req, "HAL ctlset failure", "Could not ctlset on HAL: %s",pEndpointInfo->gsHALAPIName->str); + // Verify that this client can control the stream + int iEndpointAccessControl = CheckEndpointAccessControl( pClientCtx, endpointID ); + if (iEndpointAccessControl == AHL_ACCESS_CONTROL_DENIED) + { + afb_req_fail(req, "Access control denied", "Set volume not allowed in current client context"); return; } - AFB_DEBUG("HAL ctlset response=%s", json_object_to_json_string(j_response)); - // Package event data - json_object * eventDataJ = NULL; - err = wrap_json_pack(&eventDataJ,"{s:i,s:i,s:i}","endpoint_id",endpointID,"endpoint_type",endpointType,"value",vol); - if (err) { - afb_req_fail_f(req, "Invalid event data for volume event", "Invalid event data for volume event: %s",json_object_to_json_string(eventDataJ)); + policyAllowed = Policy_SetVolume(pEndpointInfo, volumeStr); + if (!policyAllowed) + { + afb_req_fail(req, "Audio policy violation", "Set volume not allowed in current context"); return; } - afb_event_push(g_AHLCtx.policyCtx.volumeEvent,eventDataJ); afb_req_success(req, NULL, "Set volume completed"); } @@ -670,7 +736,6 @@ PUBLIC void audiohlapi_get_volume(struct afb_req req) } AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d", endpointType,endpointID); - // TODO: Cache HAL control name during device enumeration for efficiency EndpointInfoT * pEndpointInfo = GetEndpointInfo(endpointID,endpointType); if (pEndpointInfo == NULL) { @@ -678,43 +743,7 @@ PUBLIC void audiohlapi_get_volume(struct afb_req req) return; } - // Using audio role available from endpoint to target the right HAL control (build string based on convention) - GString * gsHALControlName = g_string_new(pEndpointInfo->gsAudioRole->str); - g_string_append(gsHALControlName,"_Vol"); // Use current value, not ramp target - - // Set endpoint volume using HAL services (leveraging ramps etc.) - json_object *j_response, *j_query = NULL; - - // TODO: Returned cached endpoint volume value (controlled by policy) - // Package query - err = wrap_json_pack(&j_query,"{s:s}","label",gsHALControlName->str); - if (err) { - afb_req_fail_f(req, "Invalid query for HAL ctlget", "Invalid query for HAL ctlget: %s",json_object_to_json_string(j_query)); - return; - } - - // TODO: Return cached value or move to policy - // Set the volume using the HAL - err = afb_service_call_sync(pEndpointInfo->gsHALAPIName->str, "ctlget", j_query, &j_response); - if (err) { - afb_req_fail_f(req, "HAL ctlget failure", "Could not ctlget on HAL: %s",pEndpointInfo->gsHALAPIName->str); - return; - } - AFB_INFO("HAL ctlget response=%s", json_object_to_json_string(j_response)); - - // Parse response - json_object * jRespObj = NULL; - json_object_object_get_ex(j_response, "response", &jRespObj); - json_object * jVal = NULL; - json_object_object_get_ex(jRespObj, "val", &jVal); - int val1 = 0, val2 = 0; // Why 2 values? - err = wrap_json_unpack(jVal, "[ii]", &val1, &val2); - if (err) { - afb_req_fail_f(req,"Volume retrieve failed", "Could not retrieve volume value -> %s", json_object_get_string(jVal)); - return; - } - - volumeJ = json_object_new_double((double)val1); + volumeJ = json_object_new_double((double)pEndpointInfo->iVolume); afb_req_success(req, volumeJ, "Retrieved volume value"); } @@ -763,32 +792,49 @@ PUBLIC void audiohlapi_set_property(struct afb_req req) endpointID_t endpointID = AHL_UNDEFINED; EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE; char * propertyName = NULL; - char * propValueStr = NULL; + json_object * propValueJ = NULL; int policyAllowed = AHL_POLICY_REJECT; - - // TODO: object type detection (string = state, numeric = property) queryJ = afb_req_json(req); - int err = wrap_json_unpack(queryJ, "{s:i,s:i,s:s,s:s}", "endpoint_type", &endpointType,"endpoint_id",&endpointID,"property_name",&propertyName,"value",&propValueStr); + int err = wrap_json_unpack(queryJ, "{s:i,s:i,s:s,s:o}", "endpoint_type", &endpointType,"endpoint_id",&endpointID,"property_name",&propertyName,"value",&propValueJ); if (err) { afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); return; } - AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d property_name:%s value:%s", endpointType,endpointID,propertyName,propValueStr); - - // TODO: Parse property value string to support increment/absolute/percent notation + AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d property_name:%s", endpointType,endpointID,propertyName); + + + EndpointInfoT * pEndpointInfo = GetEndpointInfo(endpointID,endpointType); + if (pEndpointInfo == NULL) + { + afb_req_fail_f(req, "Endpoint not found", "Endpoint information not found for id:%d type%d",endpointID,endpointType); + return; + } + + // Check if there is already an existing context for this client + AHLClientCtxT * pClientCtx = afb_req_context_get(req); // Retrieve client-specific data structure + if (pClientCtx == NULL) + { + afb_req_fail(req, "No context associated with the request", "No context associated with the request"); + return; + } + + // Verify that this client can control the stream + int iEndpointAccessControl = CheckEndpointAccessControl( pClientCtx, endpointID ); + if (iEndpointAccessControl == AHL_ACCESS_CONTROL_DENIED) + { + afb_req_fail(req, "Access control denied", "Set property not allowed in current client context"); + return; + } // Call policy to allow custom policy actions in current context - policyAllowed = Policy_SetProperty(endpointType, endpointID, propertyName, propValueStr); // TODO: Potentially retrieve modified value by policy (e.g. parameter limit) + policyAllowed = Policy_SetProperty(pEndpointInfo, propertyName, propValueJ); if (!policyAllowed) { afb_req_fail(req, "Audio policy violation", "Set endpoint property not allowed in current context"); return; } - // TODO: Policy to dispatch on right service target - // TODO: Policy tp cache value in property list - afb_event_push(g_AHLCtx.policyCtx.propertyEvent,queryJ); afb_req_success(req, NULL, "Set property completed"); @@ -800,8 +846,6 @@ PUBLIC void audiohlapi_get_property(struct afb_req req) endpointID_t endpointID = AHL_UNDEFINED; EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE; char * propertyName = NULL; - json_object *propertyValJ; - double value = 0.0; queryJ = afb_req_json(req); int err = wrap_json_unpack(queryJ, "{s:i,s:i,s:s}", "endpoint_type", &endpointType,"endpoint_id",&endpointID,"property_name",&propertyName); @@ -811,11 +855,21 @@ PUBLIC void audiohlapi_get_property(struct afb_req req) } AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d property_name:%s", endpointType,endpointID,propertyName); - // TODO: Retriev189e cached property value - // TODO Account for properties with string types + EndpointInfoT * pEndpointInfo = GetEndpointInfo(endpointID,endpointType); + if (pEndpointInfo == NULL) + { + afb_req_fail_f(req, "Endpoint not found", "Endpoint information not found for id:%d type%d",endpointID,endpointType); + return; + } + + // Retrieve cached property value + json_object * propertyValJ = (json_object *)g_hash_table_lookup(pEndpointInfo->pPropTable,propertyName); + if (propertyValJ == NULL) { + afb_req_fail_f(req, "Property not found", "Property information not found: %s",propertyName); + return; + } - value = 93.0; // TODO: Get actual property value - propertyValJ = json_object_new_double(value); + json_object_get(propertyValJ); // Increase ref count so that framework does not free our JSON object afb_req_success(req, propertyValJ, "Retrieved property value"); } diff --git a/src/ahl-binding.h b/src/ahl-binding.h index 508ca0a..8f308d5 100644 --- a/src/ahl-binding.h +++ b/src/ahl-binding.h @@ -31,12 +31,22 @@ #define AHL_POLICY_ACCEPT 1 #define AHL_POLICY_REJECT 0 +#define AHL_ACCESS_CONTROL_GRANTED 1 +#define AHL_ACCESS_CONTROL_DENIED 0 #define AHL_UNDEFINED -1 typedef int endpointID_t; typedef int streamID_t; +// Define default behavior of audio role when interrupted by higher priority sources +typedef enum InterruptedBehavior { + AHL_INTERRUPTEDBEHAVIOR_CONTINUE = 0, // Continue to play when interrupted (e.g. media may be ducked) + AHL_INTERRUPTEDBEHAVIOR_CANCEL, // Abort playback when interrupted (e.g. non-important HMI feedback that does not make sense later) + AHL_INTERRUPTEDBEHAVIOR_PAUSE, // Pause source when interrupted, to be resumed afterwards (e.g. non-temporal guidance) + AHL_INTERRUPTEDBEHAVIOR_MAXVALUE, // Enum count, keep at the end +} InterruptedBehaviorT; + typedef enum EndpointSelectionMode { AHL_ENDPOINTSELMODE_AUTO = 0, // Automatic endpoint selection based on config priority AHL_ENDPOINTSELMODE_MANUAL, // Explicit endpoint selection @@ -61,7 +71,8 @@ typedef struct EndpointInfo { endpointID_t endpointID; // Unique endpoint ID (per type) EndpointTypeT type; // Source or sink device - GString * gsDeviceName; // Device name for applications to display + GString * gsDeviceName; // Unique device card name + GString * gsDisplayName; // Application display name GString * gsDeviceURI; // Associated URI DeviceURITypeT deviceURIType; // Device URI type (includes audio domain information) GString * gsAudioRole; // Audio role that registered this endpoint @@ -75,7 +86,7 @@ typedef struct EndpointInfo typedef struct StreamInfo { streamID_t streamID; // Stream unique ID EndpointInfoT * pEndpointInfo; // Associated endpoint information - StreamStateT streamState; // Stream activity state + StreamStateT streamState; // Stream activity state StreamMuteT streamMute; // Stream mute state struct afb_event streamStateEvent; // Stream specific event for stream state changes EndpointSelectionModeT endpointSelMode; // Automatic (priority based) or manual endpoint selection @@ -103,8 +114,15 @@ typedef struct AHLCtx { endpointID_t nextSinkEndpointID; // Counter to assign new ID endpointID_t nextStreamID; // Counter to assign new ID GArray * pHALList; // List of HAL dependencies + int iNumActiveClients; // Number of clients with active stream(s) } AHLCtxT; +// Client specific binding context +typedef struct AHLClientCtx { + GArray * pEndpointAccessList; // List of endpoints that client has control over + GArray * pStreamAccessList; // List of streams that client has control over +} AHLClientCtxT; + // ahl-binding.c PUBLIC int AhlBindingInit(); PUBLIC void AhlOnEvent(const char *evtname, json_object *eventJ); @@ -117,17 +135,17 @@ void TermEndpoints(); int ParseHLBConfig(); // ahl-policy.c int Policy_Endpoint_Property_Init(EndpointInfoT * io_pEndpointInfo); -int Policy_Init(); -void Policy_Term(); -int Policy_OpenStream(char *pAudioRole, EndpointTypeT endpointType, endpointID_t endpointID); -int Policy_CloseStream(streamID_t streamID); -int Policy_SetStreamState(streamID_t streamID, StreamStateT streamState ); -int Policy_SetStreamMute(streamID_t streamID, StreamMuteT streamMute); -int Policy_SetVolume(EndpointTypeT endpointType, endpointID_t endpointID, char *volumeStr); -int Policy_SetProperty(EndpointTypeT endpointType, endpointID_t endpointID, char *propertyName, char *propValueStr); +int Policy_OpenStream(StreamInfoT * pStreamInfo); +int Policy_CloseStream(StreamInfoT * pStreamInfo); +int Policy_SetStreamState(StreamInfoT * pStreamInfo, int AudioRoleIndex, StreamStateT streamState); +int Policy_SetStreamMute(StreamInfoT * pStreamInfo, StreamMuteT streamMute); int Policy_PostEvent(char *eventName, char *audioRole, char *mediaName, void *audioContext); int Policy_AudioDeviceChange(); - - +int Policy_SetVolume(EndpointInfoT * f_pEndpointInfo, char *volumeStr); +//Todo +int Policy_SetProperty(EndpointInfoT * f_pEndpointInfo, char *propertyName, json_object *propValue); +int Policy_Init(); +void Policy_Term(); +void Policy_OnEvent(const char *evtname, json_object *eventJ); #endif // AHL_BINDING_INCLUDE diff --git a/src/ahl-config.c b/src/ahl-config.c index dcf828f..aa55b28 100644 --- a/src/ahl-config.c +++ b/src/ahl-config.c @@ -29,6 +29,7 @@ int ParseHLBConfig() { char * versionStr = NULL; json_object * jAudioRoles = NULL; json_object * jHALList = NULL; + char * policyModule = NULL; // TODO: This should be retrieve from binding startup arguments char configfile_path[256]; @@ -43,11 +44,13 @@ int ParseHLBConfig() { return 1; } - int err = wrap_json_unpack(config_JFile, "{s:s,s:o,s:o}", "version", &versionStr,"audio_roles",&jAudioRoles,"hal_list",&jHALList); + int err = wrap_json_unpack(config_JFile, "{s:s,s:s,s:o,s:o}", "version", &versionStr,"policy_module", &policyModule,"audio_roles",&jAudioRoles,"hal_list",&jHALList); if (err) { AFB_ERROR("Invalid configuration file -> %s", configfile_path); return 1; } + AFB_INFO("Version: %s", versionStr); + AFB_INFO("Policy module: %s", policyModule); int iHALListLength = json_object_array_length(jHALList); int iNumberOfRoles = json_object_array_length(jAudioRoles); @@ -61,7 +64,6 @@ int ParseHLBConfig() { g_AHLCtx.policyCtx.pSourceEndpoints = g_ptr_array_sized_new(iNumberOfRoles); g_AHLCtx.policyCtx.pSinkEndpoints = g_ptr_array_sized_new(iNumberOfRoles); g_AHLCtx.policyCtx.pEventList = g_ptr_array_sized_new(iNumberOfRoles); - g_AHLCtx.policyCtx.iNumberRoles = iNumberOfRoles; for (int i = 0; i < iHALListLength; i++) { @@ -88,19 +90,20 @@ int ParseHLBConfig() { json_object * jInputDevices = NULL; json_object * jEvents = NULL; char * pRoleName = NULL; - InterruptedBehaviorT interupBehavior = AHL_INTERRUPTEDBEHAVIOR_CONTINUE; //Default + char * pInteruptBehavior = NULL; int iNumOutDevices = 0; int iNumInDevices = 0; int iNumEvents = 0; - err = wrap_json_unpack(jAudioRole, "{s:s,s:i,s?o,s?o,s?o,s?i}", + err = wrap_json_unpack(jAudioRole, "{s:s,s:i,s:s,s?o,s?o,s?o}", "name", &pRoleName, "priority",&priority, + "interupt_behavior",&pInteruptBehavior, "output",&jOutputDevices, "input",&jInputDevices, - "events",&jEvents, - "interupt_behavior",&interupBehavior); + "events",&jEvents + ); if (err) { AFB_ERROR("Invalid audio role configuration : %s", json_object_to_json_string(jAudioRole)); return 1; @@ -115,9 +118,24 @@ int ParseHLBConfig() { GString * gRoleName = g_string_new( pRoleName ); g_array_append_val( g_AHLCtx.policyCtx.pAudioRoles, *gRoleName ); - g_hash_table_insert(g_AHLCtx.policyCtx.pRolePriority, pRoleName, &priority); + g_hash_table_insert(g_AHLCtx.policyCtx.pRolePriority, pRoleName, GINT_TO_POINTER(priority)); - g_array_append_val(g_AHLCtx.policyCtx.pInterruptBehavior, interupBehavior); + // Map interupt behavior string to enum value + InterruptedBehaviorT interuptBehavior = AHL_INTERRUPTEDBEHAVIOR_CONTINUE; + if ( strcasecmp(pInteruptBehavior,AHL_INTERRUPTEDBEHAVIOR_CONTINUE_STR) == 0 ) { + interuptBehavior = AHL_INTERRUPTEDBEHAVIOR_CONTINUE; + } + else if ( strcasecmp(pInteruptBehavior,AHL_INTERRUPTEDBEHAVIOR_CANCEL_STR) == 0 ) { + interuptBehavior = AHL_INTERRUPTEDBEHAVIOR_CANCEL; + } + else if ( strcasecmp(pInteruptBehavior,AHL_INTERRUPTEDBEHAVIOR_PAUSE_STR) == 0 ) { + interuptBehavior = AHL_INTERRUPTEDBEHAVIOR_PAUSE; + } + else { + AFB_ERROR("Unknown interrupt behavior : %s", pInteruptBehavior); + return 1; + } + g_array_append_val(g_AHLCtx.policyCtx.pInterruptBehavior, interuptBehavior); // Sources GArray * pRoleSourceDeviceArray = g_array_new(FALSE, TRUE, sizeof(EndpointInfoT)); diff --git a/src/ahl-deviceenum.c b/src/ahl-deviceenum.c index c69108e..4722333 100644 --- a/src/ahl-deviceenum.c +++ b/src/ahl-deviceenum.c @@ -27,6 +27,7 @@ extern AHLCtxT g_AHLCtx; +// TODO: Hash from endpoint ID information instead static endpointID_t CreateNewSourceID() { endpointID_t newID = g_AHLCtx.nextSourceEndpointID; @@ -34,6 +35,7 @@ static endpointID_t CreateNewSourceID() return newID; } +// TODO: Hash from endpoint ID information instead static endpointID_t CreateNewSinkID() { endpointID_t newID = g_AHLCtx.nextSinkEndpointID; @@ -42,6 +44,7 @@ static endpointID_t CreateNewSinkID() } // Watchout: This function uses strtok and is destructive on the input string (use a copy) +// TODO: Perhaps it would be clearer to separate domain and device URI in both API inputs and outputs static int SeparateDomainFromDeviceURI( char * in_pDeviceURI, char ** out_pDomain, char ** out_pDevice) { *out_pDomain = strtok(in_pDeviceURI, "."); @@ -85,8 +88,13 @@ static int FillALSAPCMInfo( snd_pcm_t * in_pPcmHandle, EndpointInfoT * out_pEndp snd_pcm_type_t pcmType = 0; snd_pcm_info_t * pPcmInfo = NULL; int iAlsaRet = 0; - const char * pDeviceName = NULL; + 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); @@ -100,45 +108,27 @@ static int FillALSAPCMInfo( snd_pcm_t * in_pPcmHandle, EndpointInfoT * out_pEndp 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_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 + return 1; } - g_string_assign(out_pEndpointInfo->gsDeviceName,pDeviceName); // Overwritten by HAL name if available // 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"); - retVal = 1; - goto End; + return 1; } // get device number @@ -146,8 +136,7 @@ static int FillALSAPCMInfo( snd_pcm_t * in_pPcmHandle, EndpointInfoT * out_pEndp if ( out_pEndpointInfo->alsaInfo.deviceNum < 0 ) { AFB_WARNING("No Alsa device number available"); - retVal = 1; - goto End; + return 1; } // get sub-device number @@ -155,15 +144,37 @@ static int FillALSAPCMInfo( snd_pcm_t * in_pPcmHandle, EndpointInfoT * out_pEndp if ( out_pEndpointInfo->alsaInfo.subDeviceNum < 0 ) { AFB_WARNING("No Alsa subdevice number available"); - retVal = 1; - goto End; + 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; } -End: - if(pPcmInfo) { - snd_pcm_info_free(pPcmInfo); - pPcmInfo = NULL; + // 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; } + g_string_assign(out_pEndpointInfo->gsDeviceName,pCardName); + + snd_ctl_close(ctlHandle); return retVal; } @@ -201,7 +212,7 @@ static int RetrieveAssociatedHALAPIName(EndpointInfoT * io_pEndpointInfo) int iCardNum = atoi(pDevIDStr+3); if (iCardNum == io_pEndpointInfo->alsaInfo.cardNum) { g_string_assign(io_pEndpointInfo->gsHALAPIName,pAPIName); - g_string_assign(io_pEndpointInfo->gsDeviceName,pShortName); + g_string_assign(io_pEndpointInfo->gsDisplayName,pShortName); found = 1; break; } @@ -214,6 +225,7 @@ static void InitEndpointInfo( EndpointInfoT * out_pEndpointInfo ) out_pEndpointInfo->endpointID = AHL_UNDEFINED; out_pEndpointInfo->type = ENDPOINTTYPE_MAXVALUE; out_pEndpointInfo->gsDeviceName = g_string_new("Unassigned"); + out_pEndpointInfo->gsDisplayName = g_string_new("Unassigned"); out_pEndpointInfo->gsDeviceURI = g_string_new("Unassigned"); out_pEndpointInfo->deviceURIType = DEVICEURITYPE_MAXVALUE; out_pEndpointInfo->gsAudioRole = g_string_new("Unassigned"); @@ -230,6 +242,7 @@ static void InitEndpointInfo( EndpointInfoT * out_pEndpointInfo ) static void TermEndpointInfo( EndpointInfoT * out_pEndpointInfo ) { g_string_free(out_pEndpointInfo->gsDeviceName,TRUE); + g_string_free(out_pEndpointInfo->gsDisplayName,TRUE); g_string_free(out_pEndpointInfo->gsDeviceURI,TRUE); g_string_free(out_pEndpointInfo->gsAudioRole,TRUE); g_string_free(out_pEndpointInfo->gsHALAPIName,TRUE); @@ -306,7 +319,7 @@ int EnumerateSources(json_object * in_jSourceArray, int in_iRoleIndex, char * in // 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 - g_string_assign(endpointInfo.gsDeviceName,pDeviceURIPCM); // Overwritten by HAL name if available + g_string_assign(endpointInfo.gsDeviceName,pDeviceURIPCM); if (IsAlsaDomain(pDeviceURIDomain)) { diff --git a/src/ahl-interface.h b/src/ahl-interface.h index edead36..04b2865 100644 --- a/src/ahl-interface.h +++ b/src/ahl-interface.h @@ -29,6 +29,7 @@ typedef enum DeviceURIType { DEVICEURITYPE_ALSA_DMIX, // Alsa Dmix device URI (only for playback devices) DEVICEURITYPE_ALSA_DSNOOP, // Alsa DSnoop device URI (only for capture devices) DEVICEURITYPE_ALSA_SOFTVOL, // Alsa softvol device URI + DEVICEURITYPE_ALSA_PLUG, // Alsa plug device URI DEVICEURITYPE_ALSA_OTHER, // Alsa domain URI device of unspecified type DEVICEURITYPE_PULSE, // Pulse device URI DEVICEURITYPE_GSTREAMER, // GStreamer device URI @@ -37,9 +38,10 @@ typedef enum DeviceURIType { } DeviceURITypeT; typedef enum StreamState { - STREAM_STATUS_READY = 0, // Stream is inactive - STREAM_STATUS_RUNNING, // Stream is running - STREAM_STATUS_MAXVALUE, // Enum count, keep at the end + STREAM_STATE_IDLE = 0, // Stream is inactive + STREAM_STATE_RUNNING, // Stream is active and running + STREAM_STATE_PAUSED, // Stream is active but paused + STREAM_STATE_MAXVALUE // Enum count, keep at the end } StreamStateT; typedef enum StreamMute { @@ -48,13 +50,20 @@ typedef enum StreamMute { STREAM_MUTE_MAXVALUE, // Enum count, keep at the end } StreamMuteT; -// Define default behavior of audio role when interrupted by higher priority sources -typedef enum InterruptedBehavior { - AHL_INTERRUPTEDBEHAVIOR_CONTINUE = 0, // Continue to play when interrupted (e.g. media may be ducked) - AHL_INTERRUPTEDBEHAVIOR_CANCEL, // Abort playback when interrupted (e.g. non-important HMI feedback that does not make sense later) - AHL_INTERRUPTEDBEHAVIOR_PAUSE, // Pause source when interrupted, to be resumed afterwards (e.g. non-temporal guidance) - AHL_INTERRUPTEDBEHAVIOR_MAXVALUE, // Enum count, keep at the end -} InterruptedBehaviorT; +typedef enum StreamEvent { + STREAM_EVENT_START = 0, // Stream is inactive + STREAM_EVENT_STOP, // Stream is running + STREAM_EVENT_PAUSE, // Audio stream paused + STREAM_EVENT_RESUME, // Audio stream resumed + STREAM_EVENT_MUTED, // Audio stream muted + STREAM_EVENT_UNMUTED, // Audio stream unmuted + STREAM_STATUS_MAXVALUE // Enum count, keep at the end +} StreamEventT; + +// Define default behavior of audio role when interrupted by higher priority sources (in configuration) +#define AHL_INTERRUPTEDBEHAVIOR_CONTINUE_STR "continue" // Continue to play when interrupted (e.g. media may be ducked) +#define AHL_INTERRUPTEDBEHAVIOR_CANCEL_STR "cancel" // Abort playback when interrupted (e.g. non-important HMI feedback that does not make sense later) +#define AHL_INTERRUPTEDBEHAVIOR_PAUSE_STR "pause" // Pause source when interrupted, to be resumed afterwards (e.g. non-temporal guidance) #define AHL_ENDPOINT_PROPERTY_EVENT "ahl_endpoint_property_event" #define AHL_ENDPOINT_VOLUME_EVENT "ahl_endpoint_volume_event" @@ -80,15 +89,6 @@ typedef enum SampleType { AHL_FORMAT_MAXVALUE, // Enum count, keep at the end } SampleTypeT; -// Stream event types -typedef enum StreamEventType { - AHL_STREAMEVENT_START = 0, // Audio streaming should start - AHL_STREAMEVENT_STOP, // Audio streaming should stop - AHL_STREAMEVENT_UNMUTE, // Audio stream unmuted - AHL_STREAMEVENT_MUTE, // Audio stream muted - AHL_STREAMEVENT_MAXVALUE, // Enum count, keep at the end -} StreamEventTypeT; - // Known audio domain string definitions (for configuration file format and device URI interpretation) #define AHL_DOMAIN_ALSA "alsa" #define AHL_DOMAIN_PULSE "pulse" @@ -99,7 +99,7 @@ typedef enum StreamEventType { #define AHL_ROLE_WARNING "warning" // Safety-relevant or critical alerts/alarms #define AHL_ROLE_GUIDANCE "guidance" // Important user information where user action is expected (e.g. navigation instruction) #define AHL_ROLE_NOTIFICATION "notification" // HMI or else notifications (e.g. touchscreen events, speech recognition on/off,...) -#define AHL_ROLE_COMMUNICATION "communications" // Voice communications (e.g. handsfree, speech recognition) +#define AHL_ROLE_COMMUNICATION "communication" // Voice communications (e.g. handsfree, speech recognition) #define AHL_ROLE_ENTERTAINMENT "entertainment" // Multimedia content (e.g. tuner, media player, etc.) #define AHL_ROLE_SYSTEM "system" // System level content or development #define AHL_ROLE_STARTUP "startup" // Early (startup) sound @@ -113,7 +113,7 @@ typedef enum StreamEventType { #define AHL_PROPERTY_EQ_MID "eq_mid" #define AHL_PROPERTY_EQ_HIGH "eq_treble" -// Standardized list of events +// Standardized list of events (not enforced in any way, just helps compatibility) #define AHL_EVENTS_PLAYSOUND "play_sound" #define AHL_EVENTS_ECHOCANCEL_ENABLE "echocancel_enable" #define AHL_EVENTS_ECHOCANCEL_DISABLE "echocancel_disable" diff --git a/src/ahl-policy.c b/src/ahl-policy.c index fa050af..110f7e9 100644 --- a/src/ahl-policy.c +++ b/src/ahl-policy.c @@ -18,8 +18,11 @@ #define _GNU_SOURCE #include <stdio.h> #include <string.h> - +#include <stdbool.h> #include "ahl-binding.h" +#include "wrap-json.h" + +#define MAX_ACTIVE_STREAM_POLICY 30 // This file provides example of custom, business logic driven policy actions that can affect behavior of the high level // TODO: Currently only isolated in separate source file. Objective is to make this to at least a shared lib plug-in (shared C context) @@ -27,65 +30,895 @@ extern AHLCtxT g_AHLCtx; // TODO: Cannot stay if moved to external module -static void Add_Endpoint_Property_Numeric( EndpointInfoT * io_pEndpointInfo, char * in_pPropertyName, int in_iPropertyValue) +typedef struct StreamPolicyInfo { + int RolePriority; + int iVolume; + int iVolumeSavedMute; + streamID_t streamID; + InterruptedBehaviorT interruptBehavior; +} StreamPolicyInfoT; + + +typedef struct EndPointPolicyInfo { + int endpointKey; + endpointID_t endpointID; + EndpointTypeT type; + GArray * streamInfo; //List of playing or duck stream at a given endpoint +} EndPointPolicyInfoT; + +typedef enum SystemState { + SYSTEM_STARTUP = 0, // Startup + SYSTEM_SHUTDOWN, // ShutDown + SYSTEM_NORMAL, // Normal + SYSTEM_LOW_POWER, // Low Power, save mode + SYSTEM_MAXVALUE // Enum count, keep at the end +} SystemStateT; + + +// Global Policy Local context +typedef struct PolicyLocalCtx { + GArray * pSourceEndpoints; // List of Source Endpoint with playing stream or interrupted stream + GArray * pSinkEndpoints; // List of Sink Endpoint with playing stream or interrupted stream + GArray * pStreamOpenPerPolicy; //List of number of openstream per policy + GArray * pMaxStreamOpenPerPolicy; //List of number of openstream per policy + GArray * pVolDuckPerPolicy; //List of number of openstream per policy + SystemStateT systemState; +} PolicyLocalCtxT; + +PolicyLocalCtxT g_PolicyCtx; + + +//Helper Functions +static void Add_Endpoint_Property_Double( EndpointInfoT * io_pEndpointInfo, char * in_pPropertyName, double in_dPropertyValue) +{ + json_object * propValueJ = json_object_new_double(in_dPropertyValue); + g_hash_table_insert(io_pEndpointInfo->pPropTable, in_pPropertyName, propValueJ); +} + + +static void Add_Endpoint_Property_Int( EndpointInfoT * io_pEndpointInfo, char * in_pPropertyName, int in_iPropertyValue) { json_object * propValueJ = json_object_new_int(in_iPropertyValue); g_hash_table_insert(io_pEndpointInfo->pPropTable, in_pPropertyName, propValueJ); } -static void Add_Endpoint_Property_String( EndpointInfoT * io_pEndpointInfo, char * in_pPropertyName, char * in_pPropertyValue) +static void Add_Endpoint_Property_String( EndpointInfoT * io_pEndpointInfo, char * in_pPropertyName, const char * in_pPropertyValue) { json_object * propValueJ = json_object_new_string(in_pPropertyValue); g_hash_table_insert(io_pEndpointInfo->pPropTable, in_pPropertyName, propValueJ); } -int Policy_OpenStream(char *pAudioRole, EndpointTypeT endpointType, endpointID_t endpointID) +static int PolicySetVolume(EndpointInfoT * pEndpointInfo, int iVolume) +{ + + // Using audio role available from endpoint to target the right HAL control (build string based on convention) + GString * gsHALControlName; + switch(pEndpointInfo->deviceURIType) + { + case DEVICEURITYPE_ALSA_HW: + gsHALControlName = g_string_new("Master_Playback_Volume"); + break; + case DEVICEURITYPE_ALSA_DMIX: + case DEVICEURITYPE_ALSA_DSNOOP: + case DEVICEURITYPE_ALSA_PLUG: + case DEVICEURITYPE_ALSA_SOFTVOL: + gsHALControlName = g_string_new(pEndpointInfo->gsAudioRole->str); + g_string_append(gsHALControlName,"_Vol"); // Or _Vol for direct control (no ramping) + break; + default: + //Set volume to zero for display purpose only. + //Not support yet + AFB_WARNING("Endpoint %s is not support Device Type and can't set volume",pEndpointInfo->gsDeviceName->str); + break; + } + + // Set endpoint volume using HAL services (leveraging ramps etc.) + json_object *j_response, *j_query = NULL; + + // Package query + int err = wrap_json_pack(&j_query,"{s:s,s:i}","label",gsHALControlName->str, "val",iVolume); + if (err) + { + AFB_ERROR("Invalid query for HAL ctlset: %s",json_object_to_json_string(j_query)); + return err; + } + + //TODO implement Volume limitation based on policy + + // Set the volume using the HAL + err = afb_service_call_sync(pEndpointInfo->gsHALAPIName->str, "ctlset", j_query, &j_response); + if (err) + { + AFB_ERROR("Could not ctlset on HAL: %s",pEndpointInfo->gsHALAPIName->str); + return err; + } + AFB_DEBUG("HAL ctlset response=%s", json_object_to_json_string(j_response)); + + // Package event data + json_object * eventDataJ = NULL; + err = wrap_json_pack(&eventDataJ,"{s:i,s:i,s:i}","endpoint_id", pEndpointInfo->endpointID,"endpoint_type",pEndpointInfo->type,"value",iVolume); + if (err) + { + AFB_ERROR("Invalid event data for volume event, Invalid event data for volume event: %s",json_object_to_json_string(eventDataJ)); + return err; + } + afb_event_push(g_AHLCtx.policyCtx.volumeEvent,eventDataJ); + + pEndpointInfo->iVolume = iVolume; + + return 0; +} + + +static int PolicySetVolumeMute(EndpointInfoT * pEndpointInfo, int iVolume) +{ + + // Using audio role available from endpoint to target the right HAL control (build string based on convention) + GString * gsHALControlName; + switch(pEndpointInfo->deviceURIType) + { + case DEVICEURITYPE_ALSA_HW: + gsHALControlName = g_string_new("Master_Playback_Volume"); + break; + case DEVICEURITYPE_ALSA_DMIX: + case DEVICEURITYPE_ALSA_DSNOOP: + case DEVICEURITYPE_ALSA_PLUG: + case DEVICEURITYPE_ALSA_SOFTVOL: + gsHALControlName = g_string_new(pEndpointInfo->gsAudioRole->str); + g_string_append(gsHALControlName,"_Vol"); // Or _Vol for direct control (no ramping) + break; + default: + //Set volume to zero for display purpose only. + //Not support yet + AFB_WARNING("Endpoint %s is not support Device Type and can't set volume",pEndpointInfo->gsDeviceName->str); + break; + } + + // Set endpoint volume using HAL services (leveraging ramps etc.) + json_object *j_response, *j_query = NULL; + + // Package query + int err = wrap_json_pack(&j_query,"{s:s,s:i}","label",gsHALControlName->str, "val",iVolume); + if (err) + { + AFB_ERROR("Invalid query for HAL ctlset: %s",json_object_to_json_string(j_query)); + return err; + } + + //TODO implement Volume limitation based on policy + + // Set the volume using the HAL + err = afb_service_call_sync(pEndpointInfo->gsHALAPIName->str, "ctlset", j_query, &j_response); + if (err) + { + AFB_ERROR("Could not ctlset on HAL: %s",pEndpointInfo->gsHALAPIName->str); + return err; + } + AFB_DEBUG("HAL ctlset response=%s", json_object_to_json_string(j_response)); + + // Package event data + json_object * eventDataJ = NULL; + err = wrap_json_pack(&eventDataJ,"{s:i,s:i,s:i}","endpoint_id", pEndpointInfo->endpointID,"endpoint_type",pEndpointInfo->type,"value",iVolume); + if (err) + { + AFB_ERROR("Invalid event data for volume event, Invalid event data for volume event: %s",json_object_to_json_string(eventDataJ)); + return err; + } + afb_event_push(g_AHLCtx.policyCtx.volumeEvent,eventDataJ); + + return 0; +} + + +static int PolicySetVolumeRamp(EndpointInfoT * pEndpointInfo, int iVolume) { - // TODO: Example rule -> when system is in shutdown or low power mode, no audio stream can be opened (return AHL_POLICY_REJECT) + + // Using audio role available from endpoint to target the right HAL control (build string based on convention) + GString * gsHALControlName; + switch(pEndpointInfo->deviceURIType) + { + case DEVICEURITYPE_ALSA_HW: + gsHALControlName = g_string_new("Master_Ramp"); + break; + case DEVICEURITYPE_ALSA_DMIX: + case DEVICEURITYPE_ALSA_DSNOOP: + case DEVICEURITYPE_ALSA_PLUG: + case DEVICEURITYPE_ALSA_SOFTVOL: + gsHALControlName = g_string_new(pEndpointInfo->gsAudioRole->str); + g_string_append(gsHALControlName,"_Ramp"); // Or _Vol for direct control (no ramping) + break; + default: + //Set volume to zero for display purpose only. + //Not support yet + AFB_WARNING("Endpoint %s is not a support Device Type and can't set volume",pEndpointInfo->gsDeviceName->str); + break; + } + + // Set endpoint volume using HAL services (leveraging ramps etc.) + json_object *j_response, *j_query = NULL; + + // Package query + int err = wrap_json_pack(&j_query,"{s:s,s:i}","label",gsHALControlName->str, "val",iVolume); + if (err) + { + AFB_WARNING("Invalid query for HAL ctlset: %s",json_object_to_json_string(j_query)); + return err; + } + + //TODO implement Volume limitation based on policy + + // Set the volume using the HAL + err = afb_service_call_sync(pEndpointInfo->gsHALAPIName->str, "ctlset", j_query, &j_response); + if (err) + { + AFB_WARNING("Could not ctlset on HAL: %s",pEndpointInfo->gsHALAPIName->str); + return err; + } + AFB_DEBUG("HAL ctlset response=%s", json_object_to_json_string(j_response)); + + // Package event data + json_object * eventDataJ = NULL; + err = wrap_json_pack(&eventDataJ,"{s:i,s:i,s:i, s:s}","endpoint_id", pEndpointInfo->endpointID,"endpoint_type",pEndpointInfo->type,"value",iVolume, "audio_role",gsHALControlName->str); + if (err) + { + AFB_WARNING("Invalid event data for volume event, Invalid event data for volume event: %s",json_object_to_json_string(eventDataJ)); + return err; + } + afb_event_push(g_AHLCtx.policyCtx.volumeEvent,eventDataJ); + + pEndpointInfo->iVolume = iVolume; + + return 0; +} + +static int PolicyGetVolume(EndpointInfoT * pEndpointInfo) +{ + + + GString * gsHALControlName; + + // Using audio role available from endpoint to target the right HAL control (build string based on convention) + switch(pEndpointInfo->deviceURIType) + { + case DEVICEURITYPE_ALSA_HW: + gsHALControlName = g_string_new("Master_Playback_Volume"); + break; + case DEVICEURITYPE_ALSA_DMIX: + case DEVICEURITYPE_ALSA_DSNOOP: + case DEVICEURITYPE_ALSA_PLUG: + case DEVICEURITYPE_ALSA_SOFTVOL: + gsHALControlName = g_string_new(pEndpointInfo->gsAudioRole->str); + g_string_append(gsHALControlName,"_Vol"); // Or _Vol for direct control (no ramping) + break; + default: + //Set volume to zero for display purpose only. + //Not support yet + pEndpointInfo->iVolume = 0; + AFB_WARNING("Endpoint %s is a support Device Type and can't get volume",pEndpointInfo->gsDeviceName->str); + break; + } + + // Set endpoint volume using HAL services (leveraging ramps etc.) + json_object *j_response, *j_query = NULL; + + // Package query + int err = wrap_json_pack(&j_query,"{s:s}","label",gsHALControlName->str); + if (err) + { + AFB_WARNING("Invalid query for HAL ctlset: %s",json_object_to_json_string(j_query)); + return err; + } + + //TODO implement Volume limitation based on policy + + // Set the volume using the HAL + err = afb_service_call_sync(pEndpointInfo->gsHALAPIName->str, "ctlget", j_query, &j_response); + if (err) + { + AFB_WARNING("Could not ctlset on HAL: %s",pEndpointInfo->gsHALAPIName->str); + return err; + } + AFB_DEBUG("HAL ctlget response=%s", json_object_to_json_string(j_response)); + + // Parse response + json_object * jRespObj = NULL; + json_object_object_get_ex(j_response, "response", &jRespObj); + json_object * jVal = NULL; + int val1 = 0, val2 = 0; // Why 2 values? + + json_object_object_get_ex(jRespObj, "val", &jVal); + int nbElement = json_object_array_length(jVal); + if(nbElement == 2) + { + err = wrap_json_unpack(jVal, "[ii]", &val1, &val2); + if (err) { + AFB_ERROR("Volume retrieve failed Could not retrieve volume value -> %s", json_object_get_string(jVal)); + return -1; + } + + } + else + { + err = wrap_json_unpack(jVal, "[i]", &val1); + if (err) { + AFB_ERROR("Volume retrieve failed Could not retrieve volume value -> %s", json_object_get_string(jVal)); + return -1; + } + + } + + pEndpointInfo->iVolume = val1; + + return 0; +} + +static void PolicyPostStateEvent(struct afb_event streamStateEvent, StreamEventT eventState) +{ + + json_object * eventDataJ = NULL; + int err = wrap_json_pack(&eventDataJ,"{s:i}","stateEvent",eventState); + if (err) + { + AFB_ERROR("Invalid event data for stream state event: %s",json_object_to_json_string(eventDataJ)); + } + else + { + afb_event_push(streamStateEvent,eventDataJ); + } +} + +//This function is based on ALSA right now but it could be adapt to support other framework like pulseaudio or gstreamer +static int PolicyGenEndPointKey(EndpointInfoT *pEndPointInfo) +{ + return (pEndPointInfo->type << 24)|(pEndPointInfo->alsaInfo.cardNum << 16)|(pEndPointInfo->alsaInfo.deviceNum << 8)|(pEndPointInfo->alsaInfo.subDeviceNum); +} + +static EndPointPolicyInfoT *PolicySearchEndPoint(EndpointTypeT type, int key) +{ + GArray *pcurEndpointArray = NULL; + + if(type==ENDPOINTTYPE_SINK) + { + pcurEndpointArray = g_PolicyCtx.pSinkEndpoints; + } + else + { + pcurEndpointArray = g_PolicyCtx.pSourceEndpoints; + } + + for(int i=0; i<pcurEndpointArray->len; i++) + { + EndPointPolicyInfoT * pCurEndpoint = &g_array_index(pcurEndpointArray,EndPointPolicyInfoT,i); + + if(pCurEndpoint->endpointKey == key) + { + return pCurEndpoint; + } + } + + return NULL; +} + +static StreamPolicyInfoT *PolicySearchStream(EndPointPolicyInfoT * pCurEndpoint, int streamID) +{ + + for(int i=0; i<pCurEndpoint->streamInfo->len; i++) + { + StreamPolicyInfoT * pCurStream = &g_array_index(pCurEndpoint->streamInfo,StreamPolicyInfoT,i); + + if(pCurStream->streamID == streamID) + { + return pCurStream; + } + } + + return NULL; +} + + +static StreamInfoT * PolicyGetActiveStream(streamID_t in_streamID) +{ + int iNumActiveStreams = g_AHLCtx.policyCtx.pActiveStreams->len; + StreamInfoT * pStreamInfo = NULL; + for ( int i = 0; i < iNumActiveStreams ; i++ ) { + StreamInfoT * pCurStreamInfo = &g_array_index(g_AHLCtx.policyCtx.pActiveStreams,StreamInfoT,i); + if (pCurStreamInfo->streamID == in_streamID){ + pStreamInfo = pCurStreamInfo; + break; + } + } + return pStreamInfo; +} + + +static int PolicyFindRoleIndex( const char * in_pAudioRole) +{ + int index = -1; // Not found + for (int i = 0; i < g_AHLCtx.policyCtx.iNumberRoles; i++) + { + GString gs = g_array_index( g_AHLCtx.policyCtx.pAudioRoles, GString, i ); + if ( strcasecmp(gs.str,in_pAudioRole) == 0 ) + { + index = i; + break; + } + } + return index; +} + +static int PolicyRemoveStream(EndPointPolicyInfoT *pCurrEndPointPolicy, int RemoveIndex) +{ + //Validate + if(RemoveIndex >= pCurrEndPointPolicy->streamInfo->len) + { + return -1; + + } + + + g_array_remove_index(pCurrEndPointPolicy->streamInfo,RemoveIndex); + + if(pCurrEndPointPolicy->streamInfo->len == 0) + { + + //Free streem + g_array_free(pCurrEndPointPolicy->streamInfo,TRUE); + pCurrEndPointPolicy->streamInfo = NULL; + + GArray *pcurEndpointArray = NULL; + + if(pCurrEndPointPolicy->type==ENDPOINTTYPE_SINK) + { + pcurEndpointArray = g_PolicyCtx.pSinkEndpoints; + } + else + { + pcurEndpointArray = g_PolicyCtx.pSourceEndpoints; + } + + for(int i=0; i<pcurEndpointArray->len; i++) + { + EndPointPolicyInfoT * pCurEndpoint = &g_array_index(pcurEndpointArray,EndPointPolicyInfoT,i); + if(pCurEndpoint->endpointKey == pCurrEndPointPolicy->endpointKey) + { + g_array_remove_index(pcurEndpointArray, i); + return 0; + } + } + } + + return 0; +} + +static int PolicyRunningIdleTransition(EndPointPolicyInfoT *pCurrEndPointPolicy,StreamInfoT * pStreamInfo) +{ + if(pCurrEndPointPolicy == NULL) + { + //Remove endpoint + AFB_ERROR("No Active Endpoint has been found"); + return -1; + } + + if(pCurrEndPointPolicy->streamInfo->len>0) + { + //Search for the matching stream + int iNumStream = pCurrEndPointPolicy->streamInfo->len; + for(int i=0; i<iNumStream; i++) + { + StreamPolicyInfoT currentPolicyStreamInfo = g_array_index(pCurrEndPointPolicy->streamInfo,StreamPolicyInfoT,i); + if(currentPolicyStreamInfo.streamID == pStreamInfo->streamID) + { + + //Unduck case + if((i==(pCurrEndPointPolicy->streamInfo->len-1)) && (pCurrEndPointPolicy->streamInfo->len > 1)) + { + //remove the current stream + g_array_remove_index(pCurrEndPointPolicy->streamInfo, i); + + //check the last element(Akways highest priority) + StreamPolicyInfoT HighPriorityStreamInfo = g_array_index(pCurrEndPointPolicy->streamInfo,StreamPolicyInfoT,pCurrEndPointPolicy->streamInfo->len-1); + + //Get Stream Info + StreamInfoT * pInterruptStreamInfo = PolicyGetActiveStream(HighPriorityStreamInfo.streamID); + if (pInterruptStreamInfo == NULL) { + AFB_ERROR("Stream not found, Specified stream not currently active stream_id -> %d",HighPriorityStreamInfo.streamID); + return -1; + } + + int err; + switch(currentPolicyStreamInfo.interruptBehavior) + { + case AHL_INTERRUPTEDBEHAVIOR_CONTINUE: + //unduck and set Volume back to original value + err= PolicySetVolumeRamp(pInterruptStreamInfo->pEndpointInfo, HighPriorityStreamInfo.iVolume); + if(err) + { + AFB_ERROR("Endpoint:%s Set Volume return with errorcode%i", pInterruptStreamInfo->pEndpointInfo->gsDeviceName->str, err); + return -1; + } + break; + case AHL_INTERRUPTEDBEHAVIOR_PAUSE: + pInterruptStreamInfo->streamState = STREAM_STATE_RUNNING; + PolicyPostStateEvent(pInterruptStreamInfo->streamStateEvent,STREAM_EVENT_RESUME); + break; + + case AHL_INTERRUPTEDBEHAVIOR_CANCEL: + AFB_ERROR("StreamID with Cancel InterruptedBehavior can't be unInterrupted"); + return -1; + break; + default: + AFB_ERROR("Unsupported Intterupt Behavior"); + return -1; + break; + } + + } + else + { + //remove the current stream + PolicyRemoveStream(pCurrEndPointPolicy, i); + } + return 0; + } + + } + } + + AFB_ERROR("StreamID does not match any playing stream"); + return -1; +} + +static int PolicyIdleRunningTransition(EndPointPolicyInfoT *pCurrEndPointPolicy, StreamInfoT * pStreamInfo, int AudioRoleIndex, int EndPointKey) +{ + int stream_priority; + int ori_key_pos; + + bool bKeyFound=g_hash_table_lookup_extended(g_AHLCtx.policyCtx.pRolePriority,pStreamInfo->pEndpointInfo->gsAudioRole->str,&ori_key_pos,&stream_priority); + if(bKeyFound==false) + { + AFB_ERROR("Can't find stream priority, request will be rejected"); + return -1; + } + + //stream_priority = GPOINTER_TO_INT(value); + + + InterruptedBehaviorT InterruptBehavior = g_array_index(g_AHLCtx.policyCtx.pInterruptBehavior,InterruptedBehaviorT,AudioRoleIndex); + int err; + if(pCurrEndPointPolicy == NULL) //No stream is playing on this endpoint + { + EndPointPolicyInfoT newEndPointPolicyInfo ; + StreamPolicyInfoT newStreamPolicyInfo; + + //create EndPoint and add playing stream + newEndPointPolicyInfo.endpointKey = EndPointKey; + newEndPointPolicyInfo.endpointID = pStreamInfo->pEndpointInfo->endpointID; + newEndPointPolicyInfo.type = pStreamInfo->pEndpointInfo->type; + newEndPointPolicyInfo.streamInfo = g_array_new(FALSE,TRUE,sizeof(StreamPolicyInfoT)); + + newStreamPolicyInfo.RolePriority = stream_priority; + newStreamPolicyInfo.iVolume = pStreamInfo->pEndpointInfo->iVolume; + newStreamPolicyInfo.streamID = pStreamInfo->streamID; + newStreamPolicyInfo.interruptBehavior = InterruptBehavior; + + g_array_append_val(newEndPointPolicyInfo.streamInfo, newStreamPolicyInfo); + g_array_append_val(g_PolicyCtx.pSinkEndpoints, newEndPointPolicyInfo); + + + /* + int *pVolume = &g_array_index(g_PolicyCtx.pVolInitPerPolicy,int,AudioRoleIndex); + pStreamInfo->pEndpointInfo->iVolume = *pVolume; + + err= PolicySetVolumeRamp(pStreamInfo->pEndpointInfo, pStreamInfo->pEndpointInfo->iVolume); + if(err) + { + AFB_ERROR("Endpoint:%s Set Volume return with errorcode%i",pStreamInfo->pEndpointInfo->gsDeviceName->str, err); + return err; + } */ + } + else + { + //Currently contains playing or duck stream + if(pCurrEndPointPolicy->streamInfo->len > 0) + { + //check the last element + StreamPolicyInfoT HighPriorityStreamInfo = g_array_index(pCurrEndPointPolicy->streamInfo,StreamPolicyInfoT,pCurrEndPointPolicy->streamInfo->len-1); + if((stream_priority) >= HighPriorityStreamInfo.RolePriority) + { + //Get Stream Info + StreamInfoT * pInterruptStreamInfo = PolicyGetActiveStream(HighPriorityStreamInfo.streamID); + if (pInterruptStreamInfo == NULL) { + AFB_ERROR("Stream not found Specified stream not currently active stream_id -> %d",HighPriorityStreamInfo.streamID); + return -1; + } + + switch(InterruptBehavior) + { + case AHL_INTERRUPTEDBEHAVIOR_CONTINUE: + //Save the current Volume and set the docking volume + HighPriorityStreamInfo.iVolume = pInterruptStreamInfo->pEndpointInfo->iVolume; + + int *pVolume = &g_array_index(g_PolicyCtx.pVolDuckPerPolicy,int,AudioRoleIndex); + err= PolicySetVolumeRamp(pInterruptStreamInfo->pEndpointInfo, *pVolume); + if(err) + { + AFB_ERROR("Endpoint:%s Set Volume return with errorcode%i", pInterruptStreamInfo->pEndpointInfo->gsDeviceName->str, err); + return -1; + } + break; + case AHL_INTERRUPTEDBEHAVIOR_PAUSE: + pInterruptStreamInfo->streamState = STREAM_STATE_PAUSED; + PolicyPostStateEvent(pInterruptStreamInfo->streamStateEvent,STREAM_EVENT_PAUSE); + break; + + case AHL_INTERRUPTEDBEHAVIOR_CANCEL: + pInterruptStreamInfo->streamState = STREAM_STATE_IDLE; + PolicyPostStateEvent(pInterruptStreamInfo->streamStateEvent,STREAM_EVENT_STOP); + g_array_remove_index(pCurrEndPointPolicy->streamInfo, pCurrEndPointPolicy->streamInfo->len-1); + + break; + default: + AFB_ERROR("Unsupported Intterupt Behavior"); + return AHL_POLICY_REJECT; + break; + + } + + //Add the playing stream at index 0 + StreamPolicyInfoT newStreamPolicyInfo; + newStreamPolicyInfo.RolePriority = stream_priority; + newStreamPolicyInfo.iVolume = pStreamInfo->pEndpointInfo->iVolume; + newStreamPolicyInfo.streamID = pStreamInfo->streamID; + newStreamPolicyInfo.interruptBehavior = InterruptBehavior; + + //Insert at the end, become highest priority streamID + g_array_append_val(pCurrEndPointPolicy->streamInfo, newStreamPolicyInfo); + + err= PolicySetVolumeRamp(pStreamInfo->pEndpointInfo, pStreamInfo->pEndpointInfo->iVolume); + if(err) + { + AFB_ERROR("Endpoint:%s Set Volume return with errorcode%i", pInterruptStreamInfo->pEndpointInfo->gsDeviceName->str, err); + return err; + } + + } + else + { + //Higher Priority Stream is playing + AFB_NOTICE("Higher Priority Stream is playing"); + return -1; + } + + } + else + { + //Remove endpoint + AFB_ERROR("Active EndPoint is not attached to any active stream"); + return -1; + + } + } + + return 0; +} + +//Policy API +int Policy_OpenStream(StreamInfoT * pStreamInfo) +{ + // Example rule -> when system is in shutdown or low power mode, no audio stream can be opened (return AHL_POLICY_REJECT) + // Should receive event from lower level layer + if(g_PolicyCtx.systemState != SYSTEM_NORMAL) + { + return AHL_POLICY_REJECT; + } + + //Implement Policy open stream rules, limit to a certain number of stream open based on policy + int index = PolicyFindRoleIndex(pStreamInfo->pEndpointInfo->gsAudioRole->str); + int *pNumberOpenStream = &g_array_index(g_PolicyCtx.pStreamOpenPerPolicy,int,index); + int MaxNumberOpenStream = g_array_index(g_PolicyCtx.pMaxStreamOpenPerPolicy,int,index); + + *pNumberOpenStream +=1; + if((*pNumberOpenStream) > MaxNumberOpenStream ) + { + return AHL_POLICY_REJECT; + } + + //Get actual Volume + int err=PolicyGetVolume(pStreamInfo->pEndpointInfo); + if(err != 0) + { + AFB_WARNING("Can't get volume of Endpoint %s",pStreamInfo->pEndpointInfo->gsDeviceName->str); + } return AHL_POLICY_ACCEPT; } -int Policy_CloseStream(streamID_t streamID) +int Policy_CloseStream(StreamInfoT * pStreamInfo) { - // For completeness, unlikely to have any policy rules here + //Decrement the number of openstream + int index = PolicyFindRoleIndex(pStreamInfo->pEndpointInfo->gsAudioRole->str); + int *pNumberOpenStream = &g_array_index(g_PolicyCtx.pStreamOpenPerPolicy,int,index); + + *pNumberOpenStream -= 1; return AHL_POLICY_ACCEPT; } -int Policy_SetStreamState(streamID_t streamID, StreamStateT streamState ) +int Policy_SetStreamState(StreamInfoT * pStreamInfo, int AudioRoleIndex, StreamStateT streamState) { + //DONE // If higher priority audio role stream requires audio ducking (and un-ducking) of other streams (e.g. navigation ducks entertainment) - // TODO: Could potentially provide a fairly generic system using interupt behavior information and audio role priority (implemented and can be customized here) + // Could potentially provide a fairly generic system using interupt behavior information and audio role priority (implemented and can be customized here) // Specific exception can also be - // Source exclusion. E.g. When a source (e.g tuner) with same audio role as already active stream (e.g. media player) with same endpoint target, // the former source is stopped (i.e. raise streamstate stop event) - - // If source on communication role is active (e.g. handsfree call), activating entertainment sources is prohibited - - // If handsfree or speech recognition (communication role) is started during entertainment playback, mute all entertainment streams (any endpoints except RSE) - + // If source on communication role is active (e.g. handsfree call), activating entertainment sources is prohibited // Startup or Shutdown audio stream mute entertainment (unmut when stream no longer active) + //TODO // Optional: Mute endpoint before activation, unmute afterwards (after a delay?) to avoid noises + int err; - + //Change of state + if(pStreamInfo->streamState != streamState) + { + //seach corresponding endpoint and gather information on it + int key = PolicyGenEndPointKey(pStreamInfo->pEndpointInfo); + EndPointPolicyInfoT *pCurrEndPointPolicy = PolicySearchEndPoint(pStreamInfo->pEndpointInfo->type , key); + + switch(pStreamInfo->streamState) + { + case STREAM_STATE_IDLE: + switch(streamState) + { + case STREAM_STATE_RUNNING: + err = PolicyIdleRunningTransition(pCurrEndPointPolicy, pStreamInfo, AudioRoleIndex, key); + if(err) + { + return AHL_POLICY_REJECT; + } + pStreamInfo->streamState = STREAM_STATE_RUNNING; + PolicyPostStateEvent(pStreamInfo->streamStateEvent,STREAM_EVENT_START); + break; + case STREAM_STATE_PAUSED: + err = PolicyIdleRunningTransition(pCurrEndPointPolicy, pStreamInfo, AudioRoleIndex, key); + if(err) + { + return AHL_POLICY_REJECT; + } + pStreamInfo->streamState = STREAM_STATE_PAUSED; + PolicyPostStateEvent(pStreamInfo->streamStateEvent,STREAM_EVENT_PAUSE); + break; + default: + return AHL_POLICY_REJECT; + break; + } + break; + case STREAM_STATE_RUNNING: + switch(streamState) + { + case STREAM_STATE_IDLE: + err = PolicyRunningIdleTransition(pCurrEndPointPolicy, pStreamInfo); + if(err) + { + return AHL_POLICY_REJECT; + } + pStreamInfo->streamState = STREAM_STATE_IDLE; + PolicyPostStateEvent(pStreamInfo->streamStateEvent,STREAM_EVENT_STOP); + break; + case STREAM_STATE_PAUSED: + pStreamInfo->streamState = STREAM_STATE_PAUSED; + PolicyPostStateEvent(pStreamInfo->streamStateEvent,STREAM_EVENT_PAUSE); + break; + default: + return AHL_POLICY_REJECT; + break; + } + break; + case STREAM_STATE_PAUSED: + switch(streamState) + { + case STREAM_STATE_IDLE: + err = PolicyRunningIdleTransition(pCurrEndPointPolicy, pStreamInfo); + if(err) + { + return AHL_POLICY_REJECT; + } + pStreamInfo->streamState = STREAM_STATE_IDLE; + PolicyPostStateEvent(pStreamInfo->streamStateEvent,STREAM_EVENT_STOP); + break; + case STREAM_STATE_RUNNING: + pStreamInfo->streamState = STREAM_STATE_RUNNING; + PolicyPostStateEvent(pStreamInfo->streamStateEvent,STREAM_EVENT_RESUME); + break; + default: + return AHL_POLICY_REJECT; + break; + } + break; + default: + return AHL_POLICY_REJECT; + break; + } + } return AHL_POLICY_ACCEPT; } -int Policy_SetStreamMute(streamID_t streamID, StreamMuteT streamMute) +int Policy_SetStreamMute(StreamInfoT * pStreamInfo, StreamMuteT streamMute) { + int err; + + if(streamMute == STREAM_MUTED) + { + err= PolicySetVolumeMute(pStreamInfo->pEndpointInfo, 0); + if(err) + { + AFB_ERROR("Endpoint:%s Set Volume return with errorcode%i",pStreamInfo->pEndpointInfo->gsDeviceName->str, err); + return AHL_POLICY_REJECT; + } + PolicyPostStateEvent(pStreamInfo->streamStateEvent,STREAM_EVENT_MUTED); + } + else + { + err= PolicySetVolumeMute(pStreamInfo->pEndpointInfo, pStreamInfo->pEndpointInfo->iVolume); + if(err) + { + AFB_ERROR("Endpoint:%s Set Volume return with errorcode%i",pStreamInfo->pEndpointInfo->gsDeviceName->str, err); + return AHL_POLICY_REJECT; + } + PolicyPostStateEvent(pStreamInfo->streamStateEvent,STREAM_EVENT_UNMUTED); + + + } + + pStreamInfo->streamMute = streamMute; + return AHL_POLICY_ACCEPT; } -int Policy_SetVolume(EndpointTypeT endpointType, endpointID_t endpointID, char *volumeStr) +int Policy_SetVolume(EndpointInfoT * f_pEndpointInfo, char *volumeStr) { + + // TODO: Parse volume string to support increment/absolute/percent notation (or delegate to action / policy layer to interpret) + int vol = atoi(volumeStr); + + //Set the volume + int err = PolicySetVolumeRamp(f_pEndpointInfo, vol); + if (err) + { + AFB_ERROR("Set Volume return with errorcode%i", err); + return AHL_POLICY_REJECT; + } + return AHL_POLICY_ACCEPT; } -int Policy_SetProperty(EndpointTypeT endpointType, endpointID_t endpointID, char *propertyName, char *propValueStr) +int Policy_SetProperty(EndpointInfoT * f_pEndpointInfo, char *propertyName, json_object *propValue) { + + gpointer *key_value=NULL; + key_value=g_hash_table_lookup(f_pEndpointInfo->pPropTable,propertyName); + if(key_value==NULL) + { + AFB_ERROR("Can't find property %s, request will be rejected", propertyName); + return AHL_POLICY_REJECT; + } + + // Object type detection for property value (string = state, numeric = property) + json_type jType = json_object_get_type(propValue); + switch (jType) { + case json_type_double: + Add_Endpoint_Property_Double(f_pEndpointInfo,propertyName,json_object_get_double(propValue)); + case json_type_int: + Add_Endpoint_Property_Int(f_pEndpointInfo,propertyName,json_object_get_int(propValue)); + case json_type_string: + Add_Endpoint_Property_String(f_pEndpointInfo,propertyName,json_object_get_string(propValue)); + break; + default: + AFB_ERROR("Invalid property argument Property value not a valid json object query=%s", json_object_get_string(propValue )); + return AHL_POLICY_REJECT; + } + return AHL_POLICY_ACCEPT; } @@ -112,16 +945,16 @@ int Policy_AudioDeviceChange() int Policy_Endpoint_Property_Init(EndpointInfoT * io_EndpointInfo) { // Populate list of supported properties for specified endpoint (use helper functions) - // Setup initial values for all properties + // Setup initial values for all properties GHashTabl // TODO: Switch on different known endpoints to populate different properties // Test example - Add_Endpoint_Property_Numeric(io_EndpointInfo,AHL_PROPERTY_EQ_LOW,3); - Add_Endpoint_Property_Numeric(io_EndpointInfo,AHL_PROPERTY_EQ_MID,0); - Add_Endpoint_Property_Numeric(io_EndpointInfo,AHL_PROPERTY_EQ_HIGH,6); - Add_Endpoint_Property_Numeric(io_EndpointInfo,AHL_PROPERTY_BALANCE,0); - Add_Endpoint_Property_Numeric(io_EndpointInfo,AHL_PROPERTY_FADE,30); + Add_Endpoint_Property_Int(io_EndpointInfo,AHL_PROPERTY_EQ_LOW,3); + Add_Endpoint_Property_Int(io_EndpointInfo,AHL_PROPERTY_EQ_MID,0); + Add_Endpoint_Property_Int(io_EndpointInfo,AHL_PROPERTY_EQ_HIGH,6); + Add_Endpoint_Property_Int(io_EndpointInfo,AHL_PROPERTY_BALANCE,0); + Add_Endpoint_Property_Int(io_EndpointInfo,AHL_PROPERTY_FADE,30); Add_Endpoint_Property_String(io_EndpointInfo,"preset_name","flat"); return 0; // No errors @@ -129,11 +962,240 @@ int Policy_Endpoint_Property_Init(EndpointInfoT * io_EndpointInfo) int Policy_Init() { - // Other policy specific initialization + // Initialize Ressources + g_PolicyCtx.pSourceEndpoints =g_array_new(FALSE,TRUE,sizeof(EndPointPolicyInfoT)); + g_PolicyCtx.pSinkEndpoints = g_array_new(FALSE,TRUE,sizeof(EndPointPolicyInfoT)); + g_PolicyCtx.pStreamOpenPerPolicy = g_array_sized_new(FALSE, TRUE, sizeof(int), g_AHLCtx.policyCtx.iNumberRoles); + g_PolicyCtx.pMaxStreamOpenPerPolicy = g_array_sized_new(FALSE, TRUE, sizeof(int), g_AHLCtx.policyCtx.iNumberRoles); + g_PolicyCtx.pVolDuckPerPolicy = g_array_sized_new(FALSE, TRUE, sizeof(int), g_AHLCtx.policyCtx.iNumberRoles); + + int initial_value=0; + int max_value=0; + int vol_init_value=0; + int vol_duck_value=0; + GArray * pRoleDeviceArray = NULL; + //Init the number of open stream + for(int i=0; i<g_AHLCtx.policyCtx.iNumberRoles; i++) + { + g_array_append_val(g_PolicyCtx.pStreamOpenPerPolicy, initial_value); + + GString gs = g_array_index( g_AHLCtx.policyCtx.pAudioRoles, GString, i ); + max_value = MAX_ACTIVE_STREAM_POLICY; + if ( strcasecmp(gs.str,AHL_ROLE_WARNING) == 0 ) + { + max_value = 4; + vol_init_value=80; + vol_duck_value = 0; + } + else if ( strcasecmp(gs.str,AHL_ROLE_GUIDANCE) == 0 ) + { + max_value = 10; + vol_init_value=70; + vol_duck_value = 40; + } + else if ( strcasecmp(gs.str,AHL_ROLE_NOTIFICATION) == 0 ) + { + max_value = 4; + vol_init_value=80; + vol_duck_value = 0; + } + else if ( strcasecmp(gs.str,AHL_ROLE_COMMUNICATION) == 0 ) + { + max_value = 10; + vol_init_value=70; + vol_duck_value = 30; + } + else if ( strcasecmp(gs.str,AHL_ROLE_ENTERTAINMENT) == 0 ) + { + max_value = MAX_ACTIVE_STREAM_POLICY; + vol_init_value=60; + vol_duck_value = 40; + } + else if ( strcasecmp(gs.str,AHL_ROLE_SYSTEM) == 0 ) + { + max_value = 2; + vol_init_value=100; + vol_duck_value = 0; + } + else if ( strcasecmp(gs.str,AHL_ROLE_STARTUP) == 0 ) + { + max_value = 1; + vol_init_value=90; + vol_duck_value = 0; + } + else if ( strcasecmp(gs.str,AHL_ROLE_SHUTDOWN) == 0 ) + { + max_value = 1; + vol_init_value=90; + vol_duck_value = 0; + } + + g_array_append_val(g_PolicyCtx.pMaxStreamOpenPerPolicy, max_value); + g_array_append_val(g_PolicyCtx.pVolDuckPerPolicy, vol_duck_value); + + //Get actual volume value + pRoleDeviceArray = g_ptr_array_index( g_AHLCtx.policyCtx.pSinkEndpoints, i ); + if (pRoleDeviceArray != NULL) + { + for ( int j = 0 ; j < pRoleDeviceArray->len; j++) + { + + //Init all Volume + EndpointInfoT * pEndpointInfo = &g_array_index(pRoleDeviceArray,EndpointInfoT,j); + int err= PolicySetVolumeRamp(pEndpointInfo, vol_init_value); + if(err) + { + AFB_WARNING("Endpoint:%s Set Volume return with errorcode%i",pEndpointInfo->gsDeviceName->str, err); + //try to read volume instead + err=PolicyGetVolume(pEndpointInfo); + if(err != 0) + { + AFB_WARNING("Can't get volume of Endpoint %s",pEndpointInfo->gsDeviceName->str); + } + } + + /* + EndpointInfoT * pEndpointInfo = &g_array_index(pRoleDeviceArray,EndpointInfoT,j); + int err=PolicyGetVolume(pEndpointInfo); + if(err != 0) + { + AFB_WARNING("Can't get volume of Endpoint %s",pEndpointInfo->gsDeviceName->str); + } +*/ + } + } + } + + + //Set System Normal for now, this should be set by an event + //TODO: Receive event from low level + g_PolicyCtx.systemState = SYSTEM_NORMAL; + + //register audio backend events + json_object *queryurl, *responseJ, *devidJ, *eventsJ; + + eventsJ = json_object_new_array(); + json_object_array_add(eventsJ, json_object_new_string("audiod_system_event")); + queryurl = json_object_new_object(); + json_object_object_add(queryurl, "events", eventsJ); + int returnResult = afb_service_call_sync("audiod", "subscribe", queryurl, &responseJ); + if (returnResult) { + AFB_ERROR("Fail subscribing to Audio Backend System events"); + return -1; + } + + return 0; // No errors } - + void Policy_Term() { - // Policy termination + + //Free Ressources + for(int i=0; i<g_PolicyCtx.pSourceEndpoints->len; i++) + { + EndPointPolicyInfoT * pCurEndpoint = &g_array_index(g_PolicyCtx.pSourceEndpoints,EndPointPolicyInfoT,i); + g_array_free(pCurEndpoint->streamInfo,TRUE); + pCurEndpoint->streamInfo= NULL; + } + + for(int i=0; i<g_PolicyCtx.pSinkEndpoints->len; i++) + { + EndPointPolicyInfoT * pCurEndpoint = &g_array_index(g_PolicyCtx.pSinkEndpoints,EndPointPolicyInfoT,i); + g_array_free(pCurEndpoint->streamInfo,TRUE); + pCurEndpoint->streamInfo = NULL; + } + + g_array_free(g_PolicyCtx.pSourceEndpoints,TRUE); + g_PolicyCtx.pSourceEndpoints = NULL; + g_array_free(g_PolicyCtx.pSinkEndpoints,TRUE); + g_PolicyCtx.pSinkEndpoints = NULL; + + g_array_free(g_PolicyCtx.pStreamOpenPerPolicy,TRUE); + g_PolicyCtx.pStreamOpenPerPolicy = NULL; + g_array_free(g_PolicyCtx.pMaxStreamOpenPerPolicy,TRUE); + g_PolicyCtx.pMaxStreamOpenPerPolicy = NULL; + g_array_free(g_PolicyCtx.pVolDuckPerPolicy, TRUE); + g_PolicyCtx.pVolDuckPerPolicy = NULL; } + +static void PolicySpeedModify(int speed) +{ + + for(int i=0; i<g_PolicyCtx.pSinkEndpoints->len; i++) + { + EndPointPolicyInfoT * pCurEndpoint = &g_array_index(g_PolicyCtx.pSinkEndpoints,EndPointPolicyInfoT,i); + if(pCurEndpoint == NULL) + { + AFB_WARNING("Sink Endpoint not found"); + return; + + } + //check if active + if(pCurEndpoint->streamInfo->len > 0 ) + { + StreamPolicyInfoT * pCurStream = &g_array_index(pCurEndpoint->streamInfo,StreamPolicyInfoT,pCurEndpoint->streamInfo->len-1); + + + //Get Stream Info + StreamInfoT * pActiveStreamInfo = PolicyGetActiveStream(pCurStream->streamID); + if (pActiveStreamInfo == NULL) { + AFB_WARNING("Stream not found, Specified stream not currently active stream_id -> %d",pCurStream->streamID); + return; + } + + if(strcasecmp(pActiveStreamInfo->pEndpointInfo->gsAudioRole->str,AHL_ROLE_ENTERTAINMENT)==0) + { + + if(speed > 30 && speed < 100) + { + int volume =speed; + PolicySetVolumeRamp(pActiveStreamInfo->pEndpointInfo,volume); + } + + + } + + } + + } +} + + +void Policy_OnEvent(const char *evtname, json_object *eventJ) +{ + AFB_DEBUG("Policy received event %s", evtname); + + char *eventName = NULL; + json_object *event_parameter = NULL; + int speed = 0; + + + if(strcasecmp(evtname, "audiod/system_events")==0) + { + + int err = wrap_json_unpack(eventJ, "{s:s,s:o}", "event_name", &eventName, "event_parameter", &event_parameter); + if (err) { + AFB_WARNING("Invalid arguments, Args not a valid json object query=%s", json_object_get_string(eventJ)); + return; + } + + if(strcasecmp(eventName, "speed")==0) + { + AFB_NOTICE("Invalid arguments, Args not a valid json object query=%s", json_object_get_string(event_parameter)); + err = wrap_json_unpack(event_parameter, "{s:i}", "speed_value", &speed); + if (err) { + AFB_WARNING("Invalid arguments, Args not a valid json object query=%s", json_object_get_string(event_parameter)); + return; + } + + //When speed change Modify volume on Endpoint where entertainment change + PolicySpeedModify(speed); + } + + } + + + + +}
\ No newline at end of file |