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); |