summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMisael Lopez Cruz <misael.lopez@ti.com>2014-06-03 13:08:09 -0500
committerGerrit Code Review <gerrit2@git.omapzoom.org>2014-06-03 18:17:24 -0500
commit8bbadc3eca1667ff59e7981cae9573fbd51c62a4 (patch)
tree34e847edc424c6cacfb5c9947af83493d189dc2c
parent3da8567243ec8a5d46581f9e5642a1a105471a6c (diff)
downloadjacinto6evm-8bbadc3eca1667ff59e7981cae9573fbd51c62a4.tar.gz
audio: Legacy: BT SCO voice call support
The Bluetooth SCO voice call has the following data flow: Uplink: +----------- SRC -----------+ | | McASP7 McASP3 | | Bluetooth Mic Downlink: +----------- SRC -----------+ | | McASP7 McASP3 | | Bluetooth Speaker The voice call uses the ALSA PCM devices that are normally used by the primary output and input. The primary output is switched to a null/dummy sink when the voice call starts, and switched back to the ALSA devices when the call ends. The null/dummy sink consumes the data at the same rate than an actual PCM device does, but does not render anything to an output device (e.g. speaker). Change-Id: I63a41de96c4ed34b60aab9240d10ad83c06c69ac Signed-off-by: Misael Lopez Cruz <misael.lopez@ti.com>
-rw-r--r--audio/legacy/audio_hw.c531
-rw-r--r--audio/mixer_paths.xml7
2 files changed, 514 insertions, 24 deletions
diff --git a/audio/legacy/audio_hw.c b/audio/legacy/audio_hw.c
index d25fedf..e54ee45 100644
--- a/audio/legacy/audio_hw.c
+++ b/audio/legacy/audio_hw.c
@@ -44,6 +44,12 @@
/* yet another definition of ARRAY_SIZE macro) */
#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
+/*
+ * additional space in resampler buffer allowing for extra samples to be returned
+ * by speex resampler when sample rates ratio is not an integer
+ */
+#define RESAMPLER_HEADROOM_FRAMES 10
+
/* buffer_remix: functor for doing in-place buffer manipulations.
*
* NB. When remix_func is called, the memory at `buf` must be at least
@@ -56,10 +62,34 @@ struct buffer_remix {
size_t out_chans; /* number of output channels */
};
+struct j6_voice_stream {
+ struct j6_audio_device *dev;
+ struct pcm *pcm_in;
+ struct pcm *pcm_out;
+ struct pcm_config in_config;
+ struct pcm_config out_config;
+ struct resampler_itfe *resampler;
+ struct resampler_buffer_provider buf_provider;
+ struct buffer_remix *remix;
+ pthread_t thread;
+ int16_t *in_buffer;
+ int16_t *out_buffer;
+ size_t in_frames;
+ size_t out_frames;
+ size_t frame_size;
+ char *name;
+};
+
+struct j6_voice {
+ struct j6_voice_stream ul;
+ struct j6_voice_stream dl;
+};
+
struct j6_audio_device {
struct audio_hw_device device;
struct j6_stream_in *in;
struct j6_stream_out *out;
+ struct j6_voice voice;
struct audio_route *route;
audio_devices_t in_device;
audio_devices_t out_device;
@@ -67,7 +97,10 @@ struct j6_audio_device {
unsigned int card;
unsigned int in_port;
unsigned int out_port;
+ unsigned int bt_port;
bool mic_mute;
+ bool in_call;
+ audio_mode_t mode;
};
struct j6_stream_in {
@@ -93,6 +126,7 @@ struct j6_stream_out {
struct j6_audio_device *dev;
struct pcm_config config;
struct pcm *pcm;
+ struct timespec last;
pthread_mutex_t lock;
bool standby;
int64_t written; /* total frames written, not cleared when entering standby */
@@ -113,8 +147,8 @@ static const char *supported_cards[] = {
AUDIO_DEVICE_OUT_DEFAULT)
#define CAPTURE_SAMPLE_RATE 44100
-#define CAPTURE_PERIOD_SIZE 320
-#define CAPTURE_PERIOD_COUNT 2
+#define CAPTURE_PERIOD_SIZE 960
+#define CAPTURE_PERIOD_COUNT 4
#define CAPTURE_BUFFER_SIZE (CAPTURE_PERIOD_SIZE * CAPTURE_PERIOD_COUNT)
#define PLAYBACK_SAMPLE_RATE 44100
@@ -122,6 +156,11 @@ static const char *supported_cards[] = {
#define PLAYBACK_PERIOD_COUNT 4
#define PLAYBACK_BUFFER_SIZE (PLAYBACK_PERIOD_SIZE * PLAYBACK_PERIOD_COUNT)
+#define BT_SAMPLE_RATE 8000
+#define BT_PERIOD_SIZE 160
+#define BT_PERIOD_COUNT 4
+#define BT_BUFFER_SIZE (BT_PERIOD_SIZE * BT_PERIOD_COUNT)
+
struct pcm_config pcm_config_capture = {
.channels = 2,
.rate = CAPTURE_SAMPLE_RATE,
@@ -143,6 +182,27 @@ struct pcm_config pcm_config_playback = {
.avail_min = PLAYBACK_PERIOD_SIZE,
};
+struct pcm_config pcm_config_bt_in = {
+ .channels = 2,
+ .rate = BT_SAMPLE_RATE,
+ .format = PCM_FORMAT_S16_LE,
+ .period_size = BT_PERIOD_SIZE,
+ .period_count = BT_PERIOD_COUNT,
+ .start_threshold = 1,
+ .stop_threshold = BT_BUFFER_SIZE,
+};
+
+struct pcm_config pcm_config_bt_out = {
+ .channels = 2,
+ .rate = BT_SAMPLE_RATE,
+ .format = PCM_FORMAT_S16_LE,
+ .period_size = BT_PERIOD_SIZE,
+ .period_count = BT_PERIOD_COUNT,
+ .start_threshold = BT_BUFFER_SIZE / 2,
+ .stop_threshold = BT_BUFFER_SIZE,
+ .avail_min = BT_PERIOD_SIZE,
+};
+
static int find_supported_card(void)
{
char name[256] = "";
@@ -174,6 +234,8 @@ static int find_supported_card(void)
return card;
}
+static void do_out_standby(struct j6_stream_out *out);
+
/* must be called with device lock held */
static void select_input_device(struct j6_audio_device *adev)
{
@@ -259,6 +321,362 @@ static int setup_stereo_to_mono_input_remix(struct j6_stream_in *in)
return 0;
}
+/*
+ * Implementation of buffer_remix::remix_func that duplicates the first
+ * channel into the rest of channels in the frame without doing any other
+ * processing. It assumes data in 16-bits, but it's not explicitly checked
+ */
+static void mono_remix(struct buffer_remix *data, void *buf, size_t frames)
+{
+ int16_t *buffer = (int16_t*)buf;
+ size_t i;
+
+ ALOGVV("mono_remix() remix=%p buf=%p frames=%u", data, buf, frames);
+
+ if (frames == 0)
+ return;
+
+ /* duplicate first channel into the rest of channels in the frame */
+ while (frames--) {
+ for (i = 1; i < data->out_chans; i++)
+ buffer[i] = buffer[0];
+ buffer += data->out_chans;
+ }
+}
+
+static int setup_mono_input_remix(struct j6_voice_stream *stream)
+{
+ ALOGV("setup_mono_input_remix() %s stream", stream->name);
+
+ struct buffer_remix *br = (struct buffer_remix *)malloc(sizeof(struct buffer_remix));
+ if (!br)
+ return -ENOMEM;
+
+ br->remix_func = mono_remix;
+ br->sample_size = sizeof(int16_t);
+ br->in_chans = stream->in_config.channels;
+ br->out_chans = stream->out_config.channels;
+ stream->remix = br;
+
+ return 0;
+}
+
+static int voice_get_next_buffer(struct resampler_buffer_provider *buffer_provider,
+ struct resampler_buffer* buffer)
+{
+ struct j6_voice_stream *stream;
+ int ret;
+
+ if (buffer_provider == NULL || buffer == NULL) {
+ ALOGE("voice_get_next_buffer() invalid buffer/provider");
+ return -EINVAL;
+ }
+
+ stream = (struct j6_voice_stream *)((char *)buffer_provider -
+ offsetof(struct j6_voice_stream, buf_provider));
+
+ if (stream->pcm_in == NULL) {
+ buffer->raw = NULL;
+ buffer->frame_count = 0;
+ return -ENODEV;
+ }
+
+ if (buffer->frame_count > stream->in_frames) {
+ ALOGW("voice_get_next_buffer() %s unexpected frame count %u, "
+ "buffer was allocated for %u frames",
+ stream->name, buffer->frame_count, stream->in_frames);
+ buffer->frame_count = stream->in_frames;
+ }
+
+ ret = pcm_read(stream->pcm_in, stream->in_buffer,
+ buffer->frame_count * stream->frame_size);
+ if (ret) {
+ ALOGE("voice_get_next_buffer() failed to read %s: %s",
+ stream->name, pcm_get_error(stream->pcm_in));
+ buffer->raw = NULL;
+ buffer->frame_count = 0;
+ return ret;
+ }
+
+ buffer->i16 = stream->in_buffer;
+
+ return ret;
+}
+
+static void voice_release_buffer(struct resampler_buffer_provider *buffer_provider,
+ struct resampler_buffer* buffer)
+{
+}
+
+static void *voice_thread_func(void *arg)
+{
+ struct j6_voice_stream *stream = (struct j6_voice_stream *)arg;
+ struct j6_audio_device *adev = stream->dev;
+ struct timespec now;
+ size_t frames = stream->out_frames;
+ uint32_t periods = 0;
+ uint32_t avail;
+ bool in_steady = false;
+ bool out_steady = false;
+ int ret = 0;
+
+ pcm_start(stream->pcm_in);
+
+ memset(stream->out_buffer, 0, stream->out_frames * stream->frame_size);
+
+ while (adev->in_call) {
+ if (out_steady) {
+ if (in_steady) {
+ stream->resampler->resample_from_provider(stream->resampler,
+ stream->out_buffer,
+ &frames);
+ } else {
+ ret = pcm_get_htimestamp(stream->pcm_in, &avail, &now);
+ if (!ret && (avail > 0)) {
+ in_steady = true;
+ continue;
+ }
+ }
+ } else if (++periods == stream->out_config.period_count) {
+ out_steady = true;
+ }
+
+ if (stream->remix)
+ stream->remix->remix_func(stream->remix, stream->out_buffer, frames);
+
+ ret = pcm_write(stream->pcm_out, stream->out_buffer,
+ frames * stream->frame_size);
+ if (ret) {
+ ALOGE("voice_thread_func() failed to write %s: %s",
+ stream->name, pcm_get_error(stream->pcm_out));
+ usleep((frames * 1000000) / stream->out_config.rate);
+ }
+ }
+
+ return (void*)ret;
+}
+
+static void voice_stream_exit(struct j6_voice_stream *stream)
+{
+ if (stream->resampler) {
+ release_resampler(stream->resampler);
+ stream->resampler = NULL;
+ }
+
+ if (stream->pcm_out) {
+ pcm_close(stream->pcm_out);
+ stream->pcm_out = NULL;
+ }
+
+ if (stream->pcm_in) {
+ pcm_close(stream->pcm_in);
+ stream->pcm_in = NULL;
+ }
+
+ if (stream->in_buffer) {
+ free(stream->in_buffer);
+ stream->in_buffer = NULL;
+ stream->in_frames = 0;
+ }
+
+ if (stream->out_buffer) {
+ free(stream->out_buffer);
+ stream->out_buffer = NULL;
+ stream->out_frames = 0;
+ }
+
+ if (stream->remix) {
+ free(stream->remix);
+ stream->remix = NULL;
+ }
+
+ if (stream->name) {
+ free(stream->name);
+ stream->name = NULL;
+ }
+}
+
+static int voice_stream_init(struct j6_voice_stream *stream,
+ unsigned int in_port,
+ unsigned int out_port,
+ bool needs_mono_remix)
+{
+ struct j6_audio_device *adev = stream->dev;
+ int ret;
+
+ stream->buf_provider.get_next_buffer = voice_get_next_buffer;
+ stream->buf_provider.release_buffer = voice_release_buffer;
+ ret = create_resampler(stream->in_config.rate,
+ stream->out_config.rate,
+ 2,
+ RESAMPLER_QUALITY_DEFAULT,
+ &stream->buf_provider,
+ &stream->resampler);
+ if (ret) {
+ ALOGE("voice_stream_init() failed to create %s resampler %d", stream->name, ret);
+ return ret;
+ }
+
+ stream->pcm_in = pcm_open(adev->card, in_port, PCM_IN, &stream->in_config);
+ stream->pcm_out = pcm_open(adev->card, out_port, PCM_OUT, &stream->out_config);
+
+ if (!pcm_is_ready(stream->pcm_in) || !pcm_is_ready(stream->pcm_out)) {
+ ALOGE("voice_stream_init() failed to open pcm %s devices", stream->name);
+ voice_stream_exit(stream);
+ return -ENODEV;
+ }
+
+ stream->frame_size = pcm_frames_to_bytes(stream->pcm_in, 1);
+
+ /* out_buffer will store the resampled data */
+ stream->out_frames = stream->out_config.period_size;
+ stream->out_buffer = malloc(stream->out_frames * stream->frame_size);
+
+ /* in_buffer will store the frames recorded from the PCM device */
+ stream->in_frames = (stream->out_frames * stream->in_config.rate) / stream->out_config.rate +
+ RESAMPLER_HEADROOM_FRAMES;
+ stream->in_buffer = malloc(stream->in_frames * stream->frame_size);
+
+ if (!stream->in_buffer || !stream->out_buffer) {
+ ALOGE("voice_stream_init() failed to allocate %s buffers", stream->name);
+ voice_stream_exit(stream);
+ return -ENOMEM;
+ }
+
+ if (needs_mono_remix) {
+ ret = setup_mono_input_remix(stream);
+ if (ret) {
+ ALOGE("voice_stream_init() failed to setup mono remix %d", ret);
+ voice_stream_exit(stream);
+ return ret;
+ }
+ } else {
+ stream->remix = NULL;
+ }
+
+ return 0;
+}
+
+static int enter_voice_call(struct j6_audio_device *adev)
+{
+ struct j6_voice *voice = &adev->voice;
+ int ret;
+
+ ALOGI("enter_voice_call() entering bluetooth voice call");
+
+ audio_route_apply_path(adev->route, "BT SCO Master");
+ audio_route_update_mixer(adev->route);
+
+ /* Let the primary output switch to a dummy sink */
+ if (adev->out)
+ do_out_standby(adev->out);
+
+ /* Uplink: Mic (44.1kHz) -> BT (8kHz) */
+ voice->ul.name = strdup("UL");
+ voice->ul.in_config = pcm_config_capture;
+ voice->ul.out_config = pcm_config_bt_out;
+ voice->ul.dev = adev;
+ ret = voice_stream_init(&voice->ul, adev->in_port, adev->bt_port, false);
+ if (ret) {
+ ALOGE("enter_voice_call() failed to init uplink %d", ret);
+ goto err_ul_init;
+ }
+
+ /* Downlink: BT (8kHz) -> HP/Spk (44.1kHz) */
+ voice->dl.name = strdup("DL");
+ voice->dl.in_config = pcm_config_bt_in;
+ voice->dl.out_config = pcm_config_playback;
+ voice->dl.dev = adev;
+ ret = voice_stream_init(&voice->dl, adev->bt_port, adev->out_port, true);
+ if (ret) {
+ ALOGE("enter_voice_call() failed to init downlink %d", ret);
+ goto err_dl_init;
+ }
+
+ adev->in_call = true;
+
+ /* Create uplink thread: Mic -> BT */
+ ret = pthread_create(&voice->ul.thread, NULL, voice_thread_func, &voice->ul);
+ if (ret) {
+ ALOGE("enter_voice_call() failed to create uplink thread %d", ret);
+ adev->in_call = false;
+ goto err_ul_thread;
+ }
+
+ /* Create downlink thread: BT -> HP/Spk */
+ ret = pthread_create(&voice->dl.thread, NULL, voice_thread_func, &voice->dl);
+ if (ret) {
+ ALOGE("enter_voice_call() failed to create downlink thread %d", ret);
+ adev->in_call = false;
+ goto err_dl_thread;
+ }
+
+ return 0;
+
+ err_dl_thread:
+ pthread_join(voice->ul.thread, NULL);
+ err_ul_thread:
+ voice_stream_exit(&voice->ul);
+ err_dl_init:
+ voice_stream_exit(&voice->dl);
+ err_ul_init:
+ audio_route_reset_path(adev->route, "BT SCO Master");
+ audio_route_update_mixer(adev->route);
+
+ return ret;
+}
+
+static void leave_voice_call(struct j6_audio_device *adev)
+{
+ struct j6_voice *voice = &adev->voice;
+ struct j6_voice_stream *ul = &voice->ul;
+ struct j6_voice_stream *dl = &voice->dl;
+ void *ret;
+
+ ALOGI("leave_voice_call() leaving bluetooth voice call");
+
+ adev->in_call = false;
+
+ /*
+ * The PCM ports used for Bluetooth are slaves and they can lose the
+ * BCLK and FSYNC while still active. That leads to blocking read() and
+ * write() calls, which is prevented by switching the clock source to
+ * an internal one and explicitly stopping both ports for the new source
+ * to take effect at kernel level
+ */
+ audio_route_reset_path(adev->route, "BT SCO Master");
+ audio_route_update_mixer(adev->route);
+ if (ul->pcm_out)
+ pcm_stop(ul->pcm_out);
+ if (dl->pcm_in)
+ pcm_stop(dl->pcm_in);
+
+ pthread_join(voice->dl.thread, &ret);
+ pthread_join(voice->ul.thread, &ret);
+
+ voice_stream_exit(&voice->dl);
+ voice_stream_exit(&voice->ul);
+
+ /* Let the primary output switch back to its ALSA PCM device */
+ if (adev->out)
+ do_out_standby(adev->out);
+}
+
+static uint32_t time_diff(struct timespec t1, struct timespec t0)
+{
+ struct timespec temp;
+
+ if ((t1.tv_nsec - t0.tv_nsec) < 0) {
+ temp.tv_sec = t1.tv_sec - t0.tv_sec-1;
+ temp.tv_nsec = 1000000000UL + t1.tv_nsec - t0.tv_nsec;
+ } else {
+ temp.tv_sec = t1.tv_sec - t0.tv_sec;
+ temp.tv_nsec = t1.tv_nsec - t0.tv_nsec;
+ }
+
+ return (temp.tv_sec * 1000000UL + temp.tv_nsec / 1000);
+}
+
/* audio HAL functions */
static uint32_t out_get_sample_rate(const struct audio_stream *stream)
@@ -324,9 +742,13 @@ static void do_out_standby(struct j6_stream_out *out)
struct j6_audio_device *adev = out->dev;
if (!out->standby) {
- ALOGI("do_out_standby() close card %u port %u", adev->card, adev->out_port);
- pcm_close(out->pcm);
- out->pcm = NULL;
+ if (adev->mode != AUDIO_MODE_IN_CALL) {
+ ALOGI("do_out_standby() close card %u port %u", adev->card, adev->out_port);
+ pcm_close(out->pcm);
+ out->pcm = NULL;
+ } else {
+ ALOGI("do_out_standby() close dummy card");
+ }
out->standby = true;
}
}
@@ -411,10 +833,13 @@ static ssize_t out_write(struct audio_stream_out *stream, const void* buffer,
{
struct j6_stream_out *out = (struct j6_stream_out *)(stream);
struct j6_audio_device *adev = out->dev;
+ struct timespec now;
const size_t frame_size = audio_stream_frame_size(&stream->common);
const size_t frames = bytes / frame_size;
uint32_t rate = out->config.rate;
uint32_t write_usecs = frames * 1000000 / rate;
+ uint32_t diff_usecs;
+ int ret = 0;
ALOGVV("out_write() stream=%p buffer=%p size=%u/%u time=%u usecs",
out, buffer, frames, rate, write_usecs);
@@ -423,18 +848,27 @@ static ssize_t out_write(struct audio_stream_out *stream, const void* buffer,
pthread_mutex_lock(&out->lock);
if (out->standby) {
- select_output_device(adev);
+ if (!adev->in_call) {
+ select_output_device(adev);
- ALOGI("out_write() open card %u port %u", adev->card, adev->out_port);
- out->pcm = pcm_open(adev->card, adev->out_port, PCM_OUT | PCM_MMAP, &out->config);
- if (!pcm_is_ready(out->pcm)) {
- ALOGE("out_write() failed to open pcm out: %s", pcm_get_error(out->pcm));
- pcm_close(out->pcm);
- out->pcm = NULL;
+ ALOGI("out_write() open card %u port %u", adev->card, adev->out_port);
+ out->pcm = pcm_open(adev->card, adev->out_port, PCM_OUT, &out->config);
+ if (!pcm_is_ready(out->pcm)) {
+ ALOGE("out_write() failed to open pcm out: %s", pcm_get_error(out->pcm));
+ pcm_close(out->pcm);
+ out->pcm = NULL;
+ ret = -ENODEV;
+ }
+ } else {
+ ALOGI("out_write() open dummy port");
+ clock_gettime(CLOCK_REALTIME, &out->last);
+ }
+
+ if (ret) {
usleep(write_usecs); /* limits the rate of error messages */
pthread_mutex_unlock(&out->lock);
pthread_mutex_unlock(&adev->lock);
- return -ENODEV;
+ return ret;
}
out->standby = false;
@@ -442,10 +876,19 @@ static ssize_t out_write(struct audio_stream_out *stream, const void* buffer,
pthread_mutex_unlock(&adev->lock);
- int ret = pcm_mmap_write(out->pcm, buffer, bytes);
- if (ret) {
- ALOGE("out_write() failed to write audio data %d", ret);
- usleep(write_usecs); /* limits the rate of error messages */
+ if (!adev->in_call) {
+ ret = pcm_write(out->pcm, buffer, bytes);
+ if (ret) {
+ ALOGE("out_write() failed to write audio data %d", ret);
+ usleep(write_usecs); /* limits the rate of error messages */
+ }
+ } else {
+ clock_gettime(CLOCK_REALTIME, &now);
+ diff_usecs = time_diff(now, out->last);
+ if (write_usecs > diff_usecs)
+ usleep(write_usecs - diff_usecs);
+
+ clock_gettime(CLOCK_REALTIME, &out->last);
}
out->written += frames;
@@ -481,18 +924,27 @@ static int out_get_presentation_position(const struct audio_stream_out *stream,
uint64_t *frames, struct timespec *timestamp)
{
struct j6_stream_out *out = (struct j6_stream_out *)(stream);
+ struct j6_audio_device *adev = out->dev;
+ int64_t signed_frames = -1;
size_t avail;
int ret = -1;
pthread_mutex_lock(&out->lock);
- if (pcm_get_htimestamp(out->pcm, &avail, timestamp) == 0) {
- int64_t signed_frames = out->written - pcm_get_buffer_size(out->pcm) + avail;
- /* It would be unusual for this value to be negative, but check just in case ... */
- if (signed_frames >= 0) {
- *frames = signed_frames;
- ret = 0;
+ if (!adev->in_call) {
+ if (pcm_get_htimestamp(out->pcm, &avail, timestamp) == 0) {
+ signed_frames = out->written - pcm_get_buffer_size(out->pcm) + avail;
}
+ } else {
+ clock_gettime(CLOCK_REALTIME, timestamp);
+ signed_frames = out->written +
+ (time_diff(*timestamp, out->last) * out->config.rate) / 1000000;
+ }
+
+ /* It would be unusual for this value to be negative, but check just in case ... */
+ if (signed_frames >= 0) {
+ *frames = signed_frames;
+ ret = 0;
}
pthread_mutex_unlock(&out->lock);
@@ -921,9 +1373,37 @@ static int adev_get_master_mute(struct audio_hw_device *dev, bool *muted)
static int adev_set_mode(struct audio_hw_device *dev, audio_mode_t mode)
{
+ struct j6_audio_device *adev = (struct j6_audio_device *)dev;
+ struct j6_stream_out *out = adev->out;
+ int ret = 0;
+
ALOGV("adev_set_mode() mode=0x%08x", mode);
- return 0;
+ pthread_mutex_lock(&adev->lock);
+ pthread_mutex_lock(&out->lock);
+
+ if (adev->mode == mode) {
+ ALOGV("adev_set_mode() already in mode=0x%08x", mode);
+ goto out;
+ }
+
+ if (mode == AUDIO_MODE_IN_CALL) {
+ ret = enter_voice_call(adev);
+ if (ret) {
+ ALOGE("adev_set_mode() failed to initialize voice call %d", ret);
+ goto out;
+ }
+ } else {
+ leave_voice_call(adev);
+ }
+
+ adev->mode = mode;
+
+out:
+ pthread_mutex_unlock(&out->lock);
+ pthread_mutex_unlock(&adev->lock);
+
+ return ret;
}
static int adev_set_mic_mute(struct audio_hw_device *dev, bool state)
@@ -1156,7 +1636,10 @@ static int adev_open(const hw_module_t* module, const char* name,
adev->card = find_supported_card();
adev->in_port = 0;
adev->out_port = 0;
+ adev->bt_port = 2;
adev->mic_mute = false;
+ adev->in_call = false;
+ adev->mode = AUDIO_MODE_NORMAL;
adev->route = audio_route_init(adev->card, NULL);
if (!adev->route) {
diff --git a/audio/mixer_paths.xml b/audio/mixer_paths.xml
index 06419be..09949fa 100644
--- a/audio/mixer_paths.xml
+++ b/audio/mixer_paths.xml
@@ -75,4 +75,11 @@
<ctl name="J3C Line Playback Switch" value="1" />
<ctl name="J3C PCM Playback Volume" value="127" />
+<!-- Bluetooth -->
+<ctl name="Bluetooth Mode" value="Slave" />
+
+<path name="BT SCO Master">
+ <ctl name="Bluetooth Mode" value="Master" />
+</path>
+
</mixer>