From: Andrei Emeltchenko <[email protected]>
This adds audio HAL for handling SCO. Following needs to be added to
audio_policy.conf:
sco {
outputs {
sco {
...
devices AUDIO_DEVICE_OUT_ALL_SCO
...
}
}
---
android/Android.mk | 23 ++++++
android/Makefile.am | 15 ++++
android/hal-sco.c | 211 ++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 249 insertions(+)
create mode 100644 android/hal-sco.c
diff --git a/android/Android.mk b/android/Android.mk
index 638a042..942c7ba 100644
--- a/android/Android.mk
+++ b/android/Android.mk
@@ -271,6 +271,29 @@ LOCAL_MODULE := audio.a2dp.default
include $(BUILD_SHARED_LIBRARY)
#
+# SCO audio
+#
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := bluez/android/hal-sco.c
+
+LOCAL_C_INCLUDES = \
+ $(call include-path-for, system-core) \
+ $(call include-path-for, libhardware) \
+
+LOCAL_SHARED_LIBRARIES := \
+ libcutils \
+
+LOCAL_CFLAGS := $(BLUEZ_COMMON_CFLAGS)
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE := audio.hsp.default
+
+include $(BUILD_SHARED_LIBRARY)
+
+#
# l2cap-test
#
diff --git a/android/Makefile.am b/android/Makefile.am
index 5ab6411..990ce6b 100644
--- a/android/Makefile.am
+++ b/android/Makefile.am
@@ -169,6 +169,21 @@ android_audio_a2dp_default_la_SOURCES = android/audio-msg.h \
android/hardware/hardware.h \
android/system/audio.h
+android_audio_sco_default_la_SOURCES = android/audio-msg.h \
+ android/hal-msg.h \
+ android/hal-sco.c \
+ android/hardware/audio.h \
+ android/hardware/audio_effect.h \
+ android/hardware/hardware.h \
+ android/system/audio.h
+
+android_audio_sco_default_la_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/android
+
+android_audio_sco_default_la_LDFLAGS = $(AM_LDFLAGS) -module -avoid-version \
+ -no-undefined -lrt
+
+plugin_LTLIBRARIES += android/audio.sco.default.la
+
unit_tests += android/test-ipc
android_test_ipc_SOURCES = android/test-ipc.c \
diff --git a/android/hal-sco.c b/android/hal-sco.c
new file mode 100644
index 0000000..5a70aba
--- /dev/null
+++ b/android/hal-sco.c
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2013 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 <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <hardware/audio.h>
+#include <hardware/hardware.h>
+
+#include "hal-log.h"
+
+struct sco_audio_dev {
+ struct audio_hw_device dev;
+ struct a2dp_stream_out *out;
+};
+
+static int audio_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)
+
+{
+ DBG("");
+
+ return -EINVAL;
+}
+
+static void audio_close_output_stream(struct audio_hw_device *dev,
+ struct audio_stream_out *stream_out)
+{
+ DBG("");
+
+ free(stream_out);
+}
+
+static int audio_set_parameters(struct audio_hw_device *dev,
+ const char *kvpairs)
+{
+ DBG("%s", kvpairs);
+
+ return 0;
+}
+
+static char *audio_get_parameters(const struct audio_hw_device *dev,
+ const char *keys)
+{
+ DBG("");
+
+ return strdup("");
+}
+
+static int audio_init_check(const struct audio_hw_device *dev)
+{
+ DBG("");
+
+ return 0;
+}
+
+static int audio_set_voice_volume(struct audio_hw_device *dev, float volume)
+{
+ DBG("%f", volume);
+
+ return 0;
+}
+
+static int audio_set_master_volume(struct audio_hw_device *dev, float volume)
+{
+ DBG("%f", volume);
+
+ return 0;
+}
+
+static int audio_set_mode(struct audio_hw_device *dev, int mode)
+{
+ DBG("");
+
+ return -ENOSYS;
+}
+
+static int audio_set_mic_mute(struct audio_hw_device *dev, bool state)
+{
+ DBG("");
+
+ return -ENOSYS;
+}
+
+static int audio_get_mic_mute(const struct audio_hw_device *dev, bool *state)
+{
+ DBG("");
+
+ return -ENOSYS;
+}
+
+static size_t audio_get_input_buffer_size(const struct audio_hw_device *dev,
+ const struct audio_config *config)
+{
+ DBG("");
+
+ return -ENOSYS;
+}
+
+static int audio_open_input_stream(struct audio_hw_device *dev,
+ audio_io_handle_t handle,
+ audio_devices_t devices,
+ struct audio_config *config,
+ struct audio_stream_in **stream_in)
+{
+ DBG("");
+
+ return 0;
+}
+
+static void audio_close_input_stream(struct audio_hw_device *dev,
+ struct audio_stream_in *stream_in)
+{
+ DBG("");
+
+ free(stream_in);
+}
+
+static int audio_dump(const audio_hw_device_t *device, int fd)
+{
+ DBG("");
+
+ return 0;
+}
+
+static int audio_close(hw_device_t *device)
+{
+ DBG("");
+
+ free(device);
+
+ return 0;
+}
+
+static int audio_open(const hw_module_t *module, const char *name,
+ hw_device_t **device)
+{
+ struct sco_audio_dev *adev;
+
+ DBG("");
+
+ if (strcmp(name, AUDIO_HARDWARE_INTERFACE)) {
+ error("audio: interface %s not matching [%s]", name,
+ AUDIO_HARDWARE_INTERFACE);
+ return -EINVAL;
+ }
+
+ adev = calloc(1, sizeof(struct sco_audio_dev));
+ if (!adev)
+ return -ENOMEM;
+
+ adev->dev.common.tag = HARDWARE_DEVICE_TAG;
+ adev->dev.common.version = AUDIO_DEVICE_API_VERSION_CURRENT;
+ adev->dev.common.module = (struct hw_module_t *) module;
+ adev->dev.common.close = audio_close;
+
+ adev->dev.init_check = audio_init_check;
+ adev->dev.set_voice_volume = audio_set_voice_volume;
+ adev->dev.set_master_volume = audio_set_master_volume;
+ adev->dev.set_mode = audio_set_mode;
+ adev->dev.set_mic_mute = audio_set_mic_mute;
+ adev->dev.get_mic_mute = audio_get_mic_mute;
+ adev->dev.set_parameters = audio_set_parameters;
+ adev->dev.get_parameters = audio_get_parameters;
+ adev->dev.get_input_buffer_size = audio_get_input_buffer_size;
+ adev->dev.open_output_stream = audio_open_output_stream;
+ adev->dev.close_output_stream = audio_close_output_stream;
+ adev->dev.open_input_stream = audio_open_input_stream;
+ adev->dev.close_input_stream = audio_close_input_stream;
+ adev->dev.dump = audio_dump;
+
+ *device = &adev->dev.common;
+
+ return 0;
+}
+
+static struct hw_module_methods_t hal_module_methods = {
+ .open = audio_open,
+};
+
+struct audio_module HAL_MODULE_INFO_SYM = {
+ .common = {
+ .tag = HARDWARE_MODULE_TAG,
+ .version_major = 1,
+ .version_minor = 0,
+ .id = AUDIO_HARDWARE_MODULE_ID,
+ .name = "HSP Audio HW HAL",
+ .author = "Intel Corporation",
+ .methods = &hal_module_methods,
+ },
+};
--
1.8.3.2
Hi Andrei,
On Fri, May 9, 2014 at 11:14 AM, Andrei Emeltchenko
<[email protected]> wrote:
> Hi Luiz,
>
> On Thu, May 08, 2014 at 04:51:48PM +0300, Luiz Augusto von Dentz wrote:
>> Hi Andrei,
>>
>> On Thu, May 8, 2014 at 3:25 PM, Andrei Emeltchenko
>> <[email protected]> wrote:
>> > From: Andrei Emeltchenko <[email protected]>
>> >
>> > For synchronization interleave read() and write().
>> > ---
>> > android/hal-sco.c | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
>> > 1 file changed, 73 insertions(+)
>> >
>> > diff --git a/android/hal-sco.c b/android/hal-sco.c
>> > index 7b6608b..5ec56ce 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,74 @@ 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;
>> > + }
>> > +
>> > + 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;
>>
>> You probably gonna have store the read data in a buffer to be read by
>> upper layer after remixed to 44.1 KHz, to safe space you can probably
>> leave the remixing to be done on the read callback. Im fine to do this
>> in a separate patch but we should at least add a comment about it.
>
> I think we need to have special thread which does read-write since Android
> might call read() without write().
PulseAudio synchronize by time if audio in not open in both direction,
this means we would have to sleep as we do in A2DP.
--
Luiz Augusto von Dentz
Hi Andrei,
On Friday 09 of May 2014 11:07:05 Andrei Emeltchenko wrote:
> Hi Szymon,
>
> On Fri, May 09, 2014 at 10:02:11AM +0200, Szymon Janc wrote:
> > Hi,
> >
> > On Friday 09 of May 2014 10:37:27 Andrei Emeltchenko wrote:
> > > Hi Luiz,
> > >
> > > On Thu, May 08, 2014 at 04:22:35PM +0300, Luiz Augusto von Dentz wrote:
> > > > > static bool connect_sco(void)
> > > > > @@ -904,7 +930,7 @@ static bool connect_sco(void)
> > > > > device.negotiated_codec != CODEC_ID_CVSD)
> > > > > voice_settings = BT_VOICE_TRANSPARENT;
> > > > > else
> > > > > - voice_settings = BT_VOICE_CVSD_16BIT;
> > > > > + voice_settings = 0;
> > > >
> > > > What is the reason for changing this value to 0?
> > >
> > > Otherwise we always call setsockopt which fails in old kernels.
> > >
> > > in sco_set():
> > > if (!voice)
> > > return TRUE;
> > >
> > > bt_voice.setting = voice;
> > > if (setsockopt(sock, SOL_BLUETOOTH, BT_VOICE, &bt_voice,
> > > sizeof(bt_voice)) < 0) {
> > > ERROR_FAILED(err, "setsockopt(BT_VOICE)", errno);
> > > return FALSE;
> > > }
> >
> > Mgmt 1.3 is already required by Android bluetoothd (kernel 3.9+) and most
> > likely this dependency will be bumped to 1.4 or 1.5. So I wouldn't worry much
> > about older kernels.
>
> The reason I add this patch was not because I specially like old kernels
> but because this is real use case. What is the default kernel for Android
> 4.4?
KitKat for Nexus 4/7 have kernel 3.4 but we use backports since bluetooth
subsystem must be from 3.9+ kernel.
--
Best regards,
Szymon Janc
Hi Luiz,
On Thu, May 08, 2014 at 04:51:48PM +0300, Luiz Augusto von Dentz wrote:
> Hi Andrei,
>
> On Thu, May 8, 2014 at 3:25 PM, Andrei Emeltchenko
> <[email protected]> wrote:
> > From: Andrei Emeltchenko <[email protected]>
> >
> > For synchronization interleave read() and write().
> > ---
> > android/hal-sco.c | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
> > 1 file changed, 73 insertions(+)
> >
> > diff --git a/android/hal-sco.c b/android/hal-sco.c
> > index 7b6608b..5ec56ce 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,74 @@ 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;
> > + }
> > +
> > + 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;
>
> You probably gonna have store the read data in a buffer to be read by
> upper layer after remixed to 44.1 KHz, to safe space you can probably
> leave the remixing to be done on the read callback. Im fine to do this
> in a separate patch but we should at least add a comment about it.
I think we need to have special thread which does read-write since Android
might call read() without write().
Best regards
Andrei Emeltchenko
Hi Andrei, Szymon,
On Fri, May 9, 2014 at 11:07 AM, Andrei Emeltchenko
<[email protected]> wrote:
> Hi Szymon,
>
> On Fri, May 09, 2014 at 10:02:11AM +0200, Szymon Janc wrote:
>> Hi,
>>
>> On Friday 09 of May 2014 10:37:27 Andrei Emeltchenko wrote:
>> > Hi Luiz,
>> >
>> > On Thu, May 08, 2014 at 04:22:35PM +0300, Luiz Augusto von Dentz wrote:
>> > > > static bool connect_sco(void)
>> > > > @@ -904,7 +930,7 @@ static bool connect_sco(void)
>> > > > device.negotiated_codec != CODEC_ID_CVSD)
>> > > > voice_settings = BT_VOICE_TRANSPARENT;
>> > > > else
>> > > > - voice_settings = BT_VOICE_CVSD_16BIT;
>> > > > + voice_settings = 0;
>> > >
>> > > What is the reason for changing this value to 0?
>> >
>> > Otherwise we always call setsockopt which fails in old kernels.
>> >
>> > in sco_set():
>> > if (!voice)
>> > return TRUE;
>> >
>> > bt_voice.setting = voice;
>> > if (setsockopt(sock, SOL_BLUETOOTH, BT_VOICE, &bt_voice,
>> > sizeof(bt_voice)) < 0) {
>> > ERROR_FAILED(err, "setsockopt(BT_VOICE)", errno);
>> > return FALSE;
>> > }
>>
>> Mgmt 1.3 is already required by Android bluetoothd (kernel 3.9+) and most
>> likely this dependency will be bumped to 1.4 or 1.5. So I wouldn't worry much
>> about older kernels.
>
> The reason I add this patch was not because I specially like old kernels
> but because this is real use case. What is the default kernel for Android
> 4.4?
Maybe something like the following would solve the problem:
diff --git a/android/handsfree.c b/android/handsfree.c
index b3926f3..1effb05 100644
--- a/android/handsfree.c
+++ b/android/handsfree.c
@@ -896,8 +896,9 @@ static bool connect_sco(void)
if (device.sco)
return false;
- if ((device.features & HFP_HF_FEAT_CODEC) &&
- device.negotiated_codec != CODEC_ID_CVSD)
+ if (!(device.features & HFP_HF_FEAT_CODEC))
+ voice_settings = 0;
+ else if (device.negotiated_codec != CODEC_ID_CVSD)
voice_settings = BT_VOICE_TRANSPARENT;
else
voice_settings = BT_VOICE_CVSD_16BIT;
--
Luiz Augusto von Dentz
Hi Szymon,
On Fri, May 9, 2014 at 11:02 AM, Szymon Janc <[email protected]> wrote:
> Hi,
>
> On Friday 09 of May 2014 10:37:27 Andrei Emeltchenko wrote:
>> Hi Luiz,
>>
>> On Thu, May 08, 2014 at 04:22:35PM +0300, Luiz Augusto von Dentz wrote:
>> > > static bool connect_sco(void)
>> > > @@ -904,7 +930,7 @@ static bool connect_sco(void)
>> > > device.negotiated_codec != CODEC_ID_CVSD)
>> > > voice_settings = BT_VOICE_TRANSPARENT;
>> > > else
>> > > - voice_settings = BT_VOICE_CVSD_16BIT;
>> > > + voice_settings = 0;
>> >
>> > What is the reason for changing this value to 0?
>>
>> Otherwise we always call setsockopt which fails in old kernels.
>>
>> in sco_set():
>> if (!voice)
>> return TRUE;
>>
>> bt_voice.setting = voice;
>> if (setsockopt(sock, SOL_BLUETOOTH, BT_VOICE, &bt_voice,
>> sizeof(bt_voice)) < 0) {
>> ERROR_FAILED(err, "setsockopt(BT_VOICE)", errno);
>> return FALSE;
>> }
>
> Mgmt 1.3 is already required by Android bluetoothd (kernel 3.9+) and most
> likely this dependency will be bumped to 1.4 or 1.5. So I wouldn't worry much
> about older kernels.
Well if we are talking about HSP or HFP < 1.6 it is not necessary to
use BT_VOICE.
--
Luiz Augusto von Dentz
Hi Szymon,
On Fri, May 09, 2014 at 10:02:11AM +0200, Szymon Janc wrote:
> Hi,
>
> On Friday 09 of May 2014 10:37:27 Andrei Emeltchenko wrote:
> > Hi Luiz,
> >
> > On Thu, May 08, 2014 at 04:22:35PM +0300, Luiz Augusto von Dentz wrote:
> > > > static bool connect_sco(void)
> > > > @@ -904,7 +930,7 @@ static bool connect_sco(void)
> > > > device.negotiated_codec != CODEC_ID_CVSD)
> > > > voice_settings = BT_VOICE_TRANSPARENT;
> > > > else
> > > > - voice_settings = BT_VOICE_CVSD_16BIT;
> > > > + voice_settings = 0;
> > >
> > > What is the reason for changing this value to 0?
> >
> > Otherwise we always call setsockopt which fails in old kernels.
> >
> > in sco_set():
> > if (!voice)
> > return TRUE;
> >
> > bt_voice.setting = voice;
> > if (setsockopt(sock, SOL_BLUETOOTH, BT_VOICE, &bt_voice,
> > sizeof(bt_voice)) < 0) {
> > ERROR_FAILED(err, "setsockopt(BT_VOICE)", errno);
> > return FALSE;
> > }
>
> Mgmt 1.3 is already required by Android bluetoothd (kernel 3.9+) and most
> likely this dependency will be bumped to 1.4 or 1.5. So I wouldn't worry much
> about older kernels.
The reason I add this patch was not because I specially like old kernels
but because this is real use case. What is the default kernel for Android
4.4?
Best regards
Andrei Emeltchenko
Hi,
On Friday 09 of May 2014 10:37:27 Andrei Emeltchenko wrote:
> Hi Luiz,
>
> On Thu, May 08, 2014 at 04:22:35PM +0300, Luiz Augusto von Dentz wrote:
> > > static bool connect_sco(void)
> > > @@ -904,7 +930,7 @@ static bool connect_sco(void)
> > > device.negotiated_codec != CODEC_ID_CVSD)
> > > voice_settings = BT_VOICE_TRANSPARENT;
> > > else
> > > - voice_settings = BT_VOICE_CVSD_16BIT;
> > > + voice_settings = 0;
> >
> > What is the reason for changing this value to 0?
>
> Otherwise we always call setsockopt which fails in old kernels.
>
> in sco_set():
> if (!voice)
> return TRUE;
>
> bt_voice.setting = voice;
> if (setsockopt(sock, SOL_BLUETOOTH, BT_VOICE, &bt_voice,
> sizeof(bt_voice)) < 0) {
> ERROR_FAILED(err, "setsockopt(BT_VOICE)", errno);
> return FALSE;
> }
Mgmt 1.3 is already required by Android bluetoothd (kernel 3.9+) and most
likely this dependency will be bumped to 1.4 or 1.5. So I wouldn't worry much
about older kernels.
--
Best regards,
Szymon Janc
Hi Luiz,
On Thu, May 08, 2014 at 04:22:35PM +0300, Luiz Augusto von Dentz wrote:
> > static bool connect_sco(void)
> > @@ -904,7 +930,7 @@ static bool connect_sco(void)
> > device.negotiated_codec != CODEC_ID_CVSD)
> > voice_settings = BT_VOICE_TRANSPARENT;
> > else
> > - voice_settings = BT_VOICE_CVSD_16BIT;
> > + voice_settings = 0;
>
> What is the reason for changing this value to 0?
Otherwise we always call setsockopt which fails in old kernels.
in sco_set():
if (!voice)
return TRUE;
bt_voice.setting = voice;
if (setsockopt(sock, SOL_BLUETOOTH, BT_VOICE, &bt_voice,
sizeof(bt_voice)) < 0) {
ERROR_FAILED(err, "setsockopt(BT_VOICE)", errno);
return FALSE;
}
Best regards
Andrei Emeltchenko
Hi Luiz,
On Thu, May 08, 2014 at 04:22:35PM +0300, Luiz Augusto von Dentz wrote:
> Hi Andrei,
>
> On Thu, May 8, 2014 at 3:25 PM, Andrei Emeltchenko
> <[email protected]> wrote:
> > From: Andrei Emeltchenko <[email protected]>
> >
> > If SCO Audio IPC gets connected it provides only one command:
> > connect_sco().
> > ---
> > android/handsfree.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
> > 1 file changed, 57 insertions(+), 1 deletion(-)
> >
> > diff --git a/android/handsfree.c b/android/handsfree.c
> > index 8202e53..96f0f6e 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 *audio_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);
> > @@ -887,6 +892,27 @@ 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);
> > +
> > + if (audio_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(audio_ipc, AUDIO_SERVICE_SCO_ID,
> > + AUDIO_OP_CONNECT_SCO, sizeof(rsp), &rsp,
> > + fd);
> > + }
>
> This does not look correct place to do it, I would probably move to a
> separate function and call it from device_set_audio_state.
>
> > }
> >
> > static bool connect_sco(void)
> > @@ -904,7 +930,7 @@ static bool connect_sco(void)
> > device.negotiated_codec != CODEC_ID_CVSD)
> > voice_settings = BT_VOICE_TRANSPARENT;
> > else
> > - voice_settings = BT_VOICE_CVSD_16BIT;
> > + voice_settings = 0;
>
> What is the reason for changing this value to 0?
>
> >
> > io = bt_io_connect(connect_sco_cb, NULL, NULL, &gerr,
> > BT_IO_OPT_SOURCE_BDADDR, &adapter_addr,
> > @@ -2563,6 +2589,33 @@ static void disable_sco_server(void)
> > }
> > }
> >
> > +static void bt_audio_connect_sco(const void *buf, uint16_t len)
> > +{
> > + DBG("");
> > +
> > + connect_audio();
>
> There doesn't seems to exist any connect_audio function, does this
> even compile? Btw, you need to check if it is already connected then
> you should respond immediately.
Luiz have you tried to compile it? At least I am able to fine the
function:
./android/handsfree.c:1677:static bool connect_audio(void)
static bool connect_audio(void)
{
if (device.audio_state !=
HAL_EV_HANDSFREE_AUDIO_STATE_DISCONNECTED)
return false;
/* we haven't negotiated codec, start selection */
if ((device.features & HFP_HF_FEAT_CODEC) &&
!device.negotiated_codec) {
select_codec(0);
return true;
}
return connect_sco();
}
Best regards
Andrei Emeltchenko
>
> > +}
> > +
> > +static const struct ipc_handler audio_handlers[] = {
> > + /* AUDIO_OP_CONNECT_SCO */
> > + { bt_audio_connect_sco, false, 0 }
> > +};
> > +
> > +static bool bt_audio_register(ipc_disconnect_cb disconnect)
> > +{
> > + DBG("");
> > +
> > + audio_ipc = ipc_init(BLUEZ_SCO_SK_PATH, sizeof(BLUEZ_SCO_SK_PATH),
> > + AUDIO_SERVICE_SCO_ID, false, disconnect, NULL);
> > + if (!audio_ipc)
> > + return false;
> > +
> > + ipc_register(audio_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);
> > @@ -2597,6 +2650,9 @@ done:
> > hal_ipc = ipc;
> > ipc_register(hal_ipc, HAL_SERVICE_ID_HANDSFREE, cmd_handlers,
> > G_N_ELEMENTS(cmd_handlers));
> > +
> > + bt_audio_register(NULL);
> > +
>
> I would also change it to bt_sco_register and obviously you need to
> cleanup properly.
>
> > return true;
> > }
> >
> > --
> > 1.8.3.2
> >
> > --
> > To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in
> > the body of a message to [email protected]
> > More majordomo info at http://vger.kernel.org/majordomo-info.html
>
>
>
> --
> Luiz Augusto von Dentz
Hi Andrei,
On Thu, May 8, 2014 at 3:25 PM, Andrei Emeltchenko
<[email protected]> wrote:
> From: Andrei Emeltchenko <[email protected]>
>
> For synchronization interleave read() and write().
> ---
> android/hal-sco.c | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 73 insertions(+)
>
> diff --git a/android/hal-sco.c b/android/hal-sco.c
> index 7b6608b..5ec56ce 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,74 @@ 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;
> + }
> +
> + 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;
You probably gonna have store the read data in a buffer to be read by
upper layer after remixed to 44.1 KHz, to safe space you can probably
leave the remixing to be done on the read callback. Im fine to do this
in a separate patch but we should at least add a comment about it.
> + }
> +
> + 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 +390,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
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in
> the body of a message to [email protected]
> More majordomo info at http://vger.kernel.org/majordomo-info.html
--
Luiz Augusto von Dentz
Hi Andrei,
On Thu, May 8, 2014 at 3:25 PM, Andrei Emeltchenko
<[email protected]> wrote:
> 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-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,
I suppose this should be AUDIO_DEVICE_OUT_ALL_HSP or whatever it is called.
> + 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 [email protected]
> More majordomo info at http://vger.kernel.org/majordomo-info.html
--
Luiz Augusto von Dentz
Hi Andrei,
On Thu, May 8, 2014 at 3:25 PM, Andrei Emeltchenko
<[email protected]> wrote:
> 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..89fff8d 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("message too small (%zd bytes)", len);
> return -EBADMSG;
> }
>
> if (len != (ssize_t) (sizeof(*msg) + msg->len)) {
> - DBG("message malformed (%zd bytes)", len);
> + error("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("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("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("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("invalid size for opcode 0x%x service 0x%x",
> msg->opcode, msg->service_id);
> return -EMSGSIZE;
> }
> --
> 1.8.3.2
Im not sure if that was not done on purpose, well if that gonna exit
anyway maybe it is fine to use error but then we should probably
prefix the messages with 'IPC:'
--
Luiz Augusto von Dentz
Hi Andrei,
On Thu, May 8, 2014 at 3:25 PM, Andrei Emeltchenko
<[email protected]> wrote:
> From: Andrei Emeltchenko <[email protected]>
>
> ---
> android/hal-sco.c | 320 +++++++++++++++++++++++++++++++++++++++++++++++++++++-
> 1 file changed, 319 insertions(+), 1 deletion(-)
>
> diff --git a/android/hal-sco.c b/android/hal-sco.c
> index 9815c14..7191547 100644
> --- a/android/hal-sco.c
> +++ b/android/hal-sco.c
> @@ -16,13 +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
> @@ -30,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 hsp_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 hsp_audio_config cfg;
> + int fd;
> };
>
> struct sco_audio_dev {
> @@ -46,13 +62,186 @@ struct sco_audio_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;
> }
>
> @@ -182,9 +371,18 @@ static int audio_open_output_stream(struct audio_hw_device *dev,
> {
> struct sco_audio_dev *adev = (struct sco_audio_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;
> @@ -209,9 +407,11 @@ static int audio_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;
> }
> @@ -219,9 +419,17 @@ static int audio_open_output_stream(struct audio_hw_device *dev,
> static void audio_close_output_stream(struct audio_hw_device *dev,
> struct audio_stream_out *stream_out)
> {
> - DBG("");
> + struct sco_audio_dev *hsp_dev = (struct sco_audio_dev *) dev;
> +
> + DBG("dev %p stream %p fd %d", dev, stream_out, hsp_dev->out->fd);
> +
> + if (hsp_dev->out && hsp_dev->out->fd) {
> + close(hsp_dev->out->fd);
> + hsp_dev->out->fd = -1;
> + }
>
> free(stream_out);
> + hsp_dev->out = NULL;
> }
>
> static int audio_set_parameters(struct audio_hw_device *dev,
> @@ -325,10 +533,116 @@ static int audio_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 audio_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 audio_open(const hw_module_t *module, const char *name,
> hw_device_t **device)
> {
> struct sco_audio_dev *adev;
> + int err;
>
> DBG("");
>
> @@ -338,6 +652,10 @@ static int audio_open(const hw_module_t *module, const char *name,
> return -EINVAL;
> }
>
> + err = audio_ipc_init();
Please rename to sco_ipc_init
> + if (err < 0)
> + return err;
> +
> adev = calloc(1, sizeof(struct sco_audio_dev));
> if (!adev)
> return -ENOMEM;
> --
> 1.8.3.2
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in
> the body of a message to [email protected]
> More majordomo info at http://vger.kernel.org/majordomo-info.html
--
Luiz Augusto von Dentz
Hi Andrei,
On Thu, May 8, 2014 at 3:25 PM, Andrei Emeltchenko
<[email protected]> wrote:
> From: Andrei Emeltchenko <[email protected]>
>
> If SCO Audio IPC gets connected it provides only one command:
> connect_sco().
> ---
> android/handsfree.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
> 1 file changed, 57 insertions(+), 1 deletion(-)
>
> diff --git a/android/handsfree.c b/android/handsfree.c
> index 8202e53..96f0f6e 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 *audio_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);
> @@ -887,6 +892,27 @@ 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);
> +
> + if (audio_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(audio_ipc, AUDIO_SERVICE_SCO_ID,
> + AUDIO_OP_CONNECT_SCO, sizeof(rsp), &rsp,
> + fd);
> + }
This does not look correct place to do it, I would probably move to a
separate function and call it from device_set_audio_state.
> }
>
> static bool connect_sco(void)
> @@ -904,7 +930,7 @@ static bool connect_sco(void)
> device.negotiated_codec != CODEC_ID_CVSD)
> voice_settings = BT_VOICE_TRANSPARENT;
> else
> - voice_settings = BT_VOICE_CVSD_16BIT;
> + voice_settings = 0;
What is the reason for changing this value to 0?
>
> io = bt_io_connect(connect_sco_cb, NULL, NULL, &gerr,
> BT_IO_OPT_SOURCE_BDADDR, &adapter_addr,
> @@ -2563,6 +2589,33 @@ static void disable_sco_server(void)
> }
> }
>
> +static void bt_audio_connect_sco(const void *buf, uint16_t len)
> +{
> + DBG("");
> +
> + connect_audio();
There doesn't seems to exist any connect_audio function, does this
even compile? Btw, you need to check if it is already connected then
you should respond immediately.
> +}
> +
> +static const struct ipc_handler audio_handlers[] = {
> + /* AUDIO_OP_CONNECT_SCO */
> + { bt_audio_connect_sco, false, 0 }
> +};
> +
> +static bool bt_audio_register(ipc_disconnect_cb disconnect)
> +{
> + DBG("");
> +
> + audio_ipc = ipc_init(BLUEZ_SCO_SK_PATH, sizeof(BLUEZ_SCO_SK_PATH),
> + AUDIO_SERVICE_SCO_ID, false, disconnect, NULL);
> + if (!audio_ipc)
> + return false;
> +
> + ipc_register(audio_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);
> @@ -2597,6 +2650,9 @@ done:
> hal_ipc = ipc;
> ipc_register(hal_ipc, HAL_SERVICE_ID_HANDSFREE, cmd_handlers,
> G_N_ELEMENTS(cmd_handlers));
> +
> + bt_audio_register(NULL);
> +
I would also change it to bt_sco_register and obviously you need to
cleanup properly.
> return true;
> }
>
> --
> 1.8.3.2
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in
> the body of a message to [email protected]
> More majordomo info at http://vger.kernel.org/majordomo-info.html
--
Luiz Augusto von Dentz
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 f74c1c7..7b6608b 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_audio_dev {
@@ -67,6 +73,22 @@ struct sco_audio_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 audio_open_output_stream(struct audio_hw_device *dev,
struct sco_audio_dev *adev = (struct sco_audio_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 audio_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 audio_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 audio_close_output_stream(struct audio_hw_device *dev,
--
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..89fff8d 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("message too small (%zd bytes)", len);
return -EBADMSG;
}
if (len != (ssize_t) (sizeof(*msg) + msg->len)) {
- DBG("message malformed (%zd bytes)", len);
+ error("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("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("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("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("invalid size for opcode 0x%x service 0x%x",
msg->opcode, msg->service_id);
return -EMSGSIZE;
}
--
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]>
Function adds audio_open_output_stream() and sets dummy callbacks.
---
android/hal-sco.c | 179 +++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 176 insertions(+), 3 deletions(-)
diff --git a/android/hal-sco.c b/android/hal-sco.c
index 5a70aba..9815c14 100644
--- a/android/hal-sco.c
+++ b/android/hal-sco.c
@@ -25,22 +25,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 hsp_audio_config {
+ uint32_t rate;
+ uint32_t channels;
+ audio_format_t format;
+};
+
+struct sco_stream_out {
+ struct audio_stream_out stream;
+ struct hsp_audio_config cfg;
+};
+
struct sco_audio_dev {
struct audio_hw_device dev;
- struct a2dp_stream_out *out;
+ 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 audio_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_audio_dev *adev = (struct sco_audio_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 audio_close_output_stream(struct audio_hw_device *dev,
--
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 7191547..f74c1c7 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 hsp_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 hsp_audio_config cfg;
int fd;
+
+ uint8_t *downmix_buf;
};
struct sco_audio_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 audio_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 audio_close_output_stream(struct audio_hw_device *dev,
struct audio_stream_out *stream_out)
{
struct sco_audio_dev *hsp_dev = (struct sco_audio_dev *) dev;
+ struct sco_stream_out *out = (struct sco_stream_out *) stream_out;
DBG("dev %p stream %p fd %d", dev, stream_out, hsp_dev->out->fd);
@@ -428,7 +466,8 @@ static void audio_close_output_stream(struct audio_hw_device *dev,
hsp_dev->out->fd = -1;
}
- free(stream_out);
+ free(out->downmix_buf);
+ free(out);
hsp_dev->out = NULL;
}
--
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-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
From: Andrei Emeltchenko <[email protected]>
For synchronization interleave read() and write().
---
android/hal-sco.c | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 73 insertions(+)
diff --git a/android/hal-sco.c b/android/hal-sco.c
index 7b6608b..5ec56ce 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,74 @@ 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;
+ }
+
+ 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 +390,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]>
If SCO Audio IPC gets connected it provides only one command:
connect_sco().
---
android/handsfree.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 57 insertions(+), 1 deletion(-)
diff --git a/android/handsfree.c b/android/handsfree.c
index 8202e53..96f0f6e 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 *audio_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);
@@ -887,6 +892,27 @@ 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);
+
+ if (audio_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(audio_ipc, AUDIO_SERVICE_SCO_ID,
+ AUDIO_OP_CONNECT_SCO, sizeof(rsp), &rsp,
+ fd);
+ }
}
static bool connect_sco(void)
@@ -904,7 +930,7 @@ static bool connect_sco(void)
device.negotiated_codec != CODEC_ID_CVSD)
voice_settings = BT_VOICE_TRANSPARENT;
else
- voice_settings = BT_VOICE_CVSD_16BIT;
+ voice_settings = 0;
io = bt_io_connect(connect_sco_cb, NULL, NULL, &gerr,
BT_IO_OPT_SOURCE_BDADDR, &adapter_addr,
@@ -2563,6 +2589,33 @@ 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 bool bt_audio_register(ipc_disconnect_cb disconnect)
+{
+ DBG("");
+
+ audio_ipc = ipc_init(BLUEZ_SCO_SK_PATH, sizeof(BLUEZ_SCO_SK_PATH),
+ AUDIO_SERVICE_SCO_ID, false, disconnect, NULL);
+ if (!audio_ipc)
+ return false;
+
+ ipc_register(audio_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);
@@ -2597,6 +2650,9 @@ done:
hal_ipc = ipc;
ipc_register(hal_ipc, HAL_SERVICE_ID_HANDSFREE, cmd_handlers,
G_N_ELEMENTS(cmd_handlers));
+
+ bt_audio_register(NULL);
+
return true;
}
--
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]>
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 c453f91..7c0c200 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 5113d71..adfb9f4 100644
--- a/android/Makefile.am
+++ b/android/Makefile.am
@@ -176,9 +176,13 @@ android_audio_sco_default_la_SOURCES = android/audio-msg.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]>
---
android/hal-sco.c | 320 +++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 319 insertions(+), 1 deletion(-)
diff --git a/android/hal-sco.c b/android/hal-sco.c
index 9815c14..7191547 100644
--- a/android/hal-sco.c
+++ b/android/hal-sco.c
@@ -16,13 +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
@@ -30,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 hsp_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 hsp_audio_config cfg;
+ int fd;
};
struct sco_audio_dev {
@@ -46,13 +62,186 @@ struct sco_audio_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;
}
@@ -182,9 +371,18 @@ static int audio_open_output_stream(struct audio_hw_device *dev,
{
struct sco_audio_dev *adev = (struct sco_audio_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;
@@ -209,9 +407,11 @@ static int audio_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;
}
@@ -219,9 +419,17 @@ static int audio_open_output_stream(struct audio_hw_device *dev,
static void audio_close_output_stream(struct audio_hw_device *dev,
struct audio_stream_out *stream_out)
{
- DBG("");
+ struct sco_audio_dev *hsp_dev = (struct sco_audio_dev *) dev;
+
+ DBG("dev %p stream %p fd %d", dev, stream_out, hsp_dev->out->fd);
+
+ if (hsp_dev->out && hsp_dev->out->fd) {
+ close(hsp_dev->out->fd);
+ hsp_dev->out->fd = -1;
+ }
free(stream_out);
+ hsp_dev->out = NULL;
}
static int audio_set_parameters(struct audio_hw_device *dev,
@@ -325,10 +533,116 @@ static int audio_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 audio_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 audio_open(const hw_module_t *module, const char *name,
hw_device_t **device)
{
struct sco_audio_dev *adev;
+ int err;
DBG("");
@@ -338,6 +652,10 @@ static int audio_open(const hw_module_t *module, const char *name,
return -EINVAL;
}
+ err = audio_ipc_init();
+ if (err < 0)
+ return err;
+
adev = calloc(1, sizeof(struct sco_audio_dev));
if (!adev)
return -ENOMEM;
--
1.8.3.2