From: Andrei Emeltchenko <andrei.emeltchenko@xxxxxxxxx> Adds testing support for audio-sco HAL. --- android/Android.mk | 1 + android/Makefile.am | 1 + android/client/haltest.c | 3 + android/client/if-audio-sco.c | 520 ++++++++++++++++++++++++++++++++++++++++++ android/client/if-main.h | 1 + 5 files changed, 526 insertions(+) create mode 100644 android/client/if-audio-sco.c diff --git a/android/Android.mk b/android/Android.mk index 942c7ba..c453f91 100644 --- a/android/Android.mk +++ b/android/Android.mk @@ -149,6 +149,7 @@ LOCAL_SRC_FILES := \ bluez/android/client/history.c \ bluez/android/client/tabcompletion.c \ bluez/android/client/if-audio.c \ + bluez/android/client/if-audio-sco.c \ bluez/android/client/if-av.c \ bluez/android/client/if-rc.c \ bluez/android/client/if-bt.c \ diff --git a/android/Makefile.am b/android/Makefile.am index 990ce6b..5113d71 100644 --- a/android/Makefile.am +++ b/android/Makefile.am @@ -109,6 +109,7 @@ android_haltest_SOURCES = android/client/haltest.c \ android/client/if-hl.c \ android/client/if-sock.c \ android/client/if-audio.c \ + android/client/if-audio-sco.c \ android/hardware/hardware.c \ android/hal-utils.h android/hal-utils.c diff --git a/android/client/haltest.c b/android/client/haltest.c index 5d05b75..0871dd7 100644 --- a/android/client/haltest.c +++ b/android/client/haltest.c @@ -32,6 +32,7 @@ const struct interface *interfaces[] = { &audio_if, + &sco_if, &bluetooth_if, &av_if, &rc_if, @@ -394,10 +395,12 @@ static void init(void) const struct method *m; const char *argv[4]; char init_audio[] = "audio init"; + char init_audio_sco[] = "audio-sco init"; char init_bt[] = "bluetooth init"; uint32_t i; process_line(init_audio); + process_line(init_audio_sco); process_line(init_bt); m = get_interface_method("bluetooth", "get_profile_interface"); diff --git a/android/client/if-audio-sco.c b/android/client/if-audio-sco.c new file mode 100644 index 0000000..24e07cd --- /dev/null +++ b/android/client/if-audio-sco.c @@ -0,0 +1,520 @@ +/* + * Copyright (C) 2014 Intel Corporation + * + * 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 "if-main.h" +#include "../hal-utils.h" +#include "pthread.h" +#include "unistd.h" +#include <math.h> + +audio_hw_device_t *if_audio_sco = NULL; +static struct audio_stream_out *stream_out = NULL; + +static size_t buffer_size = 0; +static pthread_t play_thread = 0; +static pthread_mutex_t outstream_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t state_mutex = PTHREAD_MUTEX_INITIALIZER; + +enum state { + STATE_STOPPED, + STATE_STOPPING, + STATE_PLAYING, + STATE_SUSPENDED, + STATE_MAX +}; + +SINTMAP(audio_channel_mask_t, -1, "(AUDIO_CHANNEL_INVALID)") + DELEMENT(AUDIO_CHANNEL_OUT_FRONT_LEFT), + DELEMENT(AUDIO_CHANNEL_OUT_FRONT_RIGHT), + DELEMENT(AUDIO_CHANNEL_OUT_FRONT_CENTER), + DELEMENT(AUDIO_CHANNEL_OUT_LOW_FREQUENCY), + DELEMENT(AUDIO_CHANNEL_OUT_BACK_LEFT), + DELEMENT(AUDIO_CHANNEL_OUT_BACK_RIGHT), + DELEMENT(AUDIO_CHANNEL_OUT_FRONT_LEFT_OF_CENTER), + DELEMENT(AUDIO_CHANNEL_OUT_FRONT_RIGHT_OF_CENTER), + DELEMENT(AUDIO_CHANNEL_OUT_BACK_CENTER), + DELEMENT(AUDIO_CHANNEL_OUT_SIDE_LEFT), + DELEMENT(AUDIO_CHANNEL_OUT_SIDE_RIGHT), + DELEMENT(AUDIO_CHANNEL_OUT_TOP_CENTER), + DELEMENT(AUDIO_CHANNEL_OUT_TOP_FRONT_LEFT), + DELEMENT(AUDIO_CHANNEL_OUT_TOP_FRONT_CENTER), + DELEMENT(AUDIO_CHANNEL_OUT_TOP_FRONT_RIGHT), + DELEMENT(AUDIO_CHANNEL_OUT_TOP_BACK_LEFT), + DELEMENT(AUDIO_CHANNEL_OUT_TOP_BACK_CENTER), + DELEMENT(AUDIO_CHANNEL_OUT_TOP_BACK_RIGHT), + DELEMENT(AUDIO_CHANNEL_OUT_MONO), + DELEMENT(AUDIO_CHANNEL_OUT_STEREO), + DELEMENT(AUDIO_CHANNEL_OUT_QUAD), + DELEMENT(AUDIO_CHANNEL_OUT_SURROUND), + DELEMENT(AUDIO_CHANNEL_OUT_5POINT1), + DELEMENT(AUDIO_CHANNEL_OUT_7POINT1), + DELEMENT(AUDIO_CHANNEL_OUT_ALL), + DELEMENT(AUDIO_CHANNEL_OUT_FRONT_LEFT), + DELEMENT(AUDIO_CHANNEL_OUT_FRONT_LEFT), + DELEMENT(AUDIO_CHANNEL_OUT_FRONT_LEFT), + DELEMENT(AUDIO_CHANNEL_OUT_FRONT_LEFT), + DELEMENT(AUDIO_CHANNEL_OUT_FRONT_LEFT), +ENDMAP + +SINTMAP(audio_format_t, -1, "(AUDIO_FORMAT_INVALID)") + DELEMENT(AUDIO_FORMAT_DEFAULT), + DELEMENT(AUDIO_FORMAT_PCM), + DELEMENT(AUDIO_FORMAT_MP3), + DELEMENT(AUDIO_FORMAT_AMR_NB), + DELEMENT(AUDIO_FORMAT_AMR_WB), + DELEMENT(AUDIO_FORMAT_AAC), + DELEMENT(AUDIO_FORMAT_HE_AAC_V1), + DELEMENT(AUDIO_FORMAT_HE_AAC_V2), + DELEMENT(AUDIO_FORMAT_VORBIS), + DELEMENT(AUDIO_FORMAT_MAIN_MASK), + DELEMENT(AUDIO_FORMAT_SUB_MASK), + DELEMENT(AUDIO_FORMAT_PCM_16_BIT), + DELEMENT(AUDIO_FORMAT_PCM_8_BIT), + DELEMENT(AUDIO_FORMAT_PCM_32_BIT), + DELEMENT(AUDIO_FORMAT_PCM_8_24_BIT), +ENDMAP + +static int current_state = STATE_STOPPED; + +#define SAMPLERATE 44100 +static short sample[SAMPLERATE]; +static uint16_t sample_pos; + +static void init_p(int argc, const char **argv) +{ + int err; + const hw_module_t *module; + audio_hw_device_t *device; + + err = hw_get_module_by_class(AUDIO_HARDWARE_MODULE_ID, "hsp", &module); + if (err) { + haltest_error("hw_get_module_by_class returned %d\n", err); + return; + } + + err = audio_hw_device_open(module, &device); + if (err) { + haltest_error("audio_hw_device_open returned %d\n", err); + return; + } + + if_audio_sco = device; +} + +static int feed_from_file(short *buffer, void *data) +{ + FILE *in = data; + return fread(buffer, buffer_size, 1, in); +} + +static int feed_from_generator(short *buffer, void *data) +{ + size_t i = 0; + float volume = 0.5; + float *freq = data; + float f = 1; + + if (freq) + f = *freq; + + /* buffer_size is in bytes but we are using buffer of shorts (2 bytes)*/ + for (i = 0; i < buffer_size / sizeof(*buffer) - 1;) { + if (sample_pos >= SAMPLERATE) + sample_pos = sample_pos % SAMPLERATE; + + /* Use the same sample for both channels */ + buffer[i++] = sample[sample_pos] * volume; + buffer[i++] = sample[sample_pos] * volume; + + sample_pos += f; + } + + return buffer_size; +} + +static void prepare_sample(void) +{ + int x; + double s; + + haltest_info("Preparing audio sample...\n"); + + for (x = 0; x < SAMPLERATE; x++) { + /* prepare sinusoidal 1Hz sample */ + s = (2.0 * 3.14159) * ((double)x / SAMPLERATE); + s = sin(s); + + /* remap <-1, 1> to signed 16bit PCM range */ + sample[x] = s * 32767; + } + + sample_pos = 0; +} + +static void *playback_thread(void *data) +{ + int (*filbuff_cb) (short*, void*); + short buffer[buffer_size / sizeof(short)]; + size_t len = 0; + ssize_t w_len = 0; + FILE *in = data; + void *cb_data = NULL; + float freq = 440.0; + + /* Use file or fall back to generator */ + if (in) { + filbuff_cb = feed_from_file; + cb_data = in; + } else { + prepare_sample(); + filbuff_cb = feed_from_generator; + cb_data = &freq; + } + + pthread_mutex_lock(&state_mutex); + current_state = STATE_PLAYING; + pthread_mutex_unlock(&state_mutex); + + do { + pthread_mutex_lock(&state_mutex); + + if (current_state == STATE_STOPPING) { + haltest_info("Detected stopping\n"); + pthread_mutex_unlock(&state_mutex); + break; + } else if (current_state == STATE_SUSPENDED) { + pthread_mutex_unlock(&state_mutex); + usleep(500); + continue; + } + + pthread_mutex_unlock(&state_mutex); + + len = filbuff_cb(buffer, cb_data); + + pthread_mutex_lock(&outstream_mutex); + if (!stream_out) { + pthread_mutex_unlock(&outstream_mutex); + break; + } + + w_len = stream_out->write(stream_out, buffer, buffer_size); + pthread_mutex_unlock(&outstream_mutex); + } while (len && w_len > 0); + + if (in) + fclose(in); + + pthread_mutex_lock(&state_mutex); + current_state = STATE_STOPPED; + pthread_mutex_unlock(&state_mutex); + + haltest_info("Done playing.\n"); + + return NULL; +} + +static void play_p(int argc, const char **argv) +{ + const char *fname = NULL; + FILE *in = NULL; + + RETURN_IF_NULL(if_audio_sco); + RETURN_IF_NULL(stream_out); + + if (argc < 3) { + haltest_error("Invalid audio file path.\n"); + haltest_info("Using sound generator.\n"); + } else { + fname = argv[2]; + in = fopen(fname, "r"); + + if (in == NULL) { + haltest_error("Cannot open file: %s\n", fname); + return; + } + haltest_info("Playing file: %s\n", fname); + } + + if (buffer_size == 0) { + haltest_error("Invalid buffer size. Was stream_out opened?\n"); + goto fail; + } + + pthread_mutex_lock(&state_mutex); + if (current_state != STATE_STOPPED) { + haltest_error("Already playing or stream suspended!\n"); + pthread_mutex_unlock(&state_mutex); + goto fail; + } + pthread_mutex_unlock(&state_mutex); + + if (pthread_create(&play_thread, NULL, playback_thread, in) != 0) { + haltest_error("Cannot create playback thread!\n"); + goto fail; + } + + return; +fail: + if (in) + fclose(in); +} + +static void stop_p(int argc, const char **argv) +{ + pthread_mutex_lock(&state_mutex); + if (current_state == STATE_STOPPED || current_state == STATE_STOPPING) { + pthread_mutex_unlock(&state_mutex); + return; + } + + current_state = STATE_STOPPING; + pthread_mutex_unlock(&state_mutex); + + pthread_mutex_lock(&outstream_mutex); + stream_out->common.standby(&stream_out->common); + pthread_mutex_unlock(&outstream_mutex); + + haltest_info("Ended %s\n", __func__); +} + +static void open_output_stream_p(int argc, const char **argv) +{ + int err; + + RETURN_IF_NULL(if_audio_sco); + + pthread_mutex_lock(&state_mutex); + if (current_state == STATE_PLAYING) { + haltest_error("Already playing!\n"); + pthread_mutex_unlock(&state_mutex); + return; + } + pthread_mutex_unlock(&state_mutex); + + err = if_audio_sco->open_output_stream(if_audio_sco, + 0, + AUDIO_DEVICE_OUT_ALL_A2DP, + AUDIO_OUTPUT_FLAG_NONE, + NULL, + &stream_out); + if (err < 0) { + haltest_error("open output stream returned %d\n", err); + return; + } + + buffer_size = stream_out->common.get_buffer_size(&stream_out->common); + if (buffer_size == 0) + haltest_error("Invalid buffer size received!\n"); + else + haltest_info("Using buffer size: %zu\n", buffer_size); +} + +static void close_output_stream_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_audio_sco); + RETURN_IF_NULL(stream_out); + + stop_p(argc, argv); + + haltest_info("Waiting for playback thread...\n"); + pthread_join(play_thread, NULL); + + if_audio_sco->close_output_stream(if_audio_sco, stream_out); + + stream_out = NULL; + buffer_size = 0; +} + +static void cleanup_p(int argc, const char **argv) +{ + int err; + + RETURN_IF_NULL(if_audio_sco); + + pthread_mutex_lock(&state_mutex); + if (current_state != STATE_STOPPED) { + pthread_mutex_unlock(&state_mutex); + close_output_stream_p(0, NULL); + } else { + pthread_mutex_unlock(&state_mutex); + } + + err = audio_hw_device_close(if_audio_sco); + if (err < 0) { + haltest_error("audio_hw_device_close returned %d\n", err); + return; + } + + if_audio_sco = NULL; +} + +static void suspend_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_audio_sco); + RETURN_IF_NULL(stream_out); + + pthread_mutex_lock(&state_mutex); + if (current_state != STATE_PLAYING) { + pthread_mutex_unlock(&state_mutex); + return; + } + current_state = STATE_SUSPENDED; + pthread_mutex_unlock(&state_mutex); + + pthread_mutex_lock(&outstream_mutex); + stream_out->common.standby(&stream_out->common); + pthread_mutex_unlock(&outstream_mutex); +} + +static void resume_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_audio_sco); + RETURN_IF_NULL(stream_out); + + pthread_mutex_lock(&state_mutex); + if (current_state == STATE_SUSPENDED) + current_state = STATE_PLAYING; + pthread_mutex_unlock(&state_mutex); +} + +static void get_latency_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_audio_sco); + RETURN_IF_NULL(stream_out); + + haltest_info("Output audio stream latency: %d\n", + stream_out->get_latency(stream_out)); +} + +static void get_buffer_size_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_audio_sco); + RETURN_IF_NULL(stream_out); + + haltest_info("Current output buffer size: %zu\n", + stream_out->common.get_buffer_size(&stream_out->common)); +} + +static void get_channels_p(int argc, const char **argv) +{ + audio_channel_mask_t channels; + + RETURN_IF_NULL(if_audio_sco); + RETURN_IF_NULL(stream_out); + + channels = stream_out->common.get_channels(&stream_out->common); + + haltest_info("Channels: %s\n", audio_channel_mask_t2str(channels)); +} + +static void get_format_p(int argc, const char **argv) +{ + audio_format_t format; + + RETURN_IF_NULL(if_audio_sco); + RETURN_IF_NULL(stream_out); + + format = stream_out->common.get_format(&stream_out->common); + + haltest_info("Format: %s\n", audio_format_t2str(format)); +} + +static void get_sample_rate_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_audio_sco); + RETURN_IF_NULL(stream_out); + + haltest_info("Current sample rate: %d\n", + stream_out->common.get_sample_rate(&stream_out->common)); +} + +static void get_parameters_p(int argc, const char **argv) +{ + const char *keystr; + + RETURN_IF_NULL(if_audio_sco); + RETURN_IF_NULL(stream_out); + + if (argc < 3) { + haltest_info("No keys given.\n"); + keystr = ""; + } else { + keystr = argv[2]; + } + + haltest_info("Current parameters: %s\n", + stream_out->common.get_parameters(&stream_out->common, + keystr)); +} + +static void set_parameters_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_audio_sco); + RETURN_IF_NULL(stream_out); + + if (argc < 3) { + haltest_error("No key=value; pairs given.\n"); + return; + } + + stream_out->common.set_parameters(&stream_out->common, argv[2]); +} + +static void set_sample_rate_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_audio_sco); + RETURN_IF_NULL(stream_out); + + if (argc < 3) + return; + + stream_out->common.set_sample_rate(&stream_out->common, atoi(argv[2])); +} + +static void init_check_p(int argc, const char **argv) +{ + RETURN_IF_NULL(if_audio_sco); + + haltest_info("Init check result: %d\n", if_audio_sco->init_check(if_audio_sco)); +} + +static struct method methods[] = { + STD_METHOD(init), + STD_METHOD(cleanup), + STD_METHOD(open_output_stream), + STD_METHOD(close_output_stream), + STD_METHODH(play, "<path to pcm file>"), + STD_METHOD(stop), + STD_METHOD(suspend), + STD_METHOD(resume), + STD_METHOD(get_latency), + STD_METHOD(get_buffer_size), + STD_METHOD(get_channels), + STD_METHOD(get_format), + STD_METHOD(get_sample_rate), + STD_METHODH(get_parameters, "<closing>"), + STD_METHODH(set_parameters, "<closing=value>"), + STD_METHODH(set_sample_rate, "<sample rate>"), + STD_METHOD(init_check), + END_METHOD +}; + +const struct interface sco_if = { + .name = "audio-sco", + .methods = methods +}; diff --git a/android/client/if-main.h b/android/client/if-main.h index ff6006c..88da0c7 100644 --- a/android/client/if-main.h +++ b/android/client/if-main.h @@ -68,6 +68,7 @@ struct interface { }; extern const struct interface audio_if; +extern const struct interface sco_if; extern const struct interface bluetooth_if; extern const struct interface av_if; extern const struct interface rc_if; -- 1.8.3.2 -- To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html