2012-09-14 12:03:07

by Mikel Astiz

[permalink] [raw]
Subject: [RFC v4 0/9] Optional acquire in Media API and related

From: Mikel Astiz <[email protected]>

Similar patchset as v3 with the integration of Luiz's feedback, including the following changes:
- Patches 7 and 8 swapped
- State "acquired" renamed to "active" (both in D-Bus and in the code)
- TRANSPORT_STATE_POST_ACQUIRE (which was transitional) dropped

>From original patch:

This patch reopens the discussion started by the thread "when is acquire
ok to call". The race condition seems to be real (even thought difficult
to reproduce), and I couldn't think of any approach to solve this
without altering the Media API.

Mikel Astiz (9):
media: Fix accesstype comparison
media: Add a2dp_sep_is_playing() to internal API
media: Add gateway_get_state() to internal API
media: Replace transport->in_use flag with state
media: Watch interface state changes in transport
media: Split transport state based on playing flag
media: Expose transport state in D-Bus
media: Automatically release transport when HUP
media: Extend media API with optional acquire

audio/a2dp.c | 8 +
audio/a2dp.h | 1 +
audio/gateway.c | 7 +
audio/gateway.h | 1 +
audio/transport.c | 390 +++++++++++++++++++++++++++++++++++++++++++----------
doc/media-api.txt | 19 +++
6 files changed, 356 insertions(+), 70 deletions(-)

--
1.7.7.6



2012-09-14 12:40:11

by Mikel Astiz

[permalink] [raw]
Subject: Re: [RFC v4 0/9] Optional acquire in Media API and related

Hi Maxim,

On Fri, Sep 14, 2012 at 2:16 PM, Maxim Levitsky <[email protected]> wrote:
> On Fri, 2012-09-14 at 14:03 +0200, Mikel Astiz wrote:
>> From: Mikel Astiz <[email protected]>
>>
>> Similar patchset as v3 with the integration of Luiz's feedback, including the following changes:
>> - Patches 7 and 8 swapped
>> - State "acquired" renamed to "active" (both in D-Bus and in the code)
>> - TRANSPORT_STATE_POST_ACQUIRE (which was transitional) dropped
>>
>> From original patch:
>>
>> This patch reopens the discussion started by the thread "when is acquire
>> ok to call". The race condition seems to be real (even thought difficult
>> to reproduce), and I couldn't think of any approach to solve this
>> without altering the Media API.
> Will this fix the issue with 2 a2dp headsets unable currently to connect
> at same time? Any idea if this is API limitation, PA bug or something
> else?

No, I'm afraid this patchset is unrelated to your issue.

Cheers,
Mikel

2012-09-14 12:16:19

by Maxim Levitsky

[permalink] [raw]
Subject: Re: [RFC v4 0/9] Optional acquire in Media API and related

On Fri, 2012-09-14 at 14:03 +0200, Mikel Astiz wrote:
> From: Mikel Astiz <[email protected]>
>
> Similar patchset as v3 with the integration of Luiz's feedback, including the following changes:
> - Patches 7 and 8 swapped
> - State "acquired" renamed to "active" (both in D-Bus and in the code)
> - TRANSPORT_STATE_POST_ACQUIRE (which was transitional) dropped
>
> From original patch:
>
> This patch reopens the discussion started by the thread "when is acquire
> ok to call". The race condition seems to be real (even thought difficult
> to reproduce), and I couldn't think of any approach to solve this
> without altering the Media API.
Will this fix the issue with 2 a2dp headsets unable currently to connect
at same time? Any idea if this is API limitation, PA bug or something
else?

Best regards,
Maxim Levitsky


2012-09-14 12:03:12

by Mikel Astiz

[permalink] [raw]
Subject: [RFC v4 5/9] media: Watch interface state changes in transport

From: Mikel Astiz <[email protected]>

Install watches to keep track whether the audio is streaming or not.
This should be relevant if the transport needs to reflect this state.
---
audio/transport.c | 103 +++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 103 insertions(+), 0 deletions(-)

diff --git a/audio/transport.c b/audio/transport.c
index 410601f..639c87f 100644
--- a/audio/transport.c
+++ b/audio/transport.c
@@ -45,6 +45,8 @@
#include "a2dp.h"
#include "headset.h"
#include "gateway.h"
+#include "sink.h"
+#include "source.h"
#include "avrcp.h"

#define MEDIA_TRANSPORT_INTERFACE "org.bluez.MediaTransport"
@@ -101,6 +103,10 @@ struct media_transport {
uint16_t omtu; /* Transport output mtu */
transport_lock_t lock;
transport_state_t state;
+ guint hs_watch;
+ guint ag_watch;
+ guint source_watch;
+ guint sink_watch;
guint (*resume) (struct media_transport *transport,
struct media_owner *owner);
guint (*suspend) (struct media_transport *transport,
@@ -173,6 +179,18 @@ void media_transport_destroy(struct media_transport *transport)
{
char *path;

+ if (transport->hs_watch)
+ headset_remove_state_cb(transport->hs_watch);
+
+ if (transport->ag_watch)
+ gateway_remove_state_cb(transport->ag_watch);
+
+ if (transport->sink_watch)
+ sink_remove_state_cb(transport->sink_watch);
+
+ if (transport->source_watch)
+ source_remove_state_cb(transport->source_watch);
+
path = g_strdup(transport->path);
g_dbus_unregister_interface(transport->conn, path,
MEDIA_TRANSPORT_INTERFACE);
@@ -1083,6 +1101,76 @@ static void headset_nrec_changed(struct audio_device *dev, gboolean nrec,
DBUS_TYPE_BOOLEAN, &nrec);
}

+static void transport_update_playing(struct media_transport *transport,
+ gboolean playing)
+{
+ DBG("%s Playing=%d", transport->path, playing);
+}
+
+static void headset_state_changed(struct audio_device *dev,
+ headset_state_t old_state,
+ headset_state_t new_state,
+ void *user_data)
+{
+ struct media_transport *transport = user_data;
+
+ if (dev != transport->device)
+ return;
+
+ if (new_state == HEADSET_STATE_PLAYING)
+ transport_update_playing(transport, TRUE);
+ else
+ transport_update_playing(transport, FALSE);
+}
+
+static void gateway_state_changed(struct audio_device *dev,
+ gateway_state_t old_state,
+ gateway_state_t new_state,
+ void *user_data)
+{
+ struct media_transport *transport = user_data;
+
+ if (dev != transport->device)
+ return;
+
+ if (new_state == GATEWAY_STATE_PLAYING)
+ transport_update_playing(transport, TRUE);
+ else
+ transport_update_playing(transport, FALSE);
+}
+
+static void sink_state_changed(struct audio_device *dev,
+ sink_state_t old_state,
+ sink_state_t new_state,
+ void *user_data)
+{
+ struct media_transport *transport = user_data;
+
+ if (dev != transport->device)
+ return;
+
+ if (new_state == SINK_STATE_PLAYING)
+ transport_update_playing(transport, TRUE);
+ else
+ transport_update_playing(transport, FALSE);
+}
+
+static void source_state_changed(struct audio_device *dev,
+ source_state_t old_state,
+ source_state_t new_state,
+ void *user_data)
+{
+ struct media_transport *transport = user_data;
+
+ if (dev != transport->device)
+ return;
+
+ if (new_state == SOURCE_STATE_PLAYING)
+ transport_update_playing(transport, TRUE);
+ else
+ transport_update_playing(transport, FALSE);
+}
+
struct media_transport *media_transport_create(DBusConnection *conn,
struct media_endpoint *endpoint,
struct audio_device *device,
@@ -1118,6 +1206,15 @@ struct media_transport *media_transport_create(DBusConnection *conn,
transport->set_property = set_property_a2dp;
transport->data = a2dp;
transport->destroy = destroy_a2dp;
+
+ if (strcasecmp(uuid, A2DP_SOURCE_UUID) == 0)
+ transport->sink_watch = sink_add_state_cb(
+ sink_state_changed,
+ transport);
+ else
+ transport->source_watch = source_add_state_cb(
+ source_state_changed,
+ transport);
} else if (strcasecmp(uuid, HFP_AG_UUID) == 0 ||
strcasecmp(uuid, HSP_AG_UUID) == 0) {
struct headset_transport *headset;
@@ -1135,6 +1232,9 @@ struct media_transport *media_transport_create(DBusConnection *conn,
transport->set_property = set_property_headset;
transport->data = headset;
transport->destroy = destroy_headset;
+ transport->hs_watch = headset_add_state_cb(
+ headset_state_changed,
+ transport);
} else if (strcasecmp(uuid, HFP_HS_UUID) == 0 ||
strcasecmp(uuid, HSP_HS_UUID) == 0) {
transport->resume = resume_gateway;
@@ -1142,6 +1242,9 @@ struct media_transport *media_transport_create(DBusConnection *conn,
transport->cancel = cancel_gateway;
transport->get_properties = get_properties_gateway;
transport->set_property = set_property_gateway;
+ transport->ag_watch = gateway_add_state_cb(
+ gateway_state_changed,
+ transport);
} else
goto fail;

--
1.7.7.6


2012-09-14 12:03:09

by Mikel Astiz

[permalink] [raw]
Subject: [RFC v4 2/9] media: Add a2dp_sep_is_playing() to internal API

From: Mikel Astiz <[email protected]>

Add this function to expose whether the local SEP is streaming or not.
---
audio/a2dp.c | 8 ++++++++
audio/a2dp.h | 1 +
2 files changed, 9 insertions(+), 0 deletions(-)

diff --git a/audio/a2dp.c b/audio/a2dp.c
index a9546b7..64b37e7 100644
--- a/audio/a2dp.c
+++ b/audio/a2dp.c
@@ -1833,6 +1833,14 @@ gboolean a2dp_sep_unlock(struct a2dp_sep *sep, struct avdtp *session)
return TRUE;
}

+gboolean a2dp_sep_is_playing(struct a2dp_sep *sep)
+{
+ if (avdtp_sep_get_state(sep->lsep) == AVDTP_STATE_STREAMING)
+ return TRUE;
+ else
+ return FALSE;
+}
+
gboolean a2dp_sep_get_lock(struct a2dp_sep *sep)
{
return sep->locked;
diff --git a/audio/a2dp.h b/audio/a2dp.h
index 27b4a57..deab3b8 100644
--- a/audio/a2dp.h
+++ b/audio/a2dp.h
@@ -90,6 +90,7 @@ gboolean a2dp_cancel(struct audio_device *dev, unsigned int id);
gboolean a2dp_sep_lock(struct a2dp_sep *sep, struct avdtp *session);
gboolean a2dp_sep_unlock(struct a2dp_sep *sep, struct avdtp *session);
gboolean a2dp_sep_get_lock(struct a2dp_sep *sep);
+gboolean a2dp_sep_is_playing(struct a2dp_sep *sep);
struct avdtp_stream *a2dp_sep_get_stream(struct a2dp_sep *sep);
struct a2dp_sep *a2dp_get_sep(struct avdtp *session,
struct avdtp_stream *stream);
--
1.7.7.6


2012-09-14 12:03:15

by Mikel Astiz

[permalink] [raw]
Subject: [RFC v4 8/9] media: Automatically release transport when HUP

From: Mikel Astiz <[email protected]>

When the remote end suspends the audio stream, release the transport
automatically without waiting until the clients call Release().

This affects the D-Bus API since clients will get an error when trying
to release the transport afterwards.

However, this should have no real impact, since most clients (i.e.
PulseAudio) would just log some error trace but otherwise ignore the
issue.
---
audio/transport.c | 14 +++++++++++++-
1 files changed, 13 insertions(+), 1 deletions(-)

diff --git a/audio/transport.c b/audio/transport.c
index 742cc72..4653cc7 100644
--- a/audio/transport.c
+++ b/audio/transport.c
@@ -60,7 +60,7 @@ typedef enum {
TRANSPORT_STATE_IDLE, /* Not acquired and suspended */
TRANSPORT_STATE_PENDING, /* Playing but not acquired */
TRANSPORT_STATE_REQUESTING, /* Acquire in progress */
- TRANSPORT_STATE_ACTIVE, /* Acquired (not necessarily playing) */
+ TRANSPORT_STATE_ACTIVE, /* Acquired and playing */
TRANSPORT_STATE_SUSPENDING, /* Release in progress */
} transport_state_t;

@@ -1178,6 +1178,18 @@ static void transport_update_playing(struct media_transport *transport,
if (playing == FALSE) {
if (transport->state == TRANSPORT_STATE_PENDING)
transport_set_state(transport, TRANSPORT_STATE_IDLE);
+ else if (transport->state == TRANSPORT_STATE_ACTIVE) {
+ /* Remove all owners */
+ while (transport->owners != NULL) {
+ struct media_owner *owner;
+
+ owner = transport->owners->data;
+ media_transport_remove(transport, owner);
+ }
+
+ /* Suspend so that the locks get released */
+ transport->suspend(transport, NULL);
+ }
} else if (transport->state == TRANSPORT_STATE_IDLE)
transport_set_state(transport, TRANSPORT_STATE_PENDING);
}
--
1.7.7.6


2012-09-14 12:03:16

by Mikel Astiz

[permalink] [raw]
Subject: [RFC v4 9/9] media: Extend media API with optional acquire

From: Mikel Astiz <[email protected]>

Acquiring a transport is needed in two different situations: either
we are initiating the audio stream locally, or the remote side initiated
it and thus we are just reacting. In the second case, we would expect
the stream is already available, and otherwise the operation should
fail. This means the media API needs to be extended in order to make
this difference.

This issue is specially relevant in the case of SCO, because the current
approach is racy. With HFP, for example (say BlueZ has the HS role), the
following race condition could be met:

1. Phone has an incoming call and thus starts in-band ringing.
2. SCO connection is accepted and stablished by BlueZ.
3. Gateway interface state is changed to Playing.
4. Exactly afterwards, the user routes the audio to the phone, to have
a private conversation. So the SCO link is closed.
5. In parallel, PulseAudio sees the transition to Playing, and acquires
the transport.
6. BlueZ receives an Acquire() request, but SCO is down. So it tries to
reconnect the SCO link.

The last step is an undesired behavior (the audio is routed back to the
car). BlueZ should be smart enough to know that the SCO connection
shouldn't be reestablished, but this is only possible if the endpoint
provides additional information in the media API.

Note that the API change introduced by this patch is backward
compatible, meaning that older versions of BlueZ will just ignore the
flag. So clients are encouraged to use it without necessarily adding a
dependency to newer versions of BlueZ.
---
audio/transport.c | 4 ++++
doc/media-api.txt | 11 +++++++++++
2 files changed, 15 insertions(+), 0 deletions(-)

diff --git a/audio/transport.c b/audio/transport.c
index 4653cc7..cc9c56f 100644
--- a/audio/transport.c
+++ b/audio/transport.c
@@ -807,6 +807,10 @@ static DBusMessage *acquire(DBusConnection *conn, DBusMessage *msg,
if (lock == 0)
return btd_error_invalid_args(msg);

+ if (transport->state != TRANSPORT_STATE_PENDING &&
+ g_strstr_len(accesstype, -1, "?") != NULL)
+ return btd_error_failed(msg, "Transport not playing");
+
if (media_transport_acquire(transport, lock) == FALSE)
return btd_error_not_authorized(msg);

diff --git a/doc/media-api.txt b/doc/media-api.txt
index da8ef53..b9802d9 100644
--- a/doc/media-api.txt
+++ b/doc/media-api.txt
@@ -282,6 +282,17 @@ Methods dict GetProperties()

"rw": Read and write access

+ The accesstype string can also be combined with a "?"
+ suffix, which will make the request optional. This
+ typically means the transport will only be acquired if
+ it is already available (remote-initiated), but
+ otherwise no request will be sent to the remote side.
+ In this last case the function will fail. Note that,
+ due to compatibility issues with older versions of
+ BlueZ, clients are encouraged to use exactly the same
+ accesstype for Release(), matching the string provided
+ to Acquire().
+
void Release(string accesstype)

Releases file descriptor.
--
1.7.7.6


2012-09-14 12:03:10

by Mikel Astiz

[permalink] [raw]
Subject: [RFC v4 3/9] media: Add gateway_get_state() to internal API

From: Mikel Astiz <[email protected]>

Expose the state of the gateway interface in the internal API.
---
audio/gateway.c | 7 +++++++
audio/gateway.h | 1 +
2 files changed, 8 insertions(+), 0 deletions(-)

diff --git a/audio/gateway.c b/audio/gateway.c
index 0603f12..53094af 100644
--- a/audio/gateway.c
+++ b/audio/gateway.c
@@ -787,6 +787,13 @@ gboolean gateway_is_active(struct audio_device *dev)
return FALSE;
}

+gateway_state_t gateway_get_state(struct audio_device *dev)
+{
+ struct gateway *gw = dev->gateway;
+
+ return gw->state;
+}
+
int gateway_connect_rfcomm(struct audio_device *dev, GIOChannel *io)
{
if (!io)
diff --git a/audio/gateway.h b/audio/gateway.h
index 0893962..d87d76a 100644
--- a/audio/gateway.h
+++ b/audio/gateway.h
@@ -59,6 +59,7 @@ void gateway_unregister(struct audio_device *dev);
struct gateway *gateway_init(struct audio_device *dev);
gboolean gateway_is_active(struct audio_device *dev);
gboolean gateway_is_connected(struct audio_device *dev);
+gateway_state_t gateway_get_state(struct audio_device *dev);
int gateway_connect_rfcomm(struct audio_device *dev, GIOChannel *io);
int gateway_connect_sco(struct audio_device *dev, GIOChannel *chan);
void gateway_start_service(struct audio_device *device);
--
1.7.7.6


2012-09-14 12:03:11

by Mikel Astiz

[permalink] [raw]
Subject: [RFC v4 4/9] media: Replace transport->in_use flag with state

From: Mikel Astiz <[email protected]>

Refactor the code to use a enum type to represent the transport state.
This should scale better when additional states need to be represented.

A helper function has been added to help track the mapping between the
enum type and the old in_use flag.
---
audio/transport.c | 77 ++++++++++++++++++++++++++++++++++++++++-------------
1 files changed, 58 insertions(+), 19 deletions(-)

diff --git a/audio/transport.c b/audio/transport.c
index d418878..410601f 100644
--- a/audio/transport.c
+++ b/audio/transport.c
@@ -54,6 +54,16 @@ typedef enum {
TRANSPORT_LOCK_WRITE = 1 << 1,
} transport_lock_t;

+typedef enum {
+ TRANSPORT_STATE_IDLE, /* Not acquired */
+ TRANSPORT_STATE_ACTIVE, /* Acquired (not necessarily playing) */
+} transport_state_t;
+
+static char *str_state[] = {
+ "TRANSPORT_STATE_IDLE",
+ "TRANSPORT_STATE_ACTIVE",
+};
+
struct media_request {
DBusMessage *msg;
guint id;
@@ -90,7 +100,7 @@ struct media_transport {
uint16_t imtu; /* Transport input mtu */
uint16_t omtu; /* Transport output mtu */
transport_lock_t lock;
- gboolean in_use;
+ transport_state_t state;
guint (*resume) (struct media_transport *transport,
struct media_owner *owner);
guint (*suspend) (struct media_transport *transport,
@@ -133,6 +143,32 @@ static transport_lock_t str2lock(const char *str)
return lock;
}

+static gboolean state_in_use(transport_state_t state)
+{
+ switch (state) {
+ case TRANSPORT_STATE_IDLE:
+ return FALSE;
+ case TRANSPORT_STATE_ACTIVE:
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void transport_set_state(struct media_transport *transport,
+ transport_state_t state)
+{
+ transport_state_t old_state = transport->state;
+
+ if (old_state == state)
+ return;
+
+ transport->state = state;
+
+ DBG("State changed %s: %s -> %s", transport->path, str_state[old_state],
+ str_state[state]);
+}
+
void media_transport_destroy(struct media_transport *transport)
{
char *path;
@@ -240,7 +276,7 @@ static void media_transport_remove(struct media_transport *transport,
media_owner_free(owner);

/* Suspend if there is no longer any owner */
- if (transport->owners == NULL && transport->in_use)
+ if (transport->owners == NULL && state_in_use(transport->state))
transport->suspend(transport, NULL);
}

@@ -322,13 +358,14 @@ static guint resume_a2dp(struct media_transport *transport,
return 0;
}

- if (transport->in_use == TRUE)
+ if (state_in_use(transport->state))
goto done;

- transport->in_use = a2dp_sep_lock(sep, a2dp->session);
- if (transport->in_use == FALSE)
+ if (a2dp_sep_lock(sep, a2dp->session) == FALSE)
return 0;

+ transport_set_state(transport, TRANSPORT_STATE_ACTIVE);
+
done:
return a2dp_resume(a2dp->session, sep, a2dp_resume_complete, owner);
}
@@ -349,7 +386,7 @@ static void a2dp_suspend_complete(struct avdtp *session,
}

a2dp_sep_unlock(sep, a2dp->session);
- transport->in_use = FALSE;
+ transport_set_state(transport, TRANSPORT_STATE_IDLE);
media_transport_remove(transport, owner);
}

@@ -362,7 +399,7 @@ static guint suspend_a2dp(struct media_transport *transport,

if (!owner) {
a2dp_sep_unlock(sep, a2dp->session);
- transport->in_use = FALSE;
+ transport_set_state(transport, TRANSPORT_STATE_IDLE);
return 0;
}

@@ -424,14 +461,15 @@ static guint resume_headset(struct media_transport *transport,
{
struct audio_device *device = transport->device;

- if (transport->in_use == TRUE)
+ if (state_in_use(transport->state))
goto done;

- transport->in_use = headset_lock(device, HEADSET_LOCK_READ |
- HEADSET_LOCK_WRITE);
- if (transport->in_use == FALSE)
+ if (headset_lock(device, HEADSET_LOCK_READ |
+ HEADSET_LOCK_WRITE) == FALSE)
return 0;

+ transport_set_state(transport, TRANSPORT_STATE_ACTIVE);
+
done:
return headset_request_stream(device, headset_resume_complete,
owner);
@@ -450,7 +488,7 @@ static void headset_suspend_complete(struct audio_device *dev, void *user_data)
}

headset_unlock(dev, HEADSET_LOCK_READ | HEADSET_LOCK_WRITE);
- transport->in_use = FALSE;
+ transport_set_state(transport, TRANSPORT_STATE_IDLE);
media_transport_remove(transport, owner);
}

@@ -461,7 +499,7 @@ static guint suspend_headset(struct media_transport *transport,

if (!owner) {
headset_unlock(device, HEADSET_LOCK_READ | HEADSET_LOCK_WRITE);
- transport->in_use = FALSE;
+ transport_set_state(transport, TRANSPORT_STATE_IDLE);
return 0;
}

@@ -529,14 +567,15 @@ static guint resume_gateway(struct media_transport *transport,
{
struct audio_device *device = transport->device;

- if (transport->in_use == TRUE)
+ if (state_in_use(transport->state))
goto done;

- transport->in_use = gateway_lock(device, GATEWAY_LOCK_READ |
- GATEWAY_LOCK_WRITE);
- if (transport->in_use == FALSE)
+ if (gateway_lock(device, GATEWAY_LOCK_READ |
+ GATEWAY_LOCK_WRITE) == FALSE)
return 0;

+ transport_set_state(transport, TRANSPORT_STATE_ACTIVE);
+
done:
return gateway_request_stream(device, gateway_resume_complete,
owner);
@@ -556,7 +595,7 @@ static gboolean gateway_suspend_complete(gpointer user_data)
}

gateway_unlock(device, GATEWAY_LOCK_READ | GATEWAY_LOCK_WRITE);
- transport->in_use = FALSE;
+ transport_set_state(transport, TRANSPORT_STATE_IDLE);
media_transport_remove(transport, owner);
return FALSE;
}
@@ -569,7 +608,7 @@ static guint suspend_gateway(struct media_transport *transport,

if (!owner) {
gateway_unlock(device, GATEWAY_LOCK_READ | GATEWAY_LOCK_WRITE);
- transport->in_use = FALSE;
+ transport_set_state(transport, TRANSPORT_STATE_IDLE);
return 0;
}

--
1.7.7.6


2012-09-14 12:03:13

by Mikel Astiz

[permalink] [raw]
Subject: [RFC v4 6/9] media: Split transport state based on playing flag

From: Mikel Astiz <[email protected]>

Split the transport states (formerly in_use) into more specific states
where the stream state (playing or suspended) is explicitly represented,
along with the transitional states (locally initiated suspend and
resume).

TRANSPORT_STATE_ACTIVE is an exception since it also includes the state
where the transport is acquired, but the audio was later suspended (not
released yet though).
---
audio/transport.c | 62 ++++++++++++++++++++++++++++++++++++++++++++++------
1 files changed, 54 insertions(+), 8 deletions(-)

diff --git a/audio/transport.c b/audio/transport.c
index 639c87f..e0402ad 100644
--- a/audio/transport.c
+++ b/audio/transport.c
@@ -57,13 +57,19 @@ typedef enum {
} transport_lock_t;

typedef enum {
- TRANSPORT_STATE_IDLE, /* Not acquired */
+ TRANSPORT_STATE_IDLE, /* Not acquired and suspended */
+ TRANSPORT_STATE_PENDING, /* Playing but not acquired */
+ TRANSPORT_STATE_REQUESTING, /* Acquire in progress */
TRANSPORT_STATE_ACTIVE, /* Acquired (not necessarily playing) */
+ TRANSPORT_STATE_SUSPENDING, /* Release in progress */
} transport_state_t;

static char *str_state[] = {
"TRANSPORT_STATE_IDLE",
+ "TRANSPORT_STATE_PENDING",
+ "TRANSPORT_STATE_REQUESTING",
"TRANSPORT_STATE_ACTIVE",
+ "TRANSPORT_STATE_SUSPENDING",
};

struct media_request {
@@ -153,8 +159,11 @@ static gboolean state_in_use(transport_state_t state)
{
switch (state) {
case TRANSPORT_STATE_IDLE:
+ case TRANSPORT_STATE_PENDING:
return FALSE;
+ case TRANSPORT_STATE_REQUESTING:
case TRANSPORT_STATE_ACTIVE:
+ case TRANSPORT_STATE_SUSPENDING:
return TRUE;
}

@@ -356,6 +365,8 @@ static void a2dp_resume_complete(struct avdtp *session,

media_owner_remove(owner);

+ transport_set_state(transport, TRANSPORT_STATE_ACTIVE);
+
return;

fail:
@@ -382,7 +393,8 @@ static guint resume_a2dp(struct media_transport *transport,
if (a2dp_sep_lock(sep, a2dp->session) == FALSE)
return 0;

- transport_set_state(transport, TRANSPORT_STATE_ACTIVE);
+ if (transport->state == TRANSPORT_STATE_IDLE)
+ transport_set_state(transport, TRANSPORT_STATE_REQUESTING);

done:
return a2dp_resume(a2dp->session, sep, a2dp_resume_complete, owner);
@@ -417,7 +429,12 @@ static guint suspend_a2dp(struct media_transport *transport,

if (!owner) {
a2dp_sep_unlock(sep, a2dp->session);
- transport_set_state(transport, TRANSPORT_STATE_IDLE);
+
+ if (a2dp_sep_is_playing(sep))
+ transport_set_state(transport, TRANSPORT_STATE_PENDING);
+ else
+ transport_set_state(transport, TRANSPORT_STATE_IDLE);
+
return 0;
}

@@ -468,6 +485,8 @@ static void headset_resume_complete(struct audio_device *dev, void *user_data)

media_owner_remove(owner);

+ transport_set_state(transport, TRANSPORT_STATE_ACTIVE);
+
return;

fail:
@@ -486,7 +505,8 @@ static guint resume_headset(struct media_transport *transport,
HEADSET_LOCK_WRITE) == FALSE)
return 0;

- transport_set_state(transport, TRANSPORT_STATE_ACTIVE);
+ if (transport->state == TRANSPORT_STATE_IDLE)
+ transport_set_state(transport, TRANSPORT_STATE_REQUESTING);

done:
return headset_request_stream(device, headset_resume_complete,
@@ -516,8 +536,15 @@ static guint suspend_headset(struct media_transport *transport,
struct audio_device *device = transport->device;

if (!owner) {
+ headset_state_t state = headset_get_state(device);
+
headset_unlock(device, HEADSET_LOCK_READ | HEADSET_LOCK_WRITE);
- transport_set_state(transport, TRANSPORT_STATE_IDLE);
+
+ if (state == HEADSET_STATE_PLAYING)
+ transport_set_state(transport, TRANSPORT_STATE_PENDING);
+ else
+ transport_set_state(transport, TRANSPORT_STATE_IDLE);
+
return 0;
}

@@ -574,6 +601,8 @@ static void gateway_resume_complete(struct audio_device *dev, GError *err,

media_owner_remove(owner);

+ transport_set_state(transport, TRANSPORT_STATE_ACTIVE);
+
return;

fail:
@@ -592,7 +621,8 @@ static guint resume_gateway(struct media_transport *transport,
GATEWAY_LOCK_WRITE) == FALSE)
return 0;

- transport_set_state(transport, TRANSPORT_STATE_ACTIVE);
+ if (transport->state == TRANSPORT_STATE_IDLE)
+ transport_set_state(transport, TRANSPORT_STATE_REQUESTING);

done:
return gateway_request_stream(device, gateway_resume_complete,
@@ -625,8 +655,15 @@ static guint suspend_gateway(struct media_transport *transport,
static int id = 1;

if (!owner) {
+ gateway_state_t state = gateway_get_state(device);
+
gateway_unlock(device, GATEWAY_LOCK_READ | GATEWAY_LOCK_WRITE);
- transport_set_state(transport, TRANSPORT_STATE_IDLE);
+
+ if (state == GATEWAY_STATE_PLAYING)
+ transport_set_state(transport, TRANSPORT_STATE_PENDING);
+ else
+ transport_set_state(transport, TRANSPORT_STATE_IDLE);
+
return 0;
}

@@ -806,6 +843,8 @@ static DBusMessage *release(DBusConnection *conn, DBusMessage *msg,
return btd_error_in_progress(msg);
}

+ transport_set_state(transport, TRANSPORT_STATE_SUSPENDING);
+
id = transport->suspend(transport, owner);
if (id == 0) {
media_transport_remove(transport, owner);
@@ -1104,7 +1143,14 @@ static void headset_nrec_changed(struct audio_device *dev, gboolean nrec,
static void transport_update_playing(struct media_transport *transport,
gboolean playing)
{
- DBG("%s Playing=%d", transport->path, playing);
+ DBG("%s State=%s Playing=%d", transport->path,
+ str_state[transport->state], playing);
+
+ if (playing == FALSE) {
+ if (transport->state == TRANSPORT_STATE_PENDING)
+ transport_set_state(transport, TRANSPORT_STATE_IDLE);
+ } else if (transport->state == TRANSPORT_STATE_IDLE)
+ transport_set_state(transport, TRANSPORT_STATE_PENDING);
}

static void headset_state_changed(struct audio_device *dev,
--
1.7.7.6


2012-09-14 12:03:14

by Mikel Astiz

[permalink] [raw]
Subject: [RFC v4 7/9] media: Expose transport state in D-Bus

From: Mikel Astiz <[email protected]>

Extend the Media API to expose the transport state in D-Bus, as a
property of the transport. This way the clients do not have to find
out which is the corresponding profile-specific interface for the
transport.

Additionally, this state along with the automatic release of transports
will allow clients to avoid the "optional release" or "accept remote
release" race condition. For example, with HSP/HFP profiles, the problem
is the following:

1. User suspends SCO in the remote end.
2. BlueZ signals the Playing->Connected state change in D-Bus.
3. Exactly afterwards, the user resumes SCO in the remote end.
4. In parallel, PulseAudio sees the aforementioned transition to
Connected, and thus releases the transport.
5. BlueZ receives a Release() request while SCO is up. So the audio
stream will be suspended.

The last step is an undesired behavior since the user explicitly wanted
to route the audio stream through Bluetooth.

The issue is difficult to reproduce but it can easily be solved by
exposing the transport state in D-Bus.
---
audio/transport.c | 29 +++++++++++++++++++++++++++++
doc/media-api.txt | 8 ++++++++
2 files changed, 37 insertions(+), 0 deletions(-)

diff --git a/audio/transport.c b/audio/transport.c
index e0402ad..742cc72 100644
--- a/audio/transport.c
+++ b/audio/transport.c
@@ -155,6 +155,22 @@ static transport_lock_t str2lock(const char *str)
return lock;
}

+static const char *state2str(transport_state_t state)
+{
+ switch (state) {
+ case TRANSPORT_STATE_IDLE:
+ case TRANSPORT_STATE_REQUESTING:
+ return "idle";
+ case TRANSPORT_STATE_PENDING:
+ return "pending";
+ case TRANSPORT_STATE_ACTIVE:
+ case TRANSPORT_STATE_SUSPENDING:
+ return "active";
+ }
+
+ return NULL;
+}
+
static gboolean state_in_use(transport_state_t state)
{
switch (state) {
@@ -174,6 +190,7 @@ static void transport_set_state(struct media_transport *transport,
transport_state_t state)
{
transport_state_t old_state = transport->state;
+ const char *str;

if (old_state == state)
return;
@@ -182,6 +199,13 @@ static void transport_set_state(struct media_transport *transport,

DBG("State changed %s: %s -> %s", transport->path, str_state[old_state],
str_state[state]);
+
+ str = state2str(state);
+
+ if (g_strcmp0(str, state2str(old_state)) != 0)
+ emit_property_changed(transport->conn, transport->path,
+ MEDIA_TRANSPORT_INTERFACE, "State",
+ DBUS_TYPE_STRING, &str);
}

void media_transport_destroy(struct media_transport *transport)
@@ -1017,6 +1041,7 @@ void transport_get_properties(struct media_transport *transport,
DBusMessageIter dict;
const char *uuid;
uint8_t codec;
+ const char *state;

dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
@@ -1036,6 +1061,10 @@ void transport_get_properties(struct media_transport *transport,
dict_append_array(&dict, "Configuration", DBUS_TYPE_BYTE,
&transport->configuration, transport->size);

+ /* State */
+ state = state2str(transport->state);
+ dict_append_entry(&dict, "State", DBUS_TYPE_STRING, &state);
+
if (transport->get_properties)
transport->get_properties(transport, &dict);

diff --git a/doc/media-api.txt b/doc/media-api.txt
index e5eeaa0..da8ef53 100644
--- a/doc/media-api.txt
+++ b/doc/media-api.txt
@@ -317,6 +317,14 @@ Properties object Device [readonly]
Configuration blob, it is used as it is so the size and
byte order must match.

+ string State [readonly]
+
+ Indicates the state of the transport. Possible
+ values are:
+ "idle": not streaming
+ "pending": streaming but not acquired
+ "active": streaming and acquired
+
uint16 Delay [readwrite]

Optional. Transport delay in 1/10 of millisecond, this
--
1.7.7.6


2012-09-14 12:03:08

by Mikel Astiz

[permalink] [raw]
Subject: [RFC v4 1/9] media: Fix accesstype comparison

From: Mikel Astiz <[email protected]>

Replace the string representation of the accesstype with a conventional
binary representation. This makes the code simpler and more efficient.

This also fixes a minor bug in the Release() D-Bus method, where the
string comparison was used to see whether the owner should be removed. A
client acquiring with "rw" and releasing with "wr" would lead to the
inconsistent state of having a released transport with an owner with no
accesstype. Partial releases can also get affected by this bug since the
released character (partial accesstype) got replaced by a whitespace.

Additionally, this approach is more robust in case new flags are added
in the future.
---
audio/transport.c | 119 ++++++++++++++++++++++++++++++-----------------------
1 files changed, 68 insertions(+), 51 deletions(-)

diff --git a/audio/transport.c b/audio/transport.c
index d40c92d..d418878 100644
--- a/audio/transport.c
+++ b/audio/transport.c
@@ -49,6 +49,11 @@

#define MEDIA_TRANSPORT_INTERFACE "org.bluez.MediaTransport"

+typedef enum {
+ TRANSPORT_LOCK_READ = 1,
+ TRANSPORT_LOCK_WRITE = 1 << 1,
+} transport_lock_t;
+
struct media_request {
DBusMessage *msg;
guint id;
@@ -58,7 +63,7 @@ struct media_owner {
struct media_transport *transport;
struct media_request *pending;
char *name;
- char *accesstype;
+ transport_lock_t lock;
guint watch;
};

@@ -84,8 +89,7 @@ struct media_transport {
int fd; /* Transport file descriptor */
uint16_t imtu; /* Transport input mtu */
uint16_t omtu; /* Transport output mtu */
- gboolean read_lock;
- gboolean write_lock;
+ transport_lock_t lock;
gboolean in_use;
guint (*resume) (struct media_transport *transport,
struct media_owner *owner);
@@ -104,6 +108,31 @@ struct media_transport {
void *data;
};

+static const char *lock2str(transport_lock_t lock)
+{
+ if (lock == 0)
+ return "";
+ else if (lock == TRANSPORT_LOCK_READ)
+ return "r";
+ else if (lock == TRANSPORT_LOCK_WRITE)
+ return "w";
+ else
+ return "rw";
+}
+
+static transport_lock_t str2lock(const char *str)
+{
+ transport_lock_t lock = 0;
+
+ if (g_strstr_len(str, -1, "r") != NULL)
+ lock |= TRANSPORT_LOCK_READ;
+
+ if (g_strstr_len(str, -1, "w") != NULL)
+ lock |= TRANSPORT_LOCK_WRITE;
+
+ return lock;
+}
+
void media_transport_destroy(struct media_transport *transport)
{
char *path;
@@ -148,17 +177,15 @@ static void media_request_reply(struct media_request *req,
}

static gboolean media_transport_release(struct media_transport *transport,
- const char *accesstype)
+ transport_lock_t lock)
{
- if (g_strstr_len(accesstype, -1, "r") != NULL) {
- transport->read_lock = FALSE;
+ transport->lock &= ~lock;
+
+ if (lock & TRANSPORT_LOCK_READ)
DBG("Transport %s: read lock released", transport->path);
- }

- if (g_strstr_len(accesstype, -1, "w") != NULL) {
- transport->write_lock = FALSE;
+ if (lock & TRANSPORT_LOCK_WRITE)
DBG("Transport %s: write lock released", transport->path);
- }

return TRUE;
}
@@ -191,7 +218,6 @@ static void media_owner_free(struct media_owner *owner)
media_owner_remove(owner);

g_free(owner->name);
- g_free(owner->accesstype);
g_free(owner);
}

@@ -200,7 +226,7 @@ static void media_transport_remove(struct media_transport *transport,
{
DBG("Transport %s Owner %s", transport->path, owner->name);

- media_transport_release(transport, owner->accesstype);
+ media_transport_release(transport, owner->lock);

/* Reply if owner has a pending request */
if (owner->pending)
@@ -260,10 +286,10 @@ static void a2dp_resume_complete(struct avdtp *session,

media_transport_set_fd(transport, fd, imtu, omtu);

- if (g_strstr_len(owner->accesstype, -1, "r") == NULL)
+ if ((owner->lock & TRANSPORT_LOCK_READ) == 0)
imtu = 0;

- if (g_strstr_len(owner->accesstype, -1, "w") == NULL)
+ if ((owner->lock & TRANSPORT_LOCK_WRITE) == 0)
omtu = 0;

ret = g_dbus_send_reply(transport->conn, req->msg,
@@ -371,10 +397,10 @@ static void headset_resume_complete(struct audio_device *dev, void *user_data)

media_transport_set_fd(transport, fd, imtu, omtu);

- if (g_strstr_len(owner->accesstype, -1, "r") == NULL)
+ if ((owner->lock & TRANSPORT_LOCK_READ) == 0)
imtu = 0;

- if (g_strstr_len(owner->accesstype, -1, "w") == NULL)
+ if ((owner->lock & TRANSPORT_LOCK_WRITE) == 0)
omtu = 0;

ret = g_dbus_send_reply(transport->conn, req->msg,
@@ -476,10 +502,10 @@ static void gateway_resume_complete(struct audio_device *dev, GError *err,

media_transport_set_fd(transport, fd, imtu, omtu);

- if (g_strstr_len(owner->accesstype, -1, "r") == NULL)
+ if ((owner->lock & TRANSPORT_LOCK_READ) == 0)
imtu = 0;

- if (g_strstr_len(owner->accesstype, -1, "w") == NULL)
+ if ((owner->lock & TRANSPORT_LOCK_WRITE) == 0)
omtu = 0;

ret = g_dbus_send_reply(transport->conn, req->msg,
@@ -569,35 +595,18 @@ static void media_owner_exit(DBusConnection *connection, void *user_data)
}

static gboolean media_transport_acquire(struct media_transport *transport,
- const char *accesstype)
+ transport_lock_t lock)
{
- gboolean read_lock = FALSE, write_lock = FALSE;
-
- if (g_strstr_len(accesstype, -1, "r") != NULL) {
- if (transport->read_lock == TRUE)
- return FALSE;
- read_lock = TRUE;
- }
-
- if (g_strstr_len(accesstype, -1, "w") != NULL) {
- if (transport->write_lock == TRUE)
- return FALSE;
- write_lock = TRUE;
- }
-
- /* Check invalid accesstype */
- if (read_lock == FALSE && write_lock == FALSE)
+ if (transport->lock & lock)
return FALSE;

- if (read_lock) {
- transport->read_lock = read_lock;
+ transport->lock |= lock;
+
+ if (lock & TRANSPORT_LOCK_READ)
DBG("Transport %s: read lock acquired", transport->path);
- }

- if (write_lock) {
- transport->write_lock = write_lock;
+ if (lock & TRANSPORT_LOCK_WRITE)
DBG("Transport %s: write lock acquired", transport->path);
- }


return TRUE;
@@ -616,16 +625,16 @@ static void media_transport_add(struct media_transport *transport,

static struct media_owner *media_owner_create(DBusConnection *conn,
DBusMessage *msg,
- const char *accesstype)
+ transport_lock_t lock)
{
struct media_owner *owner;

owner = g_new0(struct media_owner, 1);
owner->name = g_strdup(dbus_message_get_sender(msg));
- owner->accesstype = g_strdup(accesstype);
+ owner->lock = lock;

DBG("Owner created: sender=%s accesstype=%s", owner->name,
- accesstype);
+ lock2str(lock));

return owner;
}
@@ -662,6 +671,7 @@ static DBusMessage *acquire(DBusConnection *conn, DBusMessage *msg,
struct media_owner *owner;
struct media_request *req;
const char *accesstype, *sender;
+ transport_lock_t lock;
guint id;

if (!dbus_message_get_args(msg, NULL,
@@ -675,13 +685,17 @@ static DBusMessage *acquire(DBusConnection *conn, DBusMessage *msg,
if (owner != NULL)
return btd_error_not_authorized(msg);

- if (media_transport_acquire(transport, accesstype) == FALSE)
+ lock = str2lock(accesstype);
+ if (lock == 0)
+ return btd_error_invalid_args(msg);
+
+ if (media_transport_acquire(transport, lock) == FALSE)
return btd_error_not_authorized(msg);

- owner = media_owner_create(conn, msg, accesstype);
+ owner = media_owner_create(conn, msg, lock);
id = transport->resume(transport, owner);
if (id == 0) {
- media_transport_release(transport, accesstype);
+ media_transport_release(transport, lock);
media_owner_free(owner);
return btd_error_not_authorized(msg);
}
@@ -699,6 +713,7 @@ static DBusMessage *release(DBusConnection *conn, DBusMessage *msg,
struct media_transport *transport = data;
struct media_owner *owner;
const char *accesstype, *sender;
+ transport_lock_t lock;
struct media_request *req;

if (!dbus_message_get_args(msg, NULL,
@@ -712,7 +727,9 @@ static DBusMessage *release(DBusConnection *conn, DBusMessage *msg,
if (owner == NULL)
return btd_error_not_authorized(msg);

- if (g_strcmp0(owner->accesstype, accesstype) == 0) {
+ lock = str2lock(accesstype);
+
+ if (owner->lock == lock) {
guint id;

/* Not the last owner, no need to suspend */
@@ -742,9 +759,9 @@ static DBusMessage *release(DBusConnection *conn, DBusMessage *msg,
media_owner_add(owner, req);

return NULL;
- } else if (g_strstr_len(owner->accesstype, -1, accesstype) != NULL) {
- media_transport_release(transport, accesstype);
- g_strdelimit(owner->accesstype, accesstype, ' ');
+ } else if ((owner->lock & lock) == lock) {
+ media_transport_release(transport, lock);
+ owner->lock &= ~lock;
} else
return btd_error_not_authorized(msg);

--
1.7.7.6