aboutsummaryrefslogtreecommitdiffstats
path: root/plugins/alsa/alsa-api-zones.c
blob: 83f6e3ac529ae81a71979790e387cce820fc450a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
5
###########################################################################
# Copyright (C) 2015-2020 "IoT.bzh"
#
# author: José Bollo <jose.bollo@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.
###########################################################################

INSTALL(DIRECTORY afb DESTINATION ${CMAKE_INSTALL_FULL_INCLUDEDIR})
='#n246'>246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290
/*
 * 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_MIXER  // needed for vasprintf

#include "alsa-softmixer.h"
#include <string.h>

// Fulup need to be cleanup with new controller version
extern Lua2cWrapperT Lua2cWrap;

PUBLIC AlsaSndZoneT *ApiZoneGetByUid(SoftMixerT *mixer, const char *target) {
	AlsaSndZoneT * zone = NULL;

	if (mixer->nbZones == 0) {
		AFB_ApiError(mixer->api, "%s mixer=%s does not have any zone", __func__, mixer->uid);
		goto fail;
	}

    // search for subdev into every registered loop
	cds_list_for_each_entry(zone, &mixer->zones.list, list) {
		if (zone->uid && !strcasecmp(zone->uid, target)) {
			return zone;
		}
    }
fail:
	AFB_ApiError(mixer->api, "%s mixer=%s fail to find zone=%s", __func__, mixer->uid, target);
    return NULL;
}

STATIC AlsaPcmChannelT* ProcessOneChannel(SoftMixerT *mixer, const char* uid, json_object *channelJ) {

    AlsaPcmChannelT *channel = calloc(1, sizeof (AlsaPcmChannelT));
	if (!channel) {
		SOFTMIXER_NOMEM(mixer->api);
		goto fail;
	}

	CDS_INIT_LIST_HEAD(&channel->list);

	channel->volume = -1;

	int error = wrap_json_unpack(channelJ, "{ss,si,s?f !}"
            , "target", &channel->uid
            , "channel", &channel->port
			, "volume", &channel->volume
            );
	if (error)
		goto fail;

    channel->uid = strdup(channel->uid);
	if (!channel->uid) {
		SOFTMIXER_NOMEM(mixer->api);
		goto fail_channel;
	}

	AFB_ApiDebug(mixer->api, "%s: uid %s, channel uid %s, port %d, volume %f",
			__func__, uid, channel->uid, channel->port, channel->volume);

    return channel;

fail_channel:
	free(channel);
fail:
	AFB_ApiError(mixer->api, "%s: zone=%s channel: missing (target|channel) json=%s", __func__, uid, json_object_get_string(channelJ));
    return NULL;
}


STATIC AlsaSndZoneT *AttachOneZone(SoftMixerT *mixer, const char *uid, json_object *zoneJ) {

	AFB_ApiDebug(mixer->api, "%s uid %s", __func__, uid);

    json_object *sinkJ = NULL, *sourceJ = NULL;
    size_t count;
    int error;

	AlsaSndZoneT *zone = calloc(1, sizeof (AlsaSndZoneT));
	if (!zone) {
		SOFTMIXER_NOMEM(mixer->api);
		goto fail;
	}

	CDS_INIT_LIST_HEAD(&zone->list);
	CDS_INIT_LIST_HEAD(&zone->sinks.list);
	CDS_INIT_LIST_HEAD(&zone->sources.list);

    error = wrap_json_unpack(zoneJ, "{ss,s?o,s?o !}"
            , "uid", &zone->uid
            , "sink", &sinkJ
            , "source", &sourceJ
            );
    if (error || (!sinkJ && sourceJ)) {
		AFB_ApiNotice(mixer->api, "%s missing 'uid|sink|source' error=%s zone=%s", __func__, wrap_json_get_error_string(error), json_object_get_string(zoneJ));
		goto fail_zone;
    }

    // make sure remain valid even when json object is removed
    zone->uid = strdup(zone->uid);
	if (!zone->uid) {
		SOFTMIXER_NOMEM(mixer->api);
		goto fail_zone;
	}

	AlsaPcmChannelT * channel = NULL;

    if (sinkJ) {

        switch (json_object_get_type(sinkJ)) {
            case json_type_object:

				channel = ProcessOneChannel(mixer, zone->uid, sinkJ);
				if (!channel)
					goto fail_uid;

				zone->nbSinks++;
				cds_list_add_tail(&channel->list, &zone->sinks.list);

                break;
            case json_type_array:
                count = json_object_array_length(sinkJ);

                for (int idx = 0; idx < count; idx++) {
                    json_object *subdevJ = json_object_array_get_idx(sinkJ, idx);

					channel = ProcessOneChannel(mixer, zone->uid, subdevJ);
					if (!channel)
						goto fail_uid;

					zone->nbSinks++;
					cds_list_add_tail(&channel->list, &zone->sinks.list);

                }
                break;
            default:
				AFB_ApiError(mixer->api, "%s: Mixer=%s Hal=%s zone=%s invalid mapping=%s", __func__, mixer->uid, uid, zone->uid, json_object_get_string(sinkJ));
				goto fail;
        }

    }

    if (sourceJ) {

        switch (json_object_get_type(sourceJ)) {
            case json_type_object:
				channel = ProcessOneChannel(mixer, zone->uid, sourceJ);
				if (!channel)
					goto fail_uid;
				zone->nbSources++;
				cds_list_add(&channel->list, &zone->sources.list);
                break;
            case json_type_array:
                count = json_object_array_length(sourceJ);

                for (int idx = 0; idx < count; idx++) {
                    json_object *subdevJ = json_object_array_get_idx(sourceJ, idx);
					channel = ProcessOneChannel(mixer, zone->uid, subdevJ);
					if (!zone)
						goto fail_uid;

					zone->nbSources++;
					cds_list_add(&channel->list, &zone->sources.list);
                }
                break;
            default:
				AFB_ApiError(mixer->api, "%s: Mixer=%s Hal=%s zone=%s mapping=%s",
							 __func__, mixer->uid, uid, zone->uid, json_object_get_string(sourceJ));
				goto fail;
        }
    }

	AFB_ApiDebug(mixer->api, "%s uid %s DONE", __func__, uid);
    return zone;

fail_uid:
	free((char*)zone->uid);
fail_zone:
	free(zone);
fail:
    return NULL;
}

static void zoneDestroy(SoftMixerT* mixer, void * arg) {
	AlsaSndZoneT * zone = (AlsaSndZoneT*) arg;

	AFB_ApiDebug(mixer->api, "%s... %s (%d sinks, %d sources)", __func__, zone->uid, zone->nbSinks, zone->nbSources);

	AlsaPcmChannelT * channel, *tmp;

	cds_list_for_each_entry_safe(channel, tmp, &zone->sinks.list, list) {
		cds_list_del(&channel->list);
		free(channel);
	}

	cds_list_del(&zone->list);
	mixer->nbZones--;

	free((char*) zone->uid);
	free(zone);
	AFB_ApiDebug(mixer->api, "%s... DONE !", __func__);
}

PUBLIC AlsaSndZoneT * zoneCreate(SoftMixerT* mixer, const char * uid, json_object * argsJ) {

	AlsaSndZoneT * zone = AttachOneZone(mixer, uid, argsJ);
	if (!zone)
		goto fail;

	// must be set now; AlsaCreateRoute needs it !
	mixer->nbZones++;
	cds_list_add_tail(&zone->list, &mixer->zones.list);
	AlsaMixerTransactionObjectAdd(mixer->transaction, zone, zoneDestroy);

	AlsaPcmCtlT *routeConfig = AlsaCreateRoute(mixer, zone, 0);
	if (!routeConfig) {
		AFB_ApiError(mixer->api,
					 "%s: Mixer=%s Hal=%s zone=%s Fail to attach PCM Route",
					 __func__, mixer->uid, uid, zone->uid);
		goto fail;
    }

	zone->isPcmPlug = routeConfig->isPcmPlug;
	zone->quirks    = routeConfig->quirks;

fail:
	return zone;
}

PUBLIC int ApiZoneAttach(SoftMixerT *mixer, AFB_ReqT request, const char * uid, json_object * argsJ) {

	AFB_ApiDebug(mixer->api, "%s uid %s", __func__, uid);

	if (mixer->nbZones >= mixer->max.zones) {
		AFB_ReqFailF(request, "too-small", "mixer=%s max zone=%d", mixer->uid, mixer->max.zones);
		goto OnErrorExit;
	}

    switch (json_object_get_type(argsJ)) {
		long count;

        case json_type_object: {
			AlsaSndZoneT * newZone = zoneCreate(mixer, uid, argsJ);
			if (!newZone) {
                AFB_ReqFailF(request, "bad-zone", "mixer=%s invalid zone= %s", mixer->uid, json_object_get_string(argsJ));
                goto OnErrorExit;
            }
            break;
        }
        case json_type_array:
            count = json_object_array_length(argsJ);
			if (count > (mixer->max.zones - mixer->nbZones)) {
                AFB_ReqFailF(request, "too-small", "mixer=%s max zone=%d", mixer->uid, mixer->max.zones);
                goto OnErrorExit;
            }

            for (int idx = 0; idx < count; idx++) {
                json_object *zoneJ = json_object_array_get_idx(argsJ, idx);
				AlsaSndZoneT * newZone = zoneCreate(mixer, uid, zoneJ);
				if (!newZone) {
                    AFB_ReqFailF(request, "bad-zone", "mixer=%s invalid zone= %s", mixer->uid, json_object_get_string(zoneJ));
                    goto OnErrorExit;
                }
            }
            break;
        default:
            AFB_ReqFailF(request, "invalid-syntax", "mixer=%s zones invalid argsJ= %s", mixer->uid, json_object_get_string(argsJ));
            goto OnErrorExit;
    }

	AFB_ApiDebug(mixer->api, "%s uid %s DONE", __func__, uid);
    return 0;

OnErrorExit:
    return -1;
}