| /* |
| * |
| * BlueZ - Bluetooth protocol stack for Linux |
| * |
| * Copyright (C) 2012 Collabora Ltd. |
| * |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <unistd.h> |
| #include <stdint.h> |
| #include <string.h> |
| #include <poll.h> |
| |
| #include <gst/rtp/gstrtppayloads.h> |
| #include "gstavdtpsrc.h" |
| |
| GST_DEBUG_CATEGORY_STATIC (avdtpsrc_debug); |
| #define GST_CAT_DEFAULT (avdtpsrc_debug) |
| |
| enum |
| { |
| PROP_0, |
| PROP_TRANSPORT, |
| }; |
| |
| #define parent_class gst_avdtp_src_parent_class |
| G_DEFINE_TYPE (GstAvdtpSrc, gst_avdtp_src, GST_TYPE_BASE_SRC); |
| |
| static GstStaticPadTemplate gst_avdtp_src_template = |
| GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS, |
| GST_STATIC_CAPS ("application/x-rtp, " |
| "media = (string) \"audio\"," |
| "payload = (int) " |
| GST_RTP_PAYLOAD_DYNAMIC_STRING ", " |
| "clock-rate = (int) { 16000, 32000, " |
| "44100, 48000 }, " "encoding-name = (string) \"SBC\"; " |
| "application/x-rtp, " |
| "media = (string) \"audio\"," |
| "payload = (int) " |
| GST_RTP_PAYLOAD_DYNAMIC_STRING ", " |
| "clock-rate = (int) { 8000, 11025, 12000, 16000, " |
| "22050, 2400, 32000, 44100, 48000, 64000, 88200, 96000 }, " |
| "encoding-name = (string) \"MP4A-LATM\"; ")); |
| |
| static void gst_avdtp_src_finalize (GObject * object); |
| static void gst_avdtp_src_get_property (GObject * object, guint prop_id, |
| GValue * value, GParamSpec * pspec); |
| static void gst_avdtp_src_set_property (GObject * object, guint prop_id, |
| const GValue * value, GParamSpec * pspec); |
| |
| static GstCaps *gst_avdtp_src_getcaps (GstBaseSrc * bsrc, GstCaps * filter); |
| static gboolean gst_avdtp_src_query (GstPad * pad, GstObject * parent, |
| GstQuery * query); |
| static gboolean gst_avdtp_src_start (GstBaseSrc * bsrc); |
| static gboolean gst_avdtp_src_stop (GstBaseSrc * bsrc); |
| static GstFlowReturn gst_avdtp_src_create (GstBaseSrc * bsrc, guint64 offset, |
| guint length, GstBuffer ** outbuf); |
| static gboolean gst_avdtp_src_unlock (GstBaseSrc * bsrc); |
| static gboolean gst_avdtp_src_unlock_stop (GstBaseSrc * bsrc); |
| |
| static void |
| gst_avdtp_src_class_init (GstAvdtpSrcClass * klass) |
| { |
| GObjectClass *gobject_class = G_OBJECT_CLASS (klass); |
| GstElementClass *element_class = GST_ELEMENT_CLASS (klass); |
| GstBaseSrcClass *basesrc_class = GST_BASE_SRC_CLASS (klass); |
| |
| parent_class = g_type_class_peek_parent (klass); |
| |
| gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_avdtp_src_finalize); |
| gobject_class->set_property = GST_DEBUG_FUNCPTR (gst_avdtp_src_set_property); |
| gobject_class->get_property = GST_DEBUG_FUNCPTR (gst_avdtp_src_get_property); |
| |
| basesrc_class->start = GST_DEBUG_FUNCPTR (gst_avdtp_src_start); |
| basesrc_class->stop = GST_DEBUG_FUNCPTR (gst_avdtp_src_stop); |
| basesrc_class->create = GST_DEBUG_FUNCPTR (gst_avdtp_src_create); |
| basesrc_class->unlock = GST_DEBUG_FUNCPTR (gst_avdtp_src_unlock); |
| basesrc_class->unlock_stop = GST_DEBUG_FUNCPTR (gst_avdtp_src_unlock_stop); |
| basesrc_class->get_caps = GST_DEBUG_FUNCPTR (gst_avdtp_src_getcaps); |
| |
| g_object_class_install_property (gobject_class, PROP_TRANSPORT, |
| g_param_spec_string ("transport", |
| "Transport", "Use configured transport", NULL, G_PARAM_READWRITE)); |
| |
| gst_element_class_set_static_metadata (element_class, |
| "Bluetooth AVDTP Source", |
| "Source/Audio/Network/RTP", |
| "Receives audio from an A2DP device", |
| "Arun Raghavan <arun.raghavan@collabora.co.uk>"); |
| |
| GST_DEBUG_CATEGORY_INIT (avdtpsrc_debug, "avdtpsrc", 0, |
| "Bluetooth AVDTP Source"); |
| |
| gst_element_class_add_static_pad_template (element_class, |
| &gst_avdtp_src_template); |
| } |
| |
| static void |
| gst_avdtp_src_init (GstAvdtpSrc * avdtpsrc) |
| { |
| avdtpsrc->poll = gst_poll_new (TRUE); |
| |
| avdtpsrc->duration = GST_CLOCK_TIME_NONE; |
| |
| gst_base_src_set_format (GST_BASE_SRC (avdtpsrc), GST_FORMAT_TIME); |
| gst_base_src_set_live (GST_BASE_SRC (avdtpsrc), TRUE); |
| gst_base_src_set_do_timestamp (GST_BASE_SRC (avdtpsrc), TRUE); |
| |
| gst_pad_set_query_function (GST_BASE_SRC_PAD (avdtpsrc), |
| GST_DEBUG_FUNCPTR (gst_avdtp_src_query)); |
| } |
| |
| static void |
| gst_avdtp_src_finalize (GObject * object) |
| { |
| GstAvdtpSrc *avdtpsrc = GST_AVDTP_SRC (object); |
| |
| gst_poll_free (avdtpsrc->poll); |
| |
| gst_avdtp_connection_reset (&avdtpsrc->conn); |
| |
| G_OBJECT_CLASS (parent_class)->finalize (object); |
| } |
| |
| static void |
| gst_avdtp_src_get_property (GObject * object, guint prop_id, |
| GValue * value, GParamSpec * pspec) |
| { |
| GstAvdtpSrc *avdtpsrc = GST_AVDTP_SRC (object); |
| |
| switch (prop_id) { |
| case PROP_TRANSPORT: |
| g_value_set_string (value, avdtpsrc->conn.transport); |
| break; |
| |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| static void |
| gst_avdtp_src_set_property (GObject * object, guint prop_id, |
| const GValue * value, GParamSpec * pspec) |
| { |
| GstAvdtpSrc *avdtpsrc = GST_AVDTP_SRC (object); |
| |
| switch (prop_id) { |
| case PROP_TRANSPORT: |
| gst_avdtp_connection_set_transport (&avdtpsrc->conn, |
| g_value_get_string (value)); |
| break; |
| |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| static gboolean |
| gst_avdtp_src_query (GstPad * pad, GstObject * parent, GstQuery * query) |
| { |
| GstAvdtpSrc *avdtpsrc = GST_AVDTP_SRC (gst_pad_get_parent_element (pad)); |
| gboolean ret = FALSE; |
| |
| switch (GST_QUERY_TYPE (query)) { |
| case GST_QUERY_DURATION:{ |
| GstFormat format; |
| |
| if (avdtpsrc->duration != GST_CLOCK_TIME_NONE) { |
| gst_query_parse_duration (query, &format, NULL); |
| |
| if (format == GST_FORMAT_TIME) { |
| gst_query_set_duration (query, format, (gint64) avdtpsrc->duration); |
| ret = TRUE; |
| } |
| } |
| |
| break; |
| } |
| |
| default: |
| ret = gst_pad_query_default (pad, parent, query); |
| } |
| |
| return ret; |
| } |
| |
| static GstCaps * |
| gst_avdtp_src_getcaps (GstBaseSrc * bsrc, GstCaps * filter) |
| { |
| GstAvdtpSrc *avdtpsrc = GST_AVDTP_SRC (bsrc); |
| GstCaps *caps = NULL, *ret = NULL; |
| |
| if (avdtpsrc->dev_caps) { |
| const GValue *value; |
| const char *format; |
| int rate; |
| GstStructure *structure = gst_caps_get_structure (avdtpsrc->dev_caps, 0); |
| |
| format = gst_structure_get_name (structure); |
| |
| if (g_str_equal (format, "audio/x-sbc")) { |
| /* FIXME: we can return a fixed payload type once we |
| * are in PLAYING */ |
| caps = gst_caps_new_simple ("application/x-rtp", |
| "media", G_TYPE_STRING, "audio", |
| "payload", GST_TYPE_INT_RANGE, 96, 127, |
| "encoding-name", G_TYPE_STRING, "SBC", NULL); |
| } else if (g_str_equal (format, "audio/mpeg")) { |
| caps = gst_caps_new_simple ("application/x-rtp", |
| "media", G_TYPE_STRING, "audio", |
| "payload", GST_TYPE_INT_RANGE, 96, 127, |
| "encoding-name", G_TYPE_STRING, "MP4A-LATM", NULL); |
| |
| value = gst_structure_get_value (structure, "mpegversion"); |
| if (!value || !G_VALUE_HOLDS_INT (value)) { |
| GST_ERROR_OBJECT (avdtpsrc, "Failed to get mpegversion"); |
| return NULL; |
| } |
| gst_caps_set_simple (caps, "mpegversion", G_TYPE_INT, |
| g_value_get_int (value), NULL); |
| |
| value = gst_structure_get_value (structure, "channels"); |
| if (!value || !G_VALUE_HOLDS_INT (value)) { |
| GST_ERROR_OBJECT (avdtpsrc, "Failed to get channels"); |
| return NULL; |
| } |
| gst_caps_set_simple (caps, "channels", G_TYPE_INT, |
| g_value_get_int (value), NULL); |
| |
| value = gst_structure_get_value (structure, "base-profile"); |
| if (!value || !G_VALUE_HOLDS_STRING (value)) { |
| GST_ERROR_OBJECT (avdtpsrc, "Failed to get base-profile"); |
| return NULL; |
| } |
| gst_caps_set_simple (caps, "base-profile", G_TYPE_STRING, |
| g_value_get_string (value), NULL); |
| |
| } else { |
| GST_ERROR_OBJECT (avdtpsrc, |
| "Only SBC and MPEG-2/4 are supported at the moment"); |
| } |
| |
| value = gst_structure_get_value (structure, "rate"); |
| if (!value || !G_VALUE_HOLDS_INT (value)) { |
| GST_ERROR_OBJECT (avdtpsrc, "Failed to get sample rate"); |
| return NULL; |
| } |
| rate = g_value_get_int (value); |
| |
| gst_caps_set_simple (caps, "clock-rate", G_TYPE_INT, rate, NULL); |
| |
| if (filter) { |
| ret = gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST); |
| gst_caps_unref (caps); |
| } else |
| ret = caps; |
| } else { |
| GST_DEBUG_OBJECT (avdtpsrc, "device not open, using template caps"); |
| ret = GST_BASE_SRC_CLASS (parent_class)->get_caps (bsrc, filter); |
| } |
| |
| return ret; |
| } |
| |
| static void |
| avrcp_metadata_cb (GstAvrcpConnection * avrcp, GstTagList * taglist, |
| gpointer user_data) |
| { |
| GstAvdtpSrc *src = GST_AVDTP_SRC (user_data); |
| guint64 duration; |
| |
| if (gst_tag_list_get_uint64 (taglist, GST_TAG_DURATION, &duration)) { |
| src->duration = duration; |
| gst_element_post_message (GST_ELEMENT (src), |
| gst_message_new_duration_changed (GST_OBJECT (src))); |
| } |
| |
| gst_pad_push_event (GST_BASE_SRC_PAD (src), |
| gst_event_new_tag (gst_tag_list_copy (taglist))); |
| gst_element_post_message (GST_ELEMENT (src), |
| gst_message_new_tag (GST_OBJECT (src), taglist)); |
| } |
| |
| static void |
| gst_avdtp_src_start_avrcp (GstAvdtpSrc * src) |
| { |
| gchar *path, **strv; |
| int i; |
| |
| /* Strip out the /fdX in /org/bluez/dev_.../fdX */ |
| strv = g_strsplit (src->conn.transport, "/", -1); |
| |
| for (i = 0; strv[i]; i++); |
| g_return_if_fail (i > 0); |
| |
| g_free (strv[i - 1]); |
| strv[i - 1] = NULL; |
| |
| path = g_strjoinv ("/", strv); |
| g_strfreev (strv); |
| |
| src->avrcp = gst_avrcp_connection_new (path, avrcp_metadata_cb, src, NULL); |
| |
| g_free (path); |
| } |
| |
| static void |
| gst_avdtp_src_stop_avrcp (GstAvdtpSrc * src) |
| { |
| gst_avrcp_connection_free (src->avrcp); |
| } |
| |
| static gboolean |
| gst_avdtp_src_start (GstBaseSrc * bsrc) |
| { |
| GstAvdtpSrc *avdtpsrc = GST_AVDTP_SRC (bsrc); |
| |
| /* None of this can go into prepare() since we need to set up the |
| * connection to figure out what format the device is going to send us. |
| */ |
| |
| if (!gst_avdtp_connection_acquire (&avdtpsrc->conn, FALSE)) { |
| GST_ERROR_OBJECT (avdtpsrc, "Failed to acquire connection"); |
| return FALSE; |
| } |
| |
| if (!gst_avdtp_connection_get_properties (&avdtpsrc->conn)) { |
| GST_ERROR_OBJECT (avdtpsrc, "Failed to get transport properties"); |
| goto fail; |
| } |
| |
| if (!gst_avdtp_connection_conf_recv_stream_fd (&avdtpsrc->conn)) { |
| GST_ERROR_OBJECT (avdtpsrc, "Failed to configure stream fd"); |
| goto fail; |
| } |
| |
| GST_DEBUG_OBJECT (avdtpsrc, "Setting block size to link MTU (%d)", |
| avdtpsrc->conn.data.link_mtu); |
| gst_base_src_set_blocksize (GST_BASE_SRC (avdtpsrc), |
| avdtpsrc->conn.data.link_mtu); |
| |
| avdtpsrc->dev_caps = gst_avdtp_connection_get_caps (&avdtpsrc->conn); |
| if (!avdtpsrc->dev_caps) { |
| GST_ERROR_OBJECT (avdtpsrc, "Failed to get device caps"); |
| goto fail; |
| } |
| |
| gst_poll_fd_init (&avdtpsrc->pfd); |
| avdtpsrc->pfd.fd = g_io_channel_unix_get_fd (avdtpsrc->conn.stream); |
| |
| gst_poll_add_fd (avdtpsrc->poll, &avdtpsrc->pfd); |
| gst_poll_fd_ctl_read (avdtpsrc->poll, &avdtpsrc->pfd, TRUE); |
| gst_poll_set_flushing (avdtpsrc->poll, FALSE); |
| |
| g_atomic_int_set (&avdtpsrc->unlocked, FALSE); |
| |
| gst_avdtp_src_start_avrcp (avdtpsrc); |
| |
| return TRUE; |
| |
| fail: |
| gst_avdtp_connection_release (&avdtpsrc->conn); |
| return FALSE; |
| } |
| |
| static gboolean |
| gst_avdtp_src_stop (GstBaseSrc * bsrc) |
| { |
| GstAvdtpSrc *avdtpsrc = GST_AVDTP_SRC (bsrc); |
| |
| gst_poll_remove_fd (avdtpsrc->poll, &avdtpsrc->pfd); |
| gst_poll_set_flushing (avdtpsrc->poll, TRUE); |
| |
| gst_avdtp_src_stop_avrcp (avdtpsrc); |
| gst_avdtp_connection_release (&avdtpsrc->conn); |
| |
| if (avdtpsrc->dev_caps) { |
| gst_caps_unref (avdtpsrc->dev_caps); |
| avdtpsrc->dev_caps = NULL; |
| } |
| |
| return TRUE; |
| } |
| |
| static GstFlowReturn |
| gst_avdtp_src_create (GstBaseSrc * bsrc, guint64 offset, guint length, |
| GstBuffer ** outbuf) |
| { |
| GstAvdtpSrc *avdtpsrc = GST_AVDTP_SRC (bsrc); |
| GstBuffer *buf = NULL; |
| GstMapInfo info; |
| int ret; |
| |
| if (g_atomic_int_get (&avdtpsrc->unlocked)) |
| return GST_FLOW_FLUSHING; |
| |
| /* We don't operate in GST_FORMAT_BYTES, so offset is ignored */ |
| |
| while ((ret = gst_poll_wait (avdtpsrc->poll, GST_CLOCK_TIME_NONE))) { |
| if (g_atomic_int_get (&avdtpsrc->unlocked)) |
| /* We're unlocked, time to gtfo */ |
| return GST_FLOW_FLUSHING; |
| |
| if (ret < 0) |
| /* Something went wrong */ |
| goto read_error; |
| |
| if (ret > 0) |
| /* Got some data */ |
| break; |
| } |
| |
| ret = GST_BASE_SRC_CLASS (parent_class)->alloc (bsrc, offset, length, outbuf); |
| if (G_UNLIKELY (ret != GST_FLOW_OK)) |
| goto alloc_failed; |
| |
| buf = *outbuf; |
| |
| gst_buffer_map (buf, &info, GST_MAP_WRITE); |
| |
| ret = read (avdtpsrc->pfd.fd, info.data, length); |
| |
| if (ret < 0) |
| goto read_error; |
| else if (ret == 0) { |
| GST_INFO_OBJECT (avdtpsrc, "Got EOF on the transport fd"); |
| goto eof; |
| } |
| |
| if (ret < length) |
| gst_buffer_set_size (buf, ret); |
| |
| GST_LOG_OBJECT (avdtpsrc, "Read %d bytes", ret); |
| |
| gst_buffer_unmap (buf, &info); |
| *outbuf = buf; |
| |
| return GST_FLOW_OK; |
| |
| alloc_failed: |
| { |
| GST_DEBUG_OBJECT (bsrc, "alloc failed: %s", gst_flow_get_name (ret)); |
| return ret; |
| } |
| |
| read_error: |
| GST_ERROR_OBJECT (avdtpsrc, "Error while reading audio data: %s", |
| strerror (errno)); |
| gst_buffer_unref (buf); |
| return GST_FLOW_ERROR; |
| |
| eof: |
| gst_buffer_unref (buf); |
| return GST_FLOW_EOS; |
| } |
| |
| static gboolean |
| gst_avdtp_src_unlock (GstBaseSrc * bsrc) |
| { |
| GstAvdtpSrc *avdtpsrc = GST_AVDTP_SRC (bsrc); |
| |
| g_atomic_int_set (&avdtpsrc->unlocked, TRUE); |
| |
| gst_poll_set_flushing (avdtpsrc->poll, TRUE); |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| gst_avdtp_src_unlock_stop (GstBaseSrc * bsrc) |
| { |
| GstAvdtpSrc *avdtpsrc = GST_AVDTP_SRC (bsrc); |
| |
| g_atomic_int_set (&avdtpsrc->unlocked, FALSE); |
| |
| gst_poll_set_flushing (avdtpsrc->poll, FALSE); |
| |
| /* Flush out any stale data that might be buffered */ |
| gst_avdtp_connection_conf_recv_stream_fd (&avdtpsrc->conn); |
| |
| return TRUE; |
| } |
| |
| gboolean |
| gst_avdtp_src_plugin_init (GstPlugin * plugin) |
| { |
| return gst_element_register (plugin, "avdtpsrc", GST_RANK_NONE, |
| GST_TYPE_AVDTP_SRC); |
| } |