aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTai Vuong <tvuong@audiokinetic.com>2017-10-16 10:09:38 -0400
committerTai Vuong <tvuong@audiokinetic.com>2017-10-16 10:09:38 -0400
commit9b7e1d0361d1a5eee415e453ae79925084552c68 (patch)
tree19ef4489e2fb2932b1ce8106489fd1ee9507303b
parent539f65bb5ad87238422a5ca26c5b524c3be44bd1 (diff)
add policy and reference implementation
-rw-r--r--README.md1
-rw-r--r--htdocs/CMakeLists.txt2
-rw-r--r--htdocs/assets/content-background-car-wide.pngbin0 -> 1215422 bytes
-rw-r--r--htdocs/assets/content-background-car.pngbin0 -> 244208 bytes
-rw-r--r--htdocs/assets/content-background.pngbin0 -> 210766 bytes
-rw-r--r--htdocs/assets/emergency.pngbin0 -> 20039 bytes
-rw-r--r--htdocs/assets/favicon.icobin0 -> 1150 bytes
-rw-r--r--htdocs/assets/iot-bzh-logo-small.pngbin0 -> 14449 bytes
-rw-r--r--htdocs/assets/messages.pngbin0 -> 14597 bytes
-rw-r--r--htdocs/assets/music-pause.pngbin0 -> 100761 bytes
-rw-r--r--htdocs/assets/music-play.pngbin0 -> 110798 bytes
-rw-r--r--htdocs/assets/phone-call-emit1.pngbin0 -> 46021 bytes
-rw-r--r--htdocs/assets/phone-call-emit2.pngbin0 -> 56311 bytes
-rw-r--r--htdocs/assets/phone-call-emit3.pngbin0 -> 70218 bytes
-rw-r--r--htdocs/assets/phone-call.pngbin0 -> 37089 bytes
-rw-r--r--htdocs/audiobackend.html35
-rw-r--r--htdocs/audiohl.html108
-rw-r--r--htdocs/index.html7
-rw-r--r--src/ahl-binding.c400
-rw-r--r--src/ahl-binding.h42
-rw-r--r--src/ahl-config.c34
-rw-r--r--src/ahl-deviceenum.c81
-rw-r--r--src/ahl-interface.h42
-rw-r--r--src/ahl-policy.c1118
24 files changed, 1506 insertions, 364 deletions
diff --git a/README.md b/README.md
index 4c09ecc..046c2e7 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,6 @@
```
# Initial clone with submodules
git clone https://github.com/Audiokinetic-Automotive/afb-audiohighlevel.git
-cd afb-audiohighlevel
```
diff --git a/htdocs/CMakeLists.txt b/htdocs/CMakeLists.txt
index 2e51461..c08a493 100644
--- a/htdocs/CMakeLists.txt
+++ b/htdocs/CMakeLists.txt
@@ -23,7 +23,7 @@
##################################################
PROJECT_TARGET_ADD(htdocs)
- file(GLOB SOURCE_FILES "*.html" "*.js" "*.jpg" "*.css")
+ file(GLOB SOURCE_FILES LIST_DIRECTORIES true "*.html" "*.js" "*.jpg" "*.css")
add_input_files("${SOURCE_FILES}")
diff --git a/htdocs/assets/content-background-car-wide.png b/htdocs/assets/content-background-car-wide.png
new file mode 100644
index 0000000..073d1b9
--- /dev/null
+++ b/htdocs/assets/content-background-car-wide.png
Binary files differ
diff --git a/htdocs/assets/content-background-car.png b/htdocs/assets/content-background-car.png
new file mode 100644
index 0000000..4bcde72
--- /dev/null
+++ b/htdocs/assets/content-background-car.png
Binary files differ
diff --git a/htdocs/assets/content-background.png b/htdocs/assets/content-background.png
new file mode 100644
index 0000000..9be581c
--- /dev/null
+++ b/htdocs/assets/content-background.png
Binary files differ
diff --git a/htdocs/assets/emergency.png b/htdocs/assets/emergency.png
new file mode 100644
index 0000000..c959b04
--- /dev/null
+++ b/htdocs/assets/emergency.png
Binary files differ
diff --git a/htdocs/assets/favicon.ico b/htdocs/assets/favicon.ico
new file mode 100644
index 0000000..eeb7ab7
--- /dev/null
+++ b/htdocs/assets/favicon.ico
Binary files differ
diff --git a/htdocs/assets/iot-bzh-logo-small.png b/htdocs/assets/iot-bzh-logo-small.png
new file mode 100644
index 0000000..2c3b2ae
--- /dev/null
+++ b/htdocs/assets/iot-bzh-logo-small.png
Binary files differ
diff --git a/htdocs/assets/messages.png b/htdocs/assets/messages.png
new file mode 100644
index 0000000..4919452
--- /dev/null
+++ b/htdocs/assets/messages.png
Binary files differ
diff --git a/htdocs/assets/music-pause.png b/htdocs/assets/music-pause.png
new file mode 100644
index 0000000..790a6c4
--- /dev/null
+++ b/htdocs/assets/music-pause.png
Binary files differ
diff --git a/htdocs/assets/music-play.png b/htdocs/assets/music-play.png
new file mode 100644
index 0000000..37c18e3
--- /dev/null
+++ b/htdocs/assets/music-play.png
Binary files differ
diff --git a/htdocs/assets/phone-call-emit1.png b/htdocs/assets/phone-call-emit1.png
new file mode 100644
index 0000000..a8d863b
--- /dev/null
+++ b/htdocs/assets/phone-call-emit1.png
Binary files differ
diff --git a/htdocs/assets/phone-call-emit2.png b/htdocs/assets/phone-call-emit2.png
new file mode 100644
index 0000000..14c8cbe
--- /dev/null
+++ b/htdocs/assets/phone-call-emit2.png
Binary files differ
diff --git a/htdocs/assets/phone-call-emit3.png b/htdocs/assets/phone-call-emit3.png
new file mode 100644
index 0000000..5283a40
--- /dev/null
+++ b/htdocs/assets/phone-call-emit3.png
Binary files differ
diff --git a/htdocs/assets/phone-call.png b/htdocs/assets/phone-call.png
new file mode 100644
index 0000000..40007d2
--- /dev/null
+++ b/htdocs/assets/phone-call.png
Binary files differ
diff --git a/htdocs/audiobackend.html b/htdocs/audiobackend.html
new file mode 100644
index 0000000..a7c8f41
--- /dev/null
+++ b/htdocs/audiobackend.html
@@ -0,0 +1,35 @@
+<html>
+<head>
+ <link rel="stylesheet" href="AudioBinding.css">
+ <title>Audio High Level Test</title>
+
+ <script type="text/javascript" src="AFB-websock.js"></script>
+ <script type="text/javascript" src="AudioBinding.js"></script>
+</head>
+
+<body onload="init('audiod')">
+
+ <button id="connected" onclick="init('audiod');">Binder WS Fail</button> <br><br>
+ <br>
+ <ol>
+ <li><button onclick="callbinder('audiod','play', {device_name:'plug:Entertainment_Main', filepath:'style.wav', loop:0})">play('hw:0', 'style.wav')</button></li>
+ <li><button onclick="callbinder('audiod','play', {device_name:'hw:0', filepath:'application.wav', loop:1})">playloop('hw:0', 'application.wav')</button></li>
+ <li><button onclick="callbinder('audiod','play', {device_name:'hw:0', filepath:'application.wav', loop:0})">play('hw:0', 'application.wav')</button></li>
+ <li><button onclick="callbinder('audiod','stop', {audio_role:4})">stop()</button></li>
+ <li><button onclick="callbinder('audiod','pause', {audio_role:4})">pause()</button></li>
+ <li><button onclick="callbinder('audiod','subscribe', {audio_role:4})">subscribe()</button></li>
+ <li><button onclick="callbinder('audiod','unsubscribe', {audio_role:4})">unsubscribe()</button></li>
+ <li><button onclick="callbinder('audiod','post_event', {event_name:'speed', event_parameter:{speed:10}})">post_event('event_name:speed, {speed:10}')</button></li>
+
+ </li>
+
+ <br>
+ </ol>
+
+ <div id="main" style="visibility:hidden">
+ <ol>
+ <li>Question <pre id="question"></pre>
+ <li>Response <pre id="output"></pre>
+ <li>Events: <pre id="outevt"></pre>
+ </ol>
+ </div>
diff --git a/htdocs/audiohl.html b/htdocs/audiohl.html
index 41af265..ec4dd43 100644
--- a/htdocs/audiohl.html
+++ b/htdocs/audiohl.html
@@ -7,103 +7,47 @@
<script type="text/javascript" src="AudioBinding.js"></script>
</head>
-<body onload="init('audiohl')">
+<body onload="init('audiohl'); ep_type=1 ; ar='Entertainment' ; ep_id=0 ; s_id=0 ; val='0' ; p_name='balance' ; st_state=0">
- <button id="connected" onclick="init('audiohl');">Binder WS Fail</button>
+ <button id="connected" onclick="init('audiohl');">Binder WS Fail</button> <br><br>
+ <button id="monitoring" onclick="window.open('/monitoring/monitor.html','_monitor_audio')">Debug/Monitoring</a></button>
<b>Audio Role</b>
<select select_id='audiorole_list' onclick='ar=this.value'>
- <option selected value=''>Select...</option>
- <option value='warning' >Warning</option>
- <option value='guidance'>Guidance</option>
- <option value='notification'>Notification</option>
- <option value='communication'>Communication</option>
- <option value='entertainment'>Entertainment</option>
- <option value='system'>System</option>
- <option value='startup'>Startup</option>
- <option value='shutdown'>Shutdown</option>
- </select>
+ <option value='Warning' >Warning</option>
+ <option value='Guidance'>Guidance</option>
+ <option value='Notification'>Notification</option>
+ <option value='Communication'>Communication</option>
+ <option selected value='Entertainment'>Entertainment</option>
+ <option value='System'>System</option>
+ <option value='Startup'>Startup</option>
+ <option value='Shutdown'>Shutdown</option>
+ </select><br>
<b>Endpoint Type</b>
<select select_id='endpoint_type_list' onclick='ep_type=this.selectedIndex'>
<option value=0>Source</option>
- <option value=1>Sink</option>
- <option selected value=''>Select...</option>
- </select>
- <b>Endpoint ID</b>
- <select select_id='endpoint_id_list' onclick='ep_id=this.selectedIndex'>
- <option value='0'>0</option>
- <option value='1'>1</option>
- <option value='2'>2</option>
- <option value='3'>3</option>
- <option value='4'>4</option>
- <option value='5'>5</option>
- <option value='6'>6</option>
- <option value='7'>7</option>
- <option value='8'>8</option>
- <option value='9'>9</option>
- <option value='10'>10</option>
- <option value='11'>11</option>
- <option value='12'>12</option>
- <option value='13'>13</option>
- <option value='14'>14</option>
- <option value='15'>15</option>
- <option value='16'>16</option>
- <option value='17'>17</option>
- <option value='18'>18</option>
- <option value='19'>19</option>
- <option selected value=''>Select...</option>
- </select>
- <b>Stream ID</b>
- <select select_id='stream_id_list' onclick='s_id=this.selectedIndex'>
- <option value='0'>0</option>
- <option value='1'>1</option>
- <option value='2'>2</option>
- <option value='3'>3</option>
- <option value='4'>4</option>
- <option value='5'>5</option>
- <option value='6'>6</option>
- <option value='7'>7</option>
- <option value='8'>8</option>
- <option value='9'>9</option>
- <option value='10'>10</option>
- <option value='11'>11</option>
- <option value='12'>12</option>
- <option value='13'>13</option>
- <option value='14'>14</option>
- <option value='15'>15</option>
- <option value='16'>16</option>
- <option value='17'>17</option>
- <option value='18'>18</option>
- <option value='19'>19</option>
- <option selected value=''>Select...</option>
- </select>
+ <option selected value=1>Sink</option>
+ </select><br>
+ <b><label for="epidsel">Endpoint ID</label></b>
+ <input id="epidsel" type="number" value="0" min=0 step=1 maxlength=4 onchange='ep_id=eval(parseInt(this.value))'><br>
+ <b><label for="stidsel">Stream ID</label></b>
+ <input id="stidsel" type="number" value="0" min=0 step=1 maxlength=4 onchange='s_id=eval(parseInt(this.value))'><br>
<b>Property</b>
<select select_id='property_name_list' onclick='p_name=this.value'>
- <option value='balance'>Balance</option>
+ <option selected value='balance'>Balance</option>
<option value='fade'>Fade</option>
<option value='eq_bass'>EQ Bass</option>
<option value='eq_mid'>EQ Mid</option>
<option value='eq_treble'>EQ Treble</option>
- <option selected value=''>Select...</option>
- </select>
- <b>Volume/Property Value</b>
- <select select_id='value_list' onclick='val=this.value'>
- <option value='0'>0</option>
- <option value='20'>20</option>
- <option value='40'>40</option>
- <option value='60'>60</option>
- <option value='80'>80</option>
- <option value='100'>100</option>
- <option selected value=''>Select...</option>
- </select>
-
+ <option value='preset_name'>Preset Name</option>
+ </select><br>
+ <b><label for="valpropsel">Volume/Property Value</label></b>
+ <input id="valpropsel" type="number" value="0" min=0 max=100 step=1 maxlength=4 onchange='val=this.value'><br>
<b>Stream Active/Mute State</b>
<select select_id='state_value_list' onclick='st_state=this.selectedIndex'>
- <option value='off'>Off</option>
+ <option selected value='off'>Off</option>
<option value='on'>On</option>
- <option selected value=''>Select...</option>
- </select>
-
+ </select><br>
<br>
<ol>
@@ -119,7 +63,7 @@
<li><button onclick="callbinder('audiohl','get_volume', {endpoint_type:ep_type,endpoint_id:ep_id})">get_volume(endpoint_type,endpoint_id)</button></li>
<li><button onclick="callbinder('audiohl','get_list_properties', {endpoint_type:ep_type,endpoint_id:ep_id})">get_list_properties(endpoint_type,endpoint_id)</button></li>
<li><button onclick="callbinder('audiohl','set_property', {endpoint_type:ep_type,endpoint_id:ep_id,property_name:p_name,value:val})">set_property(endpoint_type,endpoint_id,property,value)</button></li>
- <li><button onclick="callbinder('audiohl','get_property', {endpoint_type:ep_type,endpoint_id:ep_id,property_name:'balance'})">get_property(endpoint_type,endpoint_id,'balance')</button></li>
+ <li><button onclick="callbinder('audiohl','get_property', {endpoint_type:ep_type,endpoint_id:ep_id,property_name:p_name})">get_property(endpoint_type,endpoint_id,property)</button></li>
<li><button onclick="callbinder('audiohl','get_list_events', {audio_role:ar})">get_list_events(audio_role)</button></li>
<li><button onclick="callbinder('audiohl','post_event', {event_name:'play_sound',audio_role:ar,media_name:'HomeButton.wav'})">post_event(play_sound,audio_role,'HomeButton.wav')</button></li>
<li><button onclick="callbinder('audiohl','subscribe', {events:['ahl_endpoint_property_event','ahl_endpoint_volume_event','ahl_post_event']})">subscribe(['ahl_endpoint_property_event','ahl_endpoint_volume_event','ahl_post_event'])</button>
diff --git a/htdocs/index.html b/htdocs/index.html
index 3c43f9b..532cfce 100644
--- a/htdocs/index.html
+++ b/htdocs/index.html
@@ -1,11 +1,10 @@
<html>
<head>
- <title>AGL-AudioBindins tests</title>
+ <title>AGL Audio Agent</title>
<body>
- <h1>audio-bindings test</h1>
+ <h1>AAA binding tests</h1>
<ol>
<li><a href="alsa-core.html">AlsaCore Low Level Binding</a>
<li><a href="alsa-hal.html" >AlsaHAL Hardware Abstraction Layer</a>
- <li><a href="audio-control.html">AudioControl Control/Policy API</a>
<li><a href="audiohl.html">High Level Audio API</a>
- <li><a href="audiohl-demo.html">High Level Audio API Demo</a>
+ <li><a href="audiobackend.html">Audio Backend Test</a>
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