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. --- 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@xxxxxxxxxxxxxxxxxxxxx http://lists.freedesktop.org/mailman/listinfo/spice-devel