diff options
author | Luiz Augusto von Dentz <luiz.dentz@openbossa.org> | 2008-01-23 13:14:02 +0000 |
---|---|---|
committer | Luiz Augusto von Dentz <luiz.dentz@openbossa.org> | 2008-01-23 13:14:02 +0000 |
commit | a0af7ee44534dad8f35a4142c6a22177e54ffc57 (patch) | |
tree | c6a475d035e40f617a213a9ad5327a7eb5d01388 /audio/gsta2dpsink.c | |
parent | 1cacae6dd9f44d0e403aa29e45eb3d20e7127f68 (diff) |
Make a2dpsink to act like a bin and split the payloader.
Diffstat (limited to 'audio/gsta2dpsink.c')
-rw-r--r-- | audio/gsta2dpsink.c | 1042 |
1 files changed, 239 insertions, 803 deletions
diff --git a/audio/gsta2dpsink.c b/audio/gsta2dpsink.c index 5798bc24..d90909c7 100644 --- a/audio/gsta2dpsink.c +++ b/audio/gsta2dpsink.c @@ -26,166 +26,67 @@ #endif #include <unistd.h> -#include <sys/un.h> -#include <sys/socket.h> -#include <fcntl.h> #include <pthread.h> -#include <netinet/in.h> - -#include <bluetooth/bluetooth.h> - -#include "ipc.h" -#include "rtp.h" #include "gstsbcutil.h" - #include "gsta2dpsink.h" -GST_DEBUG_CATEGORY_STATIC(a2dp_sink_debug); -#define GST_CAT_DEFAULT a2dp_sink_debug - -#define BUFFER_SIZE 2048 -#define TEMPLATE_MAX_BITPOOL_VALUE 64 +GST_DEBUG_CATEGORY_STATIC(gst_a2dp_sink_debug); +#define GST_CAT_DEFAULT gst_a2dp_sink_debug -#define GST_A2DP_SINK_MUTEX_LOCK(s) G_STMT_START { \ - g_mutex_lock (s->sink_lock); \ - } G_STMT_END - -#define GST_A2DP_SINK_MUTEX_UNLOCK(s) G_STMT_START { \ - g_mutex_unlock (s->sink_lock); \ - } G_STMT_END - - -struct bluetooth_data { - struct bt_getcapabilities_rsp caps; /* Bluetooth device capabilities */ - struct bt_setconfiguration_rsp cfg; /* Bluetooth device configuration */ - int samples; /* Number of encoded samples */ - gchar buffer[BUFFER_SIZE]; /* Codec transfer buffer */ - gsize count; /* Codec transfer buffer counter */ - - int nsamples; /* Cumulative number of codec samples */ - uint16_t seq_num; /* Cumulative packet sequence */ - int frame_count; /* Current frames in buffer*/ -}; - - -#define IS_SBC(n) (strcmp((n), "audio/x-sbc") == 0) -#define IS_MPEG(n) (strcmp((n), "audio/mpeg") == 0) +#define A2DP_SBC_RTP_PAYLOAD_TYPE 1 +#define TEMPLATE_MAX_BITPOOL_STR "64" enum { PROP_0, - PROP_DEVICE, + PROP_DEVICE }; -GST_BOILERPLATE(GstA2dpSink, gst_a2dp_sink, GstBaseSink, GST_TYPE_BASE_SINK); +GST_BOILERPLATE(GstA2dpSink, gst_a2dp_sink, GstBin, GST_TYPE_BIN); -static const GstElementDetails a2dp_sink_details = +static const GstElementDetails gst_a2dp_sink_details = GST_ELEMENT_DETAILS("Bluetooth A2DP sink", "Sink/Audio", "Plays audio to an A2DP device", "Marcel Holtmann <marcel@holtmann.org>"); -static GstStaticPadTemplate a2dp_sink_factory = +static GstStaticPadTemplate gst_a2dp_sink_factory = GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS, - GST_STATIC_CAPS("audio/x-sbc, " + GST_STATIC_CAPS("audio/x-sbc, " "rate = (int) { 16000, 32000, 44100, 48000 }, " "channels = (int) [ 1, 2 ], " "mode = (string) { mono, dual, stereo, joint }, " "blocks = (int) { 4, 8, 12, 16 }, " "subbands = (int) { 4, 8 }, " - "allocation = (string) { snr, loudness }," - /* FIXME use constant here */ - "bitpool = (int) [ 2, 64 ]; " - "audio/mpeg, " - "mpegversion = (int) 1, " - "layer = (int) [ 1, 3 ], " - "rate = (int) { 16000, 22050, 24000, 32000, 44100, 48000 }, " - "channels = (int) [ 1, 2 ]")); - -static GIOError gst_a2dp_sink_audioservice_send(GstA2dpSink *self, - const bt_audio_msg_header_t *msg); -static GIOError gst_a2dp_sink_audioservice_expect(GstA2dpSink *self, - bt_audio_msg_header_t *outmsg, - int expected_type); - + "allocation = (string) { snr, loudness }, " + "bitpool = (int) [ 2, " + TEMPLATE_MAX_BITPOOL_STR " ]; " + )); static void gst_a2dp_sink_base_init(gpointer g_class) { GstElementClass *element_class = GST_ELEMENT_CLASS(g_class); + gst_element_class_set_details(element_class, + &gst_a2dp_sink_details); gst_element_class_add_pad_template(element_class, - gst_static_pad_template_get(&a2dp_sink_factory)); - - gst_element_class_set_details(element_class, &a2dp_sink_details); -} - -static gboolean gst_a2dp_sink_stop(GstBaseSink *basesink) -{ - GstA2dpSink *self = GST_A2DP_SINK(basesink); - - GST_INFO_OBJECT(self, "stop"); - - if (self->watch_id != 0) { - g_source_remove(self->watch_id); - self->watch_id = 0; - } - - if (self->stream) { - g_io_channel_flush(self->stream, NULL); - g_io_channel_close(self->stream); - g_io_channel_unref(self->stream); - self->stream = NULL; - } - - if (self->server) { - bt_audio_service_close(g_io_channel_unix_get_fd(self->server)); - g_io_channel_unref(self->server); - self->server = NULL; - } - - if (self->data) { - g_free(self->data); - self->data = NULL; - } - - if (self->stream_caps) { - gst_caps_unref(self->stream_caps); - self->stream_caps = NULL; - } - - if (self->dev_caps) { - gst_caps_unref(self->dev_caps); - self->dev_caps = NULL; - } - - return TRUE; -} - -static void gst_a2dp_sink_finalize(GObject *object) -{ - GstA2dpSink *self = GST_A2DP_SINK(object); - - if (self->data) - gst_a2dp_sink_stop(GST_BASE_SINK(self)); - - if (self->device) - g_free(self->device); - - g_mutex_free(self->sink_lock); - - G_OBJECT_CLASS(parent_class)->finalize(object); + gst_static_pad_template_get(&gst_a2dp_sink_factory)); } static void gst_a2dp_sink_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { - GstA2dpSink *sink = GST_A2DP_SINK(object); + GstA2dpSink *self = GST_A2DP_SINK(object); switch (prop_id) { case PROP_DEVICE: - if (sink->device) - g_free(sink->device); - sink->device = g_value_dup_string(value); + if (self->sink != NULL) + gst_a2dp_sender_sink_set_device(self->sink, + g_value_get_string(value)); + + if (self->device != NULL) + g_free(self->device); + self->device = g_value_dup_string(value); break; default: @@ -197,11 +98,16 @@ static void gst_a2dp_sink_set_property(GObject *object, guint prop_id, static void gst_a2dp_sink_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { - GstA2dpSink *sink = GST_A2DP_SINK(object); + GstA2dpSink *self = GST_A2DP_SINK(object); + gchar *device; switch (prop_id) { case PROP_DEVICE: - g_value_set_string(value, sink->device); + if (self->sink != NULL) { + device = gst_a2dp_sender_sink_get_device(self->sink); + if (device != NULL) + g_value_take_string(value, device); + } break; default: @@ -210,775 +116,305 @@ static void gst_a2dp_sink_get_property(GObject *object, guint prop_id, } } -static gint gst_a2dp_sink_bluetooth_recvmsg_fd(GstA2dpSink *sink) -{ - int err, ret; - - ret = bt_audio_service_get_data_fd(g_io_channel_unix_get_fd(sink->server)); - - if (ret < 0) { - err = errno; - GST_ERROR_OBJECT(sink, "Unable to receive fd: %s (%d)", - strerror(err), err); - return -err; - } - - sink->stream = g_io_channel_unix_new(ret); - GST_DEBUG_OBJECT(sink, "stream_fd=%d", ret); - - return 0; -} - -static gboolean gst_a2dp_sink_init_pkt_conf(GstA2dpSink *sink, - GstCaps *caps, - sbc_capabilities_t *pkt) +static GstStateChangeReturn gst_a2dp_sink_change_state(GstElement *element, + GstStateChange transition) { - sbc_capabilities_t *cfg = &sink->data->caps.sbc_capabilities; - const GValue *value = NULL; - const char *pref, *name; - gint rate, blocks, subbands; - GstStructure *structure = gst_caps_get_structure(caps, 0); - - name = gst_structure_get_name(structure); - /* FIXME only sbc supported here, should suport mp3 */ - if (!(IS_SBC(name))) { - GST_ERROR_OBJECT(sink, "Unsupported format %s", name); - return FALSE; - } - - value = gst_structure_get_value(structure, "rate"); - rate = g_value_get_int(value); - if (rate == 44100) - cfg->frequency = BT_A2DP_SAMPLING_FREQ_44100; - else if (rate == 48000) - cfg->frequency = BT_A2DP_SAMPLING_FREQ_48000; - else if (rate == 32000) - cfg->frequency = BT_A2DP_SAMPLING_FREQ_32000; - else if (rate == 16000) - cfg->frequency = BT_A2DP_SAMPLING_FREQ_16000; - else { - GST_ERROR_OBJECT(sink, "Invalid rate while setting caps"); - return FALSE; - } - - value = gst_structure_get_value(structure, "mode"); - pref = g_value_get_string(value); - if (strcmp(pref, "auto") == 0) - cfg->channel_mode = BT_A2DP_CHANNEL_MODE_AUTO; - else if (strcmp(pref, "mono") == 0) - cfg->channel_mode = BT_A2DP_CHANNEL_MODE_MONO; - else if (strcmp(pref, "dual") == 0) - cfg->channel_mode = BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL; - else if (strcmp(pref, "stereo") == 0) - cfg->channel_mode = BT_A2DP_CHANNEL_MODE_STEREO; - else if (strcmp(pref, "joint") == 0) - cfg->channel_mode = BT_A2DP_CHANNEL_MODE_JOINT_STEREO; - else { - GST_ERROR_OBJECT(sink, "Invalid mode %s", pref); - return FALSE; - } + GstA2dpSink *self = GST_A2DP_SINK(element); - value = gst_structure_get_value(structure, "allocation"); - pref = g_value_get_string(value); - if (strcmp(pref, "auto") == 0) - cfg->allocation_method = BT_A2DP_ALLOCATION_AUTO; - else if (strcmp(pref, "loudness") == 0) - cfg->allocation_method = BT_A2DP_ALLOCATION_LOUDNESS; - else if (strcmp(pref, "snr") == 0) - cfg->allocation_method = BT_A2DP_ALLOCATION_SNR; - else { - GST_ERROR_OBJECT(sink, "Invalid allocation: %s", pref); - return FALSE; - } + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + gst_element_set_state(GST_ELEMENT(self->sink), + GST_STATE_READY); + break; - value = gst_structure_get_value(structure, "subbands"); - subbands = g_value_get_int(value); - if (subbands == 8) - cfg->subbands = BT_A2DP_SUBBANDS_8; - else if (subbands == 4) - cfg->subbands = BT_A2DP_SUBBANDS_4; - else { - GST_ERROR_OBJECT(sink, "Invalid subbands %d", subbands); - return FALSE; - } + case GST_STATE_CHANGE_READY_TO_NULL: + if (self->newseg_event != NULL) { + gst_event_unref(self->newseg_event); + self->newseg_event = NULL; + } + break; - value = gst_structure_get_value(structure, "blocks"); - blocks = g_value_get_int(value); - if (blocks == 16) - cfg->block_length = BT_A2DP_BLOCK_LENGTH_16; - else if (blocks == 12) - cfg->block_length = BT_A2DP_BLOCK_LENGTH_12; - else if (blocks == 8) - cfg->block_length = BT_A2DP_BLOCK_LENGTH_8; - else if (blocks == 4) - cfg->block_length = BT_A2DP_BLOCK_LENGTH_4; - else { - GST_ERROR_OBJECT(sink, "Invalid blocks %d", blocks); - return FALSE; + default: + break; } - /* FIXME min and max ??? */ - value = gst_structure_get_value(structure, "bitpool"); - - cfg->max_bitpool = cfg->min_bitpool = g_value_get_int(value); - - memcpy(pkt, cfg, sizeof(*pkt)); - - return TRUE; + return GST_ELEMENT_CLASS(parent_class)->change_state(element, + transition); } -static gboolean gst_a2dp_sink_conf_recv_stream_fd(GstA2dpSink *self) +static void gst_a2dp_sink_class_init(GstA2dpSinkClass *klass) { - struct bluetooth_data *data = self->data; - gint ret; - GIOError err; - GError *gerr = NULL; - GIOStatus status; - GIOFlags flags; - gsize read; - - ret = gst_a2dp_sink_bluetooth_recvmsg_fd(self); - if (ret < 0) - return FALSE; - - if (!self->stream) { - GST_ERROR_OBJECT(self, "Error while configuring device: " - "could not acquire audio socket"); - return FALSE; - } + GObjectClass *object_class = G_OBJECT_CLASS(klass); + GstElementClass *element_class = GST_ELEMENT_CLASS(klass); - /* set stream socket to nonblock */ - GST_LOG_OBJECT(self, "setting stream socket to nonblock"); - flags = g_io_channel_get_flags(self->stream); - flags |= G_IO_FLAG_NONBLOCK; - status = g_io_channel_set_flags(self->stream, flags, &gerr); - if (status != G_IO_STATUS_NORMAL) { - if (gerr) - GST_WARNING_OBJECT(self, "Error while " - "setting server socket to nonblock: " - "%s", gerr->message); - else - GST_WARNING_OBJECT(self, "Error while " - "setting server " - "socket to nonblock"); - } + parent_class = g_type_class_peek_parent(klass); - /* It is possible there is some outstanding - data in the pipe - we have to empty it */ - GST_LOG_OBJECT(self, "emptying stream pipe"); - while (1) { - err = g_io_channel_read(self->stream, data->buffer, - (gsize) data->cfg.link_mtu, - &read); - if (err != G_IO_ERROR_NONE || read <= 0) - break; - } + object_class->set_property = GST_DEBUG_FUNCPTR( + gst_a2dp_sink_set_property); + object_class->get_property = GST_DEBUG_FUNCPTR( + gst_a2dp_sink_get_property); - /* set stream socket to block */ - GST_LOG_OBJECT(self, "setting stream socket to block"); - flags = g_io_channel_get_flags(self->stream); - flags &= ~G_IO_FLAG_NONBLOCK; - status = g_io_channel_set_flags(self->stream, flags, &gerr); - if (status != G_IO_STATUS_NORMAL) { - if (gerr) - GST_WARNING_OBJECT(self, "Error while " - "setting server socket to block:" - "%s", gerr->message); - else - GST_WARNING_OBJECT(self, "Error while " - "setting server " - "socket to block"); - } + element_class->change_state = GST_DEBUG_FUNCPTR( + gst_a2dp_sink_change_state); - memset(data->buffer, 0, sizeof(data->buffer)); + g_object_class_install_property(object_class, PROP_DEVICE, + g_param_spec_string("device", "Device", + "Bluetooth remote device address", + NULL, G_PARAM_READWRITE)); - return TRUE; + GST_DEBUG_CATEGORY_INIT(gst_a2dp_sink_debug, "a2dpsink", 0, + "A2DP sink element"); } -static gboolean server_callback(GIOChannel *chan, - GIOCondition cond, gpointer data) +static GstCaps *gst_a2dp_sink_get_device_caps(GstA2dpSink *self) { - GstA2dpSink *sink; - - if (cond & G_IO_HUP || cond & G_IO_NVAL) - return FALSE; - else if (cond & G_IO_ERR) { - sink = GST_A2DP_SINK(data); - GST_WARNING_OBJECT(sink, "Untreated callback G_IO_ERR"); - } - - return TRUE; + return gst_a2dp_sender_sink_get_device_caps(self->sink); } -static gboolean gst_a2dp_sink_update_caps(GstA2dpSink *self) +static GstCaps *gst_a2dp_sink_get_caps(GstPad *pad) { - sbc_capabilities_t *sbc = &self->data->caps.sbc_capabilities; - GstStructure *structure; - GValue *value; - GValue *list; - gchar *tmp; - - GST_LOG_OBJECT(self, "updating device caps"); - - structure = gst_structure_empty_new("audio/x-sbc"); - value = g_value_init(g_new0(GValue, 1), G_TYPE_STRING); - - /* mode */ - list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST); - if (sbc->channel_mode == BT_A2DP_CHANNEL_MODE_AUTO) { - g_value_set_static_string(value, "joint"); - gst_value_list_prepend_value(list, value); - g_value_set_static_string(value, "stereo"); - gst_value_list_prepend_value(list, value); - g_value_set_static_string(value, "mono"); - gst_value_list_prepend_value(list, value); - g_value_set_static_string(value, "dual"); - gst_value_list_prepend_value(list, value); - } else { - if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_MONO) { - g_value_set_static_string(value, "mono"); - gst_value_list_prepend_value(list, value); - } - if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_STEREO) { - g_value_set_static_string(value, "stereo"); - gst_value_list_prepend_value(list, value); - } - if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL) { - g_value_set_static_string(value, "dual"); - gst_value_list_prepend_value(list, value); - } - if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_JOINT_STEREO) { - g_value_set_static_string(value, "joint"); - gst_value_list_prepend_value(list, value); - } - } - g_value_unset(value); - if (list) { - gst_structure_set_value(structure, "mode", list); - g_free(list); - list = NULL; - } - - /* subbands */ - list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST); - value = g_value_init(value, G_TYPE_INT); - if (sbc->subbands & BT_A2DP_SUBBANDS_4) { - g_value_set_int(value, 4); - gst_value_list_prepend_value(list, value); - } - if (sbc->subbands & BT_A2DP_SUBBANDS_8) { - g_value_set_int(value, 8); - gst_value_list_prepend_value(list, value); - } - g_value_unset(value); - if (list) { - gst_structure_set_value(structure, "subbands", list); - g_free(list); - list = NULL; - } - - /* blocks */ - value = g_value_init(value, G_TYPE_INT); - list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST); - if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_16) { - g_value_set_int(value, 16); - gst_value_list_prepend_value(list, value); - } - if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_12) { - g_value_set_int(value, 12); - gst_value_list_prepend_value(list, value); - } - if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_8) { - g_value_set_int(value, 8); - gst_value_list_prepend_value(list, value); - } - if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_4) { - g_value_set_int(value, 4); - gst_value_list_prepend_value(list, value); - } - g_value_unset(value); - if (list) { - gst_structure_set_value(structure, "blocks", list); - g_free(list); - list = NULL; - } - - /* allocation */ - g_value_init(value, G_TYPE_STRING); - list = g_value_init(g_new0(GValue,1), GST_TYPE_LIST); - if (sbc->allocation_method == BT_A2DP_ALLOCATION_AUTO) { - g_value_set_static_string(value, "loudness"); - gst_value_list_prepend_value(list, value); - g_value_set_static_string(value, "snr"); - gst_value_list_prepend_value(list, value); + GstCaps *caps; + GstCaps *caps_aux; + GstA2dpSink *self = GST_A2DP_SINK(GST_PAD_PARENT(pad)); + + if (self->sink == NULL) { + GST_DEBUG_OBJECT(self, "a2dpsink isn't initialized " + "returning template caps"); + caps = gst_static_pad_template_get_caps( + &gst_a2dp_sink_factory); } else { - if (sbc->allocation_method & BT_A2DP_ALLOCATION_LOUDNESS) { - g_value_set_static_string(value, "loudness"); - gst_value_list_prepend_value(list, value); - } - if (sbc->allocation_method & BT_A2DP_ALLOCATION_SNR) { - g_value_set_static_string(value, "snr"); - gst_value_list_prepend_value(list, value); - } - } - g_value_unset(value); - if (list) { - gst_structure_set_value(structure, "allocation", list); - g_free(list); - list = NULL; - } - - /* rate */ - g_value_init(value, G_TYPE_INT); - list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST); - if (sbc->frequency & BT_A2DP_SAMPLING_FREQ_48000) { - g_value_set_int(value, 48000); - gst_value_list_prepend_value(list, value); - } - if (sbc->frequency & BT_A2DP_SAMPLING_FREQ_44100) { - g_value_set_int(value, 44100); - gst_value_list_prepend_value(list, value); - } - if (sbc->frequency & BT_A2DP_SAMPLING_FREQ_32000) { - g_value_set_int(value, 32000); - gst_value_list_prepend_value(list, value); - } - if (sbc->frequency & BT_A2DP_SAMPLING_FREQ_16000) { - g_value_set_int(value, 16000); - gst_value_list_prepend_value(list, value); - } - g_value_unset(value); - if (list) { - gst_structure_set_value(structure, "rate", list); - g_free(list); - list = NULL; - } - - /* bitpool */ - value = g_value_init(value, GST_TYPE_INT_RANGE); - gst_value_set_int_range(value, - MIN(sbc->min_bitpool, TEMPLATE_MAX_BITPOOL_VALUE), - MIN(sbc->max_bitpool, TEMPLATE_MAX_BITPOOL_VALUE)); - gst_structure_set_value(structure, "bitpool", value); - - /* channels */ - gst_value_set_int_range(value, 1, 2); - gst_structure_set_value(structure, "channels", value); - - g_free(value); - - if (self->dev_caps != NULL) - gst_caps_unref(self->dev_caps); - self->dev_caps = gst_caps_new_full(structure, NULL); - - tmp = gst_caps_to_string(self->dev_caps); - GST_DEBUG_OBJECT(self, "Device capabilities: %s", tmp); - g_free(tmp); - - return TRUE; + GST_LOG_OBJECT(self, "Getting device caps"); + caps = gst_a2dp_sink_get_device_caps(self); + if (caps == NULL) + caps = gst_static_pad_template_get_caps( + &gst_a2dp_sink_factory); + } + caps_aux = gst_caps_copy(caps); + g_object_set(self->capsfilter, "caps", caps_aux, NULL); + gst_caps_unref(caps_aux); + return caps; } -static gboolean gst_a2dp_sink_get_capabilities(GstA2dpSink *self) +static gboolean gst_a2dp_sink_init_sender_sink(GstA2dpSink *self) { - gchar *buf[BT_AUDIO_IPC_PACKET_SIZE]; - struct bt_getcapabilities_req *req = (void *) buf; - bt_audio_rsp_msg_header_t *rsp_hdr = (void *) buf; - struct bt_getcapabilities_rsp *rsp = (void *) buf; - GIOError io_error; - - memset(req, 0, BT_AUDIO_IPC_PACKET_SIZE); + GstElement *sink; - req->h.msg_type = BT_GETCAPABILITIES_REQ; - strncpy(req->device, self->device, 18); - - io_error = gst_a2dp_sink_audioservice_send(self, &req->h); - if (io_error != G_IO_ERROR_NONE) { - GST_ERROR_OBJECT(self, "Error while asking device caps"); - } + if (self->sink == NULL) + sink = gst_element_factory_make("a2dpsendersink", "sendersink"); + else + sink = GST_ELEMENT(self->sink); - io_error = gst_a2dp_sink_audioservice_expect(self, &rsp_hdr->msg_h, - BT_GETCAPABILITIES_RSP); - if (io_error != G_IO_ERROR_NONE) { - GST_ERROR_OBJECT(self, "Error while getting device caps"); + if (sink == NULL) { + GST_ERROR_OBJECT(self, "Couldn't create a2dpsendersink"); return FALSE; } - if (rsp_hdr->posix_errno != 0) { - GST_ERROR_OBJECT(self, "BT_GETCAPABILITIES failed : %s(%d)", - strerror(rsp_hdr->posix_errno), - rsp_hdr->posix_errno); - return FALSE; + if (!gst_bin_add(GST_BIN(self), sink)) { + GST_ERROR_OBJECT(self, "failed to add a2dpsendersink " + "to the bin"); + goto cleanup_and_fail; } - memcpy(&self->data->caps, rsp, sizeof(*rsp)); - if (!gst_a2dp_sink_update_caps(self)) { - GST_WARNING_OBJECT(self, "failed to update capabilities"); - return FALSE; + if (gst_element_set_state(sink, GST_STATE_READY) == + GST_STATE_CHANGE_FAILURE) { + GST_ERROR_OBJECT(self, "a2dpsendersink failed to go to ready"); + goto remove_element_and_fail; } - return TRUE; -} - -static gboolean gst_a2dp_sink_start(GstBaseSink *basesink) -{ - GstA2dpSink *self = GST_A2DP_SINK(basesink); - gint sk; - gint err; - - GST_INFO_OBJECT(self, "start"); - - self->watch_id = 0; - - sk = bt_audio_service_open(); - if (sk <= 0) { - err = errno; - GST_ERROR_OBJECT(self, "Cannot open connection to bt " - "audio service: %s %d", strerror(err), err); - goto failed; + if (!gst_element_link(GST_ELEMENT(self->rtp), sink)) { + GST_ERROR_OBJECT(self, "couldn't link rtpsbcpay " + "to a2dpsendersink"); + goto remove_element_and_fail; } - self->server = g_io_channel_unix_new(sk); - self->watch_id = g_io_add_watch(self->server, G_IO_HUP | G_IO_ERR | - G_IO_NVAL, server_callback, self); + self->sink = GST_A2DP_SENDER_SINK(sink); + g_object_set(G_OBJECT(self->sink), "device", self->device, NULL); - self->data = g_new0(struct bluetooth_data, 1); - memset(self->data, 0, sizeof(struct bluetooth_data)); + gst_element_set_state(sink, GST_STATE_PAUSED); - self->stream = NULL; - self->stream_caps = NULL; + return TRUE; - if (!gst_a2dp_sink_get_capabilities(self)) - goto failed; +remove_element_and_fail: + gst_element_set_state (sink, GST_STATE_NULL); + gst_bin_remove(GST_BIN(self), sink); + return FALSE; - return TRUE; +cleanup_and_fail: + if (sink != NULL) + g_object_unref(G_OBJECT(sink)); -failed: - bt_audio_service_close(sk); return FALSE; } -static gboolean gst_a2dp_sink_stream_start(GstA2dpSink *self) +static gboolean gst_a2dp_sink_init_rtp_sbc_element(GstA2dpSink *self) { - gchar buf[BT_AUDIO_IPC_PACKET_SIZE]; - struct bt_streamstart_req *req = (void *) buf; - bt_audio_rsp_msg_header_t *rsp_hdr = (void *) buf; - struct bt_streamfd_ind *ind = (void*) buf; - GIOError io_error; - - GST_DEBUG_OBJECT(self, "stream start"); - - memset (req, 0, sizeof(buf)); - req->h.msg_type = BT_STREAMSTART_REQ; - - io_error = gst_a2dp_sink_audioservice_send(self, &req->h); - if (io_error != G_IO_ERROR_NONE) { - GST_ERROR_OBJECT(self, "Error ocurred while sending " - "start packet"); - return FALSE; - } - - GST_DEBUG_OBJECT(self, "stream start packet sent"); - - io_error = gst_a2dp_sink_audioservice_expect(self, &rsp_hdr->msg_h, - BT_STREAMSTART_RSP); - if (io_error != G_IO_ERROR_NONE) { - GST_ERROR_OBJECT(self, "Error while stream start confirmation"); - return FALSE; - } - - if (rsp_hdr->posix_errno != 0) { - GST_ERROR_OBJECT(self, "BT_STREAMSTART_RSP failed : %s(%d)", - strerror(rsp_hdr->posix_errno), - rsp_hdr->posix_errno); - return FALSE; - } - - GST_DEBUG_OBJECT(self, "stream started"); + GstElement *rtppay; - io_error = gst_a2dp_sink_audioservice_expect(self, &ind->h, - BT_STREAMFD_IND); - if (io_error != G_IO_ERROR_NONE) { - GST_ERROR_OBJECT(self, "Error while receiving stream fd"); + rtppay = gst_element_factory_make("rtpsbcpay", "rtp"); + if (rtppay == NULL) { + GST_ERROR_OBJECT(self, "Couldn't create rtpsbcpay"); return FALSE; } - if (!gst_a2dp_sink_conf_recv_stream_fd(self)) - return FALSE; - - return TRUE; -} - -static gboolean gst_a2dp_sink_configure(GstA2dpSink *self, GstCaps *caps) -{ - gchar buf[BT_AUDIO_IPC_PACKET_SIZE]; - struct bt_setconfiguration_req *req = (void *) buf; - bt_audio_rsp_msg_header_t *rsp_hdr = (void *) buf; - struct bt_setconfiguration_rsp *rsp = (void *) buf; - gboolean ret; - GIOError io_error; - - GST_DEBUG_OBJECT(self, "configuring device"); - - memset (req, 0, sizeof(buf)); - req->h.msg_type = BT_SETCONFIGURATION_REQ; - req->access_mode = BT_CAPABILITIES_ACCESS_MODE_WRITE; - strncpy(req->device, self->device, 18); - ret = gst_a2dp_sink_init_pkt_conf(self, caps, &req->sbc_capabilities); - if (!ret) { - GST_ERROR_OBJECT(self, "Couldn't parse caps " - "to packet configuration"); - return FALSE; + if (!gst_bin_add(GST_BIN(self), rtppay)) { + GST_ERROR_OBJECT(self, "failed to add rtp sbc pay to the bin"); + goto cleanup_and_fail; } - io_error = gst_a2dp_sink_audioservice_send(self, &req->h); - if (io_error != G_IO_ERROR_NONE) { - GST_ERROR_OBJECT(self, "Error ocurred while sending " - "configurarion packet"); - return FALSE; + if (gst_element_set_state(rtppay, GST_STATE_READY) == + GST_STATE_CHANGE_FAILURE) { + GST_ERROR_OBJECT(self, "rtpsbcpay failed to go to ready"); + goto remove_element_and_fail; } - GST_DEBUG_OBJECT(self, "configuration packet sent"); - - io_error = gst_a2dp_sink_audioservice_expect(self, &rsp_hdr->msg_h, - BT_SETCONFIGURATION_RSP); - if (io_error != G_IO_ERROR_NONE) { - GST_ERROR_OBJECT(self, "Error while receiving device confirmation"); - return FALSE; + if (!gst_element_link(self->capsfilter, rtppay)) { + GST_ERROR_OBJECT(self, "couldn't link capsfilter " + "to rtpsbcpay"); + goto remove_element_and_fail; } - if (rsp_hdr->posix_errno != 0) { - GST_ERROR_OBJECT(self, "BT_SETCONFIGURATION_RSP failed : %s(%d)", - strerror(rsp_hdr->posix_errno), - rsp_hdr->posix_errno); - return FALSE; - } + self->rtp = GST_BASE_RTP_PAYLOAD(rtppay); + g_object_set(G_OBJECT(self->rtp), "min-frames", -1, NULL); - memcpy(&self->data->cfg, rsp, sizeof(*rsp)); - GST_DEBUG_OBJECT(self, "configuration set"); + gst_element_set_state(rtppay, GST_STATE_PAUSED); return TRUE; -} -static GstFlowReturn gst_a2dp_sink_preroll(GstBaseSink *basesink, - GstBuffer *buffer) -{ - GstA2dpSink *sink = GST_A2DP_SINK(basesink); - gboolean ret; - - GST_A2DP_SINK_MUTEX_LOCK(sink); - - ret = gst_a2dp_sink_stream_start(sink); - - GST_A2DP_SINK_MUTEX_UNLOCK(sink); +remove_element_and_fail: + gst_element_set_state (rtppay, GST_STATE_NULL); + gst_bin_remove(GST_BIN(self), rtppay); + return FALSE; - if (!ret) - return GST_FLOW_ERROR; +cleanup_and_fail: + if (rtppay != NULL) + g_object_unref(G_OBJECT(rtppay)); - return GST_FLOW_OK; + return FALSE; } -static int gst_a2dp_sink_avdtp_write(GstA2dpSink *self) +static gboolean gst_a2dp_sink_set_caps(GstPad *pad, GstCaps *caps) { - gsize ret; - struct bluetooth_data *data = self->data; - struct rtp_header *header; - struct rtp_payload *payload; - GIOError err; - - header = (void *) data->buffer; - payload = (void *) (data->buffer + sizeof(*header)); - - memset(data->buffer, 0, sizeof(*header) + sizeof(*payload)); - - payload->frame_count = data->frame_count; - header->v = 2; - header->pt = 1; - header->sequence_number = htons(data->seq_num); - header->timestamp = htonl(data->nsamples); - header->ssrc = htonl(1); - - err = g_io_channel_write(self->stream, data->buffer, data->count, &ret); - if (err != G_IO_ERROR_NONE) { - GST_ERROR_OBJECT(self, "Error while sending data"); - ret = -1; - } - - /* Reset buffer of data to send */ - data->count = sizeof(struct rtp_header) + sizeof(struct rtp_payload); - data->frame_count = 0; - data->samples = 0; - data->seq_num++; - - return ret; -} + GstA2dpSink *self; + GstStructure *structure; -static GstFlowReturn gst_a2dp_sink_render(GstBaseSink *basesink, - GstBuffer *buffer) -{ - GstA2dpSink *self = GST_A2DP_SINK(basesink); - struct bluetooth_data *data = self->data; - gint encoded; - gint ret; + self = GST_A2DP_SINK(GST_PAD_PARENT(pad)); + GST_INFO_OBJECT(self, "setting caps"); - encoded = GST_BUFFER_SIZE(buffer); + structure = gst_caps_get_structure(caps, 0); - if (data->count + encoded >= data->cfg.link_mtu) { - ret = gst_a2dp_sink_avdtp_write(self); - if (ret < 0) - return GST_FLOW_ERROR; + /* first, we need to create our rtp payloader */ + if (gst_structure_has_name(structure, "audio/x-sbc")) + gst_a2dp_sink_init_rtp_sbc_element(self); + else { + GST_ERROR_OBJECT(self, "Unexpected media type"); + return FALSE; } - memcpy(data->buffer + data->count, GST_BUFFER_DATA(buffer), encoded); - data->count += encoded; - data->frame_count++; - - return GST_FLOW_OK; -} - -static GstCaps* gst_a2dp_sink_get_caps(GstBaseSink *basesink) -{ - GstA2dpSink *self = GST_A2DP_SINK(basesink); - - if (self->dev_caps) - return gst_caps_ref(self->dev_caps); - - return gst_caps_copy(gst_pad_get_pad_template_caps(GST_BASE_SINK_PAD(self))); -} - -static gboolean gst_a2dp_sink_set_caps(GstBaseSink *basesink, GstCaps *caps) -{ - GstA2dpSink *self = GST_A2DP_SINK(basesink); - gboolean ret; - - GST_A2DP_SINK_MUTEX_LOCK(self); - ret = gst_a2dp_sink_configure(self, caps); - - if (self->stream_caps) - gst_caps_unref(self->stream_caps); - self->stream_caps = gst_caps_ref(caps); - - GST_A2DP_SINK_MUTEX_UNLOCK(self); + if (!gst_a2dp_sink_init_sender_sink(self)) + return FALSE; - return ret; -} + if (!gst_a2dp_sender_sink_set_device_caps(self->sink, caps)) + return FALSE; -static gboolean gst_a2dp_sink_unlock(GstBaseSink *basesink) -{ - GstA2dpSink *self = GST_A2DP_SINK(basesink); + g_object_set(G_OBJECT(self->rtp), "mtu", + gst_a2dp_sender_sink_get_link_mtu(self->sink), NULL); - if (self->stream != NULL) - g_io_channel_flush (self->stream, NULL); + /* we forward our new segment here if we have one */ + gst_pad_send_event(GST_BASE_RTP_PAYLOAD_SINKPAD(self->rtp), + self->newseg_event); + self->newseg_event = NULL; - return TRUE; + return self->ghostpad_setcapsfunc(GST_PAD(self->ghostpad), caps); } -static void gst_a2dp_sink_class_init(GstA2dpSinkClass *klass) +/* used for catching newsegment events while we don't have a sink, for + * later forwarding it to the sink */ +static gboolean gst_a2dp_sink_handle_event(GstPad *pad, GstEvent *event) { - GObjectClass *object_class = G_OBJECT_CLASS(klass); - GstBaseSinkClass *basesink_class = GST_BASE_SINK_CLASS(klass); + GstA2dpSink *self; - parent_class = g_type_class_peek_parent(klass); + self = GST_A2DP_SINK(GST_PAD_PARENT(pad)); - object_class->finalize = GST_DEBUG_FUNCPTR(gst_a2dp_sink_finalize); - object_class->set_property = GST_DEBUG_FUNCPTR( - gst_a2dp_sink_set_property); - object_class->get_property = GST_DEBUG_FUNCPTR( - gst_a2dp_sink_get_property); - - basesink_class->start = GST_DEBUG_FUNCPTR(gst_a2dp_sink_start); - basesink_class->stop = GST_DEBUG_FUNCPTR(gst_a2dp_sink_stop); - basesink_class->render = GST_DEBUG_FUNCPTR(gst_a2dp_sink_render); - basesink_class->preroll = GST_DEBUG_FUNCPTR(gst_a2dp_sink_preroll); - basesink_class->set_caps = GST_DEBUG_FUNCPTR(gst_a2dp_sink_set_caps); - basesink_class->get_caps = GST_DEBUG_FUNCPTR(gst_a2dp_sink_get_caps); - basesink_class->unlock = GST_DEBUG_FUNCPTR(gst_a2dp_sink_unlock); - - g_object_class_install_property(object_class, PROP_DEVICE, - g_param_spec_string("device", "Device", - "Bluetooth remote device address", - NULL, G_PARAM_READWRITE)); + if (GST_EVENT_TYPE(event) == GST_EVENT_NEWSEGMENT && + gst_element_get_parent(GST_ELEMENT(self->sink)) != + GST_OBJECT_CAST(self)) { + if (self->newseg_event != NULL) + gst_event_unref(self->newseg_event); + self->newseg_event = gst_event_ref(event); + } - GST_DEBUG_CATEGORY_INIT(a2dp_sink_debug, "a2dpsink", 0, - "A2DP sink element"); + return self->ghostpad_eventfunc(GST_PAD(self->ghostpad), event); } -static void gst_a2dp_sink_init(GstA2dpSink *self, GstA2dpSinkClass *klass) +static gboolean gst_a2dp_sink_init_caps_filter(GstA2dpSink *self) { - self->device = NULL; - self->data = NULL; - - self->stream = NULL; + GstElement *element; - self->dev_caps = NULL; + element = gst_element_factory_make("capsfilter", "filter"); + if (element == NULL) + goto failed; - self->sink_lock = g_mutex_new(); -} + if (!gst_bin_add(GST_BIN(self), element)) + goto failed; -static GIOError gst_a2dp_sink_audioservice_send(GstA2dpSink *self, - const bt_audio_msg_header_t *msg) -{ - gint err; - GIOError error; - gsize written; - - GST_DEBUG_OBJECT(self, "sending %s", bt_audio_strmsg(msg->msg_type)); - - error = g_io_channel_write(self->server, (const gchar*) msg, - BT_AUDIO_IPC_PACKET_SIZE, &written); - if (error != G_IO_ERROR_NONE) { - err = errno; - GST_ERROR_OBJECT(self, "Error sending data to audio service:" - " %s(%d)", strerror(err), err); - } + self->capsfilter = element; + return TRUE; - return error; +failed: + GST_ERROR_OBJECT(self, "Failed to initialize caps filter"); + return FALSE; } -static GIOError gst_a2dp_sink_audioservice_recv(GstA2dpSink *self, - bt_audio_msg_header_t *inmsg) +static void gst_a2dp_sink_init(GstA2dpSink *self, + GstA2dpSinkClass *klass) { - GIOError status; - gsize bytes_read; - const char *type; - - status = g_io_channel_read(self->server, (gchar*) inmsg, - BT_AUDIO_IPC_PACKET_SIZE, &bytes_read); - if (status != G_IO_ERROR_NONE) { - GST_ERROR_OBJECT(self, "Error receiving data from service"); - return status; - } - - type = bt_audio_strmsg(inmsg->msg_type); - if (!type) { - GST_ERROR_OBJECT(self, "Bogus message type %d " - "received from audio service", - inmsg->msg_type); - return G_IO_ERROR_INVAL; - } - - GST_DEBUG_OBJECT(self, "Received %s", type); + GstPad *capsfilter_pad; - return status; + self->sink = NULL; + self->rtp = NULL; + self->device = NULL; + self->capsfilter = NULL; + self->newseg_event = NULL; + + /* we initialize our capsfilter */ + gst_a2dp_sink_init_caps_filter(self); + g_object_set(self->capsfilter, "caps", + gst_static_pad_template_get_caps(&gst_a2dp_sink_factory), + NULL); + + /* we search for the capsfilter sinkpad */ + capsfilter_pad = gst_element_get_static_pad(self->capsfilter, "sink"); + + /* now we add a ghostpad */ + self->ghostpad = GST_GHOST_PAD(gst_ghost_pad_new("sink", + capsfilter_pad)); + g_object_unref(capsfilter_pad); + + /* the getcaps of our ghostpad must reflect the device caps */ + gst_pad_set_getcaps_function(GST_PAD(self->ghostpad), + gst_a2dp_sink_get_caps); + self->ghostpad_setcapsfunc = GST_PAD_SETCAPSFUNC(self->ghostpad); + gst_pad_set_setcaps_function(GST_PAD(self->ghostpad), + GST_DEBUG_FUNCPTR(gst_a2dp_sink_set_caps)); + + /* we need to handle events on our own and we also need the eventfunc + * of the ghostpad for forwarding calls */ + self->ghostpad_eventfunc = GST_PAD_EVENTFUNC(GST_PAD(self->ghostpad)); + gst_pad_set_event_function(GST_PAD(self->ghostpad), + gst_a2dp_sink_handle_event); + + if (!gst_element_add_pad(GST_ELEMENT(self), GST_PAD(self->ghostpad))) + GST_ERROR_OBJECT(self, "failed to add ghostpad"); + + self->sink = GST_A2DP_SENDER_SINK(gst_element_factory_make( + "a2dpsendersink", "sendersink")); + if (self->sink == NULL) + GST_WARNING_OBJECT(self, "failed to create a2dpsendersink"); } -static GIOError gst_a2dp_sink_audioservice_expect(GstA2dpSink *self, - bt_audio_msg_header_t *outmsg, - int expected_type) +gboolean gst_a2dp_sink_plugin_init (GstPlugin * plugin) { - GIOError status; - - status = gst_a2dp_sink_audioservice_recv(self, outmsg); - if (status != G_IO_ERROR_NONE) - return status; - - if (outmsg->msg_type != expected_type) { - GST_ERROR_OBJECT(self, "Bogus message %s " - "received while %s was expected", - bt_audio_strmsg(outmsg->msg_type), - bt_audio_strmsg(expected_type)); - return G_IO_ERROR_INVAL; - } - - return status; + return gst_element_register (plugin, "a2dpsink", + GST_RANK_PRIMARY, GST_TYPE_A2DP_SINK); } |