/* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2002-2006 Marcel Holtmann * * * 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 #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static volatile int terminate = 0; static void sig_term(int sig) { terminate = 1; } static int rfcomm_connect(bdaddr_t *src, bdaddr_t *dst, uint8_t channel) { struct sockaddr_rc addr; int s; if ((s = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM)) < 0) { return -1; } memset(&addr, 0, sizeof(addr)); addr.rc_family = AF_BLUETOOTH; bacpy(&addr.rc_bdaddr, src); addr.rc_channel = 0; if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) { close(s); return -1; } memset(&addr, 0, sizeof(addr)); addr.rc_family = AF_BLUETOOTH; bacpy(&addr.rc_bdaddr, dst); addr.rc_channel = channel; if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) < 0 ){ close(s); return -1; } return s; } static int sco_connect(bdaddr_t *src, bdaddr_t *dst, uint16_t *handle, uint16_t *mtu) { struct sockaddr_sco addr; struct sco_conninfo conn; struct sco_options opts; socklen_t size; int s; struct linger l; if ((s = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO)) < 0) { return -1; } memset(&addr, 0, sizeof(addr)); addr.sco_family = AF_BLUETOOTH; bacpy(&addr.sco_bdaddr, src); if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) { close(s); return -1; } memset(&addr, 0, sizeof(addr)); addr.sco_family = AF_BLUETOOTH; bacpy(&addr.sco_bdaddr, dst); if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) < 0 ){ close(s); return -1; } l.l_onoff = 1; l.l_linger = 5; setsockopt(s, SOL_SOCKET, SO_LINGER, &l, sizeof l); memset(&conn, 0, sizeof(conn)); size = sizeof(conn); if (getsockopt(s, SOL_SCO, SCO_CONNINFO, &conn, &size) < 0) { close(s); return -1; } memset(&opts, 0, sizeof(opts)); size = sizeof(opts); if (getsockopt(s, SOL_SCO, SCO_OPTIONS, &opts, &size) < 0) { close(s); return -1; } if (handle) *handle = conn.hci_handle; if (mtu) *mtu = opts.mtu; return s; } static void usage(void) { printf("Usage:\n" "\thstest play [channel]\n" "\thstest record [channel]\n"); } #define PLAY 1 #define RECORD 2 int main(int argc, char *argv[]) { struct sigaction sa; fd_set rfds, wfds, efds; struct timeval timeout; unsigned char buf[2048], *p; int maxfd, sel, rlen, wlen; bdaddr_t local; bdaddr_t bdaddr; uint8_t channel; char *filename; mode_t filemode; int err, mode = 0; int dd, rd, sd, fd; uint16_t sco_handle, sco_mtu, vs; int stream = 0; switch (argc) { case 4: str2ba(argv[3], &bdaddr); channel = 6; break; case 5: str2ba(argv[3], &bdaddr); channel = atoi(argv[4]); break; default: usage(); exit(-1); } if (strncmp(argv[1], "play", 4) == 0) { mode = PLAY; filemode = O_RDONLY; } else if (strncmp(argv[1], "rec", 3) == 0) { mode = RECORD; filemode = O_WRONLY | O_CREAT | O_TRUNC; } else { usage(); exit(-1); } filename = argv[2]; hci_devba(0, &local); #if 0 dd = hci_open_dev(0); hci_read_voice_setting(dd, &vs, 1000); vs = htobs(vs); fprintf(stderr, "Voice setting: 0x%04x\n", vs); close(dd); if (vs != 0x0060) { fprintf(stderr, "The voice setting must be 0x0060\n"); //return -1; } #endif if (strcmp(filename, "-") == 0) { switch (mode) { case PLAY: fd = 0; break; case RECORD: fd = 1; break; default: return -1; } } else { if ((fd = open(filename, filemode)) < 0) { perror("Can't open input/output file"); return -1; } } memset(&sa, 0, sizeof(sa)); sa.sa_flags = SA_NOCLDSTOP; sa.sa_handler = sig_term; sigaction(SIGTERM, &sa, NULL); sigaction(SIGINT, &sa, NULL); sa.sa_handler = SIG_IGN; sigaction(SIGCHLD, &sa, NULL); sigaction(SIGPIPE, &sa, NULL); if ((rd = rfcomm_connect(&local, &bdaddr, channel)) < 0) { perror("Can't connect RFCOMM channel"); return -1; } fprintf(stderr, "RFCOMM channel connected\n"); if ((sd = sco_connect(&local, &bdaddr, &sco_handle, &sco_mtu)) < 0) { perror("Can't connect SCO audio channel"); close(rd); return -1; } sco_mtu = 48; fprintf(stderr, "SCO audio channel connected (handle %d, mtu %d)\n", sco_handle, sco_mtu); if (mode == RECORD) err = write(rd, "RING\r\n", 6); maxfd = (rd > sd) ? rd : sd; while (!terminate) { FD_ZERO(&rfds); FD_ZERO(&wfds); FD_ZERO(&efds); FD_SET(rd, &rfds); if (stream) { if (mode == RECORD) FD_SET(sd, &rfds); else FD_SET(sd, &wfds); } FD_SET(rd, &efds); FD_SET(sd, &efds); timeout.tv_sec = 0; timeout.tv_usec = 1000000; if ((sel = select(maxfd + 1, &rfds, &wfds, &efds, &timeout)) > 0) { /* communications errors */ if (FD_ISSET(sd, &efds)) { fprintf(stderr, "SCO error\n"); break; } if (FD_ISSET(rd, &efds)) { fprintf(stderr, "RFCOMM error\n"); break; } /* RFCOMM channel */ if (FD_ISSET(rd, &rfds)) { rlen = read(rd, buf, sizeof(buf)); if (rlen > 0) { if (strcmp((char*)buf,"AT+CKPD=200\r") == 0) { stream = 1; fprintf(stderr, "SCO started\n"); } if (rlen < sizeof(buf)) buf[rlen++] = '\n'; fwrite(buf, 1, rlen, stderr); wlen = write(rd, "OK\r\n", 4); } } /* play samples */ if (FD_ISSET(sd, &wfds)) { rlen = read(fd, buf, sco_mtu); if (rlen <= 0) { if (rlen < 0) perror("Unable to read file"); break; } wlen = 0; p = buf; while (rlen > sco_mtu) { wlen = write(sd, p, sco_mtu); rlen -= sco_mtu; p += sco_mtu; } wlen = write(sd, p, rlen); } /* record samples */ if (FD_ISSET(sd, &rfds)) { rlen = read(sd, buf, sizeof(buf)); if (rlen > 0) wlen = write(fd, buf, rlen); } } } close(fd); close(sd); sleep(1); close(rd); return 0; }