Merge "Add the AVRCP Device class for the AVRCP Refactor"
diff --git a/audio_a2dp_hw/test/audio_a2dp_hw_test.cc b/audio_a2dp_hw/test/audio_a2dp_hw_test.cc
index 53faa6a..8fcbae5 100644
--- a/audio_a2dp_hw/test/audio_a2dp_hw_test.cc
+++ b/audio_a2dp_hw/test/audio_a2dp_hw_test.cc
@@ -36,6 +36,10 @@
return 176400;
case BTAV_A2DP_CODEC_SAMPLE_RATE_192000:
return 192000;
+ case BTAV_A2DP_CODEC_SAMPLE_RATE_16000:
+ return 16000;
+ case BTAV_A2DP_CODEC_SAMPLE_RATE_24000:
+ return 24000;
case BTAV_A2DP_CODEC_SAMPLE_RATE_NONE:
break;
}
diff --git a/audio_hearing_aid_hw/Android.bp b/audio_hearing_aid_hw/Android.bp
new file mode 100644
index 0000000..229fc25
--- /dev/null
+++ b/audio_hearing_aid_hw/Android.bp
@@ -0,0 +1,51 @@
+cc_defaults {
+ name: "audio_hearing_aid_hw_defaults",
+ defaults: ["fluoride_defaults"],
+ include_dirs: [
+ "system/bt",
+ "system/bt/include",
+ "system/bt/audio_hearing_aid_hw/include",
+ ]
+}
+
+// Audio A2DP shared library for target
+// ========================================================
+cc_library {
+ name: "audio.hearing_aid.default",
+ defaults: ["audio_hearing_aid_hw_defaults"],
+ relative_install_path: "hw",
+ srcs: [
+ "src/audio_hearing_aid_hw.cc",
+ "src/audio_hearing_aid_hw_utils.cc",
+ ],
+ shared_libs: [
+ "liblog",
+ ],
+ static_libs: ["libosi"],
+}
+
+cc_library_static {
+ name: "libaudio-hearing-aid-hw-utils",
+ defaults: ["audio_hearing_aid_hw_defaults"],
+ srcs: [
+ "src/audio_hearing_aid_hw_utils.cc",
+ ],
+}
+
+// Audio A2DP library unit tests for target and host
+// ========================================================
+cc_test {
+ name: "net_test_audio_hearing_aid_hw",
+ test_suites: ["device-tests"],
+ defaults: ["audio_hearing_aid_hw_defaults"],
+ srcs: [
+ "test/audio_hearing_aid_hw_test.cc",
+ ],
+ shared_libs: [
+ "liblog",
+ ],
+ static_libs: [
+ "audio.hearing_aid.default",
+ "libosi",
+ ],
+}
diff --git a/audio_hearing_aid_hw/include/audio_hearing_aid_hw.h b/audio_hearing_aid_hw/include/audio_hearing_aid_hw.h
new file mode 100644
index 0000000..56d8576
--- /dev/null
+++ b/audio_hearing_aid_hw/include/audio_hearing_aid_hw.h
@@ -0,0 +1,150 @@
+/******************************************************************************
+ *
+ * Copyright 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ ******************************************************************************/
+
+/*****************************************************************************
+ *
+ * Filename: audio_hearing_aid_hw.h
+ *
+ * Description:
+ *
+ *****************************************************************************/
+
+#ifndef AUDIO_HEARING_AID_HW_H
+#define AUDIO_HEARING_AID_HW_H
+
+#include <stdint.h>
+
+#include <hardware/bt_av.h>
+
+/*****************************************************************************
+ * Constants & Macros
+ *****************************************************************************/
+
+#define HEARING_AID_AUDIO_HARDWARE_INTERFACE "audio.hearing_aid"
+#define HEARING_AID_CTRL_PATH "/data/misc/bluedroid/.hearing_aid_ctrl"
+#define HEARING_AID_DATA_PATH "/data/misc/bluedroid/.hearing_aid_data"
+
+// AUDIO_STREAM_OUTPUT_BUFFER_SZ controls the size of the audio socket buffer.
+// If one assumes the write buffer is always full during normal BT playback,
+// then increasing this value increases our playback latency.
+//
+// FIXME: The BT HAL should consume data at a constant rate.
+// AudioFlinger assumes that the HAL draws data at a constant rate, which is
+// true for most audio devices; however, the BT engine reads data at a variable
+// rate (over the short term), which confuses both AudioFlinger as well as
+// applications which deliver data at a (generally) fixed rate.
+//
+// 20 * 512 is not sufficient to smooth the variability for some BT devices,
+// resulting in mixer sleep and throttling. We increase this to 28 * 512 to help
+// reduce the effect of variable data consumption.
+#define AUDIO_STREAM_OUTPUT_BUFFER_SZ (28 * 512)
+#define AUDIO_STREAM_CONTROL_OUTPUT_BUFFER_SZ 256
+
+// AUDIO_STREAM_OUTPUT_BUFFER_PERIODS controls how the socket buffer is divided
+// for AudioFlinger data delivery. The AudioFlinger mixer delivers data in
+// chunks of AUDIO_STREAM_OUTPUT_BUFFER_SZ / AUDIO_STREAM_OUTPUT_BUFFER_PERIODS.
+// If the number of periods is 2, the socket buffer represents "double
+// buffering" of the AudioFlinger mixer buffer.
+//
+// In general, AUDIO_STREAM_OUTPUT_BUFFER_PERIODS * 16 * 4 should be a divisor
+// of AUDIO_STREAM_OUTPUT_BUFFER_SZ.
+//
+// These values should be chosen such that
+//
+// AUDIO_STREAM_BUFFER_SIZE * 1000 / (AUDIO_STREAM_OUTPUT_BUFFER_PERIODS
+// * AUDIO_STREAM_DEFAULT_RATE * 4) > 20 (ms)
+//
+// to avoid introducing the FastMixer in AudioFlinger. Using the FastMixer
+// results in unnecessary latency and CPU overhead for Bluetooth.
+#define AUDIO_STREAM_OUTPUT_BUFFER_PERIODS 2
+
+#define AUDIO_SKT_DISCONNECTED (-1)
+
+typedef enum {
+ HEARING_AID_CTRL_CMD_NONE,
+ HEARING_AID_CTRL_CMD_CHECK_READY,
+ HEARING_AID_CTRL_CMD_START,
+ HEARING_AID_CTRL_CMD_STOP,
+ HEARING_AID_CTRL_CMD_SUSPEND,
+ HEARING_AID_CTRL_GET_INPUT_AUDIO_CONFIG,
+ HEARING_AID_CTRL_GET_OUTPUT_AUDIO_CONFIG,
+ HEARING_AID_CTRL_SET_OUTPUT_AUDIO_CONFIG,
+ HEARING_AID_CTRL_CMD_OFFLOAD_START,
+} tHEARING_AID_CTRL_CMD;
+
+typedef enum {
+ HEARING_AID_CTRL_ACK_SUCCESS,
+ HEARING_AID_CTRL_ACK_FAILURE,
+ HEARING_AID_CTRL_ACK_INCALL_FAILURE, /* Failure when in Call*/
+ HEARING_AID_CTRL_ACK_UNSUPPORTED
+} tHEARING_AID_CTRL_ACK;
+
+typedef uint32_t tHA_SAMPLE_RATE;
+typedef uint8_t tHA_CHANNEL_COUNT;
+
+/*****************************************************************************
+ * Type definitions for callback functions
+ *****************************************************************************/
+
+/*****************************************************************************
+ * Type definitions and return values
+ *****************************************************************************/
+
+/*****************************************************************************
+ * Extern variables and functions
+ *****************************************************************************/
+
+/*****************************************************************************
+ * Functions
+ *****************************************************************************/
+
+// Computes the Audio Hearing Aid HAL output buffer size.
+// |codec_sample_rate| is the sample rate of the output stream.
+// |codec_bits_per_sample| is the number of bits per sample of the output
+// stream.
+// |codec_channel_mode| is the channel mode of the output stream.
+//
+// The buffer size is computed by using the following formula:
+//
+// AUDIO_STREAM_OUTPUT_BUFFER_SIZE =
+// (TIME_PERIOD_MS * AUDIO_STREAM_OUTPUT_BUFFER_PERIODS *
+// SAMPLE_RATE_HZ * NUMBER_OF_CHANNELS * (BITS_PER_SAMPLE / 8)) / 1000
+//
+// AUDIO_STREAM_OUTPUT_BUFFER_PERIODS controls how the socket buffer is
+// divided for AudioFlinger data delivery. The AudioFlinger mixer delivers
+// data in chunks of
+// (AUDIO_STREAM_OUTPUT_BUFFER_SIZE / AUDIO_STREAM_OUTPUT_BUFFER_PERIODS) .
+// If the number of periods is 2, the socket buffer represents "double
+// buffering" of the AudioFlinger mixer buffer.
+//
+// Furthermore, the AudioFlinger expects the buffer size to be a multiple
+// of 16 frames.
+//
+// NOTE: Currently, the computation uses the conservative 20ms time period.
+//
+// Returns the computed buffer size. If any of the input parameters is
+// invalid, the return value is the default |AUDIO_STREAM_OUTPUT_BUFFER_SZ|.
+extern size_t audio_ha_hw_stream_compute_buffer_size(
+ btav_a2dp_codec_sample_rate_t codec_sample_rate,
+ btav_a2dp_codec_bits_per_sample_t codec_bits_per_sample,
+ btav_a2dp_codec_channel_mode_t codec_channel_mode);
+
+// Returns a string representation of |event|.
+extern const char* audio_ha_hw_dump_ctrl_event(tHEARING_AID_CTRL_CMD event);
+
+#endif /* AUDIO_HEARING_AID_HW_H */
diff --git a/audio_hearing_aid_hw/src/audio_hearing_aid_hw.cc b/audio_hearing_aid_hw/src/audio_hearing_aid_hw.cc
new file mode 100644
index 0000000..b3a8201
--- /dev/null
+++ b/audio_hearing_aid_hw/src/audio_hearing_aid_hw.cc
@@ -0,0 +1,1875 @@
+/******************************************************************************
+ *
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ ******************************************************************************/
+
+/* Implements hal for bluedroid ha audio device */
+
+#define LOG_TAG "bt_hearing_aid_hw"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <sys/errno.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#include <mutex>
+
+#include <hardware/audio.h>
+#include <hardware/hardware.h>
+#include <system/audio.h>
+
+#include "osi/include/hash_map_utils.h"
+#include "osi/include/log.h"
+#include "osi/include/osi.h"
+#include "osi/include/socket_utils/sockets.h"
+
+#include "audio_hearing_aid_hw.h"
+
+/*****************************************************************************
+ * Constants & Macros
+ *****************************************************************************/
+
+#define CTRL_CHAN_RETRY_COUNT 3
+#define USEC_PER_SEC 1000000L
+#define SOCK_SEND_TIMEOUT_MS 2000 /* Timeout for sending */
+#define SOCK_RECV_TIMEOUT_MS 5000 /* Timeout for receiving */
+
+// set WRITE_POLL_MS to 0 for blocking sockets, nonzero for polled non-blocking
+// sockets
+#define WRITE_POLL_MS 20
+
+#define FNLOG() LOG_VERBOSE(LOG_TAG, "%s", __func__);
+#define DEBUG(fmt, ...) \
+ LOG_VERBOSE(LOG_TAG, "%s: " fmt, __func__, ##__VA_ARGS__)
+#define INFO(fmt, ...) LOG_INFO(LOG_TAG, "%s: " fmt, __func__, ##__VA_ARGS__)
+#define WARN(fmt, ...) LOG_WARN(LOG_TAG, "%s: " fmt, __func__, ##__VA_ARGS__)
+#define ERROR(fmt, ...) LOG_ERROR(LOG_TAG, "%s: " fmt, __func__, ##__VA_ARGS__)
+
+#define ASSERTC(cond, msg, val) \
+ if (!(cond)) { \
+ ERROR("### ASSERT : %s line %d %s (%d) ###", __FILE__, __LINE__, msg, \
+ val); \
+ }
+
+/*****************************************************************************
+ * Local type definitions
+ *****************************************************************************/
+
+typedef enum {
+ AUDIO_HA_STATE_STARTING,
+ AUDIO_HA_STATE_STARTED,
+ AUDIO_HA_STATE_STOPPING,
+ AUDIO_HA_STATE_STOPPED,
+ /* need explicit set param call to resume (suspend=false) */
+ AUDIO_HA_STATE_SUSPENDED,
+ AUDIO_HA_STATE_STANDBY /* allows write to autoresume */
+} ha_state_t;
+
+struct ha_stream_in;
+struct ha_stream_out;
+
+struct ha_audio_device {
+ // Important: device must be first as an audio_hw_device* may be cast to
+ // ha_audio_device* when the type is implicitly known.
+ struct audio_hw_device device;
+ std::recursive_mutex* mutex; // See note below on mutex acquisition order.
+ struct ha_stream_in* input;
+ struct ha_stream_out* output;
+};
+
+struct ha_config {
+ uint32_t rate;
+ uint32_t channel_mask;
+ bool is_stereo_to_mono; // True if fetching Stereo and mixing into Mono
+ int format;
+};
+
+/* move ctrl_fd outside output stream and keep open until HAL unloaded ? */
+
+struct ha_stream_common {
+ std::recursive_mutex* mutex; // See note below on mutex acquisition order.
+ int ctrl_fd;
+ int audio_fd;
+ size_t buffer_sz;
+ struct ha_config cfg;
+ ha_state_t state;
+};
+
+struct ha_stream_out {
+ struct audio_stream_out stream;
+ struct ha_stream_common common;
+ uint64_t frames_presented; // frames written, never reset
+ uint64_t frames_rendered; // frames written, reset on standby
+};
+
+struct ha_stream_in {
+ struct audio_stream_in stream;
+ struct ha_stream_common common;
+};
+
+/*
+ * Mutex acquisition order:
+ *
+ * The ha_audio_device (adev) mutex must be acquired before
+ * the ha_stream_common (out or in) mutex.
+ *
+ * This may differ from other audio HALs.
+ */
+
+/*****************************************************************************
+ * Static variables
+ *****************************************************************************/
+
+/*****************************************************************************
+ * Static functions
+ *****************************************************************************/
+
+static size_t out_get_buffer_size(const struct audio_stream* stream);
+
+/*****************************************************************************
+ * Externs
+ *****************************************************************************/
+
+/*****************************************************************************
+ * Functions
+ *****************************************************************************/
+static void ha_open_ctrl_path(struct ha_stream_common* common);
+
+/*****************************************************************************
+ * Miscellaneous helper functions
+ *****************************************************************************/
+
+/* logs timestamp with microsec precision
+ pprev is optional in case a dedicated diff is required */
+static void ts_log(UNUSED_ATTR const char* tag, UNUSED_ATTR int val,
+ struct timespec* pprev_opt) {
+ struct timespec now;
+ static struct timespec prev = {0, 0};
+ unsigned long long now_us;
+ unsigned long long diff_us;
+
+ clock_gettime(CLOCK_MONOTONIC, &now);
+
+ now_us = now.tv_sec * USEC_PER_SEC + now.tv_nsec / 1000;
+
+ if (pprev_opt) {
+ diff_us = (now.tv_sec - prev.tv_sec) * USEC_PER_SEC +
+ (now.tv_nsec - prev.tv_nsec) / 1000;
+ *pprev_opt = now;
+ DEBUG("[%s] ts %08lld, *diff %08lld, val %d", tag, now_us, diff_us, val);
+ } else {
+ diff_us = (now.tv_sec - prev.tv_sec) * USEC_PER_SEC +
+ (now.tv_nsec - prev.tv_nsec) / 1000;
+ prev = now;
+ DEBUG("[%s] ts %08lld, diff %08lld, val %d", tag, now_us, diff_us, val);
+ }
+}
+
+static int calc_audiotime_usec(struct ha_config cfg, int bytes) {
+ int chan_count = audio_channel_count_from_out_mask(cfg.channel_mask);
+ int bytes_per_sample;
+
+ switch (cfg.format) {
+ case AUDIO_FORMAT_PCM_8_BIT:
+ bytes_per_sample = 1;
+ break;
+ case AUDIO_FORMAT_PCM_16_BIT:
+ bytes_per_sample = 2;
+ break;
+ case AUDIO_FORMAT_PCM_24_BIT_PACKED:
+ bytes_per_sample = 3;
+ break;
+ case AUDIO_FORMAT_PCM_8_24_BIT:
+ bytes_per_sample = 4;
+ break;
+ case AUDIO_FORMAT_PCM_32_BIT:
+ bytes_per_sample = 4;
+ break;
+ default:
+ ASSERTC(false, "unsupported sample format", cfg.format);
+ bytes_per_sample = 2;
+ break;
+ }
+
+ return (
+ int)(((int64_t)bytes * (USEC_PER_SEC / (chan_count * bytes_per_sample))) /
+ cfg.rate);
+}
+
+/*****************************************************************************
+ *
+ * bluedroid stack adaptation
+ *
+ ****************************************************************************/
+
+static int skt_connect(const char* path, size_t buffer_sz) {
+ int ret;
+ int skt_fd;
+ int len;
+
+ INFO("connect to %s (sz %zu)", path, buffer_sz);
+
+ skt_fd = socket(AF_LOCAL, SOCK_STREAM, 0);
+
+ if (osi_socket_local_client_connect(
+ skt_fd, path, ANDROID_SOCKET_NAMESPACE_ABSTRACT, SOCK_STREAM) < 0) {
+ ERROR("failed to connect (%s)", strerror(errno));
+ close(skt_fd);
+ return -1;
+ }
+
+ len = buffer_sz;
+ ret =
+ setsockopt(skt_fd, SOL_SOCKET, SO_SNDBUF, (char*)&len, (int)sizeof(len));
+ if (ret < 0) ERROR("setsockopt failed (%s)", strerror(errno));
+
+ ret =
+ setsockopt(skt_fd, SOL_SOCKET, SO_RCVBUF, (char*)&len, (int)sizeof(len));
+ if (ret < 0) ERROR("setsockopt failed (%s)", strerror(errno));
+
+ /* Socket send/receive timeout value */
+ struct timeval tv;
+ tv.tv_sec = SOCK_SEND_TIMEOUT_MS / 1000;
+ tv.tv_usec = (SOCK_SEND_TIMEOUT_MS % 1000) * 1000;
+
+ ret = setsockopt(skt_fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
+ if (ret < 0) ERROR("setsockopt failed (%s)", strerror(errno));
+
+ tv.tv_sec = SOCK_RECV_TIMEOUT_MS / 1000;
+ tv.tv_usec = (SOCK_RECV_TIMEOUT_MS % 1000) * 1000;
+
+ ret = setsockopt(skt_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
+ if (ret < 0) ERROR("setsockopt failed (%s)", strerror(errno));
+
+ INFO("connected to stack fd = %d", skt_fd);
+
+ return skt_fd;
+}
+
+static int skt_read(int fd, void* p, size_t len) {
+ ssize_t read;
+
+ FNLOG();
+
+ ts_log("skt_read recv", len, NULL);
+
+ OSI_NO_INTR(read = recv(fd, p, len, MSG_NOSIGNAL));
+ if (read == -1) ERROR("read failed with errno=%d\n", errno);
+
+ return (int)read;
+}
+
+static int skt_write(int fd, const void* p, size_t len) {
+ ssize_t sent;
+ FNLOG();
+
+ ts_log("skt_write", len, NULL);
+
+ if (WRITE_POLL_MS == 0) {
+ // do not poll, use blocking send
+ OSI_NO_INTR(sent = send(fd, p, len, MSG_NOSIGNAL));
+ if (sent == -1) ERROR("write failed with error(%s)", strerror(errno));
+
+ return (int)sent;
+ }
+
+ // use non-blocking send, poll
+ int ms_timeout = SOCK_SEND_TIMEOUT_MS;
+ size_t count = 0;
+ while (count < len) {
+ OSI_NO_INTR(sent = send(fd, p, len - count, MSG_NOSIGNAL | MSG_DONTWAIT));
+ if (sent == -1) {
+ if (errno != EAGAIN && errno != EWOULDBLOCK) {
+ ERROR("write failed with error(%s)", strerror(errno));
+ return -1;
+ }
+ if (ms_timeout >= WRITE_POLL_MS) {
+ usleep(WRITE_POLL_MS * 1000);
+ ms_timeout -= WRITE_POLL_MS;
+ continue;
+ }
+ WARN("write timeout exceeded, sent %zu bytes", count);
+ return -1;
+ }
+ count += sent;
+ p = (const uint8_t*)p + sent;
+ }
+ return (int)count;
+}
+
+static int skt_disconnect(int fd) {
+ INFO("fd %d", fd);
+
+ if (fd != AUDIO_SKT_DISCONNECTED) {
+ shutdown(fd, SHUT_RDWR);
+ close(fd);
+ }
+ return 0;
+}
+
+/*****************************************************************************
+ *
+ * AUDIO CONTROL PATH
+ *
+ ****************************************************************************/
+
+static int ha_ctrl_receive(struct ha_stream_common* common, void* buffer,
+ size_t length) {
+ ssize_t ret;
+ int i;
+
+ for (i = 0;; i++) {
+ OSI_NO_INTR(ret = recv(common->ctrl_fd, buffer, length, MSG_NOSIGNAL));
+ if (ret > 0) {
+ break;
+ }
+ if (ret == 0) {
+ ERROR("receive control data failed: peer closed");
+ break;
+ }
+ if (errno != EWOULDBLOCK && errno != EAGAIN) {
+ ERROR("receive control data failed: error(%s)", strerror(errno));
+ break;
+ }
+ if (i == (CTRL_CHAN_RETRY_COUNT - 1)) {
+ ERROR("receive control data failed: max retry count");
+ break;
+ }
+ INFO("receive control data failed (%s), retrying", strerror(errno));
+ }
+ if (ret <= 0) {
+ skt_disconnect(common->ctrl_fd);
+ common->ctrl_fd = AUDIO_SKT_DISCONNECTED;
+ }
+ return ret;
+}
+
+// Sends control info for stream |common|. The data to send is stored in
+// |buffer| and has size |length|.
+// On success, returns the number of octets sent, otherwise -1.
+static int ha_ctrl_send(struct ha_stream_common* common, const void* buffer,
+ size_t length) {
+ ssize_t sent;
+ size_t remaining = length;
+ int i;
+
+ if (length == 0) return 0; // Nothing to do
+
+ for (i = 0;; i++) {
+ OSI_NO_INTR(sent = send(common->ctrl_fd, buffer, remaining, MSG_NOSIGNAL));
+ if (sent == static_cast<ssize_t>(remaining)) {
+ remaining = 0;
+ break;
+ }
+ if (sent > 0) {
+ buffer = (static_cast<const char*>(buffer) + sent);
+ remaining -= sent;
+ continue;
+ }
+ if (sent < 0) {
+ if (errno != EWOULDBLOCK && errno != EAGAIN) {
+ ERROR("send control data failed: error(%s)", strerror(errno));
+ break;
+ }
+ INFO("send control data failed (%s), retrying", strerror(errno));
+ }
+ if (i >= (CTRL_CHAN_RETRY_COUNT - 1)) {
+ ERROR("send control data failed: max retry count");
+ break;
+ }
+ }
+ if (remaining > 0) {
+ skt_disconnect(common->ctrl_fd);
+ common->ctrl_fd = AUDIO_SKT_DISCONNECTED;
+ return -1;
+ }
+ return length;
+}
+
+static int ha_command(struct ha_stream_common* common,
+ tHEARING_AID_CTRL_CMD cmd) {
+ char ack;
+
+ DEBUG("HEARING_AID COMMAND %s", audio_ha_hw_dump_ctrl_event(cmd));
+
+ if (common->ctrl_fd == AUDIO_SKT_DISCONNECTED) {
+ INFO("starting up or recovering from previous error");
+ ha_open_ctrl_path(common);
+ if (common->ctrl_fd == AUDIO_SKT_DISCONNECTED) {
+ ERROR("failure to open ctrl path");
+ return -1;
+ }
+ }
+
+ /* send command */
+ ssize_t sent;
+ OSI_NO_INTR(sent = send(common->ctrl_fd, &cmd, 1, MSG_NOSIGNAL));
+ if (sent == -1) {
+ ERROR("cmd failed (%s)", strerror(errno));
+ skt_disconnect(common->ctrl_fd);
+ common->ctrl_fd = AUDIO_SKT_DISCONNECTED;
+ return -1;
+ }
+
+ /* wait for ack byte */
+ if (ha_ctrl_receive(common, &ack, 1) < 0) {
+ ERROR("HEARING_AID COMMAND %s: no ACK", audio_ha_hw_dump_ctrl_event(cmd));
+ return -1;
+ }
+
+ DEBUG("HEARING_AID COMMAND %s DONE STATUS %d",
+ audio_ha_hw_dump_ctrl_event(cmd), ack);
+
+ if (ack == HEARING_AID_CTRL_ACK_INCALL_FAILURE) return ack;
+ if (ack != HEARING_AID_CTRL_ACK_SUCCESS) {
+ ERROR("HEARING_AID COMMAND %s error %d", audio_ha_hw_dump_ctrl_event(cmd),
+ ack);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int check_ha_ready(struct ha_stream_common* common) {
+ if (ha_command(common, HEARING_AID_CTRL_CMD_CHECK_READY) < 0) {
+ ERROR("check ha ready failed");
+ return -1;
+ }
+ return 0;
+}
+
+static int ha_read_input_audio_config(struct ha_stream_common* common) {
+ tHA_SAMPLE_RATE sample_rate;
+ tHA_CHANNEL_COUNT channel_count;
+
+ if (ha_command(common, HEARING_AID_CTRL_GET_INPUT_AUDIO_CONFIG) < 0) {
+ ERROR("get ha input audio config failed");
+ return -1;
+ }
+
+ if (ha_ctrl_receive(common, &sample_rate, sizeof(tHA_SAMPLE_RATE)) < 0)
+ return -1;
+ if (ha_ctrl_receive(common, &channel_count, sizeof(tHA_CHANNEL_COUNT)) < 0) {
+ return -1;
+ }
+
+ switch (sample_rate) {
+ case 16000:
+ case 44100:
+ case 48000:
+ common->cfg.rate = sample_rate;
+ break;
+ default:
+ ERROR("Invalid sample rate: %" PRIu32, sample_rate);
+ return -1;
+ }
+
+ switch (channel_count) {
+ case 1:
+ common->cfg.channel_mask = AUDIO_CHANNEL_IN_MONO;
+ break;
+ case 2:
+ common->cfg.channel_mask = AUDIO_CHANNEL_IN_STEREO;
+ break;
+ default:
+ ERROR("Invalid channel count: %" PRIu32, channel_count);
+ return -1;
+ }
+
+ // TODO: For now input audio format is always hard-coded as PCM 16-bit
+ common->cfg.format = AUDIO_FORMAT_PCM_16_BIT;
+
+ INFO("got input audio config %d %d", common->cfg.format, common->cfg.rate);
+
+ return 0;
+}
+
+static int ha_read_output_audio_config(
+ struct ha_stream_common* common, btav_a2dp_codec_config_t* codec_config,
+ btav_a2dp_codec_config_t* codec_capability, bool update_stream_config) {
+ struct ha_config stream_config;
+
+ if (ha_command(common, HEARING_AID_CTRL_GET_OUTPUT_AUDIO_CONFIG) < 0) {
+ ERROR("get ha output audio config failed");
+ return -1;
+ }
+
+ // Receive the current codec config
+ if (ha_ctrl_receive(common, &codec_config->sample_rate,
+ sizeof(btav_a2dp_codec_sample_rate_t)) < 0) {
+ return -1;
+ }
+ if (ha_ctrl_receive(common, &codec_config->bits_per_sample,
+ sizeof(btav_a2dp_codec_bits_per_sample_t)) < 0) {
+ return -1;
+ }
+ if (ha_ctrl_receive(common, &codec_config->channel_mode,
+ sizeof(btav_a2dp_codec_channel_mode_t)) < 0) {
+ return -1;
+ }
+
+ // Receive the current codec capability
+ if (ha_ctrl_receive(common, &codec_capability->sample_rate,
+ sizeof(btav_a2dp_codec_sample_rate_t)) < 0) {
+ return -1;
+ }
+ if (ha_ctrl_receive(common, &codec_capability->bits_per_sample,
+ sizeof(btav_a2dp_codec_bits_per_sample_t)) < 0) {
+ return -1;
+ }
+ if (ha_ctrl_receive(common, &codec_capability->channel_mode,
+ sizeof(btav_a2dp_codec_channel_mode_t)) < 0) {
+ return -1;
+ }
+
+ // Check the codec config sample rate
+ switch (codec_config->sample_rate) {
+ case BTAV_A2DP_CODEC_SAMPLE_RATE_44100:
+ stream_config.rate = 44100;
+ break;
+ case BTAV_A2DP_CODEC_SAMPLE_RATE_48000:
+ stream_config.rate = 48000;
+ break;
+ case BTAV_A2DP_CODEC_SAMPLE_RATE_88200:
+ stream_config.rate = 88200;
+ break;
+ case BTAV_A2DP_CODEC_SAMPLE_RATE_96000:
+ stream_config.rate = 96000;
+ break;
+ case BTAV_A2DP_CODEC_SAMPLE_RATE_176400:
+ stream_config.rate = 176400;
+ break;
+ case BTAV_A2DP_CODEC_SAMPLE_RATE_192000:
+ stream_config.rate = 192000;
+ break;
+ case BTAV_A2DP_CODEC_SAMPLE_RATE_16000:
+ stream_config.rate = 16000;
+ break;
+ case BTAV_A2DP_CODEC_SAMPLE_RATE_NONE:
+ default:
+ ERROR("Invalid sample rate: 0x%x", codec_config->sample_rate);
+ return -1;
+ }
+
+ // Check the codec config bits per sample
+ switch (codec_config->bits_per_sample) {
+ case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16:
+ stream_config.format = AUDIO_FORMAT_PCM_16_BIT;
+ break;
+ case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_24:
+ stream_config.format = AUDIO_FORMAT_PCM_24_BIT_PACKED;
+ break;
+ case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_32:
+ stream_config.format = AUDIO_FORMAT_PCM_32_BIT;
+ break;
+ case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_NONE:
+ default:
+ ERROR("Invalid bits per sample: 0x%x", codec_config->bits_per_sample);
+ return -1;
+ }
+
+ // Check the codec config channel mode
+ switch (codec_config->channel_mode) {
+ case BTAV_A2DP_CODEC_CHANNEL_MODE_MONO:
+ stream_config.channel_mask = AUDIO_CHANNEL_OUT_MONO;
+ stream_config.is_stereo_to_mono = true;
+ break;
+ case BTAV_A2DP_CODEC_CHANNEL_MODE_STEREO:
+ stream_config.channel_mask = AUDIO_CHANNEL_OUT_STEREO;
+ stream_config.is_stereo_to_mono = false;
+ break;
+ case BTAV_A2DP_CODEC_CHANNEL_MODE_NONE:
+ default:
+ ERROR("Invalid channel mode: 0x%x", codec_config->channel_mode);
+ return -1;
+ }
+ if (stream_config.is_stereo_to_mono) {
+ stream_config.channel_mask = AUDIO_CHANNEL_OUT_STEREO;
+ }
+
+ // Update the output stream configuration
+ if (update_stream_config) {
+ common->cfg.rate = stream_config.rate;
+ common->cfg.channel_mask = stream_config.channel_mask;
+ common->cfg.is_stereo_to_mono = stream_config.is_stereo_to_mono;
+ common->cfg.format = stream_config.format;
+ common->buffer_sz = audio_ha_hw_stream_compute_buffer_size(
+ codec_config->sample_rate, codec_config->bits_per_sample,
+ codec_config->channel_mode);
+ if (common->cfg.is_stereo_to_mono) {
+ // We need to fetch twice as much data from the Audio framework
+ common->buffer_sz *= 2;
+ }
+ }
+
+ INFO(
+ "got output codec config (update_stream_config=%s): "
+ "sample_rate=0x%x bits_per_sample=0x%x channel_mode=0x%x",
+ update_stream_config ? "true" : "false", codec_config->sample_rate,
+ codec_config->bits_per_sample, codec_config->channel_mode);
+
+ INFO(
+ "got output codec capability: sample_rate=0x%x bits_per_sample=0x%x "
+ "channel_mode=0x%x",
+ codec_capability->sample_rate, codec_capability->bits_per_sample,
+ codec_capability->channel_mode);
+
+ return 0;
+}
+
+static int ha_write_output_audio_config(struct ha_stream_common* common) {
+ btav_a2dp_codec_config_t codec_config;
+
+ if (ha_command(common, HEARING_AID_CTRL_SET_OUTPUT_AUDIO_CONFIG) < 0) {
+ ERROR("set ha output audio config failed");
+ return -1;
+ }
+
+ codec_config.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_NONE;
+ codec_config.bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_NONE;
+ codec_config.channel_mode = BTAV_A2DP_CODEC_CHANNEL_MODE_NONE;
+
+ switch (common->cfg.rate) {
+ case 44100:
+ codec_config.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_44100;
+ break;
+ case 48000:
+ codec_config.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_48000;
+ break;
+ case 88200:
+ codec_config.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_88200;
+ break;
+ case 96000:
+ codec_config.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_96000;
+ break;
+ case 176400:
+ codec_config.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_176400;
+ break;
+ case 192000:
+ codec_config.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_192000;
+ break;
+ case 16000:
+ codec_config.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_16000;
+ break;
+ default:
+ ERROR("Invalid sample rate: %" PRIu32, common->cfg.rate);
+ return -1;
+ }
+
+ switch (common->cfg.format) {
+ case AUDIO_FORMAT_PCM_16_BIT:
+ codec_config.bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16;
+ break;
+ case AUDIO_FORMAT_PCM_24_BIT_PACKED:
+ codec_config.bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_24;
+ break;
+ case AUDIO_FORMAT_PCM_32_BIT:
+ codec_config.bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_32;
+ break;
+ case AUDIO_FORMAT_PCM_8_24_BIT:
+ // FALLTHROUGH
+ // All 24-bit audio is expected in AUDIO_FORMAT_PCM_24_BIT_PACKED format
+ default:
+ ERROR("Invalid audio format: 0x%x", common->cfg.format);
+ return -1;
+ }
+
+ switch (common->cfg.channel_mask) {
+ case AUDIO_CHANNEL_OUT_MONO:
+ codec_config.channel_mode = BTAV_A2DP_CODEC_CHANNEL_MODE_MONO;
+ break;
+ case AUDIO_CHANNEL_OUT_STEREO:
+ if (common->cfg.is_stereo_to_mono) {
+ codec_config.channel_mode = BTAV_A2DP_CODEC_CHANNEL_MODE_MONO;
+ } else {
+ codec_config.channel_mode = BTAV_A2DP_CODEC_CHANNEL_MODE_STEREO;
+ }
+ break;
+ default:
+ ERROR("Invalid channel mask: 0x%x", common->cfg.channel_mask);
+ return -1;
+ }
+
+ // Send the current codec config that has been selected by us
+ if (ha_ctrl_send(common, &codec_config.sample_rate,
+ sizeof(btav_a2dp_codec_sample_rate_t)) < 0)
+ return -1;
+ if (ha_ctrl_send(common, &codec_config.bits_per_sample,
+ sizeof(btav_a2dp_codec_bits_per_sample_t)) < 0) {
+ return -1;
+ }
+ if (ha_ctrl_send(common, &codec_config.channel_mode,
+ sizeof(btav_a2dp_codec_channel_mode_t)) < 0) {
+ return -1;
+ }
+
+ INFO(
+ "sent output codec config: sample_rate=0x%x bits_per_sample=0x%x "
+ "channel_mode=0x%x",
+ codec_config.sample_rate, codec_config.bits_per_sample,
+ codec_config.channel_mode);
+
+ return 0;
+}
+
+static void ha_open_ctrl_path(struct ha_stream_common* common) {
+ int i;
+
+ if (common->ctrl_fd != AUDIO_SKT_DISCONNECTED) return; // already connected
+
+ /* retry logic to catch any timing variations on control channel */
+ for (i = 0; i < CTRL_CHAN_RETRY_COUNT; i++) {
+ /* connect control channel if not already connected */
+ if ((common->ctrl_fd = skt_connect(
+ HEARING_AID_CTRL_PATH, AUDIO_STREAM_CONTROL_OUTPUT_BUFFER_SZ)) >=
+ 0) {
+ /* success, now check if stack is ready */
+ if (check_ha_ready(common) == 0) break;
+
+ ERROR("error : ha not ready, wait 250 ms and retry");
+ usleep(250000);
+ skt_disconnect(common->ctrl_fd);
+ common->ctrl_fd = AUDIO_SKT_DISCONNECTED;
+ }
+
+ /* ctrl channel not ready, wait a bit */
+ usleep(250000);
+ }
+}
+
+/*****************************************************************************
+ *
+ * AUDIO DATA PATH
+ *
+ ****************************************************************************/
+
+static void ha_stream_common_init(struct ha_stream_common* common) {
+ FNLOG();
+
+ common->mutex = new std::recursive_mutex;
+
+ common->ctrl_fd = AUDIO_SKT_DISCONNECTED;
+ common->audio_fd = AUDIO_SKT_DISCONNECTED;
+ common->state = AUDIO_HA_STATE_STOPPED;
+
+ /* manages max capacity of socket pipe */
+ common->buffer_sz = AUDIO_STREAM_OUTPUT_BUFFER_SZ;
+}
+
+static void ha_stream_common_destroy(struct ha_stream_common* common) {
+ FNLOG();
+
+ delete common->mutex;
+ common->mutex = NULL;
+}
+
+static int start_audio_datapath(struct ha_stream_common* common) {
+ INFO("state %d", common->state);
+
+ int oldstate = common->state;
+ common->state = AUDIO_HA_STATE_STARTING;
+
+ int ha_status = ha_command(common, HEARING_AID_CTRL_CMD_START);
+ if (ha_status < 0) {
+ ERROR("Audiopath start failed (status %d)", ha_status);
+ goto error;
+ } else if (ha_status == HEARING_AID_CTRL_ACK_INCALL_FAILURE) {
+ ERROR("Audiopath start failed - in call, move to suspended");
+ goto error;
+ }
+
+ /* connect socket if not yet connected */
+ if (common->audio_fd == AUDIO_SKT_DISCONNECTED) {
+ common->audio_fd = skt_connect(HEARING_AID_DATA_PATH, common->buffer_sz);
+ if (common->audio_fd < 0) {
+ ERROR("Audiopath start failed - error opening data socket");
+ goto error;
+ }
+ }
+ common->state = (ha_state_t)AUDIO_HA_STATE_STARTED;
+ return 0;
+
+error:
+ common->state = (ha_state_t)oldstate;
+ return -1;
+}
+
+static int stop_audio_datapath(struct ha_stream_common* common) {
+ int oldstate = common->state;
+
+ INFO("state %d", common->state);
+
+ /* prevent any stray output writes from autostarting the stream
+ while stopping audiopath */
+ common->state = AUDIO_HA_STATE_STOPPING;
+
+ if (ha_command(common, HEARING_AID_CTRL_CMD_STOP) < 0) {
+ ERROR("audiopath stop failed");
+ common->state = (ha_state_t)oldstate;
+ return -1;
+ }
+
+ common->state = (ha_state_t)AUDIO_HA_STATE_STOPPED;
+
+ /* disconnect audio path */
+ skt_disconnect(common->audio_fd);
+ common->audio_fd = AUDIO_SKT_DISCONNECTED;
+
+ return 0;
+}
+
+static int suspend_audio_datapath(struct ha_stream_common* common,
+ bool standby) {
+ INFO("state %d", common->state);
+
+ if (common->state == AUDIO_HA_STATE_STOPPING) return -1;
+
+ if (ha_command(common, HEARING_AID_CTRL_CMD_SUSPEND) < 0) return -1;
+
+ if (standby)
+ common->state = AUDIO_HA_STATE_STANDBY;
+ else
+ common->state = AUDIO_HA_STATE_SUSPENDED;
+
+ /* disconnect audio path */
+ skt_disconnect(common->audio_fd);
+
+ common->audio_fd = AUDIO_SKT_DISCONNECTED;
+
+ return 0;
+}
+
+/*****************************************************************************
+ *
+ * audio output callbacks
+ *
+ ****************************************************************************/
+
+static ssize_t out_write(struct audio_stream_out* stream, const void* buffer,
+ size_t bytes) {
+ struct ha_stream_out* out = (struct ha_stream_out*)stream;
+ int sent = -1;
+ size_t write_bytes = bytes;
+
+ DEBUG("write %zu bytes (fd %d)", bytes, out->common.audio_fd);
+
+ std::unique_lock<std::recursive_mutex> lock(*out->common.mutex);
+ if (out->common.state == AUDIO_HA_STATE_SUSPENDED ||
+ out->common.state == AUDIO_HA_STATE_STOPPING) {
+ DEBUG("stream suspended or closing");
+ goto finish;
+ }
+
+ /* only allow autostarting if we are in stopped or standby */
+ if ((out->common.state == AUDIO_HA_STATE_STOPPED) ||
+ (out->common.state == AUDIO_HA_STATE_STANDBY)) {
+ if (start_audio_datapath(&out->common) < 0) {
+ goto finish;
+ }
+ } else if (out->common.state != AUDIO_HA_STATE_STARTED) {
+ ERROR("stream not in stopped or standby");
+ goto finish;
+ }
+
+ // Mix the stereo into mono if necessary
+ if (out->common.cfg.is_stereo_to_mono) {
+ const size_t frames = bytes / audio_stream_out_frame_size(stream);
+ int16_t* src = (int16_t*)buffer;
+ int16_t* dst = (int16_t*)buffer;
+ for (size_t i = 0; i < frames; i++, dst++, src += 2) {
+ *dst = (int16_t)(((int32_t)src[0] + (int32_t)src[1]) >> 1);
+ }
+ write_bytes /= 2;
+ DEBUG("stereo-to-mono mixing: write %zu bytes (fd %d)", write_bytes,
+ out->common.audio_fd);
+ }
+
+ lock.unlock();
+ sent = skt_write(out->common.audio_fd, buffer, write_bytes);
+ lock.lock();
+
+ if (sent == -1) {
+ skt_disconnect(out->common.audio_fd);
+ out->common.audio_fd = AUDIO_SKT_DISCONNECTED;
+ if ((out->common.state != AUDIO_HA_STATE_SUSPENDED) &&
+ (out->common.state != AUDIO_HA_STATE_STOPPING)) {
+ out->common.state = AUDIO_HA_STATE_STOPPED;
+ } else {
+ ERROR("write failed : stream suspended, avoid resetting state");
+ }
+ goto finish;
+ }
+
+finish:;
+ const size_t frames = bytes / audio_stream_out_frame_size(stream);
+ out->frames_rendered += frames;
+ out->frames_presented += frames;
+ lock.unlock();
+
+ // If send didn't work out, sleep to emulate write delay.
+ if (sent == -1) {
+ const int us_delay = calc_audiotime_usec(out->common.cfg, bytes);
+ DEBUG("emulate ha write delay (%d us)", us_delay);
+ usleep(us_delay);
+ }
+ return bytes;
+}
+
+static uint32_t out_get_sample_rate(const struct audio_stream* stream) {
+ struct ha_stream_out* out = (struct ha_stream_out*)stream;
+
+ DEBUG("rate %" PRIu32, out->common.cfg.rate);
+
+ return out->common.cfg.rate;
+}
+
+static int out_set_sample_rate(struct audio_stream* stream, uint32_t rate) {
+ struct ha_stream_out* out = (struct ha_stream_out*)stream;
+
+ DEBUG("out_set_sample_rate : %" PRIu32, rate);
+
+ out->common.cfg.rate = rate;
+
+ return 0;
+}
+
+static size_t out_get_buffer_size(const struct audio_stream* stream) {
+ struct ha_stream_out* out = (struct ha_stream_out*)stream;
+ // period_size is the AudioFlinger mixer buffer size.
+ const size_t period_size =
+ out->common.buffer_sz / AUDIO_STREAM_OUTPUT_BUFFER_PERIODS;
+
+ DEBUG("socket buffer size: %zu period size: %zu", out->common.buffer_sz,
+ period_size);
+
+ return period_size;
+}
+
+size_t audio_ha_hw_stream_compute_buffer_size(
+ btav_a2dp_codec_sample_rate_t codec_sample_rate,
+ btav_a2dp_codec_bits_per_sample_t codec_bits_per_sample,
+ btav_a2dp_codec_channel_mode_t codec_channel_mode) {
+ size_t buffer_sz = AUDIO_STREAM_OUTPUT_BUFFER_SZ; // Default value
+ const uint64_t time_period_ms = 20; // Conservative 20ms
+ uint32_t sample_rate;
+ uint32_t bits_per_sample;
+ uint32_t number_of_channels;
+
+ // Check the codec config sample rate
+ switch (codec_sample_rate) {
+ case BTAV_A2DP_CODEC_SAMPLE_RATE_44100:
+ sample_rate = 44100;
+ break;
+ case BTAV_A2DP_CODEC_SAMPLE_RATE_48000:
+ sample_rate = 48000;
+ break;
+ case BTAV_A2DP_CODEC_SAMPLE_RATE_88200:
+ sample_rate = 88200;
+ break;
+ case BTAV_A2DP_CODEC_SAMPLE_RATE_96000:
+ sample_rate = 96000;
+ break;
+ case BTAV_A2DP_CODEC_SAMPLE_RATE_176400:
+ sample_rate = 176400;
+ break;
+ case BTAV_A2DP_CODEC_SAMPLE_RATE_192000:
+ sample_rate = 192000;
+ break;
+ case BTAV_A2DP_CODEC_SAMPLE_RATE_16000:
+ sample_rate = 16000;
+ break;
+ case BTAV_A2DP_CODEC_SAMPLE_RATE_NONE:
+ default:
+ ERROR("Invalid sample rate: 0x%x", codec_sample_rate);
+ return buffer_sz;
+ }
+
+ // Check the codec config bits per sample
+ switch (codec_bits_per_sample) {
+ case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16:
+ bits_per_sample = 16;
+ break;
+ case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_24:
+ bits_per_sample = 24;
+ break;
+ case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_32:
+ bits_per_sample = 32;
+ break;
+ case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_NONE:
+ default:
+ ERROR("Invalid bits per sample: 0x%x", codec_bits_per_sample);
+ return buffer_sz;
+ }
+
+ // Check the codec config channel mode
+ switch (codec_channel_mode) {
+ case BTAV_A2DP_CODEC_CHANNEL_MODE_MONO:
+ number_of_channels = 1;
+ break;
+ case BTAV_A2DP_CODEC_CHANNEL_MODE_STEREO:
+ number_of_channels = 2;
+ break;
+ case BTAV_A2DP_CODEC_CHANNEL_MODE_NONE:
+ default:
+ ERROR("Invalid channel mode: 0x%x", codec_channel_mode);
+ return buffer_sz;
+ }
+
+ //
+ // The buffer size is computed by using the following formula:
+ //
+ // AUDIO_STREAM_OUTPUT_BUFFER_SIZE =
+ // (TIME_PERIOD_MS * AUDIO_STREAM_OUTPUT_BUFFER_PERIODS *
+ // SAMPLE_RATE_HZ * NUMBER_OF_CHANNELS * (BITS_PER_SAMPLE / 8)) / 1000
+ //
+ // AUDIO_STREAM_OUTPUT_BUFFER_PERIODS controls how the socket buffer is
+ // divided for AudioFlinger data delivery. The AudioFlinger mixer delivers
+ // data in chunks of
+ // (AUDIO_STREAM_OUTPUT_BUFFER_SIZE / AUDIO_STREAM_OUTPUT_BUFFER_PERIODS) .
+ // If the number of periods is 2, the socket buffer represents "double
+ // buffering" of the AudioFlinger mixer buffer.
+ //
+ // Furthermore, the AudioFlinger expects the buffer size to be a multiple
+ // of 16 frames.
+ const size_t divisor = (AUDIO_STREAM_OUTPUT_BUFFER_PERIODS * 16 *
+ number_of_channels * bits_per_sample) /
+ 8;
+
+ buffer_sz = (time_period_ms * AUDIO_STREAM_OUTPUT_BUFFER_PERIODS *
+ sample_rate * number_of_channels * (bits_per_sample / 8)) /
+ 1000;
+
+ // Adjust the buffer size so it can be divided by the divisor
+ const size_t remainder = buffer_sz % divisor;
+ if (remainder != 0) {
+ buffer_sz += divisor - remainder;
+ }
+
+ return buffer_sz;
+}
+
+static uint32_t out_get_channels(const struct audio_stream* stream) {
+ struct ha_stream_out* out = (struct ha_stream_out*)stream;
+
+ DEBUG("channels 0x%" PRIx32, out->common.cfg.channel_mask);
+
+ return out->common.cfg.channel_mask;
+}
+
+static audio_format_t out_get_format(const struct audio_stream* stream) {
+ struct ha_stream_out* out = (struct ha_stream_out*)stream;
+ DEBUG("format 0x%x", out->common.cfg.format);
+ return (audio_format_t)out->common.cfg.format;
+}
+
+static int out_set_format(UNUSED_ATTR struct audio_stream* stream,
+ UNUSED_ATTR audio_format_t format) {
+ DEBUG("setting format not yet supported (0x%x)", format);
+ return -ENOSYS;
+}
+
+static int out_standby(struct audio_stream* stream) {
+ struct ha_stream_out* out = (struct ha_stream_out*)stream;
+ int retVal = 0;
+
+ FNLOG();
+
+ std::lock_guard<std::recursive_mutex> lock(*out->common.mutex);
+ // Do nothing in SUSPENDED state.
+ if (out->common.state != AUDIO_HA_STATE_SUSPENDED)
+ retVal = suspend_audio_datapath(&out->common, true);
+ out->frames_rendered = 0; // rendered is reset, presented is not
+
+ return retVal;
+}
+
+static int out_dump(UNUSED_ATTR const struct audio_stream* stream,
+ UNUSED_ATTR int fd) {
+ FNLOG();
+ return 0;
+}
+
+static int out_set_parameters(struct audio_stream* stream,
+ const char* kvpairs) {
+ struct ha_stream_out* out = (struct ha_stream_out*)stream;
+
+ INFO("state %d kvpairs %s", out->common.state, kvpairs);
+
+ std::unordered_map<std::string, std::string> params =
+ hash_map_utils_new_from_string_params(kvpairs);
+ int status = 0;
+
+ if (params.empty()) return status;
+
+ std::lock_guard<std::recursive_mutex> lock(*out->common.mutex);
+
+ /* dump params */
+ hash_map_utils_dump_string_keys_string_values(params);
+
+ if (params["closing"].compare("true") == 0) {
+ DEBUG("stream closing, disallow any writes");
+ out->common.state = AUDIO_HA_STATE_STOPPING;
+ }
+
+ if (params["HearingAidSuspended"].compare("true") == 0) {
+ if (out->common.state == AUDIO_HA_STATE_STARTED)
+ status = suspend_audio_datapath(&out->common, false);
+ } else {
+ /* Do not start the streaming automatically. If the phone was streaming
+ * prior to being suspended, the next out_write shall trigger the
+ * AVDTP start procedure */
+ if (out->common.state == AUDIO_HA_STATE_SUSPENDED)
+ out->common.state = AUDIO_HA_STATE_STANDBY;
+ /* Irrespective of the state, return 0 */
+ }
+
+ return status;
+}
+
+static char* out_get_parameters(const struct audio_stream* stream,
+ const char* keys) {
+ FNLOG();
+
+ btav_a2dp_codec_config_t codec_config;
+ btav_a2dp_codec_config_t codec_capability;
+
+ struct ha_stream_out* out = (struct ha_stream_out*)stream;
+
+ std::unordered_map<std::string, std::string> params =
+ hash_map_utils_new_from_string_params(keys);
+ std::unordered_map<std::string, std::string> return_params;
+
+ if (params.empty()) return strdup("");
+
+ std::lock_guard<std::recursive_mutex> lock(*out->common.mutex);
+
+ if (ha_read_output_audio_config(&out->common, &codec_config,
+ &codec_capability,
+ false /* update_stream_config */) < 0) {
+ ERROR("ha_read_output_audio_config failed");
+ goto done;
+ }
+
+ // Add the format
+ if (params.find(AUDIO_PARAMETER_STREAM_SUP_FORMATS) != params.end()) {
+ std::string param;
+ if (codec_capability.bits_per_sample & BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16) {
+ if (!param.empty()) param += "|";
+ param += "AUDIO_FORMAT_PCM_16_BIT";
+ }
+ if (codec_capability.bits_per_sample & BTAV_A2DP_CODEC_BITS_PER_SAMPLE_24) {
+ if (!param.empty()) param += "|";
+ param += "AUDIO_FORMAT_PCM_24_BIT_PACKED";
+ }
+ if (codec_capability.bits_per_sample & BTAV_A2DP_CODEC_BITS_PER_SAMPLE_32) {
+ if (!param.empty()) param += "|";
+ param += "AUDIO_FORMAT_PCM_32_BIT";
+ }
+ if (param.empty()) {
+ ERROR("Invalid codec capability bits_per_sample=0x%x",
+ codec_capability.bits_per_sample);
+ goto done;
+ } else {
+ return_params[AUDIO_PARAMETER_STREAM_SUP_FORMATS] = param;
+ }
+ }
+
+ // Add the sample rate
+ if (params.find(AUDIO_PARAMETER_STREAM_SUP_SAMPLING_RATES) != params.end()) {
+ std::string param;
+ if (codec_capability.sample_rate & BTAV_A2DP_CODEC_SAMPLE_RATE_44100) {
+ if (!param.empty()) param += "|";
+ param += "44100";
+ }
+ if (codec_capability.sample_rate & BTAV_A2DP_CODEC_SAMPLE_RATE_48000) {
+ if (!param.empty()) param += "|";
+ param += "48000";
+ }
+ if (codec_capability.sample_rate & BTAV_A2DP_CODEC_SAMPLE_RATE_88200) {
+ if (!param.empty()) param += "|";
+ param += "88200";
+ }
+ if (codec_capability.sample_rate & BTAV_A2DP_CODEC_SAMPLE_RATE_96000) {
+ if (!param.empty()) param += "|";
+ param += "96000";
+ }
+ if (codec_capability.sample_rate & BTAV_A2DP_CODEC_SAMPLE_RATE_176400) {
+ if (!param.empty()) param += "|";
+ param += "176400";
+ }
+ if (codec_capability.sample_rate & BTAV_A2DP_CODEC_SAMPLE_RATE_192000) {
+ if (!param.empty()) param += "|";
+ param += "192000";
+ }
+ if (codec_capability.sample_rate & BTAV_A2DP_CODEC_SAMPLE_RATE_16000) {
+ if (!param.empty()) param += "|";
+ param += "16000";
+ }
+ if (param.empty()) {
+ ERROR("Invalid codec capability sample_rate=0x%x",
+ codec_capability.sample_rate);
+ goto done;
+ } else {
+ return_params[AUDIO_PARAMETER_STREAM_SUP_SAMPLING_RATES] = param;
+ }
+ }
+
+ // Add the channel mask
+ if (params.find(AUDIO_PARAMETER_STREAM_SUP_CHANNELS) != params.end()) {
+ std::string param;
+ if (codec_capability.channel_mode & BTAV_A2DP_CODEC_CHANNEL_MODE_MONO) {
+ if (!param.empty()) param += "|";
+ param += "AUDIO_CHANNEL_OUT_MONO";
+ }
+ if (codec_capability.channel_mode & BTAV_A2DP_CODEC_CHANNEL_MODE_STEREO) {
+ if (!param.empty()) param += "|";
+ param += "AUDIO_CHANNEL_OUT_STEREO";
+ }
+ if (param.empty()) {
+ ERROR("Invalid codec capability channel_mode=0x%x",
+ codec_capability.channel_mode);
+ goto done;
+ } else {
+ return_params[AUDIO_PARAMETER_STREAM_SUP_CHANNELS] = param;
+ }
+ }
+
+done:
+ std::string result;
+ for (const auto& ptr : return_params) {
+ result += ptr.first + "=" + ptr.second + ";";
+ }
+
+ INFO("get parameters result = %s", result.c_str());
+
+ return strdup(result.c_str());
+}
+
+static uint32_t out_get_latency(const struct audio_stream_out* stream) {
+ int latency_us;
+
+ struct ha_stream_out* out = (struct ha_stream_out*)stream;
+
+ FNLOG();
+
+ latency_us =
+ ((out->common.buffer_sz * 1000) /
+ audio_stream_out_frame_size(&out->stream) / out->common.cfg.rate) *
+ 1000;
+
+ return (latency_us / 1000) + 200;
+}
+
+static int out_set_volume(UNUSED_ATTR struct audio_stream_out* stream,
+ UNUSED_ATTR float left, UNUSED_ATTR float right) {
+ FNLOG();
+
+ /* volume controlled in audioflinger mixer (digital) */
+
+ return -ENOSYS;
+}
+
+static int out_get_presentation_position(const struct audio_stream_out* stream,
+ uint64_t* frames,
+ struct timespec* timestamp) {
+ struct ha_stream_out* out = (struct ha_stream_out*)stream;
+
+ FNLOG();
+ if (stream == NULL || frames == NULL || timestamp == NULL) return -EINVAL;
+
+ int ret = -EWOULDBLOCK;
+ std::lock_guard<std::recursive_mutex> lock(*out->common.mutex);
+ uint64_t latency_frames =
+ (uint64_t)out_get_latency(stream) * out->common.cfg.rate / 1000;
+ if (out->frames_presented >= latency_frames) {
+ *frames = out->frames_presented - latency_frames;
+ clock_gettime(CLOCK_MONOTONIC,
+ timestamp); // could also be associated with out_write().
+ ret = 0;
+ }
+ return ret;
+}
+
+static int out_get_render_position(const struct audio_stream_out* stream,
+ uint32_t* dsp_frames) {
+ struct ha_stream_out* out = (struct ha_stream_out*)stream;
+
+ FNLOG();
+ if (stream == NULL || dsp_frames == NULL) return -EINVAL;
+
+ std::lock_guard<std::recursive_mutex> lock(*out->common.mutex);
+ uint64_t latency_frames =
+ (uint64_t)out_get_latency(stream) * out->common.cfg.rate / 1000;
+ if (out->frames_rendered >= latency_frames) {
+ *dsp_frames = (uint32_t)(out->frames_rendered - latency_frames);
+ } else {
+ *dsp_frames = 0;
+ }
+ return 0;
+}
+
+static int out_add_audio_effect(UNUSED_ATTR const struct audio_stream* stream,
+ UNUSED_ATTR effect_handle_t effect) {
+ FNLOG();
+ return 0;
+}
+
+static int out_remove_audio_effect(
+ UNUSED_ATTR const struct audio_stream* stream,
+ UNUSED_ATTR effect_handle_t effect) {
+ FNLOG();
+ return 0;
+}
+
+/*
+ * AUDIO INPUT STREAM
+ */
+
+static uint32_t in_get_sample_rate(const struct audio_stream* stream) {
+ struct ha_stream_in* in = (struct ha_stream_in*)stream;
+
+ FNLOG();
+ return in->common.cfg.rate;
+}
+
+static int in_set_sample_rate(struct audio_stream* stream, uint32_t rate) {
+ struct ha_stream_in* in = (struct ha_stream_in*)stream;
+
+ FNLOG();
+
+ if (in->common.cfg.rate > 0 && in->common.cfg.rate == rate)
+ return 0;
+ else
+ return -1;
+}
+
+static size_t in_get_buffer_size(
+ UNUSED_ATTR const struct audio_stream* stream) {
+ FNLOG();
+ return 320;
+}
+
+static uint32_t in_get_channels(const struct audio_stream* stream) {
+ struct ha_stream_in* in = (struct ha_stream_in*)stream;
+
+ FNLOG();
+ return in->common.cfg.channel_mask;
+}
+
+static audio_format_t in_get_format(
+ UNUSED_ATTR const struct audio_stream* stream) {
+ FNLOG();
+ return AUDIO_FORMAT_PCM_16_BIT;
+}
+
+static int in_set_format(UNUSED_ATTR struct audio_stream* stream,
+ UNUSED_ATTR audio_format_t format) {
+ FNLOG();
+ if (format == AUDIO_FORMAT_PCM_16_BIT)
+ return 0;
+ else
+ return -1;
+}
+
+static int in_standby(UNUSED_ATTR struct audio_stream* stream) {
+ FNLOG();
+ return 0;
+}
+
+static int in_dump(UNUSED_ATTR const struct audio_stream* stream,
+ UNUSED_ATTR int fd) {
+ FNLOG();
+ return 0;
+}
+
+static int in_set_parameters(UNUSED_ATTR struct audio_stream* stream,
+ UNUSED_ATTR const char* kvpairs) {
+ FNLOG();
+ return 0;
+}
+
+static char* in_get_parameters(UNUSED_ATTR const struct audio_stream* stream,
+ UNUSED_ATTR const char* keys) {
+ FNLOG();
+ return strdup("");
+}
+
+static int in_set_gain(UNUSED_ATTR struct audio_stream_in* stream,
+ UNUSED_ATTR float gain) {
+ FNLOG();
+ return 0;
+}
+
+static ssize_t in_read(struct audio_stream_in* stream, void* buffer,
+ size_t bytes) {
+ struct ha_stream_in* in = (struct ha_stream_in*)stream;
+ int read;
+ int us_delay;
+
+ DEBUG("read %zu bytes, state: %d", bytes, in->common.state);
+
+ std::unique_lock<std::recursive_mutex> lock(*in->common.mutex);
+ if (in->common.state == AUDIO_HA_STATE_SUSPENDED ||
+ in->common.state == AUDIO_HA_STATE_STOPPING) {
+ DEBUG("stream suspended");
+ goto error;
+ }
+
+ /* only allow autostarting if we are in stopped or standby */
+ if ((in->common.state == AUDIO_HA_STATE_STOPPED) ||
+ (in->common.state == AUDIO_HA_STATE_STANDBY)) {
+ if (start_audio_datapath(&in->common) < 0) {
+ goto error;
+ }
+ } else if (in->common.state != AUDIO_HA_STATE_STARTED) {
+ ERROR("stream not in stopped or standby");
+ goto error;
+ }
+
+ lock.unlock();
+ read = skt_read(in->common.audio_fd, buffer, bytes);
+ lock.lock();
+ if (read == -1) {
+ skt_disconnect(in->common.audio_fd);
+ in->common.audio_fd = AUDIO_SKT_DISCONNECTED;
+ if ((in->common.state != AUDIO_HA_STATE_SUSPENDED) &&
+ (in->common.state != AUDIO_HA_STATE_STOPPING)) {
+ in->common.state = AUDIO_HA_STATE_STOPPED;
+ } else {
+ ERROR("read failed : stream suspended, avoid resetting state");
+ }
+ goto error;
+ } else if (read == 0) {
+ DEBUG("read time out - return zeros");
+ memset(buffer, 0, bytes);
+ read = bytes;
+ }
+ lock.unlock();
+
+ DEBUG("read %d bytes out of %zu bytes", read, bytes);
+ return read;
+
+error:
+ memset(buffer, 0, bytes);
+ us_delay = calc_audiotime_usec(in->common.cfg, bytes);
+ DEBUG("emulate ha read delay (%d us)", us_delay);
+
+ usleep(us_delay);
+ return bytes;
+}
+
+static uint32_t in_get_input_frames_lost(
+ UNUSED_ATTR struct audio_stream_in* stream) {
+ FNLOG();
+ return 0;
+}
+
+static int in_add_audio_effect(UNUSED_ATTR const struct audio_stream* stream,
+ UNUSED_ATTR effect_handle_t effect) {
+ FNLOG();
+ return 0;
+}
+
+static int in_remove_audio_effect(UNUSED_ATTR const struct audio_stream* stream,
+ UNUSED_ATTR effect_handle_t effect) {
+ FNLOG();
+
+ return 0;
+}
+
+static int adev_open_output_stream(struct audio_hw_device* dev,
+ UNUSED_ATTR audio_io_handle_t handle,
+ UNUSED_ATTR audio_devices_t devices,
+ UNUSED_ATTR audio_output_flags_t flags,
+ struct audio_config* config,
+ struct audio_stream_out** stream_out,
+ UNUSED_ATTR const char* address)
+
+{
+ struct ha_audio_device* ha_dev = (struct ha_audio_device*)dev;
+ struct ha_stream_out* out;
+ int ret = 0;
+
+ INFO("opening output");
+ // protect against adev->output and stream_out from being inconsistent
+ std::lock_guard<std::recursive_mutex> lock(*ha_dev->mutex);
+ out = (struct ha_stream_out*)calloc(1, sizeof(struct ha_stream_out));
+
+ if (!out) return -ENOMEM;
+
+ 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.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->stream.get_presentation_position = out_get_presentation_position;
+
+ /* initialize ha specifics */
+ ha_stream_common_init(&out->common);
+
+ // Make sure we always have the feeding parameters configured
+ btav_a2dp_codec_config_t codec_config;
+ btav_a2dp_codec_config_t codec_capability;
+ if (ha_read_output_audio_config(&out->common, &codec_config,
+ &codec_capability,
+ true /* update_stream_config */) < 0) {
+ ERROR("ha_read_output_audio_config failed");
+ ret = -1;
+ goto err_open;
+ }
+ // ha_read_output_audio_config() opens the socket control path (or fails)
+
+ /* set output config values */
+ if (config != nullptr) {
+ // Try to use the config parameters and send it to the remote side
+ // TODO: Shall we use out_set_format() and similar?
+ if (config->format != 0) out->common.cfg.format = config->format;
+ if (config->sample_rate != 0) out->common.cfg.rate = config->sample_rate;
+ if (config->channel_mask != 0)
+ out->common.cfg.channel_mask = config->channel_mask;
+ if ((out->common.cfg.format != 0) || (out->common.cfg.rate != 0) ||
+ (out->common.cfg.channel_mask != 0)) {
+ if (ha_write_output_audio_config(&out->common) < 0) {
+ ERROR("ha_write_output_audio_config failed");
+ ret = -1;
+ goto err_open;
+ }
+ // Read again and make sure we use the same parameters as the remote side
+ if (ha_read_output_audio_config(&out->common, &codec_config,
+ &codec_capability,
+ true /* update_stream_config */) < 0) {
+ ERROR("ha_read_output_audio_config failed");
+ ret = -1;
+ goto err_open;
+ }
+ }
+ config->format = out_get_format((const struct audio_stream*)&out->stream);
+ config->sample_rate =
+ out_get_sample_rate((const struct audio_stream*)&out->stream);
+ config->channel_mask =
+ out_get_channels((const struct audio_stream*)&out->stream);
+
+ INFO(
+ "Output stream config: format=0x%x sample_rate=%d channel_mask=0x%x "
+ "buffer_sz=%zu",
+ config->format, config->sample_rate, config->channel_mask,
+ out->common.buffer_sz);
+ }
+ *stream_out = &out->stream;
+ ha_dev->output = out;
+
+ DEBUG("success");
+ /* Delay to ensure Headset is in proper state when START is initiated from
+ * DUT immediately after the connection due to ongoing music playback. */
+ usleep(250000);
+ return 0;
+
+err_open:
+ ha_stream_common_destroy(&out->common);
+ free(out);
+ *stream_out = NULL;
+ ha_dev->output = NULL;
+ ERROR("failed");
+ return ret;
+}
+
+static void adev_close_output_stream(struct audio_hw_device* dev,
+ struct audio_stream_out* stream) {
+ struct ha_audio_device* ha_dev = (struct ha_audio_device*)dev;
+ struct ha_stream_out* out = (struct ha_stream_out*)stream;
+
+ // prevent interference with adev_set_parameters.
+ std::lock_guard<std::recursive_mutex> lock(*ha_dev->mutex);
+ {
+ std::lock_guard<std::recursive_mutex> lock(*out->common.mutex);
+ const ha_state_t state = out->common.state;
+ INFO("closing output (state %d)", (int)state);
+ if ((state == AUDIO_HA_STATE_STARTED) ||
+ (state == AUDIO_HA_STATE_STOPPING)) {
+ stop_audio_datapath(&out->common);
+ }
+
+ skt_disconnect(out->common.ctrl_fd);
+ out->common.ctrl_fd = AUDIO_SKT_DISCONNECTED;
+ }
+
+ ha_stream_common_destroy(&out->common);
+ free(stream);
+ ha_dev->output = NULL;
+
+ DEBUG("done");
+}
+
+static int adev_set_parameters(struct audio_hw_device* dev,
+ const char* kvpairs) {
+ struct ha_audio_device* ha_dev = (struct ha_audio_device*)dev;
+ int retval = 0;
+
+ // prevent interference with adev_close_output_stream
+ std::lock_guard<std::recursive_mutex> lock(*ha_dev->mutex);
+ struct ha_stream_out* out = ha_dev->output;
+
+ if (out == NULL) return retval;
+
+ INFO("state %d", out->common.state);
+
+ retval =
+ out->stream.common.set_parameters((struct audio_stream*)out, kvpairs);
+
+ return retval;
+}
+
+static char* adev_get_parameters(UNUSED_ATTR const struct audio_hw_device* dev,
+ const char* keys) {
+ FNLOG();
+
+ std::unordered_map<std::string, std::string> params =
+ hash_map_utils_new_from_string_params(keys);
+ hash_map_utils_dump_string_keys_string_values(params);
+
+ return strdup("");
+}
+
+static int adev_init_check(UNUSED_ATTR const struct audio_hw_device* dev) {
+ FNLOG();
+
+ return 0;
+}
+
+static int adev_set_voice_volume(UNUSED_ATTR struct audio_hw_device* dev,
+ UNUSED_ATTR float volume) {
+ FNLOG();
+
+ return -ENOSYS;
+}
+
+static int adev_set_master_volume(UNUSED_ATTR struct audio_hw_device* dev,
+ UNUSED_ATTR float volume) {
+ FNLOG();
+
+ return -ENOSYS;
+}
+
+static int adev_set_mode(UNUSED_ATTR struct audio_hw_device* dev,
+ UNUSED_ATTR audio_mode_t mode) {
+ FNLOG();
+
+ return 0;
+}
+
+static int adev_set_mic_mute(UNUSED_ATTR struct audio_hw_device* dev,
+ UNUSED_ATTR bool state) {
+ FNLOG();
+
+ return -ENOSYS;
+}
+
+static int adev_get_mic_mute(UNUSED_ATTR const struct audio_hw_device* dev,
+ UNUSED_ATTR bool* state) {
+ FNLOG();
+
+ return -ENOSYS;
+}
+
+static size_t adev_get_input_buffer_size(
+ UNUSED_ATTR const struct audio_hw_device* dev,
+ UNUSED_ATTR const struct audio_config* config) {
+ FNLOG();
+
+ return 320;
+}
+
+static int adev_open_input_stream(struct audio_hw_device* dev,
+ UNUSED_ATTR audio_io_handle_t handle,
+ UNUSED_ATTR audio_devices_t devices,
+ UNUSED_ATTR struct audio_config* config,
+ struct audio_stream_in** stream_in,
+ UNUSED_ATTR audio_input_flags_t flags,
+ UNUSED_ATTR const char* address,
+ UNUSED_ATTR audio_source_t source) {
+ struct ha_audio_device* ha_dev = (struct ha_audio_device*)dev;
+ struct ha_stream_in* in;
+ int ret;
+
+ FNLOG();
+
+ // protect against adev->input and stream_in from being inconsistent
+ std::lock_guard<std::recursive_mutex> lock(*ha_dev->mutex);
+ in = (struct ha_stream_in*)calloc(1, sizeof(struct ha_stream_in));
+
+ if (!in) return -ENOMEM;
+
+ in->stream.common.get_sample_rate = in_get_sample_rate;
+ in->stream.common.set_sample_rate = in_set_sample_rate;
+ in->stream.common.get_buffer_size = in_get_buffer_size;
+ in->stream.common.get_channels = in_get_channels;
+ in->stream.common.get_format = in_get_format;
+ in->stream.common.set_format = in_set_format;
+ in->stream.common.standby = in_standby;
+ in->stream.common.dump = in_dump;
+ in->stream.common.set_parameters = in_set_parameters;
+ in->stream.common.get_parameters = in_get_parameters;
+ in->stream.common.add_audio_effect = in_add_audio_effect;
+ in->stream.common.remove_audio_effect = in_remove_audio_effect;
+ in->stream.set_gain = in_set_gain;
+ in->stream.read = in_read;
+ in->stream.get_input_frames_lost = in_get_input_frames_lost;
+
+ /* initialize ha specifics */
+ ha_stream_common_init(&in->common);
+
+ *stream_in = &in->stream;
+ ha_dev->input = in;
+
+ if (ha_read_input_audio_config(&in->common) < 0) {
+ ERROR("ha_read_input_audio_config failed (%s)", strerror(errno));
+ ret = -1;
+ goto err_open;
+ }
+ // ha_read_input_audio_config() opens socket control path (or fails)
+
+ DEBUG("success");
+ return 0;
+
+err_open:
+ ha_stream_common_destroy(&in->common);
+ free(in);
+ *stream_in = NULL;
+ ha_dev->input = NULL;
+ ERROR("failed");
+ return ret;
+}
+
+static void adev_close_input_stream(struct audio_hw_device* dev,
+ struct audio_stream_in* stream) {
+ struct ha_audio_device* ha_dev = (struct ha_audio_device*)dev;
+ struct ha_stream_in* in = (struct ha_stream_in*)stream;
+
+ std::lock_guard<std::recursive_mutex> lock(*ha_dev->mutex);
+ {
+ std::lock_guard<std::recursive_mutex> lock(*in->common.mutex);
+ const ha_state_t state = in->common.state;
+ INFO("closing input (state %d)", (int)state);
+
+ if ((state == AUDIO_HA_STATE_STARTED) || (state == AUDIO_HA_STATE_STOPPING))
+ stop_audio_datapath(&in->common);
+
+ skt_disconnect(in->common.ctrl_fd);
+ in->common.ctrl_fd = AUDIO_SKT_DISCONNECTED;
+ }
+ ha_stream_common_destroy(&in->common);
+ free(stream);
+ ha_dev->input = NULL;
+
+ DEBUG("done");
+}
+
+static int adev_dump(UNUSED_ATTR const audio_hw_device_t* device,
+ UNUSED_ATTR int fd) {
+ FNLOG();
+
+ return 0;
+}
+
+static int adev_close(hw_device_t* device) {
+ struct ha_audio_device* ha_dev = (struct ha_audio_device*)device;
+ FNLOG();
+
+ delete ha_dev->mutex;
+ ha_dev->mutex = nullptr;
+ free(device);
+ return 0;
+}
+
+static int adev_open(const hw_module_t* module, const char* name,
+ hw_device_t** device) {
+ struct ha_audio_device* adev;
+
+ INFO(" adev_open in ha_hw module");
+ FNLOG();
+
+ if (strcmp(name, AUDIO_HARDWARE_INTERFACE) != 0) {
+ ERROR("interface %s not matching [%s]", name, AUDIO_HARDWARE_INTERFACE);
+ return -EINVAL;
+ }
+
+ adev = (struct ha_audio_device*)calloc(1, sizeof(struct ha_audio_device));
+
+ if (!adev) return -ENOMEM;
+
+ adev->mutex = new std::recursive_mutex;
+
+ adev->device.common.tag = HARDWARE_DEVICE_TAG;
+ adev->device.common.version = AUDIO_DEVICE_API_VERSION_2_0;
+ adev->device.common.module = (struct hw_module_t*)module;
+ adev->device.common.close = adev_close;
+
+ 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;
+
+ adev->output = NULL;
+
+ *device = &adev->device.common;
+
+ return 0;
+}
+
+static struct hw_module_methods_t hal_module_methods = {
+ .open = adev_open,
+};
+
+__attribute__((
+ visibility("default"))) struct audio_module HAL_MODULE_INFO_SYM = {
+ .common =
+ {
+ .tag = HARDWARE_MODULE_TAG,
+ .version_major = 1,
+ .version_minor = 0,
+ .id = AUDIO_HARDWARE_MODULE_ID,
+ .name = "Hearing Aid Audio HW HAL",
+ .author = "The Android Open Source Project",
+ .methods = &hal_module_methods,
+ },
+};
diff --git a/audio_hearing_aid_hw/src/audio_hearing_aid_hw_utils.cc b/audio_hearing_aid_hw/src/audio_hearing_aid_hw_utils.cc
new file mode 100644
index 0000000..2354632
--- /dev/null
+++ b/audio_hearing_aid_hw/src/audio_hearing_aid_hw_utils.cc
@@ -0,0 +1,41 @@
+/******************************************************************************
+ *
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ ******************************************************************************/
+
+#include "audio_hearing_aid_hw.h"
+
+#define CASE_RETURN_STR(const) \
+ case const: \
+ return #const;
+
+const char* audio_ha_hw_dump_ctrl_event(tHEARING_AID_CTRL_CMD event) {
+ switch (event) {
+ CASE_RETURN_STR(HEARING_AID_CTRL_CMD_NONE)
+ CASE_RETURN_STR(HEARING_AID_CTRL_CMD_CHECK_READY)
+ CASE_RETURN_STR(HEARING_AID_CTRL_CMD_START)
+ CASE_RETURN_STR(HEARING_AID_CTRL_CMD_STOP)
+ CASE_RETURN_STR(HEARING_AID_CTRL_CMD_SUSPEND)
+ CASE_RETURN_STR(HEARING_AID_CTRL_GET_INPUT_AUDIO_CONFIG)
+ CASE_RETURN_STR(HEARING_AID_CTRL_GET_OUTPUT_AUDIO_CONFIG)
+ CASE_RETURN_STR(HEARING_AID_CTRL_SET_OUTPUT_AUDIO_CONFIG)
+ CASE_RETURN_STR(HEARING_AID_CTRL_CMD_OFFLOAD_START)
+ default:
+ break;
+ }
+
+ return "UNKNOWN HEARING_AID_CTRL_CMD";
+}
diff --git a/audio_hearing_aid_hw/test/audio_hearing_aid_hw_test.cc b/audio_hearing_aid_hw/test/audio_hearing_aid_hw_test.cc
new file mode 100644
index 0000000..c5d0e2b
--- /dev/null
+++ b/audio_hearing_aid_hw/test/audio_hearing_aid_hw_test.cc
@@ -0,0 +1,146 @@
+/******************************************************************************
+ *
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ ******************************************************************************/
+
+#include <gtest/gtest.h>
+
+#include "audio_hearing_aid_hw/include/audio_hearing_aid_hw.h"
+
+namespace {
+static uint32_t codec_sample_rate2value(
+ btav_a2dp_codec_sample_rate_t codec_sample_rate) {
+ switch (codec_sample_rate) {
+ case BTAV_A2DP_CODEC_SAMPLE_RATE_44100:
+ return 44100;
+ case BTAV_A2DP_CODEC_SAMPLE_RATE_48000:
+ return 48000;
+ case BTAV_A2DP_CODEC_SAMPLE_RATE_88200:
+ return 88200;
+ case BTAV_A2DP_CODEC_SAMPLE_RATE_96000:
+ return 96000;
+ case BTAV_A2DP_CODEC_SAMPLE_RATE_176400:
+ return 176400;
+ case BTAV_A2DP_CODEC_SAMPLE_RATE_192000:
+ return 192000;
+ case BTAV_A2DP_CODEC_SAMPLE_RATE_16000:
+ return 16000;
+ case BTAV_A2DP_CODEC_SAMPLE_RATE_24000:
+ return 24000;
+ case BTAV_A2DP_CODEC_SAMPLE_RATE_NONE:
+ break;
+ }
+ return 0;
+}
+
+static uint32_t codec_bits_per_sample2value(
+ btav_a2dp_codec_bits_per_sample_t codec_bits_per_sample) {
+ switch (codec_bits_per_sample) {
+ case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16:
+ return 16;
+ case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_24:
+ return 24;
+ case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_32:
+ return 32;
+ case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_NONE:
+ break;
+ }
+ return 0;
+}
+
+static uint32_t codec_channel_mode2value(
+ btav_a2dp_codec_channel_mode_t codec_channel_mode) {
+ switch (codec_channel_mode) {
+ case BTAV_A2DP_CODEC_CHANNEL_MODE_MONO:
+ return 1;
+ case BTAV_A2DP_CODEC_CHANNEL_MODE_STEREO:
+ return 2;
+ case BTAV_A2DP_CODEC_CHANNEL_MODE_NONE:
+ break;
+ }
+ return 0;
+}
+
+} // namespace
+
+class AudioA2dpHwTest : public ::testing::Test {
+ protected:
+ AudioA2dpHwTest() {}
+
+ private:
+};
+
+TEST_F(AudioA2dpHwTest, test_compute_buffer_size) {
+ const btav_a2dp_codec_sample_rate_t codec_sample_rate_array[] = {
+ BTAV_A2DP_CODEC_SAMPLE_RATE_NONE, BTAV_A2DP_CODEC_SAMPLE_RATE_44100,
+ BTAV_A2DP_CODEC_SAMPLE_RATE_48000, BTAV_A2DP_CODEC_SAMPLE_RATE_88200,
+ BTAV_A2DP_CODEC_SAMPLE_RATE_96000, BTAV_A2DP_CODEC_SAMPLE_RATE_176400,
+ BTAV_A2DP_CODEC_SAMPLE_RATE_192000};
+
+ const btav_a2dp_codec_bits_per_sample_t codec_bits_per_sample_array[] = {
+ BTAV_A2DP_CODEC_BITS_PER_SAMPLE_NONE, BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16,
+ BTAV_A2DP_CODEC_BITS_PER_SAMPLE_24, BTAV_A2DP_CODEC_BITS_PER_SAMPLE_32};
+
+ const btav_a2dp_codec_channel_mode_t codec_channel_mode_array[] = {
+ BTAV_A2DP_CODEC_CHANNEL_MODE_NONE, BTAV_A2DP_CODEC_CHANNEL_MODE_MONO,
+ BTAV_A2DP_CODEC_CHANNEL_MODE_STEREO};
+
+ for (const auto codec_sample_rate : codec_sample_rate_array) {
+ for (const auto codec_bits_per_sample : codec_bits_per_sample_array) {
+ for (const auto codec_channel_mode : codec_channel_mode_array) {
+ size_t buffer_size = audio_ha_hw_stream_compute_buffer_size(
+ codec_sample_rate, codec_bits_per_sample, codec_channel_mode);
+
+ // Check for invalid input
+ if ((codec_sample_rate == BTAV_A2DP_CODEC_SAMPLE_RATE_NONE) ||
+ (codec_bits_per_sample == BTAV_A2DP_CODEC_BITS_PER_SAMPLE_NONE) ||
+ (codec_channel_mode == BTAV_A2DP_CODEC_CHANNEL_MODE_NONE)) {
+ EXPECT_EQ(buffer_size,
+ static_cast<size_t>(AUDIO_STREAM_OUTPUT_BUFFER_SZ));
+ continue;
+ }
+
+ uint32_t sample_rate = codec_sample_rate2value(codec_sample_rate);
+ EXPECT_TRUE(sample_rate != 0);
+
+ uint32_t bits_per_sample =
+ codec_bits_per_sample2value(codec_bits_per_sample);
+ EXPECT_TRUE(bits_per_sample != 0);
+
+ uint32_t number_of_channels =
+ codec_channel_mode2value(codec_channel_mode);
+ EXPECT_TRUE(number_of_channels != 0);
+
+ const uint64_t time_period_ms = 20; // TODO: Must be a parameter
+ size_t expected_buffer_size =
+ (time_period_ms * AUDIO_STREAM_OUTPUT_BUFFER_PERIODS * sample_rate *
+ number_of_channels * (bits_per_sample / 8)) /
+ 1000;
+
+ // Compute the divisor and adjust the buffer size
+ const size_t divisor = (AUDIO_STREAM_OUTPUT_BUFFER_PERIODS * 16 *
+ number_of_channels * bits_per_sample) /
+ 8;
+ const size_t remainder = expected_buffer_size % divisor;
+ if (remainder != 0) {
+ expected_buffer_size += divisor - remainder;
+ }
+
+ EXPECT_EQ(buffer_size, expected_buffer_size);
+ }
+ }
+ }
+}
diff --git a/binder/android/bluetooth/IBluetoothA2dp.aidl b/binder/android/bluetooth/IBluetoothA2dp.aidl
index f852d30..6606a1b 100644
--- a/binder/android/bluetooth/IBluetoothA2dp.aidl
+++ b/binder/android/bluetooth/IBluetoothA2dp.aidl
@@ -37,7 +37,6 @@
boolean setPriority(in BluetoothDevice device, int priority);
int getPriority(in BluetoothDevice device);
boolean isAvrcpAbsoluteVolumeSupported();
- oneway void adjustAvrcpAbsoluteVolume(int direction);
oneway void setAvrcpAbsoluteVolume(int volume);
boolean isA2dpPlaying(in BluetoothDevice device);
BluetoothCodecStatus getCodecStatus(in BluetoothDevice device);
diff --git a/bta/Android.bp b/bta/Android.bp
index e7f94be..5759183 100644
--- a/bta/Android.bp
+++ b/bta/Android.bp
@@ -66,6 +66,8 @@
"gatt/bta_gatts_api.cc",
"gatt/bta_gatts_main.cc",
"gatt/bta_gatts_utils.cc",
+ "hearing_aid/hearing_aid.cc",
+ "hearing_aid/hearing_aid_audio_source.cc",
"hf_client/bta_hf_client_act.cc",
"hf_client/bta_hf_client_api.cc",
"hf_client/bta_hf_client_at.cc",
@@ -107,6 +109,10 @@
"sys/bta_sys_main.cc",
"sys/utl.cc",
],
+
+ whole_static_libs: [
+ "libaudio-hearing-aid-hw-utils",
+ ],
}
// bta unit tests for target
diff --git a/bta/av/bta_av_aact.cc b/bta/av/bta_av_aact.cc
index 2429bff..c5ba927 100644
--- a/bta/av/bta_av_aact.cc
+++ b/bta/av/bta_av_aact.cc
@@ -873,7 +873,7 @@
/* if de-registering shut everything down */
msg.hdr.layer_specific = p_scb->hndl;
p_scb->started = false;
- p_scb->current_codec = nullptr;
+ p_scb->use_rtp_header_marker_bit = false;
p_scb->cong = false;
p_scb->role = role;
p_scb->cur_psc_mask = 0;
@@ -1326,7 +1326,7 @@
/* close stream */
p_scb->started = false;
- p_scb->current_codec = nullptr;
+ p_scb->use_rtp_header_marker_bit = false;
/* drop the buffers queued in L2CAP */
L2CA_FlushChannel(p_scb->l2c_cid, L2CAP_FLUSH_CHANS_ALL);
@@ -2028,7 +2028,7 @@
if (p_scb->cong) return;
- if (p_scb->current_codec->useRtpHeaderMarkerBit()) {
+ if (p_scb->use_rtp_header_marker_bit) {
m_pt |= AVDT_MARKER_SET;
}
@@ -2157,7 +2157,11 @@
p_scb->wait, p_scb->role);
p_scb->started = true;
- p_scb->current_codec = bta_av_get_a2dp_current_codec();
+ // The RTP Header marker bit
+ A2dpCodecConfig* codec_config =
+ bta_av_get_a2dp_peer_current_codec(p_scb->peer_addr);
+ CHECK(codec_config != nullptr);
+ p_scb->use_rtp_header_marker_bit = codec_config->useRtpHeaderMarkerBit();
if (p_scb->sco_suspend) {
p_scb->sco_suspend = false;
@@ -2182,8 +2186,9 @@
p_data = (tBTA_AV_DATA*)&hdr;
hdr.offset = BTA_AV_RS_FAIL;
}
- APPL_TRACE_DEBUG("%s: peer %s wait:0x%x", __func__,
- p_scb->peer_addr.ToString().c_str(), p_scb->wait);
+ APPL_TRACE_DEBUG("%s: peer %s wait:0x%x use_rtp_header_marker_bit:%s",
+ __func__, p_scb->peer_addr.ToString().c_str(), p_scb->wait,
+ (p_scb->use_rtp_header_marker_bit) ? "true" : "false");
if (p_data && (p_data->hdr.offset != BTA_AV_RS_NONE)) {
p_scb->wait &= ~BTA_AV_WAIT_ROLE_SW_BITS;
diff --git a/bta/av/bta_av_int.h b/bta/av/bta_av_int.h
index e97321d..c453ed5 100644
--- a/bta/av/bta_av_int.h
+++ b/bta/av/bta_av_int.h
@@ -492,7 +492,9 @@
uint8_t rc_handle; /* connected AVRCP handle */
bool use_rc; /* true if AVRCP is allowed */
bool started; /* true if stream started */
- A2dpCodecConfig* current_codec; /* The current A2DP codec */
+ bool use_rtp_header_marker_bit; /* true if the encoded data packets have RTP
+ * headers, and the Marker bit in the header
+ * is set according to RFC 6416 */
uint8_t
co_started; /* non-zero, if stream started from call-out perspective */
bool recfg_sup; /* true if the first attempt to reconfigure the stream was
diff --git a/bta/hearing_aid/hearing_aid.cc b/bta/hearing_aid/hearing_aid.cc
new file mode 100644
index 0000000..65eb1a1
--- /dev/null
+++ b/bta/hearing_aid/hearing_aid.cc
@@ -0,0 +1,48 @@
+/******************************************************************************
+ *
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ ******************************************************************************/
+
+#include "bta_hearing_aid_api.h"
+
+#include <base/bind.h>
+#include <base/logging.h>
+
+using base::Closure;
+
+void HearingAid::Initialize(
+ bluetooth::hearing_aid::HearingAidCallbacks* callbacks, Closure initCb) {
+ CHECK(false) << "unimplemented yet";
+}
+
+bool HearingAid::IsInitialized() {
+ CHECK(false) << "unimplemented yet";
+ return false;
+}
+
+HearingAid* HearingAid::Get() {
+ CHECK(false) << "unimplemented yet";
+ return nullptr;
+};
+
+void HearingAid::AddFromStorage(const RawAddress& address, uint16_t psm,
+ uint8_t capabilities, uint8_t codecs,
+ uint16_t audio_control_point_handle,
+ uint16_t volume_handle, uint64_t hiSyncId) {
+ CHECK(false) << "unimplemented yet";
+};
+
+void HearingAid::CleanUp() { CHECK(false) << "unimplemented yet"; };
diff --git a/bta/hearing_aid/hearing_aid_audio_source.cc b/bta/hearing_aid/hearing_aid_audio_source.cc
new file mode 100644
index 0000000..276957b
--- /dev/null
+++ b/bta/hearing_aid/hearing_aid_audio_source.cc
@@ -0,0 +1,262 @@
+/******************************************************************************
+ *
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ ******************************************************************************/
+
+#include "audio_hearing_aid_hw/include/audio_hearing_aid_hw.h"
+#include "bta_hearing_aid_api.h"
+#include "osi/include/alarm.h"
+#include "uipc.h"
+
+#include <base/files/file_util.h>
+#include <base/strings/string_number_conversions.h>
+#include <include/hardware/bt_av.h>
+
+using base::FilePath;
+extern const char* audio_ha_hw_dump_ctrl_event(tHEARING_AID_CTRL_CMD event);
+
+namespace {
+int bit_rate = 16;
+int sample_rate = 16000;
+int data_interval_ms = 20 /* msec */;
+int num_channels = 2;
+alarm_t* audio_timer = nullptr;
+
+HearingAidAudioReceiver* localAudioReceiver;
+std::unique_ptr<tUIPC_STATE> uipc_hearing_aid;
+
+void send_audio_data(void*) {
+ int bytes_per_tick =
+ (num_channels * sample_rate * data_interval_ms * (bit_rate / 8)) / 1000;
+
+ uint16_t event;
+ uint8_t p_buf[bytes_per_tick];
+
+ uint32_t bytes_read = UIPC_Read(*uipc_hearing_aid, UIPC_CH_ID_AV_AUDIO,
+ &event, p_buf, bytes_per_tick);
+
+ VLOG(2) << "bytes_read: " << bytes_read;
+
+ std::vector<uint8_t> data(p_buf, p_buf + bytes_read);
+
+ localAudioReceiver->OnAudioDataReady(data);
+}
+
+void hearing_aid_send_ack(tHEARING_AID_CTRL_ACK status) {
+ uint8_t ack = status;
+ DVLOG(2) << "Hearing Aid audio ctrl ack: " << status;
+ UIPC_Send(*uipc_hearing_aid, UIPC_CH_ID_AV_CTRL, 0, &ack, sizeof(ack));
+}
+
+void hearing_aid_data_cb(tUIPC_CH_ID, tUIPC_EVENT event) {
+ DVLOG(2) << "Hearing Aid audio data event: " << event;
+ switch (event) {
+ case UIPC_OPEN_EVT:
+ /*
+ * Read directly from media task from here on (keep callback for
+ * connection events.
+ */
+ UIPC_Ioctl(*uipc_hearing_aid, UIPC_CH_ID_AV_AUDIO,
+ UIPC_REG_REMOVE_ACTIVE_READSET, NULL);
+ UIPC_Ioctl(*uipc_hearing_aid, UIPC_CH_ID_AV_AUDIO, UIPC_SET_READ_POLL_TMO,
+ reinterpret_cast<void*>(0));
+
+ audio_timer = alarm_new_periodic("hearing_aid_data_timer");
+ alarm_set_on_mloop(audio_timer, data_interval_ms, send_audio_data,
+ nullptr);
+ break;
+ case UIPC_CLOSE_EVT:
+ hearing_aid_send_ack(HEARING_AID_CTRL_ACK_SUCCESS);
+ if (audio_timer) {
+ alarm_cancel(audio_timer);
+ }
+ break;
+ default:
+ LOG(ERROR) << "Hearing Aid audio data event not recognized:" << event;
+ }
+}
+
+void hearing_aid_recv_ctrl_data() {
+ tHEARING_AID_CTRL_CMD cmd = HEARING_AID_CTRL_CMD_NONE;
+ int n;
+
+ uint8_t read_cmd = 0; /* The read command size is one octet */
+ n = UIPC_Read(*uipc_hearing_aid, UIPC_CH_ID_AV_CTRL, NULL, &read_cmd, 1);
+ cmd = static_cast<tHEARING_AID_CTRL_CMD>(read_cmd);
+
+ /* detach on ctrl channel means audioflinger process was terminated */
+ if (n == 0) {
+ LOG(WARNING) << __func__ << "CTRL CH DETACHED";
+ UIPC_Close(*uipc_hearing_aid, UIPC_CH_ID_AV_CTRL);
+ return;
+ }
+
+ VLOG(2) << __func__ << " " << audio_ha_hw_dump_ctrl_event(cmd);
+ // a2dp_cmd_pending = cmd;
+
+ switch (cmd) {
+ case HEARING_AID_CTRL_CMD_CHECK_READY:
+ hearing_aid_send_ack(HEARING_AID_CTRL_ACK_SUCCESS);
+ break;
+
+ case HEARING_AID_CTRL_CMD_START:
+ localAudioReceiver->OnAudioResume();
+ // timer is restarted in UIPC_Open
+ UIPC_Open(*uipc_hearing_aid, UIPC_CH_ID_AV_AUDIO, hearing_aid_data_cb);
+ hearing_aid_send_ack(HEARING_AID_CTRL_ACK_SUCCESS);
+ break;
+
+ case HEARING_AID_CTRL_CMD_STOP:
+ hearing_aid_send_ack(HEARING_AID_CTRL_ACK_SUCCESS);
+ break;
+
+ case HEARING_AID_CTRL_CMD_SUSPEND:
+ if (audio_timer) alarm_cancel(audio_timer);
+ localAudioReceiver->OnAudioSuspend();
+ hearing_aid_send_ack(HEARING_AID_CTRL_ACK_SUCCESS);
+ break;
+
+ case HEARING_AID_CTRL_GET_OUTPUT_AUDIO_CONFIG: {
+ btav_a2dp_codec_config_t codec_config;
+ btav_a2dp_codec_config_t codec_capability;
+ if (sample_rate == 16000) {
+ codec_config.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_16000;
+ codec_capability.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_16000;
+ } else if (sample_rate == 24000) {
+ codec_config.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_24000;
+ codec_capability.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_24000;
+ } else {
+ LOG(FATAL) << "unsupported sample rate: " << sample_rate;
+ }
+
+ codec_config.bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16;
+ codec_capability.bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16;
+
+ codec_config.channel_mode = BTAV_A2DP_CODEC_CHANNEL_MODE_STEREO;
+ codec_capability.channel_mode = BTAV_A2DP_CODEC_CHANNEL_MODE_STEREO;
+
+ hearing_aid_send_ack(HEARING_AID_CTRL_ACK_SUCCESS);
+ // Send the current codec config
+ UIPC_Send(*uipc_hearing_aid, UIPC_CH_ID_AV_CTRL, 0,
+ reinterpret_cast<const uint8_t*>(&codec_config.sample_rate),
+ sizeof(btav_a2dp_codec_sample_rate_t));
+ UIPC_Send(*uipc_hearing_aid, UIPC_CH_ID_AV_CTRL, 0,
+ reinterpret_cast<const uint8_t*>(&codec_config.bits_per_sample),
+ sizeof(btav_a2dp_codec_bits_per_sample_t));
+ UIPC_Send(*uipc_hearing_aid, UIPC_CH_ID_AV_CTRL, 0,
+ reinterpret_cast<const uint8_t*>(&codec_config.channel_mode),
+ sizeof(btav_a2dp_codec_channel_mode_t));
+ // Send the current codec capability
+ UIPC_Send(*uipc_hearing_aid, UIPC_CH_ID_AV_CTRL, 0,
+ reinterpret_cast<const uint8_t*>(&codec_capability.sample_rate),
+ sizeof(btav_a2dp_codec_sample_rate_t));
+ UIPC_Send(
+ *uipc_hearing_aid, UIPC_CH_ID_AV_CTRL, 0,
+ reinterpret_cast<const uint8_t*>(&codec_capability.bits_per_sample),
+ sizeof(btav_a2dp_codec_bits_per_sample_t));
+ UIPC_Send(
+ *uipc_hearing_aid, UIPC_CH_ID_AV_CTRL, 0,
+ reinterpret_cast<const uint8_t*>(&codec_capability.channel_mode),
+ sizeof(btav_a2dp_codec_channel_mode_t));
+ break;
+ }
+
+ case HEARING_AID_CTRL_SET_OUTPUT_AUDIO_CONFIG: {
+ // TODO: we only support one config for now!
+ btav_a2dp_codec_config_t codec_config;
+ codec_config.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_NONE;
+ codec_config.bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_NONE;
+ codec_config.channel_mode = BTAV_A2DP_CODEC_CHANNEL_MODE_NONE;
+
+ hearing_aid_send_ack(HEARING_AID_CTRL_ACK_SUCCESS);
+ // Send the current codec config
+ if (UIPC_Read(*uipc_hearing_aid, UIPC_CH_ID_AV_CTRL, 0,
+ reinterpret_cast<uint8_t*>(&codec_config.sample_rate),
+ sizeof(btav_a2dp_codec_sample_rate_t)) !=
+ sizeof(btav_a2dp_codec_sample_rate_t)) {
+ LOG(ERROR) << __func__ << "Error reading sample rate from audio HAL";
+ break;
+ }
+ if (UIPC_Read(*uipc_hearing_aid, UIPC_CH_ID_AV_CTRL, 0,
+ reinterpret_cast<uint8_t*>(&codec_config.bits_per_sample),
+ sizeof(btav_a2dp_codec_bits_per_sample_t)) !=
+ sizeof(btav_a2dp_codec_bits_per_sample_t)) {
+ LOG(ERROR) << __func__
+ << "Error reading bits per sample from audio HAL";
+
+ break;
+ }
+ if (UIPC_Read(*uipc_hearing_aid, UIPC_CH_ID_AV_CTRL, 0,
+ reinterpret_cast<uint8_t*>(&codec_config.channel_mode),
+ sizeof(btav_a2dp_codec_channel_mode_t)) !=
+ sizeof(btav_a2dp_codec_channel_mode_t)) {
+ LOG(ERROR) << __func__ << "Error reading channel mode from audio HAL";
+
+ break;
+ }
+ LOG(INFO) << __func__ << " HEARING_AID_CTRL_SET_OUTPUT_AUDIO_CONFIG: "
+ << "sample_rate=" << codec_config.sample_rate
+ << "bits_per_sample=" << codec_config.bits_per_sample
+ << "channel_mode=" << codec_config.channel_mode;
+ break;
+ }
+
+ default:
+ LOG(ERROR) << __func__ << "UNSUPPORTED CMD: " << cmd;
+ hearing_aid_send_ack(HEARING_AID_CTRL_ACK_FAILURE);
+ break;
+ }
+ VLOG(2) << __func__ << " a2dp-ctrl-cmd : " << audio_ha_hw_dump_ctrl_event(cmd)
+ << " DONE";
+}
+
+void hearing_aid_ctrl_cb(tUIPC_CH_ID, tUIPC_EVENT event) {
+ VLOG(2) << "Hearing Aid audio ctrl event: " << event;
+ switch (event) {
+ case UIPC_OPEN_EVT:
+ break;
+ case UIPC_CLOSE_EVT:
+ UIPC_Open(*uipc_hearing_aid, UIPC_CH_ID_AV_CTRL, hearing_aid_ctrl_cb);
+ break;
+ case UIPC_RX_DATA_READY_EVT:
+ hearing_aid_recv_ctrl_data();
+ break;
+ default:
+ LOG(ERROR) << "Hearing Aid audio ctrl unrecognized event: " << event;
+ }
+}
+} // namespace
+
+void HearingAidAudioSource::Start(const CodecConfiguration& codecConfiguration,
+ HearingAidAudioReceiver* audioReceiver) {
+ localAudioReceiver = audioReceiver;
+ VLOG(2) << "Hearing Aid UIPC Open";
+}
+
+void HearingAidAudioSource::Stop() {
+ if (audio_timer) {
+ alarm_cancel(audio_timer);
+ }
+}
+
+void HearingAidAudioSource::Initialize() {
+ uipc_hearing_aid = UIPC_Init();
+ UIPC_Open(*uipc_hearing_aid, UIPC_CH_ID_AV_CTRL, hearing_aid_ctrl_cb);
+}
+
+void HearingAidAudioSource::CleanUp() {
+ UIPC_Close(*uipc_hearing_aid, UIPC_CH_ID_ALL);
+}
diff --git a/bta/include/bta_hearing_aid_api.h b/bta/include/bta_hearing_aid_api.h
new file mode 100644
index 0000000..6b1f249
--- /dev/null
+++ b/bta/include/bta_hearing_aid_api.h
@@ -0,0 +1,85 @@
+/******************************************************************************
+ *
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ ******************************************************************************/
+
+#pragma once
+
+#include <base/callback_forward.h>
+#include <hardware/bt_hearing_aid.h>
+
+using bluetooth::Uuid;
+
+/** Implementations of HearingAid will also implement this interface */
+class HearingAidAudioReceiver {
+ public:
+ virtual ~HearingAidAudioReceiver() = default;
+ virtual void OnAudioDataReady(const std::vector<uint8_t>& data) = 0;
+ virtual void OnAudioSuspend();
+ virtual void OnAudioResume();
+};
+
+class HearingAid {
+ public:
+ virtual ~HearingAid() = default;
+
+ static void Initialize(bluetooth::hearing_aid::HearingAidCallbacks* callbacks,
+ base::Closure initCb);
+ static void CleanUp();
+ static bool IsInitialized();
+ static HearingAid* Get();
+
+ static void AddFromStorage(const RawAddress& address, uint16_t psm,
+ uint8_t capabilities, uint8_t codec,
+ uint16_t audioControlPointHandle,
+ uint16_t volumeHandle, uint64_t hiSyncId);
+
+ virtual void Connect(const RawAddress& address) = 0;
+ virtual void Disconnect(const RawAddress& address) = 0;
+ virtual void SetVolume(int8_t volume) = 0;
+};
+
+/* Represents configuration of audio codec, as exchanged between hearing aid and
+ * phone.
+ * It can also be passed to the audio source to configure its parameters.
+ */
+struct CodecConfiguration {
+ /** sampling rate that the codec expects to receive from audio framework */
+ uint32_t sample_rate;
+
+ /** bitrate that codec expects to receive from audio framework in bits per
+ * channel */
+ uint32_t bit_rate;
+
+ /** Data interval determines how often we send samples to the remote. This
+ * should match how often we grab data from audio source, optionally we can
+ * grab data every 2 or 3 intervals, but this would increase latency.
+ *
+ * Value is provided in ms, must be divisable by 1.25 to make sure the
+ * connection interval is integer.
+ */
+ uint16_t data_interval_ms;
+};
+
+/** Represents source of audio for hearing aids */
+class HearingAidAudioSource {
+ public:
+ static void Start(const CodecConfiguration& codecConfiguration,
+ HearingAidAudioReceiver* audioReceiver);
+ static void Stop();
+ static void Initialize();
+ static void CleanUp();
+};
diff --git a/btif/Android.bp b/btif/Android.bp
index 88b3cfa..c23ffa5 100644
--- a/btif/Android.bp
+++ b/btif/Android.bp
@@ -54,6 +54,7 @@
"src/btif_gatt_server.cc",
"src/btif_gatt_test.cc",
"src/btif_gatt_util.cc",
+ "src/btif_hearing_aid.cc",
"src/btif_hf.cc",
"src/btif_hf_client.cc",
"src/btif_hh.cc",
diff --git a/btif/co/bta_av_co.cc b/btif/co/bta_av_co.cc
index 21842eb..edaa7b6 100644
--- a/btif/co/bta_av_co.cc
+++ b/btif/co/bta_av_co.cc
@@ -185,6 +185,14 @@
A2dpCodecConfig* GetActivePeerCurrentCodec();
/**
+ * Get the current codec configuration for a peer.
+ *
+ * @param peer_address the peer address
+ * @return the current codec configuration if found, otherwise nullptr
+ */
+ A2dpCodecConfig* GetPeerCurrentCodec(const RawAddress& peer_address);
+
+ /**
* Find the peer UUID for a given BTA AV handle.
*
* @param bta_av_handle the BTA AV handle to use
@@ -699,6 +707,16 @@
return active_peer_->GetCodecs()->getCurrentCodecConfig();
}
+A2dpCodecConfig* BtaAvCo::GetPeerCurrentCodec(const RawAddress& peer_address) {
+ std::lock_guard<std::recursive_mutex> lock(codec_lock_);
+
+ BtaAvCoPeer* peer = FindPeer(peer_address);
+ if (peer == nullptr || peer->GetCodecs() == nullptr) {
+ return nullptr;
+ }
+ return peer->GetCodecs()->getCurrentCodecConfig();
+}
+
BtaAvCoPeer* BtaAvCo::FindPeer(const RawAddress& peer_address) {
for (size_t i = 0; i < BTA_AV_CO_NUM_ELEMENTS(peers_); i++) {
BtaAvCoPeer* p_peer = &peers_[i];
@@ -1785,6 +1803,11 @@
return bta_av_co_cb.GetActivePeerCurrentCodec();
}
+A2dpCodecConfig* bta_av_get_a2dp_peer_current_codec(
+ const RawAddress& peer_address) {
+ return bta_av_co_cb.GetPeerCurrentCodec(peer_address);
+}
+
bool bta_av_co_audio_init(btav_a2dp_codec_index_t codec_index,
AvdtpSepConfig* p_cfg) {
return A2DP_InitCodecConfig(codec_index, p_cfg);
diff --git a/btif/include/btif_av_co.h b/btif/include/btif_av_co.h
index 0576c27..df7454f 100644
--- a/btif/include/btif_av_co.h
+++ b/btif/include/btif_av_co.h
@@ -66,9 +66,16 @@
const std::vector<btav_a2dp_codec_config_t>& codec_priorities);
// Gets the current A2DP codec for the active peer.
-// Returns a pointer to the current |A2dpCodec| if valid, otherwise nullptr.
+// Returns a pointer to the current |A2dpCodecConfig| if valid, otherwise
+// nullptr.
A2dpCodecConfig* bta_av_get_a2dp_current_codec(void);
+// Gets the current A2DP codec for a peer identified by |peer_address|.
+// Returns a pointer to the current |A2dpCodecConfig| if valid, otherwise
+// nullptr.
+A2dpCodecConfig* bta_av_get_a2dp_peer_current_codec(
+ const RawAddress& peer_address);
+
// Dump A2DP codec debug-related information for the A2DP module.
// |fd| is the file descriptor to use for writing the ASCII formatted
// information.
diff --git a/btif/include/btif_storage.h b/btif/include/btif_storage.h
index 1ff7f22..7e90162 100644
--- a/btif/include/btif_storage.h
+++ b/btif/include/btif_storage.h
@@ -195,6 +195,12 @@
******************************************************************************/
bt_status_t btif_storage_remove_hid_info(RawAddress* remote_bd_addr);
+/** Loads information about bonded hearing aid devices */
+void btif_storage_load_bonded_hearing_aids();
+
+/** Deletes the bonded hearing aid device info from NVRAM */
+void btif_storage_remove_hearing_aid(const RawAddress& address);
+
/*******************************************************************************
*
* Function btif_storage_is_retricted_device
diff --git a/btif/src/bluetooth.cc b/btif/src/bluetooth.cc
index d0c090b..edd6dab 100644
--- a/btif/src/bluetooth.cc
+++ b/btif/src/bluetooth.cc
@@ -37,6 +37,7 @@
#include <hardware/bt_av.h>
#include <hardware/bt_gatt.h>
#include <hardware/bt_hd.h>
+#include <hardware/bt_hearing_aid.h>
#include <hardware/bt_hf_client.h>
#include <hardware/bt_hh.h>
#include <hardware/bt_hl.h>
@@ -71,6 +72,8 @@
/* Test interface includes */
#include "mca_api.h"
+using bluetooth::hearing_aid::HearingAidInterface;
+
/*******************************************************************************
* Static variables
******************************************************************************/
@@ -109,6 +112,8 @@
extern const btrc_ctrl_interface_t* btif_rc_ctrl_get_interface();
/*SDP search client*/
extern const btsdp_interface_t* btif_sdp_get_interface();
+/*Hearing Aid client*/
+extern HearingAidInterface* btif_hearing_aid_get_interface();
/* List all test interface here */
extern const btmcap_test_interface_t* stack_mcap_get_interface();
@@ -375,6 +380,8 @@
if (is_profile(profile_id, BT_TEST_INTERFACE_MCAP_ID))
return stack_mcap_get_interface();
+ if (is_profile(profile_id, BT_PROFILE_HEARING_AID_ID))
+ return btif_hearing_aid_get_interface();
return NULL;
}
diff --git a/btif/src/btif_a2dp_control.cc b/btif/src/btif_a2dp_control.cc
index d2a804f..714cbe2 100644
--- a/btif/src/btif_a2dp_control.cc
+++ b/btif/src/btif_a2dp_control.cc
@@ -48,15 +48,16 @@
/* We can have max one command pending */
static tA2DP_CTRL_CMD a2dp_cmd_pending = A2DP_CTRL_CMD_NONE;
+std::unique_ptr<tUIPC_STATE> a2dp_uipc;
void btif_a2dp_control_init(void) {
- UIPC_Init(NULL, UIPC_USER_A2DP);
- UIPC_Open(UIPC_CH_ID_AV_CTRL, btif_a2dp_ctrl_cb);
+ a2dp_uipc = UIPC_Init();
+ UIPC_Open(*a2dp_uipc, UIPC_CH_ID_AV_CTRL, btif_a2dp_ctrl_cb);
}
void btif_a2dp_control_cleanup(void) {
/* This calls blocks until UIPC is fully closed */
- UIPC_Close(UIPC_CH_ID_ALL, UIPC_USER_A2DP);
+ UIPC_Close(*a2dp_uipc, UIPC_CH_ID_ALL);
}
static void btif_a2dp_recv_ctrl_data(void) {
@@ -64,13 +65,13 @@
int n;
uint8_t read_cmd = 0; /* The read command size is one octet */
- n = UIPC_Read(UIPC_CH_ID_AV_CTRL, NULL, &read_cmd, 1);
+ n = UIPC_Read(*a2dp_uipc, UIPC_CH_ID_AV_CTRL, NULL, &read_cmd, 1);
cmd = static_cast<tA2DP_CTRL_CMD>(read_cmd);
/* detach on ctrl channel means audioflinger process was terminated */
if (n == 0) {
APPL_TRACE_WARNING("%s: CTRL CH DETACHED", __func__);
- UIPC_Close(UIPC_CH_ID_AV_CTRL, UIPC_USER_A2DP);
+ UIPC_Close(*a2dp_uipc, UIPC_CH_ID_AV_CTRL);
return;
}
@@ -117,7 +118,7 @@
if (btif_av_stream_ready()) {
/* Setup audio data channel listener */
- UIPC_Open(UIPC_CH_ID_AV_AUDIO, btif_a2dp_data_cb);
+ UIPC_Open(*a2dp_uipc, UIPC_CH_ID_AV_AUDIO, btif_a2dp_data_cb);
/*
* Post start event and wait for audio path to open.
@@ -135,7 +136,7 @@
* Already started, setup audio data channel listener and ACK
* back immediately.
*/
- UIPC_Open(UIPC_CH_ID_AV_AUDIO, btif_a2dp_data_cb);
+ UIPC_Open(*a2dp_uipc, UIPC_CH_ID_AV_AUDIO, btif_a2dp_data_cb);
btif_a2dp_command_ack(A2DP_CTRL_ACK_SUCCESS);
break;
}
@@ -174,9 +175,10 @@
tA2DP_CHANNEL_COUNT channel_count = btif_a2dp_sink_get_channel_count();
btif_a2dp_command_ack(A2DP_CTRL_ACK_SUCCESS);
- UIPC_Send(UIPC_CH_ID_AV_CTRL, 0, reinterpret_cast<uint8_t*>(&sample_rate),
+ UIPC_Send(*a2dp_uipc, UIPC_CH_ID_AV_CTRL, 0,
+ reinterpret_cast<uint8_t*>(&sample_rate),
sizeof(tA2DP_SAMPLE_RATE));
- UIPC_Send(UIPC_CH_ID_AV_CTRL, 0, &channel_count,
+ UIPC_Send(*a2dp_uipc, UIPC_CH_ID_AV_CTRL, 0, &channel_count,
sizeof(tA2DP_CHANNEL_COUNT));
break;
}
@@ -199,25 +201,27 @@
btif_a2dp_command_ack(A2DP_CTRL_ACK_SUCCESS);
// Send the current codec config
- UIPC_Send(UIPC_CH_ID_AV_CTRL, 0,
+ UIPC_Send(*a2dp_uipc, UIPC_CH_ID_AV_CTRL, 0,
reinterpret_cast<const uint8_t*>(&codec_config.sample_rate),
sizeof(btav_a2dp_codec_sample_rate_t));
- UIPC_Send(UIPC_CH_ID_AV_CTRL, 0,
+ UIPC_Send(*a2dp_uipc, UIPC_CH_ID_AV_CTRL, 0,
reinterpret_cast<const uint8_t*>(&codec_config.bits_per_sample),
sizeof(btav_a2dp_codec_bits_per_sample_t));
- UIPC_Send(UIPC_CH_ID_AV_CTRL, 0,
+ UIPC_Send(*a2dp_uipc, UIPC_CH_ID_AV_CTRL, 0,
reinterpret_cast<const uint8_t*>(&codec_config.channel_mode),
sizeof(btav_a2dp_codec_channel_mode_t));
// Send the current codec capability
- UIPC_Send(UIPC_CH_ID_AV_CTRL, 0,
+ UIPC_Send(*a2dp_uipc, UIPC_CH_ID_AV_CTRL, 0,
reinterpret_cast<const uint8_t*>(&codec_capability.sample_rate),
sizeof(btav_a2dp_codec_sample_rate_t));
- UIPC_Send(UIPC_CH_ID_AV_CTRL, 0, reinterpret_cast<const uint8_t*>(
- &codec_capability.bits_per_sample),
- sizeof(btav_a2dp_codec_bits_per_sample_t));
- UIPC_Send(UIPC_CH_ID_AV_CTRL, 0, reinterpret_cast<const uint8_t*>(
- &codec_capability.channel_mode),
- sizeof(btav_a2dp_codec_channel_mode_t));
+ UIPC_Send(
+ *a2dp_uipc, UIPC_CH_ID_AV_CTRL, 0,
+ reinterpret_cast<const uint8_t*>(&codec_capability.bits_per_sample),
+ sizeof(btav_a2dp_codec_bits_per_sample_t));
+ UIPC_Send(
+ *a2dp_uipc, UIPC_CH_ID_AV_CTRL, 0,
+ reinterpret_cast<const uint8_t*>(&codec_capability.channel_mode),
+ sizeof(btav_a2dp_codec_channel_mode_t));
break;
}
@@ -229,7 +233,7 @@
btif_a2dp_command_ack(A2DP_CTRL_ACK_SUCCESS);
// Send the current codec config
- if (UIPC_Read(UIPC_CH_ID_AV_CTRL, 0,
+ if (UIPC_Read(*a2dp_uipc, UIPC_CH_ID_AV_CTRL, 0,
reinterpret_cast<uint8_t*>(&codec_config.sample_rate),
sizeof(btav_a2dp_codec_sample_rate_t)) !=
sizeof(btav_a2dp_codec_sample_rate_t)) {
@@ -237,7 +241,7 @@
__func__);
break;
}
- if (UIPC_Read(UIPC_CH_ID_AV_CTRL, 0,
+ if (UIPC_Read(*a2dp_uipc, UIPC_CH_ID_AV_CTRL, 0,
reinterpret_cast<uint8_t*>(&codec_config.bits_per_sample),
sizeof(btav_a2dp_codec_bits_per_sample_t)) !=
sizeof(btav_a2dp_codec_bits_per_sample_t)) {
@@ -245,7 +249,7 @@
__func__);
break;
}
- if (UIPC_Read(UIPC_CH_ID_AV_CTRL, 0,
+ if (UIPC_Read(*a2dp_uipc, UIPC_CH_ID_AV_CTRL, 0,
reinterpret_cast<uint8_t*>(&codec_config.channel_mode),
sizeof(btav_a2dp_codec_channel_mode_t)) !=
sizeof(btav_a2dp_codec_channel_mode_t)) {
@@ -270,17 +274,19 @@
case A2DP_CTRL_GET_PRESENTATION_POSITION: {
btif_a2dp_command_ack(A2DP_CTRL_ACK_SUCCESS);
- UIPC_Send(UIPC_CH_ID_AV_CTRL, 0,
+ UIPC_Send(*a2dp_uipc, UIPC_CH_ID_AV_CTRL, 0,
(uint8_t*)&(delay_report_stats.total_bytes_read),
sizeof(uint64_t));
- UIPC_Send(UIPC_CH_ID_AV_CTRL, 0,
+ UIPC_Send(*a2dp_uipc, UIPC_CH_ID_AV_CTRL, 0,
(uint8_t*)&(delay_report_stats.audio_delay), sizeof(uint16_t));
uint32_t seconds = delay_report_stats.timestamp.tv_sec;
- UIPC_Send(UIPC_CH_ID_AV_CTRL, 0, (uint8_t*)&seconds, sizeof(seconds));
+ UIPC_Send(*a2dp_uipc, UIPC_CH_ID_AV_CTRL, 0, (uint8_t*)&seconds,
+ sizeof(seconds));
uint32_t nsec = delay_report_stats.timestamp.tv_nsec;
- UIPC_Send(UIPC_CH_ID_AV_CTRL, 0, (uint8_t*)&nsec, sizeof(nsec));
+ UIPC_Send(*a2dp_uipc, UIPC_CH_ID_AV_CTRL, 0, (uint8_t*)&nsec,
+ sizeof(nsec));
break;
}
default:
@@ -304,7 +310,7 @@
case UIPC_CLOSE_EVT:
/* restart ctrl server unless we are shutting down */
if (btif_a2dp_source_media_task_is_running())
- UIPC_Open(UIPC_CH_ID_AV_CTRL, btif_a2dp_ctrl_cb);
+ UIPC_Open(*a2dp_uipc, UIPC_CH_ID_AV_CTRL, btif_a2dp_ctrl_cb);
break;
case UIPC_RX_DATA_READY_EVT:
@@ -329,8 +335,9 @@
* Read directly from media task from here on (keep callback for
* connection events.
*/
- UIPC_Ioctl(UIPC_CH_ID_AV_AUDIO, UIPC_REG_REMOVE_ACTIVE_READSET, NULL);
- UIPC_Ioctl(UIPC_CH_ID_AV_AUDIO, UIPC_SET_READ_POLL_TMO,
+ UIPC_Ioctl(*a2dp_uipc, UIPC_CH_ID_AV_AUDIO,
+ UIPC_REG_REMOVE_ACTIVE_READSET, NULL);
+ UIPC_Ioctl(*a2dp_uipc, UIPC_CH_ID_AV_AUDIO, UIPC_SET_READ_POLL_TMO,
reinterpret_cast<void*>(A2DP_DATA_READ_POLL_MS));
if (btif_av_get_peer_sep() == AVDT_TSEP_SNK) {
@@ -378,7 +385,7 @@
a2dp_cmd_pending = A2DP_CTRL_CMD_NONE;
/* Acknowledge start request */
- UIPC_Send(UIPC_CH_ID_AV_CTRL, 0, &ack, sizeof(ack));
+ UIPC_Send(*a2dp_uipc, UIPC_CH_ID_AV_CTRL, 0, &ack, sizeof(ack));
}
void btif_a2dp_control_log_bytes_read(uint32_t bytes_read) {
diff --git a/btif/src/btif_a2dp_source.cc b/btif/src/btif_a2dp_source.cc
index ba1184a..669789e 100644
--- a/btif/src/btif_a2dp_source.cc
+++ b/btif/src/btif_a2dp_source.cc
@@ -52,6 +52,8 @@
using system_bt_osi::BluetoothMetricsLogger;
using system_bt_osi::A2dpSessionMetrics;
+extern std::unique_ptr<tUIPC_STATE> a2dp_uipc;
+
/**
* The typical runlevel of the tx queue size is ~1 buffer
* but due to link flow control or thread preemption in lower
@@ -694,13 +696,13 @@
// Keep track of audio data still left in the pipe
btif_a2dp_control_log_bytes_read(
- UIPC_Read(UIPC_CH_ID_AV_AUDIO, &event, p_buf, sizeof(p_buf)));
+ UIPC_Read(*a2dp_uipc, UIPC_CH_ID_AV_AUDIO, &event, p_buf, sizeof(p_buf)));
/* Stop the timer first */
alarm_free(btif_a2dp_source_cb.media_alarm);
btif_a2dp_source_cb.media_alarm = nullptr;
- UIPC_Close(UIPC_CH_ID_AV_AUDIO, UIPC_USER_A2DP);
+ UIPC_Close(*a2dp_uipc, UIPC_CH_ID_AV_AUDIO);
/*
* Try to send acknowldegment once the media stream is
@@ -759,7 +761,8 @@
static uint32_t btif_a2dp_source_read_callback(uint8_t* p_buf, uint32_t len) {
uint16_t event;
- uint32_t bytes_read = UIPC_Read(UIPC_CH_ID_AV_AUDIO, &event, p_buf, len);
+ uint32_t bytes_read =
+ UIPC_Read(*a2dp_uipc, UIPC_CH_ID_AV_AUDIO, &event, p_buf, len);
if (bytes_read < len) {
LOG_WARN(LOG_TAG, "%s: UNDERFLOW: ONLY READ %d BYTES OUT OF %d", __func__,
@@ -869,7 +872,7 @@
time_get_os_boottime_us();
fixed_queue_flush(btif_a2dp_source_cb.tx_audio_queue, osi_free);
- UIPC_Ioctl(UIPC_CH_ID_AV_AUDIO, UIPC_REQ_RX_FLUSH, nullptr);
+ UIPC_Ioctl(*a2dp_uipc, UIPC_CH_ID_AV_AUDIO, UIPC_REQ_RX_FLUSH, nullptr);
}
static bool btif_a2dp_source_audio_tx_flush_req(void) {
diff --git a/btif/src/btif_av.cc b/btif/src/btif_av.cc
index ed48d9a..b4a7891 100644
--- a/btif/src/btif_av.cc
+++ b/btif/src/btif_av.cc
@@ -232,6 +232,20 @@
*/
bool CanBeDeleted() const;
+ /**
+ * Check whether the peer is the active one.
+ *
+ * @return true if this peer is the active one
+ */
+ bool IsActivePeer() const { return (PeerAddress() == ActivePeerAddress()); }
+
+ /**
+ * Get the address of the active peer.
+ *
+ * @return the address of the active peer
+ */
+ const RawAddress& ActivePeerAddress() const;
+
const RawAddress& PeerAddress() const { return peer_address_; }
bool IsSource() const { return (peer_sep_ == AVDT_TSEP_SRC); }
bool IsSink() const { return (peer_sep_ == AVDT_TSEP_SNK); }
@@ -800,6 +814,18 @@
(state_machine_.PreviousStateId() != BtifAvStateMachine::kStateInvalid));
}
+const RawAddress& BtifAvPeer::ActivePeerAddress() const {
+ if (IsSource()) {
+ return btif_av_sink.ActivePeer();
+ }
+ if (IsSink()) {
+ return btif_av_source.ActivePeer();
+ }
+ LOG(FATAL) << __PRETTY_FUNCTION__ << ": A2DP peer " << PeerAddress()
+ << " is neither Source nor Sink";
+ return RawAddress::kEmpty;
+}
+
bool BtifAvPeer::IsConnected() const {
int state = state_machine_.StateId();
return ((state == BtifAvStateMachine::kStateOpened) ||
@@ -1175,20 +1201,13 @@
peer_.ClearAllFlags();
// Stop A2DP if this is the active peer
- RawAddress active_peer = RawAddress::kEmpty;
- if (peer_.IsSink()) {
- active_peer = btif_av_source.ActivePeer();
- } else if (peer_.IsSource()) {
- active_peer = btif_av_sink.ActivePeer();
- }
- if (peer_.PeerAddress() == active_peer || active_peer.IsEmpty()) {
+ if (peer_.IsActivePeer() || peer_.ActivePeerAddress().IsEmpty()) {
btif_a2dp_on_idle();
}
// Reset the active peer if this was the active peer and
// the Idle state was reentered
- if (peer_.PeerAddress() == active_peer && !active_peer.IsEmpty() &&
- peer_.CanBeDeleted()) {
+ if (peer_.IsActivePeer() && peer_.CanBeDeleted()) {
if (peer_.IsSink()) {
btif_av_source.SetActivePeer(RawAddress::kEmpty);
} else if (peer_.IsSource()) {
@@ -1710,7 +1729,7 @@
// Remain in Open state if status failed
if (p_av->start.status != BTA_AV_SUCCESS) return false;
- if (peer_.IsSource()) {
+ if (peer_.IsSource() && peer_.IsActivePeer()) {
// Remove flush state, ready for streaming
btif_a2dp_sink_set_rx_flush(false);
}
@@ -1740,7 +1759,9 @@
case BTA_AV_CLOSE_EVT:
// AVDTP link is closed
- btif_a2dp_on_stopped(nullptr);
+ if (peer_.IsActivePeer()) {
+ btif_a2dp_on_stopped(nullptr);
+ }
// Inform the application that we are disconnected
btif_report_connection_state(peer_.PeerAddress(),
@@ -1857,7 +1878,9 @@
if (peer_.IsSink()) {
// Immediately stop transmission of frames while suspend is pending
- btif_a2dp_source_set_tx_flush(true);
+ if (peer_.IsActivePeer()) {
+ btif_a2dp_source_set_tx_flush(true);
+ }
} else if (peer_.IsSource()) {
btif_a2dp_on_stopped(nullptr);
}
@@ -1898,7 +1921,7 @@
if (p_av->suspend.status != BTA_AV_SUCCESS) {
peer_.ClearFlags(BtifAvPeer::kFlagLocalSuspendPending);
- if (peer_.IsSink()) {
+ if (peer_.IsSink() && peer_.IsActivePeer()) {
// Suspend failed, reset back tx flush state
btif_a2dp_source_set_tx_flush(false);
}
@@ -1952,7 +1975,9 @@
peer_.SetFlags(BtifAvPeer::kFlagPendingStop);
// AVDTP link is closed
- btif_a2dp_on_stopped(nullptr);
+ if (peer_.IsActivePeer()) {
+ btif_a2dp_on_stopped(nullptr);
+ }
// Inform the application that we are disconnected
btif_report_connection_state(peer_.PeerAddress(),
@@ -1986,12 +2011,14 @@
BTIF_TRACE_DEBUG("%s: Peer %s", __PRETTY_FUNCTION__,
peer_.PeerAddress().ToString().c_str());
- if (peer_.IsSink()) {
- // Immediately stop transmission of frames
- btif_a2dp_source_set_tx_flush(true);
- // Wait for Audio Flinger to stop A2DP
- } else if (peer_.IsSource()) {
- btif_a2dp_sink_set_rx_flush(true);
+ if (peer_.IsActivePeer()) {
+ if (peer_.IsSink()) {
+ // Immediately stop transmission of frames
+ btif_a2dp_source_set_tx_flush(true);
+ // Wait for Audio Flinger to stop A2DP
+ } else if (peer_.IsSource()) {
+ btif_a2dp_sink_set_rx_flush(true);
+ }
}
}
@@ -2014,7 +2041,9 @@
case BTA_AV_STOP_EVT:
case BTIF_AV_STOP_STREAM_REQ_EVT:
- btif_a2dp_on_stopped(nullptr);
+ if (peer_.IsActivePeer()) {
+ btif_a2dp_on_stopped(nullptr);
+ }
break;
case BTA_AV_CLOSE_EVT:
diff --git a/btif/src/btif_core.cc b/btif/src/btif_core.cc
index e3508ad..975fb26 100644
--- a/btif/src/btif_core.cc
+++ b/btif/src/btif_core.cc
@@ -527,7 +527,7 @@
bt_status_t btif_cleanup_bluetooth(void) {
LOG_INFO(LOG_TAG, "%s entered", __func__);
- BTA_VendorCleanup();
+ do_in_bta_thread(FROM_HERE, base::Bind(&BTA_VendorCleanup));
btif_dm_cleanup();
btif_jni_disassociate();
diff --git a/btif/src/btif_hearing_aid.cc b/btif/src/btif_hearing_aid.cc
new file mode 100644
index 0000000..961a542
--- /dev/null
+++ b/btif/src/btif_hearing_aid.cc
@@ -0,0 +1,120 @@
+/******************************************************************************
+ *
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ ******************************************************************************/
+
+/* Hearing Aid Profile Interface */
+
+#include "bta_closure_api.h"
+#include "bta_hearing_aid_api.h"
+#include "btif_common.h"
+#include "btif_storage.h"
+
+#include <base/bind.h>
+#include <base/location.h>
+#include <base/logging.h>
+#include <hardware/bluetooth.h>
+#include <hardware/bt_hearing_aid.h>
+
+using base::Bind;
+using base::Unretained;
+using bluetooth::hearing_aid::ConnectionState;
+using bluetooth::hearing_aid::HearingAidCallbacks;
+using bluetooth::hearing_aid::HearingAidInterface;
+
+// template specialization
+template <>
+base::Callback<void()> jni_thread_wrapper(
+ const tracked_objects::Location& from_here, base::Callback<void()> cb) {
+ return base::Bind(
+ [](const tracked_objects::Location& from_here,
+ base::Callback<void()> cb) { do_in_jni_thread(from_here, cb); },
+ from_here, std::move(cb));
+}
+
+namespace {
+class HearingAidInterfaceImpl;
+std::unique_ptr<HearingAidInterface> hearingAidInstance;
+
+class HearingAidInterfaceImpl
+ : public bluetooth::hearing_aid::HearingAidInterface,
+ public HearingAidCallbacks {
+ ~HearingAidInterfaceImpl() = default;
+
+ void Init(HearingAidCallbacks* callbacks) {
+ DVLOG(2) << __func__;
+ this->callbacks = callbacks;
+ do_in_bta_thread(
+ FROM_HERE,
+ Bind(&HearingAid::Initialize, this,
+ jni_thread_wrapper(FROM_HERE,
+ Bind(&btif_storage_load_bonded_hearing_aids))));
+ }
+
+ void OnConnectionState(ConnectionState state,
+ const RawAddress& address) override {
+ DVLOG(2) << __func__ << " address: " << address;
+ do_in_jni_thread(FROM_HERE, Bind(&HearingAidCallbacks::OnConnectionState,
+ Unretained(callbacks), state, address));
+ }
+
+ void OnDeviceAvailable(uint8_t capabilities, uint64_t hiSyncId,
+ const RawAddress& address) override {
+ DVLOG(2) << __func__ << " address: " << address
+ << ", hiSyncId: " << loghex(hiSyncId)
+ << ", capabilities: " << loghex(capabilities);
+ do_in_jni_thread(FROM_HERE, Bind(&HearingAidCallbacks::OnDeviceAvailable,
+ Unretained(callbacks), capabilities,
+ hiSyncId, address));
+ }
+
+ void Connect(const RawAddress& address) override {
+ DVLOG(2) << __func__ << " address: " << address;
+ do_in_bta_thread(FROM_HERE, Bind(&HearingAid::Connect,
+ Unretained(HearingAid::Get()), address));
+ }
+
+ void Disconnect(const RawAddress& address) override {
+ DVLOG(2) << __func__ << " address: " << address;
+ do_in_bta_thread(FROM_HERE, Bind(&HearingAid::Disconnect,
+ Unretained(HearingAid::Get()), address));
+ do_in_jni_thread(FROM_HERE,
+ Bind(&btif_storage_remove_hearing_aid, address));
+ }
+
+ void SetVolume(int8_t volume) override {
+ DVLOG(2) << __func__ << " volume: " << +volume;
+ do_in_bta_thread(FROM_HERE, Bind(&HearingAid::SetVolume,
+ Unretained(HearingAid::Get()), volume));
+ }
+
+ void Cleanup(void) {
+ DVLOG(2) << __func__;
+ do_in_bta_thread(FROM_HERE, Bind(&HearingAid::CleanUp));
+ }
+
+ private:
+ HearingAidCallbacks* callbacks;
+};
+
+} // namespace
+
+HearingAidInterface* btif_hearing_aid_get_interface() {
+ if (!hearingAidInstance)
+ hearingAidInstance.reset(new HearingAidInterfaceImpl());
+
+ return hearingAidInstance.get();
+}
diff --git a/btif/src/btif_storage.cc b/btif/src/btif_storage.cc
index 47aceed..d7a90ea 100644
--- a/btif/src/btif_storage.cc
+++ b/btif/src/btif_storage.cc
@@ -40,7 +40,9 @@
#include <time.h>
#include "bt_common.h"
+#include "bta_closure_api.h"
#include "bta_hd_api.h"
+#include "bta_hearing_aid_api.h"
#include "bta_hh_api.h"
#include "btif_api.h"
#include "btif_config.h"
@@ -54,6 +56,7 @@
#include "osi/include/log.h"
#include "osi/include/osi.h"
+using base::Bind;
using bluetooth::Uuid;
/*******************************************************************************
@@ -1352,6 +1355,12 @@
return BT_STATUS_SUCCESS;
}
+/** Loads information about bonded hearing aid devices */
+void btif_storage_load_bonded_hearing_aids() {}
+
+/** Deletes the bonded hearing aid device info from NVRAM */
+void btif_storage_remove_hearing_aid(const RawAddress& address) {}
+
/*******************************************************************************
*
* Function btif_storage_is_restricted_device
diff --git a/include/hardware/bluetooth.h b/include/hardware/bluetooth.h
index dd7b8e1..e48e560 100644
--- a/include/hardware/bluetooth.h
+++ b/include/hardware/bluetooth.h
@@ -48,6 +48,7 @@
#define BT_PROFILE_GATT_ID "gatt"
#define BT_PROFILE_AV_RC_ID "avrcp"
#define BT_PROFILE_AV_RC_CTRL_ID "avrcp_ctrl"
+#define BT_PROFILE_HEARING_AID_ID "hearing_aid"
/** Bluetooth test interface IDs */
#define BT_TEST_INTERFACE_MCAP_ID "mcap_test"
diff --git a/include/hardware/bt_av.h b/include/hardware/bt_av.h
index 3ef4c2c..8a8accd 100644
--- a/include/hardware/bt_av.h
+++ b/include/hardware/bt_av.h
@@ -89,7 +89,9 @@
BTAV_A2DP_CODEC_SAMPLE_RATE_88200 = 0x1 << 2,
BTAV_A2DP_CODEC_SAMPLE_RATE_96000 = 0x1 << 3,
BTAV_A2DP_CODEC_SAMPLE_RATE_176400 = 0x1 << 4,
- BTAV_A2DP_CODEC_SAMPLE_RATE_192000 = 0x1 << 5
+ BTAV_A2DP_CODEC_SAMPLE_RATE_192000 = 0x1 << 5,
+ BTAV_A2DP_CODEC_SAMPLE_RATE_16000 = 0x1 << 6,
+ BTAV_A2DP_CODEC_SAMPLE_RATE_24000 = 0x1 << 7
} btav_a2dp_codec_sample_rate_t;
typedef enum {
@@ -178,6 +180,12 @@
AppendCapability(sample_rate_str,
(sample_rate & BTAV_A2DP_CODEC_SAMPLE_RATE_192000),
"192000");
+ AppendCapability(sample_rate_str,
+ (sample_rate & BTAV_A2DP_CODEC_SAMPLE_RATE_16000),
+ "16000");
+ AppendCapability(sample_rate_str,
+ (sample_rate & BTAV_A2DP_CODEC_SAMPLE_RATE_24000),
+ "24000");
std::string bits_per_sample_str;
AppendCapability(bits_per_sample_str,
diff --git a/include/hardware/bt_hearing_aid.h b/include/hardware/bt_hearing_aid.h
new file mode 100644
index 0000000..3b53d37
--- /dev/null
+++ b/include/hardware/bt_hearing_aid.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_INCLUDE_BT_HEARING_AID_H
+#define ANDROID_INCLUDE_BT_HEARING_AID_H
+
+#include <hardware/bluetooth.h>
+
+namespace bluetooth {
+namespace hearing_aid {
+
+enum class ConnectionState {
+ DISCONNECTED = 0,
+ CONNECTING,
+ CONNECTED,
+ DISCONNECTING
+};
+
+class HearingAidCallbacks {
+ public:
+ virtual ~HearingAidCallbacks() = default;
+
+ /** Callback for profile connection state change */
+ virtual void OnConnectionState(ConnectionState state,
+ const RawAddress& address) = 0;
+
+ /** Callback for device being available. Is executed when devices are loaded
+ * from storage on stack bringup, and when new device is connected to profile.
+ * Main purpose of this callback is to keep its users informed of device
+ * capabilities and hiSyncId.
+ */
+ virtual void OnDeviceAvailable(uint8_t capabilities, uint64_t hiSyncId,
+ const RawAddress& address) = 0;
+};
+
+class HearingAidInterface {
+ public:
+ virtual ~HearingAidInterface() = default;
+
+ /** Register the Hearing Aid callbacks */
+ virtual void Init(HearingAidCallbacks* callbacks) = 0;
+
+ /** Connect to Hearing Aid */
+ virtual void Connect(const RawAddress& address) = 0;
+
+ /** Disconnect from Hearing Aid */
+ virtual void Disconnect(const RawAddress& address) = 0;
+
+ /** Set the volume */
+ virtual void SetVolume(int8_t volume) = 0;
+
+ /** Closes the interface. */
+ virtual void Cleanup(void) = 0;
+};
+
+} // namespace hearing_aid
+} // namespace bluetooth
+
+#endif /* ANDROID_INCLUDE_BT_HEARING_AID_H */
diff --git a/stack/a2dp/a2dp_aac.cc b/stack/a2dp/a2dp_aac.cc
index 93307c8..d147192 100644
--- a/stack/a2dp/a2dp_aac.cc
+++ b/stack/a2dp/a2dp_aac.cc
@@ -835,6 +835,8 @@
break;
case BTAV_A2DP_CODEC_SAMPLE_RATE_176400:
case BTAV_A2DP_CODEC_SAMPLE_RATE_192000:
+ case BTAV_A2DP_CODEC_SAMPLE_RATE_16000:
+ case BTAV_A2DP_CODEC_SAMPLE_RATE_24000:
case BTAV_A2DP_CODEC_SAMPLE_RATE_NONE:
break;
}
@@ -1055,6 +1057,8 @@
break;
case BTAV_A2DP_CODEC_SAMPLE_RATE_176400:
case BTAV_A2DP_CODEC_SAMPLE_RATE_192000:
+ case BTAV_A2DP_CODEC_SAMPLE_RATE_16000:
+ case BTAV_A2DP_CODEC_SAMPLE_RATE_24000:
case BTAV_A2DP_CODEC_SAMPLE_RATE_NONE:
codec_capability_.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_NONE;
codec_config_.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_NONE;
diff --git a/stack/a2dp/a2dp_sbc.cc b/stack/a2dp/a2dp_sbc.cc
index 400d6d9..112626f 100644
--- a/stack/a2dp/a2dp_sbc.cc
+++ b/stack/a2dp/a2dp_sbc.cc
@@ -990,6 +990,8 @@
case BTAV_A2DP_CODEC_SAMPLE_RATE_96000:
case BTAV_A2DP_CODEC_SAMPLE_RATE_176400:
case BTAV_A2DP_CODEC_SAMPLE_RATE_192000:
+ case BTAV_A2DP_CODEC_SAMPLE_RATE_16000:
+ case BTAV_A2DP_CODEC_SAMPLE_RATE_24000:
case BTAV_A2DP_CODEC_SAMPLE_RATE_NONE:
break;
}
@@ -1175,6 +1177,8 @@
case BTAV_A2DP_CODEC_SAMPLE_RATE_96000:
case BTAV_A2DP_CODEC_SAMPLE_RATE_176400:
case BTAV_A2DP_CODEC_SAMPLE_RATE_192000:
+ case BTAV_A2DP_CODEC_SAMPLE_RATE_16000:
+ case BTAV_A2DP_CODEC_SAMPLE_RATE_24000:
case BTAV_A2DP_CODEC_SAMPLE_RATE_NONE:
codec_capability_.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_NONE;
codec_config_.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_NONE;
diff --git a/stack/a2dp/a2dp_vendor_aptx.cc b/stack/a2dp/a2dp_vendor_aptx.cc
index cf136e3..89378a0 100644
--- a/stack/a2dp/a2dp_vendor_aptx.cc
+++ b/stack/a2dp/a2dp_vendor_aptx.cc
@@ -489,6 +489,8 @@
case BTAV_A2DP_CODEC_SAMPLE_RATE_96000:
case BTAV_A2DP_CODEC_SAMPLE_RATE_176400:
case BTAV_A2DP_CODEC_SAMPLE_RATE_192000:
+ case BTAV_A2DP_CODEC_SAMPLE_RATE_16000:
+ case BTAV_A2DP_CODEC_SAMPLE_RATE_24000:
case BTAV_A2DP_CODEC_SAMPLE_RATE_NONE:
break;
}
@@ -642,6 +644,8 @@
case BTAV_A2DP_CODEC_SAMPLE_RATE_96000:
case BTAV_A2DP_CODEC_SAMPLE_RATE_176400:
case BTAV_A2DP_CODEC_SAMPLE_RATE_192000:
+ case BTAV_A2DP_CODEC_SAMPLE_RATE_16000:
+ case BTAV_A2DP_CODEC_SAMPLE_RATE_24000:
case BTAV_A2DP_CODEC_SAMPLE_RATE_NONE:
codec_capability_.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_NONE;
codec_config_.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_NONE;
diff --git a/stack/a2dp/a2dp_vendor_aptx_hd.cc b/stack/a2dp/a2dp_vendor_aptx_hd.cc
index 907712c..5b9105a 100644
--- a/stack/a2dp/a2dp_vendor_aptx_hd.cc
+++ b/stack/a2dp/a2dp_vendor_aptx_hd.cc
@@ -506,6 +506,8 @@
case BTAV_A2DP_CODEC_SAMPLE_RATE_96000:
case BTAV_A2DP_CODEC_SAMPLE_RATE_176400:
case BTAV_A2DP_CODEC_SAMPLE_RATE_192000:
+ case BTAV_A2DP_CODEC_SAMPLE_RATE_16000:
+ case BTAV_A2DP_CODEC_SAMPLE_RATE_24000:
case BTAV_A2DP_CODEC_SAMPLE_RATE_NONE:
break;
}
@@ -659,6 +661,8 @@
case BTAV_A2DP_CODEC_SAMPLE_RATE_96000:
case BTAV_A2DP_CODEC_SAMPLE_RATE_176400:
case BTAV_A2DP_CODEC_SAMPLE_RATE_192000:
+ case BTAV_A2DP_CODEC_SAMPLE_RATE_16000:
+ case BTAV_A2DP_CODEC_SAMPLE_RATE_24000:
case BTAV_A2DP_CODEC_SAMPLE_RATE_NONE:
codec_capability_.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_NONE;
codec_config_.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_NONE;
diff --git a/stack/a2dp/a2dp_vendor_ldac.cc b/stack/a2dp/a2dp_vendor_ldac.cc
index 7f76e1f..b6b37be 100644
--- a/stack/a2dp/a2dp_vendor_ldac.cc
+++ b/stack/a2dp/a2dp_vendor_ldac.cc
@@ -654,6 +654,8 @@
return true;
}
break;
+ case BTAV_A2DP_CODEC_SAMPLE_RATE_16000:
+ case BTAV_A2DP_CODEC_SAMPLE_RATE_24000:
case BTAV_A2DP_CODEC_SAMPLE_RATE_NONE:
break;
}
@@ -875,6 +877,8 @@
codec_capability_.sample_rate = codec_user_config_.sample_rate;
codec_config_.sample_rate = codec_user_config_.sample_rate;
}
+ case BTAV_A2DP_CODEC_SAMPLE_RATE_16000:
+ case BTAV_A2DP_CODEC_SAMPLE_RATE_24000:
case BTAV_A2DP_CODEC_SAMPLE_RATE_NONE:
codec_capability_.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_NONE;
codec_config_.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_NONE;
diff --git a/udrv/include/uipc.h b/udrv/include/uipc.h
index 332beca..bd1c8bf 100644
--- a/udrv/include/uipc.h
+++ b/udrv/include/uipc.h
@@ -18,6 +18,8 @@
#ifndef UIPC_H
#define UIPC_H
+#include <mutex>
+
#define UIPC_CH_ID_AV_CTRL 0
#define UIPC_CH_ID_AV_AUDIO 1
#define UIPC_CH_NUM 2
@@ -37,13 +39,6 @@
UIPC_TX_DATA_READY_EVT = 0x0010
} tUIPC_EVENT;
-/* UIPC users */
-typedef enum {
- UIPC_USER_A2DP = 0,
- UIPC_USER_HEARING_AID = 1,
- UIPC_USER_NUM = 2
-} tUIPC_USER;
-
/*
* UIPC IOCTL Requests
*/
@@ -62,12 +57,33 @@
const char* dump_uipc_event(tUIPC_EVENT event);
+typedef struct {
+ int srvfd;
+ int fd;
+ int read_poll_tmo_ms;
+ int task_evt_flags; /* event flags pending to be processed in read task */
+ tUIPC_RCV_CBACK* cback;
+} tUIPC_CHAN;
+
+struct tUIPC_STATE {
+ pthread_t tid; /* main thread id */
+ int running;
+ std::recursive_mutex mutex;
+
+ fd_set active_set;
+ fd_set read_set;
+ int max_fd;
+ int signal_fds[2];
+
+ tUIPC_CHAN ch[UIPC_CH_NUM];
+};
+
/**
* Initialize UIPC module
*
* @param user User ID who uses UIPC
*/
-void UIPC_Init(void*, int user);
+std::unique_ptr<tUIPC_STATE> UIPC_Init();
/**
* Open a UIPC channel
@@ -76,15 +92,14 @@
* @param p_cback Callback handler
* @return true on success, otherwise false
*/
-bool UIPC_Open(tUIPC_CH_ID ch_id, tUIPC_RCV_CBACK* p_cback);
+bool UIPC_Open(tUIPC_STATE& uipc, tUIPC_CH_ID ch_id, tUIPC_RCV_CBACK* p_cback);
/**
* Closes a channel in UIPC or the entire UIPC module
*
* @param ch_id Channel ID; if ch_id is UIPC_CH_ID_ALL, then cleanup UIPC
- * @param user User ID who uses UIPC
*/
-void UIPC_Close(tUIPC_CH_ID ch_id, int user);
+void UIPC_Close(tUIPC_STATE& uipc, tUIPC_CH_ID ch_id);
/**
* Send a message over UIPC
@@ -95,8 +110,8 @@
* @param msglen Message length
* @return true on success, otherwise false
*/
-bool UIPC_Send(tUIPC_CH_ID ch_id, uint16_t msg_evt, const uint8_t* p_buf,
- uint16_t msglen);
+bool UIPC_Send(tUIPC_STATE& uipc, tUIPC_CH_ID ch_id, uint16_t msg_evt,
+ const uint8_t* p_buf, uint16_t msglen);
/**
* Read a message from UIPC
@@ -107,8 +122,8 @@
* @param len Bytes to read
* @return true on success, otherwise false
*/
-uint32_t UIPC_Read(tUIPC_CH_ID ch_id, uint16_t* p_msg_evt, uint8_t* p_buf,
- uint32_t len);
+uint32_t UIPC_Read(tUIPC_STATE& uipc, tUIPC_CH_ID ch_id, uint16_t* p_msg_evt,
+ uint8_t* p_buf, uint32_t len);
/**
* Control the UIPC parameter
@@ -118,6 +133,7 @@
* @param param Optional parameters
* @return true on success, otherwise false
*/
-bool UIPC_Ioctl(tUIPC_CH_ID ch_id, uint32_t request, void* param);
+bool UIPC_Ioctl(tUIPC_STATE& uipc, tUIPC_CH_ID ch_id, uint32_t request,
+ void* param);
#endif /* UIPC_H */
diff --git a/udrv/ulinux/uipc.cc b/udrv/ulinux/uipc.cc
index 78fdac8..73c7cb2 100644
--- a/udrv/ulinux/uipc.cc
+++ b/udrv/ulinux/uipc.cc
@@ -75,39 +75,10 @@
UIPC_TASK_FLAG_DISCONNECT_CHAN = 0x1,
} tUIPC_TASK_FLAGS;
-typedef struct {
- int srvfd;
- int fd;
- int read_poll_tmo_ms;
- int task_evt_flags; /* event flags pending to be processed in read task */
- tUIPC_RCV_CBACK* cback;
-} tUIPC_CHAN;
-
-typedef struct {
- pthread_t tid; /* main thread id */
- int running;
- std::recursive_mutex mutex;
-
- fd_set active_set;
- fd_set read_set;
- int max_fd;
- int signal_fds[2];
-
- tUIPC_CHAN ch[UIPC_CH_NUM];
- std::set<int> active_users;
-} tUIPC_MAIN;
-
-/*****************************************************************************
- * Static variables
- *****************************************************************************/
-
-static tUIPC_MAIN uipc_main;
-
/*****************************************************************************
* Static functions
*****************************************************************************/
-
-static int uipc_close_ch_locked(tUIPC_CH_ID ch_id);
+static int uipc_close_ch_locked(tUIPC_STATE& uipc, tUIPC_CH_ID ch_id);
/*****************************************************************************
* Externs
@@ -207,29 +178,29 @@
*
****************************************************************************/
-static int uipc_main_init(void) {
+static int uipc_main_init(tUIPC_STATE& uipc) {
int i;
BTIF_TRACE_EVENT("### uipc_main_init ###");
- uipc_main.tid = 0;
- uipc_main.running = 0;
- memset(&uipc_main.active_set, 0, sizeof(uipc_main.active_set));
- memset(&uipc_main.read_set, 0, sizeof(uipc_main.read_set));
- uipc_main.max_fd = 0;
- memset(&uipc_main.signal_fds, 0, sizeof(uipc_main.signal_fds));
- memset(&uipc_main.ch, 0, sizeof(uipc_main.ch));
+ uipc.tid = 0;
+ uipc.running = 0;
+ memset(&uipc.active_set, 0, sizeof(uipc.active_set));
+ memset(&uipc.read_set, 0, sizeof(uipc.read_set));
+ uipc.max_fd = 0;
+ memset(&uipc.signal_fds, 0, sizeof(uipc.signal_fds));
+ memset(&uipc.ch, 0, sizeof(uipc.ch));
/* setup interrupt socket pair */
- if (socketpair(AF_UNIX, SOCK_STREAM, 0, uipc_main.signal_fds) < 0) {
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, uipc.signal_fds) < 0) {
return -1;
}
- FD_SET(uipc_main.signal_fds[0], &uipc_main.active_set);
- uipc_main.max_fd = MAX(uipc_main.max_fd, uipc_main.signal_fds[0]);
+ FD_SET(uipc.signal_fds[0], &uipc.active_set);
+ uipc.max_fd = MAX(uipc.max_fd, uipc.signal_fds[0]);
for (i = 0; i < UIPC_CH_NUM; i++) {
- tUIPC_CHAN* p = &uipc_main.ch[i];
+ tUIPC_CHAN* p = &uipc.ch[i];
p->srvfd = UIPC_DISCONNECTED;
p->fd = UIPC_DISCONNECTED;
p->task_evt_flags = 0;
@@ -239,107 +210,104 @@
return 0;
}
-void uipc_main_cleanup(void) {
+void uipc_main_cleanup(tUIPC_STATE& uipc) {
int i;
BTIF_TRACE_EVENT("uipc_main_cleanup");
- close(uipc_main.signal_fds[0]);
- close(uipc_main.signal_fds[1]);
+ close(uipc.signal_fds[0]);
+ close(uipc.signal_fds[1]);
/* close any open channels */
- for (i = 0; i < UIPC_CH_NUM; i++) uipc_close_ch_locked(i);
-
- uipc_main.active_users.clear();
+ for (i = 0; i < UIPC_CH_NUM; i++) uipc_close_ch_locked(uipc, i);
}
/* check pending events in read task */
-static void uipc_check_task_flags_locked(void) {
+static void uipc_check_task_flags_locked(tUIPC_STATE& uipc) {
int i;
for (i = 0; i < UIPC_CH_NUM; i++) {
- if (uipc_main.ch[i].task_evt_flags & UIPC_TASK_FLAG_DISCONNECT_CHAN) {
- uipc_main.ch[i].task_evt_flags &= ~UIPC_TASK_FLAG_DISCONNECT_CHAN;
- uipc_close_ch_locked(i);
+ if (uipc.ch[i].task_evt_flags & UIPC_TASK_FLAG_DISCONNECT_CHAN) {
+ uipc.ch[i].task_evt_flags &= ~UIPC_TASK_FLAG_DISCONNECT_CHAN;
+ uipc_close_ch_locked(uipc, i);
}
/* add here */
}
}
-static int uipc_check_fd_locked(tUIPC_CH_ID ch_id) {
+static int uipc_check_fd_locked(tUIPC_STATE& uipc, tUIPC_CH_ID ch_id) {
if (ch_id >= UIPC_CH_NUM) return -1;
- // BTIF_TRACE_EVENT("CHECK SRVFD %d (ch %d)", uipc_main.ch[ch_id].srvfd,
+ // BTIF_TRACE_EVENT("CHECK SRVFD %d (ch %d)", uipc.ch[ch_id].srvfd,
// ch_id);
- if (SAFE_FD_ISSET(uipc_main.ch[ch_id].srvfd, &uipc_main.read_set)) {
+ if (SAFE_FD_ISSET(uipc.ch[ch_id].srvfd, &uipc.read_set)) {
BTIF_TRACE_EVENT("INCOMING CONNECTION ON CH %d", ch_id);
// Close the previous connection
- if (uipc_main.ch[ch_id].fd != UIPC_DISCONNECTED) {
- BTIF_TRACE_EVENT("CLOSE CONNECTION (FD %d)", uipc_main.ch[ch_id].fd);
- close(uipc_main.ch[ch_id].fd);
- FD_CLR(uipc_main.ch[ch_id].fd, &uipc_main.active_set);
- uipc_main.ch[ch_id].fd = UIPC_DISCONNECTED;
+ if (uipc.ch[ch_id].fd != UIPC_DISCONNECTED) {
+ BTIF_TRACE_EVENT("CLOSE CONNECTION (FD %d)", uipc.ch[ch_id].fd);
+ close(uipc.ch[ch_id].fd);
+ FD_CLR(uipc.ch[ch_id].fd, &uipc.active_set);
+ uipc.ch[ch_id].fd = UIPC_DISCONNECTED;
}
- uipc_main.ch[ch_id].fd = accept_server_socket(uipc_main.ch[ch_id].srvfd);
+ uipc.ch[ch_id].fd = accept_server_socket(uipc.ch[ch_id].srvfd);
- BTIF_TRACE_EVENT("NEW FD %d", uipc_main.ch[ch_id].fd);
+ BTIF_TRACE_EVENT("NEW FD %d", uipc.ch[ch_id].fd);
- if ((uipc_main.ch[ch_id].fd >= 0) && uipc_main.ch[ch_id].cback) {
+ if ((uipc.ch[ch_id].fd >= 0) && uipc.ch[ch_id].cback) {
/* if we have a callback we should add this fd to the active set
and notify user with callback event */
- BTIF_TRACE_EVENT("ADD FD %d TO ACTIVE SET", uipc_main.ch[ch_id].fd);
- FD_SET(uipc_main.ch[ch_id].fd, &uipc_main.active_set);
- uipc_main.max_fd = MAX(uipc_main.max_fd, uipc_main.ch[ch_id].fd);
+ BTIF_TRACE_EVENT("ADD FD %d TO ACTIVE SET", uipc.ch[ch_id].fd);
+ FD_SET(uipc.ch[ch_id].fd, &uipc.active_set);
+ uipc.max_fd = MAX(uipc.max_fd, uipc.ch[ch_id].fd);
}
- if (uipc_main.ch[ch_id].fd < 0) {
+ if (uipc.ch[ch_id].fd < 0) {
BTIF_TRACE_ERROR("FAILED TO ACCEPT CH %d", ch_id);
return -1;
}
- if (uipc_main.ch[ch_id].cback)
- uipc_main.ch[ch_id].cback(ch_id, UIPC_OPEN_EVT);
+ if (uipc.ch[ch_id].cback) uipc.ch[ch_id].cback(ch_id, UIPC_OPEN_EVT);
}
- // BTIF_TRACE_EVENT("CHECK FD %d (ch %d)", uipc_main.ch[ch_id].fd, ch_id);
+ // BTIF_TRACE_EVENT("CHECK FD %d (ch %d)", uipc.ch[ch_id].fd, ch_id);
- if (SAFE_FD_ISSET(uipc_main.ch[ch_id].fd, &uipc_main.read_set)) {
+ if (SAFE_FD_ISSET(uipc.ch[ch_id].fd, &uipc.read_set)) {
// BTIF_TRACE_EVENT("INCOMING DATA ON CH %d", ch_id);
- if (uipc_main.ch[ch_id].cback)
- uipc_main.ch[ch_id].cback(ch_id, UIPC_RX_DATA_READY_EVT);
+ if (uipc.ch[ch_id].cback)
+ uipc.ch[ch_id].cback(ch_id, UIPC_RX_DATA_READY_EVT);
}
return 0;
}
-static void uipc_check_interrupt_locked(void) {
- if (SAFE_FD_ISSET(uipc_main.signal_fds[0], &uipc_main.read_set)) {
+static void uipc_check_interrupt_locked(tUIPC_STATE& uipc) {
+ if (SAFE_FD_ISSET(uipc.signal_fds[0], &uipc.read_set)) {
char sig_recv = 0;
- OSI_NO_INTR(recv(uipc_main.signal_fds[0], &sig_recv, sizeof(sig_recv),
- MSG_WAITALL));
+ OSI_NO_INTR(
+ recv(uipc.signal_fds[0], &sig_recv, sizeof(sig_recv), MSG_WAITALL));
}
}
-static inline void uipc_wakeup_locked(void) {
+static inline void uipc_wakeup_locked(tUIPC_STATE& uipc) {
char sig_on = 1;
BTIF_TRACE_EVENT("UIPC SEND WAKE UP");
- OSI_NO_INTR(send(uipc_main.signal_fds[1], &sig_on, sizeof(sig_on), 0));
+ OSI_NO_INTR(send(uipc.signal_fds[1], &sig_on, sizeof(sig_on), 0));
}
-static int uipc_setup_server_locked(tUIPC_CH_ID ch_id, const char* name,
- tUIPC_RCV_CBACK* cback) {
+static int uipc_setup_server_locked(tUIPC_STATE& uipc, tUIPC_CH_ID ch_id,
+ const char* name, tUIPC_RCV_CBACK* cback) {
int fd;
BTIF_TRACE_EVENT("SETUP CHANNEL SERVER %d", ch_id);
if (ch_id >= UIPC_CH_NUM) return -1;
- std::lock_guard<std::recursive_mutex> guard(uipc_main.mutex);
+ std::lock_guard<std::recursive_mutex> guard(uipc.mutex);
fd = create_server_socket(name);
@@ -349,27 +317,27 @@
}
BTIF_TRACE_EVENT("ADD SERVER FD TO ACTIVE SET %d", fd);
- FD_SET(fd, &uipc_main.active_set);
- uipc_main.max_fd = MAX(uipc_main.max_fd, fd);
+ FD_SET(fd, &uipc.active_set);
+ uipc.max_fd = MAX(uipc.max_fd, fd);
- uipc_main.ch[ch_id].srvfd = fd;
- uipc_main.ch[ch_id].cback = cback;
- uipc_main.ch[ch_id].read_poll_tmo_ms = DEFAULT_READ_POLL_TMO_MS;
+ uipc.ch[ch_id].srvfd = fd;
+ uipc.ch[ch_id].cback = cback;
+ uipc.ch[ch_id].read_poll_tmo_ms = DEFAULT_READ_POLL_TMO_MS;
/* trigger main thread to update read set */
- uipc_wakeup_locked();
+ uipc_wakeup_locked(uipc);
return 0;
}
-static void uipc_flush_ch_locked(tUIPC_CH_ID ch_id) {
+static void uipc_flush_ch_locked(tUIPC_STATE& uipc, tUIPC_CH_ID ch_id) {
char buf[UIPC_FLUSH_BUFFER_SIZE];
struct pollfd pfd;
pfd.events = POLLIN;
- pfd.fd = uipc_main.ch[ch_id].fd;
+ pfd.fd = uipc.ch[ch_id].fd;
- if (uipc_main.ch[ch_id].fd == UIPC_DISCONNECTED) {
+ if (uipc.ch[ch_id].fd == UIPC_DISCONNECTED) {
BTIF_TRACE_EVENT("%s() - fd disconnected. Exiting", __func__);
return;
}
@@ -401,65 +369,65 @@
}
}
-static void uipc_flush_locked(tUIPC_CH_ID ch_id) {
+static void uipc_flush_locked(tUIPC_STATE& uipc, tUIPC_CH_ID ch_id) {
if (ch_id >= UIPC_CH_NUM) return;
switch (ch_id) {
case UIPC_CH_ID_AV_CTRL:
- uipc_flush_ch_locked(UIPC_CH_ID_AV_CTRL);
+ uipc_flush_ch_locked(uipc, UIPC_CH_ID_AV_CTRL);
break;
case UIPC_CH_ID_AV_AUDIO:
- uipc_flush_ch_locked(UIPC_CH_ID_AV_AUDIO);
+ uipc_flush_ch_locked(uipc, UIPC_CH_ID_AV_AUDIO);
break;
}
}
-static int uipc_close_ch_locked(tUIPC_CH_ID ch_id) {
+static int uipc_close_ch_locked(tUIPC_STATE& uipc, tUIPC_CH_ID ch_id) {
int wakeup = 0;
BTIF_TRACE_EVENT("CLOSE CHANNEL %d", ch_id);
if (ch_id >= UIPC_CH_NUM) return -1;
- if (uipc_main.ch[ch_id].srvfd != UIPC_DISCONNECTED) {
- BTIF_TRACE_EVENT("CLOSE SERVER (FD %d)", uipc_main.ch[ch_id].srvfd);
- close(uipc_main.ch[ch_id].srvfd);
- FD_CLR(uipc_main.ch[ch_id].srvfd, &uipc_main.active_set);
- uipc_main.ch[ch_id].srvfd = UIPC_DISCONNECTED;
+ if (uipc.ch[ch_id].srvfd != UIPC_DISCONNECTED) {
+ BTIF_TRACE_EVENT("CLOSE SERVER (FD %d)", uipc.ch[ch_id].srvfd);
+ close(uipc.ch[ch_id].srvfd);
+ FD_CLR(uipc.ch[ch_id].srvfd, &uipc.active_set);
+ uipc.ch[ch_id].srvfd = UIPC_DISCONNECTED;
wakeup = 1;
}
- if (uipc_main.ch[ch_id].fd != UIPC_DISCONNECTED) {
- BTIF_TRACE_EVENT("CLOSE CONNECTION (FD %d)", uipc_main.ch[ch_id].fd);
- close(uipc_main.ch[ch_id].fd);
- FD_CLR(uipc_main.ch[ch_id].fd, &uipc_main.active_set);
- uipc_main.ch[ch_id].fd = UIPC_DISCONNECTED;
+ if (uipc.ch[ch_id].fd != UIPC_DISCONNECTED) {
+ BTIF_TRACE_EVENT("CLOSE CONNECTION (FD %d)", uipc.ch[ch_id].fd);
+ close(uipc.ch[ch_id].fd);
+ FD_CLR(uipc.ch[ch_id].fd, &uipc.active_set);
+ uipc.ch[ch_id].fd = UIPC_DISCONNECTED;
wakeup = 1;
}
/* notify this connection is closed */
- if (uipc_main.ch[ch_id].cback)
- uipc_main.ch[ch_id].cback(ch_id, UIPC_CLOSE_EVT);
+ if (uipc.ch[ch_id].cback) uipc.ch[ch_id].cback(ch_id, UIPC_CLOSE_EVT);
/* trigger main thread update if something was updated */
- if (wakeup) uipc_wakeup_locked();
+ if (wakeup) uipc_wakeup_locked(uipc);
return 0;
}
-void uipc_close_locked(tUIPC_CH_ID ch_id) {
- if (uipc_main.ch[ch_id].srvfd == UIPC_DISCONNECTED) {
+void uipc_close_locked(tUIPC_STATE& uipc, tUIPC_CH_ID ch_id) {
+ if (uipc.ch[ch_id].srvfd == UIPC_DISCONNECTED) {
BTIF_TRACE_EVENT("CHANNEL %d ALREADY CLOSED", ch_id);
return;
}
/* schedule close on this channel */
- uipc_main.ch[ch_id].task_evt_flags |= UIPC_TASK_FLAG_DISCONNECT_CHAN;
- uipc_wakeup_locked();
+ uipc.ch[ch_id].task_evt_flags |= UIPC_TASK_FLAG_DISCONNECT_CHAN;
+ uipc_wakeup_locked(uipc);
}
-static void* uipc_read_task(UNUSED_ATTR void* arg) {
+static void* uipc_read_task(void* arg) {
+ tUIPC_STATE& uipc = *((tUIPC_STATE*)arg);
int ch_id;
int result;
@@ -467,11 +435,10 @@
raise_priority_a2dp(TASK_UIPC_READ);
- while (uipc_main.running) {
- uipc_main.read_set = uipc_main.active_set;
+ while (uipc.running) {
+ uipc.read_set = uipc.active_set;
- result =
- select(uipc_main.max_fd + 1, &uipc_main.read_set, NULL, NULL, NULL);
+ result = select(uipc.max_fd + 1, &uipc.read_set, NULL, NULL, NULL);
if (result == 0) {
BTIF_TRACE_EVENT("select timeout");
@@ -485,40 +452,40 @@
}
{
- std::lock_guard<std::recursive_mutex> guard(uipc_main.mutex);
+ std::lock_guard<std::recursive_mutex> guard(uipc.mutex);
/* clear any wakeup interrupt */
- uipc_check_interrupt_locked();
+ uipc_check_interrupt_locked(uipc);
/* check pending task events */
- uipc_check_task_flags_locked();
+ uipc_check_task_flags_locked(uipc);
/* make sure we service audio channel first */
- uipc_check_fd_locked(UIPC_CH_ID_AV_AUDIO);
+ uipc_check_fd_locked(uipc, UIPC_CH_ID_AV_AUDIO);
/* check for other connections */
for (ch_id = 0; ch_id < UIPC_CH_NUM; ch_id++) {
- if (ch_id != UIPC_CH_ID_AV_AUDIO) uipc_check_fd_locked(ch_id);
+ if (ch_id != UIPC_CH_ID_AV_AUDIO) uipc_check_fd_locked(uipc, ch_id);
}
}
}
BTIF_TRACE_EVENT("UIPC READ THREAD EXITING");
- uipc_main_cleanup();
+ uipc_main_cleanup(uipc);
- uipc_main.tid = 0;
+ uipc.tid = 0;
BTIF_TRACE_EVENT("UIPC READ THREAD DONE");
return nullptr;
}
-int uipc_start_main_server_thread(void) {
- uipc_main.running = 1;
+int uipc_start_main_server_thread(tUIPC_STATE& uipc) {
+ uipc.running = 1;
- if (pthread_create(&uipc_main.tid, (const pthread_attr_t*)NULL,
- uipc_read_task, nullptr) < 0) {
+ if (pthread_create(&uipc.tid, (const pthread_attr_t*)NULL, uipc_read_task,
+ &uipc) < 0) {
BTIF_TRACE_ERROR("uipc_thread_create pthread_create failed:%d", errno);
return -1;
}
@@ -527,19 +494,19 @@
}
/* blocking call */
-void uipc_stop_main_server_thread(void) {
+void uipc_stop_main_server_thread(tUIPC_STATE& uipc) {
/* request shutdown of read thread */
{
- std::lock_guard<std::recursive_mutex> lock(uipc_main.mutex);
- uipc_main.running = 0;
- uipc_wakeup_locked();
+ std::lock_guard<std::recursive_mutex> lock(uipc.mutex);
+ uipc.running = 0;
+ uipc_wakeup_locked(uipc);
}
/* wait until read thread is fully terminated */
/* tid might hold pointer value where it's value
is negative vaule with singed bit is set, so
corrected the logic to check zero or non zero */
- if (uipc_main.tid) pthread_join(uipc_main.tid, NULL);
+ if (uipc.tid) pthread_join(uipc.tid, NULL);
}
/*******************************************************************************
@@ -551,21 +518,16 @@
** Returns void
**
******************************************************************************/
-
-void UIPC_Init(UNUSED_ATTR void* p_data, int user) {
+std::unique_ptr<tUIPC_STATE> UIPC_Init() {
+ std::unique_ptr<tUIPC_STATE> uipc = std::make_unique<tUIPC_STATE>();
BTIF_TRACE_DEBUG("UIPC_Init");
- if (user < 0 || user >= UIPC_USER_NUM) {
- BTIF_TRACE_ERROR("UIPC_Close : invalid user ID %d", user);
- return;
- }
- std::lock_guard<std::recursive_mutex> lock(uipc_main.mutex);
- auto result_insert = uipc_main.active_users.insert(user);
- if ((uipc_main.active_users.size() != 1) || !result_insert.second) {
- return;
- }
- uipc_main_init();
- uipc_start_main_server_thread();
+ std::lock_guard<std::recursive_mutex> lock(uipc->mutex);
+
+ uipc_main_init(*uipc);
+ uipc_start_main_server_thread(*uipc);
+
+ return uipc;
}
/*******************************************************************************
@@ -577,27 +539,27 @@
** Returns true in case of success, false in case of failure.
**
******************************************************************************/
-bool UIPC_Open(tUIPC_CH_ID ch_id, tUIPC_RCV_CBACK* p_cback) {
+bool UIPC_Open(tUIPC_STATE& uipc, tUIPC_CH_ID ch_id, tUIPC_RCV_CBACK* p_cback) {
BTIF_TRACE_DEBUG("UIPC_Open : ch_id %d, p_cback %x", ch_id, p_cback);
- std::lock_guard<std::recursive_mutex> lock(uipc_main.mutex);
+ std::lock_guard<std::recursive_mutex> lock(uipc.mutex);
if (ch_id >= UIPC_CH_NUM) {
return false;
}
- if (uipc_main.ch[ch_id].srvfd != UIPC_DISCONNECTED) {
+ if (uipc.ch[ch_id].srvfd != UIPC_DISCONNECTED) {
BTIF_TRACE_EVENT("CHANNEL %d ALREADY OPEN", ch_id);
return 0;
}
switch (ch_id) {
case UIPC_CH_ID_AV_CTRL:
- uipc_setup_server_locked(ch_id, A2DP_CTRL_PATH, p_cback);
+ uipc_setup_server_locked(uipc, ch_id, A2DP_CTRL_PATH, p_cback);
break;
case UIPC_CH_ID_AV_AUDIO:
- uipc_setup_server_locked(ch_id, A2DP_DATA_PATH, p_cback);
+ uipc_setup_server_locked(uipc, ch_id, A2DP_DATA_PATH, p_cback);
break;
}
@@ -613,30 +575,18 @@
** Returns void
**
******************************************************************************/
-
-void UIPC_Close(tUIPC_CH_ID ch_id, int user) {
+void UIPC_Close(tUIPC_STATE& uipc, tUIPC_CH_ID ch_id) {
BTIF_TRACE_DEBUG("UIPC_Close : ch_id %d", ch_id);
- if (user < 0 || user >= UIPC_USER_NUM) {
- BTIF_TRACE_ERROR("UIPC_Close : invalid user ID %d", user);
- return;
- }
+
/* special case handling uipc shutdown */
if (ch_id != UIPC_CH_ID_ALL) {
- std::lock_guard<std::recursive_mutex> lock(uipc_main.mutex);
- uipc_close_locked(ch_id);
- return;
- }
-
- if (uipc_main.active_users.erase(user) == 0) {
- return;
- }
-
- if (!uipc_main.active_users.empty()) {
+ std::lock_guard<std::recursive_mutex> lock(uipc.mutex);
+ uipc_close_locked(uipc, ch_id);
return;
}
BTIF_TRACE_DEBUG("UIPC_Close : waiting for shutdown to complete");
- uipc_stop_main_server_thread();
+ uipc_stop_main_server_thread(uipc);
BTIF_TRACE_DEBUG("UIPC_Close : shutdown complete");
}
@@ -649,14 +599,15 @@
** Returns true in case of success, false in case of failure.
**
******************************************************************************/
-bool UIPC_Send(tUIPC_CH_ID ch_id, UNUSED_ATTR uint16_t msg_evt,
- const uint8_t* p_buf, uint16_t msglen) {
+bool UIPC_Send(tUIPC_STATE& uipc, tUIPC_CH_ID ch_id,
+ UNUSED_ATTR uint16_t msg_evt, const uint8_t* p_buf,
+ uint16_t msglen) {
BTIF_TRACE_DEBUG("UIPC_Send : ch_id:%d %d bytes", ch_id, msglen);
- std::lock_guard<std::recursive_mutex> lock(uipc_main.mutex);
+ std::lock_guard<std::recursive_mutex> lock(uipc.mutex);
ssize_t ret;
- OSI_NO_INTR(ret = write(uipc_main.ch[ch_id].fd, p_buf, msglen));
+ OSI_NO_INTR(ret = write(uipc.ch[ch_id].fd, p_buf, msglen));
if (ret < 0) {
BTIF_TRACE_ERROR("failed to write (%s)", strerror(errno));
}
@@ -674,10 +625,11 @@
**
******************************************************************************/
-uint32_t UIPC_Read(tUIPC_CH_ID ch_id, UNUSED_ATTR uint16_t* p_msg_evt,
- uint8_t* p_buf, uint32_t len) {
+uint32_t UIPC_Read(tUIPC_STATE& uipc, tUIPC_CH_ID ch_id,
+ UNUSED_ATTR uint16_t* p_msg_evt, uint8_t* p_buf,
+ uint32_t len) {
int n_read = 0;
- int fd = uipc_main.ch[ch_id].fd;
+ int fd = uipc.ch[ch_id].fd;
struct pollfd pfd;
if (ch_id >= UIPC_CH_NUM) {
@@ -698,10 +650,10 @@
a read for more than poll timeout */
int poll_ret;
- OSI_NO_INTR(poll_ret = poll(&pfd, 1, uipc_main.ch[ch_id].read_poll_tmo_ms));
+ OSI_NO_INTR(poll_ret = poll(&pfd, 1, uipc.ch[ch_id].read_poll_tmo_ms));
if (poll_ret == 0) {
BTIF_TRACE_WARNING("poll timeout (%d ms)",
- uipc_main.ch[ch_id].read_poll_tmo_ms);
+ uipc.ch[ch_id].read_poll_tmo_ms);
break;
}
if (poll_ret < 0) {
@@ -714,8 +666,8 @@
if (pfd.revents & (POLLHUP | POLLNVAL)) {
BTIF_TRACE_WARNING("poll : channel detached remotely");
- std::lock_guard<std::recursive_mutex> lock(uipc_main.mutex);
- uipc_close_locked(ch_id);
+ std::lock_guard<std::recursive_mutex> lock(uipc.mutex);
+ uipc_close_locked(uipc, ch_id);
return 0;
}
@@ -726,8 +678,8 @@
if (n == 0) {
BTIF_TRACE_WARNING("UIPC_Read : channel detached remotely");
- std::lock_guard<std::recursive_mutex> lock(uipc_main.mutex);
- uipc_close_locked(ch_id);
+ std::lock_guard<std::recursive_mutex> lock(uipc.mutex);
+ uipc_close_locked(uipc, ch_id);
return 0;
}
@@ -752,37 +704,38 @@
*
******************************************************************************/
-extern bool UIPC_Ioctl(tUIPC_CH_ID ch_id, uint32_t request, void* param) {
+extern bool UIPC_Ioctl(tUIPC_STATE& uipc, tUIPC_CH_ID ch_id, uint32_t request,
+ void* param) {
BTIF_TRACE_DEBUG("#### UIPC_Ioctl : ch_id %d, request %d ####", ch_id,
request);
- std::lock_guard<std::recursive_mutex> lock(uipc_main.mutex);
+ std::lock_guard<std::recursive_mutex> lock(uipc.mutex);
switch (request) {
case UIPC_REQ_RX_FLUSH:
- uipc_flush_locked(ch_id);
+ uipc_flush_locked(uipc, ch_id);
break;
case UIPC_REG_CBACK:
// BTIF_TRACE_EVENT("register callback ch %d srvfd %d, fd %d", ch_id,
- // uipc_main.ch[ch_id].srvfd, uipc_main.ch[ch_id].fd);
- uipc_main.ch[ch_id].cback = (tUIPC_RCV_CBACK*)param;
+ // uipc.ch[ch_id].srvfd, uipc.ch[ch_id].fd);
+ uipc.ch[ch_id].cback = (tUIPC_RCV_CBACK*)param;
break;
case UIPC_REG_REMOVE_ACTIVE_READSET:
/* user will read data directly and not use select loop */
- if (uipc_main.ch[ch_id].fd != UIPC_DISCONNECTED) {
+ if (uipc.ch[ch_id].fd != UIPC_DISCONNECTED) {
/* remove this channel from active set */
- FD_CLR(uipc_main.ch[ch_id].fd, &uipc_main.active_set);
+ FD_CLR(uipc.ch[ch_id].fd, &uipc.active_set);
/* refresh active set */
- uipc_wakeup_locked();
+ uipc_wakeup_locked(uipc);
}
break;
case UIPC_SET_READ_POLL_TMO:
- uipc_main.ch[ch_id].read_poll_tmo_ms = (intptr_t)param;
+ uipc.ch[ch_id].read_poll_tmo_ms = (intptr_t)param;
BTIF_TRACE_EVENT("UIPC_SET_READ_POLL_TMO : CH %d, TMO %d ms", ch_id,
- uipc_main.ch[ch_id].read_poll_tmo_ms);
+ uipc.ch[ch_id].read_poll_tmo_ms);
break;
default: