diff options
author | Thierry Bultel <thierry.bultel@iot.bzh> | 2018-08-31 13:53:47 +0200 |
---|---|---|
committer | Thierry Bultel <thierry.bultel@iot.bzh> | 2018-08-31 13:53:47 +0200 |
commit | 80deafbe1bfb87c3a5e9f547c9491afd210e736a (patch) | |
tree | 67f83684cf67268dcb65d8062280f848057a74c2 | |
parent | ded8e37b50982677480869763f3573ff43858505 (diff) |
Added bluez sound playback support
This adds sound playback for incoming sound from
connected bluetooth devices.
In this version, the softmixer relies on a modified
bluez-alsa version (https://github.com/iotbzh/bluez-alsa),
that provides an ioplug PCM bluezalsa connection proxy.
The softmixer api has a new verb to dynamically set the
device to listen to:
afb-client-demo ws://localhost:1234/api?token=\uuid=123 smixer bluezalsa_dev '{"interface":"hci0", "device":"F6:32:15:2A:80:70", "profile":"a2dp"}'
In this way it is possible to switch from a bluezalsa audio source to
another without any further configuration.
For now, only interface hci0 is supported.
This commit also migrates the softmixer binding to API v3
Signed-off-by: Thierry Bultel <thierry.bultel@iot.bzh>
m--------- | app-controller-submodule | 0 | ||||
-rw-r--r-- | conf.d/cmake/config.cmake | 2 | ||||
-rw-r--r-- | mixer-binding/mixer-binding.c | 30 | ||||
-rw-r--r-- | plugins/alsa/alsa-api-loop.c | 1 | ||||
-rw-r--r-- | plugins/alsa/alsa-api-mixer.c | 64 | ||||
-rw-r--r-- | plugins/alsa/alsa-api-pcm.c | 9 | ||||
-rw-r--r-- | plugins/alsa/alsa-api-streams.c | 28 | ||||
-rw-r--r-- | plugins/alsa/alsa-bluez.c | 67 | ||||
-rw-r--r-- | plugins/alsa/alsa-bluez.h | 29 | ||||
-rw-r--r-- | plugins/alsa/alsa-core-ctl.c | 16 | ||||
-rw-r--r-- | plugins/alsa/alsa-core-pcm.c | 68 | ||||
-rw-r--r-- | plugins/alsa/alsa-softmixer.h | 5 | ||||
-rw-r--r-- | plugins/alsa/alsa-utils-bypath.c | 9 |
13 files changed, 271 insertions, 57 deletions
diff --git a/app-controller-submodule b/app-controller-submodule -Subproject 4e30eb1444fc88b73582db421d3a7467a364706 +Subproject c3a70d7718d51f2266ce671ee112a25398df076 diff --git a/conf.d/cmake/config.cmake b/conf.d/cmake/config.cmake index 1c5fedd..9b4dc5f 100644 --- a/conf.d/cmake/config.cmake +++ b/conf.d/cmake/config.cmake @@ -135,7 +135,7 @@ add_definitions(-DCONTROL_PLUGIN_PATH="${CMAKE_BINARY_DIR}/package/lib/plugins:$ add_definitions(-DCONTROL_CONFIG_PATH="${CMAKE_SOURCE_DIR}/conf.d/project/etc:${CMAKE_BINARY_DIR}/package/etc:${CMAKE_INSTALL_PREFIX}/${PROJECT_NAME}/etc:${CMAKE_INSTALL_PREFIX}/${PROJECT_NAME}") add_definitions(-DCONTROL_LUA_PATH="${CMAKE_SOURCE_DIR}/conf.d/project/lua.d:${CMAKE_INSTALL_PREFIX}/${PROJECT_NAME}/var:${CMAKE_INSTALL_PREFIX}/${PROJECT_NAME}") add_definitions(-DCTL_PLUGIN_MAGIC=987456123) -add_definitions(-DUSE_API_DYN=1 -DAFB_BINDING_VERSION=3 -DAFB_BINDING_WANT_DYNAPI=1) +add_definitions(-DAFB_BINDING_VERSION=3) # (BUG!!!) as PKG_CONFIG_PATH does not work [should be an env variable] diff --git a/mixer-binding/mixer-binding.c b/mixer-binding/mixer-binding.c index 3af9c9b..ed5b5a8 100644 --- a/mixer-binding/mixer-binding.c +++ b/mixer-binding/mixer-binding.c @@ -23,8 +23,8 @@ #include "mixer-binding.h" -// default api to print log when apihandle not avaliable -PUBLIC afb_dynapi *AFB_default; +// default api to print log when apihandle not avalaible +PUBLIC afb_api_t AFB_default; // Config Section definition (note: controls section index should match handle retrieval in HalConfigExec) static CtlSectionT ctrlSections[]= { @@ -52,16 +52,16 @@ STATIC AFB_ApiVerbs CtrlApiVerbs[] = { { .verb = NULL} /* marker for end of the array */ }; -STATIC int CtrlLoadStaticVerbs (afb_dynapi *apiHandle, AFB_ApiVerbs *verbs) { +STATIC int CtrlLoadStaticVerbs (afb_api_t apiHandle, AFB_ApiVerbs *verbs) { int errcount=0; for (int idx=0; verbs[idx].verb; idx++) { - errcount+= afb_dynapi_add_verb(apiHandle, - CtrlApiVerbs[idx].verb, - CtrlApiVerbs[idx].info, - CtrlApiVerbs[idx].callback, - (void*)&CtrlApiVerbs[idx], - CtrlApiVerbs[idx].auth, 0); + errcount+= afb_api_add_verb(apiHandle, + CtrlApiVerbs[idx].verb, + CtrlApiVerbs[idx].info, + CtrlApiVerbs[idx].callback, + (void*)&CtrlApiVerbs[idx], + CtrlApiVerbs[idx].auth, 0, 0); } return errcount; @@ -75,7 +75,7 @@ STATIC int CtrlLoadOneApi (void *cbdata, AFB_ApiT apiHandle) { CtlConfigT *ctrlConfig = (CtlConfigT*) cbdata; // save closure as api's data context - afb_dynapi_set_userdata(apiHandle, ctrlConfig); + afb_api_set_userdata(apiHandle, ctrlConfig); // add static controls verbs int error = CtrlLoadStaticVerbs (apiHandle, CtrlApiVerbs); @@ -88,7 +88,7 @@ STATIC int CtrlLoadOneApi (void *cbdata, AFB_ApiT apiHandle) { error= CtlLoadSections(apiHandle, ctrlConfig, ctrlSections); // declare an event event manager for this API; - afb_dynapi_on_event(apiHandle, CtrlDispatchApiEvent); + afb_api_on_event(apiHandle, CtrlDispatchApiEvent); // should not seal API as each mixer+stream create a new verb // afb_dynapi_seal(apiHandle); @@ -98,7 +98,7 @@ OnErrorExit: return 1; } -PUBLIC int afbBindingEntry(afb_dynapi *apiHandle) { +PUBLIC int afbBindingEntry(afb_api_t apiHandle) { AFB_default = apiHandle; @@ -128,10 +128,12 @@ PUBLIC int afbBindingEntry(afb_dynapi *apiHandle) { AFB_ApiNotice (apiHandle, "Controller API='%s' info='%s'", ctrlConfig->api, ctrlConfig->info); // create one API per config file (Pre-V3 return code ToBeChanged) - int status = afb_dynapi_new_api(apiHandle, ctrlConfig->api, ctrlConfig->info, 1, CtrlLoadOneApi, ctrlConfig); + afb_api_t handle = afb_api_new_api(apiHandle, ctrlConfig->api, ctrlConfig->info, 1, CtrlLoadOneApi, ctrlConfig); + + int status = 0; // config exec should be done after api init in order to enable onload to use newly defined ctl API. - if (!status) + if (handle) status = CtlConfigExec (apiHandle, ctrlConfig); return status; diff --git a/plugins/alsa/alsa-api-loop.c b/plugins/alsa/alsa-api-loop.c index c5b2a53..e2d789e 100644 --- a/plugins/alsa/alsa-api-loop.c +++ b/plugins/alsa/alsa-api-loop.c @@ -67,6 +67,7 @@ STATIC AlsaLoopSubdevT *ProcessOneSubdev(SoftMixerT *mixer, AlsaSndLoopT *loop, AlsaDevInfoT loopSubdev; loopSubdev.devpath=NULL; loopSubdev.cardid=NULL; + loopSubdev.pcmplug_params = NULL; loopSubdev.cardidx = loop->sndcard->cid.cardidx; loopSubdev.device = loop->capture; loopSubdev.subdev = subdev->index; diff --git a/plugins/alsa/alsa-api-mixer.c b/plugins/alsa/alsa-api-mixer.c index 558a522..c4b016c 100644 --- a/plugins/alsa/alsa-api-mixer.c +++ b/plugins/alsa/alsa-api-mixer.c @@ -19,6 +19,8 @@ #define _GNU_SOURCE // needed for vasprintf #include "alsa-softmixer.h" +#include "alsa-bluez.h" + #include <string.h> #include <pthread.h> @@ -26,7 +28,7 @@ extern Lua2cWrapperT Lua2cWrap; static void MixerRemoveVerb(AFB_ReqT request) { - SoftMixerT *mixer = (SoftMixerT*) afb_request_get_vcbdata(request); + SoftMixerT *mixer = (SoftMixerT*) afb_req_get_vcbdata(request); int error; for (int idx = 0; mixer->streams[idx]->uid; idx++) { @@ -41,7 +43,7 @@ static void MixerRemoveVerb(AFB_ReqT request) { goto OnErrorExit; } - error = afb_dynapi_sub_verb(mixer->api, stream->uid); + error = afb_api_del_verb(mixer->api, stream->uid, (void**)&mixer); if (error) { AFB_ReqFailF(request, "internal-error", "Mixer=%s fail to remove verb=%s error=%s", mixer->uid, stream->uid, strerror(error)); goto OnErrorExit; @@ -481,7 +483,7 @@ OnErrorExit: STATIC void MixerInfoAction(AFB_ReqT request, json_object * argsJ) { - SoftMixerT *mixer = (SoftMixerT*) afb_request_get_vcbdata(request); + SoftMixerT *mixer = (SoftMixerT*) afb_req_get_vcbdata(request); int error, verbose = 0; json_object *streamsJ = NULL, *rampsJ = NULL, *zonesJ = NULL, *capturesJ = NULL, *playbacksJ = NULL; @@ -534,15 +536,15 @@ STATIC void MixerInfoAction(AFB_ReqT request, json_object * argsJ) { } STATIC void MixerInfoVerb(AFB_ReqT request) { - json_object *argsJ = afb_request_json(request); + json_object *argsJ = afb_req_json(request); MixerInfoAction(request, argsJ); } STATIC void MixerAttachVerb(AFB_ReqT request) { - SoftMixerT *mixer = (SoftMixerT*) afb_request_get_vcbdata(request); + SoftMixerT *mixer = (SoftMixerT*) afb_req_get_vcbdata(request); const char *uid = NULL, *prefix = NULL; json_object *playbacksJ = NULL, *capturesJ = NULL, *zonesJ = NULL, *streamsJ = NULL, *rampsJ = NULL, *loopsJ = NULL; - json_object *argsJ = afb_request_json(request); + json_object *argsJ = afb_req_json(request); json_object *responseJ = json_object_new_object(); int error; @@ -632,6 +634,49 @@ STATIC void MixerAttachVerb(AFB_ReqT request) { return; OnErrorExit: + AFB_ApiError(mixer->api,"%s FAILED", __func__); + return; +} + + +static void MixerBluezAlsaDevVerb(AFB_ReqT request) { + SoftMixerT *mixer = (SoftMixerT*) afb_req_get_vcbdata(request); + + json_object *argsJ = afb_req_json(request); + json_object *responseJ = json_object_new_object(); + + char * interface = NULL, *device = NULL, *profile = NULL; + + int error; + + error = wrap_json_unpack(argsJ, "{ss,ss,ss !}" + , "interface", &interface + , "device", &device + , "profile", &profile + ); + + if (error) { + AFB_ReqFailF(request, + "invalid-syntax", + "mixer=%s missing 'interface|device|profile' error=%s args=%s", + mixer->uid, wrap_json_get_error_string(error), json_object_get_string(argsJ)); + goto OnErrorExit; + } + + printf("%s: interface %s, device %s, profile %s\n", __func__, interface, device, profile); + + error = alsa_bluez_set_device(interface, device, profile); + if (error) { + AFB_ReqFailF(request, + "runtime error", + "Unable to set device , err %d", error); + goto OnErrorExit; + } + + + AFB_ReqSuccess(request, responseJ, NULL); + +OnErrorExit: return; } @@ -641,6 +686,7 @@ STATIC AFB_ApiVerbs CtrlApiVerbs[] = { { .verb = "attach", .callback = MixerAttachVerb, .info = "attach resources to mixer"}, { .verb = "remove", .callback = MixerRemoveVerb, .info = "remove existing mixer streams, zones, ..."}, { .verb = "info", .callback = MixerInfoVerb, .info = "list existing mixer streams, zones, ..."}, + { .verb = "bluezalsa_dev", .callback = MixerBluezAlsaDevVerb, .info = "set bluez alsa device"}, { .verb = NULL} /* marker for end of the array */ }; @@ -648,7 +694,7 @@ STATIC int LoadStaticVerbs(SoftMixerT *mixer, AFB_ApiVerbs * verbs) { int errcount = 0; for (int idx = 0; verbs[idx].verb; idx++) { - errcount += afb_dynapi_add_verb(mixer->api, CtrlApiVerbs[idx].verb, CtrlApiVerbs[idx].info, CtrlApiVerbs[idx].callback, (void*) mixer, CtrlApiVerbs[idx].auth, 0); + errcount += afb_api_add_verb(mixer->api, CtrlApiVerbs[idx].verb, CtrlApiVerbs[idx].info, CtrlApiVerbs[idx].callback, (void*) mixer, CtrlApiVerbs[idx].auth, 0, 0); } return errcount; @@ -757,7 +803,7 @@ CTLP_CAPI(MixerCreate, source, argsJ, responseJ) { mixer->sdLoop = AFB_GetEventLoop(source->api); mixer->api = source->api; - afb_dynapi_set_userdata(source->api, mixer); + afb_api_set_userdata(source->api, mixer); error = LoadStaticVerbs(mixer, CtrlApiVerbs); if (error) goto OnErrorExit; @@ -767,3 +813,5 @@ CTLP_CAPI(MixerCreate, source, argsJ, responseJ) { OnErrorExit: return -1; } + + diff --git a/plugins/alsa/alsa-api-pcm.c b/plugins/alsa/alsa-api-pcm.c index a51bb6f..5b6d3f5 100644 --- a/plugins/alsa/alsa-api-pcm.c +++ b/plugins/alsa/alsa-api-pcm.c @@ -232,11 +232,11 @@ OnErrorExit: } STATIC void ApiPcmVerbCB(AFB_ReqT request) { - apiVerbHandleT *handle = (apiVerbHandleT*) afb_request_get_vcbdata(request); + apiVerbHandleT *handle = (apiVerbHandleT*) afb_req_get_vcbdata(request); int error, verbose = 0, doInfo = 0, doToggle = 0, doMute = -1; json_object *volumeJ = NULL; json_object *responseJ = NULL; - json_object *argsJ = afb_request_json(request); + json_object *argsJ = afb_req_json(request); SoftMixerT *mixer = handle->mixer; AlsaSndCtlT *sndcard = handle->pcm->sndcard; @@ -432,8 +432,9 @@ PUBLIC AlsaSndPcmT * ApiPcmAttachOne(SoftMixerT *mixer, const char *uid, snd_pcm int error; pcm->sndcard = (AlsaSndCtlT*) calloc(1, sizeof (AlsaSndCtlT)); - error = wrap_json_unpack(argsJ, "{ss,s?s,s?s,s?i,s?i,s?o,s?o,s?o !}" + error = wrap_json_unpack(argsJ, "{ss,s?s,s?s,s?s,s?i,s?i,s?o,s?o,s?o !}" , "uid", &pcm->uid + , "pcmplug_params", &pcm->sndcard->cid.pcmplug_params , "path", &pcm->sndcard->cid.devpath , "cardid", &pcm->sndcard->cid.cardid , "device", &pcm->sndcard->cid.device @@ -549,7 +550,7 @@ PUBLIC AlsaSndPcmT * ApiPcmAttachOne(SoftMixerT *mixer, const char *uid, snd_pcm handle->pcm = pcm; handle->mixer = mixer; pcm->verb=apiVerb; - error = afb_dynapi_add_verb(mixer->api, apiVerb, apiInfo, ApiPcmVerbCB, handle, NULL, 0); + error = afb_api_add_verb(mixer->api, apiVerb, apiInfo, ApiPcmVerbCB, handle, NULL, 0, 0); if (error) { AFB_ApiError(mixer->api, "ApiPcmAttachOne mixer=%s verb=%s fail to Register Master control ", mixer->uid, apiVerb); goto OnErrorExit; diff --git a/plugins/alsa/alsa-api-streams.c b/plugins/alsa/alsa-api-streams.c index cf54d91..c06a7d4 100644 --- a/plugins/alsa/alsa-api-streams.c +++ b/plugins/alsa/alsa-api-streams.c @@ -19,8 +19,11 @@ #define _GNU_SOURCE // needed for vasprintf #include "alsa-softmixer.h" +#include "alsa-bluez.h" + #include <string.h> #include <stdbool.h> +#include <dlfcn.h> // Set stream volume control in % #define VOL_CONTROL_MAX 100 @@ -39,10 +42,10 @@ typedef struct { } apiHandleT; STATIC void StreamApiVerbCB(AFB_ReqT request) { - apiHandleT *handle = (apiHandleT*) afb_request_get_vcbdata(request); + apiHandleT *handle = (apiHandleT*) afb_req_get_vcbdata(request); int error, verbose = 0, doClose = 0, doToggle = 0, doMute = -1, doInfo = 0; long mute, volume, curvol; - json_object *volumeJ = NULL, *rampJ = NULL, *argsJ = afb_request_json(request); + json_object *volumeJ = NULL, *rampJ = NULL, *argsJ = afb_req_json(request); json_object *responseJ = NULL; SoftMixerT *mixer = handle->mixer; AlsaSndCtlT *sndcard = handle->sndcard; @@ -194,6 +197,8 @@ STATIC int CreateOneStream(SoftMixerT *mixer, const char * uid, AlsaStreamAudioT char *playbackName = NULL; char *runName = NULL; char *volName = NULL; + int pauseNumid = 0; + int volNumid = 0; AFB_ApiInfo(mixer->api, "%s, stream %s %s, source %s, sink %s, mute %d", @@ -224,6 +229,7 @@ STATIC int CreateOneStream(SoftMixerT *mixer, const char * uid, AlsaStreamAudioT captureDev->cardidx = sourceDev->cid.cardidx; captureDev->device = sourceDev->cid.device; captureDev->subdev = sourceDev->cid.subdev; + captureDev->pcmplug_params = sourceDev->cid.pcmplug_params; captureCard = sourceDev; AFB_ApiInfo(mixer->api, "%s found capture %s", __func__, uid); } else { @@ -240,6 +246,8 @@ STATIC int CreateOneStream(SoftMixerT *mixer, const char * uid, AlsaStreamAudioT capturePcm->mute = stream->mute; + AFB_ApiInfo(mixer->api,"%s: PCM opened !\n", __func__); + // Registry capturePcm PCM for active/pause event if (loopDev && loopDev->numid) { error = AlsaCtlRegister(mixer, captureCard, capturePcm, FONTEND_NUMID_RUN, loopDev->numid); @@ -281,18 +289,23 @@ STATIC int CreateOneStream(SoftMixerT *mixer, const char * uid, AlsaStreamAudioT } // retrieve channel count from route and push it to stream + stream->params->channels = zone->ccount; // create mute control and Registry it as pause/resume ctl) if (asprintf(&runName, "pause-%s", stream->uid) == -1) goto OnErrorExit; - int pauseNumid = AlsaCtlCreateControl(mixer, captureCard, runName, 1, 0, 1, 1, stream->mute); + AFB_ApiInfo(mixer->api,"%s: create mute control !\n", __func__); + + pauseNumid = AlsaCtlCreateControl(mixer, captureCard, runName, 1, 0, 1, 1, stream->mute); if (pauseNumid <= 0) { AFB_ApiError(mixer->api, "%s: Failed to create pause control", __func__); goto OnErrorExit; } + AFB_ApiInfo(mixer->api,"%s: register mute control !", __func__); + // Registry stop/play as a pause/resume control error = AlsaCtlRegister(mixer, captureCard, capturePcm, FONTEND_NUMID_PAUSE, pauseNumid); if (error) { @@ -303,6 +316,8 @@ STATIC int CreateOneStream(SoftMixerT *mixer, const char * uid, AlsaStreamAudioT if (asprintf(&volName, "vol-%s", stream->uid) == -1) goto OnErrorExit; + AFB_ApiInfo(mixer->api,"%s: create softvol", __func__); + // create stream and delay pcm opening until vol control is created streamPcm = AlsaCreateSoftvol(mixer, stream, volSlaveId, captureCard, volName, VOL_CONTROL_MAX, 0); if (!streamPcm) { @@ -310,8 +325,10 @@ STATIC int CreateOneStream(SoftMixerT *mixer, const char * uid, AlsaStreamAudioT goto OnErrorExit; } + AFB_ApiInfo(mixer->api,"%s: create softvol control", __func__); + // create volume control before softvol pcm is opened - int volNumid = AlsaCtlCreateControl(mixer, + volNumid = AlsaCtlCreateControl(mixer, captureCard, volName, stream->params->channels, @@ -406,7 +423,7 @@ STATIC int CreateOneStream(SoftMixerT *mixer, const char * uid, AlsaStreamAudioT stream->volume = volNumid; stream->mute = pauseNumid; - error = afb_dynapi_add_verb(mixer->api, stream->verb, stream->info, StreamApiVerbCB, apiHandle, NULL, 0); + error = afb_api_add_verb(mixer->api, stream->verb, stream->info, StreamApiVerbCB, apiHandle, NULL, 0, 0); if (error) { AFB_ApiError(mixer->api, "%s mixer=%s fail to Register API verb stream=%s", @@ -565,5 +582,6 @@ PUBLIC int ApiStreamAttach(SoftMixerT *mixer, AFB_ReqT request, const char *uid, return 0; OnErrorExit: + printf("%s FAILED\n", __func__); return -1; } diff --git a/plugins/alsa/alsa-bluez.c b/plugins/alsa/alsa-bluez.c new file mode 100644 index 0000000..bd6bc99 --- /dev/null +++ b/plugins/alsa/alsa-bluez.c @@ -0,0 +1,67 @@ +/* + * Copyright(C) 2018 "IoT.bzh" + * Author Thierry Bultel <thierry.bultel@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. + * + * + */ + + +#include "alsa-bluez.h" + +#include <stdbool.h> +#include <dlfcn.h> + +#define ALSA_BLUEZ_PROXY_LIB "/usr/lib/alsa-lib/libasound_module_pcm_bluealsa_proxy.so" +#define ALSA_BLUEZ_PROXY_SETDEVICE "bluealsa_proxy_set_remote_device" + +typedef int (*bluealsa_set_remote_device_ptr) (const char * interface, const char * device, const char * profile); + +static bluealsa_set_remote_device_ptr bluealsa_proxy_set_remote_device = NULL; + +void alsa_bluez_init() { + static bool initialized = false; + if (initialized) + goto failed; + + char errbuf[256]; + void * dl = snd_dlopen(ALSA_BLUEZ_PROXY_LIB, RTLD_NOW, errbuf, 256); + if (!dl) { + printf("Failed to open bluealsa proxy plugin\n"); + goto failed; + } + + void * func = snd_dlsym(dl, ALSA_BLUEZ_PROXY_SETDEVICE, SND_DLSYM_VERSION(SND_PCM_DLSYM_VERSION)); + if (!func) { + printf("Unable to find %s symbol\n", ALSA_BLUEZ_PROXY_SETDEVICE); + goto failed; + } + + bluealsa_proxy_set_remote_device = func; + initialized = true; + +failed: + return; +} + +int alsa_bluez_set_device(const char * interface, const char * device, const char * profile) { + if (!bluealsa_proxy_set_remote_device) + return -1; + + return bluealsa_proxy_set_remote_device(interface,device,profile); +} + + diff --git a/plugins/alsa/alsa-bluez.h b/plugins/alsa/alsa-bluez.h new file mode 100644 index 0000000..85d153a --- /dev/null +++ b/plugins/alsa/alsa-bluez.h @@ -0,0 +1,29 @@ +/* + * Copyright(C) 2018 "IoT.bzh" + * Author Thierry Bultel <thierry.bultel@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. + * + */ + +#ifndef __INC_ALSA_BLUEZ_H +#define __INC_ALSA_BLUEZ_H + +#include <alsa/asoundlib.h> + +extern void alsa_bluez_init(); +extern int alsa_bluez_set_device(const char * interface, const char * device, const char * profile); + +#endif /* __INC_ALSA_BLUEZ_H */ diff --git a/plugins/alsa/alsa-core-ctl.c b/plugins/alsa/alsa-core-ctl.c index d128b6c..01e560f 100644 --- a/plugins/alsa/alsa-core-ctl.c +++ b/plugins/alsa/alsa-core-ctl.c @@ -25,6 +25,8 @@ for the specific language governing permissions and #define _GNU_SOURCE // needed for vasprintf #include "alsa-softmixer.h" +#include "alsa-bluez.h" + #include <pthread.h> #include <sys/syscall.h> @@ -279,12 +281,22 @@ OnErrorExit: // Clone of AlsaLib snd_card_load2 static function -PUBLIC snd_ctl_card_info_t *AlsaCtlGetInfo(SoftMixerT *mixer, const char *cardid) { +PUBLIC snd_ctl_card_info_t *AlsaCtlGetCardInfo(SoftMixerT *mixer, const char *cardid) { int error; snd_ctl_t *ctl; AFB_ApiNotice(mixer->api, "Looking for card '%s'", cardid); + /* "bluealsa" is the name of the control external plugin + * (https://www.alsa-project.org/alsa-doc/alsa-lib/ctl_external_plugins.html) + */ + if (strstr(cardid, "bluealsa")) { + cardid="bluealsa"; + alsa_bluez_init(); + } + + AFB_ApiNotice(mixer->api, "Opening card control '%s'", cardid); + if ((error = snd_ctl_open(&ctl, cardid, SND_CTL_READONLY)) < 0) { cardid = "Not Defined"; goto OnErrorExit; @@ -627,6 +639,8 @@ OnErrorExit: PUBLIC int AlsaCtlRegister(SoftMixerT *mixer, AlsaSndCtlT *sndcard, AlsaPcmCtlT *pcmdev, RegistryNumidT type, int numid) { int index; + AFB_ApiInfo(mixer->api,"%s: %d!\n", __func__, numid); + for (index = 0; index < sndcard->rcount; index++) { if (!sndcard->registry[index]) break; } diff --git a/plugins/alsa/alsa-core-pcm.c b/plugins/alsa/alsa-core-pcm.c index c1b59b2..50e1384 100644 --- a/plugins/alsa/alsa-core-pcm.c +++ b/plugins/alsa/alsa-core-pcm.c @@ -269,7 +269,6 @@ PUBLIC int AlsaPcmConf(SoftMixerT *mixer, AlsaPcmCtlT *pcm, int mode) { }; snd_pcm_sw_params_get_avail_min(pxmSwParams, &pcm->avail_min); - printf("GET AVAILABLE MIN: %ld\n", n); int start_delay = 0; snd_pcm_uframes_t start_threshold; @@ -362,7 +361,7 @@ STATIC int AlsaPcmReadCB( struct pollfd * pfd, AlsaPcmCopyHandleT * pcmCopyHandl * hopefully we will have more luck next time */ if (availIn > availInBuf) { - printf("INCOMING BUFFER TOO SMALL !\n"); +// printf("INCOMING BUFFER TOO SMALL !\n"); availIn = availInBuf; } @@ -373,8 +372,8 @@ STATIC int AlsaPcmReadCB( struct pollfd * pfd, AlsaPcmCopyHandleT * pcmCopyHandl if (r <= 0) { pthread_mutex_unlock(&pcmCopyHandle->mutex); - // Wakeup the reader, in case it would be sleeping, - // that lets it an opportunity to pop. + // Wake up the reader, in case it is sleeping, + // that lets it an opportunity to pop something. sem_post(&pcmCopyHandle->sem); break; } @@ -386,6 +385,7 @@ STATIC int AlsaPcmReadCB( struct pollfd * pfd, AlsaPcmCopyHandleT * pcmCopyHandl pthread_mutex_unlock(&pcmCopyHandle->mutex); r = snd_pcm_readi(pcmIn, buf, r); + if (r == 0) { break; } @@ -469,7 +469,7 @@ static void readResume(AlsaPcmCopyHandleT * pcmCopyHandle) { static void *readThreadEntry(void *handle) { -#define LOOP_TIMEOUT_MSEC 10*1000 +#define LOOP_TIMEOUT_MSEC 10*1000 /* 10 seconds */ AlsaPcmCopyHandleT *pcmCopyHandle = (AlsaPcmCopyHandleT*) handle; pcmCopyHandle->tid = (int) syscall(SYS_gettid); @@ -478,8 +478,11 @@ static void *readThreadEntry(void *handle) { "%s :%s/%d Started, muted=%d", __func__, pcmCopyHandle->info, pcmCopyHandle->tid, pcmCopyHandle->pcmIn->mute); - struct pollfd * mutePfd = &pcmCopyHandle->pollFds[1]; - mutePfd->events = POLLIN | POLLHUP; + struct pollfd * mutePfd = &pcmCopyHandle->pollFds[0]; + struct pollfd * framePfd = &pcmCopyHandle->pollFds[1]; + + mutePfd->events = POLLIN | POLLHUP; + framePfd->events = POLLIN | POLLHUP; bool muted = pcmCopyHandle->pcmIn->mute; @@ -489,7 +492,7 @@ static void *readThreadEntry(void *handle) { /* loop until end */ for (;;) { - int err = poll(pcmCopyHandle->pollFds, 2, LOOP_TIMEOUT_MSEC); + int err = poll(pcmCopyHandle->pollFds, pcmCopyHandle->nbPcmFds, LOOP_TIMEOUT_MSEC); if (err < 0) { AFB_ApiError(pcmCopyHandle->api, "%s: poll err %s", __func__, strerror(errno)); continue; @@ -522,7 +525,21 @@ static void *readThreadEntry(void *handle) { continue; } - AlsaPcmReadCB(&pcmCopyHandle->pollFds[0], pcmCopyHandle); + unsigned short revents; + + int ret = snd_pcm_poll_descriptors_revents(pcmCopyHandle->pcmIn->handle, &pcmCopyHandle->pollFds[1], 1, &revents); + + if (ret == -ENODEV) { + sleep(1); + continue; + } + + if (framePfd->revents & POLLHUP) { + AFB_ApiNotice(pcmCopyHandle->api, "Frame POLLHUP"); + continue; + } + + AlsaPcmReadCB(&pcmCopyHandle->pollFds[1], pcmCopyHandle); } pthread_exit(0); @@ -631,6 +648,8 @@ PUBLIC int AlsaPcmCopy(SoftMixerT *mixer, AlsaStreamAudioT *stream, AlsaPcmCtlT pcmIn->mixer = mixer; pcmOut->mixer = mixer; + AFB_ApiInfo(mixer->api, "%s: Configure CAPTURE PCM", __func__); + // prepare PCM for capture and replay error = AlsaPcmConf(mixer, pcmIn, SND_PCM_STREAM_CAPTURE); if (error) { @@ -703,13 +722,13 @@ PUBLIC int AlsaPcmCopy(SoftMixerT *mixer, AlsaStreamAudioT *stream, AlsaPcmCtlT if (pcmInCount > 1) { AFB_ApiError(mixer->api, - "%s: Fail, pcmIn=%s; having more than one FD on capture PCM is not supported", - __func__, ALSA_PCM_UID(pcmOut->handle, string) ); + "%s: Fail, pcmIn=%s; having more than one FD on capture PCM is not supported (here, %d)", + __func__, ALSA_PCM_UID(pcmOut->handle, string) , pcmInCount); goto OnErrorExit; } - struct pollfd pcmInFds[1]; - if ((error = snd_pcm_poll_descriptors(pcmIn->handle, pcmInFds, 1)) < 0) { + struct pollfd pcmInFd; + if ((error = snd_pcm_poll_descriptors(pcmIn->handle, &pcmInFd, 1)) < 0) { AFB_ApiError(mixer->api, "%s: Fail pcmIn=%s get pollfds error=%s", __func__, ALSA_PCM_UID(pcmOut->handle, string), snd_strerror(error)); @@ -717,23 +736,27 @@ PUBLIC int AlsaPcmCopy(SoftMixerT *mixer, AlsaStreamAudioT *stream, AlsaPcmCtlT }; // create the mute pipe - int pFd[2]; - error = pipe(pFd); + int pMuteFd[2]; + error = pipe(pMuteFd); if (error < 0) { AFB_ApiError(mixer->api, "Unable to create the mute signaling pipe\n"); goto OnErrorExit; } - struct pollfd pipePoll; - pipePoll.fd = pFd[0]; - pipePoll.events = POLLIN; - pipePoll.revents = 0; + struct pollfd mutePFd; + // read end + mutePFd.fd = pMuteFd[0]; + mutePFd.events = POLLIN; + mutePFd.revents = 0; + + // write end + pcmIn->muteFd = pMuteFd[1]; - pcmIn->muteFd = pFd[1]; + cHandle->pollFds[0] = mutePFd; + cHandle->pollFds[1] = pcmInFd; - cHandle->pollFds[0] = pcmInFds[0]; - cHandle->pollFds[1] = pipePoll; + cHandle->nbPcmFds = pcmInCount+1; error = sem_init(&cHandle->sem, 0 , 0); if (error < 0) { @@ -754,6 +777,7 @@ PUBLIC int AlsaPcmCopy(SoftMixerT *mixer, AlsaStreamAudioT *stream, AlsaPcmCtlT __func__, ALSA_PCM_UID(pcmIn->handle, string), error); } + /// start a thread for writing if ((error = pthread_create(&cHandle->wthread, NULL, &readThreadEntry, cHandle)) < 0) { AFB_ApiError(mixer->api, diff --git a/plugins/alsa/alsa-softmixer.h b/plugins/alsa/alsa-softmixer.h index 96df8f3..e7218bd 100644 --- a/plugins/alsa/alsa-softmixer.h +++ b/plugins/alsa/alsa-softmixer.h @@ -86,6 +86,7 @@ typedef struct { const char *cardid; const char *name; const char *longname; + const char *pcmplug_params; int device; int subdev; } AlsaDevInfoT; @@ -135,6 +136,8 @@ typedef struct { int tid; char* info; + + int nbPcmFds; struct pollfd pollFds[2]; sem_t sem; @@ -279,7 +282,7 @@ PUBLIC snd_ctl_elem_id_t *AlsaCtlGetNameElemId(SoftMixerT *mixer, AlsaSndCtlT *s PUBLIC snd_ctl_t *AlsaCtlOpenCtl(SoftMixerT *mixer, const char *cardid) ; PUBLIC int CtlElemIdGetLong(SoftMixerT *mixer, AlsaSndCtlT *sndcard, snd_ctl_elem_id_t *elemId, long *value) ; PUBLIC int CtlElemIdSetLong(SoftMixerT *mixer, AlsaSndCtlT *sndcard, snd_ctl_elem_id_t *elemId, long value) ; -PUBLIC snd_ctl_card_info_t *AlsaCtlGetInfo(SoftMixerT *mixer, const char *cardid) ; +PUBLIC snd_ctl_card_info_t *AlsaCtlGetCardInfo(SoftMixerT *mixer, const char *cardid) ; PUBLIC int AlsaCtlNumidSetLong(SoftMixerT *mixer, AlsaSndCtlT *sndcard, int numid, long value) ; PUBLIC int AlsaCtlNumidGetLong(SoftMixerT *mixer, AlsaSndCtlT *sndcard, int numid, long* value) ; PUBLIC int AlsaCtlNameSetLong(SoftMixerT *mixer, AlsaSndCtlT *sndcard, const char *ctlName, long value) ; diff --git a/plugins/alsa/alsa-utils-bypath.c b/plugins/alsa/alsa-utils-bypath.c index 2f22333..4c258c2 100644 --- a/plugins/alsa/alsa-utils-bypath.c +++ b/plugins/alsa/alsa-utils-bypath.c @@ -27,6 +27,7 @@ #include <sys/ioctl.h> #include "alsa-softmixer.h" +#include "alsa-bluez.h" // extracted IOCTLs from <alsa/asoundlib.h> #define _IOR_HACKED(type,nr,size) _IOC(_IOC_READ,(type),(nr),size) @@ -63,6 +64,10 @@ PUBLIC AlsaPcmCtlT *AlsaByPathOpenPcm(SoftMixerT *mixer, AlsaDevInfoT *pcmDev, s AlsaPcmCtlT *pcmCtl = calloc(1, sizeof (AlsaPcmCtlT)); char *cardid = NULL; + if (pcmDev->pcmplug_params) { + pcmDev->cardid = pcmDev->pcmplug_params; + } + if (!pcmDev->cardid) { if (pcmDev->subdev) { if (asprintf(&cardid, "hw:%i,%i,%i", pcmDev->cardidx, pcmDev->device, pcmDev->subdev) == -1) @@ -115,7 +120,9 @@ PUBLIC snd_ctl_t *AlsaByPathOpenCtl(SoftMixerT *mixer, const char *uid, AlsaSndC if (dev->cid.devpath) cardInfo = AlsaByPathInfo(mixer, dev->cid.devpath); else if (dev->cid.cardid) - cardInfo = AlsaCtlGetInfo(mixer, dev->cid.cardid); + cardInfo = AlsaCtlGetCardInfo(mixer, dev->cid.cardid); + else if (dev->cid.pcmplug_params) + cardInfo = AlsaCtlGetCardInfo(mixer, dev->cid.pcmplug_params); if (!cardInfo) { AFB_ApiError(mixer->api, |