/* * BlueALSA - main.c * Copyright (c) 2016-2018 Arkadiusz Bokowy * * This file is a part of bluez-alsa. * * This project is licensed under the terms of the MIT license. * */ #if HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #if ENABLE_LDAC # include #endif #include "bluealsa.h" #include "bluez.h" #include "ctl.h" #include "transport.h" #include "utils.h" #include "shared/defs.h" #include "shared/log.h" static char *get_a2dp_codecs( const struct bluez_a2dp_codec **codecs, enum bluez_a2dp_dir dir) { const char *tmp[16] = { NULL }; int i = 0; while (*codecs != NULL) { const struct bluez_a2dp_codec *c = *codecs++; if (c->dir != dir) continue; tmp[i++] = bluetooth_a2dp_codec_to_string(c->id); } return g_strjoinv(", ", (char **)tmp); } static GMainLoop *loop = NULL; static void main_loop_stop(int sig) { /* Call to this handler restores the default action, so on the * second call the program will be forcefully terminated. */ struct sigaction sigact = { .sa_handler = SIG_DFL }; sigaction(sig, &sigact, NULL); g_main_loop_quit(loop); } int main(int argc, char **argv) { int opt; const char *opts = "hVSi:p:"; const struct option longopts[] = { { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, 'V' }, { "syslog", no_argument, NULL, 'S' }, { "device", required_argument, NULL, 'i' }, { "profile", required_argument, NULL, 'p' }, { "a2dp-force-mono", no_argument, NULL, 6 }, { "a2dp-force-audio-cd", no_argument, NULL, 7 }, { "a2dp-keep-alive", required_argument, NULL, 8 }, { "a2dp-volume", no_argument, NULL, 9 }, #if ENABLE_AAC { "aac-afterburner", no_argument, NULL, 4 }, { "aac-vbr-mode", required_argument, NULL, 5 }, #endif #if ENABLE_LDAC { "ldac-abr", no_argument, NULL, 10 }, { "ldac-eqmid", required_argument, NULL, 11 }, #endif { 0, 0, 0, 0 }, }; bool syslog = false; struct hci_dev_info *hci_devs; int hci_devs_num; /* Check if syslog forwarding has been enabled. This check has to be * done before anything else, so we can log early stage warnings and * errors. */ opterr = 0; while ((opt = getopt_long(argc, argv, opts, longopts, NULL)) != -1) switch (opt) { case 'S': syslog = true; break; case 'p': /* reset defaults if user has specified profile option */ memset(&config.enable, 0, sizeof(config.enable)); break; } log_open(argv[0], syslog, BLUEALSA_LOGTIME); if (bluealsa_config_init() != 0) { error("Couldn't initialize bluealsa config"); return EXIT_FAILURE; } if (hci_devlist(&hci_devs, &hci_devs_num)) { error("Couldn't enumerate HCI devices: %s", strerror(errno)); return EXIT_FAILURE; } if (!hci_devs_num) { error("No HCI device available"); return EXIT_FAILURE; } { /* try to get default device (if possible get active one) */ int i; for (i = 0; i < hci_devs_num; i++) if (i == 0 || hci_test_bit(HCI_UP, &hci_devs[i].flags)) memcpy(&config.hci_dev, &hci_devs[i], sizeof(config.hci_dev)); } /* parse options */ optind = 0; opterr = 1; while ((opt = getopt_long(argc, argv, opts, longopts, NULL)) != -1) switch (opt) { case 'h' /* --help */ : printf("Usage:\n" " %s [OPTION]...\n" "\nOptions:\n" " -h, --help\t\tprint this help and exit\n" " -V, --version\t\tprint version and exit\n" " -S, --syslog\t\tsend output to syslog\n" " -i, --device=hciX\tHCI device to use\n" " -p, --profile=NAME\tenable BT profile\n" " --a2dp-force-mono\tforce monophonic sound\n" " --a2dp-force-audio-cd\tforce 44.1 kHz sampling\n" " --a2dp-keep-alive=SEC\tkeep A2DP transport alive\n" " --a2dp-volume\t\tcontrol volume natively\n" #if ENABLE_AAC " --aac-afterburner\tenable afterburner\n" " --aac-vbr-mode=NB\tset VBR mode to NB\n" #endif #if ENABLE_LDAC " --ldac-abr\t\tenable adaptive bit rate\n" " --ldac-eqmid=NB\tset encoder quality to NB\n" #endif "\nAvailable BT profiles:\n" " - a2dp-source\tAdvanced Audio Source (%s)\n" " - a2dp-sink\tAdvanced Audio Sink (%s)\n" " - hfp-hf\tHands-Free (%s)\n" " - hfp-ag\tHands-Free Audio Gateway (%s)\n" " - hsp-hs\tHeadset (%s)\n" " - hsp-ag\tHeadset Audio Gateway (%s)\n" "\n" "By default only output profiles are enabled, which includes A2DP Source and\n" "HSP/HFP Audio Gateways. If one wants to enable other set of profiles, it is\n" "required to explicitly specify all of them using `-p NAME` options.\n", argv[0], get_a2dp_codecs(config.a2dp.codecs, BLUEZ_A2DP_SOURCE), get_a2dp_codecs(config.a2dp.codecs, BLUEZ_A2DP_SINK), "v1.7", "v1.7", "v1.2", "v1.2"); return EXIT_SUCCESS; case 'V' /* --version */ : printf("%s\n", PACKAGE_VERSION); return EXIT_SUCCESS; case 'S' /* --syslog */ : break; case 'i' /* --device=HCI */ : { bdaddr_t addr; int i = hci_devs_num; int found = 0; if (str2ba(optarg, &addr) == 0) { while (i--) if (bacmp(&addr, &hci_devs[i].bdaddr) == 0) { memcpy(&config.hci_dev, &hci_devs[i], sizeof(config.hci_dev)); found = 1; break; } } else { while (i--) if (strcmp(optarg, hci_devs[i].name) == 0) { memcpy(&config.hci_dev, &hci_devs[i], sizeof(config.hci_dev)); found = 1; break; } } if (found == 0) { error("HCI device not found: %s", optarg); return EXIT_FAILURE; } break; } case 'p' /* --profile=NAME */ : { size_t i; const struct { char *name; bool *ptr; } map[] = { { "a2dp-source", &config.enable.a2dp_source }, { "a2dp-sink", &config.enable.a2dp_sink }, { "hfp-hf", &config.enable.hfp_hf }, { "hfp-ag", &config.enable.hfp_ag }, { "hsp-hs", &config.enable.hsp_hs }, { "hsp-ag", &config.enable.hsp_ag }, }; for (i = 0; i < ARRAYSIZE(map); i++) if (strcasecmp(optarg, map[i].name) == 0) { *map[i].ptr = true; break; } if (i == ARRAYSIZE(map)) { error("Invalid BT profile name: %s", optarg); return EXIT_FAILURE; } break; } case 6 /* --a2dp-force-mono */ : config.a2dp.force_mono = true; break; case 7 /* --a2dp-force-audio-cd */ : config.a2dp.force_44100 = true; break; case 8 /* --a2dp-keep-alive=SEC */ : config.a2dp.keep_alive = atoi(optarg); break; case 9 /* --a2dp-volume */ : config.a2dp.volume = true; break; #if ENABLE_AAC case 4 /* --aac-afterburner */ : config.aac_afterburner = true; break; case 5 /* --aac-vbr-mode=NB */ : config.aac_vbr_mode = atoi(optarg); if (config.aac_vbr_mode > 5) { error("Invalid bitrate mode [0, 5]: %s", optarg); return EXIT_FAILURE; } break; #endif #if ENABLE_LDAC case 10 /* --ldac-abr */ : config.ldac_abr = true; break; case 11 /* --ldac-eqmid=NB */ : config.ldac_eqmid = atoi(optarg); if (config.ldac_eqmid >= LDACBT_EQMID_NUM) { error("Invalid encoder quality index [0, %d]: %s", LDACBT_EQMID_NUM - 1, optarg); return EXIT_FAILURE; } break; #endif default: fprintf(stderr, "Try '%s --help' for more information.\n", argv[0]); return EXIT_FAILURE; } /* device list is no longer required */ free(hci_devs); /* initialize random number generator */ srandom(time(NULL)); if ((bluealsa_ctl_thread_init()) == -1) return EXIT_FAILURE; gchar *address; GError *err; err = NULL; address = g_dbus_address_get_for_bus_sync(G_BUS_TYPE_SYSTEM, NULL, NULL); if ((config.dbus = g_dbus_connection_new_for_address_sync(address, G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT | G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION, NULL, NULL, &err)) == NULL) { error("Couldn't obtain D-Bus connection: %s", err->message); return EXIT_FAILURE; } bluez_subscribe_signals(); bluez_register_a2dp(); bluez_register_hfp(); /* In order to receive EPIPE while writing to the pipe whose reading end * is closed, the SIGPIPE signal has to be handled. For more information * see the io_thread_write_pcm() function. */ struct sigaction sigact = { .sa_handler = SIG_IGN }; sigaction(SIGPIPE, &sigact, NULL); /* register main loop exit handler */ sigact.sa_handler = main_loop_stop; sigaction(SIGTERM, &sigact, NULL); sigaction(SIGINT, &sigact, NULL); /* main dispatching loop */ debug("Starting main dispatching loop"); loop = g_main_loop_new(NULL, FALSE); g_main_loop_run(loop); debug("Exiting main loop"); /* From all of the cleanup routines, these ones cannot be omitted. We have * to unlink named sockets, otherwise service will not start any more. */ bluealsa_ctl_free(); bluealsa_config_free(); return EXIT_SUCCESS; }