📄 headset.c
字号:
/* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2006-2007 Nokia Corporation * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> * * * 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 <stdio.h>#include <errno.h>#include <fcntl.h>#include <unistd.h>#include <stdlib.h>#include <stdarg.h>#include <signal.h>#include <string.h>#include <getopt.h>#include <sys/ioctl.h>#include <sys/socket.h>#include <assert.h>#include <bluetooth/bluetooth.h>#include <bluetooth/hci.h>#include <bluetooth/hci_lib.h>#include <bluetooth/sco.h>#include <bluetooth/rfcomm.h>#include <bluetooth/sdp.h>#include <bluetooth/sdp_lib.h>#include <glib.h>#include <dbus/dbus.h>#include <gdbus.h>#include "logging.h"#include "device.h"#include "manager.h"#include "error.h"#include "telephony.h"#include "headset.h"#include "glib-helper.h"#include "dbus-common.h"#include "../src/adapter.h"#include "../src/device.h"#define DC_TIMEOUT 3#define RING_INTERVAL 3#define BUF_SIZE 1024#define HEADSET_GAIN_SPEAKER 'S'#define HEADSET_GAIN_MICROPHONE 'M'static struct { gboolean telephony_ready; /* Telephony plugin initialized */ uint32_t features; /* HFP AG features */ const struct indicator *indicators; /* Available HFP indicators */ int er_mode; /* Event reporting mode */ int er_ind; /* Event reporting for indicators */ int rh; /* Response and Hold state */ char *number; /* Incoming phone number */ int number_type; /* Incoming number type */ guint ring_timer; /* For incoming call indication */ const char *chld; /* Response to AT+CHLD=? */} ag = { .telephony_ready = FALSE, .features = 0, .er_mode = 3, .er_ind = 0, .rh = -1, .number = NULL, .number_type = 0, .ring_timer = 0,};static gboolean sco_hci = TRUE;static GSList *active_devices = NULL;static char *str_state[] = { "HEADSET_STATE_DISCONNECTED", "HEADSET_STATE_CONNECT_IN_PROGRESS", "HEADSET_STATE_CONNECTED", "HEADSET_STATE_PLAY_IN_PROGRESS", "HEADSET_STATE_PLAYING",};struct connect_cb { unsigned int id; headset_stream_cb_t cb; void *cb_data;};struct pending_connect { DBusMessage *msg; DBusPendingCall *call; GIOChannel *io; int err; headset_state_t target_state; GSList *callbacks;};struct headset { uint32_t hsp_handle; uint32_t hfp_handle; int rfcomm_ch; GIOChannel *rfcomm; GIOChannel *tmp_rfcomm; GIOChannel *sco; guint sco_id; gboolean auto_dc; guint dc_timer; char buf[BUF_SIZE]; int data_start; int data_length; gboolean hfp_active; gboolean search_hfp; gboolean cli_active; gboolean cme_enabled; gboolean cwa_enabled; gboolean pending_ring; headset_state_t state; struct pending_connect *pending; int sp_gain; int mic_gain; unsigned int hf_features; headset_lock_t lock;};struct event { const char *cmd; int (*callback) (struct audio_device *device, const char *buf);};static inline DBusMessage *invalid_args(DBusMessage *msg){ return g_dbus_create_error(msg, ERROR_INTERFACE ".InvalidArguments", "Invalid arguments in method call");}static DBusHandlerResult error_not_supported(DBusConnection *conn, DBusMessage *msg){ return error_common_reply(conn, msg, ERROR_INTERFACE ".NotSupported", "Not supported");}static DBusHandlerResult error_connection_attempt_failed(DBusConnection *conn, DBusMessage *msg, int err){ return error_common_reply(conn, msg, ERROR_INTERFACE ".ConnectionAttemptFailed", err > 0 ? strerror(err) : "Connection attempt failed");}static int rfcomm_connect(struct audio_device *device, headset_stream_cb_t cb, void *user_data, unsigned int *cb_id);static int get_records(struct audio_device *device, headset_stream_cb_t cb, void *user_data, unsigned int *cb_id);static void print_ag_features(uint32_t features){ GString *gstr; char *str; if (features == 0) { debug("HFP AG features: (none)"); return; } gstr = g_string_new("HFP AG features: "); if (features & AG_FEATURE_THREE_WAY_CALLING) g_string_append(gstr, "\"Three-way calling\" "); if (features & AG_FEATURE_EC_ANDOR_NR) g_string_append(gstr, "\"EC and/or NR function\" "); if (features & AG_FEATURE_VOICE_RECOGNITION) g_string_append(gstr, "\"Voice recognition function\" "); if (features & AG_FEATURE_INBAND_RINGTONE) g_string_append(gstr, "\"In-band ring tone capability\" "); if (features & AG_FEATURE_ATTACH_NUMBER_TO_VOICETAG) g_string_append(gstr, "\"Attach a number to a voice tag\" "); if (features & AG_FEATURE_REJECT_A_CALL) g_string_append(gstr, "\"Ability to reject a call\" "); if (features & AG_FEATURE_ENHANCED_CALL_STATUS) g_string_append(gstr, "\"Enhanced call status\" "); if (features & AG_FEATURE_ENHANCED_CALL_CONTROL) g_string_append(gstr, "\"Enhanced call control\" "); if (features & AG_FEATURE_EXTENDED_ERROR_RESULT_CODES) g_string_append(gstr, "\"Extended Error Result Codes\" "); str = g_string_free(gstr, FALSE); debug("%s", str); g_free(str);}static void print_hf_features(uint32_t features){ GString *gstr; char *str; if (features == 0) { debug("HFP HF features: (none)"); return; } gstr = g_string_new("HFP HF features: "); if (features & HF_FEATURE_EC_ANDOR_NR) g_string_append(gstr, "\"EC and/or NR function\" "); if (features & HF_FEATURE_CALL_WAITING_AND_3WAY) g_string_append(gstr, "\"Call waiting and 3-way calling\" "); if (features & HF_FEATURE_CLI_PRESENTATION) g_string_append(gstr, "\"CLI presentation capability\" "); if (features & HF_FEATURE_VOICE_RECOGNITION) g_string_append(gstr, "\"Voice recognition activation\" "); if (features & HF_FEATURE_REMOTE_VOLUME_CONTROL) g_string_append(gstr, "\"Remote volume control\" "); if (features & HF_FEATURE_ENHANCED_CALL_STATUS) g_string_append(gstr, "\"Enhanced call status\" "); if (features & HF_FEATURE_ENHANCED_CALL_CONTROL) g_string_append(gstr, "\"Enhanced call control\" "); str = g_string_free(gstr, FALSE); debug("%s", str); g_free(str);}static int headset_send_valist(struct headset *hs, char *format, va_list ap){ char rsp[BUF_SIZE]; ssize_t total_written, written, count; int fd; count = vsnprintf(rsp, sizeof(rsp), format, ap); if (count < 0) return -EINVAL; if (!hs->rfcomm) { error("headset_send: the headset is not connected"); return -EIO; } written = total_written = 0; fd = g_io_channel_unix_get_fd(hs->rfcomm); while (total_written < count) { written = write(fd, rsp + total_written, count - total_written); if (written < 0) return -errno; total_written += written; } return 0;}static int headset_send(struct headset *hs, char *format, ...){ va_list ap; int ret; va_start(ap, format); ret = headset_send_valist(hs, format, ap); va_end(ap); return ret;}static int supported_features(struct audio_device *device, const char *buf){ struct headset *hs = device->headset; int err; if (strlen(buf) < 9) return -EINVAL; hs->hf_features = strtoul(&buf[8], NULL, 10); print_hf_features(hs->hf_features); err = headset_send(hs, "\r\n+BRSF: %u\r\n", ag.features); if (err < 0) return err; return headset_send(hs, "\r\nOK\r\n");}static char *indicator_ranges(const struct indicator *indicators){ int i; GString *gstr; gstr = g_string_new("\r\n+CIND: "); for (i = 0; indicators[i].desc != NULL; i++) { if (i == 0) g_string_append_printf(gstr, "(\"%s\",(%s))", indicators[i].desc, indicators[i].range); else g_string_append_printf(gstr, ",(\"%s\",(%s))", indicators[i].desc, indicators[i].range); } g_string_append(gstr, "\r\n"); return g_string_free(gstr, FALSE);}static char *indicator_values(const struct indicator *indicators){ int i; GString *gstr; gstr = g_string_new("\r\n+CIND: "); for (i = 0; indicators[i].desc != NULL; i++) { if (i == 0) g_string_append_printf(gstr, "%d", indicators[i].val); else g_string_append_printf(gstr, ",%d", indicators[i].val); } g_string_append(gstr, "\r\n"); return g_string_free(gstr, FALSE);}static int report_indicators(struct audio_device *device, const char *buf){ struct headset *hs = device->headset; int err; char *str; if (strlen(buf) < 8) return -EINVAL; if (buf[7] == '=') str = indicator_ranges(ag.indicators); else str = indicator_values(ag.indicators); err = headset_send(hs, str); g_free(str); if (err < 0) return err; return headset_send(hs, "\r\nOK\r\n");}static void pending_connect_complete(struct connect_cb *cb, struct audio_device *dev){ struct headset *hs = dev->headset; if (hs->pending->err) cb->cb(NULL, cb->cb_data); else cb->cb(dev, cb->cb_data);}static void pending_connect_finalize(struct audio_device *dev){ struct headset *hs = dev->headset; struct pending_connect *p = hs->pending; g_slist_foreach(p->callbacks, (GFunc) pending_connect_complete, dev); g_slist_foreach(p->callbacks, (GFunc) g_free, NULL); g_slist_free(p->callbacks); if (p->io) { g_io_channel_close(p->io); g_io_channel_unref(p->io); } if (p->msg) dbus_message_unref(p->msg); if (p->call) { dbus_pending_call_cancel(p->call); dbus_pending_call_unref(p->call); } g_free(p); hs->pending = NULL;}static void pending_connect_init(struct headset *hs, headset_state_t target_state){ if (hs->pending) { if (hs->pending->target_state < target_state) hs->pending->target_state = target_state; return; } hs->pending = g_new0(struct pending_connect, 1); hs->pending->target_state = target_state;}static unsigned int connect_cb_new(struct headset *hs, headset_state_t target_state, headset_stream_cb_t func, void *user_data){ struct connect_cb *cb; unsigned int free_cb_id = 1; pending_connect_init(hs, target_state); cb = g_new(struct connect_cb, 1); cb->cb = func; cb->cb_data = user_data; cb->id = free_cb_id++; hs->pending->callbacks = g_slist_append(hs->pending->callbacks, cb); return cb->id;}static void send_foreach_headset(GSList *devices, int (*cmp) (struct headset *hs), char *format, ...){ GSList *l; va_list ap; for (l = devices; l != NULL; l = l->next) { struct audio_device *device = l->data; struct headset *hs = device->headset; int ret; assert(hs != NULL); if (cmp && cmp(hs) != 0) continue; va_start(ap, format); ret = headset_send_valist(hs, format, ap); if (ret < 0) error("Failed to send to headset: %s (%d)", strerror(-ret), -ret); va_end(ap); }}static int cli_cmp(struct headset *hs){ if (!hs->hfp_active) return -1; if (hs->cli_active) return 0; else return -1;}static gboolean ring_timer_cb(gpointer data){ send_foreach_headset(active_devices, NULL, "\r\nRING\r\n"); if (ag.number) send_foreach_headset(active_devices, cli_cmp, "\r\n+CLIP: \"%s\",%d\r\n", ag.number, ag.number_type); return TRUE;}static void sco_connect_cb(GIOChannel *chan, int err, const bdaddr_t *src, const bdaddr_t *dst, gpointer user_data){ int sk; struct audio_device *dev = user_data; struct headset *hs = dev->headset; struct pending_connect *p = hs->pending; if (err < 0) { error("connect(): %s (%d)", strerror(-err), -err); if (p->msg) error_connection_attempt_failed(dev->conn, p->msg, p->err); pending_connect_finalize(dev); if (hs->rfcomm) headset_set_state(dev, HEADSET_STATE_CONNECTED); else headset_set_state(dev, HEADSET_STATE_DISCONNECTED); return; } debug("SCO socket opened for headset %s", dev->path); sk = g_io_channel_unix_get_fd(chan); debug("SCO fd=%d", sk); hs->sco = chan; p->io = NULL; if (p->msg) { DBusMessage *reply = dbus_message_new_method_return(p->msg); g_dbus_send_message(dev->conn, reply); } pending_connect_finalize(dev); fcntl(sk, F_SETFL, 0); headset_set_state(dev, HEADSET_STATE_PLAYING); if (hs->pending_ring) { ring_timer_cb(NULL); ag.ring_timer = g_timeout_add_seconds(RING_INTERVAL, ring_timer_cb, NULL); hs->pending_ring = FALSE; }}static int sco_connect(struct audio_device *dev, headset_stream_cb_t cb, void *user_data, unsigned int *cb_id){ struct headset *hs = dev->headset; int err; if (hs->state != HEADSET_STATE_CONNECTED) return -EINVAL; err = bt_sco_connect(&dev->src, &dev->dst, sco_connect_cb, dev); if (err < 0) { error("connect: %s (%d)", strerror(-err), -err); return err; } headset_set_state(dev, HEADSET_STATE_PLAY_IN_PROGRESS); pending_connect_init(hs, HEADSET_STATE_PLAYING); if (cb) { unsigned int id = connect_cb_new(hs, HEADSET_STATE_PLAYING, cb, user_data); if (cb_id) *cb_id = id; } return 0;}static int hfp_cmp(struct headset *hs){ if (hs->hfp_active) return 0; else return -1;}static void hfp_slc_complete(struct audio_device *dev){ struct headset *hs = dev->headset; struct pending_connect *p = hs->pending; debug("HFP Service Level Connection established"); headset_set_state(dev, HEADSET_STATE_CONNECTED); if (p == NULL) return; if (p->target_state == HEADSET_STATE_CONNECTED) { if (p->msg) { DBusMessage *reply = dbus_message_new_method_return(p->msg); g_dbus_send_message(dev->conn, reply); } pending_connect_finalize(dev); return; } p->err = sco_connect(dev, NULL, NULL, NULL); if (p->err < 0) { if (p->msg) error_connection_attempt_failed(dev->conn, p->msg, p->err); pending_connect_finalize(dev); }}static int telephony_generic_rsp(struct audio_device *device, cme_error_t err){ struct headset *hs = device->headset; if (err != CME_ERROR_NONE) { if (hs->cme_enabled) return headset_send(hs, "\r\n+CME ERROR: %d\r\n", err); else return headset_send(hs, "\r\nERROR\r\n"); } return headset_send(hs, "\r\nOK\r\n");}int telephony_event_reporting_rsp(void *telephony_device, cme_error_t err){ struct audio_device *device = telephony_device; struct headset *hs = device->headset; int ret; if (err != CME_ERROR_NONE) return telephony_generic_rsp(telephony_device, err); ret = headset_send(hs, "\r\nOK\r\n"); if (ret < 0) return ret; if (hs->state != HEADSET_STATE_CONNECT_IN_PROGRESS) return 0; if (ag.features & AG_FEATURE_THREE_WAY_CALLING) return 0; hfp_slc_complete(device);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -