[Spice-devel] [vdagent-linux PATCH 1/2] audio: add functions to set volume/mute with pulse

Victor Toso victortoso at redhat.com
Thu Mar 19 04:15:49 PDT 2015


Hey,

On Wed, Mar 18, 2015 at 01:27:54PM -0400, Marc-André Lureau wrote:
> Hi
> 
> ----- Original Message -----
> > This patch includes the vdagentd-audio.[ch] files in order to
> > communicate with backend audio server.
> > 
> > The two functions provide a way to set volume and mute in the guest by
> > creating a temporary mainloop and requesting the changes to pulse
> > server.
> 
> Not sure PulseAudio API is best choice for modifying device volume, I would have used ALSA mixer API here (PulseAudio is fine synchronizing with ALSA changes)

I'll give it a try this afternoon, indeed it looks way simpler. Thanks
for the review/suggestion.

> > ---
> >  Makefile.am          |   6 +-
> >  configure.ac         |   1 +
> >  src/vdagentd-audio.c | 250
> >  +++++++++++++++++++++++++++++++++++++++++++++++++++
> >  src/vdagentd-audio.h |  27 ++++++
> >  4 files changed, 282 insertions(+), 2 deletions(-)
> >  create mode 100644 src/vdagentd-audio.c
> >  create mode 100644 src/vdagentd-audio.h
> > 
> > diff --git a/Makefile.am b/Makefile.am
> > index 510f460..abd5b59 100644
> > --- a/Makefile.am
> > +++ b/Makefile.am
> > @@ -9,11 +9,12 @@ src_spice_vdagent_LDADD = $(X_LIBS) $(SPICE_LIBS)
> > $(GLIB2_LIBS)
> >  src_spice_vdagent_SOURCES = src/vdagent.c src/vdagent-x11.c
> >  src/vdagent-x11-randr.c src/vdagent-file-xfers.c src/udscs.c
> >  
> >  src_spice_vdagentd_CFLAGS = $(DBUS_CFLAGS) $(LIBSYSTEMD_LOGIN_CFLAGS) \
> > -  $(PCIACCESS_CFLAGS) $(SPICE_CFLAGS) $(GLIB2_CFLAGS) $(PIE_CFLAGS)
> > +  $(PCIACCESS_CFLAGS) $(SPICE_CFLAGS) $(GLIB2_CFLAGS) $(PIE_CFLAGS)
> > $(PULSE_CFLAGS)
> >  src_spice_vdagentd_LDADD = $(DBUS_LIBS) $(LIBSYSTEMD_LOGIN_LIBS) \
> > -  $(PCIACCESS_LIBS) $(SPICE_LIBS) $(GLIB2_LIBS) $(PIE_LDFLAGS)
> > +  $(PCIACCESS_LIBS) $(SPICE_LIBS) $(GLIB2_LIBS) $(PIE_LDFLAGS) $(PULSE_LIBS)
> >  src_spice_vdagentd_SOURCES = src/vdagentd.c \
> >                               src/vdagentd-uinput.c \
> > +                             src/vdagentd-audio.c \
> >                               src/vdagentd-xorg-conf.c \
> >                               src/vdagent-virtio-port.c \
> >                               src/udscs.c
> > @@ -37,6 +38,7 @@ noinst_HEADERS = src/glib-compat.h \
> >                   src/vdagentd-proto.h \
> >                   src/vdagentd-proto-strings.h \
> >                   src/vdagentd-uinput.h \
> > +                 src/vdagentd-audio.h \
> >                   src/vdagentd-xorg-conf.h
> >  
> >  xdgautostartdir = $(sysconfdir)/xdg/autostart
> > diff --git a/configure.ac b/configure.ac
> > index 79905a8..6eb3e44 100644
> > --- a/configure.ac
> > +++ b/configure.ac
> > @@ -79,6 +79,7 @@ AC_ARG_ENABLE([static-uinput],
> >  PKG_CHECK_MODULES([GLIB2], [glib-2.0 >= 2.12])
> >  PKG_CHECK_MODULES(X, [xfixes xrandr >= 1.3 xinerama x11])
> >  PKG_CHECK_MODULES(SPICE, [spice-protocol >= 0.12.5])
> > +PKG_CHECK_MODULES(PULSE, [libpulse])
> >  
> >  if test "$with_session_info" = "auto" || test "$with_session_info" =
> >  "systemd"; then
> >      PKG_CHECK_MODULES([LIBSYSTEMD_LOGIN],
> > diff --git a/src/vdagentd-audio.c b/src/vdagentd-audio.c
> > new file mode 100644
> > index 0000000..ca2a174
> > --- /dev/null
> > +++ b/src/vdagentd-audio.c
> > @@ -0,0 +1,250 @@
> > +/*  vdagentd-audio.c vdagentd audio handling code
> > +
> > +    Copyright 2015 Red Hat, Inc.
> > +
> > +    This program is free software: you can redistribute it and/or modify
> > +    it under the terms of the GNU General Public License as published by
> > +    the Free Software Foundation, either version 3 of the License, or
> > +    (at your option) any later version.
> > +
> > +    This program is distributed in the hope that it will be useful,
> > +    but WITHOUT ANY WARRANTY; without even the implied warranty of
> > +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> > +    GNU General Public License for more details.
> > +
> > +    You should have received a copy of the GNU General Public License
> > +    along with this program.  If not, see <http://www.gnu.org/licenses/>.
> > +*/
> > +
> > +#ifdef HAVE_CONFIG_H
> > +#include <config.h>
> > +#endif
> > +
> > +#include <syslog.h>
> > +#include <pulse/pulseaudio.h>
> > +#include <stdbool.h>
> > +#include "vdagentd-audio.h"
> > +
> > +typedef enum {
> > +    OP_PLAYBACK_SYNC,
> > +    OP_RECORD_SYNC,
> > +} operation;
> > +
> > +struct OperationSpec {
> > +    pa_mainloop *mainloop;
> > +    pa_context *context;
> > +    operation op_type;
> > +    bool mute;
> > +    uint16_t nchannels;
> > +    uint16_t *volume;
> > +
> > +    /* Only quit mainloop after all callbacks are called */
> > +    int num_cb_operations;
> > +};
> > +
> > +#define VDAGENT_APP "vdagentd-audio"
> > +
> > +static void context_success_cb (pa_context *c, int success, void *userdata)
> > +{
> > +    struct OperationSpec *os = userdata;
> > +    int retval = (success) ? 0 : -1;
> > +
> > +    os->num_cb_operations--;
> > +    syslog(LOG_DEBUG, "Num callback left: %d", os->num_cb_operations);
> > +
> > +    if (!success) {
> > +        int err = pa_context_errno(os->context);
> > +        syslog(LOG_WARNING, "Operation failed: %s", pa_strerror(err));
> > +    }
> > +
> > +    if (os->num_cb_operations == 0)
> > +        pa_mainloop_quit (os->mainloop, retval);
> > +}
> > +
> > +static void vdagent_handle_operation (struct OperationSpec *os,
> > +                                      uint8_t nchannels,
> > +                                      const char *sink,
> > +                                      const char *source)
> > +{
> > +    int i;
> > +    pa_operation *op;
> > +    pa_cvolume vol;
> > +
> > +    if (nchannels != os->nchannels)
> > +        syslog (LOG_WARNING,
> > +                "Number of channels in the guest (%u) and client (%u)
> > differs",
> > +                nchannels, os->nchannels);
> > +
> > +    pa_cvolume_init(&vol);
> > +    vol.channels = nchannels;
> > +    for (i = 0; i < nchannels; i++) {
> > +        vol.values[i] = os->volume[i];
> > +        syslog(LOG_DEBUG, "Setting channel %d to volume %d (%0.2f)",
> > +               i, os->volume[i], (float) (100 * os->volume[i]) /
> > UINT16_MAX);
> > +    }
> > +
> > +    switch (os->op_type) {
> > +    case OP_PLAYBACK_SYNC:
> > +        os->num_cb_operations = 2;
> > +        op = pa_context_set_sink_volume_by_name(os->context, sink, &vol,
> > +                                                context_success_cb, os);
> > +        if (!op) {
> > +            int err = pa_context_errno(os->context);
> > +            syslog(LOG_WARNING, "Fail to set sink volume: %s",
> > pa_strerror(err));
> > +            pa_mainloop_quit (os->mainloop, -1);
> > +        }
> > +        pa_operation_unref(op);
> > +
> > +        op = pa_context_set_sink_mute_by_name(os->context, sink, os->mute,
> > +                                              context_success_cb, os);
> > +        if (!op) {
> > +            int err = pa_context_errno(os->context);
> > +            syslog(LOG_WARNING, "Fail to set sink mute: %s",
> > pa_strerror(err));
> > +            pa_mainloop_quit (os->mainloop, -1);
> > +        }
> > +        pa_operation_unref(op);
> > +        break;
> > +
> > +    case OP_RECORD_SYNC:
> > +        os->num_cb_operations = 2;
> > +        op = pa_context_set_source_volume_by_name(os->context, source, &vol,
> > +                                                  context_success_cb, os);
> > +        if (!op) {
> > +            int err = pa_context_errno(os->context);
> > +            syslog(LOG_WARNING, "Fail to set source volume: %s",
> > pa_strerror(err));
> > +            pa_mainloop_quit (os->mainloop, -1);
> > +        }
> > +        pa_operation_unref(op);
> > +
> > +        op = pa_context_set_source_mute_by_name(os->context, source,
> > os->mute,
> > +                                                context_success_cb, os);
> > +        if (!op) {
> > +            int err = pa_context_errno(os->context);
> > +            syslog(LOG_WARNING, "Fail to set source mute: %s",
> > pa_strerror(err));
> > +            pa_mainloop_quit (os->mainloop, -1);
> > +        }
> > +        pa_operation_unref(op);
> > +        break;
> > +
> > +    default:
> > +        syslog(LOG_WARNING, "Unknown operation type %d, ignoring",
> > os->op_type);
> > +    }
> > +}
> > +
> > +static void vdagent_server_info_cb (pa_context *context,
> > +                                    const pa_server_info *info,
> > +                                    void *userdata)
> > +{
> > +    vdagent_handle_operation (userdata,
> > +                              info->channel_map.channels,
> > +                              info->default_sink_name,
> > +                              info->default_source_name);
> > +}
> > +
> > +static void vdagent_context_state_cb(pa_context *context, void *userdata)
> > +{
> > +    struct OperationSpec *os = userdata;
> > +    switch (pa_context_get_state(context)) {
> > +    case PA_CONTEXT_UNCONNECTED:
> > +    case PA_CONTEXT_CONNECTING:
> > +    case PA_CONTEXT_AUTHORIZING:
> > +    case PA_CONTEXT_SETTING_NAME:
> > +        break;
> > +
> > +    case PA_CONTEXT_FAILED:
> > +        syslog(LOG_WARNING, "The connection failed or was disconnected");
> > +        goto fail;
> > +        break;
> > +
> > +    case PA_CONTEXT_TERMINATED:
> > +        syslog(LOG_WARNING, "The connection was terminated cleanly");
> > +        goto fail;
> > +        break;
> > +
> > +    case PA_CONTEXT_READY: {
> > +        pa_operation *op;
> > +        op = pa_context_get_server_info(context, vdagent_server_info_cb,
> > userdata);
> > +        if (!op) {
> > +            int err = pa_context_errno(context);
> > +            syslog(LOG_WARNING, "Fail to get server info: %s",
> > pa_strerror(err));
> > +            goto fail;
> > +        }
> > +        pa_operation_unref(op);
> > +        break;
> > +    }
> > +    default:
> > +       break;
> > +    }
> > +    return;
> > +fail:
> > +    if (os->mainloop != NULL) {
> > +        syslog(LOG_WARNING, "Cancel operation and quit pulse mainloop");
> > +        pa_mainloop_quit (os->mainloop, -1);
> > +    }
> > +}
> > +
> > +static bool vdagent_pulse_run (struct OperationSpec *os)
> > +{
> > +    pa_mainloop *loop;
> > +    pa_mainloop_api *api;
> > +    pa_context *context;
> > +    int retval;
> > +
> > +    os->mainloop = NULL;
> > +    loop = pa_mainloop_new();
> > +    if (loop == NULL) {
> > +        syslog(LOG_WARNING, "Fail to alloc pa_mainloop");
> > +        return false;
> > +    }
> > +
> > +    api = pa_mainloop_get_api(loop);
> > +    if (api == NULL) {
> > +        syslog(LOG_WARNING, "Fail to get mainloop_api");
> > +        return false;
> > +    }
> > +
> > +    context = pa_context_new(api, VDAGENT_APP);
> > +    if (context == NULL) {
> > +        syslog(LOG_WARNING, "Fail to alloc pa_context");
> > +        return false;
> > +    }
> > +
> > +    pa_context_set_state_callback(context, vdagent_context_state_cb, os);
> > +    if (pa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0) {
> > +        syslog(LOG_WARNING, "Fail to connect to default server");
> > +        pa_context_unref(context);
> > +        return false;
> > +    }
> > +
> > +    os->mainloop = loop;
> > +    os->context = context;
> > +    pa_mainloop_run(loop, &retval);
> > +    pa_context_unref(context);
> > +    return (retval == 0) ? true : false;
> > +}
> > +
> > +void vdagent_audio_playback_sync(uint8_t mute, uint8_t nchannels, uint16_t
> > *volume)
> > +{
> > +    struct OperationSpec os;
> > +    syslog(LOG_DEBUG, "%s mute=%s nchannels=%u", __func__,
> > +           (mute) ? "yes" : "no", nchannels);
> > +    os.op_type = OP_PLAYBACK_SYNC;
> > +    os.mute = (mute) ? true : false;
> > +    os.nchannels = nchannels;
> > +    os.volume = volume;
> > +    if (vdagent_pulse_run (&os) == false)
> > +        syslog(LOG_WARNING, "Fail to sync playback volume");
> > +}
> > +
> > +void vdagent_audio_record_sync(uint8_t mute, uint8_t nchannels, uint16_t
> > *volume)
> > +{
> > +    struct OperationSpec os;
> > +    syslog(LOG_DEBUG, "%s mute=%s nchannels=%u", __func__,
> > +           (mute) ? "yes" : "no", nchannels);
> > +    os.op_type = OP_RECORD_SYNC;
> > +    os.mute = (mute) ? true : false;
> > +    os.nchannels = nchannels;
> > +    os.volume = volume;
> > +    if (vdagent_pulse_run (&os) == false)
> > +        syslog(LOG_WARNING, "Fail to sync record volume");
> > +}
> > diff --git a/src/vdagentd-audio.h b/src/vdagentd-audio.h
> > new file mode 100644
> > index 0000000..950827a
> > --- /dev/null
> > +++ b/src/vdagentd-audio.h
> > @@ -0,0 +1,27 @@
> > +/*  vdagentd-audio.h vdagentd audio handling header
> > +
> > +    Copyright 2015 Red Hat, Inc.
> > +
> > +    This program is free software: you can redistribute it and/or modify
> > +    it under the terms of the GNU General Public License as published by
> > +    the Free Software Foundation, either version 3 of the License, or
> > +    (at your option) any later version.
> > +
> > +    This program is distributed in the hope that it will be useful,
> > +    but WITHOUT ANY WARRANTY; without even the implied warranty of
> > +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> > +    GNU General Public License for more details.
> > +
> > +    You should have received a copy of the GNU General Public License
> > +    along with this program.  If not, see <http://www.gnu.org/licenses/>.
> > +*/
> > +#ifndef __VDAGENTD_AUDIO_H
> > +#define __VDAGENTD_AUDIO_H
> > +
> > +#include <stdio.h>
> > +#include <stdint.h>
> > +
> > +void vdagent_audio_playback_sync(uint8_t mute, uint8_t nchannels, uint16_t
> > *volume);
> > +void vdagent_audio_record_sync(uint8_t mute, uint8_t nchannels, uint16_t
> > *volume);
> > +
> > +#endif
> > --
> > 2.1.0
> > 
> > _______________________________________________
> > Spice-devel mailing list
> > Spice-devel at lists.freedesktop.org
> > http://lists.freedesktop.org/mailman/listinfo/spice-devel
> > 


More information about the Spice-devel mailing list