diff --git a/README_ALSA.md b/README_ALSA.md index c9c529b8..b74d1f81 100644 --- a/README_ALSA.md +++ b/README_ALSA.md @@ -274,3 +274,29 @@ audio { mixer_device="hw:1" } ``` + +## Multiple devices + +If your machine has multiple physical devices like our Raspberry Pi example above (the DAC hat and the onboard headphone jack), we can make all these devices available to `forked-daapd` using seperate `alsa { .. }` sections. + +NB: When introducing `alsa { .. }` section(s) the ALSA specific configuration in the `audio { .. }` section will be ignored. + +For example: +``` +audio { + type = "alsa" +} + +alsa "dac" { + card="dac" + mixer="Analogue" + mixer_device="hw:1" +} + +alsa "headphones" { + card = "hw:0,0" + mixer = "PCM" + mixer_device = "hw:0" +} + +``` diff --git a/forked-daapd.conf.in b/forked-daapd.conf.in index 8749ded5..1e3a160b 100644 --- a/forked-daapd.conf.in +++ b/forked-daapd.conf.in @@ -216,6 +216,8 @@ audio { # socket. # server = "" + # ALSA settings can be definied in their own 'alsa { }' sections for + # multiple devices # Audio PCM device name for local audio output - ALSA only # card = "default" @@ -246,6 +248,25 @@ audio { # adjust_period_seconds = 100 } +# ALSA device settings +# Multiple sections can be defined for multiple ALSA devices on a host (i.e. a +# RPi with onboard headphones and DAC hat). ALSA may see these devices as hw:0 +# and hw:1 via 'aplay -l' (see README_ALSA) +# +# Overrides ALSA settings in 'audio' section +#alsa "alsa default" { + # Audio PCM device name for local audio output +# card = "default" + + # Mixer channel to use for volume control + # If not set, PCM will be used if available, otherwise Master. +# mixer = "" + + # Mixer device to use for volume control + # If not set, the value for "card" will be used. +# mixer_device = "" +#} + # Pipe output # Allows forked-daapd to output audio data to a named pipe #fifo { diff --git a/src/conffile.c b/src/conffile.c index 1677babf..a577f003 100644 --- a/src/conffile.c +++ b/src/conffile.c @@ -124,6 +124,17 @@ static cfg_opt_t sec_audio[] = CFG_END() }; + +/* local ALSA audio section structure */ +static cfg_opt_t sec_alsa[] = + { + CFG_STR("card", "default", CFGF_NONE), + CFG_STR("mixer", NULL, CFGF_NONE), + CFG_STR("mixer_device", NULL, CFGF_NONE), + CFG_INT("offset_ms", 0, CFGF_NONE), + CFG_END() + }; + /* AirPlay/ApEx device section structure */ static cfg_opt_t sec_airplay[] = { @@ -201,6 +212,7 @@ static cfg_opt_t toplvl_cfg[] = CFG_SEC("general", sec_general, CFGF_NONE), CFG_SEC("library", sec_library, CFGF_NONE), CFG_SEC("audio", sec_audio, CFGF_NONE), + CFG_SEC("alsa", sec_alsa, CFGF_MULTI | CFGF_TITLE), CFG_SEC("airplay", sec_airplay, CFGF_MULTI | CFGF_TITLE), CFG_SEC("chromecast", sec_chromecast, CFGF_MULTI | CFGF_TITLE), CFG_SEC("fifo", sec_fifo, CFGF_NONE), diff --git a/src/outputs/alsa.c b/src/outputs/alsa.c index 2615b2b4..4a1df9e7 100644 --- a/src/outputs/alsa.c +++ b/src/outputs/alsa.c @@ -974,10 +974,12 @@ alsa_session_make(struct output_device *device, int callback_id) CHECK_NULL(L_LAUDIO, as = calloc(1, sizeof(struct alsa_session))); + DPRINTF(E_DBG, L_LAUDIO, "alsa dev=%s type=%s callback=%d\n", device->name, device->type_name, callback_id); + as->device_id = device->id; as->callback_id = callback_id; - cfg_audio = cfg_getsec(cfg, "audio"); + cfg_audio = cfg_size(cfg, "alsa") == 0 ? cfg_getsec(cfg, "audio") : cfg_gettsec(cfg, "alsa", device->name); as->devname = cfg_getstr(cfg_audio, "card"); as->mixer_name = cfg_getstr(cfg_audio, "mixer"); @@ -1175,12 +1177,31 @@ alsa_write(struct output_buffer *obuf) } } +static void +alsa_device_add(cfg_t* cfg_audio, int id, const char* devname) +{ + struct output_device *device; + CHECK_NULL(L_LAUDIO, device = calloc(1, sizeof(struct output_device))); + + device->id = id; + device->name = strdup(devname); + device->type = OUTPUT_TYPE_ALSA; + device->type_name = outputs_name(device->type); + device->has_video = 0; + + DPRINTF(E_INFO, L_LAUDIO, "Adding ALSA device '%s' with name '%s'\n", cfg_getstr(cfg_audio, "card"), device->name); + + player_device_add(device); +} + static int alsa_init(void) { - struct output_device *device; cfg_t *cfg_audio; + cfg_t *cfg_alsasec; const char *type; + int i; + int alsa_cfg_secn; // Is ALSA enabled in config? cfg_audio = cfg_getsec(cfg, "audio"); @@ -1191,17 +1212,19 @@ alsa_init(void) alsa_sync_disable = cfg_getbool(cfg_audio, "sync_disable"); alsa_latency_history_size = cfg_getint(cfg_audio, "adjust_period_seconds"); - CHECK_NULL(L_LAUDIO, device = calloc(1, sizeof(struct output_device))); - - device->id = 0; - device->name = strdup(cfg_getstr(cfg_audio, "nickname")); - device->type = OUTPUT_TYPE_ALSA; - device->type_name = outputs_name(device->type); - device->has_video = 0; - - DPRINTF(E_INFO, L_LAUDIO, "Adding ALSA device '%s' with name '%s'\n", cfg_getstr(cfg_audio, "card"), device->name); - - player_device_add(device); + alsa_cfg_secn = cfg_size(cfg, "alsa"); + if (alsa_cfg_secn == 0) + { + alsa_device_add(cfg_audio, 0, cfg_getstr(cfg_audio, "nickname")); + } + else + { + for (i = 0; i < alsa_cfg_secn; ++i) + { + cfg_alsasec = cfg_getnsec(cfg, "alsa", i); + alsa_device_add(cfg_alsasec, i, cfg_alsasec->title); + } + } snd_lib_error_set_handler(logger_alsa);