* [PATCH] oFonoHFP profile to enable audio routing in BlueZ @ 2009-11-12 7:50 Zhang, Zhenhua 2009-11-12 16:48 ` Gustavo F. Padovan 2009-11-13 1:01 ` Zhang, Zhenhua 0 siblings, 2 replies; 5+ messages in thread From: Zhang, Zhenhua @ 2009-11-12 7:50 UTC (permalink / raw) To: linux-bluetooth@vger.kernel.org [-- Attachment #1: Type: text/plain, Size: 1659 bytes --] Hi, These three patches are created to enable audio routing for Handsfree Profile. It was created during the voicecall driver implementation in oFono so it does not use the new hfp-api yet. You can take it as reference only since it will not be commited into trunk. Basically, a new profile oFonoHFP was added to create RFCOMM connection and turn IO into TTY device for oFono. It sends TTY device string to oFono through D-Bus. Meanwhile, it listens oFono property changes to emit CallStarted and CallEnded signals. PulseAudio could listen these signals and redirect audio source/sink to use bluetooth one. If you are interested at it, you may apply 0001..0003 into BlueZ, oFono and PA respectively. Last commit SHA-1 is in patch note. And you need to enable oFonoHFP by modify audio.conf: Disable=Gateway Enable=oFonoHFP, Headset In ofono modem.conf, specify device address like: [hfp] Driver=hfp Address=00:22:A9:8C:AF:34 And power on modem by: dbus-send --system --print-reply --dest=org.ofono /hfp0 org.ofono.Modem.SetProperty string:Powered variant:boolean:true The PulseAudio will load module-bluetooth-discover automatically. If not, please load it manually. This module listens BlueZ signal and load module-bluetooth-device automatically. Unfortunately, you need to load module-loopback manually to redirect bluez source/sink to alsa, e.g.: load-module module-loopback source="bluez_source.XX..XX" sink="alsa_output.0.analog-stereo" load-module module-loopback source="alsa_input.0.analog-stereo" sink="bluez_sink.XX..XX" Feel free to let me know if any problems. Thanks. Regards, Zhenhua [-- Attachment #2: 0001-oFonoHFP-Add-oFonoHFP-profile-in-BlueZ.patch --] [-- Type: application/octet-stream, Size: 19430 bytes --] From 32367e0f96b25a0c60a9fe5f3886484c54b840e3 Mon Sep 17 00:00:00 2001 From: Zhenhua Zhang <zhenhua.zhang@intel.com> Date: Thu, 12 Nov 2009 22:24:15 +0800 Subject: [PATCH 1/3] oFonoHFP: Add oFonoHFP profile in BlueZ The oFonoHFP profile is used to provide service for oFono HFP telephony plugins. It exposes two methods: Connect and Disconnect, and two properties: Device and Connected. Once oFono request to create connection with Bluetooth device, it creates the rfcomm connection and turns io into a TTY device. And it returns TTY device name to oFono through D-Bus. It listens oFono property changes to emit CallStarted and CallEnded signal to PulseAudio. Last commit: 515274df91e470472a --- Makefile.am | 1 + audio/device.c | 5 ++ audio/device.h | 3 +- audio/main.c | 14 ++++ audio/manager.c | 193 ++++++++++++++++++++++++++++++++++++++++++++++++++-- audio/manager.h | 1 + audio/unix.c | 120 +++++++++++++++++++++++++++++++-- doc/audio-api.txt | 37 ++++++++++ 8 files changed, 358 insertions(+), 16 deletions(-) diff --git a/Makefile.am b/Makefile.am index d360acb..2aeba67 100644 --- a/Makefile.am +++ b/Makefile.am @@ -121,6 +121,7 @@ builtin_sources += audio/main.c \ audio/avdtp.h audio/avdtp.c \ audio/ipc.h audio/ipc.c \ audio/unix.h audio/unix.c \ + audio/ofono-hfp.h audio/ofono-hfp.c \ audio/telephony.h builtin_nodist += audio/telephony.c diff --git a/audio/device.c b/audio/device.c index eef2aab..63af7b5 100644 --- a/audio/device.c +++ b/audio/device.c @@ -57,6 +57,7 @@ #include "control.h" #include "headset.h" #include "gateway.h" +#include "ofono-hfp.h" #include "sink.h" #include "source.h" @@ -647,6 +648,10 @@ gboolean audio_device_is_active(struct audio_device *dev, else if (!strcmp(interface, AUDIO_GATEWAY_INTERFACE) && dev->gateway && gateway_is_connected(dev)) return TRUE; + else if (!strcmp(interface, AUDIO_OFONO_HFP_INTERFACE) + && dev->ofono_hfp + && ofono_hfp_is_connected(dev)) + return TRUE; return FALSE; } diff --git a/audio/device.h b/audio/device.h index c899d20..05f7cff 100644 --- a/audio/device.h +++ b/audio/device.h @@ -48,6 +48,7 @@ struct target; struct sink; struct headset; struct gateway; +struct ofono_hfp; struct dev_priv; struct audio_device { @@ -62,11 +63,11 @@ struct audio_device { struct headset *headset; struct gateway *gateway; + struct ofono_hfp *ofono_hfp; struct sink *sink; struct source *source; struct control *control; struct target *target; - guint hs_preauth_id; struct dev_priv *priv; diff --git a/audio/main.c b/audio/main.c index 9defe60..3a15709 100644 --- a/audio/main.c +++ b/audio/main.c @@ -46,6 +46,7 @@ #include "headset.h" #include "manager.h" #include "gateway.h" +#include "ofono-hfp.h" static GIOChannel *sco_server = NULL; @@ -98,6 +99,11 @@ static void sco_server_cb(GIOChannel *chan, GError *err, gpointer data) FALSE); if (!device) + device = manager_find_device(NULL, &src, &dst, + AUDIO_OFONO_HFP_INTERFACE, + FALSE); + + if (!device) goto drop; if (device->headset) { @@ -124,6 +130,14 @@ static void sco_server_cb(GIOChannel *chan, GError *err, gpointer data) if (gateway_connect_sco(device, chan) < 0) goto drop; + } else if (device->ofono_hfp) { + if (!ofono_hfp_is_connected(device)) { + debug("Refusing SCO from non-connected AG"); + goto drop; + } + + if (ofono_hfp_connect_sco(device, chan) < 0) + goto drop; } else goto drop; diff --git a/audio/manager.c b/audio/manager.c index c63e98a..96a622f 100644 --- a/audio/manager.c +++ b/audio/manager.c @@ -64,6 +64,7 @@ #include "a2dp.h" #include "headset.h" #include "gateway.h" +#include "ofono-hfp.h" #include "sink.h" #include "source.h" #include "control.h" @@ -74,11 +75,12 @@ typedef enum { HEADSET = 1 << 0, GATEWAY = 1 << 1, - SINK = 1 << 2, - SOURCE = 1 << 3, - CONTROL = 1 << 4, - TARGET = 1 << 5, - INVALID = 1 << 6 + OFONO_HFP = 1 << 2, + SINK = 1 << 3, + SOURCE = 1 << 4, + CONTROL = 1 << 5, + TARGET = 1 << 6, + INVALID = 1 << 7 } audio_service_type; typedef enum { @@ -110,6 +112,7 @@ static struct enabled_interfaces enabled = { .hfp = TRUE, .headset = TRUE, .gateway = FALSE, + .ofono_hfp = FALSE, .sink = TRUE, .source = FALSE, .control = TRUE, @@ -140,7 +143,7 @@ gboolean server_is_enabled(bdaddr_t *src, uint16_t svc) case HANDSFREE_SVCLASS_ID: return enabled.headset && enabled.hfp; case HANDSFREE_AGW_SVCLASS_ID: - return enabled.gateway; + return enabled.gateway || enabled.ofono_hfp; case AUDIO_SINK_SVCLASS_ID: return enabled.sink; case AUDIO_SOURCE_SVCLASS_ID: @@ -198,8 +201,11 @@ static void handle_uuid(const char *uuidstr, struct audio_device *device) break; case HANDSFREE_AGW_SVCLASS_ID: debug("Found Handsfree AG record"); - if (device->gateway == NULL) + if (enabled.gateway && (device->gateway == NULL)) device->gateway = gateway_init(device); + + if (enabled.ofono_hfp && (device->ofono_hfp == NULL)) + device->ofono_hfp = ofono_hfp_init(device); break; case AUDIO_SINK_SVCLASS_ID: debug("Found Audio Sink"); @@ -545,6 +551,23 @@ static void gateway_auth_cb(DBusError *derr, void *user_data) } } +static void ofono_hfp_auth_cb(DBusError *derr, void *user_data) +{ + struct audio_device *device = user_data; + + if (derr && dbus_error_is_set(derr)) + error("Access denied: %s", derr->message); + else { + char ag_address[18]; + + ba2str(&device->dst, ag_address); + debug("Accepted AG connection from %s for %s", + ag_address, device->path); + + ofono_hfp_start_service(device); + } +} + static void hf_io_cb(GIOChannel *chan, gpointer data) { bdaddr_t src, dst; @@ -606,6 +629,67 @@ drop: return; } +static void ofono_hf_io_cb(GIOChannel *chan, gpointer data) +{ + bdaddr_t src, dst; + GError *err = NULL; + uint8_t ch; + const char *server_uuid, *remote_uuid; + uint16_t svclass; + struct audio_device *device; + int perr; + + bt_io_get(chan, BT_IO_RFCOMM, &err, + BT_IO_OPT_SOURCE_BDADDR, &src, + BT_IO_OPT_DEST_BDADDR, &dst, + BT_IO_OPT_CHANNEL, &ch, + BT_IO_OPT_INVALID); + + if (err) { + error("%s", err->message); + g_error_free(err); + return; + } + + server_uuid = HFP_HS_UUID; + remote_uuid = HFP_AG_UUID; + svclass = HANDSFREE_AGW_SVCLASS_ID; + + device = manager_get_device(&src, &dst, TRUE); + if (!device) + goto drop; + + if (!device->ofono_hfp) { + btd_device_add_uuid(device->btd_dev, remote_uuid); + if (!device->ofono_hfp) + goto drop; + } + + if (ofono_hfp_is_connected(device)) { + debug("Refusing new connection since one already exists"); + goto drop; + } + + if (ofono_hfp_connect_rfcomm(device, chan, ch) < 0) { + error("Allocating new GIOChannel failed!"); + goto drop; + } + + perr = audio_device_request_authorization(device, server_uuid, + ofono_hfp_auth_cb, device); + if (perr < 0) { + debug("Authorization denied!"); + goto drop; + } + + return; + +drop: + g_io_channel_shutdown(chan, TRUE, NULL); + g_io_channel_unref(chan); + return; +} + static int headset_server_init(struct audio_adapter *adapter) { uint8_t chan = DEFAULT_HS_AG_CHANNEL; @@ -760,6 +844,61 @@ static int gateway_server_init(struct audio_adapter *adapter) return 0; } +static int ofono_hfp_server_init(struct audio_adapter *adapter) +{ + uint8_t chan = DEFAULT_HFP_HS_CHANNEL; + sdp_record_t *record; + gboolean master = TRUE; + GError *err = NULL; + GIOChannel *io; + bdaddr_t src; + + if (config) { + gboolean tmp; + + tmp = g_key_file_get_boolean(config, "General", "Master", + &err); + if (err) { + debug("audio.conf: %s", err->message); + g_clear_error(&err); + } else + master = tmp; + } + + adapter_get_address(adapter->btd_adapter, &src); + + io = bt_io_listen(BT_IO_RFCOMM, NULL, ofono_hf_io_cb, adapter, + NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, &src, + BT_IO_OPT_CHANNEL, chan, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, + BT_IO_OPT_MASTER, master, + BT_IO_OPT_INVALID); + if (!io) { + error("%s", err->message); + g_error_free(err); + return -1; + } + + adapter->hfp_hs_server = io; + record = hfp_hs_record(chan); + if (!record) { + error("Unable to allocate new service record"); + return -1; + } + + if (add_record_to_server(&src, record) < 0) { + error("Unable to register HFP HS service record"); + sdp_record_free(record); + g_io_channel_unref(adapter->hfp_hs_server); + adapter->hfp_hs_server = NULL; + return -1; + } + + adapter->hfp_hs_record_id = record->handle; + return 0; +} + static int audio_probe(struct btd_device *device, GSList *uuids) { struct btd_adapter *adapter = device_get_adapter(device); @@ -1022,6 +1161,22 @@ static void avrcp_server_remove(struct btd_adapter *adapter) audio_adapter_unref(adp); } +static int ofono_hfp_server_probe(struct btd_adapter *adapter) +{ + struct audio_adapter *adp; + + adp = audio_adapter_get(adapter); + if (!adp) + return -EINVAL; + + return ofono_hfp_server_init(adp); +} + +static void ofono_hfp_server_remove(struct btd_adapter *adapter) +{ + return gateway_server_remove(adapter); +} + static struct btd_device_driver audio_driver = { .name = "audio", .uuids = BTD_UUIDS(HSP_HS_UUID, HFP_HS_UUID, HSP_AG_UUID, HFP_AG_UUID, @@ -1055,6 +1210,12 @@ static struct btd_adapter_driver avrcp_server_driver = { .remove = avrcp_server_remove, }; +static struct btd_adapter_driver ofono_hfp_server_driver = { + .name = "ofono-handsfree", + .probe = ofono_hfp_server_probe, + .remove = ofono_hfp_server_remove, +}; + int audio_manager_init(DBusConnection *conn, GKeyFile *conf, gboolean *enable_sco) { @@ -1083,6 +1244,8 @@ int audio_manager_init(DBusConnection *conn, GKeyFile *conf, enabled.source = TRUE; else if (g_str_equal(list[i], "Control")) enabled.control = TRUE; + else if (g_str_equal(list[i], "oFonoHFP")) + enabled.ofono_hfp = TRUE; } g_strfreev(list); @@ -1099,6 +1262,8 @@ int audio_manager_init(DBusConnection *conn, GKeyFile *conf, enabled.source = FALSE; else if (g_str_equal(list[i], "Control")) enabled.control = FALSE; + else if (g_str_equal(list[i], "oFonoHFP")) + enabled.ofono_hfp = FALSE; } g_strfreev(list); @@ -1140,9 +1305,12 @@ proceed: if (enabled.control) btd_register_adapter_driver(&avrcp_server_driver); + if (enabled.ofono_hfp) + btd_register_adapter_driver(&ofono_hfp_server_driver); + btd_register_device_driver(&audio_driver); - *enable_sco = (enabled.gateway || enabled.headset); + *enable_sco = (enabled.gateway || enabled.headset || enabled.ofono_hfp); return 0; } @@ -1169,6 +1337,11 @@ void audio_manager_exit(void) if (enabled.gateway) btd_unregister_adapter_driver(&gateway_server_driver); + if (enabled.ofono_hfp) { + btd_unregister_adapter_driver(&ofono_hfp_server_driver); + ofono_hfp_exit(); + } + if (enabled.source || enabled.sink) btd_unregister_adapter_driver(&a2dp_server_driver); @@ -1218,6 +1391,10 @@ struct audio_device *manager_find_device(const char *path, && !dev->control) continue; + if (interface && !strcmp(AUDIO_OFONO_HFP_INTERFACE, interface) + && !dev->ofono_hfp) + continue; + if (connected && !audio_device_is_active(dev, interface)) continue; diff --git a/audio/manager.h b/audio/manager.h index cb9d63c..65ca419 100644 --- a/audio/manager.h +++ b/audio/manager.h @@ -26,6 +26,7 @@ struct enabled_interfaces { gboolean hfp; gboolean headset; gboolean gateway; + gboolean ofono_hfp; gboolean sink; gboolean source; gboolean control; diff --git a/audio/unix.c b/audio/unix.c index 6b43bd4..e4f4e4d 100644 --- a/audio/unix.c +++ b/audio/unix.c @@ -48,6 +48,7 @@ #include "headset.h" #include "sink.h" #include "gateway.h" +#include "ofono-hfp.h" #include "unix.h" #include "glib-helper.h" @@ -57,8 +58,9 @@ typedef enum { TYPE_NONE, TYPE_HEADSET, TYPE_GATEWAY, + TYPE_OFONO_HFP, TYPE_SINK, - TYPE_SOURCE + TYPE_SOURCE, } service_type_t; typedef void (*notify_cb_t) (struct audio_device *dev, void *data); @@ -196,6 +198,9 @@ static service_type_t select_service(struct audio_device *dev, const char *inter return TYPE_HEADSET; else if (!strcmp(interface, AUDIO_GATEWAY_INTERFACE) && dev->gateway) return TYPE_GATEWAY; + else if (!strcmp(interface, AUDIO_OFONO_HFP_INTERFACE) + && dev->ofono_hfp) + return TYPE_OFONO_HFP; return TYPE_NONE; } @@ -344,6 +349,32 @@ static void gateway_setup_complete(struct audio_device *dev, void *user_data) unix_ipc_sendmsg(client, &rsp->h); } +static void ofono_hfp_setup_complete(struct audio_device *dev, void *user_data) +{ + struct unix_client *client = user_data; + char buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_set_configuration_rsp *rsp = (void *) buf; + + if (!dev) { + unix_ipc_error(client, BT_SET_CONFIGURATION, EIO); + return; + } + + client->req_id = 0; + + memset(buf, 0, sizeof(buf)); + + rsp->h.type = BT_RESPONSE; + rsp->h.name = BT_SET_CONFIGURATION; + rsp->h.length = sizeof(*rsp); + + rsp->link_mtu = 48; + + client->data_fd = ofono_hfp_get_sco_fd(dev); + + unix_ipc_sendmsg(client, &rsp->h); +} + static void headset_resume_complete(struct audio_device *dev, void *user_data) { struct unix_client *client = user_data; @@ -418,6 +449,44 @@ static void gateway_resume_complete(struct audio_device *dev, void *user_data) client->req_id = 0; } +static void ofono_hfp_resume_complete(struct audio_device *dev, void *user_data) +{ + struct unix_client *client = user_data; + char buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_start_stream_rsp *rsp = (void *) buf; + struct bt_new_stream_ind *ind = (void *) buf; + + if (!dev) + goto failed; + + memset(buf, 0, sizeof(buf)); + rsp->h.type = BT_RESPONSE; + rsp->h.name = BT_START_STREAM; + rsp->h.length = sizeof(*rsp); + + unix_ipc_sendmsg(client, &rsp->h); + + memset(buf, 0, sizeof(buf)); + ind->h.type = BT_INDICATION; + ind->h.name = BT_NEW_STREAM; + ind->h.length = sizeof(*ind); + + unix_ipc_sendmsg(client, &ind->h); + + client->data_fd = ofono_hfp_get_sco_fd(dev); + if (unix_sendmsg_fd(client->sock, client->data_fd) < 0) { + error("unix_sendmsg_fd: %s(%d)", strerror(errno), errno); + unix_ipc_error(client, BT_START_STREAM, EIO); + } + + client->req_id = 0; + return; + +failed: + error("ofono_hfp_resume_complete: resume failed"); + unix_ipc_error(client, BT_START_STREAM, EIO); +} + static void headset_suspend_complete(struct audio_device *dev, void *user_data) { struct unix_client *client = user_data; @@ -870,6 +939,7 @@ static void start_discovery(struct audio_device *dev, struct unix_client *client case TYPE_HEADSET: case TYPE_GATEWAY: + case TYPE_OFONO_HFP: headset_discovery_complete(dev, client); break; @@ -969,8 +1039,9 @@ static void start_open(struct audio_device *dev, struct unix_client *client) } break; - case TYPE_GATEWAY: - break; + case TYPE_GATEWAY: + case TYPE_OFONO_HFP: + break; default: error("No known services for device"); goto failed; @@ -1038,7 +1109,14 @@ static void start_config(struct audio_device *dev, struct unix_client *client) } else id = 0; break; - + case TYPE_OFONO_HFP: + if (ofono_hfp_config_stream(dev, ofono_hfp_setup_complete, + client) >= 0) { + client->cancel = ofono_hfp_cancel_stream; + id = 1; + } else + id = 0; + break; default: error("No known services for device"); goto failed; @@ -1111,6 +1189,15 @@ static void start_resume(struct audio_device *dev, struct unix_client *client) client->cancel = gateway_cancel_stream; break; + case TYPE_OFONO_HFP: + if (ofono_hfp_request_stream(dev, ofono_hfp_resume_complete, + client)) + id = 1; + else + id = 0; + client->cancel = ofono_hfp_cancel_stream; + break; + default: error("No known services for device"); goto failed; @@ -1185,6 +1272,13 @@ static void start_suspend(struct audio_device *dev, struct unix_client *client) id = 1; break; + case TYPE_OFONO_HFP: + ofono_hfp_suspend_stream(dev); + client->cancel = ofono_hfp_cancel_stream; + headset_suspend_complete(dev, client); + id = 1; + break; + default: error("No known services for device"); goto failed; @@ -1241,7 +1335,8 @@ static void start_close(struct audio_device *dev, struct unix_client *client, } break; case TYPE_GATEWAY: - break; + case TYPE_OFONO_HFP: + break; case TYPE_SOURCE: case TYPE_SINK: a2dp = &client->d.a2dp; @@ -1317,6 +1412,13 @@ static void handle_getcapabilities_req(struct unix_client *client, interface = NULL; dev = manager_find_device(req->object, &src, &dst, interface, TRUE); + + if (!dev && (req->transport == BT_CAPABILITIES_TRANSPORT_SCO)) { + interface = g_strdup(AUDIO_OFONO_HFP_INTERFACE); + dev = manager_find_device(req->object, &src, &dst, + interface, TRUE); + } + if (!dev && (req->flags & BT_FLAG_AUTOCONNECT)) dev = manager_find_device(req->object, &src, &dst, interface, FALSE); @@ -1353,7 +1455,8 @@ static int handle_sco_open(struct unix_client *client, struct bt_open_req *req) if (!client->interface) client->interface = g_strdup(AUDIO_HEADSET_INTERFACE); else if (!g_str_equal(client->interface, AUDIO_HEADSET_INTERFACE) && - !g_str_equal(client->interface, AUDIO_GATEWAY_INTERFACE)) + !g_str_equal(client->interface, AUDIO_GATEWAY_INTERFACE) && + !g_str_equal(client->interface, AUDIO_OFONO_HFP_INTERFACE)) return -EIO; debug("open sco - object=%s source=%s destination=%s lock=%s%s", @@ -1447,10 +1550,13 @@ static int handle_sco_transport(struct unix_client *client, client->interface = g_strdup(AUDIO_HEADSET_INTERFACE); else if (dev->gateway) client->interface = g_strdup(AUDIO_GATEWAY_INTERFACE); + else if (dev->ofono_hfp) + client->interface = g_strdup(AUDIO_OFONO_HFP_INTERFACE); else return -EIO; } else if (!g_str_equal(client->interface, AUDIO_HEADSET_INTERFACE) && - !g_str_equal(client->interface, AUDIO_GATEWAY_INTERFACE)) + !g_str_equal(client->interface, AUDIO_GATEWAY_INTERFACE) && + !g_str_equal(client->interface, AUDIO_OFONO_HFP_INTERFACE)) return -EIO; return 0; diff --git a/doc/audio-api.txt b/doc/audio-api.txt index 1f09cd5..5c4182c 100644 --- a/doc/audio-api.txt +++ b/doc/audio-api.txt @@ -456,3 +456,40 @@ properties boolean Connected [readonly] uint16 MicrophoneGain [readonly] The speaker gain when available. + + +oFonoHFP hierarchy +======================== + +Service org.bluez +Interface org.bluez.oFonoHFP +Object path [variable prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX + +This interface is available for remote devices which can function in the Audio +Gateway role of the HFP profiles. It provides connection service for oFono to +create rfcomm connection with remote devices. + +Methods void Connect() + + Connect to the AG service on the remote device and + turn RFCOMM connection into TTY device. Send TTY + device name to oFono through D-Bus. + + void Disconnect() + + Disconnect from the AG service on the remote device. + +Signals PropertyChanged(string name, variant value) + + This signal indicates a changed value of the given + property. + +properties boolean Connected [readonly] + + Indicates if there is an active connection to the + AG service on the remote device. + + string Device[readonly] + + The TTY device name created from RFCOMM connection. + -- 1.6.2.5 [-- Attachment #3: 0002-oFonoHFP-Add-oFonoHFP-support-in-oFono.patch --] [-- Type: application/octet-stream, Size: 10252 bytes --] From cc80eb3be64d5837e0469a85a15ec7038338d139 Mon Sep 17 00:00:00 2001 From: Zhenhua Zhang <zhenhua.zhang@intel.com> Date: Thu, 12 Nov 2009 22:06:44 +0800 Subject: [PATCH 2/3] oFonoHFP: Add oFonoHFP support in oFono It allows HFP plugin to communiate with BlueZ oFonoHFP profile. The HFP plugin use Connect and Disconnect D-Bus methods of oFonoHFP to get TTY device from oFonoHFP. The plugin also watch property change of oFonoHFP if the device is disconnected. Last commit: 7600c4b3ac93d1476d --- plugins/hfp.c | 300 +++++++++++++++++++++++++++++++++++++++++++++++++- plugins/modemconf.c | 3 +- 2 files changed, 295 insertions(+), 8 deletions(-) diff --git a/plugins/hfp.c b/plugins/hfp.c index fc29ad9..c0f8bc8 100644 --- a/plugins/hfp.c +++ b/plugins/hfp.c @@ -30,6 +30,7 @@ #include <glib.h> #include <gatchat.h> #include <gattty.h> +#include <gdbus.h> #define OFONO_API_SUBJECT_TO_CHANGE #include <ofono/plugin.h> @@ -52,10 +53,26 @@ #include <drivers/hfpmodem/hfpmodem.h> +#include <ofono/dbus.h> + +#define BLUEZ_SERVICE "org.bluez" +#define BLUEZ_PATH "/" +#define BLUEZ_MANAGER_INTERFACE "org.bluez.Manager" +#define BLUEZ_ADAPTER_INTERFACE "org.bluez.Adapter" +#define BLUEZ_DEVICE_INTERFACE "org.bluez.Device" +#define BLUEZ_OFONO_HFP_INTERFACE "org.bluez.oFonoHFP" + +#define PROPERTY_CHANGED "PropertyChanged" + static const char *brsf_prefix[] = { "+BRSF:", NULL }; static const char *cind_prefix[] = { "+CIND:", NULL }; static const char *cmer_prefix[] = { "+CMER:", NULL }; +static DBusConnection *connection; +static char *ofono_handsfree_path; + +static int timeout; + static int hfp_disable(struct ofono_modem *modem); static void hfp_debug(const char *str, void *user_data) @@ -63,6 +80,60 @@ static void hfp_debug(const char *str, void *user_data) ofono_info("%s", str); } +static int send_method_call(const char *dest, const char *path, + const char *interface, const char *method, + DBusPendingCallNotifyFunction cb, + void *user_data, int type, ...) +{ + DBusMessage *msg; + DBusPendingCall *call; + va_list args; + + msg = dbus_message_new_method_call(dest, path, interface, method); + if (!msg) { + ofono_error("Unable to allocate new D-Bus %s message", method); + return -ENOMEM; + } + + va_start(args, type); + + if (!dbus_message_append_args_valist(msg, type, args)) { + dbus_message_unref(msg); + va_end(args); + return -EIO; + } + + va_end(args); + + if (!cb) { + g_dbus_send_message(connection, msg); + return 0; + } + + if (!dbus_connection_send_with_reply(connection, msg, &call, -1)) { + ofono_error("Sending %s failed", method); + dbus_message_unref(msg); + return -EIO; + } + + dbus_pending_call_set_notify(call, cb, user_data, NULL); + dbus_pending_call_unref(call); + dbus_message_unref(msg); + + return 0; +} + +static gboolean hfp_enable_timeout(gpointer user) +{ + struct ofono_modem *modem = user; + + if (ofono_modem_get_powered(modem)) + return FALSE; + + hfp_disable(modem); + return FALSE; +} + static void cind_status_cb(gboolean ok, GAtResult *result, gpointer user_data) { @@ -240,6 +311,86 @@ static int service_level_connection(struct ofono_modem *modem, return -EINPROGRESS; } +static void handle_ofono_property(const char *property, DBusMessageIter sub, + void *user_data) +{ + struct ofono_modem *modem = user_data; + const char *tty; + gboolean connected; + + if (g_str_equal(property, "Device")) { + dbus_message_iter_get_basic(&sub, &tty); + + if (timeout) + g_source_remove(timeout); + + service_level_connection(modem, tty); + } + + if (g_str_equal(property, "Connected")) { + dbus_message_iter_get_basic(&sub, &connected); + + if (!connected && ofono_modem_get_powered(modem)) + hfp_disable(modem); + } +} + +static void ofono_property_changed(DBusMessage *msg, void *user_data) +{ + DBusMessageIter iter, sub; + const char *property; + + dbus_message_iter_init(msg, &iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) { + ofono_error("Unexpected signature in " + "oFono PropertyChanged signal"); + return; + } + + dbus_message_iter_get_basic(&iter, &property); + + dbus_message_iter_next(&iter); + dbus_message_iter_recurse(&iter, &sub); + + handle_ofono_property(property, sub, user_data); +} + +static DBusHandlerResult ofono_handsfree_signal(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + if (dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_SIGNAL) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + if (dbus_message_is_signal(msg, BLUEZ_OFONO_HFP_INTERFACE, + PROPERTY_CHANGED)) + ofono_property_changed(msg, user_data); + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static int hfp_add_dbus_filter(struct ofono_modem *modem, void *filter) +{ + char match_string[128]; + + connection = ofono_dbus_get_connection(); + + dbus_connection_ref(connection); + + if (!dbus_connection_add_filter(connection, + (DBusHandleMessageFunction)filter, + modem, NULL)) { + ofono_error("hfp_add_dbus_filter: Can't add signal filter"); + return -EIO; + } + + snprintf(match_string, sizeof(match_string), "type=signal,interface=%s", + BLUEZ_OFONO_HFP_INTERFACE); + dbus_bus_add_match(connection, match_string, NULL); + + return 0; +} + static int hfp_probe(struct ofono_modem *modem) { struct hfp_data *data; @@ -256,9 +407,21 @@ static int hfp_probe(struct ofono_modem *modem) ofono_modem_set_data(modem, data); + hfp_add_dbus_filter(modem, ofono_handsfree_signal); + return 0; } +static void hfp_dbus_cleanup(void *filter) +{ + if (ofono_handsfree_path) + g_free(ofono_handsfree_path); + + dbus_connection_remove_filter(connection, + (DBusHandleMessageFunction)filter, NULL); + dbus_connection_unref(connection); +} + static void hfp_remove(struct ofono_modem *modem) { gpointer data = ofono_modem_get_data(modem); @@ -267,23 +430,147 @@ static void hfp_remove(struct ofono_modem *modem) g_free(data); ofono_modem_set_data(modem, NULL); + + hfp_dbus_cleanup(ofono_handsfree_signal); +} + +static void port_connect_cb(DBusPendingCall *call, gpointer user_data) +{ + DBusError err; + DBusMessage *reply; + const char *msg; + + reply = dbus_pending_call_steal_reply(call); + + dbus_error_init(&err); + + if (dbus_message_get_args(reply, &err, DBUS_TYPE_STRING, + &msg, DBUS_TYPE_INVALID) == FALSE) { + if (dbus_error_is_set(&err) == TRUE) { + ofono_error("%s", err.message); + dbus_error_free(&err); + goto done; + } + } + + if (strcmp(msg, "ok")) + ofono_error("Connect failed: %s", msg); +done: + dbus_message_unref(reply); +} + +static void find_device_cb(DBusPendingCall *call, gpointer user_data) +{ + DBusError err; + DBusMessage *reply; + const char *device; + int ret; + + reply = dbus_pending_call_steal_reply(call); + + dbus_error_init(&err); + + if (dbus_message_get_args(reply, &err, DBUS_TYPE_OBJECT_PATH, + &device, DBUS_TYPE_INVALID) == FALSE) { + if (dbus_error_is_set(&err) == TRUE) { + ofono_error("%s", err.message); + dbus_error_free(&err); + } + goto done; + } + + ofono_debug("Using device %s", device); + ofono_handsfree_path = g_strdup(device); + + ret = send_method_call(BLUEZ_SERVICE, device, + BLUEZ_OFONO_HFP_INTERFACE, "Connect", + port_connect_cb, NULL, DBUS_TYPE_INVALID); + + if (ret < 0) + ofono_error("port_connect failed(%d)", ret); + +done: + dbus_message_unref(reply);; +} + +static void get_adapter_cb(DBusPendingCall *call, gpointer user_data) +{ + DBusError err; + DBusMessage *reply; + const char *adapter; + const char *address = user_data; + int ret; + + reply = dbus_pending_call_steal_reply(call); + + dbus_error_init(&err); + if (dbus_message_get_args(reply, &err, + DBUS_TYPE_OBJECT_PATH, + &adapter, DBUS_TYPE_INVALID) == FALSE) { + if (adapter == NULL) + ofono_error("bluetooth adapter is not enabled"); + + if (dbus_error_is_set(&err) == TRUE) { + ofono_error("%s %s", adapter, err.message); + dbus_error_free(&err); + } + goto done; + } + + ret = send_method_call(BLUEZ_SERVICE, adapter, + BLUEZ_ADAPTER_INTERFACE, "FindDevice", + find_device_cb, NULL, + DBUS_TYPE_STRING, &address, + DBUS_TYPE_INVALID); + + if (ret < 0) + ofono_error("find_device failed(%d)", ret); + +done: + dbus_message_unref(reply); + +} + +static int hfp_connect_ofono_handsfree(const char *address) +{ + ofono_debug("Connect to bluetooth daemon"); + + return send_method_call(BLUEZ_SERVICE, BLUEZ_PATH, + BLUEZ_MANAGER_INTERFACE, "DefaultAdapter", + get_adapter_cb, (char *)address, + DBUS_TYPE_INVALID); } /* power up hardware */ static int hfp_enable(struct ofono_modem *modem) { - const char *tty; - int ret; + const char *address; DBG("%p", modem); - tty = ofono_modem_get_string(modem, "Device"); - if (tty == NULL) + address = ofono_modem_get_string(modem, "Address"); + + DBG("address %s", address); + + if (address == NULL) return -EINVAL; - ret = service_level_connection(modem, tty); + timeout = g_timeout_add_seconds(10, hfp_enable_timeout, modem); + + if (hfp_connect_ofono_handsfree(address) != 0) + return -EINVAL; + + return -EINPROGRESS; +} + +static int hfp_disconnect_ofono_handsfree() +{ + if (!ofono_handsfree_path || !connection) + return -1; - return ret; + return send_method_call(BLUEZ_SERVICE, ofono_handsfree_path, + BLUEZ_OFONO_HFP_INTERFACE, "Disconnect", + NULL, NULL, DBUS_TYPE_INVALID); } static int hfp_disable(struct ofono_modem *modem) @@ -303,6 +590,7 @@ static int hfp_disable(struct ofono_modem *modem) ofono_modem_set_powered(modem, FALSE); + hfp_disconnect_ofono_handsfree(); return 0; } diff --git a/plugins/modemconf.c b/plugins/modemconf.c index 39a62b8..e715822 100644 --- a/plugins/modemconf.c +++ b/plugins/modemconf.c @@ -122,12 +122,11 @@ static struct ofono_modem *create_modem(GKeyFile *keyfile, const char *group) modem = ofono_modem_create(driver); - if (!g_strcmp0(driver, "phonesim")) + if (!g_strcmp0(driver, "phonesim") || !g_strcmp0(driver, "hfp")) set_address(modem, keyfile, group); if (!g_strcmp0(driver, "atgen") || !g_strcmp0(driver, "g1") || !g_strcmp0(driver, "calypso") || - !g_strcmp0(driver, "hfp") || !g_strcmp0(driver, "palmpre")) set_device(modem, keyfile, group); -- 1.6.2.5 [-- Attachment #4: 0003-oFonoHFP-Add-oFonoHFP-patch-in-PulseAudio.patch --] [-- Type: application/octet-stream, Size: 7333 bytes --] From b7df3f91562bee93af6ebf95d9096dec3e783ec3 Mon Sep 17 00:00:00 2001 From: Zhenhua Zhang <zhenhua.zhang@intel.com> Date: Thu, 12 Nov 2009 19:27:17 +0800 Subject: [PATCH 3/3] oFonoHFP: Add oFonoHFP patch in PulseAudio The patch listens CallStarted and CallEnded signal to enable PA plugins for BlueZ. It creates BT source and sink in PA. It's not a complete patch so you still need to load loopback module manually. For example, redirect BT source to speaker: load-module module-loopback source="bluez_source.XX..XX" sink="alsa_output.0.analog-stereo" Last commit: afd1b6d355ef1a41cb (v0.9.19). --- src/modules/bluetooth/bluetooth-util.c | 39 ++++++++++++++++++++--- src/modules/bluetooth/module-bluetooth-device.c | 13 ++++++-- 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/src/modules/bluetooth/bluetooth-util.c b/src/modules/bluetooth/bluetooth-util.c index f8c5b77..d0cb4da 100644 --- a/src/modules/bluetooth/bluetooth-util.c +++ b/src/modules/bluetooth/bluetooth-util.c @@ -123,10 +123,11 @@ static pa_bool_t device_is_audio(pa_bluetooth_device *d) { return d->device_info_valid && - (d->audio_state != PA_BT_AUDIO_STATE_INVALID && + d->audio_state != PA_BT_AUDIO_STATE_INVALID; + /*(d->audio_state != PA_BT_AUDIO_STATE_INVALID && (d->audio_sink_state != PA_BT_AUDIO_STATE_INVALID || d->audio_source_state != PA_BT_AUDIO_STATE_INVALID || - d->headset_state != PA_BT_AUDIO_STATE_INVALID)); + d->headset_state != PA_BT_AUDIO_STATE_INVALID));*/ } static int parse_device_property(pa_bluetooth_discovery *y, pa_bluetooth_device *d, DBusMessageIter *i) { @@ -577,7 +578,31 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us dbus_message_get_path(m), dbus_message_get_member(m)); - if (dbus_message_is_signal(m, "org.bluez.Adapter", "DeviceRemoved")) { + if (dbus_message_get_member(m) && !strcmp(dbus_message_get_member(m),"CallStarted")) { + pa_log_error("CallStarted"); + pa_bluetooth_device *d; + + if ((d = pa_hashmap_get(y->devices, dbus_message_get_path(m)))) { + if (dbus_message_has_interface(m, "org.bluez.oFonoHFP")) { + pa_log_error("oFonoHFP CallStarted detected\n"); + } + d->audio_state = PA_BT_AUDIO_STATE_CONNECTED; + run_callback(y, d, FALSE); + } + } + else if (dbus_message_get_member(m) && !strcmp(dbus_message_get_member(m),"CallEnded")) { + pa_log_error("CallEnded"); + pa_bluetooth_device *d; + + if ((d = pa_hashmap_get(y->devices, dbus_message_get_path(m)))) { + if (dbus_message_has_interface(m, "org.bluez.oFonoHFP")) { + pa_log_error("oFonoHFP CallEnded detected\n"); + } + d->audio_state = PA_BT_AUDIO_STATE_DISCONNECTED; + run_callback(y, d, TRUE); + } + } + else if (dbus_message_is_signal(m, "org.bluez.Adapter", "DeviceRemoved")) { const char *path; pa_bluetooth_device *d; @@ -794,7 +819,9 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) { "type='signal',sender='org.bluez',interface='org.bluez.Audio',member='PropertyChanged'", "type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'", "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'", - "type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'", NULL) < 0) { + "type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'", + "type='signal',sender='org.bluez',interface='org.bluez.oFonoHFP',member='CallStarted'", + "type='signal',sender='org.bluez',interface='org.bluez.oFonoHFP',member='CallEnded'", NULL) < 0) { pa_log("Failed to add D-Bus matches: %s", err.message); goto fail; } @@ -848,7 +875,9 @@ void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) { "type='signal',sender='org.bluez',interface='org.bluez.Audio',member='PropertyChanged'", "type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'", "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'", - "type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'", NULL); + "type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'", + "type='signal',sender='org.bluez',interface='org.bluez.oFonoHFP',member='CallStarted'", + "type='signal',sender='org.bluez',interface='org.bluez.oFonoHFP',member='CallEnded'", NULL); dbus_connection_remove_filter(pa_dbus_connection_get(y->connection), filter_cb, y); diff --git a/src/modules/bluetooth/module-bluetooth-device.c b/src/modules/bluetooth/module-bluetooth-device.c index 4592fca..c8d66d4 100644 --- a/src/modules/bluetooth/module-bluetooth-device.c +++ b/src/modules/bluetooth/module-bluetooth-device.c @@ -676,6 +676,9 @@ static int set_conf(struct userdata *u) { msg.open_req.h.name = BT_OPEN; msg.open_req.h.length = sizeof(msg.open_req); + pa_assert(u->address); + pa_strlcpy(msg.open_req.destination, u->address, sizeof(msg.open_req.destination)); + pa_strlcpy(msg.open_req.object, u->path, sizeof(msg.open_req.object)); msg.open_req.seid = (u->profile == PROFILE_A2DP || u->profile == PROFILE_A2DP_SOURCE) ? u->a2dp.sbc_capabilities.capability.seid : BT_A2DP_SEID_RANGE + 1; msg.open_req.lock = (u->profile == PROFILE_A2DP) ? BT_WRITE_LOCK : BT_READ_LOCK | BT_WRITE_LOCK; @@ -2209,7 +2212,11 @@ static int add_card(struct userdata *u, const pa_bluetooth_device *device) { } if (pa_bluetooth_uuid_has(device->uuids, HSP_HS_UUID) || - pa_bluetooth_uuid_has(device->uuids, HFP_HS_UUID)) { + pa_bluetooth_uuid_has(device->uuids, HFP_HS_UUID) || + ///////////////////////////////////////////////// + pa_bluetooth_uuid_has(device->uuids, HSP_AG_UUID) || + pa_bluetooth_uuid_has(device->uuids, HFP_AG_UUID)) { + ///////////////////////////////////////////////// p = pa_card_profile_new("hsp", _("Telephony Duplex (HSP/HFP)"), sizeof(enum profile)); p->priority = 20; p->n_sinks = 1; @@ -2249,14 +2256,14 @@ static int add_card(struct userdata *u, const pa_bluetooth_device *device) { u->card->set_profile = card_set_profile; d = PA_CARD_PROFILE_DATA(u->card->active_profile); - +/* if ((device->headset_state < PA_BT_AUDIO_STATE_CONNECTED && *d == PROFILE_HSP) || (device->audio_sink_state < PA_BT_AUDIO_STATE_CONNECTED && *d == PROFILE_A2DP)) { pa_log_warn("Default profile not connected, selecting off profile"); u->card->active_profile = pa_hashmap_get(u->card->profiles, "off"); u->card->save_profile = FALSE; } - +*/ d = PA_CARD_PROFILE_DATA(u->card->active_profile); u->profile = *d; -- 1.6.2.5 ^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH] oFonoHFP profile to enable audio routing in BlueZ 2009-11-12 7:50 [PATCH] oFonoHFP profile to enable audio routing in BlueZ Zhang, Zhenhua @ 2009-11-12 16:48 ` Gustavo F. Padovan 2009-11-13 1:01 ` Zhang, Zhenhua 1 sibling, 0 replies; 5+ messages in thread From: Gustavo F. Padovan @ 2009-11-12 16:48 UTC (permalink / raw) To: Zhang, Zhenhua; +Cc: linux-bluetooth@vger.kernel.org Hi Zhenhua, 2009/11/12 Zhang, Zhenhua <zhenhua.zhang@intel.com>: > Hi, > > These three patches are created to enable audio routing for Handsfree Profile. > It was created during the voicecall driver implementation in oFono so it does > not use the new hfp-api yet. You can take it as reference only since it will > not be commited into trunk. You forgot to include ofono-hfp.h into the BlueZ patch. > > Basically, a new profile oFonoHFP was added to create RFCOMM connection > and turn IO into TTY device for oFono. It sends TTY device string to oFono > through D-Bus. Meanwhile, it listens oFono property changes to emit CallStarted > and CallEnded signals. PulseAudio could listen these signals and redirect audio > source/sink to use bluetooth one. > > If you are interested at it, you may apply 0001..0003 into BlueZ, oFono and PA > respectively. Last commit SHA-1 is in patch note. > > And you need to enable oFonoHFP by modify audio.conf: > Disable=Gateway > Enable=oFonoHFP, Headset > > In ofono modem.conf, specify device address like: > [hfp] > Driver=hfp > Address=00:22:A9:8C:AF:34 > > And power on modem by: > dbus-send --system --print-reply --dest=org.ofono /hfp0 > org.ofono.Modem.SetProperty string:Powered variant:boolean:true > > The PulseAudio will load module-bluetooth-discover automatically. If not, > please load it manually. This module listens BlueZ signal and load > module-bluetooth-device automatically. Unfortunately, you need to load > module-loopback manually to redirect bluez source/sink to alsa, e.g.: > load-module module-loopback source="bluez_source.XX..XX" > sink="alsa_output.0.analog-stereo" > load-module module-loopback source="alsa_input.0.analog-stereo" > sink="bluez_sink.XX..XX" > > Feel free to let me know if any problems. Thanks. > > Regards, > Zhenhua > > -- Gustavo F. Padovan http://padovan.org ^ permalink raw reply [flat|nested] 5+ messages in thread
* RE: [PATCH] oFonoHFP profile to enable audio routing in BlueZ 2009-11-12 7:50 [PATCH] oFonoHFP profile to enable audio routing in BlueZ Zhang, Zhenhua 2009-11-12 16:48 ` Gustavo F. Padovan @ 2009-11-13 1:01 ` Zhang, Zhenhua 2009-11-13 13:36 ` Luiz Augusto von Dentz 1 sibling, 1 reply; 5+ messages in thread From: Zhang, Zhenhua @ 2009-11-13 1:01 UTC (permalink / raw) To: linux-bluetooth@vger.kernel.org; +Cc: Gustavo F. Padovan [-- Attachment #1: Type: text/plain, Size: 1910 bytes --] Hi, Ops, forgot to add ofono-hfp.c into BlueZ patch. So I add them and resend 0001 patch. linux-bluetooth-owner@vger.kernel.org wrote: > Hi, > > These three patches are created to enable audio routing for Handsfree > Profile. It was created during the voicecall driver implementation in > oFono so it does not use the new hfp-api yet. You can take it > as reference only since it will not be commited into trunk. > > Basically, a new profile oFonoHFP was added to create RFCOMM > connection and turn IO into TTY device for oFono. It sends TTY > device string to oFono through D-Bus. Meanwhile, it listens > oFono property changes to emit CallStarted and CallEnded > signals. PulseAudio could listen these signals and redirect > audio source/sink to use bluetooth one. > > If you are interested at it, you may apply 0001..0003 into > BlueZ, oFono and PA respectively. Last commit SHA-1 is in patch note. > > And you need to enable oFonoHFP by modify audio.conf: Disable=Gateway > Enable=oFonoHFP, Headset > > In ofono modem.conf, specify device address like: > [hfp] > Driver=hfp > Address=00:22:A9:8C:AF:34 > > And power on modem by: > dbus-send --system --print-reply --dest=org.ofono /hfp0 > org.ofono.Modem.SetProperty string:Powered variant:boolean:true > > The PulseAudio will load module-bluetooth-discover > automatically. If not, please load it manually. This module > listens BlueZ signal and load module-bluetooth-device > automatically. Unfortunately, you need to load module-loopback > manually to redirect bluez source/sink to alsa, e.g.: > load-module module-loopback source="bluez_source.XX..XX" > sink="alsa_output.0.analog-stereo" > load-module module-loopback source="alsa_input.0.analog-stereo" > sink="bluez_sink.XX..XX" > > Feel free to let me know if any problems. Thanks. > > Regards, > Zhenhua Regards, Zhenhua [-- Attachment #2: 0001-oFonoHFP-Add-oFonoHFP-profile-in-BlueZ.patch --] [-- Type: application/octet-stream, Size: 41939 bytes --] From a202b4b91f5f7253bcc9532e2737b327f801c357 Mon Sep 17 00:00:00 2001 From: Zhenhua Zhang <zhenhua.zhang@intel.com> Date: Thu, 12 Nov 2009 22:24:15 +0800 Subject: [PATCH] oFonoHFP: Add oFonoHFP profile in BlueZ The oFonoHFP profile is used to provide service for oFono HFP telephony plugins. It exposes two methods: Connect and Disconnect, and two properties: Device and Connected. Once oFono request to create connection with Bluetooth device, it creates the rfcomm connection and turns io into a TTY device. And it returns TTY device name to oFono through D-Bus. It listens oFono property changes to emit CallStarted and CallEnded signal to PulseAudio. --- Makefile.am | 1 + audio/device.c | 5 + audio/device.h | 3 +- audio/main.c | 14 + audio/manager.c | 193 +++++++++++++- audio/manager.h | 1 + audio/ofono-hfp.c | 797 +++++++++++++++++++++++++++++++++++++++++++++++++++++ audio/ofono-hfp.h | 50 ++++ audio/unix.c | 120 ++++++++- doc/audio-api.txt | 37 +++ 10 files changed, 1205 insertions(+), 16 deletions(-) create mode 100644 audio/ofono-hfp.c create mode 100644 audio/ofono-hfp.h diff --git a/Makefile.am b/Makefile.am index d360acb..2aeba67 100644 --- a/Makefile.am +++ b/Makefile.am @@ -121,6 +121,7 @@ builtin_sources += audio/main.c \ audio/avdtp.h audio/avdtp.c \ audio/ipc.h audio/ipc.c \ audio/unix.h audio/unix.c \ + audio/ofono-hfp.h audio/ofono-hfp.c \ audio/telephony.h builtin_nodist += audio/telephony.c diff --git a/audio/device.c b/audio/device.c index eef2aab..63af7b5 100644 --- a/audio/device.c +++ b/audio/device.c @@ -57,6 +57,7 @@ #include "control.h" #include "headset.h" #include "gateway.h" +#include "ofono-hfp.h" #include "sink.h" #include "source.h" @@ -647,6 +648,10 @@ gboolean audio_device_is_active(struct audio_device *dev, else if (!strcmp(interface, AUDIO_GATEWAY_INTERFACE) && dev->gateway && gateway_is_connected(dev)) return TRUE; + else if (!strcmp(interface, AUDIO_OFONO_HFP_INTERFACE) + && dev->ofono_hfp + && ofono_hfp_is_connected(dev)) + return TRUE; return FALSE; } diff --git a/audio/device.h b/audio/device.h index c899d20..05f7cff 100644 --- a/audio/device.h +++ b/audio/device.h @@ -48,6 +48,7 @@ struct target; struct sink; struct headset; struct gateway; +struct ofono_hfp; struct dev_priv; struct audio_device { @@ -62,11 +63,11 @@ struct audio_device { struct headset *headset; struct gateway *gateway; + struct ofono_hfp *ofono_hfp; struct sink *sink; struct source *source; struct control *control; struct target *target; - guint hs_preauth_id; struct dev_priv *priv; diff --git a/audio/main.c b/audio/main.c index 9defe60..3a15709 100644 --- a/audio/main.c +++ b/audio/main.c @@ -46,6 +46,7 @@ #include "headset.h" #include "manager.h" #include "gateway.h" +#include "ofono-hfp.h" static GIOChannel *sco_server = NULL; @@ -98,6 +99,11 @@ static void sco_server_cb(GIOChannel *chan, GError *err, gpointer data) FALSE); if (!device) + device = manager_find_device(NULL, &src, &dst, + AUDIO_OFONO_HFP_INTERFACE, + FALSE); + + if (!device) goto drop; if (device->headset) { @@ -124,6 +130,14 @@ static void sco_server_cb(GIOChannel *chan, GError *err, gpointer data) if (gateway_connect_sco(device, chan) < 0) goto drop; + } else if (device->ofono_hfp) { + if (!ofono_hfp_is_connected(device)) { + debug("Refusing SCO from non-connected AG"); + goto drop; + } + + if (ofono_hfp_connect_sco(device, chan) < 0) + goto drop; } else goto drop; diff --git a/audio/manager.c b/audio/manager.c index c63e98a..96a622f 100644 --- a/audio/manager.c +++ b/audio/manager.c @@ -64,6 +64,7 @@ #include "a2dp.h" #include "headset.h" #include "gateway.h" +#include "ofono-hfp.h" #include "sink.h" #include "source.h" #include "control.h" @@ -74,11 +75,12 @@ typedef enum { HEADSET = 1 << 0, GATEWAY = 1 << 1, - SINK = 1 << 2, - SOURCE = 1 << 3, - CONTROL = 1 << 4, - TARGET = 1 << 5, - INVALID = 1 << 6 + OFONO_HFP = 1 << 2, + SINK = 1 << 3, + SOURCE = 1 << 4, + CONTROL = 1 << 5, + TARGET = 1 << 6, + INVALID = 1 << 7 } audio_service_type; typedef enum { @@ -110,6 +112,7 @@ static struct enabled_interfaces enabled = { .hfp = TRUE, .headset = TRUE, .gateway = FALSE, + .ofono_hfp = FALSE, .sink = TRUE, .source = FALSE, .control = TRUE, @@ -140,7 +143,7 @@ gboolean server_is_enabled(bdaddr_t *src, uint16_t svc) case HANDSFREE_SVCLASS_ID: return enabled.headset && enabled.hfp; case HANDSFREE_AGW_SVCLASS_ID: - return enabled.gateway; + return enabled.gateway || enabled.ofono_hfp; case AUDIO_SINK_SVCLASS_ID: return enabled.sink; case AUDIO_SOURCE_SVCLASS_ID: @@ -198,8 +201,11 @@ static void handle_uuid(const char *uuidstr, struct audio_device *device) break; case HANDSFREE_AGW_SVCLASS_ID: debug("Found Handsfree AG record"); - if (device->gateway == NULL) + if (enabled.gateway && (device->gateway == NULL)) device->gateway = gateway_init(device); + + if (enabled.ofono_hfp && (device->ofono_hfp == NULL)) + device->ofono_hfp = ofono_hfp_init(device); break; case AUDIO_SINK_SVCLASS_ID: debug("Found Audio Sink"); @@ -545,6 +551,23 @@ static void gateway_auth_cb(DBusError *derr, void *user_data) } } +static void ofono_hfp_auth_cb(DBusError *derr, void *user_data) +{ + struct audio_device *device = user_data; + + if (derr && dbus_error_is_set(derr)) + error("Access denied: %s", derr->message); + else { + char ag_address[18]; + + ba2str(&device->dst, ag_address); + debug("Accepted AG connection from %s for %s", + ag_address, device->path); + + ofono_hfp_start_service(device); + } +} + static void hf_io_cb(GIOChannel *chan, gpointer data) { bdaddr_t src, dst; @@ -606,6 +629,67 @@ drop: return; } +static void ofono_hf_io_cb(GIOChannel *chan, gpointer data) +{ + bdaddr_t src, dst; + GError *err = NULL; + uint8_t ch; + const char *server_uuid, *remote_uuid; + uint16_t svclass; + struct audio_device *device; + int perr; + + bt_io_get(chan, BT_IO_RFCOMM, &err, + BT_IO_OPT_SOURCE_BDADDR, &src, + BT_IO_OPT_DEST_BDADDR, &dst, + BT_IO_OPT_CHANNEL, &ch, + BT_IO_OPT_INVALID); + + if (err) { + error("%s", err->message); + g_error_free(err); + return; + } + + server_uuid = HFP_HS_UUID; + remote_uuid = HFP_AG_UUID; + svclass = HANDSFREE_AGW_SVCLASS_ID; + + device = manager_get_device(&src, &dst, TRUE); + if (!device) + goto drop; + + if (!device->ofono_hfp) { + btd_device_add_uuid(device->btd_dev, remote_uuid); + if (!device->ofono_hfp) + goto drop; + } + + if (ofono_hfp_is_connected(device)) { + debug("Refusing new connection since one already exists"); + goto drop; + } + + if (ofono_hfp_connect_rfcomm(device, chan, ch) < 0) { + error("Allocating new GIOChannel failed!"); + goto drop; + } + + perr = audio_device_request_authorization(device, server_uuid, + ofono_hfp_auth_cb, device); + if (perr < 0) { + debug("Authorization denied!"); + goto drop; + } + + return; + +drop: + g_io_channel_shutdown(chan, TRUE, NULL); + g_io_channel_unref(chan); + return; +} + static int headset_server_init(struct audio_adapter *adapter) { uint8_t chan = DEFAULT_HS_AG_CHANNEL; @@ -760,6 +844,61 @@ static int gateway_server_init(struct audio_adapter *adapter) return 0; } +static int ofono_hfp_server_init(struct audio_adapter *adapter) +{ + uint8_t chan = DEFAULT_HFP_HS_CHANNEL; + sdp_record_t *record; + gboolean master = TRUE; + GError *err = NULL; + GIOChannel *io; + bdaddr_t src; + + if (config) { + gboolean tmp; + + tmp = g_key_file_get_boolean(config, "General", "Master", + &err); + if (err) { + debug("audio.conf: %s", err->message); + g_clear_error(&err); + } else + master = tmp; + } + + adapter_get_address(adapter->btd_adapter, &src); + + io = bt_io_listen(BT_IO_RFCOMM, NULL, ofono_hf_io_cb, adapter, + NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, &src, + BT_IO_OPT_CHANNEL, chan, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, + BT_IO_OPT_MASTER, master, + BT_IO_OPT_INVALID); + if (!io) { + error("%s", err->message); + g_error_free(err); + return -1; + } + + adapter->hfp_hs_server = io; + record = hfp_hs_record(chan); + if (!record) { + error("Unable to allocate new service record"); + return -1; + } + + if (add_record_to_server(&src, record) < 0) { + error("Unable to register HFP HS service record"); + sdp_record_free(record); + g_io_channel_unref(adapter->hfp_hs_server); + adapter->hfp_hs_server = NULL; + return -1; + } + + adapter->hfp_hs_record_id = record->handle; + return 0; +} + static int audio_probe(struct btd_device *device, GSList *uuids) { struct btd_adapter *adapter = device_get_adapter(device); @@ -1022,6 +1161,22 @@ static void avrcp_server_remove(struct btd_adapter *adapter) audio_adapter_unref(adp); } +static int ofono_hfp_server_probe(struct btd_adapter *adapter) +{ + struct audio_adapter *adp; + + adp = audio_adapter_get(adapter); + if (!adp) + return -EINVAL; + + return ofono_hfp_server_init(adp); +} + +static void ofono_hfp_server_remove(struct btd_adapter *adapter) +{ + return gateway_server_remove(adapter); +} + static struct btd_device_driver audio_driver = { .name = "audio", .uuids = BTD_UUIDS(HSP_HS_UUID, HFP_HS_UUID, HSP_AG_UUID, HFP_AG_UUID, @@ -1055,6 +1210,12 @@ static struct btd_adapter_driver avrcp_server_driver = { .remove = avrcp_server_remove, }; +static struct btd_adapter_driver ofono_hfp_server_driver = { + .name = "ofono-handsfree", + .probe = ofono_hfp_server_probe, + .remove = ofono_hfp_server_remove, +}; + int audio_manager_init(DBusConnection *conn, GKeyFile *conf, gboolean *enable_sco) { @@ -1083,6 +1244,8 @@ int audio_manager_init(DBusConnection *conn, GKeyFile *conf, enabled.source = TRUE; else if (g_str_equal(list[i], "Control")) enabled.control = TRUE; + else if (g_str_equal(list[i], "oFonoHFP")) + enabled.ofono_hfp = TRUE; } g_strfreev(list); @@ -1099,6 +1262,8 @@ int audio_manager_init(DBusConnection *conn, GKeyFile *conf, enabled.source = FALSE; else if (g_str_equal(list[i], "Control")) enabled.control = FALSE; + else if (g_str_equal(list[i], "oFonoHFP")) + enabled.ofono_hfp = FALSE; } g_strfreev(list); @@ -1140,9 +1305,12 @@ proceed: if (enabled.control) btd_register_adapter_driver(&avrcp_server_driver); + if (enabled.ofono_hfp) + btd_register_adapter_driver(&ofono_hfp_server_driver); + btd_register_device_driver(&audio_driver); - *enable_sco = (enabled.gateway || enabled.headset); + *enable_sco = (enabled.gateway || enabled.headset || enabled.ofono_hfp); return 0; } @@ -1169,6 +1337,11 @@ void audio_manager_exit(void) if (enabled.gateway) btd_unregister_adapter_driver(&gateway_server_driver); + if (enabled.ofono_hfp) { + btd_unregister_adapter_driver(&ofono_hfp_server_driver); + ofono_hfp_exit(); + } + if (enabled.source || enabled.sink) btd_unregister_adapter_driver(&a2dp_server_driver); @@ -1218,6 +1391,10 @@ struct audio_device *manager_find_device(const char *path, && !dev->control) continue; + if (interface && !strcmp(AUDIO_OFONO_HFP_INTERFACE, interface) + && !dev->ofono_hfp) + continue; + if (connected && !audio_device_is_active(dev, interface)) continue; diff --git a/audio/manager.h b/audio/manager.h index cb9d63c..65ca419 100644 --- a/audio/manager.h +++ b/audio/manager.h @@ -26,6 +26,7 @@ struct enabled_interfaces { gboolean hfp; gboolean headset; gboolean gateway; + gboolean ofono_hfp; gboolean sink; gboolean source; gboolean control; diff --git a/audio/ofono-hfp.c b/audio/ofono-hfp.c new file mode 100644 index 0000000..e70b208 --- /dev/null +++ b/audio/ofono-hfp.c @@ -0,0 +1,797 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2009 Marcel Holtmann <marcel@holtmann.org> + * Copyright (C) 2008-2009 Leonid Movshovich <event.riga@gmail.org> + * Copyright (C) 2009-2009 Zhenhua Zhang <zhenhua.zhang@intel.com> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/ioctl.h> +#include <unistd.h> + +#include <glib.h> +#include <dbus/dbus.h> +#include <gdbus.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/rfcomm.h> +#include <bluetooth/hci.h> +#include <bluetooth/hci_lib.h> +#include <bluetooth/sco.h> +#include <bluetooth/sdp.h> +#include <bluetooth/sdp_lib.h> + +#include "glib-helper.h" +#include "device.h" +#include "logging.h" +#include "error.h" +#include "btio.h" +#include "dbus-common.h" +#include "ofono-hfp.h" + +#define OFONO_BUS_NAME "org.ofono" +#define OFONO_PATH "/" +#define OFONO_MODEM_INTERFACE "org.ofono.Modem" +#define OFONO_VCMANAGER_INTERFACE "org.ofono.VoiceCallManager" + +#define HANDSFREE "hfp" +#define PROPERTY_CHANGED "PropertyChanged" + +#define MAX_OPEN_TRIES 5 +#define OPEN_WAIT 300 + +struct ofono_hfp { + enum ofono_hfp_state state; + int channel; + char *tty; /* gw->tty created from rfcomm */ + GIOChannel *rfcomm; /* remote AG requested rfcomm connection */ + int rfcomm_id; /* present N in /dev/rfcommN */ + int rfcomm_fd; + GIOChannel *sco; + ofono_hfp_stream_cb_t sco_start_cb; + void *sco_start_cb_data; + DBusMessage *connect_message; +}; + +static struct audio_device *active; /* single active device with oFono */ +static DBusConnection *connection; + +static int ofono_hfp_close(struct audio_device *device); +static int ofono_hfp_release_tty(struct audio_device *device); + +static gboolean ofono_sco_io_cb(GIOChannel *chan, GIOCondition cond, + struct audio_device *dev) +{ + struct ofono_hfp *gw = dev->ofono_hfp; + + if (cond & G_IO_NVAL) + return FALSE; + + if (cond & (G_IO_ERR | G_IO_HUP)) { + debug("sco connection is released"); + g_io_channel_shutdown(gw->sco, TRUE, NULL); + g_io_channel_unref(gw->sco); + gw->sco = NULL; + return FALSE; + } + + return TRUE; +} + +static void ofono_sco_connect_cb(GIOChannel *chan, GError *err, + gpointer user_data) +{ + struct audio_device *dev = (struct audio_device *) user_data; + struct ofono_hfp *gw = dev->ofono_hfp; + + debug("In ofono_sco_connect_cb()\n"); + + gw->sco = chan; + g_io_channel_ref(chan); + + if (err) { + error("ofono_sco_connect_cb(): %s", err->message); + ofono_hfp_close(dev); + return; + } + + if (gw->sco_start_cb) + gw->sco_start_cb(dev, gw->sco_start_cb_data); + + fcntl(g_io_channel_unix_get_fd(chan), F_SETFL, 0); + g_io_add_watch(gw->sco, G_IO_ERR | G_IO_HUP | G_IO_NVAL, + (GIOFunc) ofono_sco_io_cb, dev); + +} + +static void open_notify(int fd, int err, struct audio_device *dev) +{ + DBusMessage *reply = NULL; + struct ofono_hfp *gw = dev->ofono_hfp; + const char *result = "ok"; + + if (err) { + /* Max tries exceeded */ + ofono_hfp_close(dev); + + if (gw->connect_message) + reply = g_dbus_create_error(gw->connect_message, + ERROR_INTERFACE ".Failed", + strerror(err)); + } else { + if (gw->connect_message) + reply = g_dbus_create_reply(gw->connect_message, + DBUS_TYPE_STRING, &result, + DBUS_TYPE_INVALID); + + emit_property_changed(dev->conn, dev->path, + AUDIO_OFONO_HFP_INTERFACE, + "Device", DBUS_TYPE_STRING, + &gw->tty); + + gw->state = OFONO_HFP_STATE_CONNECTED; + active = dev; + } + + /* Reply to the requestor */ + if (gw->connect_message && reply) + g_dbus_send_message(dev->conn, reply); +} + +static gboolean open_continue(gpointer user_data) +{ + struct audio_device *dev = user_data; + struct ofono_hfp *gw = dev->ofono_hfp; + int fd; + static int ntries = MAX_OPEN_TRIES; + + fd = open(gw->tty, O_RDONLY | O_NOCTTY); + if (fd < 0) { + int err = errno; + error("Could not open %s: %s (%d)", + gw->tty, strerror(err), err); + if (!--ntries) { + /* Reporting error */ + open_notify(fd, err, dev); + ntries = MAX_OPEN_TRIES; + return FALSE; + } + return TRUE; + } + /* Connection succeeded */ + open_notify(fd, 0, dev); + gw->rfcomm_fd = fd; + return FALSE; +} + +static int port_open(struct audio_device *dev) +{ + int fd; + + fd = open(dev->ofono_hfp->tty, O_RDONLY | O_NOCTTY); + if (fd < 0) { + g_timeout_add(OPEN_WAIT, open_continue, dev); + return -EINPROGRESS; + } + + return fd; +} + +void rfcomm_connect_cb(GIOChannel *rfcomm, GError *err, + gpointer user_data) +{ + struct audio_device *dev = (struct audio_device *)user_data; + struct ofono_hfp *gw = dev->ofono_hfp; + struct rfcomm_dev_req req; + int sk, fd, id; + const char *err_msg; + char src[18], dst[18]; + + if (err) { + err_msg = err->message; + if (gw->sco_start_cb) + gw->sco_start_cb(NULL, gw->sco_start_cb_data); + goto fail; + } + + ba2str(&dev->src, src); + ba2str(&dev->dst, dst); + + /* make /dev/rfcomm serial port from rfcomm */ + memset(&req, 0, sizeof(req)); + req.dev_id = -1; + req.flags = (1 << RFCOMM_REUSE_DLC); + bacpy(&req.src, &dev->src); + bacpy(&req.dst, &dev->dst); + req.channel = gw->channel; + + sk = g_io_channel_unix_get_fd(rfcomm); + id = ioctl(sk, RFCOMMCREATEDEV, &req); + if (id < 0) { + err_msg = strerror(errno); + error("ioctl(RFCOMMCREATEDEV) failed: %s (%d)", + strerror(errno), errno); + g_io_channel_shutdown(rfcomm, TRUE, NULL); + g_io_channel_unref(rfcomm); + rfcomm = NULL; + goto fail; + } + + gw->rfcomm_id = id; + gw->tty = g_strdup_printf("/dev/rfcomm%d", id); + + g_io_channel_shutdown(rfcomm, TRUE, NULL); + + /* Addressing connect port */ + fd = port_open(dev); + if (fd < 0) + /* Open in progress: Wait the callback */ + return; + + open_notify(fd, 0, dev); + gw->rfcomm_fd = fd; + return; + +fail: + error("%s", err_msg); + if (gw->connect_message) { + error_common_reply(dev->conn, gw->connect_message, + ERROR_INTERFACE ".ConnectionAttemptFailed", + err_msg); + dbus_message_unref(gw->connect_message); + gw->connect_message = NULL; + } + return; +} + +static void get_record_cb(sdp_list_t *recs, int perr, gpointer user_data) +{ + struct audio_device *dev = user_data; + struct ofono_hfp *gw = dev->ofono_hfp; + int ch = -1; + sdp_list_t *protos, *classes; + uuid_t uuid; + ofono_hfp_stream_cb_t sco_cb; + GIOChannel *io; + GError *err = NULL; + char err_msg[256]; + gboolean conn = FALSE; + + if (perr < 0) { + sprintf(err_msg, "Unable to get service record: %s (%d)", + strerror(-perr), -perr); + goto fail; + } + + if (!recs || !recs->data) { + sprintf(err_msg, "No records found"); + goto fail; + } + + if (sdp_get_service_classes(recs->data, &classes) < 0) { + sprintf(err_msg, "Unable to get service classes from record"); + goto fail; + } + + if (sdp_get_access_protos(recs->data, &protos) < 0) { + sprintf(err_msg, "Unable to get access protocols from record"); + goto fail; + } + + memcpy(&uuid, classes->data, sizeof(uuid)); + sdp_list_free(classes, free); + + if (!sdp_uuid128_to_uuid(&uuid) || uuid.type != SDP_UUID16 || + uuid.value.uuid16 != HANDSFREE_AGW_SVCLASS_ID) { + sdp_list_free(protos, NULL); + sprintf(err_msg, "Invalid service record or not HFP"); + goto fail; + } + + ch = sdp_get_proto_port(protos, RFCOMM_UUID); + sdp_list_foreach(protos, (sdp_list_func_t) sdp_list_free, NULL); + sdp_list_free(protos, NULL); + if (ch <= 0) { + sprintf(err_msg, "Unable to extract RFCOMM channel" + "from service record"); + goto fail; + } + + gw->channel = ch; + + io = bt_io_connect(BT_IO_RFCOMM, rfcomm_connect_cb, dev, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, &dev->src, + BT_IO_OPT_DEST_BDADDR, &dev->dst, + BT_IO_OPT_CHANNEL, ch, + BT_IO_OPT_INVALID); + if (!io) { + sprintf(err_msg, "Unable to connect: %s", err->message); + if (err) + g_error_free(err); + ofono_hfp_close(dev); + goto fail; + } + + g_io_channel_unref(io); + return; + +fail: + error("%s", err_msg); + if (gw->connect_message) { + error_common_reply(dev->conn, gw->connect_message, + ERROR_INTERFACE ".ConnectionAttemptFailed", + err_msg); + dbus_message_unref(gw->connect_message); + gw->connect_message = NULL; + } + + emit_property_changed(dev->conn, dev->path, + AUDIO_OFONO_HFP_INTERFACE, + "Connected", DBUS_TYPE_BOOLEAN, &conn); + + sco_cb = dev->ofono_hfp->sco_start_cb; + if (sco_cb) + sco_cb(NULL, dev->ofono_hfp->sco_start_cb_data); + return; +} + +static int get_records(struct audio_device *device) +{ + uuid_t uuid; + + sdp_uuid16_create(&uuid, HANDSFREE_AGW_SVCLASS_ID); + return bt_search_service(&device->src, &device->dst, &uuid, + get_record_cb, device, NULL); +} + +static DBusMessage *ofono_connect(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct audio_device *au_dev = (struct audio_device *) data; + struct ofono_hfp *gw = au_dev->ofono_hfp; + + debug("at the begin of ofono_connect() %p", au_dev); + + if (gw->tty) + return g_dbus_create_error(msg, ERROR_INTERFACE + ".AlreadyConnected", + "Already Connected"); + + gw->connect_message = dbus_message_ref(msg); + if (get_records(au_dev) < 0) { + dbus_message_unref(gw->connect_message); + return g_dbus_create_error(msg, ERROR_INTERFACE + ".ConnectAttemptFailed", + "Connect Attempt Failed"); + } + + return NULL; +} + +static int ofono_hfp_release_tty(struct audio_device *device) +{ + struct ofono_hfp *gw = device->ofono_hfp; + struct rfcomm_dev_req req; + int rfcomm_ctl; + int err = 0; + + rfcomm_ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_RFCOMM); + if (rfcomm_ctl < 0) + return -errno; + + if (gw->rfcomm_fd) { + close(gw->rfcomm_fd); + gw->rfcomm_fd = -1; + } + + memset(&req, 0, sizeof(req)); + req.dev_id = gw->rfcomm_id; + + req.flags = (1 << RFCOMM_HANGUP_NOW); + + if (ioctl(rfcomm_ctl, RFCOMMRELEASEDEV, &req) < 0) { + err = errno; + error("Can't release device %s: %s (%d)", + gw->tty, strerror(err), err); + } + close(rfcomm_ctl); + gw->rfcomm_id = -1; + return 0; +} + +static int ofono_hfp_close(struct audio_device *device) +{ + struct ofono_hfp *gw = device->ofono_hfp; + gboolean value = FALSE; + + if (gw->tty) { + ofono_hfp_release_tty(device); + g_free(gw->tty); + gw->tty = NULL; + } + + if (gw->rfcomm) { + g_io_channel_shutdown(gw->rfcomm, TRUE, NULL); + g_io_channel_unref(gw->rfcomm); + gw->rfcomm = NULL; + } + + if (gw->sco) { + g_io_channel_shutdown(gw->sco, TRUE, NULL); + g_io_channel_unref(gw->sco); + gw->sco = NULL; + gw->sco_start_cb = NULL; + gw->sco_start_cb_data = NULL; + } + + gw->state = OFONO_HFP_STATE_DISCONNECTED; + + emit_property_changed(device->conn, device->path, + AUDIO_OFONO_HFP_INTERFACE, + "Connected", DBUS_TYPE_BOOLEAN, &value); + return 0; +} + +static DBusMessage *ofono_disconnect(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct audio_device *device = data; + struct ofono_hfp *gw = device->ofono_hfp; + DBusMessage *reply = NULL; + char gw_addr[18]; + + if (connection == NULL) + return NULL; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + if (!gw->tty && !gw->rfcomm) + return g_dbus_create_error(msg, ERROR_INTERFACE + ".NotConnected", + "Device not Connected"); + + ofono_hfp_close(device); + ba2str(&device->dst, gw_addr); + debug("Disconnected from %s, %s", gw_addr, device->path); + + return reply; +} + +static GDBusMethodTable ofono_hfp_methods[] = { + { "Connect", "", "", ofono_connect, G_DBUS_METHOD_FLAG_ASYNC }, + { "Disconnect", "", "", ofono_disconnect }, + { NULL, NULL, NULL, NULL } +}; + +static GDBusSignalTable ofono_hfp_signals[] = { + { "PropertyChanged", "sv" }, + { "CallStarted", "" }, + { "CallEnded", "" }, + { NULL, NULL } +}; + +static void handle_vcmanager_property_changed(DBusMessage *msg, + void *user_data, + const char *obj_path) +{ + DBusMessageIter iter, sub, array; + struct audio_device *dev = (struct audio_device *) user_data; + struct ofono_hfp *gw = dev->ofono_hfp; + const char *property, *vc_obj_path = NULL; + unsigned int call_num = 0; + + if (dev != active || gw->state != OFONO_HFP_STATE_CONNECTED) + return; + + dbus_message_iter_init(msg, &iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) { + error("Unexpected signature in vcmanager" + " PropertyChanged signal"); + return; + } + + dbus_message_iter_get_basic(&iter, &property); + + dbus_message_iter_next(&iter); + dbus_message_iter_recurse(&iter, &sub); + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_ARRAY) { + error("Unexpected signature in vcmanager" + " PropertyChanged signal"); + return; + } + dbus_message_iter_recurse(&sub, &array); + while (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_INVALID) { + dbus_message_iter_get_basic(&array, &vc_obj_path); + call_num++; + dbus_message_iter_next(&array); + } + + if (call_num == 1) { + g_dbus_emit_signal(dev->conn, dev->path, + AUDIO_OFONO_HFP_INTERFACE, + "CallStarted", DBUS_TYPE_INVALID); + } else if (call_num == 0) { + g_dbus_emit_signal(dev->conn, dev->path, + AUDIO_OFONO_HFP_INTERFACE, + "CallEnded", DBUS_TYPE_INVALID); + } +} + +static void handle_ofono_hfp_property(const char *property, + DBusMessageIter sub, void *user_data) +{ + struct audio_device *dev = (struct audio_device *) user_data; + struct ofono_hfp *gw = dev->ofono_hfp; + gboolean powered; + const char *calls; + + if (dev != active) + return; + + if (g_str_equal(property, "Powered")) { + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_BOOLEAN) { + error("Invalid arg type of Powered"); + return; + } + dbus_message_iter_get_basic(&sub, &powered); + + gw->state = powered; + if (powered) + gw->state = OFONO_HFP_STATE_CONNECTED; + else + ofono_hfp_close(dev); + } else if (g_str_equal(property, "PropertyChanged")) { + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING) { + error("Invalid arg type of Powered"); + return; + } + dbus_message_iter_get_basic(&sub, &calls); + + } +} + +static void handle_ofono_hfp_property_changed(DBusMessage *msg, + void *user_data) +{ + DBusMessageIter iter, sub; + const char *property; + + dbus_message_iter_init(msg, &iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) { + error("Unexpected signature in oFono PropertyChanged signal"); + return; + } + dbus_message_iter_get_basic(&iter, &property); + + dbus_message_iter_next(&iter); + dbus_message_iter_recurse(&iter, &sub); + + handle_ofono_hfp_property(property, sub, user_data); +} + +static DBusHandlerResult ofono_signal(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + const char *path; + if (dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_SIGNAL) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + path = dbus_message_get_path(msg); + + if (strstr(path, HANDSFREE)) { + if (dbus_message_is_signal(msg, + OFONO_MODEM_INTERFACE, PROPERTY_CHANGED)) { + handle_ofono_hfp_property_changed(msg, user_data); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } else if (dbus_message_is_signal(msg, + OFONO_VCMANAGER_INTERFACE, PROPERTY_CHANGED)) { + handle_vcmanager_property_changed(msg, user_data, path); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +struct ofono_hfp *ofono_hfp_init(struct audio_device *dev) +{ + struct ofono_hfp *gw; + char match_string[128]; + + gw = g_new0(struct ofono_hfp, 1); + + connection = dbus_connection_ref(dev->conn); + + if (!g_dbus_register_interface(dev->conn, dev->path, + AUDIO_OFONO_HFP_INTERFACE, + ofono_hfp_methods, ofono_hfp_signals, + NULL, dev, NULL)) + return NULL; + + if (!dbus_connection_add_filter(dev->conn, ofono_signal, + dev, NULL)) { + error("ofono_hfp: Can't add signal filter"); + } + + snprintf(match_string, sizeof(match_string), "type=signal,interface=%s", + OFONO_MODEM_INTERFACE); + dbus_bus_add_match(dev->conn, match_string, NULL); + + snprintf(match_string, sizeof(match_string), "type=signal,interface=%s", + OFONO_VCMANAGER_INTERFACE); + dbus_bus_add_match(dev->conn, match_string, NULL); + + return gw; +} + +void ofono_hfp_exit() +{ + dbus_connection_remove_filter(connection, ofono_signal, NULL); + dbus_connection_unref(connection); + connection = NULL; +} + +gboolean ofono_hfp_is_connected(struct audio_device *dev) +{ + return (dev && dev->ofono_hfp && + dev->ofono_hfp->state == OFONO_HFP_STATE_CONNECTED); +} + +int ofono_hfp_connect_sco(struct audio_device *dev, GIOChannel *io) +{ + struct ofono_hfp *gw = dev->ofono_hfp; + + if (gw->sco) + return -EISCONN; + + gw->sco = g_io_channel_ref(io); + + g_io_add_watch(gw->sco, G_IO_ERR | G_IO_HUP | G_IO_NVAL, + (GIOFunc) ofono_sco_io_cb, dev); + + return 0; +} + +int ofono_hfp_connect_rfcomm(struct audio_device *dev, + GIOChannel *chan, int ch) +{ + if (!chan) + return -EINVAL; + + g_io_channel_ref(chan); + dev->ofono_hfp->rfcomm = chan; + dev->ofono_hfp->channel = ch; + + return 0; +} + +void ofono_hfp_start_service(struct audio_device *dev) +{ + struct ofono_hfp *gw = dev->ofono_hfp; + GError *err = NULL; + + if (gw->rfcomm == NULL) + return; + + if (!bt_io_accept(gw->rfcomm, rfcomm_connect_cb, dev, NULL, + &err)) { + error("bt_io_accept: %s", err->message); + g_error_free(err); + } +} + +/* These are functions to be called from unix.c for audio system + * ifaces (alsa, gstreamer, etc.) */ +gboolean ofono_hfp_request_stream(struct audio_device *dev, + ofono_hfp_stream_cb_t cb, void *user_data) +{ + struct ofono_hfp *gw = dev->ofono_hfp; + GError *err = NULL; + GIOChannel *io; + + if (!gw->tty && !gw->rfcomm) { + gw->sco_start_cb = cb; + gw->sco_start_cb_data = user_data; + get_records(dev); + } else if (!gw->sco) { + char source[128], destination[128]; + + gw->sco_start_cb = cb; + gw->sco_start_cb_data = user_data; + ba2str(&dev->src, source); + ba2str(&dev->dst, destination); + io = bt_io_connect(BT_IO_SCO, ofono_sco_connect_cb, dev, + NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, &dev->src, + BT_IO_OPT_DEST_BDADDR, &dev->dst, + BT_IO_OPT_INVALID); + if (!io) { + error("%s", err->message); + g_error_free(err); + return FALSE; + } + } else { + if (cb) + cb(dev, user_data); + } + + return TRUE; +} + +int ofono_hfp_config_stream(struct audio_device *dev, + ofono_hfp_stream_cb_t sco_cb, void *user_data) +{ + struct ofono_hfp *gw = dev->ofono_hfp; + + if (!gw->rfcomm && !gw->tty) { + gw->sco_start_cb = sco_cb; + gw->sco_start_cb_data = user_data; + return get_records(dev); + } + + if (sco_cb) + sco_cb(dev, user_data); + + return 0; +} + +gboolean ofono_hfp_cancel_stream(struct audio_device *dev, unsigned int id) +{ + ofono_hfp_close(dev); + return TRUE; +} + +int ofono_hfp_get_sco_fd(struct audio_device *dev) +{ + struct ofono_hfp *gw = dev->ofono_hfp; + + if (!gw || !gw->sco) + return -1; + + return g_io_channel_unix_get_fd(gw->sco); +} + +void ofono_hfp_suspend_stream(struct audio_device *dev) +{ + struct ofono_hfp *gw = dev->ofono_hfp; + + if (!gw || !gw->sco) + return; + + g_io_channel_shutdown(gw->sco, TRUE, NULL); + g_io_channel_unref(gw->sco); + gw->sco = NULL; + gw->sco_start_cb = NULL; + gw->sco_start_cb_data = NULL; +} diff --git a/audio/ofono-hfp.h b/audio/ofono-hfp.h new file mode 100644 index 0000000..a8cb5df --- /dev/null +++ b/audio/ofono-hfp.h @@ -0,0 +1,50 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2009 Marcel Holtmann <marcel@holtmann.org> + * Copyright (C) 2009-2009 Zhenhua Zhang <zhenhua.zhang@intel.com> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#define AUDIO_OFONO_HFP_INTERFACE "org.bluez.oFonoHFP" + +enum ofono_hfp_state{ + OFONO_HFP_STATE_DISCONNECTED, + OFONO_HFP_STATE_CONNECTED +}; + +typedef void (*ofono_hfp_stream_cb_t) (struct audio_device *dev, + void *user_data); + +struct ofono_hfp *ofono_hfp_init(struct audio_device *device); +void ofono_hfp_exit(); +gboolean ofono_hfp_is_connected(struct audio_device *dev); +int ofono_hfp_connect_sco(struct audio_device *dev, GIOChannel *chan); +void rfcomm_connect_cb(GIOChannel *rfcomm, GError *err, gpointer user_data); +int ofono_hfp_connect_rfcomm(struct audio_device *dev, + GIOChannel *chan, int ch); +void ofono_hfp_start_service(struct audio_device *device); +gboolean ofono_hfp_request_stream(struct audio_device *dev, + ofono_hfp_stream_cb_t cb, void *user_data); +int ofono_hfp_config_stream(struct audio_device *dev, ofono_hfp_stream_cb_t cb, + void *user_data); +gboolean ofono_hfp_cancel_stream(struct audio_device *dev, unsigned int id); +int ofono_hfp_get_sco_fd(struct audio_device *dev); +void ofono_hfp_suspend_stream(struct audio_device *dev); diff --git a/audio/unix.c b/audio/unix.c index 6b43bd4..e4f4e4d 100644 --- a/audio/unix.c +++ b/audio/unix.c @@ -48,6 +48,7 @@ #include "headset.h" #include "sink.h" #include "gateway.h" +#include "ofono-hfp.h" #include "unix.h" #include "glib-helper.h" @@ -57,8 +58,9 @@ typedef enum { TYPE_NONE, TYPE_HEADSET, TYPE_GATEWAY, + TYPE_OFONO_HFP, TYPE_SINK, - TYPE_SOURCE + TYPE_SOURCE, } service_type_t; typedef void (*notify_cb_t) (struct audio_device *dev, void *data); @@ -196,6 +198,9 @@ static service_type_t select_service(struct audio_device *dev, const char *inter return TYPE_HEADSET; else if (!strcmp(interface, AUDIO_GATEWAY_INTERFACE) && dev->gateway) return TYPE_GATEWAY; + else if (!strcmp(interface, AUDIO_OFONO_HFP_INTERFACE) + && dev->ofono_hfp) + return TYPE_OFONO_HFP; return TYPE_NONE; } @@ -344,6 +349,32 @@ static void gateway_setup_complete(struct audio_device *dev, void *user_data) unix_ipc_sendmsg(client, &rsp->h); } +static void ofono_hfp_setup_complete(struct audio_device *dev, void *user_data) +{ + struct unix_client *client = user_data; + char buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_set_configuration_rsp *rsp = (void *) buf; + + if (!dev) { + unix_ipc_error(client, BT_SET_CONFIGURATION, EIO); + return; + } + + client->req_id = 0; + + memset(buf, 0, sizeof(buf)); + + rsp->h.type = BT_RESPONSE; + rsp->h.name = BT_SET_CONFIGURATION; + rsp->h.length = sizeof(*rsp); + + rsp->link_mtu = 48; + + client->data_fd = ofono_hfp_get_sco_fd(dev); + + unix_ipc_sendmsg(client, &rsp->h); +} + static void headset_resume_complete(struct audio_device *dev, void *user_data) { struct unix_client *client = user_data; @@ -418,6 +449,44 @@ static void gateway_resume_complete(struct audio_device *dev, void *user_data) client->req_id = 0; } +static void ofono_hfp_resume_complete(struct audio_device *dev, void *user_data) +{ + struct unix_client *client = user_data; + char buf[BT_SUGGESTED_BUFFER_SIZE]; + struct bt_start_stream_rsp *rsp = (void *) buf; + struct bt_new_stream_ind *ind = (void *) buf; + + if (!dev) + goto failed; + + memset(buf, 0, sizeof(buf)); + rsp->h.type = BT_RESPONSE; + rsp->h.name = BT_START_STREAM; + rsp->h.length = sizeof(*rsp); + + unix_ipc_sendmsg(client, &rsp->h); + + memset(buf, 0, sizeof(buf)); + ind->h.type = BT_INDICATION; + ind->h.name = BT_NEW_STREAM; + ind->h.length = sizeof(*ind); + + unix_ipc_sendmsg(client, &ind->h); + + client->data_fd = ofono_hfp_get_sco_fd(dev); + if (unix_sendmsg_fd(client->sock, client->data_fd) < 0) { + error("unix_sendmsg_fd: %s(%d)", strerror(errno), errno); + unix_ipc_error(client, BT_START_STREAM, EIO); + } + + client->req_id = 0; + return; + +failed: + error("ofono_hfp_resume_complete: resume failed"); + unix_ipc_error(client, BT_START_STREAM, EIO); +} + static void headset_suspend_complete(struct audio_device *dev, void *user_data) { struct unix_client *client = user_data; @@ -870,6 +939,7 @@ static void start_discovery(struct audio_device *dev, struct unix_client *client case TYPE_HEADSET: case TYPE_GATEWAY: + case TYPE_OFONO_HFP: headset_discovery_complete(dev, client); break; @@ -969,8 +1039,9 @@ static void start_open(struct audio_device *dev, struct unix_client *client) } break; - case TYPE_GATEWAY: - break; + case TYPE_GATEWAY: + case TYPE_OFONO_HFP: + break; default: error("No known services for device"); goto failed; @@ -1038,7 +1109,14 @@ static void start_config(struct audio_device *dev, struct unix_client *client) } else id = 0; break; - + case TYPE_OFONO_HFP: + if (ofono_hfp_config_stream(dev, ofono_hfp_setup_complete, + client) >= 0) { + client->cancel = ofono_hfp_cancel_stream; + id = 1; + } else + id = 0; + break; default: error("No known services for device"); goto failed; @@ -1111,6 +1189,15 @@ static void start_resume(struct audio_device *dev, struct unix_client *client) client->cancel = gateway_cancel_stream; break; + case TYPE_OFONO_HFP: + if (ofono_hfp_request_stream(dev, ofono_hfp_resume_complete, + client)) + id = 1; + else + id = 0; + client->cancel = ofono_hfp_cancel_stream; + break; + default: error("No known services for device"); goto failed; @@ -1185,6 +1272,13 @@ static void start_suspend(struct audio_device *dev, struct unix_client *client) id = 1; break; + case TYPE_OFONO_HFP: + ofono_hfp_suspend_stream(dev); + client->cancel = ofono_hfp_cancel_stream; + headset_suspend_complete(dev, client); + id = 1; + break; + default: error("No known services for device"); goto failed; @@ -1241,7 +1335,8 @@ static void start_close(struct audio_device *dev, struct unix_client *client, } break; case TYPE_GATEWAY: - break; + case TYPE_OFONO_HFP: + break; case TYPE_SOURCE: case TYPE_SINK: a2dp = &client->d.a2dp; @@ -1317,6 +1412,13 @@ static void handle_getcapabilities_req(struct unix_client *client, interface = NULL; dev = manager_find_device(req->object, &src, &dst, interface, TRUE); + + if (!dev && (req->transport == BT_CAPABILITIES_TRANSPORT_SCO)) { + interface = g_strdup(AUDIO_OFONO_HFP_INTERFACE); + dev = manager_find_device(req->object, &src, &dst, + interface, TRUE); + } + if (!dev && (req->flags & BT_FLAG_AUTOCONNECT)) dev = manager_find_device(req->object, &src, &dst, interface, FALSE); @@ -1353,7 +1455,8 @@ static int handle_sco_open(struct unix_client *client, struct bt_open_req *req) if (!client->interface) client->interface = g_strdup(AUDIO_HEADSET_INTERFACE); else if (!g_str_equal(client->interface, AUDIO_HEADSET_INTERFACE) && - !g_str_equal(client->interface, AUDIO_GATEWAY_INTERFACE)) + !g_str_equal(client->interface, AUDIO_GATEWAY_INTERFACE) && + !g_str_equal(client->interface, AUDIO_OFONO_HFP_INTERFACE)) return -EIO; debug("open sco - object=%s source=%s destination=%s lock=%s%s", @@ -1447,10 +1550,13 @@ static int handle_sco_transport(struct unix_client *client, client->interface = g_strdup(AUDIO_HEADSET_INTERFACE); else if (dev->gateway) client->interface = g_strdup(AUDIO_GATEWAY_INTERFACE); + else if (dev->ofono_hfp) + client->interface = g_strdup(AUDIO_OFONO_HFP_INTERFACE); else return -EIO; } else if (!g_str_equal(client->interface, AUDIO_HEADSET_INTERFACE) && - !g_str_equal(client->interface, AUDIO_GATEWAY_INTERFACE)) + !g_str_equal(client->interface, AUDIO_GATEWAY_INTERFACE) && + !g_str_equal(client->interface, AUDIO_OFONO_HFP_INTERFACE)) return -EIO; return 0; diff --git a/doc/audio-api.txt b/doc/audio-api.txt index 1f09cd5..5c4182c 100644 --- a/doc/audio-api.txt +++ b/doc/audio-api.txt @@ -456,3 +456,40 @@ properties boolean Connected [readonly] uint16 MicrophoneGain [readonly] The speaker gain when available. + + +oFonoHFP hierarchy +======================== + +Service org.bluez +Interface org.bluez.oFonoHFP +Object path [variable prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX + +This interface is available for remote devices which can function in the Audio +Gateway role of the HFP profiles. It provides connection service for oFono to +create rfcomm connection with remote devices. + +Methods void Connect() + + Connect to the AG service on the remote device and + turn RFCOMM connection into TTY device. Send TTY + device name to oFono through D-Bus. + + void Disconnect() + + Disconnect from the AG service on the remote device. + +Signals PropertyChanged(string name, variant value) + + This signal indicates a changed value of the given + property. + +properties boolean Connected [readonly] + + Indicates if there is an active connection to the + AG service on the remote device. + + string Device[readonly] + + The TTY device name created from RFCOMM connection. + -- 1.6.2.5 ^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH] oFonoHFP profile to enable audio routing in BlueZ 2009-11-13 1:01 ` Zhang, Zhenhua @ 2009-11-13 13:36 ` Luiz Augusto von Dentz 2009-11-13 13:54 ` Gustavo F. Padovan 0 siblings, 1 reply; 5+ messages in thread From: Luiz Augusto von Dentz @ 2009-11-13 13:36 UTC (permalink / raw) To: Zhang, Zhenhua; +Cc: linux-bluetooth@vger.kernel.org, Gustavo F. Padovan Hi, 2009/11/12 Zhang, Zhenhua <zhenhua.zhang@intel.com>: > Hi, > > Ops, forgot to add ofono-hfp.c into BlueZ patch. So I add them and > resend 0001 patch. > > linux-bluetooth-owner@vger.kernel.org wrote: >> Hi, >> >> These three patches are created to enable audio routing for Handsfree >> Profile. It was created during the voicecall driver implementation in >> oFono so it does not use the new hfp-api yet. You can take it >> as reference only since it will not be commited into trunk. >> >> Basically, a new profile oFonoHFP was added to create RFCOMM >> connection and turn IO into TTY device for oFono. It sends TTY >> device string to oFono through D-Bus. Meanwhile, it listens >> oFono property changes to emit CallStarted and CallEnded >> signals. PulseAudio could listen these signals and redirect >> audio source/sink to use bluetooth one. >> >> If you are interested at it, you may apply 0001..0003 into >> BlueZ, oFono and PA respectively. Last commit SHA-1 is in patch note. >> >> And you need to enable oFonoHFP by modify audio.conf: Disable=Gateway >> Enable=oFonoHFP, Headset >> >> In ofono modem.conf, specify device address like: >> [hfp] >> Driver=hfp >> Address=00:22:A9:8C:AF:34 >> >> And power on modem by: >> dbus-send --system --print-reply --dest=org.ofono /hfp0 >> org.ofono.Modem.SetProperty string:Powered variant:boolean:true >> >> The PulseAudio will load module-bluetooth-discover >> automatically. If not, please load it manually. This module >> listens BlueZ signal and load module-bluetooth-device >> automatically. Unfortunately, you need to load module-loopback >> manually to redirect bluez source/sink to alsa, e.g.: >> load-module module-loopback source="bluez_source.XX..XX" >> sink="alsa_output.0.analog-stereo" >> load-module module-loopback source="alsa_input.0.analog-stereo" >> sink="bluez_sink.XX..XX" >> >> Feel free to let me know if any problems. Thanks. What happened with the original idea from Denis, I don't think oFonoHFP is a good name to start with and it should be a generic interface where any telephony agent could register. -- Luiz Augusto von Dentz Engenheiro de Computação ^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH] oFonoHFP profile to enable audio routing in BlueZ 2009-11-13 13:36 ` Luiz Augusto von Dentz @ 2009-11-13 13:54 ` Gustavo F. Padovan 0 siblings, 0 replies; 5+ messages in thread From: Gustavo F. Padovan @ 2009-11-13 13:54 UTC (permalink / raw) To: Luiz Augusto von Dentz; +Cc: Zhang, Zhenhua, linux-bluetooth@vger.kernel.org Hi Luiz, On Fri, Nov 13, 2009 at 11:36 AM, Luiz Augusto von Dentz <luiz.dentz@gmail.com> wrote: > Hi, > > 2009/11/12 Zhang, Zhenhua <zhenhua.zhang@intel.com>: >> Hi, >> >> Ops, forgot to add ofono-hfp.c into BlueZ patch. So I add them and >> resend 0001 patch. >> >> linux-bluetooth-owner@vger.kernel.org wrote: >>> Hi, >>> >>> These three patches are created to enable audio routing for Handsfree >>> Profile. It was created during the voicecall driver implementation in >>> oFono so it does not use the new hfp-api yet. You can take it >>> as reference only since it will not be commited into trunk. >>> >>> Basically, a new profile oFonoHFP was added to create RFCOMM >>> connection and turn IO into TTY device for oFono. It sends TTY >>> device string to oFono through D-Bus. Meanwhile, it listens >>> oFono property changes to emit CallStarted and CallEnded >>> signals. PulseAudio could listen these signals and redirect >>> audio source/sink to use bluetooth one. >>> >>> If you are interested at it, you may apply 0001..0003 into >>> BlueZ, oFono and PA respectively. Last commit SHA-1 is in patch note. >>> >>> And you need to enable oFonoHFP by modify audio.conf: Disable=Gateway >>> Enable=oFonoHFP, Headset >>> >>> In ofono modem.conf, specify device address like: >>> [hfp] >>> Driver=hfp >>> Address=00:22:A9:8C:AF:34 >>> >>> And power on modem by: >>> dbus-send --system --print-reply --dest=org.ofono /hfp0 >>> org.ofono.Modem.SetProperty string:Powered variant:boolean:true >>> >>> The PulseAudio will load module-bluetooth-discover >>> automatically. If not, please load it manually. This module >>> listens BlueZ signal and load module-bluetooth-device >>> automatically. Unfortunately, you need to load module-loopback >>> manually to redirect bluez source/sink to alsa, e.g.: >>> load-module module-loopback source="bluez_source.XX..XX" >>> sink="alsa_output.0.analog-stereo" >>> load-module module-loopback source="alsa_input.0.analog-stereo" >>> sink="bluez_sink.XX..XX" >>> >>> Feel free to let me know if any problems. Thanks. > > What happened with the original idea from Denis, I don't think > oFonoHFP is a good name to start with and it should be a generic > interface where any telephony agent could register. Zhenhua did these patches before the new API spec. I'll help Zhenhua to move this code to the new API. It's here just for test. > > > -- > Luiz Augusto von Dentz > Engenheiro de Computação > -- Gustavo F. Padovan http://padovan.org ^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2009-11-13 13:54 UTC | newest] Thread overview: 5+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2009-11-12 7:50 [PATCH] oFonoHFP profile to enable audio routing in BlueZ Zhang, Zhenhua 2009-11-12 16:48 ` Gustavo F. Padovan 2009-11-13 1:01 ` Zhang, Zhenhua 2009-11-13 13:36 ` Luiz Augusto von Dentz 2009-11-13 13:54 ` Gustavo F. Padovan
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox