summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--audio/gsta2dpsendersink.c345
-rw-r--r--audio/gsta2dpsendersink.h11
-rw-r--r--audio/gsta2dpsink.c212
-rw-r--r--audio/gsta2dpsink.h4
-rw-r--r--audio/gstbluetooth.c23
-rw-r--r--audio/gstsbcutil.c73
-rw-r--r--audio/gstsbcutil.h1
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);