/*
* Copyright (C) 2016 "IoT.bzh"
* Author "Manuel Bachmann"
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MEDIA_RYGEL_H
#define MEDIA_RYGEL_H
/* --------------- MEDIA RYGEL DEFINITIONS ------------------ */
#include <sys/time.h>
#include <libgupnp/gupnp-control-point.h>
#include "local-def.h"
#define URN_MEDIA_SERVER "urn:schemas-upnp-org:device:MediaServer:1"
#define URN_MEDIA_RENDERER "urn:schemas-upnp-org:device:MediaRenderer:1"
#define URN_CONTENT_DIR "urn:schemas-upnp-org:service:ContentDirectory"
#define URN_AV_TRANSPORT "urn:schemas-upnp-org:service:AVTransport"
typedef enum { PLAY, PAUSE, STOP } State;
typedef struct dev_ctx dev_ctx_T;
struct dev_ctx {
GMainContext *loop;
GUPnPContext *context;
GUPnPDeviceInfo *device_info;
GUPnPServiceInfo *content_dir;
GUPnPServiceInfo *av_transport;
char *content_res;
int content_num;
State state;
State target_state;
};
STATIC char* _rygel_list_raw (dev_ctx_T *, unsigned int *);
STATIC char* _rygel_find_id_for_index (dev_ctx_T *, char *, unsigned int);
STATIC char* _rygel_find_metadata_for_id (dev_ctx_T *, char *);
STATIC char* _rygel_find_uri_for_metadata (dev_ctx_T *, char *);
STATIC unsigned char _rygel_start_doing (dev_ctx_T *, char *, char *, State);
STATIC unsigned char _rygel_find_av_transport (dev_ctx_T *);
STATIC void _rygel_device_cb (GUPnPControlPoint *, GUPnPDeviceProxy *, gpointer);
STATIC void _rygel_av_transport_cb (GUPnPControlPoint *
@media only all and (prefers-color-scheme: dark) {
.highlight .hll { background-color: #49483e }
.highlight .c { color: #75715e } /* Comment */
.highlight .err { color: #960050; background-color: #1e0010 } /* Error */
.highlight .k { color: #66d9ef } /* Keyword */
.highlight .l { color: #ae81ff } /* Literal */
.highlight .n { color: #f8f8f2 } /* Name */
.highlight .o { color: #f92672 } /* Operator */
.highlight .p { color: #f8f8f2 } /* Punctuation */
.highlight .ch { color: #75715e } /* Comment.Hashbang */
.highlight .cm { color: #75715e } /* Comment.Multiline */
.highlight .cp { color: #75715e } /* Comment.Preproc */
.highlight .cpf { color: #75715e } /* Comment.PreprocFile */
.highlight .c1 { color: #75715e } /* Comment.Single */
.highlight .cs { color: #75715e } /* Comment.Special */
.highlight .gd { color: #f92672 } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .gi { color: #a6e22e } /* Generic.Inserted */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #75715e } /* Generic.Subheading */
.highlight .kc { color: #66d9ef } /* Keyword.Constant */
.highlight .kd { color: #66d9ef } /* Keyword.Declaration */
.highlight .kn { color: #f92672 } /* Keyword.Namespace */
.highlight .kp { color: #66d9ef } /* Keyword.Pseudo */
.highlight .kr { color: #66d9ef } /* Keyword.Reserved */
.highlight .kt { color: #66d9ef } /* Keyword.Type */
.highlight .ld { color: #e6db74 } /* Literal.Date */
.highlight .m { color: #ae81ff } /* Literal.Number */
.highlight .s { color: #e6db74 } /* Literal.String */
.highlight .na { color: #a6e22e } /* Name.Attribute */
.highlight .nb { color: #f8f8f2 } /* Name.Builtin */
.highlight .nc { color: #a6e22e } /* Name.Class */
.highlight .no { color: #66d9ef } /* Name.Constant */
.highlight .nd { color: #a6e22e } /* Name.Decorator */
.highlight .ni { color: #f8f8f2 } /* Name.Entity */
.highlight .ne { color: #a6e22e } /* Name.Exception */
.highlight .nf { color: #a6e22e } /* Name.Function */
.highlight .nl { color: #f8f8f2 } /* Name.Label */
.highlight .nn { color: #f8f8f2 } /* Name.Namespace */
.highlight .nx { color: #a6e22e } /* Name.Other */
.highlight .py { color: #f8f8f2 } /* Name.Property */
.highlight .nt { color: #f92672 } /* Name.Tag */
.highlight .nv { color: #f8f8f2 } /* Name.Variable */
.highlight .ow { color: #f92672 } /* Operator.Word */
.highlight .w { color: #f8f8f2 } /* Text.Whitespace */
.highlight .mb { color: #ae81ff } /* Literal.Number.Bin */
.highlight .mf { color: #ae81ff } /* Literal.Number.Float */
.highlight .mh { color: #ae81ff } /* Literal.Number.Hex */
.highlight .mi { color: #ae81ff } /* Literal.Number.Integer */
.highlight .mo { color: #ae81ff } /* Literal.Number.Oct */
.highlight .sa { color: #e6db74 } /* Literal.String.Affix */
.highlight .sb { color: #e6db74 } /* Literal.String.Backtick */
.highlight .sc { color: #e6db74 } /* Literal.String.Char */
.highlight .dl { color: #e6db74 } /* Literal.String.Delimiter */
.highlight .sd { color: #e6db74 } /* Literal.String.Doc */
.highlight .s2 { color: #e6db74 } /* Literal.String.Double */
.highlight .se { color: #ae81ff } /* Literal.String.Escape */
.highlight .sh { color: #e6db74 } /* Literal.String.Heredoc */
.highlight .si { color: #e6db74 } /* Literal.String.Interpol */
.highlight .sx { color: #e6db74 } /* Literal.String.Other */
.highlight .sr { color: #e6db74 } /* Literal.String.Regex */
.highlight .s1 { color: #e6db74 } /* Literal.String.Single */
.highlight .ss { color: #e6db74 } /* Literal.String.Symbol */
.highlight .bp { color: #f8f8f2 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #a6e22e } /* Name.Function.Magic */
.highlight .vc { color: #f8f8f2 } /* Name.Variable.Class */
.highlight .vg { color: #f8f8f2 } /* Name.Variable.Global */
.highlight .vi { color: #f8f8f2 } /* Name.Variable.Instance */
.highlight .vm { color: #f8f8f2 } /* Name.Variable.Magic */
.highlight .il { color: #ae81ff } /* Literal.Number.Integer.Long */
}
@media (prefers-color-scheme: light) {
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
}
/*
* Copyright (C) 2016 "IoT.bzh"
* Author Fulup Ar Foll <fulup@iot.bzh>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* AfbCallBack (snd_ctl_hal_t *handle, int numid, void **response);
* AlsaHookInit is mandatory and called with numid=0
*
* Syntax in .asoundrc file
* CrlLabel { cb MyFunctionName name "My_Second_Control" }
*
* Testing:
* aplay -DAlsaHook /usr/share/sounds/alsa/test.wav
*
* References:
* https://www.spinics.net/lists/alsa-devel/msg54235.html
* https://github.com/shivdasgujare/utilities/blob/master/nexuss/alsa-scenario-hook/src/alsa-wrapper.c
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <alsa/asoundlib.h>
#include <alsa/conf.h>
#include <alsa/pcm.h>
#include <systemd/sd-event.h>
#include <json-c/json.h>
#include "afb/afb-wsj1.h"
#include "afb/afb-ws-client.h"
#include <pthread.h>
#include <semaphore.h>
#define PLUGIN_ENTRY_POINT AlsaInstallHook
// Fulup Note: What ever you may find on Internet you should use
// SND_CONFIG_DLSYM_VERSION_HOOK and not SND_CONFIG_DLSYM_VERSION_HOOK
SND_DLSYM_BUILD_VERSION(PLUGIN_ENTRY_POINT, SND_PCM_DLSYM_VERSION)
// this should be more than enough
#define MAX_API_CALL 10
#define MAX_EVT_CALL 10
// timeout in ms
#define REQUEST_DEFAULT_TIMEOUT 500
#ifndef MAINLOOP_WATCHDOG
#define MAINLOOP_WATCHDOG 100000
#endif
// closing message is added to query when PCM is closed
#define CLOSING_MSG ",\"source\":-1}"
// Currently not implemented
#define UNUSED_ARG(x) UNUSED_ ## x __attribute__((__unused__))
void OnRequestCB(void* UNUSED_ARG(handle) , const char* UNUSED_ARG(api), const char* UNUSED_ARG(verb), struct afb_wsj1_msg*UNUSED_ARG(msg)) {}
typedef struct {
const char *api;
const char *verb;
long timeout;
char *query;
size_t length;
sd_event_source *evtSource;
char *callIdTag;
void *afbClient;
} afbRequestT;
typedef struct {
const char *name;
int signal;
} afbEventT;
typedef struct {
snd_pcm_t *pcm;
const char *uri;
struct afb_wsj1 *wsj1;
sd_event *sdLoop;
int verbose;
sem_t semaphore;
int count;
int error;
afbRequestT **request;
afbEventT **event;
} afbClientT;
static void *LoopInThread(void *handle) {
afbClientT *afbClient = (afbClientT*) handle;
int count=0;
int watchdog= MAINLOOP_WATCHDOG *1000;
/* loop until end */
for (;;) {
if (afbClient->verbose) printf("ON-MAINLOOP ping=%d\n", count++);
sd_event_run(afbClient->sdLoop, watchdog);
}
return NULL;
}
// lost connect with the AudioDaemon
static void OnHangupCB(void *handle, struct afb_wsj1 *wsj1) {
afbClientT *afbClient = (afbClientT*) handle;
SNDERR("(Hoops) Lost Connection to %s", afbClient->uri);
// try to close PCM when impossible terminate client
int err = snd_pcm_close (afbClient->pcm);
if (err) exit(1);
}
typedef enum {
HOOK_INSTALL,
HOOK_CLOSE,
} hookActionT;
void OnEventCB(void *handle, const char *event, struct afb_wsj1_msg *msg) {
afbClientT *afbClient = (afbClientT*) handle;
afbEventT **afbEvent = afbClient->event;
json_object *eventJ, *tmpJ, *dataJ;
const char *label;
int value, done, index;
eventJ = afb_wsj1_msg_object_j(msg);
done= json_object_object_get_ex(eventJ,"data", &dataJ);
if (!done) {
SNDERR ("PCM_HOOK: uri=%s empty event label", afbClient->uri);
goto OnErrorExit;
}
json_object_object_get_ex(dataJ,"signal", &tmpJ);
label=json_object_get_string(tmpJ);
json_object_object_get_ex(dataJ,"value", &tmpJ);
value=json_object_get_int(tmpJ);
for (index=0; afbEvent[index]!= NULL; index++) {
if (!strcmp(afbEvent[index]->name, label)) break;
}
if (!afbEvent[index] || !afbEvent[index]->signal) {
SNDERR ("PCM_HOOK: Unsupported uri=%s label=%s", afbClient->uri, label);
return;
}
// send signal to self process
kill (getpid(), afbEvent[index]->signal);
if (afbClient->verbose) printf("ON-EVENT label=%s signal=%d\n", label, value);
return;
OnErrorExit:
SNDERR("ON-EVENT %s(%s)\n", event, afb_wsj1_msg_object_s(msg));
return;
}
// callback interface for wsj1
static struct afb_wsj1_itf itf = {
.on_hangup = OnHangupCB,
.on_call = OnRequestCB,
.on_event = OnEventCB
};
void OnResponseCB(void *handle, struct afb_wsj1_msg *msg) {
afbRequestT *afbRequest= (afbRequestT*)handle;
afbClientT *afbClient=(afbClientT*)afbRequest->afbClient;
if (afbClient->verbose) printf("ON-RESPONSE call=%s response=%s\n", afbRequest->callIdTag, afb_wsj1_msg_object_s(msg));
// Cancel timeout for this request
sd_event_source_unref(afbRequest->evtSource);
if (! afb_wsj1_msg_is_reply_ok(msg)) goto OnErrorExit;
// When not more waiting call release semaphore
afbClient->count--;
if (afbClient->count == 0) {
if (afbClient->verbose) printf("ON-RESPONSE No More Waiting Request\n");
afbClient->error=0;
sem_post (&afbClient->semaphore);
}
return;
OnErrorExit:
fprintf(stderr, "ON-RESPONSE ERROR call=%s response=%s\n", afbRequest->callIdTag, afb_wsj1_msg_object_s(msg));
afbClient->error=1;
sem_post (&afbClient->semaphore);
}
int OnTimeoutCB (sd_event_source* source, uint64_t timer, void* handle) {
afbClientT *afbClient= (afbClientT*)handle;
SNDERR("\nON-TIMEOUT Call Request Fail URI=%s\n", afbClient->uri);
// Close PCM and release waiting client
afbClient->error=1;
sem_post (&afbClient->semaphore);
return 0;
}
// Call AGL binder asynchronously by with a timeout
static int CallWithTimeout(afbClientT *afbClient, afbRequestT *afbRequest, int count, hookActionT action) {
uint64_t usec;
int err;
// create a unique tag for request
(void) asprintf(&afbRequest->callIdTag, "%d:%s/%s", count, afbRequest->api, afbRequest->verb);
// create a timer with ~250us accuracy
sd_event_now(afbClient->sdLoop, CLOCK_MONOTONIC, &usec);
sd_event_add_time(afbClient->sdLoop, &afbRequest->evtSource, CLOCK_MONOTONIC, usec+afbRequest->timeout*1000, 250, OnTimeoutCB, afbClient);
if (afbClient->verbose) printf("CALL-REQUEST api=%s/%s tag=%s\n", afbRequest->api, afbRequest->verb, afbRequest->callIdTag);
// on PCM close replace last '}' by CLOSING_MSG
if (action == HOOK_CLOSE) {
for (size_t index=afbRequest->length; index >0; index--) {
if (afbRequest->query[index] == '}') {
strcpy (&afbRequest->query[index], CLOSING_MSG);
break;
}
}
}
err = afb_wsj1_call_s(afbClient->wsj1, afbRequest->api, afbRequest->verb, afbRequest->query, OnResponseCB, afbRequest);
if (err) goto OnErrorExit;
// save client handle in request
afbRequest->afbClient = afbClient;
afbClient->count ++;
return 0;
OnErrorExit:
return -1;
}
static int LaunchCallRequest(afbClientT *afbClient, hookActionT action) {
pthread_t tid;
int err, idx;
afbRequestT **afbRequest= afbClient->request;
if (action == HOOK_INSTALL) {
// init waiting counting semaphore
if (sem_init(&afbClient->semaphore, 1, 0) == -1) {
fprintf(stderr, "LaunchCallRequest: Fail Semaphore Init: %s\n", afbClient->uri);
}
// Create a main loop
err = sd_event_default(&afbClient->sdLoop);
if (err < 0) {
fprintf(stderr, "LaunchCallRequest: Connection to default event loop failed: %s\n", strerror(-err));
goto OnErrorExit;
}
// start a thread with a mainloop to monitor Audio-Agent
err = pthread_create(&tid, NULL, &LoopInThread, afbClient);
if (err) goto OnErrorExit;
// connect the websocket wsj1 to the uri given by the first argument
afbClient->wsj1 = afb_ws_client_connect_wsj1(afbClient->sdLoop, afbClient->uri, &itf, afbClient);
if (afbClient->wsj1 == NULL) {
fprintf(stderr, "LaunchCallRequest: Connection to %s failed\n", afbClient->uri);
goto OnErrorExit;
}
}
// send call request to audio-agent asynchronously (respond with thread mainloop context)
for (idx = 0; afbRequest[idx] != NULL; idx++) {
err = CallWithTimeout(afbClient, afbRequest[idx], idx, action);
if (err) {
fprintf(stderr, "LaunchCallRequest: Fail call %s//%s/%s&%s", afbClient->uri, afbRequest[idx]->api, afbRequest[idx]->verb, afbRequest[idx]->query);
goto OnErrorExit;
}
}
// launch counter to keep track of waiting request call
afbClient->count=idx;
return 0;
OnErrorExit:
return -1;
}
static int AlsaCloseHook(snd_pcm_hook_t *hook) {
afbClientT *afbClient = (afbClientT*) snd_pcm_hook_get_private (hook);
// launch call request and create a waiting mainloop thread
int err = LaunchCallRequest(afbClient, HOOK_CLOSE);
if (err < 0) {
fprintf (stderr, "PCM Fail to Enter Mainloop\n");
goto OnErrorExit;
}
// wait for all call request to return
sem_wait(&afbClient->semaphore);
if (afbClient->error) {
fprintf (stderr, "AlsaCloseHook: Audio Agent Fail to respond\n");
goto OnErrorExit;
}
if (afbClient->verbose) fprintf(stdout, "\nAlsaHook Close Success PCM=%s URI=%s\n", snd_pcm_name(afbClient->pcm), afbClient->uri);
return 0;
OnErrorExit:
fprintf(stderr, "\nAlsaPcmHook Plugin Close Fail PCM=%s\n", snd_pcm_name(afbClient->pcm));
return 0;
}
// Function call when Plugin PCM is OPEN
int PLUGIN_ENTRY_POINT (snd_pcm_t *pcm, snd_config_t *conf) {
snd_pcm_hook_t *h_close = NULL;
snd_config_iterator_t it, next;
afbClientT *afbClient = malloc(sizeof (afbClientT));
afbRequestT **afbRequest = malloc(MAX_API_CALL * sizeof(afbRequestT*));
afbEventT **afbEvent= malloc(MAX_EVT_CALL * sizeof(afbEventT*));
int err;
// start populating client handle
afbClient->pcm = pcm;
afbClient->verbose = 0;
afbClient->request = afbRequest;
// Get PCM arguments from asoundrc
snd_config_for_each(it, next, conf) {
snd_config_t *node = snd_config_iterator_entry(it);
const char *id;
// ignore comment en empty lines
if (snd_config_get_id(node, &id) < 0) continue;
if (strcmp(id, "comment") == 0 || strcmp(id, "hint") == 0) continue;
if (strcmp(id, "uri") == 0) {
const char *uri;
if (snd_config_get_string(node, &uri) < 0) {
SNDERR("Invalid String for %s", id);
goto OnErrorExit;
}
afbClient->uri=strdup(uri);
continue;
}
if (strcmp(id, "verbose") == 0) {
afbClient->verbose= snd_config_get_bool(node);
if (afbClient->verbose < 0) {
SNDERR("Invalid Boolean for %s", id);
goto OnErrorExit;
}
continue;
}
if (strcmp(id, "request") == 0) {
const char *callConf, *callLabel;
snd_config_type_t ctype;
snd_config_iterator_t currentCall, follow;
snd_config_t *itemConf;
int callCount=0;
ctype = snd_config_get_type(node);
if (ctype != SND_CONFIG_TYPE_COMPOUND) {
snd_config_get_string(node, &callConf);
SNDERR("Invalid compound type for %s", callConf);
goto OnErrorExit;
}
// loop on each call
snd_config_for_each(currentCall, follow, node) {
snd_config_t *ctlconfig = snd_config_iterator_entry(currentCall);
// ignore empty line
if (snd_config_get_id(ctlconfig, &callLabel) < 0) continue;
// each clt should be a valid config compound
ctype = snd_config_get_type(ctlconfig);
if (ctype != SND_CONFIG_TYPE_COMPOUND) {
snd_config_get_string(node, &callConf);
SNDERR("Invalid call element for %s value=%s", callLabel, callConf);
goto OnErrorExit;
}
// allocate an empty call request
afbRequest[callCount] = calloc(1, sizeof (afbRequestT));
err = snd_config_search(ctlconfig, "api", &itemConf);
if (!err) {
const char *api;
if (snd_config_get_string(itemConf, &api) < 0) {
SNDERR("Invalid api string for %s", callLabel);
goto OnErrorExit;
}
afbRequest[callCount]->api=strdup(api);
}
err = snd_config_search(ctlconfig, "verb", &itemConf);
if (!err) {
const char *verb;
if (snd_config_get_string(itemConf, &verb) < 0) {
SNDERR("Invalid verb string %s", id);
goto OnErrorExit;
}
afbRequest[callCount]->verb=strdup(verb);
}
err = snd_config_search(ctlconfig, "timeout", &itemConf);
if (!err) {
if (snd_config_get_integer(itemConf, &afbRequest[callCount]->timeout) < 0) {
SNDERR("Invalid timeout Integer %s", id);
goto OnErrorExit;
}
}
err = snd_config_search(ctlconfig, "query", &itemConf);
if (!err) {
const char *query;
if (snd_config_get_string(itemConf, &query) < 0) {
SNDERR("Invalid args string %s", id);
goto OnErrorExit;
}
// reserve enough space to ad closing message
afbRequest[callCount]->length= strlen(query);
afbRequest[callCount]->query = malloc (afbRequest[callCount]->length+strlen(CLOSING_MSG)+1);
strcpy (afbRequest[callCount]->query, query);
// cleanup string for json_tokener
for (int idx = 0; query[idx] != '\0'; idx++) {
if (query[idx] == '\'') afbRequest[callCount]->query[idx] = '"';
else afbRequest[callCount]->query[idx] = query[idx];
}
json_object *queryJ = json_tokener_parse(afbRequest[callCount]->query);
if (!queryJ) {
SNDERR("Invalid Json %s args=%s should be args=\"{'tok1':'val1', 'tok2':'val2'}\" ", id, afbRequest[callCount]->query);
goto OnErrorExit;
}
}
// Simple check on call request validity
if (!afbRequest[callCount]->query) afbRequest[callCount]->query= "";
if (!afbRequest[callCount]->timeout) afbRequest[callCount]->timeout=REQUEST_DEFAULT_TIMEOUT ;
if (!afbRequest[callCount]->verb || !afbRequest[callCount]->api) {
SNDERR("Missing api/verb %s in asoundrc", callLabel);
goto OnErrorExit;
}
// move to next call if any
callCount ++;
if (callCount == MAX_API_CALL) {
SNDERR("Too Many call MAX_API_CALL=%d", MAX_API_CALL);
goto OnErrorExit;
}
afbRequest[callCount]=NULL; // afbRequest array is NULL terminated
}
continue;
}
if (strcmp(id, "event") == 0) {
const char *callConf, *callLabel;
snd_config_type_t ctype;
snd_config_iterator_t currentCall, follow;
int callCount=0;
ctype = snd_config_get_type(node);
if (ctype != SND_CONFIG_TYPE_COMPOUND) {
snd_config_get_string(node, &callConf);
SNDERR("Invalid compound type for %s", callConf);
goto OnErrorExit;
}
// loop on each call
snd_config_for_each(currentCall, follow, node) {
snd_config_t *ctlconfig = snd_config_iterator_entry(currentCall);
long sigval;
// ignore empty line
if (snd_config_get_id(ctlconfig, &callLabel) < 0) continue;
// each clt should be a valid config compound
ctype = snd_config_get_type(ctlconfig);
if (ctype != SND_CONFIG_TYPE_INTEGER) {
snd_config_get_string(ctlconfig, &callConf);
SNDERR("Invalid signal number for %s value=%s", callLabel, callConf);
goto OnErrorExit;
}
// allocate an empty call request
snd_config_get_integer(ctlconfig, &sigval);
afbEvent[callCount] = calloc(1, sizeof (afbEventT));
afbEvent[callCount]->name=strdup(callLabel);
afbEvent[callCount]->signal= (int)sigval;
// move to next call if any
callCount ++;
if (callCount == MAX_EVT_CALL) {
SNDERR("Too Many call MAX_EVT_CALL=%d", MAX_EVT_CALL);
goto OnErrorExit;
}
afbEvent[callCount]=NULL; // afbEvent array is NULL terminated
}
continue;
}
}
if (afbClient->verbose) fprintf(stdout, "\nAlsaHook Install Start PCM=%s URI=%s\n", snd_pcm_name(afbClient->pcm), afbClient->uri);
err = snd_pcm_hook_add(&h_close, afbClient->pcm, SND_PCM_HOOK_TYPE_CLOSE, AlsaCloseHook, afbClient);
if (err < 0) goto OnErrorExit;
// launch call request and create a waiting mainloop thread
err = LaunchCallRequest(afbClient, HOOK_INSTALL);
if (err < 0) {
fprintf (stderr, "PCM Fail to Enter Mainloop\n");
goto OnErrorExit;
}
// wait for all call request to return
sem_wait(&afbClient->semaphore);
if (afbClient->error) {
fprintf (stderr, "PCM Authorisation Deny from AAAA Controller (AGL Advanced Audio Agent)\n");
goto OnErrorExit;
}
if (afbClient->verbose) fprintf(stdout, "\nAlsaHook Install Success PCM=%s URI=%s\n", snd_pcm_name(afbClient->pcm), afbClient->uri);
return 0;
OnErrorExit:
fprintf(stderr, "\nAlsaPcmHook Plugin Install Fail PCM=%s\n", snd_pcm_name(pcm));
if (h_close)
snd_pcm_hook_remove(h_close);
return -EINVAL;
}