/* $Id$ */ /*** This file is part of gst-polyp. gst-polyp is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. gst-polyp 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with gst-polyp; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. ***/ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include "polypsrc.h" #include "polyputil.h" GST_DEBUG_CATEGORY_EXTERN(polyp_debug); #define GST_CAT_DEFAULT polyp_debug enum { ARG_0, ARG_SERVER, ARG_SOURCE, }; static GstAudioSrcClass *parent_class = NULL; static void gst_polypsrc_destroy_stream(GstPolypSrc *polypsrc); static void gst_polypsrc_destroy_context(GstPolypSrc *polypsrc); static void gst_polypsrc_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void gst_polypsrc_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void gst_polypsrc_finalize(GObject *object); static void gst_polypsrc_dispose(GObject *object); static gboolean gst_polypsrc_open(GstAudioSrc *asrc); static gboolean gst_polypsrc_close(GstAudioSrc *asrc); static gboolean gst_polypsrc_prepare(GstAudioSrc *asrc, GstRingBufferSpec *spec); static gboolean gst_polypsrc_unprepare(GstAudioSrc *asrc); static guint gst_polypsrc_read(GstAudioSrc *asrc, gpointer data, guint length); static guint gst_polypsrc_delay(GstAudioSrc *asrc); #if (G_BYTE_ORDER == G_LITTLE_ENDIAN) # define ENDIANNESS "LITTLE_ENDIAN, BIG_ENDIAN" #else # define ENDIANNESS "BIG_ENDIAN, LITTLE_ENDIAN" #endif static void gst_polypsrc_base_init(gpointer g_class) { static GstStaticPadTemplate pad_template = GST_STATIC_PAD_TEMPLATE( "src", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS( "audio/x-raw-int, " "endianness = (int) { " ENDIANNESS " }, " "signed = (boolean) TRUE, " "width = (int) 16, " "depth = (int) 16, " "rate = (int) [ 1, MAX ], " "channels = (int) [ 1, 16 ];" "audio/x-raw-int, " "signed = (boolean) FALSE, " "width = (int) 8, " "depth = (int) 8, " "rate = (int) [ 1, MAX ], " "channels = (int) [ 1, 16 ];" "audio/x-raw-float, " "endianness = (int) { " ENDIANNESS " }, " "width = (int) 32, " "rate = (int) [ 1, MAX ], " "channels = (int) [ 1, 16 ];" "audio/x-alaw, " "rate = (int) [ 1, MAX], " "channels = (int) [ 1, 16 ];" "audio/x-mulaw, " "rate = (int) [ 1, MAX], " "channels = (int) [ 1, 16 ]" ) ); static const GstElementDetails details = GST_ELEMENT_DETAILS( "Polypaudio Audio Source", "Source/Audio", "Captures audio from a Polypaudio server", "Lennart Poettering"); GstElementClass *element_class = GST_ELEMENT_CLASS(g_class); gst_element_class_set_details(element_class, &details); gst_element_class_add_pad_template(element_class, gst_static_pad_template_get(&pad_template)); } static void gst_polypsrc_class_init( gpointer g_class, gpointer class_data) { GObjectClass *gobject_class = G_OBJECT_CLASS(g_class); GstAudioSrcClass *gstaudiosrc_class = GST_AUDIO_SRC_CLASS(g_class); parent_class = g_type_class_peek_parent(g_class); gobject_class->dispose = GST_DEBUG_FUNCPTR(gst_polypsrc_dispose); gobject_class->finalize = GST_DEBUG_FUNCPTR(gst_polypsrc_finalize); gobject_class->set_property = GST_DEBUG_FUNCPTR(gst_polypsrc_set_property); gobject_class->get_property = GST_DEBUG_FUNCPTR(gst_polypsrc_get_property); gstaudiosrc_class->open = GST_DEBUG_FUNCPTR(gst_polypsrc_open); gstaudiosrc_class->close = GST_DEBUG_FUNCPTR(gst_polypsrc_close); gstaudiosrc_class->prepare = GST_DEBUG_FUNCPTR(gst_polypsrc_prepare); gstaudiosrc_class->unprepare = GST_DEBUG_FUNCPTR(gst_polypsrc_unprepare); gstaudiosrc_class->read = GST_DEBUG_FUNCPTR(gst_polypsrc_read); gstaudiosrc_class->delay = GST_DEBUG_FUNCPTR(gst_polypsrc_delay); /* Overwrite GObject fields */ g_object_class_install_property( gobject_class, ARG_SERVER, g_param_spec_string("server", "Server", "The Polypaudio server to connect to", NULL, G_PARAM_READWRITE)); g_object_class_install_property( gobject_class, ARG_SOURCE, g_param_spec_string("source", "source", "The Polypaudio source device to connect to", NULL, G_PARAM_READWRITE)); } static void gst_polypsrc_init( GTypeInstance * instance, gpointer g_class) { GstPolypSrc *polypsrc = GST_POLYPSRC(instance); int e; polypsrc->server = polypsrc->device = NULL; polypsrc->context = NULL; polypsrc->stream = NULL; polypsrc->read_buffer = NULL; polypsrc->read_buffer_length = 0; polypsrc->mainloop = pa_threaded_mainloop_new(); g_assert(polypsrc->mainloop); e = pa_threaded_mainloop_start(polypsrc->mainloop); g_assert(e == 0); } static void gst_polypsrc_destroy_stream(GstPolypSrc* polypsrc) { if (polypsrc->stream) { pa_stream_disconnect(polypsrc->stream); pa_stream_unref(polypsrc->stream); polypsrc->stream = NULL; } } static void gst_polypsrc_destroy_context(GstPolypSrc* polypsrc) { gst_polypsrc_destroy_stream(polypsrc); if (polypsrc->context) { pa_context_disconnect(polypsrc->context); pa_context_unref(polypsrc->context); polypsrc->context = NULL; } } static void gst_polypsrc_finalize(GObject * object) { GstPolypSrc *polypsrc = GST_POLYPSRC(object); pa_threaded_mainloop_stop(polypsrc->mainloop); gst_polypsrc_destroy_context(polypsrc); g_free(polypsrc->server); g_free(polypsrc->device); pa_threaded_mainloop_free(polypsrc->mainloop); G_OBJECT_CLASS(parent_class)->finalize(object); } static void gst_polypsrc_dispose(GObject * object) { G_OBJECT_CLASS(parent_class)->dispose(object); } static void gst_polypsrc_set_property( GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstPolypSrc *polypsrc = GST_POLYPSRC(object); switch (prop_id) { case ARG_SERVER: g_free(polypsrc->server); polypsrc->server = g_value_dup_string(value); break; case ARG_SOURCE: g_free(polypsrc->device); polypsrc->device = g_value_dup_string(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void gst_polypsrc_get_property( GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { GstPolypSrc *polypsrc = GST_POLYPSRC(object); switch(prop_id) { case ARG_SERVER: g_value_set_string(value, polypsrc->server); break; case ARG_SOURCE: g_value_set_string(value, polypsrc->device); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void gst_polypsrc_context_state_cb(pa_context *c, void *userdata) { GstPolypSrc *polypsrc = GST_POLYPSRC(userdata); switch (pa_context_get_state(c)) { case PA_CONTEXT_READY: case PA_CONTEXT_TERMINATED: case PA_CONTEXT_FAILED: pa_threaded_mainloop_signal(polypsrc->mainloop, 0); break; case PA_CONTEXT_UNCONNECTED: case PA_CONTEXT_CONNECTING: case PA_CONTEXT_AUTHORIZING: case PA_CONTEXT_SETTING_NAME: break; } } static void gst_polypsrc_stream_state_cb(pa_stream *s, void * userdata) { GstPolypSrc *polypsrc = GST_POLYPSRC(userdata); switch (pa_stream_get_state(s)) { case PA_STREAM_READY: case PA_STREAM_FAILED: case PA_STREAM_TERMINATED: pa_threaded_mainloop_signal(polypsrc->mainloop, 0); break; case PA_STREAM_UNCONNECTED: case PA_STREAM_CREATING: break; } } static void gst_polypsrc_stream_request_cb(pa_stream *s, size_t length, void *userdata) { GstPolypSrc *polypsrc = GST_POLYPSRC(userdata); pa_threaded_mainloop_signal(polypsrc->mainloop, 0); } static gboolean gst_polypsrc_open(GstAudioSrc *asrc) { GstPolypSrc *polypsrc = GST_POLYPSRC(asrc); gchar *name = gst_polyp_client_name(); pa_threaded_mainloop_lock(polypsrc->mainloop); if (!(polypsrc->context = pa_context_new(pa_threaded_mainloop_get_api(polypsrc->mainloop), name))) { GST_ELEMENT_ERROR(polypsrc, RESOURCE, FAILED, ("Failed to create context"), (NULL)); goto unlock_and_fail; } pa_context_set_state_callback(polypsrc->context, gst_polypsrc_context_state_cb, polypsrc); if (pa_context_connect(polypsrc->context, polypsrc->server, 0, NULL) < 0) { GST_ELEMENT_ERROR(polypsrc, RESOURCE, FAILED, ("Failed to connect: %s", pa_strerror(pa_context_errno(polypsrc->context))), (NULL)); goto unlock_and_fail; } /* Wait until the context is ready */ pa_threaded_mainloop_wait(polypsrc->mainloop); if (pa_context_get_state(polypsrc->context) != PA_CONTEXT_READY) { GST_ELEMENT_ERROR(polypsrc, RESOURCE, FAILED, ("Failed to connect: %s", pa_strerror(pa_context_errno(polypsrc->context))), (NULL)); goto unlock_and_fail; } pa_threaded_mainloop_unlock(polypsrc->mainloop); g_free(name); return TRUE; unlock_and_fail: pa_threaded_mainloop_unlock(polypsrc->mainloop); g_free(name); return FALSE; } static gboolean gst_polypsrc_close(GstAudioSrc *asrc) { GstPolypSrc *polypsrc = GST_POLYPSRC(asrc); pa_threaded_mainloop_lock(polypsrc->mainloop); gst_polypsrc_destroy_context(polypsrc); pa_threaded_mainloop_unlock(polypsrc->mainloop); return TRUE; } static gboolean gst_polypsrc_prepare(GstAudioSrc *asrc, GstRingBufferSpec *spec) { pa_buffer_attr buf_attr; GstPolypSrc *polypsrc = GST_POLYPSRC(asrc); if (!gst_polyp_fill_sample_spec(spec, &polypsrc->sample_spec)) { GST_ELEMENT_ERROR(polypsrc, RESOURCE, SETTINGS, ("Invalid sample specification."), (NULL)); goto unlock_and_fail; } pa_threaded_mainloop_lock(polypsrc->mainloop); if (!polypsrc->context || pa_context_get_state(polypsrc->context) != PA_CONTEXT_READY) { GST_ELEMENT_ERROR(polypsrc, RESOURCE, FAILED, ("Bad context state: %s", polypsrc->context ? pa_strerror(pa_context_errno(polypsrc->context)) : NULL), (NULL)); goto unlock_and_fail; } if (!(polypsrc->stream = pa_stream_new(polypsrc->context, "Record Stream", &polypsrc->sample_spec, NULL))) { GST_ELEMENT_ERROR(polypsrc, RESOURCE, FAILED, ("Failed to create stream: %s", pa_strerror(pa_context_errno(polypsrc->context))), (NULL)); goto unlock_and_fail; } pa_stream_set_state_callback(polypsrc->stream, gst_polypsrc_stream_state_cb, polypsrc); pa_stream_set_read_callback(polypsrc->stream, gst_polypsrc_stream_request_cb, polypsrc); memset(&buf_attr, 0, sizeof(buf_attr)); buf_attr.maxlength = spec->segtotal*spec->segsize*2; buf_attr.fragsize = spec->segsize; if (pa_stream_connect_record(polypsrc->stream, polypsrc->device, &buf_attr, PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_AUTO_TIMING_UPDATE|PA_STREAM_NOT_MONOTONOUS) < 0) { GST_ELEMENT_ERROR(polypsrc, RESOURCE, FAILED, ("Failed to connect stream: %s", pa_strerror(pa_context_errno(polypsrc->context))), (NULL)); goto unlock_and_fail; } /* Wait until the stream is ready */ pa_threaded_mainloop_wait(polypsrc->mainloop); if (pa_stream_get_state(polypsrc->stream) != PA_STREAM_READY) { GST_ELEMENT_ERROR(polypsrc, RESOURCE, FAILED, ("Failed to connect stream: %s", pa_strerror(pa_context_errno(polypsrc->context))), (NULL)); goto unlock_and_fail; } pa_threaded_mainloop_unlock(polypsrc->mainloop); spec->bytes_per_sample = pa_frame_size(&polypsrc->sample_spec); memset(spec->silence_sample, 0, spec->bytes_per_sample); return TRUE; unlock_and_fail: pa_threaded_mainloop_unlock(polypsrc->mainloop); return FALSE; } static gboolean gst_polypsrc_unprepare(GstAudioSrc * asrc) { GstPolypSrc *polypsrc = GST_POLYPSRC(asrc); pa_threaded_mainloop_lock(polypsrc->mainloop); gst_polypsrc_destroy_stream(polypsrc); pa_threaded_mainloop_unlock(polypsrc->mainloop); polypsrc->read_buffer = NULL; polypsrc->read_buffer_length = 0; return TRUE; } #define CHECK_DEAD_GOTO(polypsrc, label) \ if (!(polypsrc)->context || pa_context_get_state((polypsrc)->context) != PA_CONTEXT_READY || \ !(polypsrc)->stream || pa_stream_get_state((polypsrc)->stream) != PA_STREAM_READY) { \ GST_ELEMENT_ERROR((polypsrc), RESOURCE, FAILED, ("Disconnected: %s", (polypsrc)->context ? pa_strerror(pa_context_errno((polypsrc)->context)) : NULL), (NULL)); \ goto label; \ } static guint gst_polypsrc_read(GstAudioSrc *asrc, gpointer data, guint length) { GstPolypSrc *polypsrc = GST_POLYPSRC(asrc); size_t sum = 0; pa_threaded_mainloop_lock(polypsrc->mainloop); CHECK_DEAD_GOTO(polypsrc, unlock_and_fail); while (length > 0) { size_t l; if (!polypsrc->read_buffer) { for (;;) { if (pa_stream_peek(polypsrc->stream, &polypsrc->read_buffer, &polypsrc->read_buffer_length) < 0) { GST_ELEMENT_ERROR(polypsrc, RESOURCE, FAILED, ("pa_stream_peek() failed: %s", pa_strerror(pa_context_errno(polypsrc->context))), (NULL)); goto unlock_and_fail; } if (polypsrc->read_buffer) break; pa_threaded_mainloop_wait(polypsrc->mainloop); CHECK_DEAD_GOTO(polypsrc, unlock_and_fail); } } g_assert(polypsrc->read_buffer && polypsrc->read_buffer_length); l = polypsrc->read_buffer_length > length ? length : polypsrc->read_buffer_length; memcpy(data, polypsrc->read_buffer, l); polypsrc->read_buffer = (const guint8*) polypsrc->read_buffer + l; polypsrc->read_buffer_length -= l; data = (guint8*) data + l; length -= l; sum += l; if (polypsrc->read_buffer_length <= 0) { if (pa_stream_drop(polypsrc->stream) < 0) { GST_ELEMENT_ERROR(polypsrc, RESOURCE, FAILED, ("pa_stream_drop() failed: %s", pa_strerror(pa_context_errno(polypsrc->context))), (NULL)); goto unlock_and_fail; } polypsrc->read_buffer = NULL; polypsrc->read_buffer_length = 0; } } pa_threaded_mainloop_unlock(polypsrc->mainloop); return sum; unlock_and_fail: pa_threaded_mainloop_unlock(polypsrc->mainloop); return 0; } static guint gst_polypsrc_delay(GstAudioSrc *asrc) { GstPolypSrc *polypsrc = GST_POLYPSRC(asrc); pa_usec_t t; int negative; pa_threaded_mainloop_lock(polypsrc->mainloop); CHECK_DEAD_GOTO(polypsrc, unlock_and_fail); if (pa_stream_get_latency(polypsrc->stream, &t, &negative) < 0) { if (pa_context_errno(polypsrc->context) != PA_ERR_NODATA) { GST_ELEMENT_ERROR(polypsrc, RESOURCE, FAILED, ("pa_stream_get_latency() failed: %s", pa_strerror(pa_context_errno(polypsrc->context))), (NULL)); goto unlock_and_fail; } GST_WARNING("Not data while querying latency"); t = 0; } else if (negative) t = 0; pa_threaded_mainloop_unlock(polypsrc->mainloop); return (guint) ((t * polypsrc->sample_spec.rate) / 1000000LL); unlock_and_fail: pa_threaded_mainloop_unlock(polypsrc->mainloop); return 0; } GType gst_polypsrc_get_type(void) { static GType polypsrc_type = 0; if (!polypsrc_type) { static const GTypeInfo polypsrc_info = { sizeof(GstPolypSrcClass), gst_polypsrc_base_init, NULL, gst_polypsrc_class_init, NULL, NULL, sizeof(GstPolypSrc), 0, gst_polypsrc_init, }; polypsrc_type = g_type_register_static( GST_TYPE_AUDIO_SRC, "GstPolypSrc", &polypsrc_info, 0); } return polypsrc_type; }