bluez-alsa
bluez-alsa copied to clipboard
HFP plays silence, bluealsa to soundbar
When I play in HFP mode (no ofono) to my soundbar or vehicle it appears to play, but I hear no sound. However, when I play from my Android phone (either an actual phone call or using BTmono) I can play to the soundbar and hear it.
Note: When I say "HFP", I'm referencing the Hands-Free Bluetooth Profile Specification, Revision v1.7.2, available here: https://www.bluetooth.com/specifications/profiles-overview/
The solution to the problem is to send unsolicited +CIEV result code messages at the appropriate times, as part of setting up a "call" (audio playback) and then again when ending the call. HFP 4.11 describes the overall audio connection setup process.
HFP 4.18 describes in detail the +CIEV commands to send to the HF to inform it of events surrounding a call. In the bluealsa case, the ATD step providing the phone number from the HF device is not necessary, so that part can apparently be skipped.
HFP 4.15.2 describes how to terminate a call from the AG.
EDIT: Removed all the exploration notes and partially broken proof of concept patch. I'm currently working on a better patch and I'll share that when ready.
@Arkq This new patch against 740886b09bc2df10bb1254de5fd6feda91f9ed5b seems to work reliably in my car now. I'm still not sure if transport_acquire_bt_sco is the right place for the pre-sleep. I'm not be covering all cases on the close; I only did happy path for that. Still, I hope this helps!
--- bluez-alsa-ce960be902ea9dd9d7dd211afd0ec232433b3eda.orig/src/ba-transport.c 2019-11-11 13:18:58.868404453 -0700
+++ bluez-alsa-ce960be902ea9dd9d7dd211afd0ec232433b3eda/src/ba-transport.c 2019-11-15 13:52:14.083809051 -0700
@@ -713,7 +713,7 @@ int ba_transport_drain_pcm(struct ba_tra
* arbitrary time before releasing transport. In order to make it right,
* there is a requirement for an asynchronous release mechanism, which
* is not implemented - it requires a little bit of refactoring. */
- usleep(200000);
+ usleep(2000000);
debug("PCM drained");
return 0;
@@ -878,6 +878,7 @@ static int transport_acquire_bt_sco(stru
debug("New SCO link: %s: %d", batostr_(&t->d->addr), t->bt_fd);
t->mtu_read = t->mtu_write = hci_sco_get_mtu(t->bt_fd);
+ usleep(3000000);
return t->bt_fd;
--- bluez-alsa-ce960be902ea9dd9d7dd211afd0ec232433b3eda.orig/src/rfcomm.c 2019-11-12 12:22:58.966070114 -0700
+++ bluez-alsa-ce960be902ea9dd9d7dd211afd0ec232433b3eda/src/rfcomm.c 2019-11-15 15:53:42.743021348 -0700
@@ -176,7 +176,7 @@ static int rfcomm_handler_cind_get_cb(st
(void)at;
const int fd = c->t->bt_fd;
- if (rfcomm_write_at(fd, AT_TYPE_RESP, "+CIND", "0,0,0,0,0,0,0") == -1)
+ if (rfcomm_write_at(fd, AT_TYPE_RESP, "+CIND", "0,0,1,5,0,5,0") == -1)
return -1;
if (rfcomm_write_at(fd, AT_TYPE_RESP, NULL, "OK") == -1)
return -1;
@@ -673,6 +673,40 @@ static int rfcomm_notify_volume_change_s
return 0;
}
+/**
+ * Notify connected BT device of call establishment. */
+static int rfcomm_notify_establish_call(struct rfcomm_conn *c) {
+
+ struct ba_transport * const t = c->t;
+ const int fd = t->bt_fd;
+
+ if (t->type.profile & BA_TRANSPORT_PROFILE_HFP_AG) {
+ if (rfcomm_write_at(fd, AT_TYPE_RESP, "+CIEV", "2,2") == -1)
+ return -1;
+ if (rfcomm_write_at(fd, AT_TYPE_RESP, "+CIEV", "2,3") == -1)
+ return -1;
+ if (rfcomm_write_at(fd, AT_TYPE_RESP, "+CIEV", "1,1") == -1)
+ return -1;
+ if (rfcomm_write_at(fd, AT_TYPE_RESP, "+CIEV", "2,0") == -1)
+ return -1;
+ }
+
+ return 0;
+}
+
+/**
+ * Notify connected BT device of call termination. */
+static int rfcomm_notify_terminate_call(struct rfcomm_conn *c) {
+
+ struct ba_transport * const t = c->t;
+ const int fd = t->bt_fd;
+
+ if (t->type.profile & BA_TRANSPORT_PROFILE_HFP_AG)
+ return rfcomm_write_at(fd, AT_TYPE_RESP, "+CIEV", "1,0");
+
+ return 0;
+}
+
void *rfcomm_thread(struct ba_transport *t) {
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
@@ -871,6 +905,14 @@ void *rfcomm_thread(struct ba_transport
if (pfds[0].revents & POLLIN) {
/* dispatch incoming event */
switch (ba_transport_recv_signal(t)) {
+ case TRANSPORT_PCM_OPEN:
+ if (rfcomm_notify_establish_call(&conn) == -1)
+ goto ioerror;
+ break;
+ case TRANSPORT_PCM_CLOSE:
+ if (rfcomm_notify_terminate_call(&conn) == -1)
+ goto ioerror;
+ break;
case TRANSPORT_SET_VOLUME:
if (rfcomm_notify_volume_change_mic(&conn, false) == -1)
goto ioerror;
--- bluez-alsa-ce960be902ea9dd9d7dd211afd0ec232433b3eda.orig/src/sco.c 2019-11-15 14:38:06.086858454 -0700
+++ bluez-alsa-ce960be902ea9dd9d7dd211afd0ec232433b3eda/src/sco.c 2019-11-15 16:43:25.773335655 -0700
@@ -311,6 +311,10 @@ void *sco_thread(struct ba_transport *t)
* For a headset mode we will wait for an incoming connection from
* some remote Audio Gateway. */
if (t->type.profile & BA_TRANSPORT_PROFILE_MASK_AG)
+ if (t->sco.rfcomm != NULL) {
+ /* notify associated RFCOMM transport */
+ ba_transport_send_signal(t->sco.rfcomm, TRANSPORT_PCM_OPEN);
+ }
t->acquire(t);
#if ENABLE_MSBC
if (t->type.codec == HFP_CODEC_MSBC) {
@@ -334,6 +338,10 @@ void *sco_thread(struct ba_transport *t)
t->sco.spk_pcm.fd == -1 && t->sco.mic_pcm.fd == -1) {
debug("Releasing SCO due to PCM inactivity");
t->release(t);
+ if (t->sco.rfcomm != NULL) {
+ /* notify associated RFCOMM transport */
+ ba_transport_send_signal(t->sco.rfcomm, TRANSPORT_PCM_CLOSE);
+ }
}
continue;
case TRANSPORT_PCM_SYNC:
I'm still not sure if transport_acquire_bt_sco is the right place for the pre-sleep.
In general adding sleeps anywhere is a bad idea and bad design :D Unfortunately, in few places the design is indeed bad (e.g. drain functionality) so I had to add sleep in order to "fix" behavior.
As for the other part of the patch, I thought that that might be the issue (proper RFCOMM communication prior to call). To be honest, I haven't added that part on purpose. Because such problem might escalate and eventually we will end up replicating entire oFono project. Somewhere the line has to be drawn. I will think a little bit about that case and maybe you are right that this will have to be added to bluealsa in order to support wider range of devices. Many thanks for the research! :)
EDIT: Could you check what will happen if you set "invalid" value for battery level (e.g. -1)? Because, it would be best to indicate that the device does not uses battery, and I'm not sure whether spec covers such scenario. Otherwise, the battery level should be obtained from the system (somehow).
I agree 100% on the sleeps. In my car, the HF device is just really slow and takes seconds for the audio to start playing. My soundbar in HF mode requires a delay too, but less than a second (very strange, as A2DP mode appears instant for both devices). If I start playing right away to either device, audio at the beginning is lost. Then I noticed when I played sound via BTmono to my soundbar that there was a several second delay and came to the conclusion that BTmono was deliberately pausing and that my car must not be the only super slow HF device out there. Perhaps there is some way to know when the HF device is actually ready? The HFP spec seems to gloss over some of the details.
I understand your not wanting to duplicate ofono phonesim. The major problem with ofono phonesim for my project was that it requires QT. I looked at the code thinking I could easily rip the GUI code out, but unfortunately they didn't choose a MVC design.
Without ofono, audio playback with bluealsa as an AG didn't work to most of my HF devices. Perhaps I'm reading too much into it, but I think the key might be in HFP 4.11, where it's only considered optional that a HF device be capable of playing audio outside a call.
@killerkalamari I've just added integration with UPower D-Bus service. If you've got UPower on the host, add --enable-upower
during configuration. Then, the host's battery level will be properly exposed to the connected BT device. If host is not battery powered (e.g. PC) bluealsa will report full battery level for HFP-AG. In such case notification to connected headset will not be sent, so phone should not display battery bar.
I've changed the order of indicators in the CIND response, so you will have to update it correctly when resolving conflict during applying your patch.
I've added a wiki page https://github.com/Arkq/bluez-alsa/wiki/Using-BlueALSA-with-HFP-and-HSP-Devices showing how to use bluealsa-rfcomm
to achieve the same outcome as the patch by @killerkalamari here. After allowing a little time in case that gives rise to lots of new issues saying I've got it wrong, I think this issue can then be closed .
After allowing a little time in case that gives rise to lots of new issues saying I've got it wrong, I think this issue can then be closed.
It looks good. But, as it happens, I'm no longer in a position to be able to test this.
To be honest I have forgot about that issue. However, few months ago I thought that maybe it will be a good idea to imitate call start in bluealsa itself (like in this patch https://github.com/Arkq/bluez-alsa/issues/266#issuecomment-554574719), because that's what iPhone does when one tries to use recorder with Bluetooth headphones connected. But I will have to thought it through, so it will be also possible to integrate bluealsa with applications which emulates phone and allows actual phone calls.
I've labelled this issue as an enhancement request, as it remains open simply because of the suggestion that BlueALSA could possibly add more complete HFP support for the HFP-AG role, and it not a bug report as such.