mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-23 18:21:04 +00:00
bac0e14eae
If properties are proxied through GBinding this can work only if the proxied property keeps it's own value. The previous implementation will read the original value if the proxied property signals a change and thus nothing will happen.
534 lines
15 KiB
C
534 lines
15 KiB
C
/*
|
|
*
|
|
* 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)
|
|
|
|
#define DEFAULT_VOLUME 127
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_TRANSPORT,
|
|
PROP_TRANSPORT_VOLUME,
|
|
};
|
|
|
|
#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 (GstBaseSrc * bsrc, 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);
|
|
basesrc_class->query = GST_DEBUG_FUNCPTR (gst_avdtp_src_query);
|
|
|
|
g_object_class_install_property (gobject_class, PROP_TRANSPORT,
|
|
g_param_spec_string ("transport",
|
|
"Transport", "Use configured transport", NULL, G_PARAM_READWRITE));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_TRANSPORT_VOLUME,
|
|
g_param_spec_uint ("transport-volume",
|
|
"Transport volume",
|
|
"Volume of the transport (only valid if transport is acquired)",
|
|
0, 127, DEFAULT_VOLUME, 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;
|
|
avdtpsrc->transport_volume = DEFAULT_VOLUME;
|
|
|
|
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);
|
|
}
|
|
|
|
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;
|
|
|
|
case PROP_TRANSPORT_VOLUME:
|
|
g_value_set_uint (value, avdtpsrc->transport_volume);
|
|
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;
|
|
|
|
case PROP_TRANSPORT_VOLUME:
|
|
avdtpsrc->transport_volume = g_value_get_uint (value);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_avdtp_src_query (GstBaseSrc * bsrc, GstQuery * query)
|
|
{
|
|
GstAvdtpSrc *avdtpsrc = GST_AVDTP_SRC (bsrc);
|
|
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_BASE_SRC_CLASS (parent_class)->query (bsrc, 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");
|
|
gst_caps_unref (caps);
|
|
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");
|
|
gst_caps_unref (caps);
|
|
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");
|
|
gst_caps_unref (caps);
|
|
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");
|
|
gst_caps_unref (caps);
|
|
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);
|
|
|
|
/* The life time of the connection is shorter than the src object, so we
|
|
* don't need to worry about memory management */
|
|
gst_avdtp_connection_notify_volume (&avdtpsrc->conn, G_OBJECT (avdtpsrc),
|
|
"transport-volume");
|
|
|
|
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);
|
|
}
|