From: Andrei Emeltchenko <[email protected]>
Function adds audio_open_output_stream() and sets dummy callbacks.
---
android/hal-sco.c | 178 +++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 176 insertions(+), 2 deletions(-)
diff --git a/android/hal-sco.c b/android/hal-sco.c
index 8f8c3b6..06e39ed 100644
--- a/android/hal-sco.c
+++ b/android/hal-sco.c
@@ -26,21 +26,195 @@
#include "hal-log.h"
+#define AUDIO_STREAM_DEFAULT_RATE 44100
+#define AUDIO_STREAM_DEFAULT_FORMAT AUDIO_FORMAT_PCM_16_BIT
+
+#define OUT_BUFFER_SIZE 2560
+
+struct sco_audio_config {
+ uint32_t rate;
+ uint32_t channels;
+ audio_format_t format;
+};
+
+struct sco_stream_out {
+ struct audio_stream_out stream;
+ struct sco_audio_config cfg;
+};
+
struct sco_dev {
struct audio_hw_device dev;
+ struct sco_stream_out *out;
};
+/* Audio stream functions */
+
+static ssize_t out_write(struct audio_stream_out *stream, const void *buffer,
+ size_t bytes)
+{
+ /* write data */
+
+ return bytes;
+}
+
+static uint32_t out_get_sample_rate(const struct audio_stream *stream)
+{
+ struct sco_stream_out *out = (struct sco_stream_out *) stream;
+
+ DBG("rate %u", out->cfg.rate);
+
+ return out->cfg.rate;
+}
+
+static int out_set_sample_rate(struct audio_stream *stream, uint32_t rate)
+{
+ DBG("rate %u", rate);
+
+ return 0;
+}
+
+static size_t out_get_buffer_size(const struct audio_stream *stream)
+{
+ DBG("buf size %u", OUT_BUFFER_SIZE);
+
+ return OUT_BUFFER_SIZE;
+}
+
+static uint32_t out_get_channels(const struct audio_stream *stream)
+{
+ DBG("");
+
+ /* AudioFlinger can only provide stereo stream, so we return it here and
+ * later we'll downmix this to mono in case codec requires it
+ */
+ return AUDIO_CHANNEL_OUT_STEREO;
+}
+
+static audio_format_t out_get_format(const struct audio_stream *stream)
+{
+ struct sco_stream_out *out = (struct sco_stream_out *) stream;
+
+ DBG("");
+
+ return out->cfg.format;
+}
+
+static int out_set_format(struct audio_stream *stream, audio_format_t format)
+{
+ DBG("");
+
+ return -ENOSYS;
+}
+
+static int out_standby(struct audio_stream *stream)
+{
+ DBG("");
+
+ return 0;
+}
+
+static int out_dump(const struct audio_stream *stream, int fd)
+{
+ DBG("");
+
+ return -ENOSYS;
+}
+
+static int out_set_parameters(struct audio_stream *stream, const char *kvpairs)
+{
+ DBG("%s", kvpairs);
+
+ return 0;
+}
+
+static char *out_get_parameters(const struct audio_stream *stream,
+ const char *keys)
+{
+ DBG("");
+
+ return strdup("");
+}
+
+static uint32_t out_get_latency(const struct audio_stream_out *stream)
+{
+ DBG("");
+
+ return 0;
+}
+
+static int out_set_volume(struct audio_stream_out *stream, float left,
+ float right)
+{
+ DBG("");
+
+ return -ENOSYS;
+}
+
+static int out_get_render_position(const struct audio_stream_out *stream,
+ uint32_t *dsp_frames)
+{
+ DBG("");
+
+ return -ENOSYS;
+}
+
+static int out_add_audio_effect(const struct audio_stream *stream,
+ effect_handle_t effect)
+{
+ DBG("");
+
+ return -ENOSYS;
+}
+
+static int out_remove_audio_effect(const struct audio_stream *stream,
+ effect_handle_t effect)
+{
+ DBG("");
+
+ return -ENOSYS;
+}
+
static int sco_open_output_stream(struct audio_hw_device *dev,
audio_io_handle_t handle,
audio_devices_t devices,
audio_output_flags_t flags,
struct audio_config *config,
struct audio_stream_out **stream_out)
-
{
+ struct sco_dev *adev = (struct sco_dev *) dev;
+ struct sco_stream_out *out;
+
DBG("");
- return -EINVAL;
+ out = calloc(1, sizeof(struct sco_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->cfg.format = AUDIO_STREAM_DEFAULT_FORMAT;
+ out->cfg.channels = AUDIO_CHANNEL_OUT_MONO;
+ out->cfg.rate = AUDIO_STREAM_DEFAULT_RATE;
+
+ *stream_out = &out->stream;
+ adev->out = out;
+
+ return 0;
}
static void sco_close_output_stream(struct audio_hw_device *dev,
--
1.8.3.2
Hi Andrei,
On Mon, May 12, 2014 at 11:56 AM, Andrei Emeltchenko
<[email protected]> wrote:
> From: Andrei Emeltchenko <[email protected]>
>
> Function adds audio_open_output_stream() and sets dummy callbacks.
> ---
> android/hal-sco.c | 178 +++++++++++++++++++++++++++++++++++++++++++++++++++++-
> 1 file changed, 176 insertions(+), 2 deletions(-)
>
> diff --git a/android/hal-sco.c b/android/hal-sco.c
> index 8f8c3b6..06e39ed 100644
> --- a/android/hal-sco.c
> +++ b/android/hal-sco.c
> @@ -26,21 +26,195 @@
>
> #include "hal-log.h"
>
> +#define AUDIO_STREAM_DEFAULT_RATE 44100
> +#define AUDIO_STREAM_DEFAULT_FORMAT AUDIO_FORMAT_PCM_16_BIT
> +
> +#define OUT_BUFFER_SIZE 2560
> +
> +struct sco_audio_config {
> + uint32_t rate;
> + uint32_t channels;
> + audio_format_t format;
> +};
> +
> +struct sco_stream_out {
> + struct audio_stream_out stream;
> + struct sco_audio_config cfg;
> +};
> +
> struct sco_dev {
> struct audio_hw_device dev;
> + struct sco_stream_out *out;
> };
>
> +/* Audio stream functions */
> +
> +static ssize_t out_write(struct audio_stream_out *stream, const void *buffer,
> + size_t bytes)
> +{
> + /* write data */
> +
> + return bytes;
> +}
> +
> +static uint32_t out_get_sample_rate(const struct audio_stream *stream)
> +{
> + struct sco_stream_out *out = (struct sco_stream_out *) stream;
> +
> + DBG("rate %u", out->cfg.rate);
> +
> + return out->cfg.rate;
> +}
> +
> +static int out_set_sample_rate(struct audio_stream *stream, uint32_t rate)
> +{
> + DBG("rate %u", rate);
> +
> + return 0;
> +}
> +
> +static size_t out_get_buffer_size(const struct audio_stream *stream)
> +{
> + DBG("buf size %u", OUT_BUFFER_SIZE);
> +
> + return OUT_BUFFER_SIZE;
> +}
> +
> +static uint32_t out_get_channels(const struct audio_stream *stream)
> +{
> + DBG("");
> +
> + /* AudioFlinger can only provide stereo stream, so we return it here and
> + * later we'll downmix this to mono in case codec requires it
> + */
> + return AUDIO_CHANNEL_OUT_STEREO;
> +}
> +
> +static audio_format_t out_get_format(const struct audio_stream *stream)
> +{
> + struct sco_stream_out *out = (struct sco_stream_out *) stream;
> +
> + DBG("");
> +
> + return out->cfg.format;
> +}
> +
> +static int out_set_format(struct audio_stream *stream, audio_format_t format)
> +{
> + DBG("");
> +
> + return -ENOSYS;
> +}
> +
> +static int out_standby(struct audio_stream *stream)
> +{
> + DBG("");
> +
> + return 0;
> +}
> +
> +static int out_dump(const struct audio_stream *stream, int fd)
> +{
> + DBG("");
> +
> + return -ENOSYS;
> +}
> +
> +static int out_set_parameters(struct audio_stream *stream, const char *kvpairs)
> +{
> + DBG("%s", kvpairs);
> +
> + return 0;
> +}
> +
> +static char *out_get_parameters(const struct audio_stream *stream,
> + const char *keys)
> +{
> + DBG("");
> +
> + return strdup("");
> +}
> +
> +static uint32_t out_get_latency(const struct audio_stream_out *stream)
> +{
> + DBG("");
> +
> + return 0;
> +}
> +
> +static int out_set_volume(struct audio_stream_out *stream, float left,
> + float right)
> +{
> + DBG("");
> +
> + return -ENOSYS;
> +}
> +
> +static int out_get_render_position(const struct audio_stream_out *stream,
> + uint32_t *dsp_frames)
> +{
> + DBG("");
> +
> + return -ENOSYS;
> +}
> +
> +static int out_add_audio_effect(const struct audio_stream *stream,
> + effect_handle_t effect)
> +{
> + DBG("");
> +
> + return -ENOSYS;
> +}
> +
> +static int out_remove_audio_effect(const struct audio_stream *stream,
> + effect_handle_t effect)
> +{
> + DBG("");
> +
> + return -ENOSYS;
> +}
> +
> static int sco_open_output_stream(struct audio_hw_device *dev,
> audio_io_handle_t handle,
> audio_devices_t devices,
> audio_output_flags_t flags,
> struct audio_config *config,
> struct audio_stream_out **stream_out)
> -
> {
> + struct sco_dev *adev = (struct sco_dev *) dev;
> + struct sco_stream_out *out;
> +
> DBG("");
>
> - return -EINVAL;
> + out = calloc(1, sizeof(struct sco_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->cfg.format = AUDIO_STREAM_DEFAULT_FORMAT;
> + out->cfg.channels = AUDIO_CHANNEL_OUT_MONO;
> + out->cfg.rate = AUDIO_STREAM_DEFAULT_RATE;
> +
> + *stream_out = &out->stream;
> + adev->out = out;
> +
> + return 0;
> }
>
> static void sco_close_output_stream(struct audio_hw_device *dev,
> --
> 1.8.3.2
Pushed, note that I have changed quite a bit of some code including
fixing the resampler to not define variable in the middle of the code.
--
Luiz Augusto von Dentz
Hi Marcel,
On Mon, May 12, 2014 at 06:29:40PM -0700, Marcel Holtmann wrote:
> Hi Andrei,
>
> > The patch adds support for resampling audio in host and Android. There
> > are Android wrappers for SPEEXDSP library added to host.
> > ---
> > android/Android.mk | 2 +
> > android/Makefile.am | 6 +-
> > android/audio_utils/resampler.c | 265 ++++++++++++++++++++++++++++++++++++++++
> > android/audio_utils/resampler.h | 109 +++++++++++++++++
> > configure.ac | 7 ++
> > 5 files changed, 388 insertions(+), 1 deletion(-)
> > create mode 100644 android/audio_utils/resampler.c
> > create mode 100644 android/audio_utils/resampler.h
> >
> > diff --git a/android/Android.mk b/android/Android.mk
> > index 6d0169b..cc0f8f5 100644
> > --- a/android/Android.mk
> > +++ b/android/Android.mk
> > @@ -282,9 +282,11 @@ LOCAL_SRC_FILES := bluez/android/hal-sco.c
> > LOCAL_C_INCLUDES = \
> > $(call include-path-for, system-core) \
> > $(call include-path-for, libhardware) \
> > + $(call include-path-for, audio-utils) \
> >
> > LOCAL_SHARED_LIBRARIES := \
> > libcutils \
> > + libaudioutils \
> >
>
> where is this coming from? Why this directory? Why this name?
This is standard Android library and directory name. You can search it in
Android AOSP.
> Why not use compile it directly into the SCO audio module and use a name like android/audio_resampler.[ch].
This is standard Android library used also by other projects.
Best regards
Andrei Emeltchenko
Hi Andrei,
> The patch adds support for resampling audio in host and Android. There
> are Android wrappers for SPEEXDSP library added to host.
> ---
> android/Android.mk | 2 +
> android/Makefile.am | 6 +-
> android/audio_utils/resampler.c | 265 ++++++++++++++++++++++++++++++++++++++++
> android/audio_utils/resampler.h | 109 +++++++++++++++++
> configure.ac | 7 ++
> 5 files changed, 388 insertions(+), 1 deletion(-)
> create mode 100644 android/audio_utils/resampler.c
> create mode 100644 android/audio_utils/resampler.h
>
> diff --git a/android/Android.mk b/android/Android.mk
> index 6d0169b..cc0f8f5 100644
> --- a/android/Android.mk
> +++ b/android/Android.mk
> @@ -282,9 +282,11 @@ LOCAL_SRC_FILES := bluez/android/hal-sco.c
> LOCAL_C_INCLUDES = \
> $(call include-path-for, system-core) \
> $(call include-path-for, libhardware) \
> + $(call include-path-for, audio-utils) \
>
> LOCAL_SHARED_LIBRARIES := \
> libcutils \
> + libaudioutils \
>
where is this coming from? Why this directory? Why this name?
Why not use compile it directly into the SCO audio module and use a name like android/audio_resampler.[ch].
Regards
Marcel
From: Andrei Emeltchenko <[email protected]>
The patch adds support for resampling audio in host and Android. There
are Android wrappers for SPEEXDSP library added to host.
---
android/Android.mk | 2 +
android/Makefile.am | 6 +-
android/audio_utils/resampler.c | 265 ++++++++++++++++++++++++++++++++++++++++
android/audio_utils/resampler.h | 109 +++++++++++++++++
configure.ac | 7 ++
5 files changed, 388 insertions(+), 1 deletion(-)
create mode 100644 android/audio_utils/resampler.c
create mode 100644 android/audio_utils/resampler.h
diff --git a/android/Android.mk b/android/Android.mk
index 6d0169b..cc0f8f5 100644
--- a/android/Android.mk
+++ b/android/Android.mk
@@ -282,9 +282,11 @@ LOCAL_SRC_FILES := bluez/android/hal-sco.c
LOCAL_C_INCLUDES = \
$(call include-path-for, system-core) \
$(call include-path-for, libhardware) \
+ $(call include-path-for, audio-utils) \
LOCAL_SHARED_LIBRARIES := \
libcutils \
+ libaudioutils \
LOCAL_CFLAGS := $(BLUEZ_COMMON_CFLAGS)
diff --git a/android/Makefile.am b/android/Makefile.am
index 6570868..6587fc3 100644
--- a/android/Makefile.am
+++ b/android/Makefile.am
@@ -175,9 +175,13 @@ android_audio_sco_default_la_SOURCES = android/hal-log.h \
android/hardware/audio.h \
android/hardware/audio_effect.h \
android/hardware/hardware.h \
+ android/audio_utils/resampler.c \
+ android/audio_utils/resampler.h \
android/system/audio.h
-android_audio_sco_default_la_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/android
+android_audio_sco_default_la_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/android -Wno-declaration-after-statement
+
+android_audio_sco_default_la_LIBADD = @SPEEXDSP_LIBS@
android_audio_sco_default_la_LDFLAGS = $(AM_LDFLAGS) -module -avoid-version \
-no-undefined -lrt
diff --git a/android/audio_utils/resampler.c b/android/audio_utils/resampler.c
new file mode 100644
index 0000000..137e4d2
--- /dev/null
+++ b/android/audio_utils/resampler.c
@@ -0,0 +1,265 @@
+/*
+** Copyright 2011, 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.
+*/
+
+//#define LOG_NDEBUG 0
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <system/audio.h>
+#include <audio_utils/resampler.h>
+#include <speex/speex_resampler.h>
+
+#include "hal-log.h"
+
+struct resampler {
+ struct resampler_itfe itfe;
+ SpeexResamplerState *speex_resampler; // handle on speex resampler
+ struct resampler_buffer_provider *provider; // buffer provider installed by client
+ uint32_t in_sample_rate; // input sampling rate in Hz
+ uint32_t out_sample_rate; // output sampling rate in Hz
+ uint32_t channel_count; // number of channels (interleaved)
+ int16_t *in_buf; // input buffer
+ size_t in_buf_size; // input buffer size
+ size_t frames_in; // number of frames in input buffer
+ size_t frames_rq; // cached number of output frames
+ size_t frames_needed; // minimum number of input frames to produce
+ // frames_rq output frames
+ int32_t speex_delay_ns; // delay introduced by speex resampler in ns
+};
+
+
+//------------------------------------------------------------------------------
+// speex based resampler
+//------------------------------------------------------------------------------
+
+static void resampler_reset(struct resampler_itfe *resampler)
+{
+ struct resampler *rsmp = (struct resampler *)resampler;
+
+ rsmp->frames_in = 0;
+ rsmp->frames_rq = 0;
+
+ if (rsmp != NULL && rsmp->speex_resampler != NULL) {
+ speex_resampler_reset_mem(rsmp->speex_resampler);
+ }
+}
+
+static int32_t resampler_delay_ns(struct resampler_itfe *resampler)
+{
+ struct resampler *rsmp = (struct resampler *)resampler;
+
+ int32_t delay = (int32_t)((1000000000 * (int64_t)rsmp->frames_in) / rsmp->in_sample_rate);
+ delay += rsmp->speex_delay_ns;
+
+ return delay;
+}
+
+// outputs a number of frames less or equal to *outFrameCount and updates *outFrameCount
+// with the actual number of frames produced.
+static int resampler_resample_from_provider(struct resampler_itfe *resampler,
+ int16_t *out,
+ size_t *outFrameCount)
+{
+ struct resampler *rsmp = (struct resampler *)resampler;
+
+ if (rsmp == NULL || out == NULL || outFrameCount == NULL) {
+ return -EINVAL;
+ }
+ if (rsmp->provider == NULL) {
+ *outFrameCount = 0;
+ return -ENOSYS;
+ }
+
+ size_t framesRq = *outFrameCount;
+ // update and cache the number of frames needed at the input sampling rate to produce
+ // the number of frames requested at the output sampling rate
+ if (framesRq != rsmp->frames_rq) {
+ rsmp->frames_needed = (framesRq * rsmp->in_sample_rate) / rsmp->out_sample_rate + 1;
+ rsmp->frames_rq = framesRq;
+ }
+
+ size_t framesWr = 0;
+ size_t inFrames = 0;
+ while (framesWr < framesRq) {
+ if (rsmp->frames_in < rsmp->frames_needed) {
+ // make sure that the number of frames present in rsmp->in_buf (rsmp->frames_in) is at
+ // least the number of frames needed to produce the number of frames requested at
+ // the output sampling rate
+ if (rsmp->in_buf_size < rsmp->frames_needed) {
+ rsmp->in_buf_size = rsmp->frames_needed;
+ rsmp->in_buf = (int16_t *)realloc(rsmp->in_buf,
+ rsmp->in_buf_size * rsmp->channel_count * sizeof(int16_t));
+ }
+ struct resampler_buffer buf;
+ buf.frame_count = rsmp->frames_needed - rsmp->frames_in;
+ rsmp->provider->get_next_buffer(rsmp->provider, &buf);
+ if (buf.raw == NULL) {
+ break;
+ }
+ memcpy(rsmp->in_buf + rsmp->frames_in * rsmp->channel_count,
+ buf.raw,
+ buf.frame_count * rsmp->channel_count * sizeof(int16_t));
+ rsmp->frames_in += buf.frame_count;
+ rsmp->provider->release_buffer(rsmp->provider, &buf);
+ }
+
+ size_t outFrames = framesRq - framesWr;
+ inFrames = rsmp->frames_in;
+ if (rsmp->channel_count == 1) {
+ speex_resampler_process_int(rsmp->speex_resampler,
+ 0,
+ rsmp->in_buf,
+ (void *) &inFrames,
+ out + framesWr,
+ (void *) &outFrames);
+ } else {
+ speex_resampler_process_interleaved_int(rsmp->speex_resampler,
+ rsmp->in_buf,
+ (void *) &inFrames,
+ out + framesWr * rsmp->channel_count,
+ (void *) &outFrames);
+ }
+ framesWr += outFrames;
+ rsmp->frames_in -= inFrames;
+
+ if ((framesWr != framesRq) && (rsmp->frames_in != 0))
+ warn("ReSampler::resample() remaining %zd frames in and %zd out",
+ rsmp->frames_in, (framesRq - framesWr));
+ }
+ if (rsmp->frames_in) {
+ memmove(rsmp->in_buf,
+ rsmp->in_buf + inFrames * rsmp->channel_count,
+ rsmp->frames_in * rsmp->channel_count * sizeof(int16_t));
+ }
+ *outFrameCount = framesWr;
+
+ return 0;
+}
+
+static int resampler_resample_from_input(struct resampler_itfe *resampler,
+ int16_t *in,
+ size_t *inFrameCount,
+ int16_t *out,
+ size_t *outFrameCount)
+{
+ struct resampler *rsmp = (struct resampler *)resampler;
+
+ if (rsmp == NULL || in == NULL || inFrameCount == NULL ||
+ out == NULL || outFrameCount == NULL) {
+ return -EINVAL;
+ }
+ if (rsmp->provider != NULL) {
+ *outFrameCount = 0;
+ return -ENOSYS;
+ }
+
+ if (rsmp->channel_count == 1) {
+ speex_resampler_process_int(rsmp->speex_resampler,
+ 0,
+ in,
+ (void *) inFrameCount,
+ out,
+ (void *) outFrameCount);
+ } else {
+ speex_resampler_process_interleaved_int(rsmp->speex_resampler,
+ in,
+ (void *) inFrameCount,
+ out,
+ (void *) outFrameCount);
+ }
+
+ DBG("resampler_resample_from_input() DONE in %zd out %zd", *inFrameCount, *outFrameCount);
+
+ return 0;
+}
+
+int create_resampler(uint32_t inSampleRate,
+ uint32_t outSampleRate,
+ uint32_t channelCount,
+ uint32_t quality,
+ struct resampler_buffer_provider* provider,
+ struct resampler_itfe **resampler)
+{
+ int error;
+ struct resampler *rsmp;
+
+ DBG("create_resampler() In SR %d Out SR %d channels %d",
+ inSampleRate, outSampleRate, channelCount);
+
+ if (resampler == NULL) {
+ return -EINVAL;
+ }
+
+ *resampler = NULL;
+
+ if (quality <= RESAMPLER_QUALITY_MIN || quality >= RESAMPLER_QUALITY_MAX) {
+ return -EINVAL;
+ }
+
+ rsmp = (struct resampler *)calloc(1, sizeof(struct resampler));
+
+ rsmp->speex_resampler = speex_resampler_init(channelCount,
+ inSampleRate,
+ outSampleRate,
+ quality,
+ &error);
+ if (rsmp->speex_resampler == NULL) {
+ error("ReSampler: Cannot create speex resampler: %s", speex_resampler_strerror(error));
+ free(rsmp);
+ return -ENODEV;
+ }
+
+ rsmp->itfe.reset = resampler_reset;
+ rsmp->itfe.resample_from_provider = resampler_resample_from_provider;
+ rsmp->itfe.resample_from_input = resampler_resample_from_input;
+ rsmp->itfe.delay_ns = resampler_delay_ns;
+
+ rsmp->provider = provider;
+ rsmp->in_sample_rate = inSampleRate;
+ rsmp->out_sample_rate = outSampleRate;
+ rsmp->channel_count = channelCount;
+ rsmp->in_buf = NULL;
+ rsmp->in_buf_size = 0;
+
+ resampler_reset(&rsmp->itfe);
+
+ int frames = speex_resampler_get_input_latency(rsmp->speex_resampler);
+ rsmp->speex_delay_ns = (int32_t)((1000000000 * (int64_t)frames) / rsmp->in_sample_rate);
+ frames = speex_resampler_get_output_latency(rsmp->speex_resampler);
+ rsmp->speex_delay_ns += (int32_t)((1000000000 * (int64_t)frames) / rsmp->out_sample_rate);
+
+ *resampler = &rsmp->itfe;
+ DBG("create_resampler() DONE rsmp %p &rsmp->itfe %p speex %p",
+ rsmp, &rsmp->itfe, rsmp->speex_resampler);
+ return 0;
+}
+
+void release_resampler(struct resampler_itfe *resampler)
+{
+ struct resampler *rsmp = (struct resampler *)resampler;
+
+ if (rsmp == NULL) {
+ return;
+ }
+
+ free(rsmp->in_buf);
+
+ if (rsmp->speex_resampler != NULL) {
+ speex_resampler_destroy(rsmp->speex_resampler);
+ }
+ free(rsmp);
+}
diff --git a/android/audio_utils/resampler.h b/android/audio_utils/resampler.h
new file mode 100644
index 0000000..0c7046f
--- /dev/null
+++ b/android/audio_utils/resampler.h
@@ -0,0 +1,109 @@
+/*
+** Copyright 2008, 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_RESAMPLER_H
+#define ANDROID_RESAMPLER_H
+
+#include <stdint.h>
+#include <sys/time.h>
+
+__BEGIN_DECLS
+
+
+#define RESAMPLER_QUALITY_MAX 10
+#define RESAMPLER_QUALITY_MIN 0
+#define RESAMPLER_QUALITY_DEFAULT 4
+#define RESAMPLER_QUALITY_VOIP 3
+#define RESAMPLER_QUALITY_DESKTOP 5
+
+struct resampler_buffer {
+ union {
+ void* raw;
+ short* i16;
+ int8_t* i8;
+ };
+ size_t frame_count;
+};
+
+/* call back interface used by the resampler to get new data */
+struct resampler_buffer_provider
+{
+ /**
+ * get a new buffer of data:
+ * as input: buffer->frame_count is the number of frames requested
+ * as output: buffer->frame_count is the number of frames returned
+ * buffer->raw points to data returned
+ */
+ int (*get_next_buffer)(struct resampler_buffer_provider *provider,
+ struct resampler_buffer *buffer);
+ /**
+ * release a consumed buffer of data:
+ * as input: buffer->frame_count is the number of frames released
+ * buffer->raw points to data released
+ */
+ void (*release_buffer)(struct resampler_buffer_provider *provider,
+ struct resampler_buffer *buffer);
+};
+
+/* resampler interface */
+struct resampler_itfe {
+ /**
+ * reset resampler state
+ */
+ void (*reset)(struct resampler_itfe *resampler);
+ /**
+ * resample input from buffer provider and output at most *outFrameCount to out buffer.
+ * *outFrameCount is updated with the actual number of frames produced.
+ */
+ int (*resample_from_provider)(struct resampler_itfe *resampler,
+ int16_t *out,
+ size_t *outFrameCount);
+ /**
+ * resample at most *inFrameCount frames from in buffer and output at most
+ * *outFrameCount to out buffer. *inFrameCount and *outFrameCount are updated respectively
+ * with the number of frames remaining in input and written to output.
+ */
+ int (*resample_from_input)(struct resampler_itfe *resampler,
+ int16_t *in,
+ size_t *inFrameCount,
+ int16_t *out,
+ size_t *outFrameCount);
+ /**
+ * return the latency introduced by the resampler in ns.
+ */
+ int32_t (*delay_ns)(struct resampler_itfe *resampler);
+};
+
+/**
+ * create a resampler according to input parameters passed.
+ * If resampler_buffer_provider is not NULL only resample_from_provider() can be called.
+ * If resampler_buffer_provider is NULL only resample_from_input() can be called.
+ */
+int create_resampler(uint32_t inSampleRate,
+ uint32_t outSampleRate,
+ uint32_t channelCount,
+ uint32_t quality,
+ struct resampler_buffer_provider *provider,
+ struct resampler_itfe **);
+
+/**
+ * release resampler resources.
+ */
+void release_resampler(struct resampler_itfe *);
+
+__END_DECLS
+
+#endif // ANDROID_RESAMPLER_H
diff --git a/configure.ac b/configure.ac
index 54a387f..e1a2288 100644
--- a/configure.ac
+++ b/configure.ac
@@ -259,6 +259,13 @@ if (test "${enable_android}" = "yes"); then
AC_SUBST(SBC_LIBS)
fi
+if (test "${enable_android}" = "yes"); then
+ PKG_CHECK_MODULES(SPEEXDSP, speexdsp >= 1.2, dummy=yes,
+ AC_MSG_ERROR(SPEEXDSP library >= 1.2 is required))
+ AC_SUBST(SPEEXDSP_CFLAGS)
+ AC_SUBST(SPEEXDSP_LIBS)
+fi
+
AC_DEFINE_UNQUOTED(ANDROID_STORAGEDIR, "${storagedir}/android",
[Directory for the Android daemon storage files])
--
1.8.3.2
From: Andrei Emeltchenko <[email protected]>
Adds testing support for audio-sco HAL.
---
android/Android.mk | 1 +
android/Makefile.am | 1 +
android/client/haltest.c | 3 +
android/client/if-main.h | 1 +
android/client/if-sco.c | 520 +++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 526 insertions(+)
create mode 100644 android/client/if-sco.c
diff --git a/android/Android.mk b/android/Android.mk
index b7b284d..6d0169b 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-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 78015e6..6570868 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-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-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;
diff --git a/android/client/if-sco.c b/android/client/if-sco.c
new file mode 100644
index 0000000..96ddcc4
--- /dev/null
+++ b/android/client/if-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_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 = "audio-sco",
+ .methods = methods
+};
--
1.8.3.2
From: Andrei Emeltchenko <[email protected]>
Resample Android audio from 44100 to 8000.
---
android/hal-sco.c | 112 ++++++++++++++++++++++++++++++++++++++++++++++++++----
1 file changed, 105 insertions(+), 7 deletions(-)
diff --git a/android/hal-sco.c b/android/hal-sco.c
index f440287..b244333 100644
--- a/android/hal-sco.c
+++ b/android/hal-sco.c
@@ -27,6 +27,7 @@
#include <hardware/audio.h>
#include <hardware/hardware.h>
+#include <audio_utils/resampler.h>
#include "../src/shared/util.h"
#include "sco-msg.h"
@@ -34,6 +35,7 @@
#include "hal-log.h"
#define AUDIO_STREAM_DEFAULT_RATE 44100
+#define AUDIO_STREAM_SCO_RATE 8000
#define AUDIO_STREAM_DEFAULT_FORMAT AUDIO_FORMAT_PCM_16_BIT
#define OUT_BUFFER_SIZE 2560
@@ -60,6 +62,10 @@ struct sco_stream_out {
int fd;
uint8_t *downmix_buf;
+
+ struct resampler_itfe *resampler;
+ int16_t *resample_buf;
+ uint32_t resample_frame_num;
};
struct sco_dev {
@@ -67,6 +73,22 @@ struct sco_dev {
struct sco_stream_out *out;
};
+/*
+ * return the minimum frame numbers from resampling between BT stack's rate
+ * and audio flinger's. For output stream, 'output' shall be true, otherwise
+ * false for input streams at audio flinger side.
+ */
+static size_t get_resample_frame_num(uint32_t sco_rate, uint32_t rate,
+ size_t frame_num, bool output)
+{
+ size_t resample_frames_num = frame_num * sco_rate / rate + output;
+
+ DBG("resampler: sco_rate %d frame_num %zd rate %d resample frames %zd",
+ sco_rate, frame_num, rate, resample_frames_num);
+
+ return resample_frames_num;
+}
+
/* Audio IPC functions */
static int audio_ipc_cmd(uint8_t service_id, uint8_t opcode, uint16_t len,
@@ -258,6 +280,9 @@ static ssize_t out_write(struct audio_stream_out *stream, const void *buffer,
{
struct sco_stream_out *out = (struct sco_stream_out *) stream;
size_t frame_num = bytes / audio_stream_frame_size(&out->stream.common);
+ size_t output_frame_num = frame_num;
+ void *send_buf = out->downmix_buf;
+ size_t total;
DBG("write to fd %d bytes %zu", out->fd, bytes);
@@ -267,6 +292,34 @@ static ssize_t out_write(struct audio_stream_out *stream, const void *buffer,
}
downmix_to_mono(out, buffer, frame_num);
+
+ if (out->resampler) {
+ int ret;
+
+ /* limit resampler's output within what resample buf can hold */
+ output_frame_num = out->resample_frame_num;
+
+ ret = out->resampler->resample_from_input(out->resampler,
+ send_buf,
+ &frame_num,
+ out->resample_buf,
+ &output_frame_num);
+ if (ret) {
+ error("Failed to resample frames: %zd input %zd (%s)",
+ frame_num, output_frame_num, strerror(ret));
+ return -1;
+ }
+
+ send_buf = out->resample_buf;
+
+ DBG("Resampled: frame_num %zd, output_frame_num %zd",
+ frame_num, output_frame_num);
+ }
+
+ total = output_frame_num * sizeof(int16_t) * 1;
+
+ DBG("total %zd", total);
+
return bytes;
}
@@ -299,19 +352,18 @@ static size_t out_get_buffer_size(const struct audio_stream *stream)
static uint32_t out_get_channels(const struct audio_stream *stream)
{
- DBG("");
+ struct sco_stream_out *out = (struct sco_stream_out *) stream;
- /* AudioFlinger can only provide stereo stream, so we return it here and
- * later we'll downmix this to mono in case codec requires it
- */
- return AUDIO_CHANNEL_OUT_STEREO;
+ DBG("channels num: %u", popcount(out->cfg.channels));
+
+ return out->cfg.channels;
}
static audio_format_t out_get_format(const struct audio_stream *stream)
{
struct sco_stream_out *out = (struct sco_stream_out *) stream;
- DBG("");
+ DBG("format: %u", out->cfg.format);
return out->cfg.format;
}
@@ -401,6 +453,8 @@ static int sco_open_output_stream(struct audio_hw_device *dev,
struct sco_dev *adev = (struct sco_dev *) dev;
struct sco_stream_out *out;
int fd = -1;
+ int chan_num, ret;
+ size_t resample_size;
uint16_t mtu;
DBG("");
@@ -433,8 +487,9 @@ static int sco_open_output_stream(struct audio_hw_device *dev,
out->stream.write = out_write;
out->stream.get_render_position = out_get_render_position;
+ /* Configuration for Android */
out->cfg.format = AUDIO_STREAM_DEFAULT_FORMAT;
- out->cfg.channels = AUDIO_CHANNEL_OUT_MONO;
+ out->cfg.channels = AUDIO_CHANNEL_OUT_STEREO;
out->cfg.rate = AUDIO_STREAM_DEFAULT_RATE;
out->cfg.frame_num = OUT_STREAM_FRAMES;
out->cfg.mtu = mtu;
@@ -446,11 +501,54 @@ static int sco_open_output_stream(struct audio_hw_device *dev,
}
DBG("size %zd", out_get_buffer_size(&out->stream.common));
+
+ /* Channel numbers for resampler */
+ chan_num = 1;
+
+ ret = create_resampler(out->cfg.rate, AUDIO_STREAM_SCO_RATE, chan_num,
+ RESAMPLER_QUALITY_DEFAULT, NULL,
+ &out->resampler);
+ if (ret) {
+ error("Failed to create resampler (%s)", strerror(ret));
+ goto failed;
+ }
+
+ DBG("Created resampler: input rate [%d] output rate [%d] channels [%d]",
+ out->cfg.rate, AUDIO_STREAM_SCO_RATE, chan_num);
+
+ out->resample_frame_num = get_resample_frame_num(AUDIO_STREAM_SCO_RATE,
+ out->cfg.rate,
+ out->cfg.frame_num, 1);
+
+ if (!out->resample_frame_num) {
+ error("frame num is too small to resample, discard it");
+ goto failed;
+ }
+
+ resample_size = sizeof(int16_t) * chan_num * out->resample_frame_num;
+
+ out->resample_buf = malloc(resample_size);
+ if (!out->resample_buf) {
+ error("failed to allocate resample buffer for %u frames",
+ out->resample_frame_num);
+ goto failed;
+ }
+
+ DBG("resampler: frame num %u buf size %zd bytes",
+ out->resample_frame_num, resample_size);
+
*stream_out = &out->stream;
adev->out = out;
out->fd = fd;
return 0;
+failed:
+ free(out->downmix_buf);
+ free(out);
+ stream_out = NULL;
+ adev->out = NULL;
+
+ return ret;
}
static void sco_close_output_stream(struct audio_hw_device *dev,
--
1.8.3.2
From: Andrei Emeltchenko <[email protected]>
SCO API will be used when communicating with SCO Audio HAL.
---
android/sco-ipc-api.txt | 36 ++++++++++++++++++++++++++++++++++++
android/sco-msg.h | 36 ++++++++++++++++++++++++++++++++++++
2 files changed, 72 insertions(+)
create mode 100644 android/sco-ipc-api.txt
create mode 100644 android/sco-msg.h
diff --git a/android/sco-ipc-api.txt b/android/sco-ipc-api.txt
new file mode 100644
index 0000000..75b2cba
--- /dev/null
+++ b/android/sco-ipc-api.txt
@@ -0,0 +1,36 @@
+Bluetooth SCO Audio Plugin
+==========================
+
+The SCO Audio Plugin communicate through abstract socket name
+"\0bluez_sco_socket".
+
+ .---Audio---. .--Android--.
+ | Plugin | | Daemon |
+ | | Command | |
+ | | --------------------------> | |
+ | | | |
+ | | <-------------------------- | |
+ | | Response | |
+ | | | |
+ | | | |
+ | | | |
+ '-----------' '-----------'
+
+
+ Audio HAL Daemon
+ ----------------------------------------------------
+
+ call connect_sco() --> create SCO socket
+ return connect_sco() <-- return socket fd and mtu
+
+SCO Audio Service (ID 0)
+========================
+
+ Opcode 0x00 - Error response
+
+ Response parameters: Status (1 octet)
+
+ Opcode 0x01 - Connect Audio SCO command
+
+ Command parameters: <none>
+ Response parameters: MTU (2 octets)
diff --git a/android/sco-msg.h b/android/sco-msg.h
new file mode 100644
index 0000000..cd2f894
--- /dev/null
+++ b/android/sco-msg.h
@@ -0,0 +1,36 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2014 Intel Corporation. All rights reserved.
+ *
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+static const char BLUEZ_SCO_SK_PATH[] = "\0bluez_sco_socket";
+
+#define AUDIO_SERVICE_SCO_ID 1
+
+#define AUDIO_STATUS_SUCCESS IPC_STATUS_SUCCESS
+#define AUDIO_STATUS_FAILED 0x01
+
+#define AUDIO_OP_STATUS IPC_OP_STATUS
+
+#define AUDIO_OP_CONNECT_SCO 0x01
+struct audio_rsp_connect_sco {
+ uint16_t mtu;
+} __attribute__((packed));
--
1.8.3.2
From: Andrei Emeltchenko <[email protected]>
---
android/client/if-audio.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/android/client/if-audio.c b/android/client/if-audio.c
index ede8533..6a48025 100644
--- a/android/client/if-audio.c
+++ b/android/client/if-audio.c
@@ -22,7 +22,7 @@
#include <math.h>
audio_hw_device_t *if_audio = NULL;
-struct audio_stream_out *stream_out = NULL;
+static struct audio_stream_out *stream_out = NULL;
static size_t buffer_size = 0;
static pthread_t play_thread = 0;
--
1.8.3.2
From: Andrei Emeltchenko <[email protected]>
---
android/hal-sco.c | 319 +++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 318 insertions(+), 1 deletion(-)
diff --git a/android/hal-sco.c b/android/hal-sco.c
index 06e39ed..c154c43 100644
--- a/android/hal-sco.c
+++ b/android/hal-sco.c
@@ -16,14 +16,20 @@
*/
#include <errno.h>
+#include <pthread.h>
+#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/socket.h>
+#include <sys/un.h>
#include <unistd.h>
#include <hardware/audio.h>
#include <hardware/hardware.h>
+#include "sco-msg.h"
+#include "ipc-common.h"
#include "hal-log.h"
#define AUDIO_STREAM_DEFAULT_RATE 44100
@@ -31,15 +37,24 @@
#define OUT_BUFFER_SIZE 2560
+static int listen_sk = -1;
+static int audio_sk = -1;
+
+static pthread_t ipc_th = 0;
+static pthread_mutex_t sk_mutex = PTHREAD_MUTEX_INITIALIZER;
+
struct sco_audio_config {
uint32_t rate;
uint32_t channels;
+ uint16_t mtu;
audio_format_t format;
};
struct sco_stream_out {
struct audio_stream_out stream;
+
struct sco_audio_config cfg;
+ int fd;
};
struct sco_dev {
@@ -47,13 +62,186 @@ struct sco_dev {
struct sco_stream_out *out;
};
+/* Audio IPC functions */
+
+static int audio_ipc_cmd(uint8_t service_id, uint8_t opcode, uint16_t len,
+ void *param, size_t *rsp_len, void *rsp, int *fd)
+{
+ ssize_t ret;
+ struct msghdr msg;
+ struct iovec iv[2];
+ struct ipc_hdr cmd;
+ char cmsgbuf[CMSG_SPACE(sizeof(int))];
+ struct ipc_status s;
+ size_t s_len = sizeof(s);
+
+ pthread_mutex_lock(&sk_mutex);
+
+ if (audio_sk < 0) {
+ error("audio: Invalid cmd socket passed to audio_ipc_cmd");
+ goto failed;
+ }
+
+ if (!rsp || !rsp_len) {
+ memset(&s, 0, s_len);
+ rsp_len = &s_len;
+ rsp = &s;
+ }
+
+ memset(&msg, 0, sizeof(msg));
+ memset(&cmd, 0, sizeof(cmd));
+
+ cmd.service_id = service_id;
+ cmd.opcode = opcode;
+ cmd.len = len;
+
+ iv[0].iov_base = &cmd;
+ iv[0].iov_len = sizeof(cmd);
+
+ iv[1].iov_base = param;
+ iv[1].iov_len = len;
+
+ msg.msg_iov = iv;
+ msg.msg_iovlen = 2;
+
+ ret = sendmsg(audio_sk, &msg, 0);
+ if (ret < 0) {
+ error("audio: Sending command failed:%s", strerror(errno));
+ goto failed;
+ }
+
+ /* socket was shutdown */
+ if (ret == 0) {
+ error("audio: Command socket closed");
+ goto failed;
+ }
+
+ memset(&msg, 0, sizeof(msg));
+ memset(&cmd, 0, sizeof(cmd));
+
+ iv[0].iov_base = &cmd;
+ iv[0].iov_len = sizeof(cmd);
+
+ iv[1].iov_base = rsp;
+ iv[1].iov_len = *rsp_len;
+
+ msg.msg_iov = iv;
+ msg.msg_iovlen = 2;
+
+ if (fd) {
+ memset(cmsgbuf, 0, sizeof(cmsgbuf));
+ msg.msg_control = cmsgbuf;
+ msg.msg_controllen = sizeof(cmsgbuf);
+ }
+
+ ret = recvmsg(audio_sk, &msg, 0);
+ if (ret < 0) {
+ error("audio: Receiving command response failed:%s",
+ strerror(errno));
+ goto failed;
+ }
+
+ if (ret < (ssize_t) sizeof(cmd)) {
+ error("audio: Too small response received(%zd bytes)", ret);
+ goto failed;
+ }
+
+ if (cmd.service_id != service_id) {
+ error("audio: Invalid service id (%u vs %u)", cmd.service_id,
+ service_id);
+ goto failed;
+ }
+
+ if (ret != (ssize_t) (sizeof(cmd) + cmd.len)) {
+ error("audio: Malformed response received(%zd bytes)", ret);
+ goto failed;
+ }
+
+ if (cmd.opcode != opcode && cmd.opcode != AUDIO_OP_STATUS) {
+ error("audio: Invalid opcode received (%u vs %u)",
+ cmd.opcode, opcode);
+ goto failed;
+ }
+
+ if (cmd.opcode == AUDIO_OP_STATUS) {
+ struct ipc_status *s = rsp;
+
+ if (sizeof(*s) != cmd.len) {
+ error("audio: Invalid status length");
+ goto failed;
+ }
+
+ if (s->code == AUDIO_STATUS_SUCCESS) {
+ error("audio: Invalid success status response");
+ goto failed;
+ }
+
+ pthread_mutex_unlock(&sk_mutex);
+
+ return s->code;
+ }
+
+ pthread_mutex_unlock(&sk_mutex);
+
+ /* Receive auxiliary data in msg */
+ if (fd) {
+ struct cmsghdr *cmsg;
+
+ *fd = -1;
+
+ for (cmsg = CMSG_FIRSTHDR(&msg); cmsg;
+ cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+ if (cmsg->cmsg_level == SOL_SOCKET
+ && cmsg->cmsg_type == SCM_RIGHTS) {
+ memcpy(fd, CMSG_DATA(cmsg), sizeof(int));
+ break;
+ }
+ }
+
+ if (*fd < 0)
+ goto failed;
+ }
+
+ if (rsp_len)
+ *rsp_len = cmd.len;
+
+ return AUDIO_STATUS_SUCCESS;
+
+failed:
+ /* Some serious issue happen on IPC - recover */
+ shutdown(audio_sk, SHUT_RDWR);
+ pthread_mutex_unlock(&sk_mutex);
+
+ return AUDIO_STATUS_FAILED;
+}
+
+static int ipc_connect_sco(int *fd, uint16_t *mtu)
+{
+ struct audio_rsp_connect_sco rsp;
+ size_t rsp_len = sizeof(rsp);
+ int ret;
+
+ DBG("");
+
+ ret = audio_ipc_cmd(AUDIO_SERVICE_SCO_ID, AUDIO_OP_CONNECT_SCO, 0,
+ NULL, &rsp_len, &rsp, fd);
+
+ *mtu = rsp.mtu;
+
+ return ret;
+}
+
/* Audio stream functions */
static ssize_t out_write(struct audio_stream_out *stream, const void *buffer,
size_t bytes)
{
+ struct sco_stream_out *out = (struct sco_stream_out *) stream;
+
/* write data */
+ DBG("write to fd %d bytes %zu", out->fd, bytes);
+
return bytes;
}
@@ -183,9 +371,18 @@ static int sco_open_output_stream(struct audio_hw_device *dev,
{
struct sco_dev *adev = (struct sco_dev *) dev;
struct sco_stream_out *out;
+ int fd = -1;
+ uint16_t mtu;
DBG("");
+ if (ipc_connect_sco(&fd, &mtu) != AUDIO_STATUS_SUCCESS) {
+ error("audio: cannot get fd");
+ return -EIO;
+ }
+
+ DBG("got sco fd %d mtu %u", fd, mtu);
+
out = calloc(1, sizeof(struct sco_stream_out));
if (!out)
return -ENOMEM;
@@ -210,9 +407,11 @@ static int sco_open_output_stream(struct audio_hw_device *dev,
out->cfg.format = AUDIO_STREAM_DEFAULT_FORMAT;
out->cfg.channels = AUDIO_CHANNEL_OUT_MONO;
out->cfg.rate = AUDIO_STREAM_DEFAULT_RATE;
+ out->cfg.mtu = mtu;
*stream_out = &out->stream;
adev->out = out;
+ out->fd = fd;
return 0;
}
@@ -220,9 +419,17 @@ static int sco_open_output_stream(struct audio_hw_device *dev,
static void sco_close_output_stream(struct audio_hw_device *dev,
struct audio_stream_out *stream_out)
{
- DBG("");
+ struct sco_dev *sco_dev = (struct sco_dev *) dev;
+
+ DBG("dev %p stream %p fd %d", dev, stream_out, sco_dev->out->fd);
+
+ if (sco_dev->out && sco_dev->out->fd) {
+ close(sco_dev->out->fd);
+ sco_dev->out->fd = -1;
+ }
free(stream_out);
+ sco_dev->out = NULL;
}
static int sco_set_parameters(struct audio_hw_device *dev,
@@ -326,10 +533,116 @@ static int sco_close(hw_device_t *device)
return 0;
}
+static void *ipc_handler(void *data)
+{
+ bool done = false;
+ struct pollfd pfd;
+ int sk;
+
+ DBG("");
+
+ while (!done) {
+ DBG("Waiting for connection ...");
+
+ sk = accept(listen_sk, NULL, NULL);
+ if (sk < 0) {
+ int err = errno;
+
+ if (err == EINTR)
+ continue;
+
+ if (err != ECONNABORTED && err != EINVAL)
+ error("audio: Failed to accept socket: %d (%s)",
+ err, strerror(err));
+
+ break;
+ }
+
+ pthread_mutex_lock(&sk_mutex);
+ audio_sk = sk;
+ pthread_mutex_unlock(&sk_mutex);
+
+ DBG("Audio IPC: Connected");
+
+ memset(&pfd, 0, sizeof(pfd));
+ pfd.fd = audio_sk;
+ pfd.events = POLLHUP | POLLERR | POLLNVAL;
+
+ /* Check if socket is still alive. Empty while loop.*/
+ while (poll(&pfd, 1, -1) < 0 && errno == EINTR);
+
+ if (pfd.revents & (POLLHUP | POLLERR | POLLNVAL)) {
+ info("Audio HAL: Socket closed");
+
+ pthread_mutex_lock(&sk_mutex);
+ close(audio_sk);
+ audio_sk = -1;
+ pthread_mutex_unlock(&sk_mutex);
+ }
+ }
+
+ info("Closing Audio IPC thread");
+ return NULL;
+}
+
+static int sco_ipc_init(void)
+{
+ struct sockaddr_un addr;
+ int err;
+ int sk;
+
+ DBG("");
+
+ sk = socket(PF_LOCAL, SOCK_SEQPACKET, 0);
+ if (sk < 0) {
+ err = -errno;
+ error("audio: Failed to create socket: %d (%s)", -err,
+ strerror(-err));
+ return err;
+ }
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+
+ memcpy(addr.sun_path, BLUEZ_SCO_SK_PATH, sizeof(BLUEZ_SCO_SK_PATH));
+
+ if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+ err = -errno;
+ error("audio: Failed to bind socket: %d (%s)", -err,
+ strerror(-err));
+ goto failed;
+ }
+
+ if (listen(sk, 1) < 0) {
+ err = -errno;
+ error("audio: Failed to listen on the socket: %d (%s)", -err,
+ strerror(-err));
+ goto failed;
+ }
+
+ listen_sk = sk;
+
+ err = pthread_create(&ipc_th, NULL, ipc_handler, NULL);
+ if (err) {
+ err = -err;
+ ipc_th = 0;
+ error("audio: Failed to start Audio IPC thread: %d (%s)",
+ -err, strerror(-err));
+ goto failed;
+ }
+
+ return 0;
+
+failed:
+ close(sk);
+ return err;
+}
+
static int sco_open(const hw_module_t *module, const char *name,
hw_device_t **device)
{
struct sco_dev *dev;
+ int err;
DBG("");
@@ -339,6 +652,10 @@ static int sco_open(const hw_module_t *module, const char *name,
return -EINVAL;
}
+ err = sco_ipc_init();
+ if (err < 0)
+ return err;
+
dev = calloc(1, sizeof(struct sco_dev));
if (!dev)
return -ENOMEM;
--
1.8.3.2
From: Andrei Emeltchenko <[email protected]>
For synchronization interleave read() and write().
---
android/hal-sco.c | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 74 insertions(+)
diff --git a/android/hal-sco.c b/android/hal-sco.c
index b244333..97f0808 100644
--- a/android/hal-sco.c
+++ b/android/hal-sco.c
@@ -41,6 +41,8 @@
#define OUT_BUFFER_SIZE 2560
#define OUT_STREAM_FRAMES 2560
+#define SOCKET_POLL_TIMEOUT_MS 500
+
static int listen_sk = -1;
static int audio_sk = -1;
@@ -275,6 +277,75 @@ static void downmix_to_mono(struct sco_stream_out *out, const uint8_t *buffer,
}
}
+static bool write_data(struct sco_stream_out *out, const uint8_t *buffer,
+ size_t bytes)
+{
+ struct pollfd pfd;
+ size_t len, written = 0;
+ int ret;
+ uint16_t mtu = /* out->cfg.mtu */ 48;
+ uint8_t read_buf[mtu];
+ bool do_write = false;
+
+ pfd.fd = out->fd;
+ pfd.events = POLLOUT | POLLIN | POLLHUP | POLLNVAL;
+
+ while (bytes > written) {
+
+ /* poll for sending */
+ if (poll(&pfd, 1, SOCKET_POLL_TIMEOUT_MS) == 0) {
+ DBG("timeout fd %d", out->fd);
+ return false;
+ }
+
+ if (pfd.revents & (POLLHUP | POLLNVAL)) {
+ error("error fd %d, events 0x%x", out->fd, pfd.revents);
+ return false;
+ }
+
+ /* FIXME synchronize by time instead of read() */
+ if (pfd.revents & POLLIN) {
+ ret = read(out->fd, read_buf, mtu);
+ if (ret < 0) {
+ error("Error reading fd %d (%s)", out->fd,
+ strerror(errno));
+ return false;
+ }
+
+ do_write = true;
+ }
+
+ if (!do_write)
+ continue;
+
+ len = bytes - written > mtu ? mtu : bytes - written;
+
+ ret = write(out->fd, buffer + written, len);
+ if (ret > 0) {
+ written += ret;
+ do_write = false;
+ continue;
+ }
+
+ if (errno == EAGAIN) {
+ ret = errno;
+ warn("write failed (%d)", ret);
+ continue;
+ }
+
+ if (errno != EINTR) {
+ ret = errno;
+ error("write failed (%d) fd %d bytes %zd", ret, out->fd,
+ bytes);
+ return false;
+ }
+ }
+
+ DBG("written %zd bytes", bytes);
+
+ return true;
+}
+
static ssize_t out_write(struct audio_stream_out *stream, const void *buffer,
size_t bytes)
{
@@ -320,6 +391,9 @@ static ssize_t out_write(struct audio_stream_out *stream, const void *buffer,
DBG("total %zd", total);
+ if (!write_data(out, send_buf, total))
+ return -1;
+
return bytes;
}
--
1.8.3.2
From: Andrei Emeltchenko <[email protected]>
---
android/hal-sco.c | 49 ++++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 44 insertions(+), 5 deletions(-)
diff --git a/android/hal-sco.c b/android/hal-sco.c
index c154c43..f440287 100644
--- a/android/hal-sco.c
+++ b/android/hal-sco.c
@@ -28,6 +28,7 @@
#include <hardware/audio.h>
#include <hardware/hardware.h>
+#include "../src/shared/util.h"
#include "sco-msg.h"
#include "ipc-common.h"
#include "hal-log.h"
@@ -36,6 +37,7 @@
#define AUDIO_STREAM_DEFAULT_FORMAT AUDIO_FORMAT_PCM_16_BIT
#define OUT_BUFFER_SIZE 2560
+#define OUT_STREAM_FRAMES 2560
static int listen_sk = -1;
static int audio_sk = -1;
@@ -46,6 +48,7 @@ static pthread_mutex_t sk_mutex = PTHREAD_MUTEX_INITIALIZER;
struct sco_audio_config {
uint32_t rate;
uint32_t channels;
+ uint32_t frame_num;
uint16_t mtu;
audio_format_t format;
};
@@ -55,6 +58,8 @@ struct sco_stream_out {
struct sco_audio_config cfg;
int fd;
+
+ uint8_t *downmix_buf;
};
struct sco_dev {
@@ -233,15 +238,35 @@ static int ipc_connect_sco(int *fd, uint16_t *mtu)
/* Audio stream functions */
+static void downmix_to_mono(struct sco_stream_out *out, const uint8_t *buffer,
+ size_t frame_num)
+{
+ const int16_t *input = (const void *) buffer;
+ int16_t *output = (void *) out->downmix_buf;
+ size_t i;
+
+ for (i = 0; i < frame_num; i++) {
+ int16_t l = le16_to_cpu(get_unaligned(&input[i * 2]));
+ int16_t r = le16_to_cpu(get_unaligned(&input[i * 2 + 1]));
+
+ put_unaligned(cpu_to_le16((l + r) >> 1), &output[i]);
+ }
+}
+
static ssize_t out_write(struct audio_stream_out *stream, const void *buffer,
size_t bytes)
{
struct sco_stream_out *out = (struct sco_stream_out *) stream;
-
- /* write data */
+ size_t frame_num = bytes / audio_stream_frame_size(&out->stream.common);
DBG("write to fd %d bytes %zu", out->fd, bytes);
+ if (!out->downmix_buf) {
+ error("audio: downmix buffer not initialized");
+ return -1;
+ }
+
+ downmix_to_mono(out, buffer, frame_num);
return bytes;
}
@@ -263,9 +288,13 @@ static int out_set_sample_rate(struct audio_stream *stream, uint32_t rate)
static size_t out_get_buffer_size(const struct audio_stream *stream)
{
- DBG("buf size %u", OUT_BUFFER_SIZE);
+ struct sco_stream_out *out = (struct sco_stream_out *) stream;
+ size_t size = audio_stream_frame_size(&out->stream.common) *
+ out->cfg.frame_num;
+
+ DBG("buf size %zd", size);
- return OUT_BUFFER_SIZE;
+ return size;
}
static uint32_t out_get_channels(const struct audio_stream *stream)
@@ -407,8 +436,16 @@ static int sco_open_output_stream(struct audio_hw_device *dev,
out->cfg.format = AUDIO_STREAM_DEFAULT_FORMAT;
out->cfg.channels = AUDIO_CHANNEL_OUT_MONO;
out->cfg.rate = AUDIO_STREAM_DEFAULT_RATE;
+ out->cfg.frame_num = OUT_STREAM_FRAMES;
out->cfg.mtu = mtu;
+ out->downmix_buf = malloc(out_get_buffer_size(&out->stream.common));
+ if (!out->downmix_buf) {
+ free(out);
+ return -ENOMEM;
+ }
+
+ DBG("size %zd", out_get_buffer_size(&out->stream.common));
*stream_out = &out->stream;
adev->out = out;
out->fd = fd;
@@ -420,6 +457,7 @@ static void sco_close_output_stream(struct audio_hw_device *dev,
struct audio_stream_out *stream_out)
{
struct sco_dev *sco_dev = (struct sco_dev *) dev;
+ struct sco_stream_out *out = (struct sco_stream_out *) stream_out;
DBG("dev %p stream %p fd %d", dev, stream_out, sco_dev->out->fd);
@@ -428,7 +466,8 @@ static void sco_close_output_stream(struct audio_hw_device *dev,
sco_dev->out->fd = -1;
}
- free(stream_out);
+ free(out->downmix_buf);
+ free(out);
sco_dev->out = NULL;
}
--
1.8.3.2
From: Andrei Emeltchenko <[email protected]>
If SCO Audio IPC gets connected it provides only one command:
connect_sco().
---
android/handsfree.c | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 69 insertions(+)
diff --git a/android/handsfree.c b/android/handsfree.c
index 8a27840..befaa6f 100644
--- a/android/handsfree.c
+++ b/android/handsfree.c
@@ -45,6 +45,7 @@
#include "bluetooth.h"
#include "src/log.h"
#include "utils.h"
+#include "sco-msg.h"
#define HSP_AG_CHANNEL 12
#define HFP_AG_CHANNEL 13
@@ -156,7 +157,9 @@ static struct {
static uint32_t hfp_ag_features = 0;
static bdaddr_t adapter_addr;
+
static struct ipc *hal_ipc = NULL;
+static struct ipc *sco_ipc = NULL;
static uint32_t hfp_record_id = 0;
static GIOChannel *hfp_server = NULL;
@@ -822,6 +825,8 @@ static gboolean sco_watch_cb(GIOChannel *chan, GIOCondition cond,
g_io_channel_unref(device.sco);
device.sco = NULL;
+ DBG("");
+
device.sco_watch = 0;
device_set_audio_state(HAL_EV_HANDSFREE_AUDIO_STATE_DISCONNECTED);
@@ -856,6 +861,30 @@ done:
hfp_gw_send_info(device.gw, "+BCS: %u", type);
}
+static void send_sco_fd(GIOChannel *chan)
+{
+ if (sco_ipc) {
+ int fd = g_io_channel_unix_get_fd(chan);
+ GError *err = NULL;
+ uint16_t mtu = 48;
+ struct audio_rsp_connect_sco rsp;
+
+ if (!bt_io_get(chan, &err, BT_IO_OPT_MTU, &mtu,
+ BT_IO_OPT_INVALID)) {
+ error("Unable to get MTU: %s\n", err->message);
+ g_clear_error(&err);
+ }
+
+ DBG("fd %d mtu %u", fd, mtu);
+
+ rsp.mtu = mtu;
+
+ ipc_send_rsp_full(sco_ipc, AUDIO_SERVICE_SCO_ID,
+ AUDIO_OP_CONNECT_SCO, sizeof(rsp), &rsp,
+ fd);
+ }
+}
+
static void connect_sco_cb(GIOChannel *chan, GError *err, gpointer user_data)
{
DBG("");
@@ -887,6 +916,7 @@ static void connect_sco_cb(GIOChannel *chan, GError *err, gpointer user_data)
sco_watch_cb, NULL);
device_set_audio_state(HAL_EV_HANDSFREE_AUDIO_STATE_CONNECTED);
+ send_sco_fd(chan);
}
static bool connect_sco(void)
@@ -2564,6 +2594,41 @@ static void disable_sco_server(void)
}
}
+static void bt_audio_connect_sco(const void *buf, uint16_t len)
+{
+ DBG("");
+
+ connect_audio();
+}
+
+static const struct ipc_handler audio_handlers[] = {
+ /* AUDIO_OP_CONNECT_SCO */
+ { bt_audio_connect_sco, false, 0 }
+};
+
+static void bt_sco_unregister(void)
+{
+ DBG("");
+
+ ipc_cleanup(sco_ipc);
+ sco_ipc = NULL;
+}
+
+static bool bt_sco_register(ipc_disconnect_cb disconnect)
+{
+ DBG("");
+
+ sco_ipc = ipc_init(BLUEZ_SCO_SK_PATH, sizeof(BLUEZ_SCO_SK_PATH),
+ AUDIO_SERVICE_SCO_ID, false, disconnect, NULL);
+ if (!sco_ipc)
+ return false;
+
+ ipc_register(sco_ipc, AUDIO_SERVICE_SCO_ID, audio_handlers,
+ G_N_ELEMENTS(audio_handlers));
+
+ return true;
+}
+
bool bt_handsfree_register(struct ipc *ipc, const bdaddr_t *addr, uint8_t mode)
{
DBG("mode 0x%x", mode);
@@ -2598,6 +2663,9 @@ done:
hal_ipc = ipc;
ipc_register(hal_ipc, HAL_SERVICE_ID_HANDSFREE, cmd_handlers,
G_N_ELEMENTS(cmd_handlers));
+
+ bt_sco_register(NULL);
+
return true;
}
@@ -2605,6 +2673,7 @@ void bt_handsfree_unregister(void)
{
DBG("");
+ bt_sco_unregister();
ipc_unregister(hal_ipc, HAL_SERVICE_ID_HANDSFREE);
hal_ipc = NULL;
--
1.8.3.2
From: Andrei Emeltchenko <[email protected]>
This helps to identify problem since DBG is not printed by default.
---
android/ipc.c | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/android/ipc.c b/android/ipc.c
index 8cd34ea..be963b5 100644
--- a/android/ipc.c
+++ b/android/ipc.c
@@ -96,31 +96,31 @@ static int ipc_handle_msg(struct service_handler *handlers, size_t max_index,
const struct ipc_handler *handler;
if (len < (ssize_t) sizeof(*msg)) {
- DBG("message too small (%zd bytes)", len);
+ error("IPC: message too small (%zd bytes)", len);
return -EBADMSG;
}
if (len != (ssize_t) (sizeof(*msg) + msg->len)) {
- DBG("message malformed (%zd bytes)", len);
+ error("IPC: message malformed (%zd bytes)", len);
return -EBADMSG;
}
/* if service is valid */
if (msg->service_id > max_index) {
- DBG("unknown service (0x%x)", msg->service_id);
+ error("IPC: unknown service (0x%x)", msg->service_id);
return -EOPNOTSUPP;
}
/* if service is registered */
if (!handlers[msg->service_id].handler) {
- DBG("service not registered (0x%x)", msg->service_id);
+ error("IPC: service not registered (0x%x)", msg->service_id);
return -EOPNOTSUPP;
}
/* if opcode is valid */
if (msg->opcode == IPC_OP_STATUS ||
msg->opcode > handlers[msg->service_id].size) {
- DBG("invalid opcode 0x%x for service 0x%x", msg->opcode,
+ error("IPC: invalid opcode 0x%x for service 0x%x", msg->opcode,
msg->service_id);
return -EOPNOTSUPP;
}
@@ -131,7 +131,7 @@ static int ipc_handle_msg(struct service_handler *handlers, size_t max_index,
/* if payload size is valid */
if ((handler->var_len && handler->data_len > msg->len) ||
(!handler->var_len && handler->data_len != msg->len)) {
- DBG("invalid size for opcode 0x%x service 0x%x",
+ error("IPC: invalid size for opcode 0x%x service 0x%x",
msg->opcode, msg->service_id);
return -EMSGSIZE;
}
--
1.8.3.2