2014-04-16 23:50:33

by Andrzej Kaczmarek

[permalink] [raw]
Subject: [PATCH v2 1/6] android/a2dp: Prefer master role on incoming connections

---
android/a2dp.c | 1 +
1 file changed, 1 insertion(+)

diff --git a/android/a2dp.c b/android/a2dp.c
index 4ea16e2..8fb84a3 100644
--- a/android/a2dp.c
+++ b/android/a2dp.c
@@ -1577,6 +1577,7 @@ bool bt_a2dp_register(struct ipc *ipc, const bdaddr_t *addr, uint8_t mode)
BT_IO_OPT_SOURCE_BDADDR, &adapter_addr,
BT_IO_OPT_PSM, L2CAP_PSM_AVDTP,
BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM,
+ BT_IO_OPT_MASTER, true,
BT_IO_OPT_INVALID);
if (!server) {
error("Failed to listen on AVDTP channel: %s", err->message);
--
1.9.2



2014-04-17 07:04:07

by Luiz Augusto von Dentz

[permalink] [raw]
Subject: Re: [PATCH v2 1/6] android/a2dp: Prefer master role on incoming connections

Hi Andrzej,

On Thu, Apr 17, 2014 at 2:50 AM, Andrzej Kaczmarek
<[email protected]> wrote:
> ---
> android/a2dp.c | 1 +
> 1 file changed, 1 insertion(+)
>
> diff --git a/android/a2dp.c b/android/a2dp.c
> index 4ea16e2..8fb84a3 100644
> --- a/android/a2dp.c
> +++ b/android/a2dp.c
> @@ -1577,6 +1577,7 @@ bool bt_a2dp_register(struct ipc *ipc, const bdaddr_t *addr, uint8_t mode)
> BT_IO_OPT_SOURCE_BDADDR, &adapter_addr,
> BT_IO_OPT_PSM, L2CAP_PSM_AVDTP,
> BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM,
> + BT_IO_OPT_MASTER, true,
> BT_IO_OPT_INVALID);
> if (!server) {
> error("Failed to listen on AVDTP channel: %s", err->message);
> --
> 1.9.2

Pushed, thanks.


--
Luiz Augusto von Dentz

2014-04-16 23:50:36

by Andrzej Kaczmarek

[permalink] [raw]
Subject: [PATCH v2 4/6] android/hal-audio: Resync audio when lagging too much

In case we're more than 100ms behind actual audio clock, we start to
skip writing data until we're back in sync. Delay value of 100ms is
what PulseAudio use for the same purpose.
---
android/hal-audio.c | 38 +++++++++++++++++++++++++++++---------
1 file changed, 29 insertions(+), 9 deletions(-)

diff --git a/android/hal-audio.c b/android/hal-audio.c
index a261871..3ff3b9d 100644
--- a/android/hal-audio.c
+++ b/android/hal-audio.c
@@ -45,6 +45,8 @@

#define MAX_FRAMES_IN_PAYLOAD 15

+#define MAX_DELAY 100000 /* 100ms */
+
static const uint8_t a2dp_src_uuid[] = {
0x00, 0x00, 0x11, 0x0a, 0x00, 0x00, 0x10, 0x00,
0x80, 0x00, 0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb };
@@ -236,6 +238,8 @@ struct audio_endpoint {
uint16_t seq;
uint32_t samples;
struct timespec start;
+
+ bool resync;
};

static struct audio_endpoint audio_endpoints[MAX_AUDIO_ENDPOINTS];
@@ -914,6 +918,7 @@ static bool resume_endpoint(struct audio_endpoint *ep)
return false;

ep->samples = 0;
+ ep->resync = false;

return true;
}
@@ -1031,10 +1036,15 @@ static bool write_data(struct a2dp_stream_out *out, const void *buffer,
audio_sent = ep->samples * 1000000ll / out->cfg.rate;
audio_passed = timespec_diff_us(&current, &ep->start);

- /* if we're ahead of stream then wait for next write point */
+ /* if we're ahead of stream then wait for next write point
+ * if we're lagging more than 100ms then stop writing and just
+ * skip data until we're back in sync
+ */
if (audio_sent > audio_passed) {
struct timespec anchor;

+ ep->resync = false;
+
timespec_add(&ep->start, audio_sent, &anchor);

while (true) {
@@ -1051,18 +1061,28 @@ static bool write_data(struct a2dp_stream_out *out, const void *buffer,
return false;
}
}
- }
+ } else if (!ep->resync) {
+ uint64_t diff = audio_passed - audio_sent;

- /* wait some time for socket to be ready for write,
- * but we'll just skip writing data if timeout occurs
- */
- if (!wait_for_endpoint(ep, &do_write))
- return false;
+ if (diff > MAX_DELAY) {
+ warn("lag is %jums, resyncing", diff / 1000);
+ ep->resync = true;
+ }
+ }

- if (do_write)
- if (!write_to_endpoint(ep, written))
+ /* in resync mode we'll just drop mediapackets */
+ if (!ep->resync) {
+ /* wait some time for socket to be ready for write,
+ * but we'll just skip writing data if timeout occurs
+ */
+ if (!wait_for_endpoint(ep, &do_write))
return false;

+ if (do_write)
+ if (!write_to_endpoint(ep, written))
+ return false;
+ }
+
/* AudioFlinger provides 16bit PCM, so sample size is 2 bytes
* multiplied by number of channels. Number of channels is
* simply number of bits set in channels mask.
--
1.9.2


2014-04-16 23:50:37

by Andrzej Kaczmarek

[permalink] [raw]
Subject: [PATCH v2 5/6] android/hal-audio: Add support to control audio quality

This patch adds new codec abstraction call which can be used to adjust
audio quality while playing. As for now we can either decrease quality
or restore default one.

It's up to actual codec capabilities and implementation how this can be
handled. In case of SBC bitpool is decreased by fixed amount (5) until
min allowable value is reached (33) - the same values are used in
PulseAudio.
---
android/hal-audio.c | 83 ++++++++++++++++++++++++++++++++++++++++++-----------
1 file changed, 67 insertions(+), 16 deletions(-)

diff --git a/android/hal-audio.c b/android/hal-audio.c
index 3ff3b9d..e25aa07 100644
--- a/android/hal-audio.c
+++ b/android/hal-audio.c
@@ -47,6 +47,9 @@

#define MAX_DELAY 100000 /* 100ms */

+#define SBC_QUALITY_MIN_BITPOOL 33
+#define SBC_QUALITY_STEP 5
+
static const uint8_t a2dp_src_uuid[] = {
0x00, 0x00, 0x11, 0x0a, 0x00, 0x00, 0x10, 0x00,
0x80, 0x00, 0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb };
@@ -128,6 +131,8 @@ struct sbc_data {

sbc_t enc;

+ uint16_t payload_len;
+
size_t in_frame_len;
size_t in_buf_size;

@@ -189,6 +194,10 @@ static size_t sbc_get_mediapacket_duration(void *codec_data);
static ssize_t sbc_encode_mediapacket(void *codec_data, const uint8_t *buffer,
size_t len, struct media_packet *mp,
size_t mp_data_len, size_t *written);
+static bool sbc_update_qos(void *codec_data, uint8_t op);
+
+#define QOS_POLICY_DEFAULT 0x00
+#define QOS_POLICY_DECREASE 0x01

struct audio_codec {
uint8_t type;
@@ -205,6 +214,7 @@ struct audio_codec {
ssize_t (*encode_mediapacket) (void *codec_data, const uint8_t *buffer,
size_t len, struct media_packet *mp,
size_t mp_data_len, size_t *written);
+ bool (*update_qos) (void *codec_data, uint8_t op);
};

static const struct audio_codec audio_codecs[] = {
@@ -219,6 +229,7 @@ static const struct audio_codec audio_codecs[] = {
.get_buffer_size = sbc_get_buffer_size,
.get_mediapacket_duration = sbc_get_mediapacket_duration,
.encode_mediapacket = sbc_encode_mediapacket,
+ .update_qos = sbc_update_qos,
}
};

@@ -421,14 +432,33 @@ static void sbc_init_encoder(struct sbc_data *sbc_data)
in->min_bitpool, in->max_bitpool);
}

-static int sbc_codec_init(struct audio_preset *preset, uint16_t payload_len,
- void **codec_data)
+static void sbc_codec_calculate(struct sbc_data *sbc_data)
{
- struct sbc_data *sbc_data;
size_t in_frame_len;
size_t out_frame_len;
size_t num_frames;

+ in_frame_len = sbc_get_codesize(&sbc_data->enc);
+ out_frame_len = sbc_get_frame_length(&sbc_data->enc);
+ num_frames = sbc_data->payload_len / out_frame_len;
+
+ sbc_data->in_frame_len = in_frame_len;
+ sbc_data->in_buf_size = num_frames * in_frame_len;
+
+ sbc_data->out_frame_len = out_frame_len;
+
+ sbc_data->frame_duration = sbc_get_frame_duration(&sbc_data->enc);
+ sbc_data->frames_per_packet = num_frames;
+
+ DBG("in_frame_len=%zu out_frame_len=%zu frames_per_packet=%zu",
+ in_frame_len, out_frame_len, num_frames);
+}
+
+static int sbc_codec_init(struct audio_preset *preset, uint16_t payload_len,
+ void **codec_data)
+{
+ struct sbc_data *sbc_data;
+
if (preset->len != sizeof(a2dp_sbc_t)) {
error("SBC: preset size mismatch");
return AUDIO_STATUS_FAILED;
@@ -442,20 +472,9 @@ static int sbc_codec_init(struct audio_preset *preset, uint16_t payload_len,

sbc_init_encoder(sbc_data);

- in_frame_len = sbc_get_codesize(&sbc_data->enc);
- out_frame_len = sbc_get_frame_length(&sbc_data->enc);
- num_frames = payload_len / out_frame_len;
-
- sbc_data->in_frame_len = in_frame_len;
- sbc_data->in_buf_size = num_frames * in_frame_len;
-
- sbc_data->out_frame_len = out_frame_len;
-
- sbc_data->frame_duration = sbc_get_frame_duration(&sbc_data->enc);
- sbc_data->frames_per_packet = num_frames;
+ sbc_data->payload_len = payload_len;

- DBG("in_frame_len=%zu out_frame_len=%zu frames_per_packet=%zu",
- in_frame_len, out_frame_len, num_frames);
+ sbc_codec_calculate(sbc_data);

*codec_data = sbc_data;

@@ -550,6 +569,38 @@ static ssize_t sbc_encode_mediapacket(void *codec_data, const uint8_t *buffer,
return consumed;
}

+static bool sbc_update_qos(void *codec_data, uint8_t op)
+{
+ struct sbc_data *sbc_data = (struct sbc_data *) codec_data;
+ uint8_t curr_bitpool = sbc_data->enc.bitpool;
+ uint8_t new_bitpool = curr_bitpool;
+
+ switch (op) {
+ case QOS_POLICY_DEFAULT:
+ new_bitpool = sbc_data->sbc.max_bitpool;
+ break;
+
+ case QOS_POLICY_DECREASE:
+ if (curr_bitpool > SBC_QUALITY_MIN_BITPOOL) {
+ new_bitpool = curr_bitpool - SBC_QUALITY_STEP;
+ if (new_bitpool < SBC_QUALITY_MIN_BITPOOL)
+ new_bitpool = SBC_QUALITY_MIN_BITPOOL;
+ }
+ break;
+ }
+
+ if (new_bitpool == curr_bitpool)
+ return false;
+
+ sbc_data->enc.bitpool = new_bitpool;
+
+ sbc_codec_calculate(sbc_data);
+
+ info("SBC: bitpool chaged: %d -> %d", curr_bitpool, new_bitpool);
+
+ return true;
+}
+
static int audio_ipc_cmd(uint8_t service_id, uint8_t opcode, uint16_t len,
void *param, size_t *rsp_len, void *rsp, int *fd)
{
--
1.9.2


2014-04-16 23:50:38

by Andrzej Kaczmarek

[permalink] [raw]
Subject: [PATCH v2 6/6] android/hal-audio: Adjust audio quality automatically

In case we go out of sync with audio clock and start skipping data to
catch up, we also decrease audio quality to have better change to avoid
going out of sync in future, i.e. due to poor radio link quality.

Quality is reset to default value on stream resume.
---
android/hal-audio.c | 5 +++++
1 file changed, 5 insertions(+)

diff --git a/android/hal-audio.c b/android/hal-audio.c
index e25aa07..f60d8cf 100644
--- a/android/hal-audio.c
+++ b/android/hal-audio.c
@@ -971,6 +971,8 @@ static bool resume_endpoint(struct audio_endpoint *ep)
ep->samples = 0;
ep->resync = false;

+ ep->codec->update_qos(ep->codec_data, QOS_POLICY_DEFAULT);
+
return true;
}

@@ -1117,6 +1119,9 @@ static bool write_data(struct a2dp_stream_out *out, const void *buffer,

if (diff > MAX_DELAY) {
warn("lag is %jums, resyncing", diff / 1000);
+
+ ep->codec->update_qos(ep->codec_data,
+ QOS_POLICY_DECREASE);
ep->resync = true;
}
}
--
1.9.2


2014-04-16 23:50:35

by Andrzej Kaczmarek

[permalink] [raw]
Subject: [PATCH v2 3/6] android/hal-audio: Make audio socket non-blocking

This patch makes writing to audio socket non-blocking since there's no
point in waiting indefinitely to write some data. Instead, we wait no
more than 500ms for socket to be ready and just skip packet otherwise.
---
android/hal-audio.c | 191 +++++++++++++++++++++++++++++++++++-----------------
1 file changed, 130 insertions(+), 61 deletions(-)

diff --git a/android/hal-audio.c b/android/hal-audio.c
index 992fdf7..a261871 100644
--- a/android/hal-audio.c
+++ b/android/hal-audio.c
@@ -147,6 +147,27 @@ static void timespec_add(struct timespec *base, uint64_t time_us,
}
}

+static void timespec_diff(struct timespec *a, struct timespec *b,
+ struct timespec *res)
+{
+ res->tv_sec = a->tv_sec - b->tv_sec;
+ res->tv_nsec = a->tv_nsec - b->tv_nsec;
+
+ if (res->tv_nsec < 0) {
+ res->tv_sec--;
+ res->tv_nsec += 1000000000; /* 1sec */
+ }
+}
+
+static uint64_t timespec_diff_us(struct timespec *a, struct timespec *b)
+{
+ struct timespec res;
+
+ timespec_diff(a, b, &res);
+
+ return res.tv_sec * 1000000ll + res.tv_nsec / 1000ll;
+}
+
#if defined(ANDROID)
/* Bionic does not have clock_nanosleep() prototype in time.h even though
* it provides its implementation.
@@ -830,26 +851,6 @@ static void unregister_endpoints(void)
}
}

-static int set_blocking(int fd)
-{
- int flags;
-
- flags = fcntl(fd, F_GETFL, 0);
- if (flags < 0) {
- int err = -errno;
- error("fcntl(F_GETFL): %s (%d)", strerror(-err), -err);
- return err;
- }
-
- if (fcntl(fd, F_SETFL, flags & ~O_NONBLOCK) < 0) {
- int err = -errno;
- error("fcntl(F_SETFL): %s (%d)", strerror(-err), -err);
- return err;
- }
-
- return 0;
-}
-
static bool open_endpoint(struct audio_endpoint *ep,
struct audio_input_config *cfg)
{
@@ -863,9 +864,6 @@ static bool open_endpoint(struct audio_endpoint *ep,
AUDIO_STATUS_SUCCESS)
return false;

- if (set_blocking(fd) < 0)
- goto failed;
-
DBG("mtu=%u", mtu);

payload_len = mtu - sizeof(*ep->mp);
@@ -915,7 +913,6 @@ static bool resume_endpoint(struct audio_endpoint *ep)
if (ipc_resume_stream_cmd(ep->id) != AUDIO_STATUS_SUCCESS)
return false;

- clock_gettime(CLOCK_MONOTONIC, &ep->start);
ep->samples = 0;

return true;
@@ -936,6 +933,64 @@ static void downmix_to_mono(struct a2dp_stream_out *out, const uint8_t *buffer,
}
}

+static bool wait_for_endpoint(struct audio_endpoint *ep, bool *writable)
+{
+ int ret;
+
+ while (true) {
+ struct pollfd pollfd;
+
+ pollfd.fd = ep->fd;
+ pollfd.events = POLLOUT;
+ pollfd.revents = 0;
+
+ ret = poll(&pollfd, 1, 500);
+
+ if (ret >= 0) {
+ *writable = !!(pollfd.revents & POLLOUT);
+ break;
+ }
+
+ if (errno != EINTR) {
+ ret = errno;
+ error("poll failed (%d)", ret);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool write_to_endpoint(struct audio_endpoint *ep, size_t bytes)
+{
+ struct media_packet *mp = (struct media_packet *) ep->mp;
+ int ret;
+
+ while (true) {
+ ret = write(ep->fd, mp, sizeof(*mp) + bytes);
+
+ if (ret >= 0)
+ break;
+
+ /* this should not happen so let's issue warning, but do not
+ * fail, we can try to write next packet
+ */
+ if (errno == EAGAIN) {
+ ret = errno;
+ warn("write failed (%d)", ret);
+ break;
+ }
+
+ if (errno != EINTR) {
+ ret = errno;
+ error("write failed (%d)", ret);
+ return false;
+ }
+ }
+
+ return true;
+}
+
static bool write_data(struct a2dp_stream_out *out, const void *buffer,
size_t bytes)
{
@@ -949,58 +1004,72 @@ static bool write_data(struct a2dp_stream_out *out, const void *buffer,
ssize_t read;
uint32_t samples;
int ret;
- uint64_t time_us;
- struct timespec anchor;
+ struct timespec current;
+ uint64_t audio_sent, audio_passed;
+ bool do_write = false;

- time_us = ep->samples * 1000000ll / out->cfg.rate;
+ /* prepare media packet in advance so we don't waste time after
+ * wakeup
+ */
+ mp->hdr.sequence_number = htons(ep->seq++);
+ mp->hdr.timestamp = htonl(ep->samples);
+ read = ep->codec->encode_mediapacket(ep->codec_data,
+ buffer + consumed,
+ bytes - consumed, mp,
+ free_space, &written);

- timespec_add(&ep->start, time_us, &anchor);
+ /* not much we can do here, let's just ignore remaining
+ * data and continue
+ */
+ if (read <= 0)
+ return true;

- while (true) {
- ret = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME,
- &anchor, NULL);
+ /* calculate where are we and where we should be */
+ clock_gettime(CLOCK_MONOTONIC, &current);
+ if (!ep->samples)
+ memcpy(&ep->start, &current, sizeof(ep->start));
+ audio_sent = ep->samples * 1000000ll / out->cfg.rate;
+ audio_passed = timespec_diff_us(&current, &ep->start);

- if (!ret)
- break;
+ /* if we're ahead of stream then wait for next write point */
+ if (audio_sent > audio_passed) {
+ struct timespec anchor;

- if (ret != EINTR) {
- error("clock_nanosleep failed (%d)", ret);
- return false;
+ timespec_add(&ep->start, audio_sent, &anchor);
+
+ while (true) {
+ ret = clock_nanosleep(CLOCK_MONOTONIC,
+ TIMER_ABSTIME, &anchor,
+ NULL);
+
+ if (!ret)
+ break;
+
+ if (ret != EINTR) {
+ error("clock_nanosleep failed (%d)",
+ ret);
+ return false;
+ }
}
}

- read = ep->codec->encode_mediapacket(ep->codec_data,
- buffer + consumed,
- bytes - consumed, mp,
- free_space, &written);
-
- /* This is non-fatal and we can just assume buffer was processed
- * properly and wait for next one.
+ /* wait some time for socket to be ready for write,
+ * but we'll just skip writing data if timeout occurs
*/
- if (read <= 0)
- return true;
-
- consumed += read;
+ if (!wait_for_endpoint(ep, &do_write))
+ return false;

- mp->hdr.sequence_number = htons(ep->seq++);
- mp->hdr.timestamp = htonl(ep->samples);
+ if (do_write)
+ if (!write_to_endpoint(ep, written))
+ return false;

/* AudioFlinger provides 16bit PCM, so sample size is 2 bytes
- * multipled by number of channels. Number of channels is simply
- * number of bits set in channels mask.
+ * multiplied by number of channels. Number of channels is
+ * simply number of bits set in channels mask.
*/
samples = read / (2 * popcount(out->cfg.channels));
ep->samples += samples;
-
- while (true) {
- ret = write(ep->fd, mp, sizeof(*mp) + written);
-
- if (ret >= 0)
- break;
-
- if (errno != EINTR)
- return false;
- }
+ consumed += read;
}

return true;
--
1.9.2


2014-04-16 23:50:34

by Andrzej Kaczmarek

[permalink] [raw]
Subject: [PATCH v2 2/6] android/hal-audio: Add resume_endpoint helper

---
android/hal-audio.c | 16 ++++++++++++----
1 file changed, 12 insertions(+), 4 deletions(-)

diff --git a/android/hal-audio.c b/android/hal-audio.c
index 23eb6b8..992fdf7 100644
--- a/android/hal-audio.c
+++ b/android/hal-audio.c
@@ -910,6 +910,17 @@ static void close_endpoint(struct audio_endpoint *ep)
ep->codec_data = NULL;
}

+static bool resume_endpoint(struct audio_endpoint *ep)
+{
+ if (ipc_resume_stream_cmd(ep->id) != AUDIO_STATUS_SUCCESS)
+ return false;
+
+ clock_gettime(CLOCK_MONOTONIC, &ep->start);
+ ep->samples = 0;
+
+ return true;
+}
+
static void downmix_to_mono(struct a2dp_stream_out *out, const uint8_t *buffer,
size_t bytes)
{
@@ -1010,12 +1021,9 @@ static ssize_t out_write(struct audio_stream_out *stream, const void *buffer,
if (out->audio_state == AUDIO_A2DP_STATE_STANDBY) {
DBG("stream in standby, auto-start");

- if (ipc_resume_stream_cmd(out->ep->id) != AUDIO_STATUS_SUCCESS)
+ if (!resume_endpoint(out->ep))
return -1;

- clock_gettime(CLOCK_MONOTONIC, &out->ep->start);
- out->ep->samples = 0;
-
out->audio_state = AUDIO_A2DP_STATE_STARTED;
}

--
1.9.2