diff options
-rw-r--r-- | doc/README-polyp | 41 | ||||
-rw-r--r-- | polyp/ctl_polyp.c | 264 |
2 files changed, 252 insertions, 53 deletions
diff --git a/doc/README-polyp b/doc/README-polyp new file mode 100644 index 0000000..de46c3d --- /dev/null +++ b/doc/README-polyp @@ -0,0 +1,41 @@ +Polypaudio <--> ALSA plugins +============================ + +This plugin allows any program that uses the ALSA API to access a Polypaudio +sound daemon. In other words, native ALSA applications can play and record +sound across a network. + +There are two plugins in the suite, one for PCM and one for mixer control. A +typical configuration will look like: + + pcm.polyp { + type polyp + } + + ctl.polyp { + type polyp + } + +Put the above in ~/.asoundrc, or /etc/asound.conf, and use "polyp" as device +in your ALSA applications. For example: + + % aplay -Dpolyp foo.wav + % amixer -Dpolyp + +Polypaudio will accept more or less any format you throw at it. So a plug +wrapper is unnecessary. Mixing is also handled so dmix will only cause a +performance hit without any gain. + +The plugins will respect your Polypaudio environment variables (like +POLYP_SERVER), but you can override these in ALSA's configuration files. + +Both plugins accept the "server" parameter, specifying which Polypaudio server +to contact. Both also accept the "device" parameter, which indicate which +source and sink to use. + +The mixer control plugin also accepts the parameters "source" and "sink" for +when you need to specify a sink/source combination with different names. If +you need to do this with PCM:s then specify two PCM:s with different "device". + +If you do not specify any source and/or sink, then the server's defaults will +be used. diff --git a/polyp/ctl_polyp.c b/polyp/ctl_polyp.c index 16043bf..aba002b 100644 --- a/polyp/ctl_polyp.c +++ b/polyp/ctl_polyp.c @@ -30,15 +30,23 @@ typedef struct snd_ctl_polyp { snd_polyp_t *p; - char *device; + char *source; + char *sink; - pa_cvolume volume; + pa_cvolume sink_volume; + pa_cvolume source_volume; + + int sink_muted; + int source_muted; int subscribed; int updated; } snd_ctl_polyp_t; -#define MIXER_NAME "Master" +#define SOURCE_VOL_NAME "Capture Volume" +#define SOURCE_MUTE_NAME "Capture Switch" +#define SINK_VOL_NAME "Master Playback Volume" +#define SINK_MUTE_NAME "Master Playback Switch" static void sink_info_cb(pa_context *c, const pa_sink_info *i, int is_last, void *userdata) { @@ -50,18 +58,52 @@ static void sink_info_cb(pa_context *c, const pa_sink_info *i, int is_last, void assert(ctl && i); - if (ctl->volume.channels == i->volume.channels) { - for (chan = 0;chan < ctl->volume.channels;chan++) - if (i->volume.values[chan] != ctl->volume.values[chan]) + if (ctl->sink_volume.channels == i->volume.channels) { + for (chan = 0;chan < ctl->sink_volume.channels;chan++) + if (i->volume.values[chan] != ctl->sink_volume.values[chan]) break; - if (chan == ctl->volume.channels) + if (chan == ctl->sink_volume.channels) return; ctl->updated = 1; } - memcpy(&ctl->volume, &i->volume, sizeof(pa_cvolume)); + memcpy(&ctl->sink_volume, &i->volume, sizeof(pa_cvolume)); + + if (!!ctl->sink_muted != !!i->mute) { + ctl->sink_muted = i->mute; + ctl->updated = 1; + } +} + +static void source_info_cb(pa_context *c, const pa_source_info *i, int is_last, void *userdata) +{ + snd_ctl_polyp_t *ctl = (snd_ctl_polyp_t*)userdata; + int chan; + + if (is_last) + return; + + assert(ctl && i); + + if (ctl->source_volume.channels == i->volume.channels) { + for (chan = 0;chan < ctl->source_volume.channels;chan++) + if (i->volume.values[chan] != ctl->source_volume.values[chan]) + break; + + if (chan == ctl->source_volume.channels) + return; + + ctl->updated = 1; + } + + memcpy(&ctl->source_volume, &i->volume, sizeof(pa_cvolume)); + + if (!!ctl->source_muted != !!i->mute) { + ctl->source_muted = i->mute; + ctl->updated = 1; + } } static void event_cb(pa_context *c, pa_subscription_event_type_t t, @@ -72,9 +114,13 @@ static void event_cb(pa_context *c, pa_subscription_event_type_t t, assert(ctl && ctl->p && ctl->p->context); - o = pa_context_get_sink_info_by_name(ctl->p->context, ctl->device, + o = pa_context_get_sink_info_by_name(ctl->p->context, ctl->sink, sink_info_cb, ctl); pa_operation_unref(o); + + o = pa_context_get_source_info_by_name(ctl->p->context, ctl->source, + source_info_cb, ctl); + pa_operation_unref(o); } static int polyp_update_volume(snd_ctl_polyp_t *ctl) @@ -84,26 +130,36 @@ static int polyp_update_volume(snd_ctl_polyp_t *ctl) assert(ctl && ctl->p && ctl->p->context); - o = pa_context_get_sink_info_by_name(ctl->p->context, ctl->device, + o = pa_context_get_sink_info_by_name(ctl->p->context, ctl->sink, sink_info_cb, ctl); err = polyp_wait_operation(ctl->p, o); pa_operation_unref(o); if (err < 0) return err; + o = pa_context_get_source_info_by_name(ctl->p->context, ctl->source, + source_info_cb, ctl); + err = polyp_wait_operation(ctl->p, o); + pa_operation_unref(o); + if (err < 0) + return err; + return 0; } static int polyp_elem_count(snd_ctl_ext_t *ext) { snd_ctl_polyp_t *ctl = ext->private_data; + int count = 0; assert(ctl); - if (ctl->device) - return 1; + if (ctl->source) + count += 2; + if (ctl->sink) + count += 2; - return 0; + return count; } static int polyp_elem_list(snd_ctl_ext_t *ext, unsigned int offset, @@ -114,7 +170,19 @@ static int polyp_elem_list(snd_ctl_ext_t *ext, unsigned int offset, assert(ctl); snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER); - snd_ctl_elem_id_set_name(id, MIXER_NAME); + + if (ctl->source) { + if (offset == 0) + snd_ctl_elem_id_set_name(id, SOURCE_VOL_NAME); + else if (offset == 1) + snd_ctl_elem_id_set_name(id, SOURCE_MUTE_NAME); + } else + offset += 2; + + if (offset == 2) + snd_ctl_elem_id_set_name(id, SINK_VOL_NAME); + else if (offset == 3) + snd_ctl_elem_id_set_name(id, SINK_MUTE_NAME); return 0; } @@ -126,8 +194,14 @@ static snd_ctl_ext_key_t polyp_find_elem(snd_ctl_ext_t *ext, name = snd_ctl_elem_id_get_name(id); - if (strcmp(name, MIXER_NAME) == 0) + if (strcmp(name, SOURCE_VOL_NAME) == 0) return 0; + if (strcmp(name, SOURCE_MUTE_NAME) == 0) + return 1; + if (strcmp(name, SINK_VOL_NAME) == 0) + return 2; + if (strcmp(name, SINK_MUTE_NAME) == 0) + return 3; return SND_CTL_EXT_KEY_NOT_FOUND; } @@ -140,7 +214,7 @@ static int polyp_get_attribute(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, assert(ctl && ctl->p); - if (key != 0) + if (key > 3) return -EINVAL; err = polyp_finish_poll(ctl->p); @@ -155,9 +229,19 @@ static int polyp_get_attribute(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, if (err < 0) return err; - *type = SND_CTL_ELEM_TYPE_INTEGER; + if (key & 1) + *type = SND_CTL_ELEM_TYPE_BOOLEAN; + else + *type = SND_CTL_ELEM_TYPE_INTEGER; + *acc = SND_CTL_EXT_ACCESS_READWRITE; - *count = ctl->volume.channels; + + if (key == 0) + *count = ctl->source_volume.channels; + else if (key == 2) + *count = ctl->sink_volume.channels; + else + *count = 1; return 0; } @@ -165,9 +249,6 @@ static int polyp_get_attribute(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, static int polyp_get_integer_info(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, long *imin, long *imax, long *istep) { - if (key != 0) - return -EINVAL; - *istep = 1; *imin = 0; *imax = PA_VOLUME_NORM; @@ -180,12 +261,10 @@ static int polyp_read_integer(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, { snd_ctl_polyp_t *ctl = ext->private_data; int err, i; + pa_cvolume *vol = NULL; assert(ctl && ctl->p); - if (key != 0) - return -EINVAL; - err = polyp_finish_poll(ctl->p); if (err < 0) return err; @@ -198,8 +277,27 @@ static int polyp_read_integer(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, if (err < 0) return err; - for (i = 0;i < ctl->volume.channels;i++) - value[i] = ctl->volume.values[i]; + switch (key) { + case 0: + vol = &ctl->source_volume; + break; + case 1: + *value = !ctl->source_muted; + break; + case 2: + vol = &ctl->sink_volume; + break; + case 3: + *value = !ctl->sink_muted; + break; + default: + return -EINVAL; + } + + if (vol) { + for (i = 0;i < vol->channels;i++) + value[i] = vol->values[i]; + } return 0; } @@ -209,14 +307,11 @@ static int polyp_write_integer(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, { snd_ctl_polyp_t *ctl = ext->private_data; int err, i; - pa_cvolume vol; pa_operation *o; + pa_cvolume *vol = NULL; assert(ctl && ctl->p && ctl->p->context); - if (key != 0) - return -EINVAL; - err = polyp_finish_poll(ctl->p); if (err < 0) return err; @@ -229,21 +324,52 @@ static int polyp_write_integer(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, if (err < 0) return err; - for (i = 0;i < ctl->volume.channels;i++) - if (value[i] != ctl->volume.values[i]) - break; - - if (i == ctl->volume.channels) - return 0; - - memset(&vol, 0, sizeof(pa_cvolume)); + switch (key) { + case 0: + vol = &ctl->source_volume; + break; + case 1: + if (!!ctl->source_muted == !*value) + return 0; + ctl->source_muted = !*value; + break; + case 2: + vol = &ctl->sink_volume; + break; + case 3: + if (!!ctl->sink_muted == !*value) + return 0; + ctl->sink_muted = !*value; + break; + default: + return -EINVAL; + } - vol.channels = ctl->volume.channels; - for (i = 0;i < vol.channels;i++) - vol.values[i] = value[i]; + if (vol) { + for (i = 0;i < vol->channels;i++) + if (value[i] != vol->values[i]) + break; - o = pa_context_set_sink_volume_by_name(ctl->p->context, ctl->device, &vol, - NULL, NULL); + if (i == vol->channels) + return 0; + + for (i = 0;i < vol->channels;i++) + vol->values[i] = value[i]; + + if (key == 0) + o = pa_context_set_source_volume_by_name(ctl->p->context, + ctl->source, vol, NULL, NULL); + else + o = pa_context_set_sink_volume_by_name(ctl->p->context, + ctl->sink, vol, NULL, NULL); + } else { + if (key == 1) + o = pa_context_set_source_mute_by_name(ctl->p->context, + ctl->source, ctl->source_muted, NULL, NULL); + else + o = pa_context_set_sink_mute_by_name(ctl->p->context, + ctl->sink, ctl->sink_muted, NULL, NULL); + } err = polyp_wait_operation(ctl->p, o); pa_operation_unref(o); @@ -325,8 +451,10 @@ static void polyp_close(snd_ctl_ext_t *ext) if (ctl->p) polyp_free(ctl->p); - if (ctl->device) - free(ctl->device); + if (ctl->source) + free(ctl->source); + if (ctl->sink) + free(ctl->sink); free(ctl); } @@ -351,9 +479,12 @@ static void server_info_cb(pa_context *c, const pa_server_info*i, void *userdata { snd_ctl_polyp_t *ctl = (snd_ctl_polyp_t*)userdata; - assert(ctl && i && i->default_sink_name); + assert(ctl && i); - ctl->device = strdup(i->default_sink_name); + if (i->default_source_name && !ctl->source) + ctl->source = strdup(i->default_source_name); + if (i->default_sink_name && !ctl->sink) + ctl->sink = strdup(i->default_sink_name); } SND_CTL_PLUGIN_DEFINE_FUNC(polyp) @@ -361,6 +492,8 @@ SND_CTL_PLUGIN_DEFINE_FUNC(polyp) snd_config_iterator_t i, next; const char *server = NULL; const char *device = NULL; + const char *source = NULL; + const char *sink = NULL; int err; snd_ctl_polyp_t *ctl; pa_operation *o; @@ -386,6 +519,20 @@ SND_CTL_PLUGIN_DEFINE_FUNC(polyp) } continue; } + if (strcmp(id, "source") == 0) { + if (snd_config_get_string(n, &source) < 0) { + SNDERR("Invalid type for %s", id); + return -EINVAL; + } + continue; + } + if (strcmp(id, "sink") == 0) { + if (snd_config_get_string(n, &sink) < 0) { + SNDERR("Invalid type for %s", id); + return -EINVAL; + } + continue; + } SNDERR("Unknown field %s", id); return -EINVAL; } @@ -403,9 +550,17 @@ SND_CTL_PLUGIN_DEFINE_FUNC(polyp) if (err < 0) goto error; - if (device) - ctl->device = strdup(device); - else { + if (source) + ctl->source = strdup(source); + else if (device) + ctl->source = strdup(device); + + if (sink) + ctl->sink = strdup(sink); + else if (device) + ctl->sink = strdup(device); + + if (!ctl->source || !ctl->sink) { o = pa_context_get_server_info(ctl->p->context, server_info_cb, ctl); err = polyp_wait_operation(ctl->p, o); pa_operation_unref(o); @@ -415,7 +570,8 @@ SND_CTL_PLUGIN_DEFINE_FUNC(polyp) pa_context_set_subscribe_callback(ctl->p->context, event_cb, ctl); - o = pa_context_subscribe(ctl->p->context, PA_SUBSCRIPTION_MASK_SINK, NULL, NULL); + o = pa_context_subscribe(ctl->p->context, + PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE, NULL, NULL); err = polyp_wait_operation(ctl->p, o); pa_operation_unref(o); if (err < 0) @@ -441,8 +597,10 @@ SND_CTL_PLUGIN_DEFINE_FUNC(polyp) return 0; error: - if (ctl->device) - free(ctl->device); + if (ctl->source) + free(ctl->source); + if (ctl->sink) + free(ctl->sink); if (ctl->p) polyp_free(ctl->p); |