diff options
| -rw-r--r-- | audio/gsta2dpsendersink.c | 345 | ||||
| -rw-r--r-- | audio/gsta2dpsendersink.h | 11 | ||||
| -rw-r--r-- | audio/gsta2dpsink.c | 212 | ||||
| -rw-r--r-- | audio/gsta2dpsink.h | 4 | ||||
| -rw-r--r-- | audio/gstbluetooth.c | 23 | ||||
| -rw-r--r-- | audio/gstsbcutil.c | 73 | ||||
| -rw-r--r-- | audio/gstsbcutil.h | 1 | 
7 files changed, 620 insertions, 49 deletions
diff --git a/audio/gsta2dpsendersink.c b/audio/gsta2dpsendersink.c index 1f54f29c..0256bb93 100644 --- a/audio/gsta2dpsendersink.c +++ b/audio/gsta2dpsendersink.c @@ -35,6 +35,8 @@  #include <bluetooth/bluetooth.h> +#include <gst/rtp/gstrtpbuffer.h> +  #include "ipc.h"  #include "rtp.h"  #include "gstsbcutil.h" @@ -46,6 +48,8 @@ GST_DEBUG_CATEGORY_STATIC(a2dp_sender_sink_debug);  #define BUFFER_SIZE 2048  #define TEMPLATE_MAX_BITPOOL 64 +#define CRC_PROTECTED 1 +#define CRC_UNPROTECTED 0  #define GST_A2DP_SENDER_SINK_MUTEX_LOCK(s) G_STMT_START {	\  		g_mutex_lock (s->sink_lock);		\ @@ -84,7 +88,18 @@ 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\"" +				"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_a2dp_sender_sink_audioservice_send(GstA2dpSenderSink *self, @@ -584,20 +599,145 @@ static GstStructure *gst_a2dp_sender_sink_parse_sbc_caps(  	return structure;  } +static GstStructure *gst_a2dp_sender_sink_parse_mpeg_caps( +			GstA2dpSenderSink *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_a2dp_sender_sink_update_caps(GstA2dpSenderSink *self)  {  	sbc_capabilities_t *sbc = &self->data->caps.sbc_capabilities;  	mpeg_capabilities_t *mpeg = &self->data->caps.mpeg_capabilities; -	GstStructure *structure; +	GstStructure *sbc_structure; +	GstStructure *mpeg_structure;  	gchar *tmp;  	GST_LOG_OBJECT(self, "updating device caps"); -	structure = gst_a2dp_sender_sink_parse_sbc_caps(self, sbc); +	sbc_structure = gst_a2dp_sender_sink_parse_sbc_caps(self, sbc); +	mpeg_structure = gst_a2dp_sender_sink_parse_mpeg_caps(self, mpeg);  	if (self->dev_caps != NULL)  		gst_caps_unref(self->dev_caps); -	self->dev_caps = gst_caps_new_full(structure, NULL); +	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); @@ -623,6 +763,7 @@ static gboolean gst_a2dp_sender_sink_get_capabilities(GstA2dpSenderSink *self)  	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"); +		return FALSE;  	}  	io_error = gst_a2dp_sender_sink_audioservice_expect(self, @@ -648,6 +789,71 @@ static gboolean gst_a2dp_sender_sink_get_capabilities(GstA2dpSenderSink *self)  	return TRUE;  } +static gint gst_a2dp_sender_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_a2dp_sender_sink_tag(const GstTagList *taglist, +			const gchar* tag, gpointer user_data) +{ +	gboolean crc; +	gchar *channel_mode = NULL; +	GstA2dpSenderSink *self = GST_A2DP_SENDER_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_a2dp_sender_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_a2dp_sender_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_a2dp_sender_sink_event(GstBaseSink *basesink, +			GstEvent *event) +{ +	GstA2dpSenderSink *self = GST_A2DP_SENDER_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_a2dp_sender_sink_tag, self); +	} + +	return TRUE; +} +  static gboolean gst_a2dp_sender_sink_start(GstBaseSink *basesink)  {  	GstA2dpSenderSink *self = GST_A2DP_SENDER_SINK(basesink); @@ -675,9 +881,15 @@ static gboolean gst_a2dp_sender_sink_start(GstBaseSink *basesink)  	self->stream = NULL;  	self->stream_caps = NULL; +	self->mp3_using_crc = -1; +	self->channel_mode = -1; +	self->mpeg_stream_changed = FALSE; -	if (!gst_a2dp_sender_sink_get_capabilities(self)) +	if (!gst_a2dp_sender_sink_get_capabilities(self)) { +		GST_ERROR_OBJECT(self, "failed to get capabilities " +				"from device");  		goto failed; +	}  	return TRUE; @@ -739,6 +951,77 @@ static gboolean gst_a2dp_sender_sink_stream_start(GstA2dpSenderSink *self)  	return TRUE;  } +static gboolean gst_a2dp_sender_sink_init_mp3_pkt_conf( +		GstA2dpSenderSink *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_a2dp_sender_sink_configure(GstA2dpSenderSink *self,  			GstCaps *caps)  { @@ -748,6 +1031,7 @@ static gboolean gst_a2dp_sender_sink_configure(GstA2dpSenderSink *self,  	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); @@ -757,8 +1041,17 @@ static gboolean gst_a2dp_sender_sink_configure(GstA2dpSenderSink *self,  	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); +	structure = gst_caps_get_structure(caps, 0); + +	if (gst_structure_has_name(structure, "audio/x-sbc")) +		ret = gst_a2dp_sender_sink_init_pkt_conf(self, caps, +				&req->sbc_capabilities); +	else if (gst_structure_has_name(structure, "audio/mpeg")) +		ret = gst_a2dp_sender_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"); @@ -884,6 +1177,8 @@ static void gst_a2dp_sender_sink_class_init(GstA2dpSenderSinkClass *klass)  					gst_a2dp_sender_sink_preroll);  	basesink_class->unlock = GST_DEBUG_FUNCPTR(  					gst_a2dp_sender_sink_unlock); +	basesink_class->event = GST_DEBUG_FUNCPTR( +					gst_a2dp_sender_sink_event);  	basesink_class->buffer_alloc =  		GST_DEBUG_FUNCPTR(gst_a2dp_sender_sink_buffer_alloc); @@ -1022,3 +1317,39 @@ gchar *gst_a2dp_sender_sink_get_device(GstA2dpSenderSink *self)  	return g_strdup(self->device);  } +void gst_a2dp_sender_sink_set_crc(GstA2dpSenderSink *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_a2dp_sender_sink_set_channel_mode(GstA2dpSenderSink *self, +			const gchar *mode) +{ +	gint new_mode; + +	new_mode = gst_a2dp_sender_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); +} + + diff --git a/audio/gsta2dpsendersink.h b/audio/gsta2dpsendersink.h index 863ef6cb..f23c86b2 100644 --- a/audio/gsta2dpsendersink.h +++ b/audio/gsta2dpsendersink.h @@ -56,6 +56,11 @@ struct _GstA2dpSenderSink {  	struct bluetooth_data *data;  	GIOChannel *server; +	/* mp3 stream data (outside caps data)*/ +	gboolean mpeg_stream_changed; +	gint mp3_using_crc; +	gint channel_mode; +  	/* stream connection data */  	GstCaps *stream_caps; @@ -85,6 +90,12 @@ gchar *gst_a2dp_sender_sink_get_device(GstA2dpSenderSink *sink);  gboolean gst_a2dp_sender_sink_plugin_init(GstPlugin *plugin); +void gst_a2dp_sender_sink_set_crc(GstA2dpSenderSink *self, gboolean crc); + +void gst_a2dp_sender_sink_set_channel_mode(GstA2dpSenderSink *self, +			const gchar *mode); + +  G_END_DECLS  #endif /* __GST_A2DP_SENDER_SINK_H */ diff --git a/audio/gsta2dpsink.c b/audio/gsta2dpsink.c index d90909c7..091d5472 100644 --- a/audio/gsta2dpsink.c +++ b/audio/gsta2dpsink.c @@ -61,8 +61,14 @@ static GstStaticPadTemplate gst_a2dp_sink_factory =  				"allocation = (string) { snr, loudness }, "  				"bitpool = (int) [ 2, "  				TEMPLATE_MAX_BITPOOL_STR " ]; " +				"audio/mpeg;"  				)); +static gboolean gst_a2dp_sink_handle_event(GstPad *pad, GstEvent *event); +static gboolean gst_a2dp_sink_set_caps(GstPad *pad, GstCaps *caps); +static GstCaps *gst_a2dp_sink_get_caps(GstPad *pad); +static gboolean gst_a2dp_sink_init_caps_filter(GstA2dpSink *self); +  static void gst_a2dp_sink_base_init(gpointer g_class)  {  	GstElementClass *element_class = GST_ELEMENT_CLASS(g_class); @@ -116,30 +122,100 @@ static void gst_a2dp_sink_get_property(GObject *object, guint prop_id,  	}  } +static gboolean gst_a2dp_sink_init_ghost_pad(GstA2dpSink *self) +{ +	GstPad *capsfilter_pad; + +	/* 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"); + +	return TRUE; +} +  static GstStateChangeReturn gst_a2dp_sink_change_state(GstElement *element,  			GstStateChange transition)  { +	GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;  	GstA2dpSink *self = GST_A2DP_SINK(element);  	switch (transition) { +	case GST_STATE_CHANGE_READY_TO_PAUSED: +		self->taglist = gst_tag_list_new(); +		break; +  	case GST_STATE_CHANGE_NULL_TO_READY: -		gst_element_set_state(GST_ELEMENT(self->sink), +		self->sink = GST_A2DP_SENDER_SINK(gst_element_factory_make( +				"a2dpsendersink", "sendersink")); +		if (self->sink == NULL) { +			GST_WARNING_OBJECT(self, "failed to create a2dpsendersink"); +			return GST_STATE_CHANGE_FAILURE; +		} + +		if (self->device != NULL) +			gst_a2dp_sender_sink_set_device(self->sink, +					self->device); + +		ret = gst_element_set_state(GST_ELEMENT(self->sink),  			GST_STATE_READY);  		break; +	default: +		break; +	} -	case GST_STATE_CHANGE_READY_TO_NULL: +	if (ret == GST_STATE_CHANGE_FAILURE) +		return ret; + +	ret = GST_ELEMENT_CLASS(parent_class)->change_state(element, +                        transition); + +	switch (transition) { +	case GST_STATE_CHANGE_PAUSED_TO_READY: +		if (self->taglist) { +			gst_tag_list_free(self->taglist); +			self->taglist = NULL; +		}  		if (self->newseg_event != NULL) {  			gst_event_unref(self->newseg_event);  			self->newseg_event = NULL;  		} +		if (self->taglist) { +			gst_tag_list_free(self->taglist); +			self->taglist = NULL; +		} +		break; + +	case GST_STATE_CHANGE_READY_TO_NULL: +		if (!gst_bin_remove(GST_BIN(self), GST_ELEMENT(self->sink))) +			GST_WARNING_OBJECT(self, "Failed to remove " +					"a2dpsendersink from bin");  		break;  	default:  		break;  	} -	return GST_ELEMENT_CLASS(parent_class)->change_state(element, -			transition); +	return ret;  }  static void gst_a2dp_sink_class_init(GstA2dpSinkClass *klass) @@ -281,6 +357,54 @@ static gboolean gst_a2dp_sink_init_rtp_sbc_element(GstA2dpSink *self)  	return TRUE;  remove_element_and_fail: +	gst_element_set_state(rtppay, GST_STATE_NULL); +	gst_bin_remove(GST_BIN(self), rtppay); +	return FALSE; + +cleanup_and_fail: +	if (rtppay != NULL) +		g_object_unref(G_OBJECT(rtppay)); + +	return FALSE; +} + +static gboolean gst_a2dp_sink_init_rtp_mpeg_element(GstA2dpSink *self) +{ +	GstElement *rtppay; + +	/* FIXME we will need a internal mpegparse for identifying +	 * stream stuff */ + +	rtppay = gst_element_factory_make("rtpmpapay", "rtp"); +	if (rtppay == NULL) { +		GST_ERROR_OBJECT(self, "Couldn't create rtpmpapay"); +		return FALSE; +	} + +	if (!gst_bin_add(GST_BIN(self), rtppay)) { +		GST_ERROR_OBJECT(self, "failed to add rtpmpapay to the bin"); +		goto cleanup_and_fail; +	} + +	if (gst_element_set_state(rtppay, GST_STATE_READY) == +			GST_STATE_CHANGE_FAILURE) { +		GST_ERROR_OBJECT(self, "rtpmpapay failed to go to ready"); +		goto remove_element_and_fail; +	} + +	if (!gst_element_link(self->capsfilter, rtppay)) { +		GST_ERROR_OBJECT(self, "couldn't link capsfilter " +			"to rtpmpapay"); +		goto remove_element_and_fail; +	} + +	self->rtp = GST_BASE_RTP_PAYLOAD(rtppay); + +	gst_element_set_state(rtppay, GST_STATE_PAUSED); + +	return TRUE; + +remove_element_and_fail:  	gst_element_set_state (rtppay, GST_STATE_NULL);  	gst_bin_remove(GST_BIN(self), rtppay);  	return FALSE; @@ -296,6 +420,10 @@ static gboolean gst_a2dp_sink_set_caps(GstPad *pad, GstCaps *caps)  {  	GstA2dpSink *self;  	GstStructure *structure; +	GstEvent *event; +	GstPad *capsfilterpad; +	gboolean crc; +	gchar *mode = NULL;  	self = GST_A2DP_SINK(GST_PAD_PARENT(pad));  	GST_INFO_OBJECT(self, "setting caps"); @@ -303,9 +431,13 @@ static gboolean gst_a2dp_sink_set_caps(GstPad *pad, GstCaps *caps)  	structure = gst_caps_get_structure(caps, 0);  	/* 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 { +	if (gst_structure_has_name(structure, "audio/x-sbc")) { +		if (!gst_a2dp_sink_init_rtp_sbc_element(self)) +			return FALSE; +	} else if (gst_structure_has_name(structure, "audio/mpeg")) { +		if (!gst_a2dp_sink_init_rtp_mpeg_element(self)) +			return FALSE; +	} else {  		GST_ERROR_OBJECT(self, "Unexpected media type");  		return FALSE;  	} @@ -313,6 +445,27 @@ static gboolean gst_a2dp_sink_set_caps(GstPad *pad, GstCaps *caps)  	if (!gst_a2dp_sink_init_sender_sink(self))  		return FALSE; +	/* check if we should push the taglist FIXME should we push this? +	 * we can send the tags directly if needed */ +	if (self->taglist != NULL && +			gst_structure_has_name(structure, "audio/mpeg")) { + +		event = gst_event_new_tag(self->taglist); + +		/* send directly the crc */ +		if (gst_tag_list_get_boolean(self->taglist, "has-crc", &crc)) +			gst_a2dp_sender_sink_set_crc(self->sink, crc); + +		if (gst_tag_list_get_string(self->taglist, "channel-mode", +				&mode)) +			gst_a2dp_sender_sink_set_channel_mode(self->sink, mode); + +		capsfilterpad = gst_ghost_pad_get_target(self->ghostpad); +		gst_pad_send_event(capsfilterpad, event); +		self->taglist = NULL; +		g_free(mode); +	} +  	if (!gst_a2dp_sender_sink_set_device_caps(self->sink, caps))  		return FALSE; @@ -332,6 +485,7 @@ static gboolean gst_a2dp_sink_set_caps(GstPad *pad, GstCaps *caps)  static gboolean gst_a2dp_sink_handle_event(GstPad *pad, GstEvent *event)  {  	GstA2dpSink *self; +	GstTagList *taglist = NULL;  	self = GST_A2DP_SINK(GST_PAD_PARENT(pad)); @@ -341,6 +495,17 @@ static gboolean gst_a2dp_sink_handle_event(GstPad *pad, GstEvent *event)  		if (self->newseg_event != NULL)  			gst_event_unref(self->newseg_event);  		self->newseg_event = gst_event_ref(event); +	} else if (GST_EVENT_TYPE(event) == GST_EVENT_TAG && +			gst_element_get_parent(GST_ELEMENT(self->sink)) != +			GST_OBJECT_CAST(self)) { +		if (self->taglist == NULL) { +			gst_event_parse_tag(event, &self->taglist); +		} else { +			gst_event_parse_tag(event, &taglist); +			gst_tag_list_insert(self->taglist, taglist, +					GST_TAG_MERGE_REPLACE); +		} +		/* FIXME handle tag events */  	}  	return self->ghostpad_eventfunc(GST_PAD(self->ghostpad), event); @@ -368,13 +533,14 @@ failed:  static void gst_a2dp_sink_init(GstA2dpSink *self,  			GstA2dpSinkClass *klass)  { -	GstPad *capsfilter_pad; -  	self->sink = NULL;  	self->rtp = NULL;  	self->device = NULL;  	self->capsfilter = NULL; +	self->mpegparse = NULL;  	self->newseg_event = NULL; +	self->taglist = NULL; +	self->ghostpad = NULL;  	/* we initialize our capsfilter */  	gst_a2dp_sink_init_caps_filter(self); @@ -382,34 +548,8 @@ static void gst_a2dp_sink_init(GstA2dpSink *self,  		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); +	gst_a2dp_sink_init_ghost_pad(self); -	/* 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");  }  gboolean gst_a2dp_sink_plugin_init (GstPlugin * plugin) diff --git a/audio/gsta2dpsink.h b/audio/gsta2dpsink.h index 4bf9d603..f74185ef 100644 --- a/audio/gsta2dpsink.h +++ b/audio/gsta2dpsink.h @@ -47,6 +47,7 @@ struct _GstA2dpSink {  	GstBaseRTPPayload *rtp;  	GstA2dpSenderSink *sink;  	GstElement *capsfilter; +	GstElement *mpegparse;  	gchar *device; @@ -55,6 +56,9 @@ struct _GstA2dpSink {  	GstPadEventFunction ghostpad_eventfunc;  	GstEvent *newseg_event; +	/* Store the tags received before the a2dpsender sink is created +	 * when it is created we forward this to it */ +	GstTagList *taglist;  };  struct _GstA2dpSinkClass { diff --git a/audio/gstbluetooth.c b/audio/gstbluetooth.c index 764bc899..eaab23d2 100644 --- a/audio/gstbluetooth.c +++ b/audio/gstbluetooth.c @@ -25,6 +25,11 @@  #include <config.h>  #endif +#include <gst/gst.h> + +#include "gstsbcutil.h" +#include <sbc.h> +  #include "gstsbcenc.h"  #include "gstsbcdec.h"  #include "gstsbcparse.h" @@ -38,12 +43,26 @@ static GstStaticCaps sbc_caps = GST_STATIC_CAPS("audio/x-sbc");  static void sbc_typefind(GstTypeFind *tf, gpointer ignore)  { -	guint8 *data = gst_type_find_peek(tf, 0, 1); +	GstCaps *caps; +	guint8 *aux; +	sbc_t sbc; +	guint8 *data = gst_type_find_peek(tf, 0, 32); + +	if (sbc_init(&sbc, 0) < 0) +		return;  	if (data == NULL || *data != 0x9c)	/* SBC syncword */  		return; -	gst_type_find_suggest(tf, GST_TYPE_FIND_POSSIBLE, SBC_CAPS); +	aux = g_new(guint8, 32); +	memcpy(aux, data, 32); +	sbc_parse(&sbc, aux, 32); +	g_free(aux); +	caps = gst_sbc_parse_caps_from_sbc(&sbc); +	sbc_finish(&sbc); + +	gst_type_find_suggest(tf, GST_TYPE_FIND_POSSIBLE, caps); +	gst_caps_unref(caps);  }  static gchar *sbc_exts[] = { "sbc", NULL }; diff --git a/audio/gstsbcutil.c b/audio/gstsbcutil.c index d791a8d6..04b7217a 100644 --- a/audio/gstsbcutil.c +++ b/audio/gstsbcutil.c @@ -89,12 +89,22 @@ const gchar *gst_sbc_get_allocation_from_list(const GValue *value)  /*   * Selects one mode from the ones on the list - * TODO - use a better aproach   */ -const gchar *gst_sbc_get_mode_from_list(const GValue *value) +const gchar *gst_sbc_get_mode_from_list(const GValue *list)  { -	guint size = gst_value_list_get_size(value); -	return g_value_get_string(gst_value_list_get_value(value, size-1)); +	int i; +	const GValue *value; +	const gchar *aux; + +	guint size = gst_value_list_get_size(list); +	for (i = 0; i < size; i++) { +		value = gst_value_list_get_value(list, i); +		aux = g_value_get_string(value); +		if (strcmp("stereo", aux) == 0) { +			return "stereo"; +		} +	} +	return g_value_get_string(gst_value_list_get_value(list, size-1));  }  gint gst_sbc_get_allocation_mode_int(const gchar *allocation) @@ -157,6 +167,61 @@ const gchar *gst_sbc_get_allocation_string(int alloc)  	}  } +/* channel mode */ +#define SBC_CM_MONO             0x00 +#define SBC_CM_DUAL_CHANNEL     0x01 +#define SBC_CM_STEREO           0x02 +#define SBC_CM_JOINT_STEREO     0x03 + +/* allocation mode */ +#define SBC_AM_LOUDNESS         0x00 +#define SBC_AM_SNR              0x01 + +const gchar *gst_sbc_get_mode_string_from_sbc_t(int channels, int joint) +{ +	if (channels == 2 && joint == 1) +		return "joint"; +	else if (channels == 2 && joint == 0) +		return "stereo"; +	else +		return NULL; +} + +const gchar *gst_sbc_get_allocation_string_from_sbc_t(int alloc) +{ +	switch (alloc) { +	case SBC_AM_LOUDNESS: +		return "loudness"; +	case SBC_AM_SNR: +		return "snr"; +	default: +		return NULL; +	} +} + +GstCaps* gst_sbc_parse_caps_from_sbc(sbc_t *sbc) +{ +	GstCaps *caps; +	const gchar *mode_str; +	const gchar *allocation_str; + +	mode_str = gst_sbc_get_mode_string_from_sbc_t(sbc->channels, +							sbc->joint); +	allocation_str = gst_sbc_get_allocation_string_from_sbc_t( +							sbc->allocation); +	caps = gst_caps_new_simple("audio/x-sbc", +				"rate", G_TYPE_INT, sbc->rate, +				"channels", G_TYPE_INT, sbc->channels, +				"mode", G_TYPE_STRING, mode_str, +				"subbands", G_TYPE_INT, sbc->subbands, +				"blocks", G_TYPE_INT, sbc->blocks, +				"allocation", G_TYPE_STRING, allocation_str, +				"bitpool", G_TYPE_INT, sbc->bitpool, +				NULL); + +	return caps; +} +  GstCaps* gst_sbc_caps_from_sbc(sbc_capabilities_t *sbc, gint channels)  {  	GstCaps *caps; diff --git a/audio/gstsbcutil.h b/audio/gstsbcutil.h index a67bf1be..4bdb5ac5 100644 --- a/audio/gstsbcutil.h +++ b/audio/gstsbcutil.h @@ -47,6 +47,7 @@ gint gst_sbc_get_mode_int(const gchar *mode);  const gchar *gst_sbc_get_mode_string(int joint);  GstCaps* gst_sbc_caps_from_sbc(sbc_capabilities_t *sbc, gint channels); +GstCaps* gst_sbc_parse_caps_from_sbc(sbc_t *sbc);  GstCaps* gst_sbc_util_caps_fixate(GstCaps *caps, gchar** error_message);  | 
