HFP Client SCO audio routing?


I have achieved a CLEAR AUDIO HFP test call.

Two things left to do;

  1. figure out how to set it to mono,
  2. Figure out how to tell Android where to find it.

Kernel diff;

diff --git a/arch/arm64/boot/dts/hisilicon/hi3660-hikey960.dts b/arch/arm64/boot/dts/hisilicon/hi3660-hikey960.dts
index c7dfbd1..eb0fad7 100644
--- a/arch/arm64/boot/dts/hisilicon/hi3660-hikey960.dts
+++ b/arch/arm64/boot/dts/hisilicon/hi3660-hikey960.dts
@@ -271,6 +271,7 @@
 			ldo11: LDO11 { /* Low Speed Connector */
+				regulator-always-on;
 				regulator-name = "VOUT11_1V8_2V95";
 				regulator-min-microvolt = <1750000>;
 				regulator-max-microvolt = <3300000>;
diff --git a/arch/arm64/boot/dts/hisilicon/hi3660.dtsi b/arch/arm64/boot/dts/hisilicon/hi3660.dtsi
index 12544c3..fd4b838 100644
--- a/arch/arm64/boot/dts/hisilicon/hi3660.dtsi
+++ b/arch/arm64/boot/dts/hisilicon/hi3660.dtsi
@@ -1338,9 +1338,14 @@
 			status = "ok";
+		sco_codec: bt_sco {
+			compatible = "linux,bt-sco";
+			#sound-dai-cells = <0>;
+		};
 		sound {
 			compatible = "simple-audio-card";
-			simple-audio-card,name = "hikey-hdmi";
+			simple-audio-card,name = "hikey-btsco";
 			simple-audio-card,format = "i2s";
 			simple-audio-card,bitclock-master = <&sound_master>;
@@ -1351,7 +1356,7 @@
 			simple-audio-card,codec {
-				sound-dai = <&adv7533>;
+				sound-dai = <&sco_codec>;
diff --git a/arch/arm64/configs/hikey960_defconfig b/arch/arm64/configs/hikey960_defconfig
index 8613e46..9462f23 100644
--- a/arch/arm64/configs/hikey960_defconfig
+++ b/arch/arm64/configs/hikey960_defconfig
@@ -323,6 +323,7 @@ CONFIG_SND_USB_AUDIO=y
diff --git a/sound/soc/codecs/bt-sco.c b/sound/soc/codecs/bt-sco.c
index 8014e69..ae03023 100644
--- a/sound/soc/codecs/bt-sco.c
+++ b/sound/soc/codecs/bt-sco.c
@@ -31,34 +31,17 @@ static struct snd_soc_dai_driver bt_sco_dai[] = {
 		.playback = {
 			.stream_name = "Playback",
 			.channels_min = 1,
-			.channels_max = 1,
+			.channels_max = 2,
 			.rates = SNDRV_PCM_RATE_8000,
 			.formats = SNDRV_PCM_FMTBIT_S16_LE,
 		.capture = {
 			 .stream_name = "Capture",
 			.channels_min = 1,
-			.channels_max = 1,
+			.channels_max = 2,
 			.rates = SNDRV_PCM_RATE_8000,
 			.formats = SNDRV_PCM_FMTBIT_S16_LE,
-	},
-	{
-		.name = "bt-sco-pcm-wb",
-		.playback = {
-			.stream_name = "Playback",
-			.channels_min = 1,
-			.channels_max = 1,
-			.rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000,
-			.formats = SNDRV_PCM_FMTBIT_S16_LE,
-		},
-		.capture = {
-			 .stream_name = "Capture",
-			.channels_min = 1,
-			.channels_max = 1,
-			.rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000,
-			.formats = SNDRV_PCM_FMTBIT_S16_LE,
-		},
diff --git a/sound/soc/hisilicon/hisi-i2s.c b/sound/soc/hisilicon/hisi-i2s.c
index 4c74a6c..b0028ca 100644
--- a/sound/soc/hisilicon/hisi-i2s.c
+++ b/sound/soc/hisilicon/hisi-i2s.c
@@ -276,18 +276,18 @@ struct snd_soc_dai_driver hisi_i2s_dai_init = {
 	.name = "hisi_i2s",
 	.probe		= hisi_i2s_dai_probe,
 	.playback = {
-		.channels_min = 2,
+		.channels_min = 1,
 		.channels_max = 2,
 		.formats = SNDRV_PCM_FMTBIT_S16_LE |
-		.rates = SNDRV_PCM_RATE_48000,
+		.rates = SNDRV_PCM_RATE_8000,
 	.capture = {
-		.channels_min = 2,
+		.channels_min = 1,
 		.channels_max = 2,
 		.formats = SNDRV_PCM_FMTBIT_S16_LE |
-		.rates = SNDRV_PCM_RATE_48000,
+		.rates = SNDRV_PCM_RATE_8000,
 	.ops = &hisi_i2s_dai_ops,
@@ -305,7 +305,7 @@ static const struct snd_pcm_hardware snd_hisi_hardware = {
                                   SNDRV_PCM_INFO_RESUME |
                                   SNDRV_PCM_INFO_INTERLEAVED |
-        .period_bytes_min       = 4096,
+        .period_bytes_min       = 1024,
         .period_bytes_max       = 4096,
         .periods_min            = 4,
         .periods_max            = UINT_MAX,
diff --git a/sound/soc/hisilicon/hisi-i2s.h b/sound/soc/hisilicon/hisi-i2s.h
index 7dc0801..9db85f8 100644
--- a/sound/soc/hisilicon/hisi-i2s.h
+++ b/sound/soc/hisilicon/hisi-i2s.h
@@ -79,7 +79,7 @@ enum hisi_i2s_rates {
 #define HI_ASP_CFG_R_CLK_SEL_EN				BIT(2)
 #define HI_ASP_CFG_R_CLK_SEL				0x140010
 #define HI_ASP_CFG_R_CLK1_DIV_SEL			0xbcdc9a
-#define HI_ASP_CFG_R_CLK4_DIV_SEL			0x00ff000f
+#define HI_ASP_CFG_R_CLK4_DIV_SEL			0x00ff005f
 #define HI_ASP_CFG_R_CLK6_DIV_SEL			0x00ff003f
 #define HI_ASP_CFG_SIO_MODE				0
 #define HI_ASP_SIO_MODE_SEL_EN				BIT(0)

WL1837MOD PCM configuration;
dut_mode_send 0x3f 0x0106 0x00 0x02 0x01 0x40 0x1f 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x10 0x00 0x01 0x00 0x01 0x10 0x00 0x01 0x00 0x00 0x00 0x10 0x00 0x21 0x00 0x01 0x10 0x00 0x21 0x00 0x00 0x00

The patch to run that command is up a few comments.


I wonder if you can use DSP format instead of standard I2S. As you already know, I2S always have frame sync with 50% duty cycle, indicating dual channel data fields with left channel data transferred during one half of the cycle and right channel data transferred during the other half.
In DSP format, the trailing edge of the frame-synchronization pulse starts the data transfer, this is up to the host/slave to know where data begins/ends (e.g shift for right channel), this makes mono transfer easier, one FS pulse -> mono data -> FS pulse -> mono-data -> … The wilink controller at least seems to support this, you need to configure Frame Sync duty cylce to 1 and all channel 2 parameters to 0 (I assume).


Well, I think I understand where you’re coming from with that idea, but unfortunately, the problem is one step back from the actual wire. The problem is between ALSA and the SIO. When the SIO is set for 2 channels, then it doesn’t know what to do if it doesn’t receive any data on channel 2.

So there are actually two approaches I can take on this. The ideal approach is to find the register that controls the number of channels. For the 6210, its defined by HII2S_I2S_CFG: https://android.googlesource.com/kernel/hikey-linaro/+/android-hikey-linaro-4.9/sound/soc/hisilicon/hi6210-i2s.c#437 with the second channel mask being defined by HII2S_I2S_CFG__S2_FRAME_MODE. So if I could figure out those values from the production code, then I’ll have it.

The second approach is to feed bogus data into or discard data from channel 2 when its in mono. This approach has the advantage of not being tied to super secret hardware specifications.


Hmm… There might not be a register on the 960 to set the number of channels. The production code doesn’t appear to have anything of the sort. So the problem might be all in the kernel, so… dma driver?


Dear doitright, I am also trying to implement HFP client on hikey 960 and your thread is very helpful for me.
Besides, I am also trying to enable A2DP sink function on this board. Now I can establish an A2DP connection between my phone and hikey 960, but it has no sound when I play musics on my phone. Sound is still played by my phone. Would you please tell me how to let my phone send sound packets to the board and let the board play sound?
I am a newbie in Android and really need help, thank you.


@IsaacChang : I didn’t have to do anything specific to make A2DP work, it came along for the ride with the other changes I made. But note that the implementation of A2DP (whether source or sink) is configured to send the data over HCI. Also note that HCI is very important for A2DP in Android, since Android implements some improved codecs for transferring audio which are NOT available via the bluetooth hardware, like aptX.

I’m using AUTOMOTIVE overlays on mine, since my objective is to build a car radio.
Note: PRODUCT_PACKAGE_OVERLAYS := packages/services/Car/car_product/overlay
which includes this;
Which is where the actual a2dp sink overlay comes from.

Also note that it won’t just “start playing” when you start a sound on the audio source device. I had to hit the play button on the AVRCP application on the hikey before sound would actually play. Feels like a security feature, don’t let randomly connected things play stuff unless they are specifically allowed to?

If you are NOT building the car configuration, you may need something else to do that job.
Something like this;


@doitright : Thank you for replying. It’s so lucky to meet you, because my objective is also to build a car radio. I am really a rookie in Android, even though I have ever seen the key word “car” in the source tree, I don’t know how to configure it to use AUTOMOTIVE overlays. Would you please explain how to achieve it? Thank you.


The key part of Android for Automotive (Android Automotive is very different from Android Auto, despite the similar naming), is going to be found in platform/packages/apps/Car/* and platform/packages/services/Car

There is an example device configured at device/generic/car

This patch for the hikey 960 device tree should get you started, but be aware that the car service does NOT automatically start, so there’s going to be a mess in the logcat and some extra CPU load until you start it manually via
# /vendor/bin/hw/android.hardware.automotive.vehicle@2.0-service &

This patch applies to device/linaro/hikey/
(and pay attention, I’ve changed the display resolution in there to match my display, which may not match yours)

diff --git a/BoardConfigCommon.mk b/BoardConfigCommon.mk
index b1487ce..29f4d51 100644
--- a/BoardConfigCommon.mk
+++ b/BoardConfigCommon.mk
@@ -33,6 +33,7 @@ USE_CAMERA_STUB := true
diff --git a/hikey960.mk b/hikey960.mk
index 521321b..9bc3f9a 100644
--- a/hikey960.mk
+++ b/hikey960.mk
@@ -16,9 +16,29 @@ $(call inherit-product, device/linaro/hikey/hikey960/device-hikey960.mk)
 $(call inherit-product, device/linaro/hikey/device-common.mk)
 $(call inherit-product, $(SRC_TARGET_DIR)/product/full_base.mk)
+$(call inherit-product, packages/services/Car/car_product/build/car.mk)
+PRODUCT_PACKAGE_OVERLAYS := packages/services/Car/car_product/overlay
+PRODUCT_PACKAGES += vehicle.default \
+	CarSettings \
+	Launcher3
+    device/generic/car/common/bootanimations/bootanimation-832.zip:system/media/bootanimation.zip \
+    device/generic/car/common/android.hardware.dummy.xml:system/etc/permissions/handheld_core_hardware.xml \
+    packages/services/Car/car_product/init/init.car.rc:root/init.car.rc \
+    packages/services/Car/car_product/init/init.bootstat.rc:root/init.bootstat.rc
+    frameworks/native/data/etc/android.hardware.type.automotive.xml:system/etc/permissions/android.hardware.type.automotive.xml \
+    frameworks/native/data/etc/android.hardware.screen.landscape.xml:system/etc/permissions/android.hardware.screen.landscape.xml
 # Overrides
 PRODUCT_NAME := hikey960
 PRODUCT_DEVICE := hikey960
-PRODUCT_MODEL := AOSP on hikey960
+PRODUCT_MODEL := AOSP CAR on hikey960
+    android.car.drawer.unlimited=true
diff --git a/hikey960/BoardConfig.mk b/hikey960/BoardConfig.mk
index 0253141..f2621b3 100644
--- a/hikey960/BoardConfig.mk
+++ b/hikey960/BoardConfig.mk
@@ -8,8 +8,8 @@ TARGET_2ND_CPU_VARIANT := cortex-a73
-BOARD_KERNEL_CMDLINE := androidboot.hardware=hikey960 console=ttyFIQ0 androidboot.console=ttyFIQ0
-BOARD_KERNEL_CMDLINE += firmware_class.path=/system/etc/firmware loglevel=15
+BOARD_KERNEL_CMDLINE := androidboot.hardware=hikey960 console=ttyFIQ0 androidboot.console=ttyFIQ0 androidboot.selinux=permissive
+BOARD_KERNEL_CMDLINE += firmware_class.path=/system/etc/firmware loglevel=15 video=HDMI-A-1:1280x800@60
 BOARD_KERNEL_CMDLINE += overlay_mgr.overlay_dt_entry=hardware_cfg_$(TARGET_SENSOR_MEZZANINE)
diff --git a/init.common.rc b/init.common.rc
index 0481b64..94e7409 100644
--- a/init.common.rc
+++ b/init.common.rc
@@ -1,5 +1,6 @@
 import init.${ro.hardware}.usb.rc
 import init.${ro.hardware}.power.rc
+import init.car.rc
 on init
     # mount debugfs
diff --git a/manifest.xml b/manifest.xml
index 61da9eb..6be8016 100644
--- a/manifest.xml
+++ b/manifest.xml
@@ -9,6 +9,15 @@
     <hal format="hidl">
+        <name>android.hardware.automotive.vehicle</name>
+        <transport>hwbinder</transport>
+        <version>2.1</version>
+        <interface>
+            <name>IVehicle</name>
+            <instance>default</instance>
+        </interface>
+    </hal>
+    <hal format="hidl">
diff --git a/overlay/frameworks/base/core/res/res/values/config.xml b/overlay/frameworks/base/core/res/res/values/config.xml
index 8fc81a3..e8581da 100644
--- a/overlay/frameworks/base/core/res/res/values/config.xml
+++ b/overlay/frameworks/base/core/res/res/values/config.xml
@@ -22,10 +22,10 @@
     <!-- This device is not "voice capable"; it's data-only. -->
-    <bool name="config_voice_capable">false</bool>
+    <bool name="config_voice_capable">true</bool>
     <!-- This device does not allow sms service. -->
-    <bool name="config_sms_capable">false</bool>
+    <bool name="config_sms_capable">true</bool>
     <!-- Separate software navigation bar required on this device. -->
     <bool name="config_showNavigationBar">true</bool>


@doitright : Thank you for your help. I have modified those files and built successfully.
A2DP can run normally, but the sound is very small and I can not adjust it to be louder.
Did you have the same problem?


@IsaacChang we’re starting to get a bit off topic for this thread. Think you could create a new thread for your A2DP questions? For controlling the volume, I’d suggest plugging in a keyboard that has volume keys, or using the volume rocker on your cell phone (if that is your audio source).


So moving on with the HFP/SCO…

I think I can manage with the kernel/i2s stuck in stereo mode and do some data manipulations in the HAL. The next step is to work on the HAL, since going over HeadsetClientStateMachine.java, it says that the actual audio routing is up to the HAL, but it sets some parameters to make it actually happen. In particular, “hfp_enable=true”. This parameter is currently only handled by the qualcomm audio hal, which is kind of expected, since the hfp client code was written by qualcomm, and most google devices use qualcomm hardware.

The state machine also sends a parameter to set the sample rate for the BT connection (either 8k or 16k for WB), so it would also be nice to somehow get from that parameter to sending the PCM config to the BT chip to switch sample rates as needed.

EDIT: so it doesn’t look like Android knows much (anything) about routing audio between multiple audio HALs, so I’m going to have to build my own USB audio HAL, well… copy the default USB audio HAL and modify it to do the job. I was going to have to dive into the USB audio hal anyway, since the default one only implements front speaker output and doesn’t distinguish between line-in and microphone in.


I think I’ve come up with a good plan;

I’m going to take the USB audio hal, copy it into the device tree, link it to the webrtc audio processing library (which has things like echo cancellation, noise filter, automatic input gain control, voice detection, etc.) and when it receives the hfp_enable parameter, open the i2s2 audio stream and route it to the USB.


Good job, hope to see your achievement soon. :slight_smile:


@IsaacChang Funny, because I’m trying to hook up a high quality DAC for the same purpose (car audio)


My progress

hikey960:/ # mcap_tool
set_aid_and_cap : pid 4957, uid 0 gid 0[0109/043107.350552:INFO:mcap_tool.cc(995)] Fluoride MCAP test app is starting
[0109/043107.352348:INFO:mcap_tool.cc(352)] Loading HAL library and extensions
[0109/043107.382499:INFO:hal_util.cc(56)] hal_util_load_bt_library loaded HAL path=libbluetooth.so btinterface=0x7d3bcc2400 handle=0x10aaf9de8d1b3ee1
[0109/043107.382811:INFO:mcap_tool.cc(360)] HAL library loaded
[0109/043107.382849:INFO:mcap_tool.cc(541)] adapter_init
[0109/043107.389004:INFO:mcap_tool.cc(390)] HAL REQUEST SUCCESS
[0109/043109.391424:INFO:mcap_tool.cc(550)] adapter_enable
[0109/043109.392043:INFO:mcap_tool.cc(390)] HAL REQUEST SUCCESS
[0109/043109.407740:INFO:btu_task.cc(107)] Bluetooth chip preload is complete
[0109/043109.408561:INFO:gatt_api.cc(947)] GATT_Register 81818181-8181-8181-8181-818181818181
[0109/043109.408703:INFO:gatt_api.cc(967)] allocated gatt_if=1
[0109/043109.408787:INFO:gatt_api.cc(160)] GATTS_AddService
[0109/043109.408849:INFO:gatt_api.cc(264)] GATTS_AddService: service parsed correctly, now starting
[0109/043109.408953:ERROR:gatt_attr.cc(302)] gatt_profile_db_init: gatt_if=1
[0109/043109.409146:INFO:gatt_api.cc(947)] GATT_Register 82828282-8282-8282-8282-828282828282
[0109/043109.409206:INFO:gatt_api.cc(967)] allocated gatt_if=2
[0109/043109.409266:INFO:gatt_api.cc(160)] GATTS_AddService
[0109/043109.409315:INFO:gatt_api.cc(264)] GATTS_AddService: service parsed correctly, now starting
[INFO:ble_advertiser_hci_interface.cc(737)] Legacy advertising will be in use
[INFO:gatt_api.cc(947)] GATT_Register 36e83712-a180-278d-40ca-3d5793874533
[INFO:gatt_api.cc(967)] allocated gatt_if=3
[INFO:mcap_tool.cc(423)] Local Bd Addr = 38:d2:69:f5:9d:bc

dut_mode_send 0x3f 0x0106 0x00 0x02 0x01 0x40 0x1f 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x10 0x00 0x01 0x00 0x01 0x10 0x00 0x01 0x00 0x00 0x00 0x10 0x00 0x21 0x00 0x01 0x10 0x00 0x21 0x00 0x00 0x00
[INFO:mcap_tool.cc(596)] BT DUT MODE SEND
[INFO:mcap_tool.cc(628)] ogf = (int)63
[INFO:mcap_tool.cc(629)] ocf = (int)262
[INFO:mcap_tool.cc(633)] opcode = (int)64774
[INFO:mcap_tool.cc(634)] param_len = (int)34
params: 0x00 0x02 0x01 0x40 0x1F 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x10 0x00 0x01 0x00 0x01 0x10 0x00 0x01 0x00 0x00 0x00 0x10 0x00 0x21 0x00 0x01 0x10 0x00 0x21 0x00 0x00 0x00
[INFO:mcap_tool.cc(390)] HAL REQUEST SUCCESS
DUT MODE RECV: [Opcode = 0xFD06] [Param_len = 1] [Param = 0x00]

hikey960:/ # tinypcminfo -D0 -d0
Info for card 0, device 0:

PCM out:
cannot open device '/dev/snd/pcmC0D0p’
Device does not exist.

PCM in:
cannot open device '/dev/snd/pcmC0D0c’
Device does not exist.
hikey960:/ # ls /dev/snd -l
total 0
crw-rw---- 1 system audio 116, 0 1970-01-01 00:00 controlC0
crw-rw---- 1 system audio 116, 16 1970-01-01 00:00 pcmC0D0p
crw-rw---- 1 system audio 116, 33 1970-01-01 00:00 timer

What’s going wrong?


Looks like something is wrong with your kernel. How exactly did you apply the patch? Anything odd in the dmesg? How did you get the new kernel and dt into your android build?


I patch my kernel manually, remove unwanted lines and add new lines.
Rebuild the kernel, then copy Image.gz and hi3660-hikey960.dtb to device/linaro/hikey-kernel.
Rename Image.gz as Image.gz-hikey960-4.9 and hi3660-hikey960.dtb as hi3660-hikey960.dtb-4.9.
Finally, make bootimage, and flash the new boot.img

I did’t check messages dumped by dmesg, if anything odd I will let you know tomorrow.


Please only patch code using the patch command. This will eliminate all chance of errors.

And you need to be aware that “make bootimage” does NOT regenerate the dt.img file, which I do notice you did not mention having flashed to the device. You MUST write the updated device tree.

Having missed the device tree fully explains your results, since the kernel changes to hisi-i2s.c break compatibility with adv7533 (HDMI) audio codec since they no longer have a common sample rate available between them.


A little bit more progress…

01-18 22:15:08.627  4584  4730 D HeadsetClientStateMachine: AM -> HF 4 11
01-18 22:15:08.627  4584  4730 D HeadsetClientStateMachine: hfp_enable=true mAudioWbs is true
01-18 22:15:08.627  4584  4730 D HeadsetClientStateMachine: Setting sampling rate as 16000
01-18 22:15:08.627  2301  2311 D modules.usbaudio_hal.hikey: adev_set_parameters: kvpairs: hfp_set_sampling_rate=16000
01-18 22:15:08.628  4584  4730 D HeadsetClientStateMachine: hf_volume 11
01-18 22:15:08.628  4584  4730 D HeadsetClientStateMachine: hfp_enable=true
01-18 22:15:08.628  2301  2311 D modules.usbaudio_hal.hikey: adev_set_parameters: kvpairs: hfp_enable=true
01-18 22:15:08.628  2301  2311 D modules.usbaudio_hal.hikey: adev_set_parameters: kvpairs: hfp_volume=11

Ignore the fact that its claiming to be setting wide band audio / 16 kHz. This is after the two sides both agree on 16 kHz, but before the SCO is actually established, so it doesn’t account for the fact that it will fall back to 8 kHz a moment later. The lines with “modules.usbaudio_hal.hikey” are printing from the USB audio HAL.

You can see that hfp sends 3 parameters to the audio hal… sample rate, enable, and volume… ** always in that order **, which is important because it means that we don’t need to worry about switching the sample rate after it is already enabled – this can be stored in a variable.



01-19 07:23:53.931  2430  2875 D HeadsetClientStateMachine: AM -> HF 4 11
01-19 07:23:53.931  2430  2875 D HeadsetClientStateMachine: hfp_enable=true mAudioWbs is false
01-19 07:23:53.931  2430  2875 D HeadsetClientStateMachine: Setting sampling rate as 8000
01-19 07:23:53.932  2430  2875 D HeadsetClientStateMachine: hf_volume 11

My sample rate is 8000, because I setup “AT+BAC=1\r” ?