diff options
| author | Luiz Augusto von Dentz <luiz.dentz@openbossa.org> | 2008-01-23 13:14:02 +0000 | 
|---|---|---|
| committer | Luiz Augusto von Dentz <luiz.dentz@openbossa.org> | 2008-01-23 13:14:02 +0000 | 
| commit | a0af7ee44534dad8f35a4142c6a22177e54ffc57 (patch) | |
| tree | c6a475d035e40f617a213a9ad5327a7eb5d01388 | |
| parent | 1cacae6dd9f44d0e403aa29e45eb3d20e7127f68 (diff) | |
Make a2dpsink to act like a bin and split the payloader.
| -rw-r--r-- | audio/Makefile.am | 4 | ||||
| -rw-r--r-- | audio/gsta2dpsendersink.c | 1013 | ||||
| -rw-r--r-- | audio/gsta2dpsendersink.h | 90 | ||||
| -rw-r--r-- | audio/gsta2dpsink.c | 1042 | ||||
| -rw-r--r-- | audio/gsta2dpsink.h | 29 | ||||
| -rw-r--r-- | audio/gstbluetooth.c | 20 | ||||
| -rw-r--r-- | audio/gstrtpsbcpay.c | 337 | ||||
| -rw-r--r-- | audio/gstrtpsbcpay.h | 65 | ||||
| -rw-r--r-- | audio/gstsbcdec.c | 17 | ||||
| -rw-r--r-- | audio/gstsbcdec.h | 2 | ||||
| -rw-r--r-- | audio/gstsbcenc.c | 131 | ||||
| -rw-r--r-- | audio/gstsbcenc.h | 2 | ||||
| -rw-r--r-- | audio/gstsbcparse.c | 223 | ||||
| -rw-r--r-- | audio/gstsbcparse.h | 5 | ||||
| -rw-r--r-- | audio/gstsbcutil.c | 100 | ||||
| -rw-r--r-- | audio/gstsbcutil.h | 19 | 
16 files changed, 2037 insertions, 1062 deletions
| diff --git a/audio/Makefile.am b/audio/Makefile.am index f9284b05..5d838c85 100644 --- a/audio/Makefile.am +++ b/audio/Makefile.am @@ -43,11 +43,13 @@ libgstbluetooth_la_SOURCES = gstbluetooth.c \  				gstsbcenc.h gstsbcenc.c \  				gstsbcdec.h gstsbcdec.c \  				gstsbcparse.h gstsbcparse.c \ +				gsta2dpsendersink.h gsta2dpsendersink.c \  				gsta2dpsink.h gsta2dpsink.c \  				gstsbcutil.h gstsbcutil.c \ +				gstrtpsbcpay.h gstrtpsbcpay.c \  				rtp.h ipc.h ipc.c  libgstbluetooth_la_LDFLAGS = -module -avoid-version -export-symbols-regex gst_plugin_desc -libgstbluetooth_la_LIBADD = @SBC_LIBS@ @GSTREAMER_LIBS@ -lgstaudio-0.10 +libgstbluetooth_la_LIBADD = @SBC_LIBS@ @GSTREAMER_LIBS@ -lgstaudio-0.10 -lgstrtp-0.10  libgstbluetooth_la_CFLAGS = @GSTREAMER_CFLAGS@ @SBC_CFLAGS@  endif  endif diff --git a/audio/gsta2dpsendersink.c b/audio/gsta2dpsendersink.c new file mode 100644 index 00000000..cfb67b87 --- /dev/null +++ b/audio/gsta2dpsendersink.c @@ -0,0 +1,1013 @@ +/* + * + *  BlueZ - Bluetooth protocol stack for Linux + * + *  Copyright (C) 2004-2007  Marcel Holtmann <marcel@holtmann.org> + * + * + *  This library 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. + * + *  This library 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 this library; if not, write to the Free Software + *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <unistd.h> +#include <sys/un.h> +#include <sys/socket.h> +#include <fcntl.h> +#include <pthread.h> + +#include <netinet/in.h> + +#include <bluetooth/bluetooth.h> + +#include "ipc.h" +#include "rtp.h" +#include "gstsbcutil.h" + +#include "gsta2dpsendersink.h" + +GST_DEBUG_CATEGORY_STATIC(a2dp_sender_sink_debug); +#define GST_CAT_DEFAULT a2dp_sender_sink_debug + +#define BUFFER_SIZE 2048 +#define TEMPLATE_MAX_BITPOOL 64 + +#define GST_A2DP_SENDER_SINK_MUTEX_LOCK(s) G_STMT_START {	\ +		g_mutex_lock (s->sink_lock);		\ +	} G_STMT_END + +#define GST_A2DP_SENDER_SINK_MUTEX_UNLOCK(s) G_STMT_START {	\ +		g_mutex_unlock (s->sink_lock);		\ +	} G_STMT_END + + +struct bluetooth_data { +	struct bt_getcapabilities_rsp caps; /* Bluetooth device caps */ +	guint link_mtu; + +	gchar buffer[BUFFER_SIZE];	/* Codec transfer buffer */ +}; + +#define IS_SBC(n) (strcmp((n), "audio/x-sbc") == 0) +#define IS_MPEG(n) (strcmp((n), "audio/mpeg") == 0) + +enum { +	PROP_0, +	PROP_DEVICE, +}; + +GST_BOILERPLATE(GstA2dpSenderSink, gst_a2dp_sender_sink, GstBaseSink, +			GST_TYPE_BASE_SINK); + +static const GstElementDetails a2dp_sender_sink_details = +	GST_ELEMENT_DETAILS("Bluetooth A2DP sink", +				"Sink/Audio", +				"Plays audio to an A2DP device", +				"Marcel Holtmann <marcel@holtmann.org>"); + +static GstStaticPadTemplate a2dp_sender_sink_factory = +	GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS, +		GST_STATIC_CAPS("application/x-rtp, " +				"media = (string) \"audio\", " +				"encoding-name = (string) \"SBC\"" +				)); + +static GIOError gst_a2dp_sender_sink_audioservice_send(GstA2dpSenderSink *self, +					const bt_audio_msg_header_t *msg); +static GIOError gst_a2dp_sender_sink_audioservice_expect( +				GstA2dpSenderSink *self, +				bt_audio_msg_header_t *outmsg, +				int expected_type); + + +static void gst_a2dp_sender_sink_base_init(gpointer g_class) +{ +	GstElementClass *element_class = GST_ELEMENT_CLASS(g_class); + +	gst_element_class_add_pad_template(element_class, +		gst_static_pad_template_get(&a2dp_sender_sink_factory)); + +	gst_element_class_set_details(element_class, &a2dp_sender_sink_details); +} + +static gboolean gst_a2dp_sender_sink_stop(GstBaseSink *basesink) +{ +	GstA2dpSenderSink *self = GST_A2DP_SENDER_SINK(basesink); + +	GST_INFO_OBJECT(self, "stop"); + +	if (self->watch_id != 0) { +		g_source_remove(self->watch_id); +		self->watch_id = 0; +	} + +	if (self->server) { +		bt_audio_service_close(g_io_channel_unix_get_fd(self->server)); +		g_io_channel_unref(self->server); +		self->server = NULL; +	} + +	if (self->stream) { +		g_io_channel_flush(self->stream, NULL); +		g_io_channel_close(self->stream); +		g_io_channel_unref(self->stream); +		self->stream = NULL; +	} + +	if (self->data) { +		g_free(self->data); +		self->data = NULL; +	} + +	if (self->stream_caps) { +		gst_caps_unref(self->stream_caps); +		self->stream_caps = NULL; +	} + +	if (self->dev_caps) { +		gst_caps_unref(self->dev_caps); +		self->dev_caps = NULL; +	} + +	return TRUE; +} + +static void gst_a2dp_sender_sink_finalize(GObject *object) +{ +	GstA2dpSenderSink *self = GST_A2DP_SENDER_SINK(object); + +	if (self->data) +		gst_a2dp_sender_sink_stop(GST_BASE_SINK(self)); + +	if (self->device) +		g_free(self->device); + +	g_mutex_free(self->sink_lock); + +	G_OBJECT_CLASS(parent_class)->finalize(object); +} + +static void gst_a2dp_sender_sink_set_property(GObject *object, guint prop_id, +					const GValue *value, GParamSpec *pspec) +{ +	GstA2dpSenderSink *sink = GST_A2DP_SENDER_SINK(object); + +	switch (prop_id) { +	case PROP_DEVICE: +		if (sink->device) +			g_free(sink->device); +		sink->device = g_value_dup_string(value); +		break; + +	default: +		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); +		break; +	} +} + +static void gst_a2dp_sender_sink_get_property(GObject *object, guint prop_id, +					GValue *value, GParamSpec *pspec) +{ +	GstA2dpSenderSink *sink = GST_A2DP_SENDER_SINK(object); + +	switch (prop_id) { +	case PROP_DEVICE: +		g_value_set_string(value, sink->device); +		break; + +	default: +		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); +		break; +	} +} + +static gint gst_a2dp_sender_sink_bluetooth_recvmsg_fd(GstA2dpSenderSink *sink) +{ +	int err, ret; + +	ret = bt_audio_service_get_data_fd( +			g_io_channel_unix_get_fd(sink->server)); + +	if (ret < 0) { +		err = errno; +		GST_ERROR_OBJECT(sink, "Unable to receive fd: %s (%d)", +				strerror(err), err); +		return -err; +	} + +	sink->stream = g_io_channel_unix_new(ret); +	GST_DEBUG_OBJECT(sink, "stream_fd=%d", ret); + +	return 0; +} + +static gboolean gst_a2dp_sender_sink_init_pkt_conf(GstA2dpSenderSink *sink, +					GstCaps *caps, +					sbc_capabilities_t *pkt) +{ +	sbc_capabilities_t *cfg = &sink->data->caps.sbc_capabilities; +	const GValue *value = NULL; +	const char *pref, *name; +	gint rate, subbands, blocks; +	GstStructure *structure = gst_caps_get_structure(caps, 0); + +	name = gst_structure_get_name(structure); +	/* FIXME only sbc supported here, should suport mp3 */ +	if (!(IS_SBC(name))) { +		GST_ERROR_OBJECT(sink, "Unsupported format %s", name); +		return FALSE; +	} + +	value = gst_structure_get_value(structure, "rate"); +	rate = g_value_get_int(value); +	if (rate == 44100) +		cfg->frequency = BT_A2DP_SAMPLING_FREQ_44100; +	else if (rate == 48000) +		cfg->frequency = BT_A2DP_SAMPLING_FREQ_48000; +	else if (rate == 32000) +		cfg->frequency = BT_A2DP_SAMPLING_FREQ_32000; +	else if (rate == 16000) +		cfg->frequency = BT_A2DP_SAMPLING_FREQ_16000; +	else { +		GST_ERROR_OBJECT(sink, "Invalid rate while setting caps"); +		return FALSE; +	} + +	value = gst_structure_get_value(structure, "mode"); +	pref = g_value_get_string(value); +	if (strcmp(pref, "auto") == 0) +		cfg->channel_mode = BT_A2DP_CHANNEL_MODE_AUTO; +	else if (strcmp(pref, "mono") == 0) +		cfg->channel_mode = BT_A2DP_CHANNEL_MODE_MONO; +	else if (strcmp(pref, "dual") == 0) +		cfg->channel_mode = BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL; +	else if (strcmp(pref, "stereo") == 0) +		cfg->channel_mode = BT_A2DP_CHANNEL_MODE_STEREO; +	else if (strcmp(pref, "joint") == 0) +		cfg->channel_mode = BT_A2DP_CHANNEL_MODE_JOINT_STEREO; +	else { +		GST_ERROR_OBJECT(sink, "Invalid mode %s", pref); +		return FALSE; +	} + +	value = gst_structure_get_value(structure, "allocation"); +	pref = g_value_get_string(value); +	if (strcmp(pref, "auto") == 0) +		cfg->allocation_method = BT_A2DP_ALLOCATION_AUTO; +	else if (strcmp(pref, "loudness") == 0) +		cfg->allocation_method = BT_A2DP_ALLOCATION_LOUDNESS; +	else if (strcmp(pref, "snr") == 0) +		cfg->allocation_method = BT_A2DP_ALLOCATION_SNR; +	else { +		GST_ERROR_OBJECT(sink, "Invalid allocation: %s", pref); +		return FALSE; +	} + +	value = gst_structure_get_value(structure, "subbands"); +	subbands = g_value_get_int(value); +	if (subbands == 8) +		cfg->subbands = BT_A2DP_SUBBANDS_8; +	else if (subbands == 4) +		cfg->subbands = BT_A2DP_SUBBANDS_4; +	else { +		GST_ERROR_OBJECT(sink, "Invalid subbands %d", subbands); +		return FALSE; +	} + +	value = gst_structure_get_value(structure, "blocks"); +	blocks = g_value_get_int(value); +	if (blocks == 16) +		cfg->block_length = BT_A2DP_BLOCK_LENGTH_16; +	else if (blocks == 12) +		cfg->block_length = BT_A2DP_BLOCK_LENGTH_12; +	else if (blocks == 8) +		cfg->block_length = BT_A2DP_BLOCK_LENGTH_8; +	else if (blocks == 4) +		cfg->block_length = BT_A2DP_BLOCK_LENGTH_4; +	else { +		GST_ERROR_OBJECT(sink, "Invalid blocks %d", blocks); +		return FALSE; +	} + +	value = gst_structure_get_value(structure, "bitpool"); +	cfg->max_bitpool = cfg->min_bitpool = g_value_get_int(value); + +	memcpy(pkt, cfg, sizeof(*pkt)); + +	return TRUE; +} + +static gboolean gst_a2dp_sender_sink_conf_recv_stream_fd( +					GstA2dpSenderSink *self) +{ +	struct bluetooth_data *data = self->data; +	gint ret; +	GIOError err; +	GError *gerr = NULL; +	GIOStatus status; +	GIOFlags flags; +	gsize read; + +	ret = gst_a2dp_sender_sink_bluetooth_recvmsg_fd(self); +	if (ret < 0) +		return FALSE; + +	if (!self->stream) { +		GST_ERROR_OBJECT(self, "Error while configuring device: " +				"could not acquire audio socket"); +		return FALSE; +	} + +	/* set stream socket to nonblock */ +	GST_LOG_OBJECT(self, "setting stream socket to nonblock"); +	flags = g_io_channel_get_flags(self->stream); +	flags |= G_IO_FLAG_NONBLOCK; +	status = g_io_channel_set_flags(self->stream, flags, &gerr); +	if (status != G_IO_STATUS_NORMAL) { +		if (gerr) +			GST_WARNING_OBJECT(self, "Error while " +				"setting server socket to nonblock: " +				"%s", gerr->message); +		else +			GST_WARNING_OBJECT(self, "Error while " +					"setting server " +					"socket to nonblock"); +	} + +	/* It is possible there is some outstanding +	data in the pipe - we have to empty it */ +	GST_LOG_OBJECT(self, "emptying stream pipe"); +	while (1) { +		err = g_io_channel_read(self->stream, data->buffer, +					(gsize) data->link_mtu, +					&read); +		if (err != G_IO_ERROR_NONE || read <= 0) +			break; +	} + +	/* set stream socket to block */ +	GST_LOG_OBJECT(self, "setting stream socket to block"); +	flags = g_io_channel_get_flags(self->stream); +	flags &= ~G_IO_FLAG_NONBLOCK; +	status = g_io_channel_set_flags(self->stream, flags, &gerr); +	if (status != G_IO_STATUS_NORMAL) { +		if (gerr) +			GST_WARNING_OBJECT(self, "Error while " +				"setting server socket to block:" +				"%s", gerr->message); +		else +			GST_WARNING_OBJECT(self, "Error while " +				"setting server " +				"socket to block"); +	} + +	memset(data->buffer, 0, sizeof(data->buffer)); + +	return TRUE; +} + +static gboolean server_callback(GIOChannel *chan, +					GIOCondition cond, gpointer data) +{ +	GstA2dpSenderSink *sink; + +	if (cond & G_IO_HUP || cond & G_IO_NVAL) +		return FALSE; +	else if (cond & G_IO_ERR) { +		sink = GST_A2DP_SENDER_SINK(data); +		GST_WARNING_OBJECT(sink, "Untreated callback G_IO_ERR"); +	} + +	return TRUE; +} + +static gboolean gst_a2dp_sender_sink_update_caps(GstA2dpSenderSink *self) +{ +	sbc_capabilities_t *sbc = &self->data->caps.sbc_capabilities; +	GstStructure *structure; +	GValue *value; +	GValue *list; +	gchar *tmp; +	gboolean mono, stereo; + +	GST_LOG_OBJECT(self, "updating device caps"); + +	structure = gst_structure_empty_new("audio/x-sbc"); +	value = g_value_init(g_new0(GValue, 1), G_TYPE_STRING); + +	/* mode */ +	list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST); +	if (sbc->channel_mode == BT_A2DP_CHANNEL_MODE_AUTO) { +		g_value_set_static_string(value, "joint"); +		gst_value_list_prepend_value(list, value); +		g_value_set_static_string(value, "stereo"); +		gst_value_list_prepend_value(list, value); +		g_value_set_static_string(value, "mono"); +		gst_value_list_prepend_value(list, value); +		g_value_set_static_string(value, "dual"); +		gst_value_list_prepend_value(list, value); +	} else { +		if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_MONO) { +			g_value_set_static_string(value, "mono"); +			gst_value_list_prepend_value(list, value); +		} +		if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_STEREO) { +			g_value_set_static_string(value, "stereo"); +			gst_value_list_prepend_value(list, value); +		} +		if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL) { +			g_value_set_static_string(value, "dual"); +			gst_value_list_prepend_value(list, value); +		} +		if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_JOINT_STEREO) { +			g_value_set_static_string(value, "joint"); +			gst_value_list_prepend_value(list, value); +		} +	} +	g_value_unset(value); +	if (list) { +		gst_structure_set_value(structure, "mode", list); +		g_free(list); +		list = NULL; +	} + +	/* subbands */ +	list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST); +	value = g_value_init(value, G_TYPE_INT); +	if (sbc->subbands & BT_A2DP_SUBBANDS_4) { +		g_value_set_int(value, 4); +		gst_value_list_prepend_value(list, value); +	} +	if (sbc->subbands & BT_A2DP_SUBBANDS_8) { +		g_value_set_int(value, 8); +		gst_value_list_prepend_value(list, value); +	} +	g_value_unset(value); +	if (list) { +		gst_structure_set_value(structure, "subbands", list); +		g_free(list); +		list = NULL; +	} + +	/* blocks */ +	value = g_value_init(value, G_TYPE_INT); +	list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST); +	if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_16) { +		g_value_set_int(value, 16); +		gst_value_list_prepend_value(list, value); +	} +	if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_12) { +		g_value_set_int(value, 12); +		gst_value_list_prepend_value(list, value); +	} +	if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_8) { +		g_value_set_int(value, 8); +		gst_value_list_prepend_value(list, value); +	} +	if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_4) { +		g_value_set_int(value, 4); +		gst_value_list_prepend_value(list, value); +	} +	g_value_unset(value); +	if (list) { +		gst_structure_set_value(structure, "blocks", list); +		g_free(list); +		list = NULL; +	} + +	/* allocation */ +	g_value_init(value, G_TYPE_STRING); +	list = g_value_init(g_new0(GValue,1), GST_TYPE_LIST); +	if (sbc->allocation_method == BT_A2DP_ALLOCATION_AUTO) { +		g_value_set_static_string(value, "loudness"); +		gst_value_list_prepend_value(list, value); +		g_value_set_static_string(value, "snr"); +		gst_value_list_prepend_value(list, value); +	} else { +		if (sbc->allocation_method & BT_A2DP_ALLOCATION_LOUDNESS) { +			g_value_set_static_string(value, "loudness"); +			gst_value_list_prepend_value(list, value); +		} +		if (sbc->allocation_method & BT_A2DP_ALLOCATION_SNR) { +			g_value_set_static_string(value, "snr"); +			gst_value_list_prepend_value(list, value); +		} +	} +	g_value_unset(value); +	if (list) { +		gst_structure_set_value(structure, "allocation", list); +		g_free(list); +		list = NULL; +	} + +	/* rate */ +	g_value_init(value, G_TYPE_INT); +	list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST); +	if (sbc->frequency & BT_A2DP_SAMPLING_FREQ_48000) { +		g_value_set_int(value, 48000); +		gst_value_list_prepend_value(list, value); +	} +	if (sbc->frequency & BT_A2DP_SAMPLING_FREQ_44100) { +		g_value_set_int(value, 44100); +		gst_value_list_prepend_value(list, value); +	} +	if (sbc->frequency & BT_A2DP_SAMPLING_FREQ_32000) { +		g_value_set_int(value, 32000); +		gst_value_list_prepend_value(list, value); +	} +	if (sbc->frequency & BT_A2DP_SAMPLING_FREQ_16000) { +		g_value_set_int(value, 16000); +		gst_value_list_prepend_value(list, value); +	} +	g_value_unset(value); +	if (list) { +		gst_structure_set_value(structure, "rate", list); +		g_free(list); +		list = NULL; +	} + +	/* bitpool */ +	value = g_value_init(value, GST_TYPE_INT_RANGE); +	gst_value_set_int_range(value, +			MIN(sbc->min_bitpool, TEMPLATE_MAX_BITPOOL), +			MIN(sbc->max_bitpool, TEMPLATE_MAX_BITPOOL)); +	gst_structure_set_value(structure, "bitpool", value); +	g_value_unset(value); + +	/* channels */ +	if (sbc->channel_mode == BT_A2DP_CHANNEL_MODE_AUTO) { +		g_value_init(value, GST_TYPE_INT_RANGE); +		gst_value_set_int_range(value, 1, 2); +	} else { +		mono = FALSE; +		stereo = FALSE; +		if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_MONO) +			mono = TRUE; +		if ((sbc->channel_mode & BT_A2DP_CHANNEL_MODE_STEREO) || +				(sbc->channel_mode & +				BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL) || +				(sbc->channel_mode & +				BT_A2DP_CHANNEL_MODE_JOINT_STEREO)) +			stereo = TRUE; + +		if (mono && stereo) { +			g_value_init(value, GST_TYPE_INT_RANGE); +			gst_value_set_int_range(value, 1, 2); +		} else { +			g_value_init(value, G_TYPE_INT); +			if (mono) +				g_value_set_int(value, 1); +			else if (stereo) +				g_value_set_int(value, 2); +			else { +				GST_ERROR_OBJECT(self, +					"Unexpected number of channels"); +				g_value_set_int(value, 0); +			} +		} +	} +	gst_structure_set_value(structure, "channels", value); +	g_free(value); + +	if (self->dev_caps != NULL) +		gst_caps_unref(self->dev_caps); +	self->dev_caps = gst_caps_new_full(structure, NULL); + +	tmp = gst_caps_to_string(self->dev_caps); +	GST_DEBUG_OBJECT(self, "Device capabilities: %s", tmp); +	g_free(tmp); + +	return TRUE; +} + +static gboolean gst_a2dp_sender_sink_get_capabilities(GstA2dpSenderSink *self) +{ +	gchar *buf[BT_AUDIO_IPC_PACKET_SIZE]; +	struct bt_getcapabilities_req *req = (void *) buf; +	struct bt_getcapabilities_rsp *rsp = (void *) buf; +	GIOError io_error; + +	memset(req, 0, BT_AUDIO_IPC_PACKET_SIZE); + +	req->h.msg_type = BT_GETCAPABILITIES_REQ; +	if (self->device == NULL) +		return FALSE; +	strncpy(req->device, self->device, 18); + +	io_error = gst_a2dp_sender_sink_audioservice_send(self, &req->h); +	if (io_error != G_IO_ERROR_NONE) { +		GST_ERROR_OBJECT(self, "Error while asking device caps"); +	} + +	io_error = gst_a2dp_sender_sink_audioservice_expect(self, +			&rsp->rsp_h.msg_h, BT_GETCAPABILITIES_RSP); +	if (io_error != G_IO_ERROR_NONE) { +		GST_ERROR_OBJECT(self, "Error while getting device caps"); +		return FALSE; +	} + +	if (rsp->rsp_h.posix_errno != 0) { +		GST_ERROR_OBJECT(self, "BT_GETCAPABILITIES failed : %s(%d)", +					strerror(rsp->rsp_h.posix_errno), +					rsp->rsp_h.posix_errno); +		return FALSE; +	} + +	memcpy(&self->data->caps, rsp, sizeof(*rsp)); +	if (!gst_a2dp_sender_sink_update_caps(self)) { +		GST_WARNING_OBJECT(self, "failed to update capabilities"); +		return FALSE; +	} + +	return TRUE; +} + +static gboolean gst_a2dp_sender_sink_start(GstBaseSink *basesink) +{ +	GstA2dpSenderSink *self = GST_A2DP_SENDER_SINK(basesink); +	gint sk; +	gint err; + +	GST_INFO_OBJECT(self, "start"); + +	self->watch_id = 0; + +	sk = bt_audio_service_open(); +	if (sk <= 0) { +		err = errno; +		GST_ERROR_OBJECT(self, "Cannot open connection to bt " +			"audio service: %s %d", strerror(err), err); +		goto failed; +	} + +	self->server = g_io_channel_unix_new(sk); +	self->watch_id = g_io_add_watch(self->server, G_IO_HUP | G_IO_ERR | +					G_IO_NVAL, server_callback, self); + +	self->data = g_new0(struct bluetooth_data, 1); +	memset(self->data, 0, sizeof(struct bluetooth_data)); + +	self->stream = NULL; +	self->stream_caps = NULL; + +	if (!gst_a2dp_sender_sink_get_capabilities(self)) +		goto failed; + +	return TRUE; + +failed: +	bt_audio_service_close(sk); +	return FALSE; +} + +static gboolean gst_a2dp_sender_sink_stream_start(GstA2dpSenderSink *self) +{ +	gchar buf[BT_AUDIO_IPC_PACKET_SIZE]; +	struct bt_streamstart_req *req = (void *) buf; +	struct bt_streamstart_rsp *rsp = (void *) buf; +	struct bt_streamfd_ind *ind = (void*) buf; +	GIOError io_error; + +	GST_DEBUG_OBJECT(self, "stream start"); + +	memset (req, 0, sizeof(buf)); +	req->h.msg_type = BT_STREAMSTART_REQ; + +	io_error = gst_a2dp_sender_sink_audioservice_send(self, &req->h); +	if (io_error != G_IO_ERROR_NONE) { +		GST_ERROR_OBJECT(self, "Error ocurred while sending " +					"start packet"); +		return FALSE; +	} + +	GST_DEBUG_OBJECT(self, "stream start packet sent"); + +	io_error = gst_a2dp_sender_sink_audioservice_expect(self, +			&rsp->rsp_h.msg_h, BT_STREAMSTART_RSP); +	if (io_error != G_IO_ERROR_NONE) { +		GST_ERROR_OBJECT(self, "Error while stream " +			"start confirmation"); +		return FALSE; +	} + +	if (rsp->rsp_h.posix_errno != 0) { +		GST_ERROR_OBJECT(self, "BT_STREAMSTART_RSP failed : %s(%d)", +					strerror(rsp->rsp_h.posix_errno), +					rsp->rsp_h.posix_errno); +		return FALSE; +	} + +	GST_DEBUG_OBJECT(self, "stream started"); + +	io_error = gst_a2dp_sender_sink_audioservice_expect(self, &ind->h, +			BT_STREAMFD_IND); +	if (io_error != G_IO_ERROR_NONE) { +		GST_ERROR_OBJECT(self, "Error while receiving " +			"stream filedescriptor"); +		return FALSE; +	} + +	if (!gst_a2dp_sender_sink_conf_recv_stream_fd(self)) +		return FALSE; + +	return TRUE; +} + +static gboolean gst_a2dp_sender_sink_configure(GstA2dpSenderSink *self, +			GstCaps *caps) +{ +	gchar buf[BT_AUDIO_IPC_PACKET_SIZE]; +	struct bt_setconfiguration_req *req = (void *) buf; +	struct bt_setconfiguration_rsp *rsp = (void *) buf; +	gboolean ret; +	GIOError io_error; +	gchar *temp; + +	temp = gst_caps_to_string(caps); +	GST_DEBUG_OBJECT(self, "configuring device with caps: %s", temp); +	g_free(temp); + +	memset (req, 0, sizeof(buf)); +	req->h.msg_type = BT_SETCONFIGURATION_REQ; +	req->access_mode = BT_CAPABILITIES_ACCESS_MODE_WRITE; +	strncpy(req->device, self->device, 18); +	ret = gst_a2dp_sender_sink_init_pkt_conf(self, caps, +			&req->sbc_capabilities); +	if (!ret) { +		GST_ERROR_OBJECT(self, "Couldn't parse caps " +				"to packet configuration"); +		return FALSE; +	} + +	io_error = gst_a2dp_sender_sink_audioservice_send(self, &req->h); +	if (io_error != G_IO_ERROR_NONE) { +		GST_ERROR_OBJECT(self, "Error ocurred while sending " +					"configurarion packet"); +		return FALSE; +	} + +	GST_DEBUG_OBJECT(self, "configuration packet sent"); + +	io_error = gst_a2dp_sender_sink_audioservice_expect(self, +			&rsp->rsp_h.msg_h, BT_SETCONFIGURATION_RSP); +	if (io_error != G_IO_ERROR_NONE) { +		GST_ERROR_OBJECT(self, "Error while receiving device " +					"confirmation"); +		return FALSE; +	} + +	if (rsp->rsp_h.posix_errno != 0) { +		GST_ERROR_OBJECT(self, "BT_SETCONFIGURATION_RSP failed : " +					"%s(%d)", +					strerror(rsp->rsp_h.posix_errno), +					rsp->rsp_h.posix_errno); +		return FALSE; +	} + +	self->data->link_mtu = rsp->link_mtu; +	GST_DEBUG_OBJECT(self, "configuration set"); + +	return TRUE; +} + +static GstFlowReturn gst_a2dp_sender_sink_preroll(GstBaseSink *basesink, +					GstBuffer *buffer) +{ +	GstA2dpSenderSink *sink = GST_A2DP_SENDER_SINK(basesink); +	gboolean ret; + +	GST_A2DP_SENDER_SINK_MUTEX_LOCK(sink); + +	ret = gst_a2dp_sender_sink_stream_start(sink); + +	GST_A2DP_SENDER_SINK_MUTEX_UNLOCK(sink); + +	if (!ret) +		return GST_FLOW_ERROR; + +	return GST_FLOW_OK; +} + +static GstFlowReturn gst_a2dp_sender_sink_render(GstBaseSink *basesink, +					GstBuffer *buffer) +{ +	GstA2dpSenderSink *self = GST_A2DP_SENDER_SINK(basesink); +	gsize ret; +	GIOError err; + +	err = g_io_channel_write(self->stream, (gchar*)GST_BUFFER_DATA(buffer), +			 (gsize)(GST_BUFFER_SIZE(buffer)), &ret); + +	if (err != G_IO_ERROR_NONE) { +		GST_ERROR_OBJECT(self, "Error while writting to socket: %d %s", +				errno, strerror(errno)); +		return GST_FLOW_ERROR; +	} + +	return GST_FLOW_OK; +} + +static gboolean gst_a2dp_sender_sink_unlock(GstBaseSink *basesink) +{ +	GstA2dpSenderSink *self = GST_A2DP_SENDER_SINK(basesink); + +	if (self->stream != NULL) +		g_io_channel_flush (self->stream, NULL); + +	return TRUE; +} + +static GstFlowReturn gst_a2dp_sender_sink_buffer_alloc(GstBaseSink *basesink, +				guint64 offset, guint size, GstCaps* caps, +				GstBuffer **buf) +{ +	GstA2dpSenderSink *self = GST_A2DP_SENDER_SINK(basesink); + +	*buf = gst_buffer_new_and_alloc(size); +	if (!(*buf)) { +		GST_ERROR_OBJECT(self, "buffer allocation failed"); +		return GST_FLOW_ERROR; +	} + +	gst_buffer_set_caps(*buf, caps); + +	GST_BUFFER_OFFSET(*buf) = offset; + +	return GST_FLOW_OK; +} + +static void gst_a2dp_sender_sink_class_init(GstA2dpSenderSinkClass *klass) +{ +	GObjectClass *object_class = G_OBJECT_CLASS(klass); +	GstBaseSinkClass *basesink_class = GST_BASE_SINK_CLASS(klass); + +	parent_class = g_type_class_peek_parent(klass); + +	object_class->finalize = GST_DEBUG_FUNCPTR( +					gst_a2dp_sender_sink_finalize); +	object_class->set_property = GST_DEBUG_FUNCPTR( +					gst_a2dp_sender_sink_set_property); +	object_class->get_property = GST_DEBUG_FUNCPTR( +					gst_a2dp_sender_sink_get_property); + +	basesink_class->start = GST_DEBUG_FUNCPTR(gst_a2dp_sender_sink_start); +	basesink_class->stop = GST_DEBUG_FUNCPTR(gst_a2dp_sender_sink_stop); +	basesink_class->render = GST_DEBUG_FUNCPTR( +					gst_a2dp_sender_sink_render); +	basesink_class->preroll = GST_DEBUG_FUNCPTR( +					gst_a2dp_sender_sink_preroll); +	basesink_class->unlock = GST_DEBUG_FUNCPTR( +					gst_a2dp_sender_sink_unlock); + +	basesink_class->buffer_alloc = +		GST_DEBUG_FUNCPTR(gst_a2dp_sender_sink_buffer_alloc); + +	g_object_class_install_property(object_class, PROP_DEVICE, +					g_param_spec_string("device", "Device", +					"Bluetooth remote device address", +					NULL, G_PARAM_READWRITE)); + +	GST_DEBUG_CATEGORY_INIT(a2dp_sender_sink_debug, "a2dpsendersink", 0, +				"A2DP sink element"); +} + +static void gst_a2dp_sender_sink_init(GstA2dpSenderSink *self, +			GstA2dpSenderSinkClass *klass) +{ +	self->device = NULL; +	self->data = NULL; + +	self->stream = NULL; + +	self->dev_caps = NULL; + +	self->sink_lock = g_mutex_new(); +} + +static GIOError gst_a2dp_sender_sink_audioservice_send( +					GstA2dpSenderSink *self, +					const bt_audio_msg_header_t *msg) +{ +	GIOError error; +	gsize written; + +	error = g_io_channel_write(self->server, (const gchar*) msg, +			BT_AUDIO_IPC_PACKET_SIZE, &written); +	if (error != G_IO_ERROR_NONE) +		GST_ERROR_OBJECT(self, "Error sending data to audio service:" +			" %s(%d)", strerror(errno), errno); + +	return error; +} + +static GIOError gst_a2dp_sender_sink_audioservice_recv( +					GstA2dpSenderSink *self, +					bt_audio_msg_header_t *inmsg) +{ +	GIOError status; +	gsize bytes_read; +	const char *type; + +	status = g_io_channel_read(self->server, (gchar*) inmsg, +			BT_AUDIO_IPC_PACKET_SIZE, &bytes_read); +	if (status != G_IO_ERROR_NONE) { +		GST_ERROR_OBJECT(self, "Error receiving data from " +				"audio service"); +		return status; +	} + +	type = bt_audio_strmsg(inmsg->msg_type); +	if (!type) { +		status = G_IO_ERROR_INVAL; +		GST_ERROR_OBJECT(self, "Bogus message type %d " +				"received from audio service", +				inmsg->msg_type); +	} + +	return status; +} + +static GIOError gst_a2dp_sender_sink_audioservice_expect( +			GstA2dpSenderSink *self, bt_audio_msg_header_t *outmsg, +			int expected_type) +{ +	GIOError status; + +	status = gst_a2dp_sender_sink_audioservice_recv(self, outmsg); +	if (status != G_IO_ERROR_NONE) +		return status; + +	if (outmsg->msg_type != expected_type) +		status = G_IO_ERROR_INVAL; + +	return status; +} + +gboolean gst_a2dp_sender_sink_plugin_init (GstPlugin * plugin) +{ +	return gst_element_register (plugin, "a2dpsendersink", +			GST_RANK_NONE, GST_TYPE_A2DP_SENDER_SINK); +} + + +/* public functions */ +GstCaps *gst_a2dp_sender_sink_get_device_caps(GstA2dpSenderSink *sink) +{ +	if (sink->dev_caps == NULL) +		return NULL; + +	return gst_caps_copy(sink->dev_caps); +} + +gboolean gst_a2dp_sender_sink_set_device_caps(GstA2dpSenderSink *self, +			GstCaps *caps) +{ +	gboolean ret; + +	GST_DEBUG_OBJECT(self, "setting device caps"); +	GST_A2DP_SENDER_SINK_MUTEX_LOCK(self); +	ret = gst_a2dp_sender_sink_configure(self, caps); + +	if (self->stream_caps) +		gst_caps_unref(self->stream_caps); +	self->stream_caps = gst_caps_ref(caps); + +	GST_A2DP_SENDER_SINK_MUTEX_UNLOCK(self); + +	return ret; +} + +guint gst_a2dp_sender_sink_get_link_mtu(GstA2dpSenderSink *sink) +{ +	return sink->data->link_mtu; +} + +void gst_a2dp_sender_sink_set_device(GstA2dpSenderSink *self, const gchar* dev) +{ +	if (self->device != NULL) +		g_free(self->device); + +	GST_LOG_OBJECT(self, "Setting device: %s", dev); +	self->device = g_strdup(dev); +} + +gchar *gst_a2dp_sender_sink_get_device(GstA2dpSenderSink *self) +{ +	return g_strdup(self->device); +} + diff --git a/audio/gsta2dpsendersink.h b/audio/gsta2dpsendersink.h new file mode 100644 index 00000000..863ef6cb --- /dev/null +++ b/audio/gsta2dpsendersink.h @@ -0,0 +1,90 @@ +/* + * + *  BlueZ - Bluetooth protocol stack for Linux + * + *  Copyright (C) 2004-2007  Marcel Holtmann <marcel@holtmann.org> + * + * + *  This library 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. + * + *  This library 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 this library; if not, write to the Free Software + *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA + * + */ + +#ifndef __GST_A2DP_SENDER_SINK_H +#define __GST_A2DP_SENDER_SINK_H + +#include <gst/gst.h> +#include <gst/base/gstbasesink.h> + +G_BEGIN_DECLS + +#define GST_TYPE_A2DP_SENDER_SINK \ +	(gst_a2dp_sender_sink_get_type()) +#define GST_A2DP_SENDER_SINK(obj) \ +	(G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_A2DP_SENDER_SINK,\ +		GstA2dpSenderSink)) +#define GST_A2DP_SENDER_SINK_CLASS(klass) \ +	(G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_A2DP_SENDER_SINK,\ +		GstA2dpSenderSinkClass)) +#define GST_IS_A2DP_SENDER_SINK(obj) \ +	(G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_A2DP_SENDER_SINK)) +#define GST_IS_A2DP_SENDER_SINK_CLASS(obj) \ +	(G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_A2DP_SENDER_SINK)) + +typedef struct _GstA2dpSenderSink GstA2dpSenderSink; +typedef struct _GstA2dpSenderSinkClass GstA2dpSenderSinkClass; + +struct bluetooth_data; + +struct _GstA2dpSenderSink { +	GstBaseSink sink; + +	gchar *device; +	GIOChannel *stream; + +	struct bluetooth_data *data; +	GIOChannel *server; + +	/* stream connection data */ +	GstCaps *stream_caps; + +	GstCaps *dev_caps; + +	GMutex *sink_lock; + +	guint watch_id; +}; + +struct _GstA2dpSenderSinkClass { +	GstBaseSinkClass parent_class; +}; + +GType gst_a2dp_sender_sink_get_type(void); + +GstCaps *gst_a2dp_sender_sink_get_device_caps(GstA2dpSenderSink *sink); +gboolean gst_a2dp_sender_sink_set_device_caps(GstA2dpSenderSink *sink, +			GstCaps *caps); + +guint gst_a2dp_sender_sink_get_link_mtu(GstA2dpSenderSink *sink); + +void gst_a2dp_sender_sink_set_device(GstA2dpSenderSink *sink, +		const gchar* device); + +gchar *gst_a2dp_sender_sink_get_device(GstA2dpSenderSink *sink); + +gboolean gst_a2dp_sender_sink_plugin_init(GstPlugin *plugin); + +G_END_DECLS + +#endif /* __GST_A2DP_SENDER_SINK_H */ diff --git a/audio/gsta2dpsink.c b/audio/gsta2dpsink.c index 5798bc24..d90909c7 100644 --- a/audio/gsta2dpsink.c +++ b/audio/gsta2dpsink.c @@ -26,166 +26,67 @@  #endif  #include <unistd.h> -#include <sys/un.h> -#include <sys/socket.h> -#include <fcntl.h>  #include <pthread.h> -#include <netinet/in.h> - -#include <bluetooth/bluetooth.h> - -#include "ipc.h" -#include "rtp.h"  #include "gstsbcutil.h" -  #include "gsta2dpsink.h" -GST_DEBUG_CATEGORY_STATIC(a2dp_sink_debug); -#define GST_CAT_DEFAULT a2dp_sink_debug - -#define BUFFER_SIZE 2048 -#define TEMPLATE_MAX_BITPOOL_VALUE 64 +GST_DEBUG_CATEGORY_STATIC(gst_a2dp_sink_debug); +#define GST_CAT_DEFAULT gst_a2dp_sink_debug -#define GST_A2DP_SINK_MUTEX_LOCK(s) G_STMT_START {	\ -		g_mutex_lock (s->sink_lock);		\ -	} G_STMT_END - -#define GST_A2DP_SINK_MUTEX_UNLOCK(s) G_STMT_START {	\ -		g_mutex_unlock (s->sink_lock);		\ -	} G_STMT_END - - -struct bluetooth_data { -	struct bt_getcapabilities_rsp caps; /* Bluetooth device capabilities */ -	struct bt_setconfiguration_rsp cfg; /* Bluetooth device configuration */ -	int samples;			/* Number of encoded samples */ -	gchar buffer[BUFFER_SIZE];	/* Codec transfer buffer */ -	gsize count;			/* Codec transfer buffer counter */ - -	int nsamples;			/* Cumulative number of codec samples */ -	uint16_t seq_num;		/* Cumulative packet sequence */ -	int frame_count;		/* Current frames in buffer*/ -}; - - -#define IS_SBC(n) (strcmp((n), "audio/x-sbc") == 0) -#define IS_MPEG(n) (strcmp((n), "audio/mpeg") == 0) +#define A2DP_SBC_RTP_PAYLOAD_TYPE 1 +#define TEMPLATE_MAX_BITPOOL_STR "64"  enum {  	PROP_0, -	PROP_DEVICE, +	PROP_DEVICE  }; -GST_BOILERPLATE(GstA2dpSink, gst_a2dp_sink, GstBaseSink, GST_TYPE_BASE_SINK); +GST_BOILERPLATE(GstA2dpSink, gst_a2dp_sink, GstBin, GST_TYPE_BIN); -static const GstElementDetails a2dp_sink_details = +static const GstElementDetails gst_a2dp_sink_details =  	GST_ELEMENT_DETAILS("Bluetooth A2DP sink",  				"Sink/Audio",  				"Plays audio to an A2DP device",  				"Marcel Holtmann <marcel@holtmann.org>"); -static GstStaticPadTemplate a2dp_sink_factory = +static GstStaticPadTemplate gst_a2dp_sink_factory =  	GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS, -		GST_STATIC_CAPS("audio/x-sbc, " +			GST_STATIC_CAPS("audio/x-sbc, "  				"rate = (int) { 16000, 32000, 44100, 48000 }, "  				"channels = (int) [ 1, 2 ], "  				"mode = (string) { mono, dual, stereo, joint }, "  				"blocks = (int) { 4, 8, 12, 16 }, "  				"subbands = (int) { 4, 8 }, " -				"allocation = (string) { snr, loudness }," -				/* FIXME use constant here */ -				"bitpool = (int) [ 2, 64 ]; " -				"audio/mpeg, " -				"mpegversion = (int) 1, " -				"layer = (int) [ 1, 3 ], " -				"rate = (int) { 16000, 22050, 24000, 32000, 44100, 48000 }, " -				"channels = (int) [ 1, 2 ]")); - -static GIOError gst_a2dp_sink_audioservice_send(GstA2dpSink *self, -					const bt_audio_msg_header_t *msg); -static GIOError gst_a2dp_sink_audioservice_expect(GstA2dpSink *self, -				bt_audio_msg_header_t *outmsg, -				int expected_type); - +				"allocation = (string) { snr, loudness }, " +				"bitpool = (int) [ 2, " +				TEMPLATE_MAX_BITPOOL_STR " ]; " +				));  static void gst_a2dp_sink_base_init(gpointer g_class)  {  	GstElementClass *element_class = GST_ELEMENT_CLASS(g_class); +	gst_element_class_set_details(element_class, +		&gst_a2dp_sink_details);  	gst_element_class_add_pad_template(element_class, -		gst_static_pad_template_get(&a2dp_sink_factory)); - -	gst_element_class_set_details(element_class, &a2dp_sink_details); -} - -static gboolean gst_a2dp_sink_stop(GstBaseSink *basesink) -{ -	GstA2dpSink *self = GST_A2DP_SINK(basesink); - -	GST_INFO_OBJECT(self, "stop"); - -	if (self->watch_id != 0) { -		g_source_remove(self->watch_id); -		self->watch_id = 0; -	} - -	if (self->stream) { -		g_io_channel_flush(self->stream, NULL); -		g_io_channel_close(self->stream); -		g_io_channel_unref(self->stream); -		self->stream = NULL; -	} - -	if (self->server) { -		bt_audio_service_close(g_io_channel_unix_get_fd(self->server)); -		g_io_channel_unref(self->server); -		self->server = NULL; -	} - -	if (self->data) { -		g_free(self->data); -		self->data = NULL; -	} - -	if (self->stream_caps) { -		gst_caps_unref(self->stream_caps); -		self->stream_caps = NULL; -	} - -	if (self->dev_caps) { -		gst_caps_unref(self->dev_caps); -		self->dev_caps = NULL; -	} - -	return TRUE; -} - -static void gst_a2dp_sink_finalize(GObject *object) -{ -	GstA2dpSink *self = GST_A2DP_SINK(object); - -	if (self->data) -		gst_a2dp_sink_stop(GST_BASE_SINK(self)); - -	if (self->device) -		g_free(self->device); - -	g_mutex_free(self->sink_lock); - -	G_OBJECT_CLASS(parent_class)->finalize(object); +		gst_static_pad_template_get(&gst_a2dp_sink_factory));  }  static void gst_a2dp_sink_set_property(GObject *object, guint prop_id,  					const GValue *value, GParamSpec *pspec)  { -	GstA2dpSink *sink = GST_A2DP_SINK(object); +	GstA2dpSink *self = GST_A2DP_SINK(object);  	switch (prop_id) {  	case PROP_DEVICE: -		if (sink->device) -			g_free(sink->device); -		sink->device = g_value_dup_string(value); +		if (self->sink != NULL) +			gst_a2dp_sender_sink_set_device(self->sink, +				g_value_get_string(value)); + +		if (self->device != NULL) +			g_free(self->device); +		self->device = g_value_dup_string(value);  		break;  	default: @@ -197,11 +98,16 @@ static void gst_a2dp_sink_set_property(GObject *object, guint prop_id,  static void gst_a2dp_sink_get_property(GObject *object, guint prop_id,  					GValue *value, GParamSpec *pspec)  { -	GstA2dpSink *sink = GST_A2DP_SINK(object); +	GstA2dpSink *self = GST_A2DP_SINK(object); +	gchar *device;  	switch (prop_id) {  	case PROP_DEVICE: -		g_value_set_string(value, sink->device); +		if (self->sink != NULL) { +			device = gst_a2dp_sender_sink_get_device(self->sink); +			if (device != NULL) +				g_value_take_string(value, device); +		}  		break;  	default: @@ -210,775 +116,305 @@ static void gst_a2dp_sink_get_property(GObject *object, guint prop_id,  	}  } -static gint gst_a2dp_sink_bluetooth_recvmsg_fd(GstA2dpSink *sink) -{ -	int err, ret; - -	ret = bt_audio_service_get_data_fd(g_io_channel_unix_get_fd(sink->server)); - -	if (ret < 0) { -		err = errno; -		GST_ERROR_OBJECT(sink, "Unable to receive fd: %s (%d)", -				strerror(err), err); -		return -err; -	} - -	sink->stream = g_io_channel_unix_new(ret); -	GST_DEBUG_OBJECT(sink, "stream_fd=%d", ret); - -	return 0; -} - -static gboolean gst_a2dp_sink_init_pkt_conf(GstA2dpSink *sink, -					GstCaps *caps, -					sbc_capabilities_t *pkt) +static GstStateChangeReturn gst_a2dp_sink_change_state(GstElement *element, +			GstStateChange transition)  { -	sbc_capabilities_t *cfg = &sink->data->caps.sbc_capabilities; -	const GValue *value = NULL; -	const char *pref, *name; -	gint rate, blocks, subbands; -	GstStructure *structure = gst_caps_get_structure(caps, 0); - -	name = gst_structure_get_name(structure); -	/* FIXME only sbc supported here, should suport mp3 */ -	if (!(IS_SBC(name))) { -		GST_ERROR_OBJECT(sink, "Unsupported format %s", name); -		return FALSE; -	} - -	value = gst_structure_get_value(structure, "rate"); -	rate = g_value_get_int(value); -	if (rate == 44100) -		cfg->frequency = BT_A2DP_SAMPLING_FREQ_44100; -	else if (rate == 48000) -		cfg->frequency = BT_A2DP_SAMPLING_FREQ_48000; -	else if (rate == 32000) -		cfg->frequency = BT_A2DP_SAMPLING_FREQ_32000; -	else if (rate == 16000) -		cfg->frequency = BT_A2DP_SAMPLING_FREQ_16000; -	else { -		GST_ERROR_OBJECT(sink, "Invalid rate while setting caps"); -		return FALSE; -	} - -	value = gst_structure_get_value(structure, "mode"); -	pref = g_value_get_string(value); -	if (strcmp(pref, "auto") == 0) -		cfg->channel_mode = BT_A2DP_CHANNEL_MODE_AUTO; -	else if (strcmp(pref, "mono") == 0) -		cfg->channel_mode = BT_A2DP_CHANNEL_MODE_MONO; -	else if (strcmp(pref, "dual") == 0) -		cfg->channel_mode = BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL; -	else if (strcmp(pref, "stereo") == 0) -		cfg->channel_mode = BT_A2DP_CHANNEL_MODE_STEREO; -	else if (strcmp(pref, "joint") == 0) -		cfg->channel_mode = BT_A2DP_CHANNEL_MODE_JOINT_STEREO; -	else { -		GST_ERROR_OBJECT(sink, "Invalid mode %s", pref); -		return FALSE; -	} +	GstA2dpSink *self = GST_A2DP_SINK(element); -	value = gst_structure_get_value(structure, "allocation"); -	pref = g_value_get_string(value); -	if (strcmp(pref, "auto") == 0) -		cfg->allocation_method = BT_A2DP_ALLOCATION_AUTO; -	else if (strcmp(pref, "loudness") == 0) -		cfg->allocation_method = BT_A2DP_ALLOCATION_LOUDNESS; -	else if (strcmp(pref, "snr") == 0) -		cfg->allocation_method = BT_A2DP_ALLOCATION_SNR; -	else { -		GST_ERROR_OBJECT(sink, "Invalid allocation: %s", pref); -		return FALSE; -	} +	switch (transition) { +	case GST_STATE_CHANGE_NULL_TO_READY: +		gst_element_set_state(GST_ELEMENT(self->sink), +			GST_STATE_READY); +		break; -	value = gst_structure_get_value(structure, "subbands"); -	subbands = g_value_get_int(value); -	if (subbands == 8) -		cfg->subbands = BT_A2DP_SUBBANDS_8; -	else if (subbands == 4) -		cfg->subbands = BT_A2DP_SUBBANDS_4; -	else { -		GST_ERROR_OBJECT(sink, "Invalid subbands %d", subbands); -		return FALSE; -	} +	case GST_STATE_CHANGE_READY_TO_NULL: +		if (self->newseg_event != NULL) { +			gst_event_unref(self->newseg_event); +			self->newseg_event = NULL; +		} +		break; -	value = gst_structure_get_value(structure, "blocks"); -	blocks = g_value_get_int(value); -	if (blocks == 16) -		cfg->block_length = BT_A2DP_BLOCK_LENGTH_16; -	else if (blocks == 12) -		cfg->block_length = BT_A2DP_BLOCK_LENGTH_12; -	else if (blocks == 8) -		cfg->block_length = BT_A2DP_BLOCK_LENGTH_8; -	else if (blocks == 4) -		cfg->block_length = BT_A2DP_BLOCK_LENGTH_4; -	else { -		GST_ERROR_OBJECT(sink, "Invalid blocks %d", blocks); -		return FALSE; +	default: +		break;  	} -	/* FIXME min and max ??? */ -	value = gst_structure_get_value(structure, "bitpool"); - -	cfg->max_bitpool = cfg->min_bitpool = g_value_get_int(value); - -	memcpy(pkt, cfg, sizeof(*pkt)); - -	return TRUE; +	return GST_ELEMENT_CLASS(parent_class)->change_state(element, +			transition);  } -static gboolean gst_a2dp_sink_conf_recv_stream_fd(GstA2dpSink *self) +static void gst_a2dp_sink_class_init(GstA2dpSinkClass *klass)  { -	struct bluetooth_data *data = self->data; -	gint ret; -	GIOError err; -	GError *gerr = NULL; -	GIOStatus status; -	GIOFlags flags; -	gsize read; - -	ret = gst_a2dp_sink_bluetooth_recvmsg_fd(self); -	if (ret < 0) -		return FALSE; - -	if (!self->stream) { -		GST_ERROR_OBJECT(self, "Error while configuring device: " -				"could not acquire audio socket"); -		return FALSE; -	} +	GObjectClass *object_class = G_OBJECT_CLASS(klass); +	GstElementClass *element_class = GST_ELEMENT_CLASS(klass); -	/* set stream socket to nonblock */ -	GST_LOG_OBJECT(self, "setting stream socket to nonblock"); -	flags = g_io_channel_get_flags(self->stream); -	flags |= G_IO_FLAG_NONBLOCK; -	status = g_io_channel_set_flags(self->stream, flags, &gerr); -	if (status != G_IO_STATUS_NORMAL) { -		if (gerr) -			GST_WARNING_OBJECT(self, "Error while " -				"setting server socket to nonblock: " -				"%s", gerr->message); -		else -			GST_WARNING_OBJECT(self, "Error while " -				"setting server " -				"socket to nonblock"); -	} +	parent_class = g_type_class_peek_parent(klass); -	/* It is possible there is some outstanding -	data in the pipe - we have to empty it */ -	GST_LOG_OBJECT(self, "emptying stream pipe"); -	while (1) { -		err = g_io_channel_read(self->stream, data->buffer, -					(gsize) data->cfg.link_mtu, -					&read); -		if (err != G_IO_ERROR_NONE || read <= 0) -			break; -	} +	object_class->set_property = GST_DEBUG_FUNCPTR( +					gst_a2dp_sink_set_property); +	object_class->get_property = GST_DEBUG_FUNCPTR( +					gst_a2dp_sink_get_property); -	/* set stream socket to block */ -	GST_LOG_OBJECT(self, "setting stream socket to block"); -	flags = g_io_channel_get_flags(self->stream); -	flags &= ~G_IO_FLAG_NONBLOCK; -	status = g_io_channel_set_flags(self->stream, flags, &gerr); -	if (status != G_IO_STATUS_NORMAL) { -		if (gerr) -			GST_WARNING_OBJECT(self, "Error while " -				"setting server socket to block:" -				"%s", gerr->message); -		else -			GST_WARNING_OBJECT(self, "Error while " -				"setting server " -				"socket to block"); -	} +	element_class->change_state = GST_DEBUG_FUNCPTR( +					gst_a2dp_sink_change_state); -	memset(data->buffer, 0, sizeof(data->buffer)); +	g_object_class_install_property(object_class, PROP_DEVICE, +			g_param_spec_string("device", "Device", +			"Bluetooth remote device address", +			NULL, G_PARAM_READWRITE)); -	return TRUE; +	GST_DEBUG_CATEGORY_INIT(gst_a2dp_sink_debug, "a2dpsink", 0, +				"A2DP sink element");  } -static gboolean server_callback(GIOChannel *chan, -					GIOCondition cond, gpointer data) +static GstCaps *gst_a2dp_sink_get_device_caps(GstA2dpSink *self)  { -	GstA2dpSink *sink; - -	if (cond & G_IO_HUP || cond & G_IO_NVAL) -		return FALSE; -	else if (cond & G_IO_ERR) { -		sink = GST_A2DP_SINK(data); -		GST_WARNING_OBJECT(sink, "Untreated callback G_IO_ERR"); -	} - -	return TRUE; +	return gst_a2dp_sender_sink_get_device_caps(self->sink);  } -static gboolean gst_a2dp_sink_update_caps(GstA2dpSink *self) +static GstCaps *gst_a2dp_sink_get_caps(GstPad *pad)  { -	sbc_capabilities_t *sbc = &self->data->caps.sbc_capabilities; -	GstStructure *structure; -	GValue *value; -	GValue *list; -	gchar *tmp; - -	GST_LOG_OBJECT(self, "updating device caps"); - -	structure = gst_structure_empty_new("audio/x-sbc"); -	value = g_value_init(g_new0(GValue, 1), G_TYPE_STRING); - -	/* mode */ -	list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST); -	if (sbc->channel_mode == BT_A2DP_CHANNEL_MODE_AUTO) { -		g_value_set_static_string(value, "joint"); -		gst_value_list_prepend_value(list, value); -		g_value_set_static_string(value, "stereo"); -		gst_value_list_prepend_value(list, value); -		g_value_set_static_string(value, "mono"); -		gst_value_list_prepend_value(list, value); -		g_value_set_static_string(value, "dual"); -		gst_value_list_prepend_value(list, value); -	} else { -		if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_MONO) { -			g_value_set_static_string(value, "mono"); -			gst_value_list_prepend_value(list, value); -		} -		if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_STEREO) { -			g_value_set_static_string(value, "stereo"); -			gst_value_list_prepend_value(list, value); -		} -		if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL) { -			g_value_set_static_string(value, "dual"); -			gst_value_list_prepend_value(list, value); -		} -		if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_JOINT_STEREO) { -			g_value_set_static_string(value, "joint"); -			gst_value_list_prepend_value(list, value); -		} -	} -	g_value_unset(value); -	if (list) { -		gst_structure_set_value(structure, "mode", list); -		g_free(list); -		list = NULL; -	} - -	/* subbands */ -	list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST); -	value = g_value_init(value, G_TYPE_INT); -	if (sbc->subbands & BT_A2DP_SUBBANDS_4) { -		g_value_set_int(value, 4); -		gst_value_list_prepend_value(list, value); -	} -	if (sbc->subbands & BT_A2DP_SUBBANDS_8) { -		g_value_set_int(value, 8); -		gst_value_list_prepend_value(list, value); -	} -	g_value_unset(value); -	if (list) { -		gst_structure_set_value(structure, "subbands", list); -		g_free(list); -		list = NULL; -	} - -	/* blocks */ -	value = g_value_init(value, G_TYPE_INT); -	list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST); -	if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_16) { -		g_value_set_int(value, 16); -		gst_value_list_prepend_value(list, value); -	} -	if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_12) { -		g_value_set_int(value, 12); -		gst_value_list_prepend_value(list, value); -	} -	if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_8) { -		g_value_set_int(value, 8); -		gst_value_list_prepend_value(list, value); -	} -	if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_4) { -		g_value_set_int(value, 4); -		gst_value_list_prepend_value(list, value); -	} -	g_value_unset(value); -	if (list) { -		gst_structure_set_value(structure, "blocks", list); -		g_free(list); -		list = NULL; -	} - -	/* allocation */ -	g_value_init(value, G_TYPE_STRING); -	list = g_value_init(g_new0(GValue,1), GST_TYPE_LIST); -	if (sbc->allocation_method == BT_A2DP_ALLOCATION_AUTO) { -		g_value_set_static_string(value, "loudness"); -		gst_value_list_prepend_value(list, value); -		g_value_set_static_string(value, "snr"); -		gst_value_list_prepend_value(list, value); +	GstCaps *caps; +	GstCaps *caps_aux; +	GstA2dpSink *self = GST_A2DP_SINK(GST_PAD_PARENT(pad)); + +	if (self->sink == NULL) { +		GST_DEBUG_OBJECT(self, "a2dpsink isn't initialized " +			"returning template caps"); +		caps = gst_static_pad_template_get_caps( +				&gst_a2dp_sink_factory);  	} else { -		if (sbc->allocation_method & BT_A2DP_ALLOCATION_LOUDNESS) { -			g_value_set_static_string(value, "loudness"); -			gst_value_list_prepend_value(list, value); -		} -		if (sbc->allocation_method & BT_A2DP_ALLOCATION_SNR) { -			g_value_set_static_string(value, "snr"); -			gst_value_list_prepend_value(list, value); -		} -	} -	g_value_unset(value); -	if (list) { -		gst_structure_set_value(structure, "allocation", list); -		g_free(list); -		list = NULL; -	} - -	/* rate */ -	g_value_init(value, G_TYPE_INT); -	list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST); -	if (sbc->frequency & BT_A2DP_SAMPLING_FREQ_48000) { -		g_value_set_int(value, 48000); -		gst_value_list_prepend_value(list, value); -	} -	if (sbc->frequency & BT_A2DP_SAMPLING_FREQ_44100) { -		g_value_set_int(value, 44100); -		gst_value_list_prepend_value(list, value); -	} -	if (sbc->frequency & BT_A2DP_SAMPLING_FREQ_32000) { -		g_value_set_int(value, 32000); -		gst_value_list_prepend_value(list, value); -	} -	if (sbc->frequency & BT_A2DP_SAMPLING_FREQ_16000) { -		g_value_set_int(value, 16000); -		gst_value_list_prepend_value(list, value); -	} -	g_value_unset(value); -	if (list) { -		gst_structure_set_value(structure, "rate", list); -		g_free(list); -		list = NULL; -	} - -	/* bitpool */ -	value = g_value_init(value, GST_TYPE_INT_RANGE); -	gst_value_set_int_range(value, -			MIN(sbc->min_bitpool, TEMPLATE_MAX_BITPOOL_VALUE), -			MIN(sbc->max_bitpool, TEMPLATE_MAX_BITPOOL_VALUE)); -	gst_structure_set_value(structure, "bitpool", value); - -	/* channels */ -	gst_value_set_int_range(value, 1, 2); -	gst_structure_set_value(structure, "channels", value); - -	g_free(value); - -	if (self->dev_caps != NULL) -		gst_caps_unref(self->dev_caps); -	self->dev_caps = gst_caps_new_full(structure, NULL); - -	tmp = gst_caps_to_string(self->dev_caps); -	GST_DEBUG_OBJECT(self, "Device capabilities: %s", tmp); -	g_free(tmp); - -	return TRUE; +		GST_LOG_OBJECT(self, "Getting device caps"); +		caps = gst_a2dp_sink_get_device_caps(self); +		if (caps == NULL) +			caps = gst_static_pad_template_get_caps( +                                &gst_a2dp_sink_factory); +	} +	caps_aux = gst_caps_copy(caps); +	g_object_set(self->capsfilter, "caps", caps_aux, NULL); +	gst_caps_unref(caps_aux); +	return caps;  } -static gboolean gst_a2dp_sink_get_capabilities(GstA2dpSink *self) +static gboolean gst_a2dp_sink_init_sender_sink(GstA2dpSink *self)  { -	gchar *buf[BT_AUDIO_IPC_PACKET_SIZE]; -	struct bt_getcapabilities_req *req = (void *) buf; -	bt_audio_rsp_msg_header_t *rsp_hdr = (void *) buf; -	struct bt_getcapabilities_rsp *rsp = (void *) buf; -	GIOError io_error; - -	memset(req, 0, BT_AUDIO_IPC_PACKET_SIZE); +	GstElement *sink; -	req->h.msg_type = BT_GETCAPABILITIES_REQ; -	strncpy(req->device, self->device, 18); - -	io_error = gst_a2dp_sink_audioservice_send(self, &req->h); -	if (io_error != G_IO_ERROR_NONE) { -		GST_ERROR_OBJECT(self, "Error while asking device caps"); -	} +	if (self->sink == NULL) +		sink = gst_element_factory_make("a2dpsendersink", "sendersink"); +	else +		sink = GST_ELEMENT(self->sink); -	io_error = gst_a2dp_sink_audioservice_expect(self, &rsp_hdr->msg_h, -			BT_GETCAPABILITIES_RSP); -	if (io_error != G_IO_ERROR_NONE) { -		GST_ERROR_OBJECT(self, "Error while getting device caps"); +	if (sink == NULL) { +		GST_ERROR_OBJECT(self, "Couldn't create a2dpsendersink");  		return FALSE;  	} -	if (rsp_hdr->posix_errno != 0) { -		GST_ERROR_OBJECT(self, "BT_GETCAPABILITIES failed : %s(%d)", -					strerror(rsp_hdr->posix_errno), -					rsp_hdr->posix_errno); -		return FALSE; +	if (!gst_bin_add(GST_BIN(self), sink)) { +		GST_ERROR_OBJECT(self, "failed to add a2dpsendersink " +			"to the bin"); +		goto cleanup_and_fail;  	} -	memcpy(&self->data->caps, rsp, sizeof(*rsp)); -	if (!gst_a2dp_sink_update_caps(self)) { -		GST_WARNING_OBJECT(self, "failed to update capabilities"); -		return FALSE; +	if (gst_element_set_state(sink, GST_STATE_READY) == +			GST_STATE_CHANGE_FAILURE) { +		GST_ERROR_OBJECT(self, "a2dpsendersink failed to go to ready"); +		goto remove_element_and_fail;  	} -	return TRUE; -} - -static gboolean gst_a2dp_sink_start(GstBaseSink *basesink) -{ -	GstA2dpSink *self = GST_A2DP_SINK(basesink); -	gint sk; -	gint err; - -	GST_INFO_OBJECT(self, "start"); - -	self->watch_id = 0; - -	sk = bt_audio_service_open(); -	if (sk <= 0) { -		err = errno; -		GST_ERROR_OBJECT(self, "Cannot open connection to bt " -			"audio service: %s %d", strerror(err), err); -		goto failed; +	if (!gst_element_link(GST_ELEMENT(self->rtp), sink)) { +		GST_ERROR_OBJECT(self, "couldn't link rtpsbcpay " +			"to a2dpsendersink"); +		goto remove_element_and_fail;  	} -	self->server = g_io_channel_unix_new(sk); -	self->watch_id = g_io_add_watch(self->server, G_IO_HUP | G_IO_ERR | -					G_IO_NVAL, server_callback, self); +	self->sink = GST_A2DP_SENDER_SINK(sink); +	g_object_set(G_OBJECT(self->sink), "device", self->device, NULL); -	self->data = g_new0(struct bluetooth_data, 1); -	memset(self->data, 0, sizeof(struct bluetooth_data)); +	gst_element_set_state(sink, GST_STATE_PAUSED); -	self->stream = NULL; -	self->stream_caps = NULL; +	return TRUE; -	if (!gst_a2dp_sink_get_capabilities(self)) -		goto failed; +remove_element_and_fail: +	gst_element_set_state (sink, GST_STATE_NULL); +	gst_bin_remove(GST_BIN(self), sink); +	return FALSE; -	return TRUE; +cleanup_and_fail: +	if (sink != NULL) +		g_object_unref(G_OBJECT(sink)); -failed: -	bt_audio_service_close(sk);  	return FALSE;  } -static gboolean gst_a2dp_sink_stream_start(GstA2dpSink *self) +static gboolean gst_a2dp_sink_init_rtp_sbc_element(GstA2dpSink *self)  { -	gchar buf[BT_AUDIO_IPC_PACKET_SIZE]; -	struct bt_streamstart_req *req = (void *) buf; -	bt_audio_rsp_msg_header_t *rsp_hdr = (void *) buf; -	struct bt_streamfd_ind *ind = (void*) buf; -	GIOError io_error; - -	GST_DEBUG_OBJECT(self, "stream start"); - -	memset (req, 0, sizeof(buf)); -	req->h.msg_type = BT_STREAMSTART_REQ; - -	io_error = gst_a2dp_sink_audioservice_send(self, &req->h); -	if (io_error != G_IO_ERROR_NONE) { -		GST_ERROR_OBJECT(self, "Error ocurred while sending " -					"start packet"); -		return FALSE; -	} - -	GST_DEBUG_OBJECT(self, "stream start packet sent"); - -	io_error = gst_a2dp_sink_audioservice_expect(self, &rsp_hdr->msg_h, -			BT_STREAMSTART_RSP); -	if (io_error != G_IO_ERROR_NONE) { -		GST_ERROR_OBJECT(self, "Error while stream start confirmation"); -		return FALSE; -	} - -	if (rsp_hdr->posix_errno != 0) { -		GST_ERROR_OBJECT(self, "BT_STREAMSTART_RSP failed : %s(%d)", -					strerror(rsp_hdr->posix_errno), -					rsp_hdr->posix_errno); -		return FALSE; -	} - -	GST_DEBUG_OBJECT(self, "stream started"); +	GstElement *rtppay; -	io_error = gst_a2dp_sink_audioservice_expect(self, &ind->h, -			BT_STREAMFD_IND); -	if (io_error != G_IO_ERROR_NONE) { -		GST_ERROR_OBJECT(self, "Error while receiving stream fd"); +	rtppay = gst_element_factory_make("rtpsbcpay", "rtp"); +	if (rtppay == NULL) { +		GST_ERROR_OBJECT(self, "Couldn't create rtpsbcpay");  		return FALSE;  	} -	if (!gst_a2dp_sink_conf_recv_stream_fd(self)) -		return FALSE; - -	return TRUE; -} - -static gboolean gst_a2dp_sink_configure(GstA2dpSink *self, GstCaps *caps) -{ -	gchar buf[BT_AUDIO_IPC_PACKET_SIZE]; -	struct bt_setconfiguration_req *req = (void *) buf; -	bt_audio_rsp_msg_header_t *rsp_hdr  = (void *) buf; -	struct bt_setconfiguration_rsp *rsp = (void *) buf; -	gboolean ret; -	GIOError io_error; - -	GST_DEBUG_OBJECT(self, "configuring device"); - -	memset (req, 0, sizeof(buf)); -	req->h.msg_type = BT_SETCONFIGURATION_REQ; -	req->access_mode = BT_CAPABILITIES_ACCESS_MODE_WRITE; -	strncpy(req->device, self->device, 18); -	ret = gst_a2dp_sink_init_pkt_conf(self, caps, &req->sbc_capabilities); -	if (!ret) { -		GST_ERROR_OBJECT(self, "Couldn't parse caps " -				"to packet configuration"); -		return FALSE; +	if (!gst_bin_add(GST_BIN(self), rtppay)) { +		GST_ERROR_OBJECT(self, "failed to add rtp sbc pay to the bin"); +		goto cleanup_and_fail;  	} -	io_error = gst_a2dp_sink_audioservice_send(self, &req->h); -	if (io_error != G_IO_ERROR_NONE) { -		GST_ERROR_OBJECT(self, "Error ocurred while sending " -					"configurarion packet"); -		return FALSE; +	if (gst_element_set_state(rtppay, GST_STATE_READY) == +			GST_STATE_CHANGE_FAILURE) { +		GST_ERROR_OBJECT(self, "rtpsbcpay failed to go to ready"); +		goto remove_element_and_fail;  	} -	GST_DEBUG_OBJECT(self, "configuration packet sent"); - -	io_error = gst_a2dp_sink_audioservice_expect(self, &rsp_hdr->msg_h, -			BT_SETCONFIGURATION_RSP); -	if (io_error != G_IO_ERROR_NONE) { -		GST_ERROR_OBJECT(self, "Error while receiving device confirmation"); -		return FALSE; +	if (!gst_element_link(self->capsfilter, rtppay)) { +		GST_ERROR_OBJECT(self, "couldn't link capsfilter " +			"to rtpsbcpay"); +		goto remove_element_and_fail;  	} -	if (rsp_hdr->posix_errno != 0) { -		GST_ERROR_OBJECT(self, "BT_SETCONFIGURATION_RSP failed : %s(%d)", -					strerror(rsp_hdr->posix_errno), -					rsp_hdr->posix_errno); -		return FALSE; -	} +	self->rtp = GST_BASE_RTP_PAYLOAD(rtppay); +	g_object_set(G_OBJECT(self->rtp), "min-frames", -1, NULL); -	memcpy(&self->data->cfg, rsp, sizeof(*rsp)); -	GST_DEBUG_OBJECT(self, "configuration set"); +	gst_element_set_state(rtppay, GST_STATE_PAUSED);  	return TRUE; -} -static GstFlowReturn gst_a2dp_sink_preroll(GstBaseSink *basesink, -					GstBuffer *buffer) -{ -	GstA2dpSink *sink = GST_A2DP_SINK(basesink); -	gboolean ret; - -	GST_A2DP_SINK_MUTEX_LOCK(sink); - -	ret = gst_a2dp_sink_stream_start(sink); - -	GST_A2DP_SINK_MUTEX_UNLOCK(sink); +remove_element_and_fail: +	gst_element_set_state (rtppay, GST_STATE_NULL); +	gst_bin_remove(GST_BIN(self), rtppay); +	return FALSE; -	if (!ret) -		return GST_FLOW_ERROR; +cleanup_and_fail: +	if (rtppay != NULL) +		g_object_unref(G_OBJECT(rtppay)); -	return GST_FLOW_OK; +	return FALSE;  } -static int gst_a2dp_sink_avdtp_write(GstA2dpSink *self) +static gboolean gst_a2dp_sink_set_caps(GstPad *pad, GstCaps *caps)  { -	gsize ret; -	struct bluetooth_data *data = self->data; -	struct rtp_header *header; -	struct rtp_payload *payload; -	GIOError err; - -	header = (void *) data->buffer; -	payload = (void *) (data->buffer + sizeof(*header)); - -	memset(data->buffer, 0, sizeof(*header) + sizeof(*payload)); - -	payload->frame_count = data->frame_count; -	header->v = 2; -	header->pt = 1; -	header->sequence_number = htons(data->seq_num); -	header->timestamp = htonl(data->nsamples); -	header->ssrc = htonl(1); - -	err = g_io_channel_write(self->stream, data->buffer, data->count, &ret); -	if (err != G_IO_ERROR_NONE) { -		GST_ERROR_OBJECT(self, "Error while sending data"); -		ret = -1; -	} - -	/* Reset buffer of data to send */ -	data->count = sizeof(struct rtp_header) + sizeof(struct rtp_payload); -	data->frame_count = 0; -	data->samples = 0; -	data->seq_num++; - -	return ret; -} +	GstA2dpSink *self; +	GstStructure *structure; -static GstFlowReturn gst_a2dp_sink_render(GstBaseSink *basesink, -					GstBuffer *buffer) -{ -	GstA2dpSink *self = GST_A2DP_SINK(basesink); -	struct bluetooth_data *data = self->data; -	gint encoded; -	gint ret; +	self = GST_A2DP_SINK(GST_PAD_PARENT(pad)); +	GST_INFO_OBJECT(self, "setting caps"); -	encoded = GST_BUFFER_SIZE(buffer); +	structure = gst_caps_get_structure(caps, 0); -	if (data->count + encoded >= data->cfg.link_mtu) { -		ret = gst_a2dp_sink_avdtp_write(self); -		if (ret < 0) -			return GST_FLOW_ERROR; +	/* first, we need to create our rtp payloader */ +	if (gst_structure_has_name(structure, "audio/x-sbc")) +		gst_a2dp_sink_init_rtp_sbc_element(self); +	else { +		GST_ERROR_OBJECT(self, "Unexpected media type"); +		return FALSE;  	} -	memcpy(data->buffer + data->count, GST_BUFFER_DATA(buffer), encoded); -	data->count += encoded; -	data->frame_count++; - -	return GST_FLOW_OK; -} - -static GstCaps* gst_a2dp_sink_get_caps(GstBaseSink *basesink) -{ -	GstA2dpSink *self = GST_A2DP_SINK(basesink); - -	if (self->dev_caps) -		return gst_caps_ref(self->dev_caps); - -	return gst_caps_copy(gst_pad_get_pad_template_caps(GST_BASE_SINK_PAD(self))); -} - -static gboolean gst_a2dp_sink_set_caps(GstBaseSink *basesink, GstCaps *caps) -{ -	GstA2dpSink *self = GST_A2DP_SINK(basesink); -	gboolean ret; - -	GST_A2DP_SINK_MUTEX_LOCK(self); -	ret = gst_a2dp_sink_configure(self, caps); - -	if (self->stream_caps) -		gst_caps_unref(self->stream_caps); -	self->stream_caps = gst_caps_ref(caps); - -	GST_A2DP_SINK_MUTEX_UNLOCK(self); +	if (!gst_a2dp_sink_init_sender_sink(self)) +		return FALSE; -	return ret; -} +	if (!gst_a2dp_sender_sink_set_device_caps(self->sink, caps)) +		return FALSE; -static gboolean gst_a2dp_sink_unlock(GstBaseSink *basesink) -{ -	GstA2dpSink *self = GST_A2DP_SINK(basesink); +	g_object_set(G_OBJECT(self->rtp), "mtu", +		gst_a2dp_sender_sink_get_link_mtu(self->sink), NULL); -	if (self->stream != NULL) -		g_io_channel_flush (self->stream, NULL); +	/* we forward our new segment here if we have one */ +	gst_pad_send_event(GST_BASE_RTP_PAYLOAD_SINKPAD(self->rtp), +				self->newseg_event); +	self->newseg_event = NULL; -	return TRUE; +	return self->ghostpad_setcapsfunc(GST_PAD(self->ghostpad), caps);  } -static void gst_a2dp_sink_class_init(GstA2dpSinkClass *klass) +/* used for catching newsegment events while we don't have a sink, for + * later forwarding it to the sink */ +static gboolean gst_a2dp_sink_handle_event(GstPad *pad, GstEvent *event)  { -	GObjectClass *object_class = G_OBJECT_CLASS(klass); -	GstBaseSinkClass *basesink_class = GST_BASE_SINK_CLASS(klass); +	GstA2dpSink *self; -	parent_class = g_type_class_peek_parent(klass); +	self = GST_A2DP_SINK(GST_PAD_PARENT(pad)); -	object_class->finalize = GST_DEBUG_FUNCPTR(gst_a2dp_sink_finalize); -	object_class->set_property = GST_DEBUG_FUNCPTR( -					gst_a2dp_sink_set_property); -	object_class->get_property = GST_DEBUG_FUNCPTR( -					gst_a2dp_sink_get_property); - -	basesink_class->start = GST_DEBUG_FUNCPTR(gst_a2dp_sink_start); -	basesink_class->stop = GST_DEBUG_FUNCPTR(gst_a2dp_sink_stop); -	basesink_class->render = GST_DEBUG_FUNCPTR(gst_a2dp_sink_render); -	basesink_class->preroll = GST_DEBUG_FUNCPTR(gst_a2dp_sink_preroll); -	basesink_class->set_caps = GST_DEBUG_FUNCPTR(gst_a2dp_sink_set_caps); -	basesink_class->get_caps = GST_DEBUG_FUNCPTR(gst_a2dp_sink_get_caps); -	basesink_class->unlock = GST_DEBUG_FUNCPTR(gst_a2dp_sink_unlock); - -	g_object_class_install_property(object_class, PROP_DEVICE, -					g_param_spec_string("device", "Device", -					"Bluetooth remote device address", -					NULL, G_PARAM_READWRITE)); +	if (GST_EVENT_TYPE(event) == GST_EVENT_NEWSEGMENT && +			gst_element_get_parent(GST_ELEMENT(self->sink)) != +			GST_OBJECT_CAST(self)) { +		if (self->newseg_event != NULL) +			gst_event_unref(self->newseg_event); +		self->newseg_event = gst_event_ref(event); +	} -	GST_DEBUG_CATEGORY_INIT(a2dp_sink_debug, "a2dpsink", 0, -				"A2DP sink element"); +	return self->ghostpad_eventfunc(GST_PAD(self->ghostpad), event);  } -static void gst_a2dp_sink_init(GstA2dpSink *self, GstA2dpSinkClass *klass) +static gboolean gst_a2dp_sink_init_caps_filter(GstA2dpSink *self)  { -	self->device = NULL; -	self->data = NULL; - -	self->stream = NULL; +	GstElement *element; -	self->dev_caps = NULL; +	element = gst_element_factory_make("capsfilter", "filter"); +	if (element == NULL) +		goto failed; -	self->sink_lock = g_mutex_new(); -} +	if (!gst_bin_add(GST_BIN(self), element)) +		goto failed; -static GIOError gst_a2dp_sink_audioservice_send(GstA2dpSink *self, -					const bt_audio_msg_header_t *msg) -{ -	gint err; -	GIOError error; -	gsize written; - -	GST_DEBUG_OBJECT(self, "sending %s", bt_audio_strmsg(msg->msg_type)); - -	error = g_io_channel_write(self->server, (const gchar*) msg, -			BT_AUDIO_IPC_PACKET_SIZE, &written); -	if (error != G_IO_ERROR_NONE) { -		err = errno; -		GST_ERROR_OBJECT(self, "Error sending data to audio service:" -			" %s(%d)", strerror(err), err); -	} +	self->capsfilter = element; +	return TRUE; -	return error; +failed: +	GST_ERROR_OBJECT(self, "Failed to initialize caps filter"); +	return FALSE;  } -static GIOError gst_a2dp_sink_audioservice_recv(GstA2dpSink *self, -					bt_audio_msg_header_t *inmsg) +static void gst_a2dp_sink_init(GstA2dpSink *self, +			GstA2dpSinkClass *klass)  { -	GIOError status; -	gsize bytes_read; -	const char *type; - -	status = g_io_channel_read(self->server, (gchar*) inmsg, -			BT_AUDIO_IPC_PACKET_SIZE, &bytes_read); -	if (status != G_IO_ERROR_NONE) { -		GST_ERROR_OBJECT(self, "Error receiving data from service"); -		return status; -	} - -	type = bt_audio_strmsg(inmsg->msg_type); -	if (!type) { -		GST_ERROR_OBJECT(self, "Bogus message type %d " -				"received from audio service", -				inmsg->msg_type); -		return G_IO_ERROR_INVAL; -	} - -	GST_DEBUG_OBJECT(self, "Received %s", type); +	GstPad *capsfilter_pad; -	return status; +	self->sink = NULL; +	self->rtp = NULL; +	self->device = NULL; +	self->capsfilter = NULL; +	self->newseg_event = NULL; + +	/* we initialize our capsfilter */ +	gst_a2dp_sink_init_caps_filter(self); +	g_object_set(self->capsfilter, "caps", +		gst_static_pad_template_get_caps(&gst_a2dp_sink_factory), +		NULL); + +	/* we search for the capsfilter sinkpad */ +	capsfilter_pad = gst_element_get_static_pad(self->capsfilter, "sink"); + +	/* now we add a ghostpad */ +	self->ghostpad = GST_GHOST_PAD(gst_ghost_pad_new("sink", +		capsfilter_pad)); +	g_object_unref(capsfilter_pad); + +	/* the getcaps of our ghostpad must reflect the device caps */ +	gst_pad_set_getcaps_function(GST_PAD(self->ghostpad), +				gst_a2dp_sink_get_caps); +	self->ghostpad_setcapsfunc = GST_PAD_SETCAPSFUNC(self->ghostpad); +	gst_pad_set_setcaps_function(GST_PAD(self->ghostpad), +			GST_DEBUG_FUNCPTR(gst_a2dp_sink_set_caps)); + +	/* we need to handle events on our own and we also need the eventfunc +	 * of the ghostpad for forwarding calls */ +	self->ghostpad_eventfunc = GST_PAD_EVENTFUNC(GST_PAD(self->ghostpad)); +	gst_pad_set_event_function(GST_PAD(self->ghostpad), +			gst_a2dp_sink_handle_event); + +	if (!gst_element_add_pad(GST_ELEMENT(self), GST_PAD(self->ghostpad))) +		GST_ERROR_OBJECT(self, "failed to add ghostpad"); + +	self->sink = GST_A2DP_SENDER_SINK(gst_element_factory_make( +			"a2dpsendersink", "sendersink")); +	if (self->sink == NULL) +		GST_WARNING_OBJECT(self, "failed to create a2dpsendersink");  } -static GIOError gst_a2dp_sink_audioservice_expect(GstA2dpSink *self, -					bt_audio_msg_header_t *outmsg, -					int expected_type) +gboolean gst_a2dp_sink_plugin_init (GstPlugin * plugin)  { -	GIOError status; - -	status = gst_a2dp_sink_audioservice_recv(self, outmsg); -	if (status != G_IO_ERROR_NONE) -		return status; - -	if (outmsg->msg_type != expected_type) { -		GST_ERROR_OBJECT(self, "Bogus message %s " -				"received while %s was expected", -				bt_audio_strmsg(outmsg->msg_type), -				bt_audio_strmsg(expected_type)); -		return G_IO_ERROR_INVAL; -	} - -	return status; +	return gst_element_register (plugin, "a2dpsink", +			GST_RANK_PRIMARY, GST_TYPE_A2DP_SINK);  } diff --git a/audio/gsta2dpsink.h b/audio/gsta2dpsink.h index f5b9b69b..4bf9d603 100644 --- a/audio/gsta2dpsink.h +++ b/audio/gsta2dpsink.h @@ -22,7 +22,8 @@   */  #include <gst/gst.h> -#include <gst/base/gstbasesink.h> +#include <gst/rtp/gstbasertppayload.h> +#include "gsta2dpsendersink.h"  G_BEGIN_DECLS @@ -40,31 +41,27 @@ G_BEGIN_DECLS  typedef struct _GstA2dpSink GstA2dpSink;  typedef struct _GstA2dpSinkClass GstA2dpSinkClass; -struct bluetooth_data; -  struct _GstA2dpSink { -	GstBaseSink sink; - -	gchar *device; -	GIOChannel *stream; +	GstBin bin; -	struct bluetooth_data *data; -	GIOChannel *server; +	GstBaseRTPPayload *rtp; +	GstA2dpSenderSink *sink; +	GstElement *capsfilter; -	/* stream connection data */ -	GstCaps *stream_caps; - -	GstCaps *dev_caps; +	gchar *device; -	GMutex *sink_lock; +	GstGhostPad *ghostpad; +	GstPadSetCapsFunction ghostpad_setcapsfunc; +	GstPadEventFunction ghostpad_eventfunc; -	guint watch_id; +	GstEvent *newseg_event;  };  struct _GstA2dpSinkClass { -	GstBaseSinkClass parent_class; +	GstBinClass parent_class;  };  GType gst_a2dp_sink_get_type(void); +gboolean gst_a2dp_sink_plugin_init (GstPlugin * plugin);  G_END_DECLS diff --git a/audio/gstbluetooth.c b/audio/gstbluetooth.c index 593a311e..764bc899 100644 --- a/audio/gstbluetooth.c +++ b/audio/gstbluetooth.c @@ -28,7 +28,9 @@  #include "gstsbcenc.h"  #include "gstsbcdec.h"  #include "gstsbcparse.h" +#include "gsta2dpsendersink.h"  #include "gsta2dpsink.h" +#include "gstrtpsbcpay.h"  static GstStaticCaps sbc_caps = GST_STATIC_CAPS("audio/x-sbc"); @@ -55,20 +57,22 @@ static gboolean plugin_init(GstPlugin *plugin)  					SBC_CAPS, NULL, NULL) == FALSE)  		return FALSE; -	if (gst_element_register(plugin, "sbcenc", -			GST_RANK_NONE, GST_TYPE_SBC_ENC) == FALSE) +	if (!gst_sbc_enc_plugin_init(plugin))  		return FALSE; -	if (gst_element_register(plugin, "sbcdec", -			GST_RANK_PRIMARY, GST_TYPE_SBC_DEC) == FALSE) +	if (!gst_sbc_dec_plugin_init(plugin))  		return FALSE; -	if (gst_element_register(plugin, "sbcparse", -			GST_RANK_PRIMARY, GST_TYPE_SBC_PARSE) == FALSE) +	if (!gst_sbc_parse_plugin_init(plugin))  		return FALSE; -	if (gst_element_register(plugin, "a2dpsink", -			GST_RANK_PRIMARY, GST_TYPE_A2DP_SINK) == FALSE) +	if (!gst_a2dp_sender_sink_plugin_init(plugin)) +		return FALSE; + +	if (!gst_a2dp_sink_plugin_init(plugin)) +		return FALSE; + +	if (!gst_rtp_sbc_pay_plugin_init(plugin))  		return FALSE;  	return TRUE; diff --git a/audio/gstrtpsbcpay.c b/audio/gstrtpsbcpay.c new file mode 100644 index 00000000..68aa28a9 --- /dev/null +++ b/audio/gstrtpsbcpay.c @@ -0,0 +1,337 @@ +/* + * + *  BlueZ - Bluetooth protocol stack for Linux + * + *  Copyright (C) 2004-2007  Marcel Holtmann <marcel@holtmann.org> + * + * + *  This library 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. + * + *  This library 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 this library; if not, write to the Free Software + *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "gstrtpsbcpay.h" +#include <math.h> +#include <string.h> + +#define RTP_SBC_PAYLOAD_HEADER_SIZE 1 +#define DEFAULT_MIN_FRAMES 0 +#define RTP_SBC_HEADER_TOTAL (12 + RTP_SBC_PAYLOAD_HEADER_SIZE) + +#if __BYTE_ORDER == __LITTLE_ENDIAN + +struct rtp_payload { +        guint8 frame_count:4; +        guint8 rfa0:1; +        guint8 is_last_fragment:1; +        guint8 is_first_fragment:1; +        guint8 is_fragmented:1; +} __attribute__ ((packed)); + +#elif __BYTE_ORDER == __BIG_ENDIAN + +struct rtp_payload { +        guint8 is_fragmented:1; +        guint8 is_first_fragment:1; +        guint8 is_last_fragment:1; +        guint8 rfa0:1; +        guint8 frame_count:4; +} __attribute__ ((packed)); + +#else +#error "Unknown byte order" +#endif + +enum { +	PROP_0, +	PROP_MIN_FRAMES +}; + +GST_DEBUG_CATEGORY_STATIC(gst_rtp_sbc_pay_debug); +#define GST_CAT_DEFAULT gst_rtp_sbc_pay_debug + +GST_BOILERPLATE(GstRtpSBCPay, gst_rtp_sbc_pay, GstBaseRTPPayload, +		GST_TYPE_BASE_RTP_PAYLOAD); + +static const GstElementDetails gst_rtp_sbc_pay_details = +	GST_ELEMENT_DETAILS("RTP packet payloader", +				"Codec/Payloader/Network", +				"Payload SBC audio as RTP packets", +				"Thiago Sousa Santos " +				"<thiagoss@lcc.ufcg.edu.br>"); + +static GstStaticPadTemplate gst_rtp_sbc_pay_sink_factory = +	GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS, +		GST_STATIC_CAPS("audio/x-sbc, " /* FIXME remove those caps? */ +				"rate = (int) { 16000, 32000, 44100, 48000 }, " +				"channels = (int) [ 1, 2 ], " +				"mode = (string) { mono, dual, stereo, joint }, " +				"blocks = (int) { 4, 8, 12, 16 }, " +				"subbands = (int) { 4, 8 }, " +				"allocation = (string) { snr, loudness }," +				"bitpool = (int) [ 2, 64 ]; ") +	); + +static GstStaticPadTemplate gst_rtp_sbc_pay_src_factory = +	GST_STATIC_PAD_TEMPLATE("src", GST_PAD_SRC, GST_PAD_ALWAYS, +		GST_STATIC_CAPS("application/x-rtp") /* FIXME put things here */ +	); + +static void gst_rtp_sbc_pay_set_property (GObject * object, guint prop_id, +                        const GValue * value, GParamSpec * pspec); +static void gst_rtp_sbc_pay_get_property (GObject * object, guint prop_id, +                        GValue * value, GParamSpec * pspec); + +static gint gst_rtp_sbc_pay_get_frame_len(gint subbands, gint channels, +		gint blocks, gint bitpool, const gchar* channel_mode) +{ +	gint len; +	gint join; + +	len = 4 + (4 * subbands * channels)/8; + +	if (strcmp(channel_mode, "mono") == 0 || +		strcmp(channel_mode, "dual") == 0) +		len += ((blocks * channels * bitpool)+7) / 8; +	else { +		join = strcmp(channel_mode, "joint") == 0 ? 1 : 0; +		len += ((join * subbands + blocks * bitpool)+7)/8; +	} + +	return len; +} + +static gboolean gst_rtp_sbc_pay_set_caps(GstBaseRTPPayload *payload, +			GstCaps *caps) +{ +	GstRtpSBCPay *sbcpay; +	gint rate, subbands, channels, blocks, bitpool; +	gint frame_len; +	const gchar* channel_mode; +	GstStructure *structure; + +	sbcpay = GST_RTP_SBC_PAY(payload); + +	structure = gst_caps_get_structure(caps, 0); +	if (!gst_structure_get_int(structure, "rate", &rate)) +		return FALSE; +	if (!gst_structure_get_int(structure, "channels", &channels)) +		return FALSE; +	if (!gst_structure_get_int(structure, "blocks", &blocks)) +		return FALSE; +	if (!gst_structure_get_int(structure, "bitpool", &bitpool)) +		return FALSE; +	if (!gst_structure_get_int(structure, "subbands", &subbands)) +		return FALSE; + +	channel_mode = gst_structure_get_string(structure, "mode"); +	if (!channel_mode) +		return FALSE; + +	frame_len = gst_rtp_sbc_pay_get_frame_len(subbands, channels, blocks, +				bitpool, channel_mode); + +	sbcpay->frame_length = frame_len; + +	gst_basertppayload_set_options (payload, "audio", FALSE, "SBC", rate); + +	GST_DEBUG_OBJECT(payload, "calculated frame length: %d ", frame_len); + +	return gst_basertppayload_set_outcaps (payload, NULL); +} + +static GstFlowReturn gst_rtp_sbc_pay_flush_buffers(GstRtpSBCPay *sbcpay) +{ +	guint available; +	guint max_payload; +	GstBuffer* outbuf; +	guint8 *payload_data; +	guint8 *data; +	struct rtp_payload *payload; + +	if (sbcpay->frame_length == 0) { +		GST_ERROR_OBJECT(sbcpay, "Frame length is 0"); +		return GST_FLOW_ERROR; +	} + +	available = gst_adapter_available(sbcpay->adapter); + +	max_payload = gst_rtp_buffer_calc_payload_len( +		GST_BASE_RTP_PAYLOAD_MTU(sbcpay) - RTP_SBC_PAYLOAD_HEADER_SIZE, +		0, 0); + +	max_payload = MIN(max_payload, available); + +	outbuf = gst_rtp_buffer_new_allocate(max_payload + +			RTP_SBC_PAYLOAD_HEADER_SIZE, 0, 0); + +	gst_rtp_buffer_set_payload_type(outbuf, +			GST_BASE_RTP_PAYLOAD_PT(sbcpay)); + +	data = gst_adapter_take(sbcpay->adapter, max_payload); +	payload_data = gst_rtp_buffer_get_payload(outbuf); + +	payload = (struct rtp_payload*) payload_data; +	memset(payload, 0, sizeof(struct rtp_payload)); +	payload->frame_count = max_payload / sbcpay->frame_length; + +	memcpy(payload_data + RTP_SBC_PAYLOAD_HEADER_SIZE, data, max_payload); +	g_free(data); + +	/* FIXME - timestamp it! */ +	GST_DEBUG_OBJECT (sbcpay, "Pushing %d bytes", max_payload); + +	return gst_basertppayload_push (GST_BASE_RTP_PAYLOAD(sbcpay), outbuf); +} + +static GstFlowReturn gst_rtp_sbc_pay_handle_buffer(GstBaseRTPPayload *payload, +			GstBuffer *buffer) +{ +	GstRtpSBCPay *sbcpay; +	guint available; + +	sbcpay = GST_RTP_SBC_PAY(payload); +	gst_adapter_push(sbcpay->adapter, gst_buffer_copy(buffer)); + +	available = gst_adapter_available(sbcpay->adapter); +	if (available + RTP_SBC_HEADER_TOTAL >= +			GST_BASE_RTP_PAYLOAD_MTU(sbcpay) || +			(sbcpay->min_frames != -1 && available > +			(sbcpay->min_frames * sbcpay->frame_length))) +		return gst_rtp_sbc_pay_flush_buffers(sbcpay); + +	return GST_FLOW_OK; +} + +static gboolean gst_rtp_sbc_pay_handle_event(GstPad *pad, +				GstEvent *event) +{ +	GstRtpSBCPay *sbcpay = GST_RTP_SBC_PAY(GST_PAD_PARENT(pad)); + +	switch (GST_EVENT_TYPE(event)) { +	case GST_EVENT_EOS: +		gst_rtp_sbc_pay_flush_buffers(sbcpay); +		break; +	default: +		break; +	} + +	return FALSE; +} + +static void gst_rtp_sbc_pay_base_init(gpointer g_class) +{ +	GstElementClass *element_class = GST_ELEMENT_CLASS(g_class); + +	gst_element_class_add_pad_template(element_class, +		gst_static_pad_template_get(&gst_rtp_sbc_pay_sink_factory)); +	gst_element_class_add_pad_template(element_class, +		gst_static_pad_template_get(&gst_rtp_sbc_pay_src_factory)); + +	gst_element_class_set_details(element_class, &gst_rtp_sbc_pay_details); +} + +static void gst_rtp_sbc_pay_finalize(GObject *object) +{ +	GstRtpSBCPay *sbcpay = GST_RTP_SBC_PAY(object); +	g_object_unref (sbcpay->adapter); + +	GST_CALL_PARENT (G_OBJECT_CLASS, finalize, (object)); +} + +static void gst_rtp_sbc_pay_class_init(GstRtpSBCPayClass *klass) +{ +	GObjectClass *gobject_class; +	GstBaseRTPPayloadClass *payload_class = +		GST_BASE_RTP_PAYLOAD_CLASS(klass); + +	gobject_class = G_OBJECT_CLASS(klass); +	parent_class = g_type_class_peek_parent(klass); + +	gobject_class->finalize = GST_DEBUG_FUNCPTR(gst_rtp_sbc_pay_finalize); +	gobject_class->set_property = GST_DEBUG_FUNCPTR( +			gst_rtp_sbc_pay_set_property); +	gobject_class->get_property = GST_DEBUG_FUNCPTR( +			gst_rtp_sbc_pay_get_property); + +	payload_class->set_caps = GST_DEBUG_FUNCPTR(gst_rtp_sbc_pay_set_caps); +	payload_class->handle_buffer = GST_DEBUG_FUNCPTR( +			gst_rtp_sbc_pay_handle_buffer); +	payload_class->handle_event = GST_DEBUG_FUNCPTR( +			gst_rtp_sbc_pay_handle_event); + +	/* properties */ +	g_object_class_install_property (G_OBJECT_CLASS (klass), +		PROP_MIN_FRAMES, +		g_param_spec_int ("min-frames", "minimum frame number", +		"Minimum quantity of frames to send in one packet " +		"(-1 for maximum allowed by the mtu)", +		-1, G_MAXINT, DEFAULT_MIN_FRAMES, G_PARAM_READWRITE)); + +	GST_DEBUG_CATEGORY_INIT(gst_rtp_sbc_pay_debug, "rtpsbcpay", 0, +				"RTP SBC payloader"); +} + +static void gst_rtp_sbc_pay_set_property (GObject * object, guint prop_id, +			const GValue * value, GParamSpec * pspec) +{ +	GstRtpSBCPay *sbcpay; + +	sbcpay = GST_RTP_SBC_PAY (object); + +	switch (prop_id) { +	case PROP_MIN_FRAMES: +		sbcpay->min_frames = g_value_get_int(value); +		break; +	default: +		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); +	break; +	} +} + +static void gst_rtp_sbc_pay_get_property (GObject * object, guint prop_id, +                        GValue * value, GParamSpec * pspec) +{ +	GstRtpSBCPay *sbcpay; + +	sbcpay = GST_RTP_SBC_PAY (object); + +	switch (prop_id) { +	case PROP_MIN_FRAMES: +		g_value_set_int(value, sbcpay->min_frames); +		break; +	default: +		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); +	break; +	} +} + +static void gst_rtp_sbc_pay_init(GstRtpSBCPay *self, GstRtpSBCPayClass *klass) +{ +	self->adapter = gst_adapter_new(); +	self->frame_length = 0; + +	self->min_frames = DEFAULT_MIN_FRAMES; +} + +gboolean gst_rtp_sbc_pay_plugin_init (GstPlugin * plugin) +{ +	return gst_element_register (plugin, "rtpsbcpay", +			GST_RANK_NONE, GST_TYPE_RTP_SBC_PAY); +} + diff --git a/audio/gstrtpsbcpay.h b/audio/gstrtpsbcpay.h new file mode 100644 index 00000000..f086a1c7 --- /dev/null +++ b/audio/gstrtpsbcpay.h @@ -0,0 +1,65 @@ +/* + * + *  BlueZ - Bluetooth protocol stack for Linux + * + *  Copyright (C) 2004-2007  Marcel Holtmann <marcel@holtmann.org> + * + * + *  This library 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. + * + *  This library 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 this library; if not, write to the Free Software + *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA + * + */ + +#include <gst/gst.h> +#include <gst/rtp/gstbasertppayload.h> +#include <gst/base/gstadapter.h> +#include <gst/rtp/gstrtpbuffer.h> + +G_BEGIN_DECLS + +#define GST_TYPE_RTP_SBC_PAY \ +	(gst_rtp_sbc_pay_get_type()) +#define GST_RTP_SBC_PAY(obj) \ +	(G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_RTP_SBC_PAY,\ +		GstRtpSBCPay)) +#define GST_RTP_SBC_PAY_CLASS(klass) \ +	(G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_RTP_SBC_PAY,\ +		GstRtpSBCPayClass)) +#define GST_IS_RTP_SBC_PAY(obj) \ +	(G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_RTP_SBC_PAY)) +#define GST_IS_RTP_SBC_PAY_CLASS(obj) \ +	(G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_RTP_SBC_PAY)) + +typedef struct _GstRtpSBCPay GstRtpSBCPay; +typedef struct _GstRtpSBCPayClass GstRtpSBCPayClass; + +struct _GstRtpSBCPay { +	GstBaseRTPPayload base; + +	GstAdapter *adapter; + +	guint frame_length; + +	guint min_frames; +}; + +struct _GstRtpSBCPayClass { +	GstBaseRTPPayloadClass parent_class; +}; + +GType gst_rtp_sbc_pay_get_type(void); + +gboolean gst_rtp_sbc_pay_plugin_init (GstPlugin * plugin); + +G_END_DECLS diff --git a/audio/gstsbcdec.c b/audio/gstsbcdec.c index a60c3e69..8c27daba 100644 --- a/audio/gstsbcdec.c +++ b/audio/gstsbcdec.c @@ -188,10 +188,21 @@ static void gst_sbc_dec_class_init(GstSbcDecClass *klass)  static void gst_sbc_dec_init(GstSbcDec *self, GstSbcDecClass *klass)  { -	self->sinkpad = gst_pad_new_from_static_template(&sbc_dec_sink_factory, "sink"); -	gst_pad_set_chain_function(self->sinkpad, GST_DEBUG_FUNCPTR(sbc_dec_chain)); +	self->sinkpad = gst_pad_new_from_static_template( +			&sbc_dec_sink_factory, "sink"); +	gst_pad_set_chain_function(self->sinkpad, GST_DEBUG_FUNCPTR( +			sbc_dec_chain));  	gst_element_add_pad(GST_ELEMENT(self), self->sinkpad); -	self->srcpad = gst_pad_new_from_static_template(&sbc_dec_src_factory, "src"); +	self->srcpad = gst_pad_new_from_static_template( +			&sbc_dec_src_factory, "src");  	gst_element_add_pad(GST_ELEMENT(self), self->srcpad);  } + +gboolean gst_sbc_dec_plugin_init (GstPlugin * plugin) +{ +	return gst_element_register (plugin, "sbcdec", +			GST_RANK_PRIMARY, GST_TYPE_SBC_DEC); +} + + diff --git a/audio/gstsbcdec.h b/audio/gstsbcdec.h index 4a6922a0..0bb0b57e 100644 --- a/audio/gstsbcdec.h +++ b/audio/gstsbcdec.h @@ -58,4 +58,6 @@ struct _GstSbcDecClass {  GType gst_sbc_dec_get_type(void); +gboolean gst_sbc_dec_plugin_init(GstPlugin *plugin); +  G_END_DECLS diff --git a/audio/gstsbcenc.c b/audio/gstsbcenc.c index 185151f5..08ddc14f 100644 --- a/audio/gstsbcenc.c +++ b/audio/gstsbcenc.c @@ -120,32 +120,6 @@ static GstStaticPadTemplate sbc_enc_src_factory =  gboolean gst_sbc_enc_fill_sbc_params(GstSbcEnc *enc, GstCaps *caps); -static void sbc_enc_set_structure_int_param(GstSbcEnc *enc, -			GstStructure *structure, const gchar* field, -			gint field_value) -{ -	GValue *value; - -	value = g_new0(GValue,1); -	value = g_value_init(value, G_TYPE_INT); -	g_value_set_int(value, field_value); -	gst_structure_set_value(structure, field, value); -	g_free(value); -} - -static void sbc_enc_set_structure_string_param(GstSbcEnc *enc, -			GstStructure *structure, const gchar* field, -			const gchar* field_value) -{ -	GValue *value; - -	value = g_new0(GValue,1); -	value = g_value_init(value, G_TYPE_STRING); -	g_value_set_string(value, field_value); -	gst_structure_set_value(structure, field, value); -	g_free(value); -} -  static GstCaps* sbc_enc_generate_srcpad_caps(GstSbcEnc *enc)  {  	GstCaps* src_caps; @@ -153,45 +127,49 @@ static GstCaps* sbc_enc_generate_srcpad_caps(GstSbcEnc *enc)  	GEnumValue *enum_value;  	GEnumClass *enum_class;  	gchar* temp; +	GValue *value;  	src_caps = gst_caps_copy(gst_pad_get_pad_template_caps(enc->srcpad));  	structure = gst_caps_get_structure(src_caps, 0); +	value = g_new0(GValue, 1); +  	if (enc->rate != 0) -		sbc_enc_set_structure_int_param(enc, structure, "rate", -			enc->rate); +		gst_sbc_util_set_structure_int_param(structure, "rate", +			enc->rate, value);  	if (enc->channels != 0) -		sbc_enc_set_structure_int_param(enc, structure, "channels", -			enc->channels); +		gst_sbc_util_set_structure_int_param(structure, "channels", +			enc->channels, value);  	if (enc->subbands != 0) -		sbc_enc_set_structure_int_param(enc, structure, "subbands", -			enc->subbands); +		gst_sbc_util_set_structure_int_param(structure, "subbands", +			enc->subbands, value);  	if (enc->blocks != 0) -		sbc_enc_set_structure_int_param(enc, structure, "blocks", -			enc->blocks); +		gst_sbc_util_set_structure_int_param(structure, "blocks", +			enc->blocks, value);  	if (enc->mode != BT_A2DP_CHANNEL_MODE_AUTO) {  		enum_class = g_type_class_ref(GST_TYPE_SBC_MODE);  		enum_value = g_enum_get_value(enum_class, enc->mode); -		sbc_enc_set_structure_string_param(enc, structure, "mode", -			enum_value->value_nick); +		gst_sbc_util_set_structure_string_param(structure, "mode", +			enum_value->value_nick, value);  		g_type_class_unref(enum_class);  	}  	if (enc->allocation != BT_A2DP_ALLOCATION_AUTO) {  		enum_class = g_type_class_ref(GST_TYPE_SBC_ALLOCATION);  		enum_value = g_enum_get_value(enum_class, enc->allocation); -		sbc_enc_set_structure_string_param(enc, structure, "allocation", -			enum_value->value_nick); +		gst_sbc_util_set_structure_string_param(structure, "allocation", +			enum_value->value_nick, value);  		g_type_class_unref(enum_class);  	}  	temp = gst_caps_to_string(src_caps);  	GST_DEBUG_OBJECT(enc, "Srcpad caps: %s", temp);  	g_free(temp); +	g_free(value);  	return src_caps;  } @@ -207,23 +185,10 @@ static GstCaps* sbc_enc_src_getcaps (GstPad * pad)  static gboolean sbc_enc_src_setcaps (GstPad *pad, GstCaps *caps)  { -	GstCaps* srcpad_caps; -	GstCaps* temp_caps; -	gboolean res = TRUE;  	GstSbcEnc *enc = GST_SBC_ENC(GST_PAD_PARENT(pad));  	GST_LOG_OBJECT(enc, "setting srcpad caps"); -	srcpad_caps = sbc_enc_generate_srcpad_caps(enc); -	temp_caps = gst_caps_intersect(srcpad_caps, caps); -	if (temp_caps == GST_CAPS_NONE) -		res = FALSE; - -	gst_caps_unref(temp_caps); -	gst_caps_unref(srcpad_caps); - -	g_return_val_if_fail(res, FALSE); -  	return gst_sbc_enc_fill_sbc_params(enc, caps);  } @@ -313,38 +278,16 @@ error:  gboolean gst_sbc_enc_fill_sbc_params(GstSbcEnc *enc, GstCaps *caps)  { -	GstStructure *structure; -	gint rate, channels, subbands, blocks, bitpool; -	const gchar* mode; -	const gchar* allocation; - -	g_assert(gst_caps_is_fixed(caps)); -	structure = gst_caps_get_structure(caps, 0); - -	if (!gst_structure_get_int(structure, "rate", &rate)) -		return FALSE; -	if (!gst_structure_get_int(structure, "channels", &channels)) -		return FALSE; -	if (!gst_structure_get_int(structure, "subbands", &subbands)) -		return FALSE; -	if (!gst_structure_get_int(structure, "blocks", &blocks)) -		return FALSE; -	if (!gst_structure_get_int(structure, "bitpool", &bitpool)) +	if (!gst_sbc_util_fill_sbc_params(&enc->sbc, caps))  		return FALSE; -	if (!(mode = gst_structure_get_string(structure, "mode"))) -		return FALSE; -	if (!(allocation = gst_structure_get_string(structure, "allocation"))) -		return FALSE; - -	enc->rate = enc->sbc.rate = rate; -	enc->channels = enc->sbc.channels = channels; -	enc->blocks = enc->sbc.blocks = blocks; -	enc->subbands = enc->sbc.subbands = subbands; -	enc->sbc.bitpool = bitpool; -	enc->mode = enc->sbc.joint = gst_sbc_get_mode_int(mode); -	enc->allocation = enc->sbc.allocation = gst_sbc_get_allocation_mode_int(allocation); +	enc->rate = enc->sbc.rate; +	enc->channels = enc->sbc.channels; +	enc->blocks = enc->sbc.blocks; +	enc->subbands = enc->sbc.subbands; +	enc->mode = enc->sbc.joint; +	enc->allocation = enc->sbc.allocation;  	enc->codesize = sbc_get_codesize(&enc->sbc);  	enc->frame_length = sbc_get_frame_length(&enc->sbc);  	enc->frame_duration = sbc_get_frame_duration(&enc->sbc); @@ -390,6 +333,8 @@ static GstFlowReturn sbc_enc_chain(GstPad *pad, GstBuffer *buffer)  		gst_adapter_flush(adapter, consumed);  		GST_BUFFER_TIMESTAMP(output) = GST_BUFFER_TIMESTAMP(buffer); +		/* we have only 1 frame */ +		GST_BUFFER_DURATION(output) = enc->frame_duration;  		res = gst_pad_push(enc->srcpad, output);  		if (res != GST_FLOW_OK) @@ -559,17 +504,22 @@ static void gst_sbc_enc_class_init(GstSbcEncClass *klass)  static void gst_sbc_enc_init(GstSbcEnc *self, GstSbcEncClass *klass)  { -	self->sinkpad = gst_pad_new_from_static_template(&sbc_enc_sink_factory, "sink"); +	self->sinkpad = gst_pad_new_from_static_template( +		&sbc_enc_sink_factory, "sink");  	gst_pad_set_setcaps_function (self->sinkpad,  			GST_DEBUG_FUNCPTR (sbc_enc_sink_setcaps));  	gst_element_add_pad(GST_ELEMENT(self), self->sinkpad); -	self->srcpad = gst_pad_new_from_static_template(&sbc_enc_src_factory, "src"); -	gst_pad_set_getcaps_function(self->srcpad, GST_DEBUG_FUNCPTR(sbc_enc_src_getcaps)); -	gst_pad_set_setcaps_function(self->srcpad, GST_DEBUG_FUNCPTR(sbc_enc_src_setcaps)); +	self->srcpad = gst_pad_new_from_static_template( +		&sbc_enc_src_factory, "src"); +	gst_pad_set_getcaps_function(self->srcpad, +		GST_DEBUG_FUNCPTR(sbc_enc_src_getcaps)); +	gst_pad_set_setcaps_function(self->srcpad, +		GST_DEBUG_FUNCPTR(sbc_enc_src_setcaps));  	gst_element_add_pad(GST_ELEMENT(self), self->srcpad); -	gst_pad_set_chain_function(self->sinkpad, GST_DEBUG_FUNCPTR(sbc_enc_chain)); +	gst_pad_set_chain_function(self->sinkpad, +		GST_DEBUG_FUNCPTR(sbc_enc_chain));  	self->subbands = SBC_ENC_DEFAULT_SUB_BANDS;  	self->blocks = SBC_ENC_DEFAULT_BLOCKS; @@ -578,5 +528,16 @@ static void gst_sbc_enc_init(GstSbcEnc *self, GstSbcEncClass *klass)  	self->rate = SBC_ENC_DEFAULT_RATE;  	self->channels = SBC_ENC_DEFAULT_CHANNELS; +	self->frame_length = 0; +	self->frame_duration = 0; +  	self->adapter = gst_adapter_new();  } + +gboolean gst_sbc_enc_plugin_init (GstPlugin * plugin) +{ +	return gst_element_register (plugin, "sbcenc", +			GST_RANK_NONE, GST_TYPE_SBC_ENC); +} + + diff --git a/audio/gstsbcenc.h b/audio/gstsbcenc.h index d81428c9..c7b21638 100644 --- a/audio/gstsbcenc.h +++ b/audio/gstsbcenc.h @@ -68,4 +68,6 @@ struct _GstSbcEncClass {  GType gst_sbc_enc_get_type(void); +gboolean gst_sbc_enc_plugin_init(GstPlugin *plugin); +  G_END_DECLS diff --git a/audio/gstsbcparse.c b/audio/gstsbcparse.c index bae7d623..49d0bb6e 100644 --- a/audio/gstsbcparse.c +++ b/audio/gstsbcparse.c @@ -56,171 +56,83 @@ static GstStaticPadTemplate sbc_parse_src_factory =  				"allocation = (string) { snr, loudness },"  				"bitpool = (int) [ 2, 64 ]")); -/* Creates a fixed caps from the caps given. */ -/* FIXME use gstsbcutil caps fixating function */ -static GstCaps* sbc_parse_select_caps(GstSbcParse *parse, GstCaps *caps) +static gboolean sbc_parse_sink_setcaps(GstPad * pad, GstCaps * caps)  { -	GstCaps *result; +	GstSbcParse *parse;  	GstStructure *structure; -	const GValue *value; -	gboolean error = FALSE; -	gint temp, rate, channels, blocks, subbands, bitpool; -	const gchar* allocation = NULL; -	const gchar* mode = NULL; -	const gchar* error_message = NULL; -	gchar* str; - -	str = gst_caps_to_string(caps); -	GST_DEBUG_OBJECT(parse, "Parsing caps: %s", str); -	g_free(str); +	gint rate, channels; + +	parse = GST_SBC_PARSE(GST_PAD_PARENT(pad));  	structure = gst_caps_get_structure(caps, 0); -	if (!gst_structure_has_field(structure, "rate")) { -		error = TRUE; -		error_message = "no rate."; -		goto error; -	} else { -		value = gst_structure_get_value(structure, "rate"); -		if (GST_VALUE_HOLDS_LIST(value)) -			temp = gst_sbc_select_rate_from_list(value); -		else -			temp = g_value_get_int(value); -		rate = temp; -	} +	if (!gst_structure_get_int(structure, "rate", &rate)) +		return FALSE; -	if (!gst_structure_has_field(structure, "channels")) { -		error = TRUE; -		error_message = "no channels."; -		goto error; -	} else { -		value = gst_structure_get_value(structure, "channels"); -		if (GST_VALUE_HOLDS_INT_RANGE(value)) -			temp = gst_sbc_select_channels_from_range(value); -		else -			temp = g_value_get_int(value); -		channels = temp; -	} +	if (!gst_structure_get_int(structure, "channels", &channels)) +		return FALSE; -	if (!gst_structure_has_field(structure, "blocks")) { -		error = TRUE; -		error_message = "no blocks."; -		goto error; -	} else { -		value = gst_structure_get_value(structure, "blocks"); -		if (GST_VALUE_HOLDS_LIST(value)) -			temp = gst_sbc_select_blocks_from_list(value); -		else -			temp = g_value_get_int(value); -		blocks = temp; -	} +	if (!(parse->rate == 0 || rate == parse->rate)) +		return FALSE; -	if (!gst_structure_has_field(structure, "subbands")) { -		error = TRUE; -		error_message = "no subbands."; -		goto error; -	} else { -		value = gst_structure_get_value(structure, "subbands"); -		if (GST_VALUE_HOLDS_LIST(value)) -			temp = gst_sbc_select_subbands_from_list(value); -		else -			temp = g_value_get_int(value); -		subbands = temp; -	} +	if (!(parse->channels == 0 || channels == parse->channels)) +		return FALSE; -	if (!gst_structure_has_field(structure, "bitpool")) { -		error = TRUE; -		error_message = "no bitpool"; -		goto error; -	} else { -		value = gst_structure_get_value(structure, "bitpool"); -		if (GST_VALUE_HOLDS_INT_RANGE(value)) -			temp = gst_sbc_select_bitpool_from_range(value); -		else -			temp = g_value_get_int(value); -		bitpool = temp; -	} +	parse->rate = rate; +	parse->channels = channels; -	if (!gst_structure_has_field(structure, "allocation")) { -		error = TRUE; -		error_message = "no allocation."; -		goto error; -	} else { -		value = gst_structure_get_value(structure, "allocation"); -		if (GST_VALUE_HOLDS_LIST(value)) -			allocation = gst_sbc_get_allocation_from_list(value); -		else -			allocation = g_value_get_string(value); -	} +	return gst_sbc_util_fill_sbc_params(&parse->sbc, caps); +} -	if (!gst_structure_has_field(structure, "mode")) { -		error = TRUE; -		error_message = "no mode."; -		goto error; -	} else { -		value = gst_structure_get_value(structure, "mode"); -		if (GST_VALUE_HOLDS_LIST(value)) -			mode = gst_sbc_get_mode_from_list(value); -		else -			mode = g_value_get_string(value); -	} +static GstCaps* sbc_parse_src_getcaps(GstPad *pad) +{ +	GstCaps *caps; +	const GstCaps *allowed_caps; +	GstStructure *structure; +	GValue *value; +	GstSbcParse *parse = GST_SBC_PARSE(GST_PAD_PARENT(pad)); -error: -	if (error) { -		GST_ERROR_OBJECT (parse, "Invalid input caps: %s", -				error_message); -		return NULL; -	} +	allowed_caps = gst_pad_get_allowed_caps(pad); +	if (allowed_caps == NULL) +		allowed_caps = gst_pad_get_pad_template_caps(pad); +	caps = gst_caps_copy(allowed_caps); -	result = gst_caps_new_simple("audio/x-sbc", -					"rate", G_TYPE_INT, rate, -					"channels", G_TYPE_INT, channels, -					"mode", G_TYPE_STRING, mode, -					"blocks", G_TYPE_INT, blocks, -					"subbands", G_TYPE_INT, subbands, -					"allocation", G_TYPE_STRING, allocation, -					"bitpool", G_TYPE_INT, bitpool, -					NULL); -	parse->sbc.rate = rate; -	parse->sbc.channels = channels; -	parse->sbc.blocks = blocks; -	parse->sbc.subbands = subbands; -	parse->sbc.bitpool = bitpool; -	parse->sbc.joint = gst_sbc_get_mode_int(mode); -	parse->sbc.allocation = gst_sbc_get_allocation_mode_int(allocation); - -	return result; +	value = g_new0(GValue, 1); + +	structure = gst_caps_get_structure(caps, 0); + +	if (parse->rate != 0) +		gst_sbc_util_set_structure_int_param(structure, "rate", +				parse->rate, value); +	if (parse->channels != 0) +		gst_sbc_util_set_structure_int_param(structure, "channels", +				parse->channels, value); + +	g_free(value); + +	return caps;  } -static gboolean sbc_parse_sink_setcaps(GstPad * pad, GstCaps * caps) +static gboolean sbc_parse_src_acceptcaps(GstPad *pad, GstCaps *caps)  { +	GstStructure *structure;  	GstSbcParse *parse; -	GstCaps *inter, *other, *srccaps; +	gint rate, channels;  	parse = GST_SBC_PARSE(GST_PAD_PARENT(pad)); -	other = gst_pad_peer_get_caps(parse->srcpad); -	if (other == NULL) -		other = gst_caps_new_any(); +	structure = gst_caps_get_structure(caps, 0); -	inter = gst_caps_intersect(caps, other); -	if (gst_caps_is_empty(inter)) { -		gst_caps_unref(inter); +	if (!gst_structure_get_int(structure, "rate", &rate))  		return FALSE; -	} -	srccaps = sbc_parse_select_caps(parse, inter); -	if (srccaps == NULL) { -		gst_caps_unref(inter); +	if (!gst_structure_get_int(structure, "channels", &channels))  		return FALSE; -	} -	gst_pad_set_caps(parse->srcpad, srccaps); +	if ((parse->rate == 0 || parse->rate == rate) +		&& (parse->channels == 0 || parse->channels == channels)) +		return TRUE; -	gst_caps_unref(inter); -	gst_caps_unref(other); -	gst_caps_unref(srccaps); - -	return TRUE; +	return FALSE;  }  static GstFlowReturn sbc_parse_chain(GstPad *pad, GstBuffer *buffer) @@ -234,11 +146,13 @@ static GstFlowReturn sbc_parse_chain(GstPad *pad, GstBuffer *buffer)  	timestamp = GST_BUFFER_TIMESTAMP(buffer);  	if (parse->buffer) { -		GstBuffer *temp = buffer; +		GstBuffer *temp; +		temp = buffer;  		buffer = gst_buffer_span(parse->buffer, 0, buffer, -			GST_BUFFER_SIZE(parse->buffer) + GST_BUFFER_SIZE(buffer)); -		gst_buffer_unref(temp); +			GST_BUFFER_SIZE(parse->buffer) +			+ GST_BUFFER_SIZE(buffer));  		gst_buffer_unref(parse->buffer); +		gst_buffer_unref(temp);  		parse->buffer = NULL;  	} @@ -300,11 +214,13 @@ static GstStateChangeReturn sbc_parse_change_state(GstElement *element,  	case GST_STATE_CHANGE_PAUSED_TO_READY:  		GST_DEBUG("Finish subband codec"); +  		if (parse->buffer) {  			gst_buffer_unref(parse->buffer);  			parse->buffer = NULL;  		}  		sbc_finish(&parse->sbc); +  		break;  	default: @@ -341,12 +257,27 @@ static void gst_sbc_parse_class_init(GstSbcParseClass *klass)  static void gst_sbc_parse_init(GstSbcParse *self, GstSbcParseClass *klass)  { -	self->sinkpad = gst_pad_new_from_static_template(&sbc_parse_sink_factory, "sink"); -	gst_pad_set_chain_function(self->sinkpad, GST_DEBUG_FUNCPTR(sbc_parse_chain)); +	self->sinkpad = gst_pad_new_from_static_template( +		&sbc_parse_sink_factory, "sink"); +	gst_pad_set_chain_function(self->sinkpad, +		GST_DEBUG_FUNCPTR(sbc_parse_chain));  	gst_pad_set_setcaps_function (self->sinkpad,  		GST_DEBUG_FUNCPTR (sbc_parse_sink_setcaps));  	gst_element_add_pad(GST_ELEMENT(self), self->sinkpad); -	self->srcpad = gst_pad_new_from_static_template(&sbc_parse_src_factory, "src"); +	self->srcpad = gst_pad_new_from_static_template( +		&sbc_parse_src_factory, "src"); +	gst_pad_set_getcaps_function (self->srcpad, +		GST_DEBUG_FUNCPTR (sbc_parse_src_getcaps)); +	gst_pad_set_acceptcaps_function (self->srcpad, +		GST_DEBUG_FUNCPTR (sbc_parse_src_acceptcaps)); +	/* FIXME get encoding parameters on set caps */  	gst_element_add_pad(GST_ELEMENT(self), self->srcpad);  } + +gboolean gst_sbc_parse_plugin_init (GstPlugin * plugin) +{ +	return gst_element_register (plugin, "sbcparse", +			GST_RANK_NONE, GST_TYPE_SBC_PARSE); +} + diff --git a/audio/gstsbcparse.h b/audio/gstsbcparse.h index ceaf2197..eb9ca441 100644 --- a/audio/gstsbcparse.h +++ b/audio/gstsbcparse.h @@ -50,6 +50,9 @@ struct _GstSbcParse {  	GstBuffer *buffer;  	sbc_t sbc; + +	gint channels; +	gint rate;  };  struct _GstSbcParseClass { @@ -58,4 +61,6 @@ struct _GstSbcParseClass {  GType gst_sbc_parse_get_type(void); +gboolean gst_sbc_parse_plugin_init(GstPlugin *plugin); +  G_END_DECLS diff --git a/audio/gstsbcutil.c b/audio/gstsbcutil.c index f2351e6b..d791a8d6 100644 --- a/audio/gstsbcutil.c +++ b/audio/gstsbcutil.c @@ -26,6 +26,7 @@  #endif  #include "ipc.h" +#include <math.h>  #include "gstsbcutil.h"  /* @@ -306,4 +307,103 @@ error:  	return result;  } +/** + * Sets the int field_value to the  param "field" on the structure. + * value is used to do the operation, it must be a uninitialized (zero-filled) + * GValue, it will be left unitialized at the end of the function. + */ +void gst_sbc_util_set_structure_int_param(GstStructure *structure, +			const gchar* field, gint field_value, +			GValue *value) +{ +	value = g_value_init(value, G_TYPE_INT); +	g_value_set_int(value, field_value); +	gst_structure_set_value(structure, field, value); +	g_value_unset(value); +} + +/** + * Sets the string field_value to the  param "field" on the structure. + * value is used to do the operation, it must be a uninitialized (zero-filled) + * GValue, it will be left unitialized at the end of the function. + */ +void gst_sbc_util_set_structure_string_param(GstStructure *structure, +			const gchar* field, const gchar* field_value, +			GValue *value) +{ +	value = g_value_init(value, G_TYPE_STRING); +	g_value_set_string(value, field_value); +	gst_structure_set_value(structure, field, value); +	g_value_unset(value); +} + +gboolean gst_sbc_util_fill_sbc_params(sbc_t *sbc, GstCaps *caps) +{ +	GstStructure *structure; +	gint rate, channels, subbands, blocks, bitpool; +	const gchar* mode; +	const gchar* allocation; + +	g_assert(gst_caps_is_fixed(caps)); + +	structure = gst_caps_get_structure(caps, 0); + +	if (!gst_structure_get_int(structure, "rate", &rate)) +		return FALSE; +	if (!gst_structure_get_int(structure, "channels", &channels)) +		return FALSE; +	if (!gst_structure_get_int(structure, "subbands", &subbands)) +		return FALSE; +	if (!gst_structure_get_int(structure, "blocks", &blocks)) +		return FALSE; +	if (!gst_structure_get_int(structure, "bitpool", &bitpool)) +		return FALSE; + +	if (!(mode = gst_structure_get_string(structure, "mode"))) +		return FALSE; +	if (!(allocation = gst_structure_get_string(structure, "allocation"))) +		return FALSE; + +	sbc->rate = rate; +	sbc->channels = channels; +	sbc->blocks = blocks; +	sbc->subbands = subbands; +	sbc->bitpool = bitpool; +	sbc->joint = gst_sbc_get_mode_int(mode); +	sbc->allocation = gst_sbc_get_allocation_mode_int(allocation); + +	return TRUE; +} + +gint gst_sbc_util_calc_frame_len(gint subbands, gint channels, +                gint blocks, gint bitpool, gint channel_mode) +{ +        gint len; +        gint join; +        len = 4 + (4 * subbands * channels)/8; + +        if (channel_mode == BT_A2DP_CHANNEL_MODE_MONO || +                channel_mode == BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL) +                len += ((blocks * channels * bitpool)+7) / 8; +        else { +                join = channel_mode == BT_A2DP_CHANNEL_MODE_JOINT_STEREO +			? 1 : 0; +                len += ((join * subbands + blocks * bitpool)+7)/8; +        } + +        return len; +} + +gint gst_sbc_util_calc_bitrate(gint frame_len, gint rate, gint subbands, +                gint blocks) +{ +	return (((frame_len * 8 * rate / subbands) / blocks) / 1000); +} + +gint64 gst_sbc_util_calc_frame_duration(gint rate, gint blocks, gint subbands) +{ +	gint64 res = 1000000; +	return res * blocks * subbands / rate; +} + diff --git a/audio/gstsbcutil.h b/audio/gstsbcutil.h index 4581abf7..a67bf1be 100644 --- a/audio/gstsbcutil.h +++ b/audio/gstsbcutil.h @@ -49,3 +49,22 @@ const gchar *gst_sbc_get_mode_string(int joint);  GstCaps* gst_sbc_caps_from_sbc(sbc_capabilities_t *sbc, gint channels);  GstCaps* gst_sbc_util_caps_fixate(GstCaps *caps, gchar** error_message); + +void gst_sbc_util_set_structure_int_param(GstStructure *structure, +			const gchar* field, gint field_value, +			GValue *value); + +void gst_sbc_util_set_structure_string_param(GstStructure *structure, +			const gchar* field, const gchar* field_value, +			GValue *value); + +gboolean gst_sbc_util_fill_sbc_params(sbc_t *sbc, GstCaps *caps); + +gint gst_sbc_util_calc_frame_len(gint subbands, gint channels, +                gint blocks, gint bitpool, gint channel_mode); + +gint gst_sbc_util_calc_bitrate(gint frame_len, gint rate, gint subbands, +		gint blocks); + +gint64 gst_sbc_util_calc_frame_duration(gint rate, gint blocks, gint subbands); + | 
