/* * Copyright(C) 2018 "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. * * reference : * https://github.com/zonque/simple-alsa-loop/blob/master/loop.c * https://www.alsa-project.org/alsa-doc/alsa-lib/_2test_2pcm_8c-example.html#a31 * */ #define _GNU_SOURCE // needed for vasprintf #include <pthread.h> #include <sys/syscall.h> #include "alsa-softmixer.h" typedef struct { const char *uid; long current; long target; int numid; AlsaSndCtlT *sndcard; AlsaVolRampT *ramp; sd_event_source *evtsrc; SoftMixerT *mixer; sd_event *sdLoop; } VolRampHandleT; STATIC int VolRampTimerCB(sd_event_source* source, uint64_t timer, void* handle) { VolRampHandleT *rHandle = (VolRampHandleT*)handle; int error; uint64_t usec; // RampDown if (rHandle->current > rHandle->target) { rHandle->current = rHandle->current - rHandle->ramp->stepDown; if (rHandle->current < rHandle->target) rHandle->current = rHandle->target; } // RampUp if (rHandle->current < rHandle->target) { rHandle->current = rHandle->current + rHandle->ramp->stepUp; if (rHandle->current > rHandle->target) rHandle->current = rHandle->target; } error = AlsaCtlNumidSetLong(rHandle->mixer, rHandle->sndcard, rHandle->numid, rHandle->current); if (error) goto OnErrorExit; // we reach target stop volram event if (rHandle->current == rHandle->target) { sd_event_source_unref(rHandle->evtsrc); free(rHandle); } else { // otherwise validate timer for a new run sd_event_now(rHandle->sdLoop, CLOCK_MONOTONIC, &usec); sd_event_source_set_enabled(rHandle->evtsrc, SD_EVENT_ONESHOT); error = sd_event_source_set_time(rHandle->evtsrc, usec + rHandle->ramp->delay); } return 0; OnErrorExit: AFB_ApiWarning(rHandle->mixer->api, "VolRampTimerCB stream=%s numid=%d value=%ld", rHandle->uid, rHandle->numid, rHandle->current); sd_event_source_unref(source); // abandon volRamp return -1; } PUBLIC int AlsaVolRampApply(SoftMixerT *mixer, AlsaSndCtlT *sndcard, AlsaStreamAudioT *stream, json_object *rampJ) { long curvol, newvol; const char *uid, *volS; json_object *volJ; int error, index; uint64_t usec; int count = 0; error = wrap_json_unpack(rampJ, "{ss so !}" , "uid", &uid , "volume", &volJ ); if (error) { AFB_ApiError(mixer->api, "AlsaVolRampApply:mixer=%s stream=%s invalid-json should {uid:ramp, vol:[+,-,=]value} ramp=%s", mixer->uid, stream->uid, json_object_get_string(rampJ)); goto OnErrorExit; } switch (json_object_get_type(volJ)) { case json_type_string: volS = json_object_get_string(volJ); switch (volS[0]) { case '+': count= sscanf(&volS[1], "%ld", &newvol); newvol = curvol + newvol; break; case '-': count= sscanf(&volS[1], "%ld", &newvol); newvol = curvol - newvol; break; case '=': count= sscanf(&volS[1], "%ld", &newvol); break; default: // hope for int as a string and force it as relative sscanf(&volS[0], "%ld", &newvol); if (newvol < 0) newvol = curvol - newvol; else newvol = curvol + newvol; } if (count != 1) { AFB_ApiError(mixer->api, "AlsaVolRampApply:mixer=%s stream=%s invalid-numeric expect {uid:%s, vol:[+,-,=]value} get vol:%s", mixer->uid, stream->uid, uid, json_object_get_string(volJ)); goto OnErrorExit; } break; case json_type_int: newvol = json_object_get_int(volJ); break; default: AFB_ApiError(mixer->api, "AlsaVolRampApply:mixer=%s stream=%s invalid-type expect {uid:%s, vol:[+,-,=]value} get vol:%s", mixer->uid, stream->uid, uid, json_object_get_string(volJ)); goto OnErrorExit; } error = AlsaCtlNumidGetLong(mixer, sndcard, stream->volume, &curvol); if (error) { AFB_ApiError(mixer->api, "AlsaVolRampApply:mixer=%s stream=%s ramp=%s Fail to get volume from numid=%d", mixer->uid, stream->uid, uid, stream->volume); goto OnErrorExit; } // search for ramp uid in mixer for (index=0; index<= mixer->max.ramps; index++) { if (!strcasecmp(mixer->ramps[index]->uid, uid)) { break; } } if (index == mixer->max.ramps) { AFB_ApiError(mixer->api, "AlsaVolRampApply:mixer=%s stream=%s ramp=%s does not exit", mixer->uid, stream->uid, uid); goto OnErrorExit; } VolRampHandleT *rHandle = calloc(1, sizeof (VolRampHandleT)); rHandle->uid = stream->uid; rHandle->numid = stream->volume; rHandle->sndcard = sndcard; rHandle->mixer = mixer; rHandle->ramp = mixer->ramps[index]; rHandle->target = newvol; rHandle->current = curvol; rHandle->sdLoop = mixer->sdLoop; // set a timer with ~250us accuracy sd_event_now(rHandle->sdLoop, CLOCK_MONOTONIC, &usec); (void) sd_event_add_time(rHandle->sdLoop, &rHandle->evtsrc, CLOCK_MONOTONIC, usec + 100, 250, VolRampTimerCB, rHandle); return 0; OnErrorExit: return -1; }