C++程序  |  1013行  |  30.71 KB

/*
 *  Copyright (C) 2008-2011 The Android Open Source Project
 *
 *  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
 *
 */

#define LOG_TAG "a2dp_audio_hw"
//#define LOG_NDEBUG 0

#include <errno.h>
#include <pthread.h>
#include <stdint.h>
#include <sys/time.h>

#include <cutils/log.h>
#include <cutils/str_parms.h>

#include <hardware/hardware.h>
#include <system/audio.h>
#include <hardware/audio.h>

#include <hardware_legacy/power.h>

#include "liba2dp.h"

/* for backward compatibility with older audio framework */
#ifndef AUDIO_PARAMETER_A2DP_SINK_ADDRESS
    #define AUDIO_PARAMETER_A2DP_SINK_ADDRESS "a2dp_sink_address"
#endif

#define A2DP_WAKE_LOCK_NAME            "A2dpOutputStream"
#define MAX_WRITE_RETRIES              5

#define A2DP_SUSPENDED_PARM            "A2dpSuspended"
#define BLUETOOOTH_ENABLED_PARM        "bluetooth_enabled"


/* number of periods in pcm buffer (one period corresponds to buffer size reported to audio flinger
 * by out_get_buffer_size() */
#define BUF_NUM_PERIODS 6
/* maximum time allowed by out_standby_stream_locked() for 2dp_write() to complete */
#define BUF_WRITE_COMPLETION_TIMEOUT_MS 5000
/* maximum time allowed by out_write() for frames to be available in in write thread buffer */
#define BUF_WRITE_AVAILABILITY_TIMEOUT_MS 5000
/* maximum number of attempts to wait for a write completion in out_standby_stream_locked() */
#define MAX_WRITE_COMPLETION_ATTEMPTS 5

/* NOTE: there are 2 mutexes used by the a2dp output stream.
 *  - lock: protects all calls to a2dp lib functions (a2dp_stop(), a2dp_cleanup()...).
 *    One exception is a2dp_write() which is also protected by the flag write_busy. This is because
 *    out_write() cannot block waiting for a2dp_write() to complete because this function
 *    can sleep to throttle the A2DP bit rate.
 *    This flag is always set/reset and tested with "lock" mutex held.
 *  - buf_lock: protects access to pcm buffer read and write indexes.
 *
 *  The locking order is always as follows:
 *      buf_lock -> lock
 *
 * If you need to hold the adev_a2dp->lock AND the astream_out->lock or astream_out->buf_lock,
 * you MUST take adev_a2dp lock first!!
 */

struct astream_out;
struct adev_a2dp {
    struct audio_hw_device  device;

    audio_mode_t            mode;
    bool                    bt_enabled;
    bool                    suspended;

    pthread_mutex_t         lock;

    struct astream_out      *output;
};

struct astream_out {
    struct audio_stream_out stream;

    uint32_t                sample_rate;
    size_t                  buffer_size;
    uint32_t                channels;
    audio_format_t          format;

    int                     fd;
    bool                    standby;
    int                     start_count;
    int                     retry_count;
    void*                   data;

    pthread_mutex_t         lock;   /* see NOTE on mutex locking order above */

    audio_devices_t         device;
    uint64_t                 last_write_time;
    uint32_t                buffer_duration_us;

    bool                    bt_enabled;
    bool                    suspended;
    char                    a2dp_addr[20];

    uint32_t *buf;              /* pcm buffer between audioflinger thread and write thread*/
    size_t buf_size;            /* size of pcm buffer in frames */
    size_t buf_rd_idx;          /* read index in pcm buffer, in frames*/
    size_t buf_wr_idx;          /* write index in pcm buffer, in frames */
    size_t buf_frames_ready;    /* number of frames ready for writing to a2dp sink */
    pthread_mutex_t buf_lock;   /* mutex protecting read and write indexes */
                                /* see NOTE on mutex locking order above */
    pthread_cond_t buf_cond;    /* condition signaling data write/read to/from pcm buffer */
    pthread_t buf_thread;       /* thread reading data from buffer and writing to a2dp sink*/
    bool buf_thread_exit;       /* flag requesting write thread exit */
    bool write_busy;            /* indicates that a write to a2dp sink is in progress and that
                                   standby must wait for this flag to be cleared by write thread */
    pthread_cond_t write_cond;  /* condition associated with write_busy flag */
};

static uint64_t system_time(void)
{
    struct timespec t;

    t.tv_sec = t.tv_nsec = 0;
    clock_gettime(CLOCK_MONOTONIC, &t);

    return t.tv_sec*1000000000LL + t.tv_nsec;
}

/** audio_stream_out implementation **/
static uint32_t out_get_sample_rate(const struct audio_stream *stream)
{
    const struct astream_out *out = (const struct astream_out *)stream;
    return out->sample_rate;
}

static int out_set_sample_rate(struct audio_stream *stream, uint32_t rate)
{
    struct astream_out *out = (struct astream_out *)stream;

    ALOGE("(%s:%d) %s: Implement me!", __FILE__, __LINE__, __func__);
    return 0;
}

static size_t out_get_buffer_size(const struct audio_stream *stream)
{
    const struct astream_out *out = (const struct astream_out *)stream;
    return out->buffer_size;
}

static uint32_t out_get_channels(const struct audio_stream *stream)
{
    const struct astream_out *out = (const struct astream_out *)stream;
    return out->channels;
}

static audio_format_t out_get_format(const struct audio_stream *stream)
{
    const struct astream_out *out = (const struct astream_out *)stream;
    return out->format;
}

static audio_format_t out_set_format(struct audio_stream *stream, audio_format_t format)
{
    struct astream_out *out = (struct astream_out *)stream;
    ALOGE("(%s:%d) %s: Implement me!", __FILE__, __LINE__, __func__);
    return 0;
}

static int out_dump(const struct audio_stream *stream, int fd)
{
    return 0;
}

static uint32_t out_get_latency(const struct audio_stream_out *stream)
{
    const struct astream_out *out = (const struct astream_out *)stream;

    return ((out->buffer_duration_us * BUF_NUM_PERIODS) / 1000) + 200;
}

static int out_set_volume(struct audio_stream_out *stream, float left,
                          float right)
{
    return -ENOSYS;
}

static int out_get_render_position(const struct audio_stream_out *stream,
                                   uint32_t *dsp_frames)
{
    return -ENOSYS;
}

static int _out_init_locked(struct astream_out *out, const char *addr)
{
    int ret;

    if (out->data)
        return 0;

    /* XXX: shouldn't this use the sample_rate/channel_count from 'out'? */
    ret = a2dp_init(44100, 2, &out->data);
    if (ret < 0) {
        ALOGE("a2dp_init failed err: %d\n", ret);
        out->data = NULL;
        return ret;
    }

    /* XXX: is this even necessary? */
    if (addr)
        strlcpy(out->a2dp_addr, addr, sizeof(out->a2dp_addr));
    a2dp_set_sink(out->data, out->a2dp_addr);

    return 0;
}

static bool _out_validate_parms(struct astream_out *out, audio_format_t format,
                                uint32_t chans, uint32_t rate)
{
    if ((format && (format != out->format)) ||
        (chans && (chans != out->channels)) ||
        (rate && (rate != out->sample_rate)))
        return false;
    return true;
}

static int out_standby_stream_locked(struct astream_out *out)
{
    int ret = 0;
    int attempts = MAX_WRITE_COMPLETION_ATTEMPTS;

    if (out->standby || !out->data)
        return 0;

    out->standby = true;
    /* wait for write completion if needed */
    while (out->write_busy && attempts--) {
        ret = pthread_cond_timeout_np(&out->write_cond,
                                &out->lock,
                                BUF_WRITE_COMPLETION_TIMEOUT_MS);
        ALOGE_IF(ret != 0, "out_standby_stream_locked() wait cond error %d", ret);
    }
    ALOGE_IF(attempts == 0, "out_standby_stream_locked() a2dp_write() would not stop!!!");

    ALOGV_IF(!out->bt_enabled, "Standby skip stop: enabled %d", out->bt_enabled);
    if (out->bt_enabled) {
        ret = a2dp_stop(out->data);
    }
    release_wake_lock(A2DP_WAKE_LOCK_NAME);

    return ret;
}

static int out_close_stream_locked(struct astream_out *out)
{
    out_standby_stream_locked(out);

    if (out->data) {
        ALOGV("%s: calling a2dp_cleanup()", __func__);
        a2dp_cleanup(out->data);
        out->data = NULL;
    }

    return 0;
}

static int out_standby(struct audio_stream *stream)
{
    struct astream_out *out = (struct astream_out *)stream;

    pthread_mutex_lock(&out->lock);
    out_standby_stream_locked(out);
    pthread_mutex_unlock(&out->lock);

    return 0;
}

static int out_set_parameters(struct audio_stream *stream, const char *kvpairs)
{
    struct astream_out *out = (struct astream_out *)stream;
    struct str_parms *parms;
    char *str;
    char value[32];
    int ret;

    parms = str_parms_create_str(kvpairs);

    pthread_mutex_lock(&out->lock);

    ret = str_parms_get_str(parms, AUDIO_PARAMETER_A2DP_SINK_ADDRESS, value, sizeof(value));
    if (ret >= 0) {
        /* strlen(00:00:00:00:00:00) == 17 */
        if (strlen(value) == 17) {
            strlcpy(out->a2dp_addr, value, sizeof(out->a2dp_addr));
            if (out->data)
                a2dp_set_sink(out->data, out->a2dp_addr);
        } else
            ret = -EINVAL;
    }

    pthread_mutex_unlock(&out->lock);
    str_parms_destroy(parms);
    return ret;
}

static audio_devices_t out_get_device(const struct audio_stream *stream)
{
    const struct astream_out *out = (const struct astream_out *)stream;
    return out->device;
}


static int out_set_device(struct audio_stream *stream, audio_devices_t device)
{
    struct astream_out *out = (struct astream_out *)stream;

    if (!audio_is_a2dp_device(device))
        return -EINVAL;

    /* XXX: if out->device ever starts getting used for anything, need to
     * grab the out->lock */
    out->device = device;
    return 0;
}

static char * out_get_parameters(const struct audio_stream *stream,
                                 const char *keys)
{
    struct astream_out *out = (struct astream_out *)stream;
    struct str_parms *parms;
    struct str_parms *out_parms;
    char *str;
    char value[20];
    int ret;

    parms = str_parms_create_str(keys);
    out_parms = str_parms_create();

    pthread_mutex_lock(&out->lock);

    ret = str_parms_get_str(parms, AUDIO_PARAMETER_A2DP_SINK_ADDRESS, value, sizeof(value));
    if (ret >= 0)
        str_parms_add_str(out_parms, AUDIO_PARAMETER_A2DP_SINK_ADDRESS, out->a2dp_addr);

    pthread_mutex_unlock(&out->lock);

    str = str_parms_to_str(out_parms);
    str_parms_destroy(out_parms);
    str_parms_destroy(parms);

    return str;
}

size_t _out_frames_available_locked(struct astream_out *out)
{

    size_t frames = out->buf_size - out->buf_frames_ready;
    if (frames > out->buf_size - out->buf_wr_idx) {
        frames = out->buf_size - out->buf_wr_idx;
    }
    return frames;
}

size_t _out_frames_ready_locked(struct astream_out *out)
{
    size_t frames = out->buf_frames_ready;

    if (frames > out->buf_size - out->buf_rd_idx) {
        frames = out->buf_size - out->buf_rd_idx;
    }
    return frames;
}

void _out_inc_wr_idx_locked(struct astream_out *out, size_t frames)
{
    out->buf_wr_idx += frames;
    out->buf_frames_ready += frames;
    if (out->buf_wr_idx == out->buf_size) {
        out->buf_wr_idx = 0;
    }
    pthread_cond_signal(&out->buf_cond);
}

void _out_inc_rd_idx_locked(struct astream_out *out, size_t frames)
{
    out->buf_rd_idx += frames;
    out->buf_frames_ready -= frames;
    if (out->buf_rd_idx == out->buf_size) {
        out->buf_rd_idx = 0;
    }
    pthread_cond_signal(&out->buf_cond);
}

static ssize_t out_write(struct audio_stream_out *stream, const void* buffer,
                         size_t bytes)
{
    struct astream_out *out = (struct astream_out *)stream;
    int ret;
    size_t frames_total = bytes / sizeof(uint32_t); // always stereo 16 bit
    uint32_t *buf = (uint32_t *)buffer;
    size_t frames_written = 0;

    pthread_mutex_lock(&out->buf_lock);
    pthread_mutex_lock(&out->lock);
    if (!out->bt_enabled || out->suspended) {
        ALOGV("a2dp %s: bluetooth disabled bt_en %d, suspended %d",
             out->bt_enabled, out->suspended);
        ret = -1;
        goto err_bt_disabled;
    }

    if (out->standby) {
        acquire_wake_lock(PARTIAL_WAKE_LOCK, A2DP_WAKE_LOCK_NAME);
        out->standby = false;
        out->last_write_time = system_time();
        out->buf_rd_idx = 0;
        out->buf_wr_idx = 0;
        out->buf_frames_ready = 0;
    }

    ret = _out_init_locked(out, NULL);
    if (ret < 0) {
        goto err_init;
    }

    pthread_mutex_unlock(&out->lock);

    while (frames_written < frames_total) {
        size_t frames = _out_frames_available_locked(out);
        if (frames == 0) {
            int ret = pthread_cond_timeout_np(&out->buf_cond,
                                              &out->buf_lock,
                                              BUF_WRITE_AVAILABILITY_TIMEOUT_MS);
            if (ret != 0) {
                pthread_mutex_lock(&out->lock);
                goto err_write;
            }
            frames  = _out_frames_available_locked(out);
        }
        if (frames > frames_total - frames_written) {
            frames = frames_total - frames_written;
        }
        memcpy(out->buf + out->buf_wr_idx, buf + frames_written, frames * sizeof(uint32_t));
        frames_written += frames;
        _out_inc_wr_idx_locked(out, frames);
        pthread_mutex_lock(&out->lock);
        if (out->standby) {
            goto err_write;
        }
        pthread_mutex_unlock(&out->lock);
    }
    pthread_mutex_unlock(&out->buf_lock);

    return bytes;

/* out->lock must be locked and out->buf_lock unlocked when jumping here */
err_write:
err_init:
err_bt_disabled:
    pthread_mutex_unlock(&out->buf_lock);
    ALOGV("!!!! write error");
    out_standby_stream_locked(out);
    pthread_mutex_unlock(&out->lock);

    /* XXX: simulate audio output timing in case of error?!?! */
    usleep(out->buffer_duration_us);
    return ret;
}


static void *_out_buf_thread_func(void *context)
{
    struct astream_out *out = (struct astream_out *)context;

    pthread_mutex_lock(&out->buf_lock);

    while(!out->buf_thread_exit) {
        size_t frames;

        frames = _out_frames_ready_locked(out);
        while (frames && !out->buf_thread_exit) {
            int retries = MAX_WRITE_RETRIES;
            uint64_t now;
            uint32_t elapsed_us;

            while (frames > 0 && !out->buf_thread_exit) {
                int ret;
                uint32_t buffer_duration_us;
                /* PCM format is always 16bit stereo */
                size_t bytes = frames * sizeof(uint32_t);
                if (bytes > out->buffer_size) {
                    bytes = out->buffer_size;
                }

                pthread_mutex_lock(&out->lock);
                if (out->standby) {
                    /* abort and clear all pending frames if standby requested */
                    pthread_mutex_unlock(&out->lock);
                    frames = _out_frames_ready_locked(out);
                    _out_inc_rd_idx_locked(out, frames);
                    goto wait;
                }
                /* indicate to out_standby_stream_locked() that a2dp_write() is active */
                out->write_busy = true;
                pthread_mutex_unlock(&out->lock);
                pthread_mutex_unlock(&out->buf_lock);

                ret = a2dp_write(out->data, out->buf + out->buf_rd_idx, bytes);

                /* clear write_busy condition */
                pthread_mutex_lock(&out->buf_lock);
                pthread_mutex_lock(&out->lock);
                out->write_busy = false;
                pthread_cond_signal(&out->write_cond);
                pthread_mutex_unlock(&out->lock);

                if (ret < 0) {
                    ALOGE("%s: a2dp_write failed (%d)\n", __func__, ret);
                    /* skip pending frames in case of write error */
                    _out_inc_rd_idx_locked(out, frames);
                    break;
                } else if (ret == 0) {
                    if (retries-- == 0) {
                        /* skip pending frames in case of multiple time out */
                        _out_inc_rd_idx_locked(out, frames);
                        break;
                    }
                    continue;
                }
                ret /= sizeof(uint32_t);
                _out_inc_rd_idx_locked(out, ret);
                frames -= ret;

                /* XXX: PLEASE FIX ME!!!! */

                /* if A2DP sink runs abnormally fast, sleep a little so that
                 * audioflinger mixer thread does no spin and starve other threads. */
                /* NOTE: It is likely that the A2DP headset is being disconnected */
                now = system_time();
                elapsed_us = (now - out->last_write_time) / 1000UL;
                buffer_duration_us = ((ret * 1000) / out->sample_rate) * 1000;

                if (elapsed_us < (buffer_duration_us / 4)) {
                    ALOGV("A2DP sink runs too fast");
                    usleep(buffer_duration_us - elapsed_us);
                }
                out->last_write_time = now;

            }
            frames = _out_frames_ready_locked(out);
        }
wait:
        if (!out->buf_thread_exit) {
            pthread_cond_wait(&out->buf_cond, &out->buf_lock);
        }
    }
    pthread_mutex_unlock(&out->buf_lock);
    return NULL;
}



static int out_add_audio_effect(const struct audio_stream *stream, effect_handle_t effect)
{
    return 0;
}

static int out_remove_audio_effect(const struct audio_stream *stream, effect_handle_t effect)
{
    return 0;
}

static int _out_bt_enable(struct astream_out *out, bool enable)
{
    int ret = 0;

    pthread_mutex_lock(&out->lock);
    out->bt_enabled = enable;
    if (!enable)
        ret = out_close_stream_locked(out);
    pthread_mutex_unlock(&out->lock);

    return ret;
}

static int _out_a2dp_suspend(struct astream_out *out, bool suspend)
{
    pthread_mutex_lock(&out->lock);
    out->suspended = suspend;
    out_standby_stream_locked(out);
    pthread_mutex_unlock(&out->lock);

    return 0;
}

#ifdef AUDIO_DEVICE_API_VERSION_1_0
static int adev_open_output_stream(struct audio_hw_device *dev,
                                   audio_io_handle_t handle,
                                   audio_devices_t devices,
                                   audio_output_flags_t flags,
                                   struct audio_config *config,
                                   struct audio_stream_out **stream_out)
#else
static int adev_open_output_stream(struct audio_hw_device *dev,
                                   uint32_t devices, audio_format_t *format,
                                   uint32_t *channels, uint32_t *sample_rate,
                                   struct audio_stream_out **stream_out)
#endif
{
    struct adev_a2dp *adev = (struct adev_a2dp *)dev;
    struct astream_out *out;
    int ret;

    pthread_mutex_lock(&adev->lock);

    /* one output stream at a time */
    if (adev->output) {
        ALOGV("output exists");
        ret = -EBUSY;
        goto err_output_exists;
    }

    out = calloc(1, sizeof(struct astream_out));
    if (!out) {
        ret = -ENOMEM;
        goto err_alloc;
    }

    pthread_mutex_init(&out->lock, NULL);

    out->stream.common.get_sample_rate = out_get_sample_rate;
    out->stream.common.set_sample_rate = out_set_sample_rate;
    out->stream.common.get_buffer_size = out_get_buffer_size;
    out->stream.common.get_channels = out_get_channels;
    out->stream.common.get_format = out_get_format;
    out->stream.common.set_format = out_set_format;
    out->stream.common.standby = out_standby;
    out->stream.common.dump = out_dump;
    out->stream.common.set_parameters = out_set_parameters;
    out->stream.common.get_parameters = out_get_parameters;
    out->stream.common.set_device = out_set_device;
    out->stream.common.get_device = out_get_device;
    out->stream.common.add_audio_effect = out_add_audio_effect;
    out->stream.common.remove_audio_effect = out_remove_audio_effect;
    out->stream.get_latency = out_get_latency;
    out->stream.set_volume = out_set_volume;
    out->stream.write = out_write;
    out->stream.get_render_position = out_get_render_position;

    out->sample_rate = 44100;
    out->buffer_size = 512 * 20;
    out->channels = AUDIO_CHANNEL_OUT_STEREO;
    out->format = AUDIO_FORMAT_PCM_16_BIT;

    out->fd = -1;
    out->device = devices;
    out->bt_enabled = adev->bt_enabled;
    out->suspended = adev->suspended;

    /* for now, buffer_duration_us is precalculated and never changed.
     * if the sample rate or the format ever changes on the fly, we'd have
     * to recalculate this */
    out->buffer_duration_us = ((out->buffer_size * 1000 ) /
                               audio_stream_frame_size(&out->stream.common) /
                               out->sample_rate) * 1000;
#ifdef AUDIO_DEVICE_API_VERSION_1_0
    if (!_out_validate_parms(out, config->format,
                             config->channel_mask,
                             config->sample_rate))
#else
    if (!_out_validate_parms(out, format ? *format : 0,
                             channels ? *channels : 0,
                             sample_rate ? *sample_rate : 0))
#endif
    {
        ALOGV("invalid parameters");
        ret = -EINVAL;
        goto err_validate_parms;
    }

    int err = pthread_create(&out->buf_thread, (const pthread_attr_t *) NULL, _out_buf_thread_func, out);
    if (err != 0) {
        goto err_validate_parms;
    }

    /* PCM format is always 16bit, stereo */
    out->buf_size = (out->buffer_size * BUF_NUM_PERIODS) / sizeof(int32_t);
    out->buf = (uint32_t *)malloc(out->buf_size * sizeof(int32_t));
    if (!out->buf) {
        goto err_validate_parms;
    }

    /* XXX: check return code? */
    if (adev->bt_enabled)
        _out_init_locked(out, "00:00:00:00:00:00");

    adev->output = out;

#ifdef AUDIO_DEVICE_API_VERSION_1_0
    config->format = out->format;
    config->channel_mask = out->channels;
    config->sample_rate = out->sample_rate;
#else
    if (format)
        *format = out->format;
    if (channels)
        *channels = out->channels;
    if (sample_rate)
        *sample_rate = out->sample_rate;
#endif
    pthread_mutex_unlock(&adev->lock);

    *stream_out = &out->stream;

    return 0;

err_validate_parms:
    free(out);
err_alloc:
err_output_exists:
    pthread_mutex_unlock(&adev->lock);
    *stream_out = NULL;
    return ret;
}

/* needs the adev->lock held */
static void adev_close_output_stream_locked(struct adev_a2dp *dev,
                                            struct astream_out *stream)
{
    struct adev_a2dp *adev = (struct adev_a2dp *)dev;
    struct astream_out *out = (struct astream_out *)stream;

    /* invalid stream? */
    if (!adev->output || adev->output != out) {
        ALOGE("%s: unknown stream %p (ours is %p)", __func__, out, adev->output);
        return;
    }

    pthread_mutex_lock(&out->lock);
    /* out_write() must not be executed from now on */
    out->bt_enabled = false;
    out_close_stream_locked(out);
    pthread_mutex_unlock(&out->lock);
    if (out->buf_thread) {
        pthread_mutex_lock(&out->buf_lock);
        out->buf_thread_exit = true;
        pthread_cond_broadcast(&out->buf_cond);
        pthread_mutex_unlock(&out->buf_lock);
        pthread_join(out->buf_thread, (void **) NULL);
        pthread_cond_destroy(&out->buf_cond);
        pthread_mutex_destroy(&out->buf_lock);
    }
    if (out->buf) {
        free(out->buf);
    }

    adev->output = NULL;
    free(out);
}

static void adev_close_output_stream(struct audio_hw_device *dev,
                                     struct audio_stream_out *stream)
{
    struct adev_a2dp *adev = (struct adev_a2dp *)dev;
    struct astream_out *out = (struct astream_out *)stream;

    pthread_mutex_lock(&adev->lock);
    adev_close_output_stream_locked(adev, out);
    pthread_mutex_unlock(&adev->lock);
}

static int adev_set_parameters(struct audio_hw_device *dev, const char *kvpairs)
{
    struct adev_a2dp *adev = (struct adev_a2dp *)dev;
    struct str_parms *parms;
    char *str;
    char value[8];
    int ret;

    parms = str_parms_create_str(kvpairs);

    pthread_mutex_lock(&adev->lock);

    ret = str_parms_get_str(parms, BLUETOOOTH_ENABLED_PARM, value,
                            sizeof(value));
    if (ret >= 0) {
        adev->bt_enabled = !strcmp(value, "true");
        if (adev->output)
            _out_bt_enable(adev->output, adev->bt_enabled);
    }

    ret = str_parms_get_str(parms, A2DP_SUSPENDED_PARM, value, sizeof(value));
    if (ret >= 0) {
        adev->suspended = !strcmp(value, "true");
        if (adev->output)
            _out_a2dp_suspend(adev->output, adev->suspended);
    }

    pthread_mutex_unlock(&adev->lock);

    str_parms_destroy(parms);

    return ret;
}

static char * adev_get_parameters(const struct audio_hw_device *dev,
                                  const char *keys)
{
    struct adev_a2dp *adev = (struct adev_a2dp *)dev;
    struct str_parms *parms;
    struct str_parms *out_parms;
    char *str;
    char value[8];
    int ret;

    parms = str_parms_create_str(keys);
    out_parms = str_parms_create();

    pthread_mutex_lock(&adev->lock);

    ret = str_parms_get_str(parms, BLUETOOOTH_ENABLED_PARM, value,
                            sizeof(value));
    if (ret >= 0)
        str_parms_add_str(out_parms, BLUETOOOTH_ENABLED_PARM,
                          adev->bt_enabled ? "true" : "false");

    ret = str_parms_get_str(parms, A2DP_SUSPENDED_PARM, value, sizeof(value));
    if (ret >= 0)
        str_parms_add_str(out_parms, A2DP_SUSPENDED_PARM,
                          adev->suspended ? "true" : "false");

    pthread_mutex_unlock(&adev->lock);

    str = str_parms_to_str(out_parms);
    str_parms_destroy(out_parms);
    str_parms_destroy(parms);

    return str;
}

static int adev_init_check(const struct audio_hw_device *dev)
{
    return 0;
}

static int adev_set_voice_volume(struct audio_hw_device *dev, float volume)
{
    return -ENOSYS;
}

static int adev_set_master_volume(struct audio_hw_device *dev, float volume)
{
    return -ENOSYS;
}

static int adev_set_mode(struct audio_hw_device *dev, audio_mode_t mode)
{
    /* TODO: do we care for the mode? */
    return 0;
}

static int adev_set_mic_mute(struct audio_hw_device *dev, bool state)
{
    return -ENOSYS;
}

static int adev_get_mic_mute(const struct audio_hw_device *dev, bool *state)
{
    return -ENOSYS;
}

#ifdef AUDIO_DEVICE_API_VERSION_1_0
static size_t adev_get_input_buffer_size(const struct audio_hw_device *dev,
                                         const struct audio_config *config)
#else
static size_t adev_get_input_buffer_size(const struct audio_hw_device *dev,
                                         uint32_t sample_rate, audio_format_t format,
                                         int channel_count)
#endif
{
    /* no input */
    return 0;
}

#ifdef AUDIO_DEVICE_API_VERSION_1_0
static int adev_open_input_stream(struct audio_hw_device *dev,
                                  audio_io_handle_t handle,
                                  audio_devices_t devices,
                                  struct audio_config *config,
                                  struct audio_stream_in **stream_in)
#else
static int adev_open_input_stream(struct audio_hw_device *dev, uint32_t devices,
                                  audio_format_t *format, uint32_t *channels,
                                  uint32_t *sample_rate,
                                  audio_in_acoustics_t acoustics,
                                  struct audio_stream_in **stream_in)
#endif
{
    return -ENOSYS;
}

static void adev_close_input_stream(struct audio_hw_device *dev,
                                   struct audio_stream_in *in)
{
    return;
}

static int adev_dump(const audio_hw_device_t *device, int fd)
{
    return 0;
}

static int adev_close(hw_device_t *device)
{
    struct adev_a2dp *adev = (struct adev_a2dp *)device;

    pthread_mutex_lock(&adev->lock);
    if (adev->output)
        adev_close_output_stream_locked(adev, adev->output);
    pthread_mutex_unlock(&adev->lock);
    free(adev);

    return 0;
}

static uint32_t adev_get_supported_devices(const struct audio_hw_device *dev)
{
    return AUDIO_DEVICE_OUT_ALL_A2DP;
}

static int adev_open(const hw_module_t* module, const char* name,
                     hw_device_t** device)
{
    struct adev_a2dp *adev;
    int ret;

    if (strcmp(name, AUDIO_HARDWARE_INTERFACE) != 0)
        return -EINVAL;

    adev = calloc(1, sizeof(struct adev_a2dp));
    if (!adev)
        return -ENOMEM;

    adev->bt_enabled = true;
    adev->suspended = false;
    pthread_mutex_init(&adev->lock, NULL);
    adev->output = NULL;

    adev->device.common.tag = HARDWARE_DEVICE_TAG;
#ifdef AUDIO_DEVICE_API_VERSION_1_0
    adev->device.common.version = AUDIO_DEVICE_API_VERSION_1_0;
#else
    adev->device.common.version = 0;
#endif
    adev->device.common.module = (struct hw_module_t *) module;
    adev->device.common.close = adev_close;

    adev->device.get_supported_devices = adev_get_supported_devices;
    adev->device.init_check = adev_init_check;
    adev->device.set_voice_volume = adev_set_voice_volume;
    adev->device.set_master_volume = adev_set_master_volume;
    adev->device.set_mode = adev_set_mode;
    adev->device.set_mic_mute = adev_set_mic_mute;
    adev->device.get_mic_mute = adev_get_mic_mute;
    adev->device.set_parameters = adev_set_parameters;
    adev->device.get_parameters = adev_get_parameters;
    adev->device.get_input_buffer_size = adev_get_input_buffer_size;
    adev->device.open_output_stream = adev_open_output_stream;
    adev->device.close_output_stream = adev_close_output_stream;
    adev->device.open_input_stream = adev_open_input_stream;
    adev->device.close_input_stream = adev_close_input_stream;
    adev->device.dump = adev_dump;

    *device = &adev->device.common;

    return 0;

err_str_parms_create:
    free(adev);
    return ret;
}

static struct hw_module_methods_t hal_module_methods = {
    .open = adev_open,
};

struct audio_module HAL_MODULE_INFO_SYM = {
    .common = {
        .tag = HARDWARE_MODULE_TAG,
#ifdef AUDIO_MODULE_API_VERSION_0_1
        .module_api_version = AUDIO_MODULE_API_VERSION_0_1,
        .hal_api_version = HARDWARE_HAL_API_VERSION,
#else
        .version_major = 1,
        .version_minor = 0,
#endif
        .id = AUDIO_HARDWARE_MODULE_ID,
        .name = "A2DP Audio HW HAL",
        .author = "The Android Open Source Project",
        .methods = &hal_module_methods,
    },
};