diff options
author | Misael Lopez Cruz <misael.lopez@ti.com> | 2014-06-03 13:08:09 -0500 |
---|---|---|
committer | Gerrit Code Review <gerrit2@git.omapzoom.org> | 2014-06-03 18:17:24 -0500 |
commit | 8bbadc3eca1667ff59e7981cae9573fbd51c62a4 (patch) | |
tree | 34e847edc424c6cacfb5c9947af83493d189dc2c | |
parent | 3da8567243ec8a5d46581f9e5642a1a105471a6c (diff) | |
download | jacinto6evm-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.c | 531 | ||||
-rw-r--r-- | audio/mixer_paths.xml | 7 |
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> |