mirror of
https://github.com/owntone/owntone-server.git
synced 2025-01-24 05:03:17 -05:00
[alsa] Changes to commit 0cb4e0b: multiple devices
This commit is contained in:
parent
0cb4e0b862
commit
4e1e5efedc
@ -219,6 +219,9 @@ In the config file, you can select ALSA for local audio. This is the default.
|
||||
When using ALSA, the server will try to syncronize playback with AirPlay. You
|
||||
can adjust the syncronization in the config file.
|
||||
|
||||
For most setups the default values in the config file should work. If they
|
||||
don't, there is help here: [README_ALSA.md](https://github.com/ejurgensen/forked-daapd/blob/master/README_ALSA.md)
|
||||
|
||||
|
||||
## Local audio, Bluetooth and more through Pulseaudio
|
||||
|
||||
|
@ -144,7 +144,7 @@ Simple mixer control 'Analogue Playback Boost',0
|
||||
...
|
||||
```
|
||||
|
||||
This card has multiple controls but we want the first mixer listed with a `pvolume` capability - in this case that mixer value required for the server configuration is called `Anaolgue`.
|
||||
This card has multiple controls but we want the first mixer listed with a `pvolume` capability - in this case that mixer value required for the server configuration is called `Analogue`.
|
||||
|
||||
For the server configuration, we will use:
|
||||
```
|
||||
@ -163,7 +163,7 @@ This is the name of the underlying physical device used for the mixer - it is ty
|
||||
|
||||
## Handling Devices that cannot concurrently play multiple audio streams
|
||||
|
||||
Some devices such as various RPI DAC boards (IQaudio DAC, Allo Boss DAC...) can not have multiple streams openned at the same time/cannot play multiple sound files at the same time: this results in `Device or resource busy` errors. You can confirm if your sound card has this problem by using the example below once have determined the names/cards information as above.
|
||||
Some devices such as various RPI DAC boards (IQaudio DAC, Allo Boss DAC...) cannot have multiple streams openned at the same time/cannot play multiple sound files at the same time. This results in `Device or resource busy` errors. You can confirm if your sound card has this problem by using the example below once have determined the names/cards information as above.
|
||||
|
||||
Using our `hw:1` device we try:
|
||||
|
||||
@ -203,7 +203,7 @@ $ aplay -v -Dhw:1 /tmp/sine441.wav
|
||||
aplay: main:788: audio open error: Device or resource busy
|
||||
```
|
||||
|
||||
In this instance this device can not open multiple streams - `forked-daapd` can handle this situation transparently with some audio being truncated from the end of the current file as the server prepares to play the following track. If this handling is causing you problems you may wish to use [ALSA's `dmix` functionally](https://www.alsa-project.org/main/index.php/Asoundrc#Software_mixing) which provides a software mixing module. We will need to define a `dmix` component and configure the server to use that as it's sound card.
|
||||
In this instance this device cannot open multiple streams - `forked-daapd` can handle this situation transparently with some audio being truncated from the end of the current file as the server prepares to play the following track. If this handling is causing you problems you may wish to use [ALSA's `dmix` functionally](https://www.alsa-project.org/main/index.php/Asoundrc#Software_mixing) which provides a software mixing module. We will need to define a `dmix` component and configure the server to use that as it's sound card.
|
||||
|
||||
The downside to the `dmix` approach will be the need to fix a samplerate (48000 being the default) for this software mixing module meaning any files that have a mismatched samplerate will be resampled.
|
||||
|
||||
@ -288,13 +288,12 @@ audio {
|
||||
}
|
||||
|
||||
alsa "dac" {
|
||||
card="dac"
|
||||
mixer="Analogue"
|
||||
mixer_device="hw:1"
|
||||
}
|
||||
|
||||
alsa "headphones" {
|
||||
card = "hw:0,0"
|
||||
alsa "hw:0,0" {
|
||||
nickname = "headphones"
|
||||
mixer = "PCM"
|
||||
mixer_device = "hw:0"
|
||||
}
|
||||
|
@ -216,8 +216,6 @@ 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"
|
||||
|
||||
@ -249,21 +247,21 @@ audio {
|
||||
}
|
||||
|
||||
# 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"
|
||||
# If you have multiple ALSA devices you can configure them individually via
|
||||
# sections like the below. Make sure to set the "card name" correctly. See the
|
||||
# README about ALSA for details. Note that these settings will override the ALSA
|
||||
# settings in the "audio" section above.
|
||||
#alsa "card name" {
|
||||
# Name - used in the speaker list in Remote
|
||||
# If not set, the card name will be used
|
||||
# nickname = "Computer"
|
||||
|
||||
# Mixer channel to use for volume control
|
||||
# If not set, PCM will be used if available, otherwise Master.
|
||||
# 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.
|
||||
# If not set, the card name will be used
|
||||
# mixer_device = ""
|
||||
#}
|
||||
|
||||
|
@ -128,7 +128,7 @@ static cfg_opt_t sec_audio[] =
|
||||
/* local ALSA audio section structure */
|
||||
static cfg_opt_t sec_alsa[] =
|
||||
{
|
||||
CFG_STR("card", "default", CFGF_NONE),
|
||||
CFG_STR("nickname", NULL, CFGF_NONE),
|
||||
CFG_STR("mixer", NULL, CFGF_NONE),
|
||||
CFG_STR("mixer_device", NULL, CFGF_NONE),
|
||||
CFG_INT("offset_ms", 0, CFGF_NONE),
|
||||
|
@ -111,6 +111,15 @@ struct alsa_playback_session
|
||||
struct alsa_playback_session *next;
|
||||
};
|
||||
|
||||
// Info about the device, which is not required by the player, only internally
|
||||
struct alsa_extra
|
||||
{
|
||||
const char *card_name;
|
||||
const char *mixer_name;
|
||||
const char *mixer_device_name;
|
||||
int offset_ms;
|
||||
};
|
||||
|
||||
struct alsa_session
|
||||
{
|
||||
enum output_device_state state;
|
||||
@ -119,7 +128,6 @@ struct alsa_session
|
||||
int callback_id;
|
||||
|
||||
const char *devname;
|
||||
const char *card_name;
|
||||
const char *mixer_name;
|
||||
const char *mixer_device_name;
|
||||
|
||||
@ -969,30 +977,20 @@ static struct alsa_session *
|
||||
alsa_session_make(struct output_device *device, int callback_id)
|
||||
{
|
||||
struct alsa_session *as;
|
||||
cfg_t *cfg_audio;
|
||||
struct alsa_extra *ae;
|
||||
int ret;
|
||||
|
||||
CHECK_NULL(L_LAUDIO, as = calloc(1, sizeof(struct alsa_session)));
|
||||
ae = device->extra_device_info;
|
||||
|
||||
DPRINTF(E_DBG, L_LAUDIO, "alsa dev=%s type=%s callback=%d\n", device->name, device->type_name, callback_id);
|
||||
CHECK_NULL(L_LAUDIO, as = calloc(1, sizeof(struct alsa_session)));
|
||||
|
||||
as->device_id = device->id;
|
||||
as->callback_id = callback_id;
|
||||
|
||||
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");
|
||||
as->mixer_device_name = cfg_getstr(cfg_audio, "mixer_device");
|
||||
if (!as->mixer_device_name || strlen(as->mixer_device_name) == 0)
|
||||
as->mixer_device_name = cfg_getstr(cfg_audio, "card");
|
||||
|
||||
as->offset_ms = cfg_getint(cfg_audio, "offset_ms");
|
||||
if (abs(as->offset_ms) > 1000)
|
||||
{
|
||||
DPRINTF(E_LOG, L_LAUDIO, "The ALSA offset_ms (%d) set in the configuration is out of bounds\n", as->offset_ms);
|
||||
as->offset_ms = 1000 * (as->offset_ms/abs(as->offset_ms));
|
||||
}
|
||||
as->devname = ae->card_name;
|
||||
as->mixer_name = ae->mixer_name;
|
||||
as->mixer_device_name = ae->mixer_device_name;
|
||||
as->offset_ms = ae->offset_ms;
|
||||
|
||||
ret = mixer_open(&as->mixer, as->mixer_device_name, as->mixer_name);
|
||||
if (ret < 0)
|
||||
@ -1117,6 +1115,14 @@ alsa_device_cb_set(struct output_device *device, int callback_id)
|
||||
as->callback_id = callback_id;
|
||||
}
|
||||
|
||||
static void
|
||||
alsa_device_free_extra(struct output_device *device)
|
||||
{
|
||||
struct alsa_extra *ae = device->extra_device_info;
|
||||
|
||||
free(ae);
|
||||
}
|
||||
|
||||
static void
|
||||
alsa_write(struct output_buffer *obuf)
|
||||
{
|
||||
@ -1178,20 +1184,44 @@ alsa_write(struct output_buffer *obuf)
|
||||
}
|
||||
|
||||
static void
|
||||
alsa_device_add(cfg_t* cfg_audio, int id, const char* devname)
|
||||
alsa_device_add(cfg_t* cfg_audio, int id)
|
||||
{
|
||||
struct output_device *device;
|
||||
struct alsa_extra *ae;
|
||||
int ret;
|
||||
|
||||
CHECK_NULL(L_LAUDIO, device = calloc(1, sizeof(struct output_device)));
|
||||
CHECK_NULL(L_LAUDIO, ae = calloc(1, sizeof(struct alsa_extra)));
|
||||
|
||||
device->id = id;
|
||||
device->name = strdup(devname);
|
||||
device->name = strdup(cfg_getstr(cfg_audio, "nickname"));
|
||||
device->type = OUTPUT_TYPE_ALSA;
|
||||
device->type_name = outputs_name(device->type);
|
||||
device->has_video = 0;
|
||||
device->extra_device_info = ae;
|
||||
|
||||
DPRINTF(E_INFO, L_LAUDIO, "Adding ALSA device '%s' with name '%s'\n", cfg_getstr(cfg_audio, "card"), device->name);
|
||||
// The audio section will have no title, so there we get the value from the
|
||||
// "card" option
|
||||
ae->card_name = cfg_title(cfg_audio);
|
||||
if (!ae->card_name)
|
||||
ae->card_name = cfg_getstr(cfg_audio, "card");
|
||||
|
||||
player_device_add(device);
|
||||
ae->mixer_name = cfg_getstr(cfg_audio, "mixer");
|
||||
ae->mixer_device_name = cfg_getstr(cfg_audio, "mixer_device_name");
|
||||
if (!ae->mixer_device_name || strlen(ae->mixer_device_name) == 0)
|
||||
ae->mixer_device_name = ae->card_name;
|
||||
|
||||
ae->offset_ms = cfg_getint(cfg_audio, "offset_ms");
|
||||
if (abs(ae->offset_ms) > 1000)
|
||||
{
|
||||
DPRINTF(E_LOG, L_LAUDIO, "The ALSA offset_ms (%d) set in the configuration is out of bounds\n", ae->offset_ms);
|
||||
ae->offset_ms = 1000 * (ae->offset_ms/abs(ae->offset_ms));
|
||||
}
|
||||
|
||||
DPRINTF(E_INFO, L_LAUDIO, "Adding ALSA device '%s' with name '%s'\n", ae->card_name, device->name);
|
||||
|
||||
ret = player_device_add(device);
|
||||
if (ret < 0)
|
||||
outputs_device_free(device);
|
||||
}
|
||||
|
||||
static int
|
||||
@ -1215,14 +1245,14 @@ alsa_init(void)
|
||||
alsa_cfg_secn = cfg_size(cfg, "alsa");
|
||||
if (alsa_cfg_secn == 0)
|
||||
{
|
||||
alsa_device_add(cfg_audio, 0, cfg_getstr(cfg_audio, "nickname"));
|
||||
alsa_device_add(cfg_audio, 0);
|
||||
}
|
||||
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);
|
||||
alsa_device_add(cfg_alsasec, i);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1259,5 +1289,6 @@ struct output_definition output_alsa =
|
||||
.device_probe = alsa_device_probe,
|
||||
.device_volume_set = alsa_device_volume_set,
|
||||
.device_cb_set = alsa_device_cb_set,
|
||||
.device_free_extra = alsa_device_free_extra,
|
||||
.write = alsa_write,
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user