diff options
Diffstat (limited to 'audio/gstavdtpsink.c')
| -rw-r--r-- | audio/gstavdtpsink.c | 1355 | 
1 files changed, 1355 insertions, 0 deletions
diff --git a/audio/gstavdtpsink.c b/audio/gstavdtpsink.c new file mode 100644 index 00000000..c956ecbd --- /dev/null +++ b/audio/gstavdtpsink.c @@ -0,0 +1,1355 @@ +/* + * + *  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 <gst/rtp/gstrtpbuffer.h> + +#include "ipc.h" +#include "rtp.h" +#include "gstsbcutil.h" + +#include "gstavdtpsink.h" + +GST_DEBUG_CATEGORY_STATIC(avdtp_sink_debug); +#define GST_CAT_DEFAULT avdtp_sink_debug + +#define BUFFER_SIZE 2048 +#define TEMPLATE_MAX_BITPOOL 64 +#define CRC_PROTECTED 1 +#define CRC_UNPROTECTED 0 + +#define GST_AVDTP_SINK_MUTEX_LOCK(s) G_STMT_START {	\ +		g_mutex_lock (s->sink_lock);		\ +	} G_STMT_END + +#define GST_AVDTP_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(GstAvdtpSink, gst_avdtp_sink, GstBaseSink, +			GST_TYPE_BASE_SINK); + +static const GstElementDetails avdtp_sink_details = +	GST_ELEMENT_DETAILS("Bluetooth AVDTP sink", +				"Sink/Audio", +				"Plays audio to an A2DP device", +				"Marcel Holtmann <marcel@holtmann.org>"); + +static GstStaticPadTemplate avdtp_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\";" +				"application/x-rtp, " +				"media = (string) \"audio\", " +				"payload = (int) " +				GST_RTP_PAYLOAD_MPA_STRING ", " +				"clock-rate = (int) 90000; " +				"application/x-rtp, " +				"media = (string) \"audio\", " +				"payload = (int) " +				GST_RTP_PAYLOAD_DYNAMIC_STRING ", " +				"clock-rate = (int) 90000, " +				"encoding-name = (string) \"MPA\"" +				)); + +static GIOError gst_avdtp_sink_audioservice_send(GstAvdtpSink *self, +					const bt_audio_msg_header_t *msg); +static GIOError gst_avdtp_sink_audioservice_expect( +				GstAvdtpSink *self, +				bt_audio_msg_header_t *outmsg, +				int expected_type); + + +static void gst_avdtp_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(&avdtp_sink_factory)); + +	gst_element_class_set_details(element_class, &avdtp_sink_details); +} + +static gboolean gst_avdtp_sink_stop(GstBaseSink *basesink) +{ +	GstAvdtpSink *self = GST_AVDTP_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_avdtp_sink_finalize(GObject *object) +{ +	GstAvdtpSink *self = GST_AVDTP_SINK(object); + +	if (self->data) +		gst_avdtp_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_avdtp_sink_set_property(GObject *object, guint prop_id, +					const GValue *value, GParamSpec *pspec) +{ +	GstAvdtpSink *sink = GST_AVDTP_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_avdtp_sink_get_property(GObject *object, guint prop_id, +					GValue *value, GParamSpec *pspec) +{ +	GstAvdtpSink *sink = GST_AVDTP_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_avdtp_sink_bluetooth_recvmsg_fd(GstAvdtpSink *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_avdtp_sink_init_pkt_conf(GstAvdtpSink *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_SBC_SAMPLING_FREQ_44100; +	else if (rate == 48000) +		cfg->frequency = BT_SBC_SAMPLING_FREQ_48000; +	else if (rate == 32000) +		cfg->frequency = BT_SBC_SAMPLING_FREQ_32000; +	else if (rate == 16000) +		cfg->frequency = BT_SBC_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_avdtp_sink_conf_recv_stream_fd( +					GstAvdtpSink *self) +{ +	struct bluetooth_data *data = self->data; +	gint ret; +	GIOError err; +	GError *gerr = NULL; +	GIOStatus status; +	GIOFlags flags; +	gsize read; + +	ret = gst_avdtp_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) +{ +	GstAvdtpSink *sink; + +	if (cond & G_IO_HUP || cond & G_IO_NVAL) +		return FALSE; +	else if (cond & G_IO_ERR) { +		sink = GST_AVDTP_SINK(data); +		GST_WARNING_OBJECT(sink, "Untreated callback G_IO_ERR"); +	} + +	return TRUE; +} + +static GstStructure *gst_avdtp_sink_parse_sbc_caps( +			GstAvdtpSink *self, sbc_capabilities_t *sbc) +{ +	GstStructure *structure; +	GValue *value; +	GValue *list; +	gboolean mono, stereo; + +	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_SBC_SAMPLING_FREQ_48000) { +		g_value_set_int(value, 48000); +		gst_value_list_prepend_value(list, value); +	} +	if (sbc->frequency & BT_SBC_SAMPLING_FREQ_44100) { +		g_value_set_int(value, 44100); +		gst_value_list_prepend_value(list, value); +	} +	if (sbc->frequency & BT_SBC_SAMPLING_FREQ_32000) { +		g_value_set_int(value, 32000); +		gst_value_list_prepend_value(list, value); +	} +	if (sbc->frequency & BT_SBC_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); + +	return structure; +} + +static GstStructure *gst_avdtp_sink_parse_mpeg_caps( +			GstAvdtpSink *self, mpeg_capabilities_t *mpeg) +{ +	GstStructure *structure; +	GValue *value; +	GValue *list; +	gboolean valid_layer = FALSE; +	gboolean mono, stereo; + +	GST_LOG_OBJECT(self, "parsing mpeg caps"); + +	structure = gst_structure_empty_new("audio/mpeg"); +	value = g_new0(GValue, 1); +	g_value_init(value, G_TYPE_INT); + +	list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST); +	g_value_set_int(value, 1); +	gst_value_list_prepend_value(list, value); +	g_value_set_int(value, 2); +	gst_value_list_prepend_value(list, value); +	gst_structure_set_value(structure, "mpegversion", list); +	g_free(list); + +	/* layer */ +	GST_LOG_OBJECT(self, "setting mpeg layer"); +	list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST); +	if (mpeg->layer & BT_MPEG_LAYER_1) { +		g_value_set_int(value, 1); +		gst_value_list_prepend_value(list, value); +		valid_layer = TRUE; +	} +	if (mpeg->layer & BT_MPEG_LAYER_2) { +		g_value_set_int(value, 2); +		gst_value_list_prepend_value(list, value); +		valid_layer = TRUE; +	} +	if (mpeg->layer & BT_MPEG_LAYER_3) { +		g_value_set_int(value, 3); +		gst_value_list_prepend_value(list, value); +		valid_layer = TRUE; +	} +	if (list) { +		gst_structure_set_value(structure, "layer", list); +		g_free(list); +		list = NULL; +	} + +	if (!valid_layer) { +		gst_structure_free(structure); +		g_free(value); +		return NULL; +	} + +	/* rate */ +	GST_LOG_OBJECT(self, "setting mpeg rate"); +	list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST); +	if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_48000) { +		g_value_set_int(value, 48000); +		gst_value_list_prepend_value(list, value); +	} +	if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_44100) { +		g_value_set_int(value, 44100); +		gst_value_list_prepend_value(list, value); +	} +	if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_32000) { +		g_value_set_int(value, 32000); +		gst_value_list_prepend_value(list, value); +	} +	if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_24000) { +		g_value_set_int(value, 24000); +		gst_value_list_prepend_value(list, value); +	} +	if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_22050) { +		g_value_set_int(value, 22050); +		gst_value_list_prepend_value(list, value); +	} +	if (mpeg->frequency & BT_MPEG_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; +	} + +	/* channels */ +	GST_LOG_OBJECT(self, "setting mpeg channels"); +	mono = FALSE; +	stereo = FALSE; +	if (mpeg->channel_mode & BT_A2DP_CHANNEL_MODE_MONO) +		mono = TRUE; +	if ((mpeg->channel_mode & BT_A2DP_CHANNEL_MODE_STEREO) || +			(mpeg->channel_mode & +			BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL) || +			(mpeg->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); + +	return structure; +} + +static gboolean gst_avdtp_sink_update_caps(GstAvdtpSink *self) +{ +	sbc_capabilities_t *sbc = &self->data->caps.sbc_capabilities; +	mpeg_capabilities_t *mpeg = &self->data->caps.mpeg_capabilities; +	GstStructure *sbc_structure; +	GstStructure *mpeg_structure; +	gchar *tmp; + +	GST_LOG_OBJECT(self, "updating device caps"); + +	sbc_structure = gst_avdtp_sink_parse_sbc_caps(self, sbc); +	mpeg_structure = gst_avdtp_sink_parse_mpeg_caps(self, mpeg); + +	if (self->dev_caps != NULL) +		gst_caps_unref(self->dev_caps); +	self->dev_caps = gst_caps_new_full(sbc_structure, NULL); +	if (mpeg_structure != NULL) +		gst_caps_append_structure(self->dev_caps, mpeg_structure); + +	tmp = gst_caps_to_string(self->dev_caps); +	GST_DEBUG_OBJECT(self, "Device capabilities: %s", tmp); +	g_free(tmp); + +	return TRUE; +} + +static gboolean gst_avdtp_sink_get_capabilities(GstAvdtpSink *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_avdtp_sink_audioservice_send(self, &req->h); +	if (io_error != G_IO_ERROR_NONE) { +		GST_ERROR_OBJECT(self, "Error while asking device caps"); +		return FALSE; +	} + +	io_error = gst_avdtp_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_avdtp_sink_update_caps(self)) { +		GST_WARNING_OBJECT(self, "failed to update capabilities"); +		return FALSE; +	} + +	return TRUE; +} + +static gint gst_avdtp_sink_get_channel_mode(const gchar *mode) +{ +	if (strcmp(mode, "stereo") == 0) +		return BT_A2DP_CHANNEL_MODE_STEREO; +	else if (strcmp(mode, "joint") == 0) +		return BT_A2DP_CHANNEL_MODE_JOINT_STEREO; +	else if (strcmp(mode, "dual") == 0) +		return BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL; +	else if (strcmp(mode, "mono") == 0) +		return BT_A2DP_CHANNEL_MODE_MONO; +	else +		return -1; +} + +static void gst_avdtp_sink_tag(const GstTagList *taglist, +			const gchar* tag, gpointer user_data) +{ +	gboolean crc; +	gchar *channel_mode = NULL; +	GstAvdtpSink *self = GST_AVDTP_SINK(user_data); + +	if (strcmp(tag, "has-crc") == 0) { + +		if (!gst_tag_list_get_boolean(taglist, tag, &crc)) { +			GST_WARNING_OBJECT(self, "failed to get crc tag"); +			self->mpeg_stream_changed = TRUE; +		} + +		gst_avdtp_sink_set_crc(self, crc); + +	} else if (strcmp(tag, "channel-mode") == 0) { + +		if (!gst_tag_list_get_string(taglist, tag, &channel_mode)) { +			GST_WARNING_OBJECT(self, +				"failed to get channel-mode tag"); +			self->mpeg_stream_changed = TRUE; +		} + +		self->channel_mode = gst_avdtp_sink_get_channel_mode( +					channel_mode); +		if (self->channel_mode == -1) +			GST_WARNING_OBJECT(self, "Received invalid channel " +					"mode: %s", channel_mode); +		g_free(channel_mode); + +	} else +		GST_DEBUG_OBJECT(self, "received unused tag: %s", tag); +} + +static gboolean gst_avdtp_sink_event(GstBaseSink *basesink, +			GstEvent *event) +{ +	GstAvdtpSink *self = GST_AVDTP_SINK(basesink); +	GstTagList *taglist = NULL; + +	if (GST_EVENT_TYPE(event) == GST_EVENT_TAG) { +		/* we check the tags, mp3 has tags that are importants and +		 * are outside caps */ +		gst_event_parse_tag(event, &taglist); +		gst_tag_list_foreach(taglist, gst_avdtp_sink_tag, self); +	} + +	return TRUE; +} + +static gboolean gst_avdtp_sink_start(GstBaseSink *basesink) +{ +	GstAvdtpSink *self = GST_AVDTP_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; +	self->mp3_using_crc = -1; +	self->channel_mode = -1; +	self->mpeg_stream_changed = FALSE; + +	if (!gst_avdtp_sink_get_capabilities(self)) { +		GST_ERROR_OBJECT(self, "failed to get capabilities " +				"from device"); +		goto failed; +	} + +	return TRUE; + +failed: +	bt_audio_service_close(sk); +	return FALSE; +} + +static gboolean gst_avdtp_sink_stream_start(GstAvdtpSink *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_avdtp_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_avdtp_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_avdtp_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_avdtp_sink_conf_recv_stream_fd(self)) +		return FALSE; + +	return TRUE; +} + +static gboolean gst_avdtp_sink_init_mp3_pkt_conf( +		GstAvdtpSink *self, GstCaps *caps, +		mpeg_capabilities_t *pkt) +{ +	const GValue *value = NULL; +	gint rate, layer; +	GstStructure *structure = gst_caps_get_structure(caps, 0); + +	/* layer */ +	value = gst_structure_get_value(structure, "layer"); +	layer = g_value_get_int(value); +	if (layer == 1) +		pkt->layer = BT_MPEG_LAYER_1; +	else if (layer == 2) +		pkt->layer = BT_MPEG_LAYER_2; +	else if (layer == 3) +		pkt->layer = BT_MPEG_LAYER_3; +	else { +		GST_ERROR_OBJECT(self, "Unexpected layer: %d", layer); +		return FALSE; +	} + +	/* crc */ +	if (self->mp3_using_crc != -1) +		pkt->crc = self->mp3_using_crc; +	else { +		GST_ERROR_OBJECT(self, "No info about crc was received, " +				" can't proceed"); +		return FALSE; +	} + +	/* channel mode */ +	if (self->channel_mode != -1) +		pkt->channel_mode = self->channel_mode; +	else { +		GST_ERROR_OBJECT(self, "No info about channel mode " +				"received, can't proceed"); +		return FALSE; +	} + +	/* mpf - we will only use the mandatory one */ +	pkt->mpf = 0; + +	value = gst_structure_get_value(structure, "rate"); +	rate = g_value_get_int(value); +	if (rate == 44100) +		pkt->frequency = BT_MPEG_SAMPLING_FREQ_44100; +	else if (rate == 48000) +		pkt->frequency = BT_MPEG_SAMPLING_FREQ_48000; +	else if (rate == 32000) +		pkt->frequency = BT_MPEG_SAMPLING_FREQ_32000; +	else if (rate == 24000) +		pkt->frequency = BT_MPEG_SAMPLING_FREQ_24000; +	else if (rate == 22050) +		pkt->frequency = BT_MPEG_SAMPLING_FREQ_22050; +	else if (rate == 16000) +		pkt->frequency = BT_MPEG_SAMPLING_FREQ_16000; +	else { +		GST_ERROR_OBJECT(self, "Invalid rate while setting caps"); +		return FALSE; +	} + +	/* vbr - we always say its vbr, we don't have how to know it */ +	pkt->bitrate = 0x8000; + +	/* bitrate - we don't set anything, its vbr */ +	/* FIXME - is this right? */ + +	return TRUE; +} + +static gboolean gst_avdtp_sink_configure(GstAvdtpSink *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; +	GstStructure *structure; + +	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); +	structure = gst_caps_get_structure(caps, 0); + +	if (gst_structure_has_name(structure, "audio/x-sbc")) +		ret = gst_avdtp_sink_init_pkt_conf(self, caps, +				&req->sbc_capabilities); +	else if (gst_structure_has_name(structure, "audio/mpeg")) +		ret = gst_avdtp_sink_init_mp3_pkt_conf(self, caps, +				&req->mpeg_capabilities); +	else +		ret = FALSE; + +	if (!ret) { +		GST_ERROR_OBJECT(self, "Couldn't parse caps " +				"to packet configuration"); +		return FALSE; +	} + +	io_error = gst_avdtp_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_avdtp_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_avdtp_sink_preroll(GstBaseSink *basesink, +					GstBuffer *buffer) +{ +	GstAvdtpSink *sink = GST_AVDTP_SINK(basesink); +	gboolean ret; + +	GST_AVDTP_SINK_MUTEX_LOCK(sink); + +	ret = gst_avdtp_sink_stream_start(sink); + +	GST_AVDTP_SINK_MUTEX_UNLOCK(sink); + +	if (!ret) +		return GST_FLOW_ERROR; + +	return GST_FLOW_OK; +} + +static GstFlowReturn gst_avdtp_sink_render(GstBaseSink *basesink, +					GstBuffer *buffer) +{ +	GstAvdtpSink *self = GST_AVDTP_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_avdtp_sink_unlock(GstBaseSink *basesink) +{ +	GstAvdtpSink *self = GST_AVDTP_SINK(basesink); + +	if (self->stream != NULL) +		g_io_channel_flush (self->stream, NULL); + +	return TRUE; +} + +static GstFlowReturn gst_avdtp_sink_buffer_alloc(GstBaseSink *basesink, +				guint64 offset, guint size, GstCaps* caps, +				GstBuffer **buf) +{ +	GstAvdtpSink *self = GST_AVDTP_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_avdtp_sink_class_init(GstAvdtpSinkClass *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_avdtp_sink_finalize); +	object_class->set_property = GST_DEBUG_FUNCPTR( +					gst_avdtp_sink_set_property); +	object_class->get_property = GST_DEBUG_FUNCPTR( +					gst_avdtp_sink_get_property); + +	basesink_class->start = GST_DEBUG_FUNCPTR(gst_avdtp_sink_start); +	basesink_class->stop = GST_DEBUG_FUNCPTR(gst_avdtp_sink_stop); +	basesink_class->render = GST_DEBUG_FUNCPTR( +					gst_avdtp_sink_render); +	basesink_class->preroll = GST_DEBUG_FUNCPTR( +					gst_avdtp_sink_preroll); +	basesink_class->unlock = GST_DEBUG_FUNCPTR( +					gst_avdtp_sink_unlock); +	basesink_class->event = GST_DEBUG_FUNCPTR( +					gst_avdtp_sink_event); + +	basesink_class->buffer_alloc = +		GST_DEBUG_FUNCPTR(gst_avdtp_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(avdtp_sink_debug, "a2dpsendersink", 0, +				"A2DP sink element"); +} + +static void gst_avdtp_sink_init(GstAvdtpSink *self, +			GstAvdtpSinkClass *klass) +{ +	self->device = NULL; +	self->data = NULL; + +	self->stream = NULL; + +	self->dev_caps = NULL; + +	self->sink_lock = g_mutex_new(); +} + +static GIOError gst_avdtp_sink_audioservice_send( +					GstAvdtpSink *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_avdtp_sink_audioservice_recv( +					GstAvdtpSink *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_avdtp_sink_audioservice_expect( +			GstAvdtpSink *self, bt_audio_msg_header_t *outmsg, +			int expected_type) +{ +	GIOError status; + +	status = gst_avdtp_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_avdtp_sink_plugin_init (GstPlugin * plugin) +{ +	return gst_element_register (plugin, "avdtpsink", +			GST_RANK_NONE, GST_TYPE_AVDTP_SINK); +} + + +/* public functions */ +GstCaps *gst_avdtp_sink_get_device_caps(GstAvdtpSink *sink) +{ +	if (sink->dev_caps == NULL) +		return NULL; + +	return gst_caps_copy(sink->dev_caps); +} + +gboolean gst_avdtp_sink_set_device_caps(GstAvdtpSink *self, +			GstCaps *caps) +{ +	gboolean ret; + +	GST_DEBUG_OBJECT(self, "setting device caps"); +	GST_AVDTP_SINK_MUTEX_LOCK(self); +	ret = gst_avdtp_sink_configure(self, caps); + +	if (self->stream_caps) +		gst_caps_unref(self->stream_caps); +	self->stream_caps = gst_caps_ref(caps); + +	GST_AVDTP_SINK_MUTEX_UNLOCK(self); + +	return ret; +} + +guint gst_avdtp_sink_get_link_mtu(GstAvdtpSink *sink) +{ +	return sink->data->link_mtu; +} + +void gst_avdtp_sink_set_device(GstAvdtpSink *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_avdtp_sink_get_device(GstAvdtpSink *self) +{ +	return g_strdup(self->device); +} + +void gst_avdtp_sink_set_crc(GstAvdtpSink *self, gboolean crc) +{ +	gint new_crc; + +	new_crc = crc ? CRC_PROTECTED : CRC_UNPROTECTED; + +	/* test if we already received a different crc */ +	if (self->mp3_using_crc != -1 && new_crc != self->mp3_using_crc) { +		GST_ERROR_OBJECT(self, "crc changed during stream"); +		/* FIXME test this, its not being used anywhere */ +		self->mpeg_stream_changed = TRUE; +		return; +	} +	self->mp3_using_crc = new_crc; + +} + +void gst_avdtp_sink_set_channel_mode(GstAvdtpSink *self, +			const gchar *mode) +{ +	gint new_mode; + +	new_mode = gst_avdtp_sink_get_channel_mode(mode); + +	if (self->channel_mode != -1 && new_mode != self->channel_mode) { +		GST_ERROR_OBJECT(self, "channel mode changed during stream"); +		self->mpeg_stream_changed = TRUE; +	} + +	self->channel_mode = new_mode; +	if (self->channel_mode == -1) +		GST_WARNING_OBJECT(self, "Received invalid channel " +				"mode: %s", mode); +} + +  | 
