diff --git a/android/Android.mk b/android/Android.mk
index 2d7f0d5..ddf1bd2 100644
--- a/android/Android.mk
+++ b/android/Android.mk
bluez/android/client/history.c \
bluez/android/client/tabcompletion.c \
bluez/android/client/if-audio.c \
+ bluez/android/client/if-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 f0f4817..e40891d 100644
--- a/android/Makefile.am
+++ b/android/Makefile.am
android/client/if-hl.c \
android/client/if-sock.c \
android/client/if-audio.c \
+ android/client/if-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..d86fe49 100644
--- a/android/client/haltest.c
+++ b/android/client/haltest.c
const struct interface *interfaces[] = {
&audio_if,
+ &sco_if,
&bluetooth_if,
&av_if,
&rc_if,
const struct method *m;
const char *argv[4];
char init_audio[] = "audio init";
+ char init_sco[] = "sco init";
char init_bt[] = "bluetooth init";
uint32_t i;
process_line(init_audio);
+ process_line(init_sco);
process_line(init_bt);
m = get_interface_method("bluetooth", "get_profile_interface");
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
};
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;
diff --git a/android/client/if-sco.c b/android/client/if-sco.c
new file mode 100644
index 0000000..b7f5a80
--- /dev/null
+++ b/android/client/if-sco.c
+/*
+ * 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, "sco", &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_SCO,
+ 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 = "sco",
+ .methods = methods
+};