[Spice-devel] [vdagent-linux PATCH 1/2] audio: add functions to set volume/mute with pulse
Victor Toso
victortoso at redhat.com
Wed Mar 18 10:16:11 PDT 2015
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
More information about the Spice-devel
mailing list