summaryrefslogtreecommitdiff
path: root/gtk/spice-pulse.c
diff options
context:
space:
mode:
Diffstat (limited to 'gtk/spice-pulse.c')
-rw-r--r--gtk/spice-pulse.c187
1 files changed, 187 insertions, 0 deletions
diff --git a/gtk/spice-pulse.c b/gtk/spice-pulse.c
new file mode 100644
index 0000000..e158254
--- /dev/null
+++ b/gtk/spice-pulse.c
@@ -0,0 +1,187 @@
+#include "spice-pulse.h"
+
+#include <assert.h>
+
+#include <pulse/glib-mainloop.h>
+#include <pulse/context.h>
+#include <pulse/stream.h>
+#include <pulse/sample.h>
+
+#define SPICE_PULSE_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_PULSE, spice_pulse))
+
+struct stream {
+ pa_sample_spec spec;
+ pa_stream *stream;
+};
+
+struct spice_pulse {
+ SpiceSession *session;
+ SpiceChannel *pchannel;
+
+ pa_glib_mainloop *mainloop;
+ pa_context *context;
+ struct stream playback;
+};
+
+G_DEFINE_TYPE(SpicePulse, spice_pulse, G_TYPE_OBJECT)
+
+static const char *stream_state_names[] = {
+ [ PA_STREAM_UNCONNECTED ] = "unconnected",
+ [ PA_STREAM_CREATING ] = "creating",
+ [ PA_STREAM_READY ] = "ready",
+ [ PA_STREAM_FAILED ] = "failed",
+ [ PA_STREAM_TERMINATED ] = "terminated",
+};
+
+static const char *context_state_names[] = {
+ [ PA_CONTEXT_UNCONNECTED ] = "unconnected",
+ [ PA_CONTEXT_CONNECTING ] = "connecting",
+ [ PA_CONTEXT_AUTHORIZING ] = "authorizing",
+ [ PA_CONTEXT_SETTING_NAME ] = "setting_name",
+ [ PA_CONTEXT_READY ] = "ready",
+ [ PA_CONTEXT_FAILED ] = "failed",
+ [ PA_CONTEXT_TERMINATED ] = "terminated",
+};
+#define STATE_NAME(array, state) \
+ ((state < G_N_ELEMENTS(array)) ? array[state] : NULL)
+
+static void spice_pulse_finalize(GObject *obj)
+{
+ G_OBJECT_CLASS(spice_pulse_parent_class)->finalize(obj);
+}
+
+static void spice_pulse_init(SpicePulse *pulse)
+{
+ spice_pulse *p;
+
+ p = pulse->priv = SPICE_PULSE_GET_PRIVATE(pulse);
+ memset(p, 0, sizeof(*p));
+}
+
+static void spice_pulse_class_init(SpicePulseClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+
+ gobject_class->finalize = spice_pulse_finalize;
+
+ g_type_class_add_private(klass, sizeof(spice_pulse));
+}
+
+/* ------------------------------------------------------------------ */
+
+static void playback_start(SpiceChannel *channel, gint format, gint channels,
+ gint frequency, gpointer data)
+{
+ SpicePulse *pulse = data;
+ spice_pulse *p = SPICE_PULSE_GET_PRIVATE(pulse);
+ pa_context_state_t state;
+
+ state = pa_context_get_state(p->context);
+ switch (state) {
+ case PA_CONTEXT_READY:
+ if (p->playback.stream &&
+ (p->playback.spec.rate != frequency ||
+ p->playback.spec.channels != channels)) {
+ pa_stream_disconnect(p->playback.stream);
+ pa_stream_unref(p->playback.stream);
+ p->playback.stream = NULL;
+ }
+ if (p->playback.stream == NULL) {
+ assert(format == SPICE_AUDIO_FMT_S16);
+ p->playback.spec.format = PA_SAMPLE_S16LE;
+ p->playback.spec.rate = frequency;
+ p->playback.spec.channels = channels;
+ p->playback.stream = pa_stream_new(p->context, "playback",
+ &p->playback.spec, NULL);
+ pa_stream_connect_playback(p->playback.stream, NULL, NULL, 0, NULL, NULL);
+ }
+ if (pa_stream_is_corked(p->playback.stream)) {
+ pa_stream_cork(p->playback.stream, 0, NULL, NULL);
+ }
+ break;
+ default:
+ fprintf(stderr, "%s: pulse context not ready (%s)\n",
+ __FUNCTION__, STATE_NAME(context_state_names, state));
+ break;
+ }
+}
+
+static void playback_data(SpiceChannel *channel, gpointer *audio, gint size,
+ gpointer data)
+{
+ SpicePulse *pulse = data;
+ spice_pulse *p = SPICE_PULSE_GET_PRIVATE(pulse);
+ pa_stream_state_t state;
+
+ if (!p->playback.stream)
+ return;
+
+ state = pa_stream_get_state(p->playback.stream);
+ switch (state) {
+ case PA_STREAM_READY:
+ pa_stream_write(p->playback.stream, audio, size, NULL, 0, PA_SEEK_RELATIVE);
+ break;
+ default:
+ fprintf(stderr, "%s: pulse playback stream not ready (%s)\n",
+ __FUNCTION__, STATE_NAME(stream_state_names, state));
+ break;
+ }
+}
+
+static void playback_stop(SpiceChannel *channel, gpointer data)
+{
+ SpicePulse *pulse = data;
+ spice_pulse *p = SPICE_PULSE_GET_PRIVATE(pulse);
+
+ if (!p->playback.stream)
+ return;
+
+ pa_stream_cork(p->playback.stream, 1, NULL, NULL);
+}
+
+static void channel_new(SpiceSession *s, SpiceChannel *channel, gpointer data)
+{
+ SpicePulse *pulse = data;
+ int type = spice_channel_type(channel);
+
+ switch (type) {
+ case SPICE_CHANNEL_PLAYBACK:
+ g_signal_connect(channel, "spice-playback-start",
+ G_CALLBACK(playback_start), pulse);
+ g_signal_connect(channel, "spice-playback-data",
+ G_CALLBACK(playback_data), pulse);
+ g_signal_connect(channel, "spice-playback-stop",
+ G_CALLBACK(playback_stop), pulse);
+ spice_channel_connect(channel);
+ break;
+ default:
+ return;
+ }
+}
+
+SpicePulse *spice_pulse_new(SpiceSession *session, GMainLoop *mainloop,
+ const char *name)
+{
+ SpicePulse *pulse;
+ spice_pulse *p;
+ SpiceChannel *channels[16];
+ int i, n;
+
+ pulse = g_object_new(SPICE_TYPE_PULSE, NULL);
+ p = SPICE_PULSE_GET_PRIVATE(pulse);
+ p->session = session;
+
+ g_signal_connect(session, "spice-session-channel-new",
+ G_CALLBACK(channel_new), pulse);
+ n = spice_session_get_channels(session, channels, SPICE_N_ELEMENTS(channels));
+ for (i = 0; i < n; i++) {
+ channel_new(session, channels[i], (gpointer*)pulse);
+ }
+
+ p->mainloop = pa_glib_mainloop_new(g_main_loop_get_context(mainloop));
+ p->context = pa_context_new(pa_glib_mainloop_get_api(p->mainloop), name);
+ pa_context_connect(p->context, NULL, 0, NULL);
+
+ return pulse;
+}