📄 headset.c
字号:
/* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2004-2007 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 <signal.h>#include <string.h>#include <getopt.h>#include <sys/ioctl.h>#include <sys/socket.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 "dbus.h"#include "dbus-helper.h"#include "logging.h"#include "device.h"#include "manager.h"#include "error.h"#include "headset.h"#define RING_INTERVAL 3000#define BUF_SIZE 1024#define HEADSET_GAIN_SPEAKER 'S'#define HEADSET_GAIN_MICROPHONE 'M'static char *str_state[] = {"DISCONNECTED", "CONNECTING", "CONNECTED", "STREAM_STARTING", "STREAMING"};struct pending_connect { DBusMessage *msg; DBusPendingCall *call; GIOChannel *io; guint io_id; int sock; int err; unsigned int id; headset_stream_cb_t cb; void *cb_data;};struct headset { uint32_t hsp_handle; uint32_t hfp_handle; int rfcomm_ch; GIOChannel *rfcomm; GIOChannel *sco; guint sco_id; guint ring_timer; char buf[BUF_SIZE]; int data_start; int data_length; headset_type_t type; headset_state_t state; GSList *pending; int sp_gain; int mic_gain; headset_lock_t lock;};static int rfcomm_connect(struct device *device, struct pending_connect *c);static void pending_connect_free(struct pending_connect *c){ if (c->io) { g_io_channel_close(c->io); g_io_channel_unref(c->io); } if (c->msg) dbus_message_unref(c->msg); if (c->call) { dbus_pending_call_cancel(c->call); dbus_pending_call_unref(c->call); } g_free(c);}static void hs_signal_gain_setting(struct device *device, const char *buf){ const char *name; dbus_uint16_t gain; if (strlen(buf) < 6) { error("Too short string for Gain setting"); return; } gain = (dbus_uint16_t) strtol(&buf[5], NULL, 10); if (gain > 15) { error("Invalid gain value received: %u", gain); return; } switch (buf[3]) { case HEADSET_GAIN_SPEAKER: if (device->headset->sp_gain == gain) return; name = "SpeakerGainChanged"; device->headset->sp_gain = gain; break; case HEADSET_GAIN_MICROPHONE: if (device->headset->mic_gain == gain) return; name = "MicrophoneGainChanged"; device->headset->mic_gain = gain; break; default: error("Unknown gain setting"); return; } dbus_connection_emit_signal(device->conn, device->path, AUDIO_HEADSET_INTERFACE, name, DBUS_TYPE_UINT16, &gain, DBUS_TYPE_INVALID);}static headset_event_t parse_headset_event(const char *buf, char *rsp, int rsp_len){ printf("Received: %s\n", buf); /* Return an error if this is not a proper AT command */ if (strncmp(buf, "AT", 2)) { snprintf(rsp, rsp_len, "\r\nERROR\r\n"); return HEADSET_EVENT_INVALID; } buf += 2; snprintf(rsp, rsp_len, "\r\nOK\r\n"); if (!strncmp(buf, "+CKPD", 5)) return HEADSET_EVENT_KEYPRESS; else if (!strncmp(buf, "+VG", 3)) return HEADSET_EVENT_GAIN; else return HEADSET_EVENT_UNKNOWN;}static void close_sco(struct device *device){ struct headset *hs = device->headset; if (hs->sco) { g_source_remove(hs->sco_id); hs->sco_id = 0; g_io_channel_close(hs->sco); g_io_channel_unref(hs->sco); hs->sco = NULL; }}static gboolean rfcomm_io_cb(GIOChannel *chan, GIOCondition cond, struct device *device){ struct headset *hs; unsigned char buf[BUF_SIZE]; char *cr, rsp[BUF_SIZE]; gsize bytes_read = 0; gsize free_space, count, bytes_written, total_bytes_written; GIOError err; off_t cmd_len; if (cond & G_IO_NVAL) return FALSE; hs = device->headset; if (cond & (G_IO_ERR | G_IO_HUP)) goto failed; err = g_io_channel_read(chan, (gchar *) buf, sizeof(buf) - 1, &bytes_read); if (err != G_IO_ERROR_NONE) return TRUE; free_space = sizeof(hs->buf) - hs->data_start - hs->data_length - 1; if (free_space < bytes_read) { /* Very likely that the HS is sending us garbage so * just ignore the data and disconnect */ error("Too much data to fit incomming buffer"); goto failed; } memcpy(&hs->buf[hs->data_start], buf, bytes_read); hs->data_length += bytes_read; /* Make sure the data is null terminated so we can use string * functions */ hs->buf[hs->data_start + hs->data_length] = '\0'; cr = strchr(&hs->buf[hs->data_start], '\r'); if (!cr) return TRUE; cmd_len = 1 + (off_t) cr - (off_t) &hs->buf[hs->data_start]; *cr = '\0'; memset(rsp, 0, sizeof(rsp)); switch (parse_headset_event(&hs->buf[hs->data_start], rsp, sizeof(rsp))) { case HEADSET_EVENT_GAIN: hs_signal_gain_setting(device, &hs->buf[hs->data_start] + 2); break; case HEADSET_EVENT_KEYPRESS: if (hs->ring_timer) { g_source_remove(hs->ring_timer); hs->ring_timer = 0; } dbus_connection_emit_signal(device->conn, device->path, AUDIO_HEADSET_INTERFACE, "AnswerRequested", DBUS_TYPE_INVALID); break; case HEADSET_EVENT_INVALID: case HEADSET_EVENT_UNKNOWN: default: debug("Unknown headset event"); break; } count = strlen(rsp); total_bytes_written = bytes_written = 0; err = G_IO_ERROR_NONE; while (err == G_IO_ERROR_NONE && total_bytes_written < count) { err = g_io_channel_write(hs->rfcomm, rsp + total_bytes_written, count - total_bytes_written, &bytes_written); if (err != G_IO_ERROR_NONE) error("Error while writting to the audio output channel"); total_bytes_written += bytes_written; }; hs->data_start += cmd_len; hs->data_length -= cmd_len; if (!hs->data_length) hs->data_start = 0; return TRUE;failed: headset_set_state(device, HEADSET_STATE_DISCONNECTED); return FALSE;}static gboolean sco_cb(GIOChannel *chan, GIOCondition cond, struct device *device){ struct headset *hs; if (cond & G_IO_NVAL) return FALSE; hs = device->headset; error("Audio connection got disconnected"); headset_set_state(device, HEADSET_STATE_CONNECTED); return FALSE;}static GIOError headset_send(struct headset *hs, const char *str){ GIOError err; gsize total_written, written, count; if (hs->state < HEADSET_STATE_CONNECTED || !hs->rfcomm) { error("headset_send: the headset is not connected"); return G_IO_ERROR_UNKNOWN; } count = strlen(str); written = total_written = 0; while (total_written < count) { err = g_io_channel_write(hs->rfcomm, str + total_written, count - total_written, &written); if (err != G_IO_ERROR_NONE) return err; total_written += written; } return G_IO_ERROR_NONE;}static void pending_connect_ok(struct pending_connect *c, struct device *dev){ struct headset *hs = dev->headset; DBusMessage *reply; if (c->msg) { reply = dbus_message_new_method_return(c->msg); if (reply) send_message_and_unref(dev->conn, reply); } if (c->cb) { if (hs->rfcomm && hs->sco) c->cb(dev, c->cb_data); else c->cb(NULL, c->cb_data); } pending_connect_free(c);}static gboolean finalize_stream_setup(struct device *dev){ struct headset *hs = dev->headset; g_slist_foreach(hs->pending, (GFunc) pending_connect_ok, dev); g_slist_free(hs->pending); hs->pending = NULL; return FALSE;}static void pending_connect_failed(struct pending_connect *c, struct device *dev){ if (c->msg) err_connect_failed(dev->conn, c->msg, strerror(c->err)); if (c->cb) c->cb(NULL, c->cb_data); pending_connect_free(c);}static gboolean sco_connect_cb(GIOChannel *chan, GIOCondition cond, struct device *device){ struct headset *hs; struct pending_connect *c; int ret, sk, err; socklen_t len; if (cond & G_IO_NVAL) return FALSE; hs = device->headset; c = hs->pending->data; sk = g_io_channel_unix_get_fd(chan); len = sizeof(ret); if (getsockopt(sk, SOL_SOCKET, SO_ERROR, &ret, &len) < 0) { err = errno; error("getsockopt(SO_ERROR): %s (%d)", strerror(err), err); goto failed; } if (ret != 0) { err = ret; error("connect(): %s (%d)", strerror(ret), ret); goto failed; } debug("SCO socket opened for headset %s", device->path); info("SCO fd=%d", sk); hs->sco = chan; c->io = NULL; g_slist_foreach(hs->pending, (GFunc) pending_connect_ok, device); g_slist_free(hs->pending); hs->pending = NULL; fcntl(sk, F_SETFL, 0); headset_set_state(device, HEADSET_STATE_PLAYING); return FALSE;failed: g_slist_foreach(hs->pending, (GFunc) pending_connect_failed, device); g_slist_free(hs->pending); hs->pending = NULL; headset_set_state(device, HEADSET_STATE_CONNECTED); return FALSE;}static int sco_connect(struct device *device, struct pending_connect *c){ struct headset *hs = device->headset; struct sockaddr_sco addr; gboolean do_callback = FALSE; int sk, err; if (!g_slist_find(hs->pending, c)) hs->pending = g_slist_append(hs->pending, c); if (hs->state != HEADSET_STATE_CONNECTED) return 0; sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO); if (sk < 0) { err = errno; error("socket(BTPROTO_SCO): %s (%d)", strerror(err), err); return -err; } c->io = g_io_channel_unix_new(sk); if (!c->io) { close(sk); return -EINVAL; } memset(&addr, 0, sizeof(addr)); addr.sco_family = AF_BLUETOOTH; bacpy(&addr.sco_bdaddr, BDADDR_ANY); if (bind(sk, (struct sockaddr *)&addr, sizeof(addr)) < 0) { err = errno; error("socket(BTPROTO_SCO): %s (%d)", strerror(err), err); return -err; } if (set_nonblocking(sk) < 0) { err = errno; return -err; } memset(&addr, 0, sizeof(addr)); addr.sco_family = AF_BLUETOOTH; bacpy(&addr.sco_bdaddr, &device->dst); if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { if (!(errno == EAGAIN || errno == EINPROGRESS)) { err = errno; error("connect: %s (%d)", strerror(errno), errno); return -err; } c->io_id = g_io_add_watch(c->io, G_IO_OUT | G_IO_NVAL | G_IO_ERR | G_IO_HUP, (GIOFunc) sco_connect_cb, device); } else do_callback = TRUE; headset_set_state(device, HEADSET_STATE_PLAY_IN_PROGRESS); if (!g_slist_find(hs->pending, c)) hs->pending = g_slist_append(hs->pending, c); if (do_callback) sco_connect_cb(c->io, G_IO_OUT, device); return 0;}static gboolean rfcomm_connect_cb(GIOChannel *chan, GIOCondition cond, struct device *device){ struct headset *hs; struct pending_connect *c; char hs_address[18]; int sk, ret, err = 0; socklen_t len; if (cond & G_IO_NVAL) return FALSE; hs = device->headset; c = hs->pending->data; sk = g_io_channel_unix_get_fd(chan); len = sizeof(ret); if (getsockopt(sk, SOL_SOCKET, SO_ERROR, &ret, &len) < 0) { err = errno; error("getsockopt(SO_ERROR): %s (%d)", strerror(err), err); goto failed; } if (ret != 0) { err = ret; error("connect(): %s (%d)", strerror(ret), ret); goto failed; } ba2str(&device->dst, hs_address); hs->rfcomm = chan; c->io = NULL; headset_set_state(device, HEADSET_STATE_CONNECTED); debug("%s: Connected to %s", device->path, hs_address); g_io_add_watch(chan, G_IO_IN | G_IO_ERR | G_IO_HUP| G_IO_NVAL, (GIOFunc) rfcomm_io_cb, device); if (c->cb) { if (sco_connect(device, c) < 0) goto failed; return FALSE; }
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -