summaryrefslogtreecommitdiff
path: root/gtk/spice-pulse.c
diff options
context:
space:
mode:
authorMarc-André Lureau <marcandre.lureau@redhat.com>2010-11-25 02:21:35 +0100
committerMarc-André Lureau <marcandre.lureau@redhat.com>2010-11-25 02:22:22 +0100
commitf07e0346c5e98567722997568ceea421b2458646 (patch)
treea4bd2ab346db24ca9eacd257e3553b8cf27918d5 /gtk/spice-pulse.c
parentc21519732cba755e19cff99b176f82e36e789fad (diff)
gtk: add raw audio recording
Diffstat (limited to 'gtk/spice-pulse.c')
-rw-r--r--gtk/spice-pulse.c299
1 files changed, 281 insertions, 18 deletions
diff --git a/gtk/spice-pulse.c b/gtk/spice-pulse.c
index ea1e4cb..d85056e 100644
--- a/gtk/spice-pulse.c
+++ b/gtk/spice-pulse.c
@@ -22,6 +22,7 @@
#include <pulse/context.h>
#include <pulse/stream.h>
#include <pulse/sample.h>
+#include <pulse/error.h>
#define SPICE_PULSE_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_PULSE, spice_pulse))
@@ -30,6 +31,8 @@ struct stream {
pa_sample_spec spec;
pa_stream *stream;
int state;
+ pa_operation *uncork_op;
+ pa_operation *cork_op;
};
struct spice_pulse {
@@ -66,11 +69,65 @@ static const char *context_state_names[] = {
#define STATE_NAME(array, state) \
((state < G_N_ELEMENTS(array)) ? array[state] : NULL)
+static void channel_event(SpiceChannel *channel, SpiceChannelEvent event,
+ gpointer data);
+
static void spice_pulse_finalize(GObject *obj)
{
+ spice_pulse *p;
+
+ p = SPICE_PULSE_GET_PRIVATE(obj);
+
+ if (p->playback.uncork_op)
+ pa_operation_unref(p->playback.uncork_op);
+
+ if (p->playback.cork_op)
+ pa_operation_unref(p->playback.cork_op);
+
+ if (p->record.uncork_op)
+ pa_operation_unref(p->record.uncork_op);
+
+ if (p->record.cork_op)
+ pa_operation_unref(p->record.cork_op);
+
+ if (p->context != NULL)
+ pa_context_unref(p->context);
+
+ if (p->mainloop != NULL)
+ pa_glib_mainloop_free(p->mainloop);
+
G_OBJECT_CLASS(spice_pulse_parent_class)->finalize(obj);
}
+static void spice_pulse_dispose(GObject *obj)
+{
+ spice_pulse *p;
+
+ SPICE_DEBUG("%s", __FUNCTION__);
+ p = SPICE_PULSE_GET_PRIVATE(obj);
+
+ if (p->pchannel != NULL) {
+ g_signal_handlers_disconnect_by_func(p->pchannel,
+ channel_event, obj);
+ g_object_unref(p->pchannel);
+ p->pchannel = NULL;
+ }
+
+ if (p->rchannel != NULL) {
+ g_signal_handlers_disconnect_by_func(p->rchannel,
+ channel_event, obj);
+ g_object_unref(p->rchannel);
+ p->rchannel = NULL;
+ }
+
+ if (p->session != NULL) {
+ g_object_unref(p->session);
+ p->session = NULL;
+ }
+
+ G_OBJECT_CLASS(spice_pulse_parent_class)->dispose(obj);
+}
+
static void spice_pulse_init(SpicePulse *pulse)
{
spice_pulse *p;
@@ -84,11 +141,73 @@ static void spice_pulse_class_init(SpicePulseClass *klass)
GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
gobject_class->finalize = spice_pulse_finalize;
+ gobject_class->dispose = spice_pulse_dispose;
g_type_class_add_private(klass, sizeof(spice_pulse));
}
/* ------------------------------------------------------------------ */
+static void pulse_uncork_cb(pa_stream *pastream, int success, void *data)
+{
+ struct stream *s = data;
+
+ if (!success)
+ g_warning("pulseaudio uncork operation failed");
+
+ pa_operation_unref(s->uncork_op);
+ s->uncork_op = NULL;
+}
+
+static void stream_uncork(SpicePulse *pulse, struct stream *s)
+{
+ spice_pulse *p = SPICE_PULSE_GET_PRIVATE(pulse);
+ pa_operation *o = NULL;
+
+ if (s->cork_op) {
+ pa_operation_cancel(s->cork_op);
+ pa_operation_unref(s->cork_op);
+ s->cork_op = NULL;
+ }
+
+ if (pa_stream_is_corked(s->stream) && !s->uncork_op) {
+ if (!(o = pa_stream_cork(s->stream, 0, pulse_uncork_cb, s))) {
+ g_warning("pa_stream_cork() failed: %s",
+ pa_strerror(pa_context_errno(p->context)));
+ }
+ s->uncork_op = o;
+ }
+}
+
+static void pulse_cork_cb(pa_stream *pastream, int success, void *data)
+{
+ struct stream *s = data;
+
+ if (!success)
+ g_warning("pulseaudio cork operation failed");
+
+ pa_operation_unref(s->cork_op);
+ s->cork_op = NULL;
+}
+
+static void stream_cork(SpicePulse *pulse, struct stream *s)
+{
+ spice_pulse *p = SPICE_PULSE_GET_PRIVATE(pulse);
+ pa_operation *o = NULL;
+
+ if (s->uncork_op) {
+ pa_operation_cancel(s->uncork_op);
+ pa_operation_unref(s->uncork_op);
+ s->uncork_op = NULL;
+ }
+
+ if (pa_stream_is_corked(s->stream) && !s->cork_op) {
+ if (!(o = pa_stream_cork(s->stream, 1, pulse_cork_cb, s))) {
+ g_warning("pa_stream_cork() failed: %s",
+ pa_strerror(pa_context_errno(p->context)));
+ }
+ s->cork_op = o;
+ }
+}
static void playback_start(SpicePlaybackChannel *channel, gint format, gint channels,
gint frequency, gpointer data)
@@ -97,32 +216,40 @@ static void playback_start(SpicePlaybackChannel *channel, gint format, gint chan
spice_pulse *p = SPICE_PULSE_GET_PRIVATE(pulse);
pa_context_state_t state;
+ g_return_if_fail(p != NULL);
+
state = pa_context_get_state(p->context);
switch (state) {
+ /* TODO: create stream after pa state == READY */
case PA_CONTEXT_READY:
if (p->state != state) {
- g_message("%s: pulse context ready", __FUNCTION__);
+ SPICE_DEBUG("%s: pulse context ready", __FUNCTION__);
}
if (p->playback.stream &&
(p->playback.spec.rate != frequency ||
p->playback.spec.channels != channels)) {
- pa_stream_disconnect(p->playback.stream);
+ if (pa_stream_disconnect(p->playback.stream) < 0) {
+ g_warning("pa_stream_disconnect() failed: %s",
+ pa_strerror(pa_context_errno(p->context)));
+ }
pa_stream_unref(p->playback.stream);
p->playback.stream = NULL;
}
if (p->playback.stream == NULL) {
g_return_if_fail(format == SPICE_AUDIO_FMT_S16);
- p->playback.state = PA_STREAM_READY;
+ p->playback.state = PA_STREAM_READY;
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);
+ if (pa_stream_connect_playback(p->playback.stream,
+ NULL, NULL, 0, NULL, NULL) < 0) {
+ g_warning("pa_stream_connect_playback() failed: %s",
+ pa_strerror(pa_context_errno(p->context)));
+ }
}
+ stream_uncork(pulse, &p->playback);
break;
default:
if (p->state != state) {
@@ -148,9 +275,12 @@ static void playback_data(SpicePlaybackChannel *channel, gpointer *audio, gint s
switch (state) {
case PA_STREAM_READY:
if (p->playback.state != state) {
- g_message("%s: pulse playback stream ready", __FUNCTION__);
+ SPICE_DEBUG("%s: pulse playback stream ready", __FUNCTION__);
+ }
+ if (pa_stream_write(p->playback.stream, audio, size, NULL, 0, PA_SEEK_RELATIVE) < 0) {
+ g_warning("pa_stream_write() failed: %s",
+ pa_strerror(pa_context_errno(p->context)));
}
- pa_stream_write(p->playback.stream, audio, size, NULL, 0, PA_SEEK_RELATIVE);
break;
default:
if (p->playback.state != state) {
@@ -167,21 +297,142 @@ static void playback_stop(SpicePlaybackChannel *channel, gpointer data)
SpicePulse *pulse = data;
spice_pulse *p = pulse->priv;
+ SPICE_DEBUG("%s", __FUNCTION__);
if (!p->playback.stream)
return;
- pa_stream_cork(p->playback.stream, 1, NULL, NULL);
+ stream_cork(pulse, &p->playback);
+}
+
+static void stream_read_callback(pa_stream *s, size_t length, void *data)
+{
+ SpicePulse *pulse = data;
+ spice_pulse *p = SPICE_PULSE_GET_PRIVATE(pulse);
+ pa_context_state_t state;
+
+ g_return_if_fail(p != NULL);
+
+ while (pa_stream_readable_size(s) > 0) {
+ const void *snddata;
+
+ if (pa_stream_peek(s, &snddata, &length) < 0) {
+ g_warning("pa_stream_peek() failed: %s",
+ pa_strerror(pa_context_errno(p->context)));
+ return;
+ }
+
+ g_return_if_fail(snddata);
+ g_return_if_fail(length > 0);
+
+ spice_record_send_data(SPICE_RECORD_CHANNEL(p->rchannel),
+ /* FIXME: server side doesn't care about ts?
+ what is the unit? ms apparently */
+ (gpointer)snddata, length, 0);
+
+ if (pa_stream_drop(s) < 0) {
+ g_warning("pa_stream_drop() failed: %s",
+ pa_strerror(pa_context_errno(p->context)));
+ return;
+ }
+ }
}
-static void record_start(SpicePlaybackChannel *channel, gint format, gint channels,
+static void record_start(SpiceRecordChannel *channel, gint format, gint channels,
gint frequency, gpointer data)
{
- SPICE_DEBUG("%s", __FUNCTION__);
+ 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->state != state) {
+ SPICE_DEBUG("%s: pulse context ready", __FUNCTION__);
+ }
+ if (p->record.stream &&
+ (p->record.spec.rate != frequency ||
+ p->record.spec.channels != channels)) {
+ if (pa_stream_disconnect(p->record.stream) < 0) {
+ g_warning("pa_stream_disconnect() failed: %s",
+ pa_strerror(pa_context_errno(p->context)));
+ }
+ pa_stream_unref(p->record.stream);
+ p->record.stream = NULL;
+ }
+ if (p->record.stream == NULL) {
+ pa_buffer_attr buffer_attr = { 0, };
+ pa_stream_flags_t flags;
+
+ g_return_if_fail(format == SPICE_AUDIO_FMT_S16);
+ p->record.state = PA_STREAM_READY;
+ p->record.spec.format = PA_SAMPLE_S16LE;
+ p->record.spec.rate = frequency;
+ p->record.spec.channels = channels;
+
+ p->record.stream = pa_stream_new(p->context, "record",
+ &p->record.spec, NULL);
+ pa_stream_set_read_callback(p->record.stream, stream_read_callback, pulse);
+
+ /* FIXME: we might want customizable latency */
+ buffer_attr.maxlength = -1;
+ buffer_attr.prebuf = -1;
+ buffer_attr.fragsize = buffer_attr.tlength = (uint32_t) 4096;
+ buffer_attr.minreq = (uint32_t) -1;
+ flags = PA_STREAM_ADJUST_LATENCY;
+
+ if (pa_stream_connect_record(p->record.stream, NULL, &buffer_attr, flags) < 0) {
+ g_warning("pa_stream_connect_record() failed: %s",
+ pa_strerror(pa_context_errno(p->context)));
+ }
+ }
+ stream_uncork(pulse, &p->record);
+ break;
+ default:
+ if (p->state != state) {
+ g_warning("%s: pulse context not ready (%s)",
+ __FUNCTION__, STATE_NAME(context_state_names, state));
+ }
+ break;
+ }
+ p->state = state;
}
-static void record_stop(SpicePlaybackChannel *channel, gpointer data)
+static void record_stop(SpiceRecordChannel *channel, gpointer data)
{
+ SpicePulse *pulse = data;
+ spice_pulse *p = pulse->priv;
+
SPICE_DEBUG("%s", __FUNCTION__);
+ if (!p->record.stream)
+ return;
+
+ stream_cork(pulse, &p->record);
+}
+
+static void channel_event(SpiceChannel *channel, SpiceChannelEvent event,
+ gpointer data)
+{
+ SpicePulse *pulse = data;
+ spice_pulse *p = pulse->priv;
+
+ switch (event) {
+ case SPICE_CHANNEL_OPENED:
+ break;
+ case SPICE_CHANNEL_CLOSED:
+ if (channel == p->pchannel) {
+ p->pchannel = NULL;
+ g_object_unref(channel);
+ } else if (channel == p->rchannel) {
+ record_stop(SPICE_RECORD_CHANNEL(channel), pulse);
+ p->rchannel = NULL;
+ g_object_unref(channel);
+ } else
+ g_return_if_reached();
+ break;
+ default:
+ break;
+ }
}
static void channel_new(SpiceSession *s, SpiceChannel *channel, gpointer data)
@@ -190,22 +441,26 @@ static void channel_new(SpiceSession *s, SpiceChannel *channel, gpointer data)
spice_pulse *p = pulse->priv;
if (SPICE_IS_PLAYBACK_CHANNEL(channel)) {
- p->pchannel = channel;
+ p->pchannel = g_object_ref(channel);
g_signal_connect(channel, "playback-start",
G_CALLBACK(playback_start), pulse);
g_signal_connect(channel, "playback-data",
G_CALLBACK(playback_data), pulse);
g_signal_connect(channel, "playback-stop",
G_CALLBACK(playback_stop), pulse);
+ g_signal_connect(channel, "channel-event",
+ G_CALLBACK(channel_event), pulse);
spice_channel_connect(channel);
}
if (SPICE_IS_RECORD_CHANNEL(channel)) {
- p->rchannel = channel;
+ p->rchannel = g_object_ref(channel);
g_signal_connect(channel, "record-start",
G_CALLBACK(record_start), pulse);
g_signal_connect(channel, "record-stop",
G_CALLBACK(record_stop), pulse);
+ g_signal_connect(channel, "channel-event",
+ G_CALLBACK(channel_event), pulse);
spice_channel_connect(channel);
}
}
@@ -219,20 +474,28 @@ SpicePulse *spice_pulse_new(SpiceSession *session, GMainContext *context,
pulse = g_object_new(SPICE_TYPE_PULSE, NULL);
p = SPICE_PULSE_GET_PRIVATE(pulse);
- p->session = session;
+ p->session = g_object_ref(session);
g_signal_connect(session, "channel-new",
G_CALLBACK(channel_new), pulse);
list = spice_session_get_channels(session);
for (list = g_list_first(list); list != NULL; list = g_list_next(list)) {
- channel_new(session, list->data, (gpointer*)pulse);
+ channel_new(session, list->data, (gpointer)pulse);
}
g_list_free(list);
p->mainloop = pa_glib_mainloop_new(context);
p->state = PA_CONTEXT_READY;
p->context = pa_context_new(pa_glib_mainloop_get_api(p->mainloop), name);
- pa_context_connect(p->context, NULL, 0, NULL);
+ if (pa_context_connect(p->context, NULL, 0, NULL) < 0) {
+ g_warning("pa_context_connect() failed: %s",
+ pa_strerror(pa_context_errno(p->context)));
+ goto error;
+ }
return pulse;
+
+error:
+ g_object_unref(pulse);
+ return NULL;
}