/* 
 | 
 * 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 <errno.h> 
 | 
#include <getopt.h> 
 | 
#include <signal.h> 
 | 
#include <stdio.h> 
 | 
#include <stdlib.h> 
 | 
  
 | 
#include <bluetooth/bluetooth.h> 
 | 
#include <bluetooth/hci.h> 
 | 
#include <bluetooth/hci_lib.h> 
 | 
  
 | 
#include <glib.h> 
 | 
#include <gio/gio.h> 
 | 
  
 | 
#if ENABLE_LDAC 
 | 
# include <ldacBT.h> 
 | 
#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; 
 | 
} 
 |