diff --git a/sys/bluez/Makefile.am b/sys/bluez/Makefile.am index c6b13dbb4a..7823d29653 100644 --- a/sys/bluez/Makefile.am +++ b/sys/bluez/Makefile.am @@ -5,7 +5,8 @@ libgstbluez_la_SOURCES = \ gsta2dpsink.c \ gstavdtpsink.c \ gstavdtpsrc.c \ - gstavdtputil.c + gstavdtputil.c \ + gstavrcputil.c nodist_libgstbluez_la_SOURCES = \ $(BUILT_SOURCES) diff --git a/sys/bluez/gstavdtpsrc.c b/sys/bluez/gstavdtpsrc.c index 00c807a635..1c68e45365 100644 --- a/sys/bluez/gstavdtpsrc.c +++ b/sys/bluez/gstavdtpsrc.c @@ -68,6 +68,8 @@ 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, @@ -117,9 +119,14 @@ 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 @@ -169,6 +176,35 @@ gst_avdtp_src_set_property (GObject * object, guint prop_id, } } +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) { @@ -247,6 +283,54 @@ gst_avdtp_src_getcaps (GstBaseSrc * bsrc, GstCaps * 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) { @@ -291,6 +375,8 @@ gst_avdtp_src_start (GstBaseSrc * bsrc) g_atomic_int_set (&avdtpsrc->unlocked, FALSE); + gst_avdtp_src_start_avrcp (avdtpsrc); + return TRUE; fail: @@ -306,6 +392,7 @@ gst_avdtp_src_stop (GstBaseSrc * 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) { diff --git a/sys/bluez/gstavdtpsrc.h b/sys/bluez/gstavdtpsrc.h index 67027fa481..334ff151a6 100644 --- a/sys/bluez/gstavdtpsrc.h +++ b/sys/bluez/gstavdtpsrc.h @@ -27,6 +27,7 @@ #include #include #include "gstavdtputil.h" +#include "gstavrcputil.h" G_BEGIN_DECLS #define GST_TYPE_AVDTP_SRC \ @@ -54,9 +55,13 @@ struct _GstAvdtpSrc GstAvdtpConnection conn; GstCaps *dev_caps; + GstAvrcpConnection *avrcp; + GstPoll *poll; GstPollFD pfd; volatile gint unlocked; + + GstClockTime duration; }; GType gst_avdtp_src_get_type (void); diff --git a/sys/bluez/gstavrcputil.c b/sys/bluez/gstavrcputil.c new file mode 100644 index 0000000000..efce32dd42 --- /dev/null +++ b/sys/bluez/gstavrcputil.c @@ -0,0 +1,333 @@ +/* + * Copyright (C) 2015 Arun Raghavan + * + * + * 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 +#endif + +#include "gstavrcputil.h" +#include "bluez.h" + +#include + +#define BLUEZ_NAME "org.bluez" +#define BLUEZ_PATH "/" +#define BLUEZ_MEDIA_PLAYER_IFACE BLUEZ_NAME ".MediaPlayer1" + +struct _GstAvrcpConnection +{ + GMainContext *context; + GMainLoop *mainloop; + GThread *thread; + + gchar *dev_path; + GDBusObjectManager *manager; + BluezMediaPlayer1 *player; + + GstAvrcpMetadataCb cb; + gpointer user_data; + GDestroyNotify user_data_free_cb; +}; + +static const char * +tag_from_property (const char *name) +{ + if (g_str_equal (name, "Title")) + return GST_TAG_TITLE; + else if (g_str_equal (name, "Artist")) + return GST_TAG_ARTIST; + else if (g_str_equal (name, "Album")) + return GST_TAG_ALBUM; + else if (g_str_equal (name, "Genre")) + return GST_TAG_GENRE; + else if (g_str_equal (name, "NumberOfTracks")) + return GST_TAG_TRACK_COUNT; + else if (g_str_equal (name, "TrackNumber")) + return GST_TAG_TRACK_NUMBER; + else if (g_str_equal (name, "Duration")) + return GST_TAG_DURATION; + else + return NULL; +} + +static GstTagList * +tag_list_from_variant (GVariant * properties, gboolean track) +{ + const gchar *name, *s; + GVariant *value; + GVariantIter *iter; + GstTagList *taglist = NULL; + + iter = g_variant_iter_new (properties); + + if (track) + taglist = gst_tag_list_new_empty (); + + /* The properties are in two levels -- at the top level we have the position + * and the 'track'. The 'track' is another level of {sv} so we recurse one + * level to pick up the actual track data. We get the taglist from the + * recursive call, and ignore the position for now. */ + + while (g_variant_iter_next (iter, "{&sv}", &name, &value)) { + if (!track && g_str_equal (name, "Track")) { + /* Top level property */ + taglist = tag_list_from_variant (value, TRUE); + + } else if (track) { + /* If we get here, we are in the recursive call and we're dealing with + * properties under "Track" */ + GType type; + const gchar *tag; + guint i; + guint64 i64; + + tag = tag_from_property (name); + if (!tag) + goto next; + + type = gst_tag_get_type (tag); + + switch (type) { + case G_TYPE_STRING: + s = g_variant_get_string (value, NULL); + if (s && s[0] != '\0') + gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag, s, NULL); + break; + + case G_TYPE_UINT: + i = g_variant_get_uint32 (value); + if (i > 0) + gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag, i, NULL); + break; + + case G_TYPE_UINT64: + /* If we're here, the tag is 'duration' */ + i64 = g_variant_get_uint32 (value); + if (i64 > 0 && i64 != (guint32) (-1)) { + gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag, + i64 * GST_MSECOND, NULL); + } + break; + + default: + GST_WARNING ("Unknown property: %s", name); + break; + } + } + + next: + g_variant_unref (value); + } + + g_variant_iter_free (iter); + + if (taglist && gst_tag_list_is_empty (taglist)) { + gst_tag_list_unref (taglist); + taglist = NULL; + } + + return taglist; +} + +static void +player_property_changed_cb (GDBusProxy * proxy, GVariant * properties, + GStrv invalid, gpointer user_data) +{ + GstAvrcpConnection *avrcp = (GstAvrcpConnection *) user_data; + GstTagList *taglist; + + taglist = tag_list_from_variant (properties, FALSE); + + if (taglist) + avrcp->cb (avrcp, taglist, avrcp->user_data); +} + +static GstTagList * +player_get_taglist (BluezMediaPlayer1 * player) +{ + GstTagList *taglist = NULL; + GVariant *track; + + track = bluez_media_player1_get_track (player); + if (track) + taglist = tag_list_from_variant (track, TRUE); + + return taglist; +} + +static void +gst_avrcp_connection_set_player (GstAvrcpConnection * avrcp, + BluezMediaPlayer1 * player) +{ + GstTagList *taglist; + + if (avrcp->player) + g_object_unref (avrcp->player); + + if (!player) { + avrcp->player = NULL; + return; + } + + avrcp->player = g_object_ref (player); + + g_signal_connect (player, "g-properties-changed", + G_CALLBACK (player_property_changed_cb), avrcp); + + taglist = player_get_taglist (avrcp->player); + + if (taglist) + avrcp->cb (avrcp, taglist, avrcp->user_data); +} + +static BluezMediaPlayer1 * +media_player_from_dbus_object (GDBusObject * object) +{ + return (BluezMediaPlayer1 *) g_dbus_object_get_interface (object, + BLUEZ_MEDIA_PLAYER_IFACE); +} + +static GType +manager_proxy_type_func (GDBusObjectManagerClient * manager, + const gchar * object_path, const gchar * interface_name, gpointer user_data) +{ + if (!interface_name) + return G_TYPE_DBUS_OBJECT_PROXY; + + if (g_str_equal (interface_name, BLUEZ_MEDIA_PLAYER_IFACE)) + return BLUEZ_TYPE_MEDIA_PLAYER1_PROXY; + + return G_TYPE_DBUS_PROXY; +} + +static void +manager_object_added_cb (GDBusObjectManager * manager, + GDBusObject * object, gpointer user_data) +{ + GstAvrcpConnection *avrcp = (GstAvrcpConnection *) user_data; + BluezMediaPlayer1 *player; + + if (!(player = media_player_from_dbus_object (object))) + return; + + gst_avrcp_connection_set_player (avrcp, player); +} + +static void +manager_object_removed_cb (GDBusObjectManager * manager, + GDBusObject * object, gpointer user_data) +{ + GstAvrcpConnection *avrcp = (GstAvrcpConnection *) user_data; + BluezMediaPlayer1 *player; + + if (!(player = media_player_from_dbus_object (object))) + return; + + if (player == avrcp->player) + gst_avrcp_connection_set_player (avrcp, NULL); +} + +static void +manager_ready_cb (GObject * object, GAsyncResult * res, gpointer user_data) +{ + GstAvrcpConnection *avrcp = (GstAvrcpConnection *) user_data; + GList *objects, *i; + GError *err = NULL; + + avrcp->manager = g_dbus_object_manager_client_new_for_bus_finish (res, &err); + if (!avrcp->manager) { + GST_WARNING ("Could not create ObjectManager proxy: %s", err->message); + g_error_free (err); + return; + } + + g_signal_connect (avrcp->manager, "object-added", + G_CALLBACK (manager_object_added_cb), avrcp); + g_signal_connect (avrcp->manager, "object-removed", + G_CALLBACK (manager_object_removed_cb), avrcp); + + objects = g_dbus_object_manager_get_objects (avrcp->manager); + + for (i = objects; i; i = i->next) { + BluezMediaPlayer1 *player = + media_player_from_dbus_object (G_DBUS_OBJECT (i->data)); + + if (player && g_str_equal (avrcp->dev_path, + bluez_media_player1_get_device (player))) { + gst_avrcp_connection_set_player (avrcp, player); + break; + } + } + + g_list_free_full (objects, g_object_unref); +} + +GstAvrcpConnection * +gst_avrcp_connection_new (const gchar * dev_path, GstAvrcpMetadataCb cb, + gpointer user_data, GDestroyNotify user_data_free_cb) +{ + GstAvrcpConnection *avrcp; + + avrcp = g_new0 (GstAvrcpConnection, 1); + + avrcp->cb = cb; + avrcp->user_data = user_data; + avrcp->user_data_free_cb = user_data_free_cb; + + avrcp->context = g_main_context_new (); + avrcp->mainloop = g_main_loop_new (avrcp->context, FALSE); + + avrcp->dev_path = g_strdup (dev_path); + + g_main_context_push_thread_default (avrcp->context); + + g_dbus_object_manager_client_new_for_bus (G_BUS_TYPE_SYSTEM, + G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE, BLUEZ_NAME, BLUEZ_PATH, + manager_proxy_type_func, NULL, NULL, NULL, manager_ready_cb, avrcp); + + g_main_context_pop_thread_default (avrcp->context); + + avrcp->thread = g_thread_new ("gstavrcp", (GThreadFunc) g_main_loop_run, + avrcp->mainloop); + + return avrcp; +} + +void +gst_avrcp_connection_free (GstAvrcpConnection * avrcp) +{ + g_main_loop_quit (avrcp->mainloop); + g_main_loop_unref (avrcp->mainloop); + + g_main_context_unref (avrcp->context); + + g_thread_join (avrcp->thread); + + if (avrcp->player) + g_object_unref (avrcp->player); + + if (avrcp->manager) + g_object_unref (avrcp->manager); + + if (avrcp->user_data_free_cb) + avrcp->user_data_free_cb (avrcp->user_data); + + g_free (avrcp->dev_path); + g_free (avrcp); +} diff --git a/sys/bluez/gstavrcputil.h b/sys/bluez/gstavrcputil.h new file mode 100644 index 0000000000..b59aa3caf2 --- /dev/null +++ b/sys/bluez/gstavrcputil.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2015 Arun Raghavan + * + * + * 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 + */ + +#ifndef __GST_AVRCP_UTIL_H +#define __GST_AVRCP_UTIL_H + +#include + +typedef struct _GstAvrcpConnection GstAvrcpConnection; + +typedef void (*GstAvrcpMetadataCb) (GstAvrcpConnection *, GstTagList *, + gpointer); + +GstAvrcpConnection * +gst_avrcp_connection_new (const gchar * dev_path, GstAvrcpMetadataCb cb, + gpointer user_data, GDestroyNotify user_data_free_cb); + +void gst_avrcp_connection_free (GstAvrcpConnection * avrcp); + +#endif /* __GST_AVRCP_UTIL_H */ diff --git a/sys/bluez/org.bluez.xml b/sys/bluez/org.bluez.xml index ff52ee303b..facae702bf 100644 --- a/sys/bluez/org.bluez.xml +++ b/sys/bluez/org.bluez.xml @@ -26,4 +26,28 @@ + + + + + + + + + + + + + + + + + + + + + + + +