summaryrefslogtreecommitdiffstats
path: 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409
/*
 * 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.
 *
 */

#define _GNU_SOURCE  // needed for vasprintf

#ifndef _ALSA_SOFTMIXER_
#define _ALSA_SOFTMIXER_

#include <json-c/json_object.h>
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <alsa/asoundlib.h>
#include <stdbool.h>
#include <systemd/sd-event.h>
#include <semaphore.h>

#include <urcu/list.h>

#include "ctl-plugin.h"
#include "wrap-json.h"

#include "alsa-ringbuf.h"

#include "alsa-transaction.h"

#ifndef PUBLIC
#define PUBLIC
#endif
#ifndef STATIC
#define STATIC static
#endif

#define MAINLOOP_CONCURENCY 0
#define MAINLOOP_WATCHDOG 30000
#define ALSA_DEFAULT_PCM_RATE 48000
#define ALSA_DEFAULT_PCM_VOLUME 80

#define ALSA_CARDID_MAX_LEN 64


#define SMIXER_SUBDS_CTLS 16
#define SMIXER_DEFLT_LOOPS 4
#define SMIXER_DEFLT_SINKS 8
#define SMIXER_DEFLT_SOURCES 32
#define SMIXER_DEFLT_ZONES 32
#define SMIXER_DEFLT_STREAMS 32
#define SMIXER_DEFLT_RAMPS 8

#define SMIXER_DEFLT_DELAYMS	1000

#define SOFTMIXER_VERB_NONE "none"

#define ALSA_PLUG_PROTO(plugin) \
    int _snd_pcm_ ## plugin ## _open(snd_pcm_t **pcmp, const char *name, snd_config_t *root, snd_config_t *conf, snd_pcm_stream_t stream, int mode)

#define SOFTMIXER_NOMEM(api) \
	AFB_ApiError((api), "%s: Insufficient memory", __func__)

// auto switch from Log to API request depending on request presence.
#define AFB_IfReqFailF(mixer, request, status, format, ...) \
 if (request) AFB_ReqFailF(request, status, format, __VA_ARGS__); \
 else AFB_ApiError(mixer->api, format, __VA_ARGS__); 

#ifndef PUBLIC
#define PUBLIC
#endif
#ifndef STATIC
#define STATIC static
#endif

typedef enum {
    FONTEND_NUMID_IGNORE,
    FONTEND_NUMID_PAUSE,
    FONTEND_NUMID_RUN
} RegistryNumidT;

typedef struct {
    int cardidx;
    const char *devpath;
    const char *cardid;
    const char *name;
    const char *longname;
    const char *pcmplug_params;
    int device;
    int subdev;
} AlsaDevInfoT;

#define SND_FORMAT_STRING_LEN 16
#define SND_FORMAT_DEFAULT "S16_LE"

typedef struct {
    unsigned int rate;
    unsigned int channels;
    char formatString[SND_FORMAT_STRING_LEN];
    snd_pcm_format_t format;
    snd_pcm_access_t access;
    size_t sampleSize;
} AlsaPcmHwInfoT;

typedef struct {
	const char * name;
    int ccount;
    bool mute;
	int eventFd;
    AlsaDevInfoT cid;
    snd_pcm_t *handle;
    AlsaPcmHwInfoT *params;

	struct SoftMixerT_ * mixer;

    snd_pcm_uframes_t avail_min;

	bool closeAtDeletion; // intermediate pcms in the pcm chain must not be closed, else it make libasound abort()
	bool isPcmPlug;
} AlsaPcmCtlT;

typedef struct {
	AlsaPcmCtlT *pcmIn;
	AlsaPcmCtlT *pcmOut;
    AFB_ApiT api;
    sd_event_source* evtsrc;

    size_t frame_size;
    snd_pcm_uframes_t latency;	/* final latency in frames */

    // IO Job
	alsa_ringbuf_t * rbuf;

	uint32_t		  write_err_count;
	uint32_t		  read_err_count;

    unsigned int channels;
    sd_event *sdLoop;

    pthread_t rthread;
    pthread_t wthread;

    int tid;
    char* info;

    int nbPcmFds;
	struct pollfd * pollFds;

    sem_t sem;
    pthread_mutex_t mutex;

    int saveFd;
	bool ending;

	struct AlsaStreamAudioT_ *  stream;

} AlsaPcmCopyHandleT;

typedef struct {
    const char*name;
    int numid;
    int count;
    long min;
    long max;
    long step;
} AlsaSndControlT;


typedef struct {
    const char*uid;
    int port;
	double volume;
	struct cds_list_head	list;
} AlsaPcmChannelT;


typedef struct {
    const char *uid;
    int delay; // delay between volset in us
    int stepDown; // linear %
    int stepUp; // linear %
	struct cds_list_head list;
} AlsaVolRampT;


typedef struct {
    int numid;
    RegistryNumidT type;
    AlsaPcmCtlT *pcm;
	struct cds_list_head cardListEntry;	/* node to list in AlsaSndCtlT */
	struct AlsaSndCtlT_ *sndcard; /* ref to owner */
} RegistryEntryPcmT;


typedef struct AlsaSndCtlT_ {
    AlsaDevInfoT cid;
    snd_ctl_t *ctl;
    AlsaPcmHwInfoT *params;
	long nbRegistry;
	struct cds_list_head registryList;
	struct SubscribeHandleT_ * eventSubscribeHandle;
} AlsaSndCtlT;


typedef struct {
    const char *uid;
	unsigned int nbSources;
	AlsaPcmChannelT sources;
	unsigned int nbSinks;
	AlsaPcmChannelT sinks;
    int ccount;
    AlsaPcmHwInfoT *params;
	struct cds_list_head	list;
	snd_config_t * routeConfig;
	bool isPcmPlug;
} AlsaSndZoneT;

typedef struct {
    const char *uid;
    const char *verb;
    AlsaSndCtlT *sndcard;
    AlsaSndControlT volume;
    AlsaSndControlT mute;
	unsigned int nbChannels;
	AlsaPcmChannelT channels;
    snd_pcm_stream_t direction;
	struct cds_list_head	list;
	bool isPcmPlug;
	void * apiVerbHandle;
} AlsaSndPcmT;

typedef struct {
    char * uid;
    int index; // AVIRT: parent PCM index (Since subdev idx is always 0)
    int numid;
	struct cds_list_head list;
} AlsaLoopSubdevT;


struct SoftMixerT_;

typedef struct AlsaSndLoopT {
    bool avirt; // AVIRT: Is this loop AVIRT?
    const char *uid;
	struct SoftMixerT_ * mixer; /* owner */
    int playback; // AVIRT: UNUSED
    int capture; // AVIRT: UNUSED
    AlsaSndCtlT *sndcard;
	int nbSubdevs;
	AlsaLoopSubdevT subdevs;
	struct cds_list_head list;
} AlsaSndLoopT;

typedef struct AlsaStreamAudioT_ {
    const char *uid;
    const char *verb;
    const char *info;
    const char *sink;
	const char *playback;
    const char *source;
    const char *ramp;
    int volume;
    int mute;
	unsigned int	delayms;
    AlsaPcmHwInfoT *params;
    AlsaPcmCopyHandleT *copy;
	struct cds_list_head	list;	/* link to the global list*/
	AlsaPcmCtlT * softvol;
	snd_config_t * softvolConfig;
	snd_config_t * rateConfig;
	void * verbApiHandle;
} AlsaStreamAudioT;

typedef struct SoftMixerT_{
    const char *uid;
    const char *info;
    AFB_ApiT api;
    sd_event *sdLoop;

    struct {
        unsigned int loops;
        unsigned int sinks;
        unsigned int sources;
        unsigned int zones;
        unsigned int streams;
        unsigned int ramps;
    } max;
	unsigned int nbLoops;
	AlsaSndLoopT loops;
	unsigned int nbSinks;
	AlsaSndPcmT	sinks;
	unsigned int nbSources;
	AlsaSndPcmT	sources;
	unsigned int nbZones;
	AlsaSndZoneT zones;
	unsigned int nbStreams;
	AlsaStreamAudioT streams;
	unsigned int nbRamps;
	AlsaVolRampT ramps;

	AlsaMixerTransaction * transaction; // current transaction

	struct cds_list_head list; // node to list of all mixers

	struct cds_list_head transactionList; // all transactions of this mixer
} SoftMixerT;

// alsa-utils-bypath.c
PUBLIC snd_ctl_card_info_t *AlsaByPathInfo(SoftMixerT *mixer, const char *devpath);
PUBLIC AlsaPcmCtlT * AlsaPcmCtlNew(SoftMixerT*, const char*);
PUBLIC void AlsaPcmCtlDelete(SoftMixerT *mixer, void *);
PUBLIC AlsaPcmCtlT *AlsaByPathOpenPcmCtl(SoftMixerT *mixer, AlsaDevInfoT *pcmId, snd_pcm_stream_t direction);
PUBLIC snd_ctl_t *AlsaByPathOpenCtl(SoftMixerT *mixer, const char *uid, AlsaSndCtlT *dev);

// alsa-utils-dump.c
#define ALSA_PCM_UID(pcmHandle, buffer) AlsaDumpPcmUid(pcmHandle, buffer, sizeof(buffer))
#define ALSA_CTL_UID(ctlHandle, buffer) AlsaDumpCtlUid(ctlHandle, buffer, sizeof(buffer))
PUBLIC json_object *AlsaDumpObjF(const char *format, ...);
PUBLIC char *AlsaDumpPcmUid(snd_pcm_t *pcmHandle, char *buffer, size_t len);
PUBLIC char *AlsaDumpCtlUid(snd_ctl_t *ctlHandle, char *buffer, size_t len);
PUBLIC void AlsaDumpFormats(SoftMixerT *mixer, snd_pcm_t *pcmHandle);
PUBLIC void AlsaDumpCtlSubdev(SoftMixerT *mixer, snd_ctl_t *handle);
PUBLIC void AlsaDumpPcmParams(SoftMixerT *mixer, snd_pcm_hw_params_t *pcmHwParams);
PUBLIC void AlsaDumpPcmInfo(SoftMixerT *mixer, const char* info, snd_pcm_t *pcm);
PUBLIC void AlsaDumpElemConfig(SoftMixerT *mixer, const char* info, const char* elem);
PUBLIC void AlsaDumpCtlConfig(SoftMixerT *mixer, const char* info, snd_config_t *config, int indent);

// alsa-core-ctl.c
PUBLIC snd_ctl_elem_id_t *AlsaCtlGetNumidElemId(SoftMixerT *mixer, AlsaSndCtlT *sndcard, int numid) ;
PUBLIC snd_ctl_elem_id_t *AlsaCtlGetNameElemId(SoftMixerT *mixer, AlsaSndCtlT *sndcard, const char *ctlName) ;

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 *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) ;
PUBLIC int AlsaCtlNameGetLong(SoftMixerT *mixer, AlsaSndCtlT *sndcard, const char *ctlName, long* value) ;
PUBLIC int AlsaCtlCreateControl(SoftMixerT *mixer, AlsaSndCtlT *sndcard, char* ctlName, int ctlCount, int ctlMin, int ctlMax, int ctlStep, long value) ;

PUBLIC int AlsaCtlSubscribe(SoftMixerT *mixer, const char *uid, AlsaSndCtlT *sndcard) ;

PUBLIC void AlsaCtlUnregister(SoftMixerT* mixer, void *);
PUBLIC int AlsaCtlRegister(SoftMixerT *mixer, AlsaSndCtlT *sndcard, AlsaPcmCtlT *pcmdev,  RegistryNumidT type, int numid);

// alsa-core-pcm.c
PUBLIC int AlsaPcmConf(SoftMixerT *mixer, AlsaPcmCtlT *pcm, int mode);
PUBLIC int AlsaPcmCopyStart(SoftMixerT *mixer, AlsaStreamAudioT *stream, AlsaPcmCtlT *pcmIn, AlsaPcmCtlT *pcmOut, AlsaPcmHwInfoT * opts);
PUBLIC int AlsaPcmCopyStop(SoftMixerT *mixer, AlsaPcmCopyHandleT * handle);

// alsa-plug-*.c _snd_pcm_PLUGIN_open_ see macro ALSA_PLUG_PROTO(plugin)
PUBLIC int AlsaPcmCopyMuteSignal(SoftMixerT *mixer, AlsaPcmCtlT *pcmIn, bool mute);

PUBLIC AlsaPcmCtlT* AlsaCreateSoftvol(SoftMixerT *mixer, AlsaStreamAudioT *stream, char *slaveid, AlsaSndCtlT *sndcard, char* ctlName, int max, int open);
PUBLIC AlsaPcmCtlT* AlsaCreateRoute(SoftMixerT *mixer, AlsaSndZoneT *zone, int open);
PUBLIC AlsaPcmCtlT* AlsaCreateRate(SoftMixerT *mixer, AlsaStreamAudioT *stream, const char* pcmName, AlsaPcmCtlT *pcmSlave, AlsaPcmHwInfoT *params, int open);
PUBLIC AlsaPcmCtlT* AlsaCreateDmix(SoftMixerT *mixer, const char* pcmName, AlsaSndPcmT *pcmSlave, int open);

// alsa-api-*

PUBLIC int ApiLoopAttach(SoftMixerT *mixer, AFB_ReqT request, const char *, json_object * argsJ, json_object *streamsJ);
PUBLIC int ApiSourceAttach(SoftMixerT *mixer, AFB_ReqT request, const char *, json_object * argsJ);
PUBLIC int ApiSinkAttach(SoftMixerT *mixer, AFB_ReqT request, const char *, json_object * argsJ);
PUBLIC int ApiStreamAttach(SoftMixerT *mixer, AFB_ReqT request, const char * uid, const char *prefix, json_object * argsJ);
PUBLIC int ApiZoneAttach(SoftMixerT *mixer, AFB_ReqT request, const char *, json_object * argsJ);
PUBLIC int ApiRampAttach(SoftMixerT *mixer, AFB_ReqT request, const char *, json_object *argsJ);

PUBLIC void loopsDisplay(SoftMixerT * mixer);

// helper used for attach verb,and also by the streams API for fake zones
PUBLIC AlsaSndZoneT * zoneCreate(SoftMixerT* mixer, const char * uid, json_object * argsJ);

PUBLIC AlsaLoopSubdevT *ApiLoopFindSubdev(SoftMixerT *mixer, const char *streamUid, const char *targetUid, AlsaSndLoopT **loop);

PUBLIC AlsaPcmHwInfoT *ApiPcmSetParams(SoftMixerT *mixer, const char *uid, json_object *paramsJ);
PUBLIC AlsaPcmHwInfoT* ApiPcmParamsDup(SoftMixerT* mixer, AlsaPcmHwInfoT* params);
PUBLIC void ApiPcmParamsShow(SoftMixerT * mixer, const char * msg, const AlsaPcmHwInfoT * params);
PUBLIC void ApiPcmDelParams(SoftMixerT*, AlsaPcmHwInfoT*);

PUBLIC AlsaSndPcmT *ApiPcmAttachOne(SoftMixerT *mixer, const char *uid, snd_pcm_stream_t direction, json_object *argsJ);
PUBLIC AlsaSndPcmT * ApiPcmNew(SoftMixerT* mixer);
PUBLIC void ApiPcmDelete(SoftMixerT * mixer, AlsaSndPcmT * pcm);

PUBLIC AlsaVolRampT *ApiRampGetByUid(SoftMixerT *mixer, const char *uid);

PUBLIC AlsaPcmHwInfoT *ApiSinkGetParamsByZone(SoftMixerT *mixer, const char *target);

PUBLIC AlsaSndPcmT  *ApiSinkGetByUid(SoftMixerT *mixer, const char *target);
PUBLIC AlsaSndCtlT *ApiSourceFindSubdev(SoftMixerT *mixer, const char *target);


PUBLIC AlsaSndZoneT *ApiZoneGetByUid(SoftMixerT *mixer, const char *target);

// alsa-effect-ramp.c
PUBLIC AlsaVolRampT *ApiRampGetByUid(SoftMixerT *mixer, const char *uid);
PUBLIC int AlsaVolRampApply(SoftMixerT *mixer, AlsaSndCtlT *sndcard, AlsaStreamAudioT *stream, json_object *rampJ);

#endif