/* 
 | 
 * BlueALSA - aplay.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 <getopt.h> 
 | 
#include <poll.h> 
 | 
#include <pthread.h> 
 | 
#include <signal.h> 
 | 
#include <stdbool.h> 
 | 
#include <stdio.h> 
 | 
#include <stdlib.h> 
 | 
#include <string.h> 
 | 
#include <unistd.h> 
 | 
#include <sys/socket.h> 
 | 
#include <sys/un.h> 
 | 
  
 | 
#include <alsa/asoundlib.h> 
 | 
#include <gio/gio.h> 
 | 
  
 | 
#include "shared/ctl-client.h" 
 | 
#include "shared/defs.h" 
 | 
#include "shared/ffb.h" 
 | 
#include "shared/log.h" 
 | 
  
 | 
struct pcm_worker { 
 | 
    struct ba_msg_transport transport; 
 | 
    pthread_t thread; 
 | 
    /* file descriptor of BlueALSA */ 
 | 
    int ba_fd; 
 | 
    /* file descriptor of PCM FIFO */ 
 | 
    int pcm_fd; 
 | 
    /* opened playback PCM device */ 
 | 
    snd_pcm_t *pcm; 
 | 
    /* if true, worker is marked for eviction */ 
 | 
    bool eviction; 
 | 
    /* if true, playback is active */ 
 | 
    bool active; 
 | 
    /* human-readable BT address */ 
 | 
    char addr[18]; 
 | 
}; 
 | 
  
 | 
static unsigned int verbose = 0; 
 | 
static const char *device = "default"; 
 | 
static const char *ba_interface = "hci0"; 
 | 
static unsigned int pcm_buffer_time = 500000; 
 | 
static unsigned int pcm_period_time = 100000; 
 | 
static enum ba_pcm_type ba_type = BA_PCM_TYPE_A2DP; 
 | 
static bool pcm_mixer = true; 
 | 
  
 | 
static GDBusConnection *dbus = NULL; 
 | 
  
 | 
static pthread_rwlock_t workers_lock = PTHREAD_RWLOCK_INITIALIZER; 
 | 
static struct pcm_worker *workers = NULL; 
 | 
static size_t workers_count = 0; 
 | 
static size_t workers_size = 0; 
 | 
  
 | 
static bool main_loop_on = true; 
 | 
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); 
 | 
  
 | 
    main_loop_on = false; 
 | 
} 
 | 
  
 | 
static int pcm_set_hw_params(snd_pcm_t *pcm, int channels, int rate, 
 | 
        unsigned int *buffer_time, unsigned int *period_time, char **msg) { 
 | 
  
 | 
    const snd_pcm_access_t access = SND_PCM_ACCESS_RW_INTERLEAVED; 
 | 
    const snd_pcm_format_t format = SND_PCM_FORMAT_S16_LE; 
 | 
    snd_pcm_hw_params_t *params; 
 | 
    char buf[256]; 
 | 
    int dir; 
 | 
    int err; 
 | 
  
 | 
    snd_pcm_hw_params_alloca(¶ms); 
 | 
  
 | 
    if ((err = snd_pcm_hw_params_any(pcm, params)) < 0) { 
 | 
        snprintf(buf, sizeof(buf), "Set all possible ranges: %s", snd_strerror(err)); 
 | 
        goto fail; 
 | 
    } 
 | 
    if ((err = snd_pcm_hw_params_set_access(pcm, params, access)) != 0) { 
 | 
        snprintf(buf, sizeof(buf), "Set assess type: %s: %s", snd_strerror(err), snd_pcm_access_name(access)); 
 | 
        goto fail; 
 | 
    } 
 | 
    if ((err = snd_pcm_hw_params_set_format(pcm, params, format)) != 0) { 
 | 
        snprintf(buf, sizeof(buf), "Set format: %s: %s", snd_strerror(err), snd_pcm_format_name(format)); 
 | 
        goto fail; 
 | 
    } 
 | 
    if ((err = snd_pcm_hw_params_set_channels(pcm, params, channels)) != 0) { 
 | 
        snprintf(buf, sizeof(buf), "Set channels: %s: %d", snd_strerror(err), channels); 
 | 
        goto fail; 
 | 
    } 
 | 
    if ((err = snd_pcm_hw_params_set_rate(pcm, params, rate, 0)) != 0) { 
 | 
        snprintf(buf, sizeof(buf), "Set sampling rate: %s: %d", snd_strerror(err), rate); 
 | 
        goto fail; 
 | 
    } 
 | 
    if ((err = snd_pcm_hw_params_set_buffer_time_near(pcm, params, buffer_time, &dir)) != 0) { 
 | 
        snprintf(buf, sizeof(buf), "Set buffer time: %s: %u", snd_strerror(err), *buffer_time); 
 | 
        goto fail; 
 | 
    } 
 | 
    if ((err = snd_pcm_hw_params_set_period_time_near(pcm, params, period_time, &dir)) != 0) { 
 | 
        snprintf(buf, sizeof(buf), "Set period time: %s: %u", snd_strerror(err), *period_time); 
 | 
        goto fail; 
 | 
    } 
 | 
    if ((err = snd_pcm_hw_params(pcm, params)) != 0) { 
 | 
        snprintf(buf, sizeof(buf), "%s", snd_strerror(err)); 
 | 
        goto fail; 
 | 
    } 
 | 
  
 | 
    return 0; 
 | 
  
 | 
fail: 
 | 
    if (msg != NULL) 
 | 
        *msg = strdup(buf); 
 | 
    return err; 
 | 
} 
 | 
  
 | 
static int pcm_set_sw_params(snd_pcm_t *pcm, snd_pcm_uframes_t buffer_size, 
 | 
        snd_pcm_uframes_t period_size, char **msg) { 
 | 
  
 | 
    snd_pcm_sw_params_t *params; 
 | 
    char buf[256]; 
 | 
    int err; 
 | 
  
 | 
    snd_pcm_sw_params_alloca(¶ms); 
 | 
  
 | 
    if ((err = snd_pcm_sw_params_current(pcm, params)) != 0) { 
 | 
        snprintf(buf, sizeof(buf), "Get current params: %s", snd_strerror(err)); 
 | 
        goto fail; 
 | 
    } 
 | 
  
 | 
    /* start the transfer when the buffer is full (or almost full) */ 
 | 
    snd_pcm_uframes_t threshold = (buffer_size / period_size) * period_size; 
 | 
    if ((err = snd_pcm_sw_params_set_start_threshold(pcm, params, threshold)) != 0) { 
 | 
        snprintf(buf, sizeof(buf), "Set start threshold: %s: %lu", snd_strerror(err), threshold); 
 | 
        goto fail; 
 | 
    } 
 | 
  
 | 
    /* allow the transfer when at least period_size samples can be processed */ 
 | 
    if ((err = snd_pcm_sw_params_set_avail_min(pcm, params, period_size)) != 0) { 
 | 
        snprintf(buf, sizeof(buf), "Set avail min: %s: %lu", snd_strerror(err), period_size); 
 | 
        goto fail; 
 | 
    } 
 | 
  
 | 
    if ((err = snd_pcm_sw_params(pcm, params)) != 0) { 
 | 
        snprintf(buf, sizeof(buf), "%s", snd_strerror(err)); 
 | 
        goto fail; 
 | 
    } 
 | 
  
 | 
    return 0; 
 | 
  
 | 
fail: 
 | 
    if (msg != NULL) 
 | 
        *msg = strdup(buf); 
 | 
    return err; 
 | 
} 
 | 
  
 | 
static int pcm_open(snd_pcm_t **pcm, int channels, int rate, 
 | 
        unsigned int *buffer_time, unsigned int *period_time, char **msg) { 
 | 
  
 | 
    snd_pcm_t *_pcm = NULL; 
 | 
    char buf[256]; 
 | 
    char *tmp; 
 | 
    int err; 
 | 
  
 | 
    if ((err = snd_pcm_open(&_pcm, device, SND_PCM_STREAM_PLAYBACK, 0)) != 0) { 
 | 
        snprintf(buf, sizeof(buf), "%s", snd_strerror(err)); 
 | 
        goto fail; 
 | 
    } 
 | 
  
 | 
    if ((err = pcm_set_hw_params(_pcm, channels, rate, buffer_time, period_time, &tmp)) != 0) { 
 | 
        snprintf(buf, sizeof(buf), "Set HW params: %s", tmp); 
 | 
        goto fail; 
 | 
    } 
 | 
  
 | 
    snd_pcm_uframes_t buffer_size, period_size; 
 | 
    if ((err = snd_pcm_get_params(_pcm, &buffer_size, &period_size)) != 0) { 
 | 
        snprintf(buf, sizeof(buf), "Get params: %s", snd_strerror(err)); 
 | 
        goto fail; 
 | 
    } 
 | 
  
 | 
    if ((err = pcm_set_sw_params(_pcm, buffer_size, period_size, &tmp)) != 0) { 
 | 
        snprintf(buf, sizeof(buf), "Set SW params: %s", tmp); 
 | 
        goto fail; 
 | 
    } 
 | 
  
 | 
    if ((err = snd_pcm_prepare(_pcm)) != 0) { 
 | 
        snprintf(buf, sizeof(buf), "Prepare: %s", snd_strerror(err)); 
 | 
        goto fail; 
 | 
    } 
 | 
  
 | 
    *pcm = _pcm; 
 | 
    return 0; 
 | 
  
 | 
fail: 
 | 
    if (_pcm != NULL) 
 | 
        snd_pcm_close(_pcm); 
 | 
    if (msg != NULL) 
 | 
        *msg = strdup(buf); 
 | 
    return err; 
 | 
} 
 | 
  
 | 
static struct pcm_worker *get_active_worker(void) { 
 | 
  
 | 
    struct pcm_worker *w = NULL; 
 | 
    size_t i; 
 | 
  
 | 
    pthread_rwlock_rdlock(&workers_lock); 
 | 
  
 | 
    for (i = 0; i < workers_count; i++) 
 | 
        if (workers[i].active) { 
 | 
            w = &workers[i]; 
 | 
            break; 
 | 
        } 
 | 
  
 | 
    pthread_rwlock_unlock(&workers_lock); 
 | 
  
 | 
    return w; 
 | 
} 
 | 
  
 | 
static int pause_device_player(const bdaddr_t *dev) { 
 | 
  
 | 
    GDBusMessage *msg = NULL, *rep = NULL; 
 | 
    GError *err = NULL; 
 | 
    char obj[64]; 
 | 
    int ret = 0; 
 | 
  
 | 
    sprintf(obj, "/org/bluez/%s/dev_%2.2X_%2.2X_%2.2X_%2.2X_%2.2X_%2.2X/player0", 
 | 
            ba_interface, dev->b[5], dev->b[4], dev->b[3], dev->b[2], dev->b[1], dev->b[0]); 
 | 
    msg = g_dbus_message_new_method_call("org.bluez", obj, "org.bluez.MediaPlayer1", "Pause"); 
 | 
  
 | 
    if ((rep = g_dbus_connection_send_message_with_reply_sync(dbus, msg, 
 | 
                    G_DBUS_SEND_MESSAGE_FLAGS_NONE, -1, NULL, NULL, &err)) == NULL) 
 | 
        goto fail; 
 | 
    if (g_dbus_message_get_message_type(rep) == G_DBUS_MESSAGE_TYPE_ERROR) { 
 | 
        g_dbus_message_to_gerror(rep, &err); 
 | 
        goto fail; 
 | 
    } 
 | 
  
 | 
    debug("Requested playback pause"); 
 | 
    goto final; 
 | 
  
 | 
fail: 
 | 
    ret = -1; 
 | 
  
 | 
final: 
 | 
    if (msg != NULL) 
 | 
        g_object_unref(msg); 
 | 
    if (rep != NULL) 
 | 
        g_object_unref(rep); 
 | 
    if (err != NULL) { 
 | 
        debug("Couldn't pause player: %s", err->message); 
 | 
        g_error_free(err); 
 | 
    } 
 | 
  
 | 
    return ret; 
 | 
} 
 | 
  
 | 
static void pcm_worker_routine_exit(struct pcm_worker *worker) { 
 | 
    if (worker->pcm_fd != -1) { 
 | 
        bluealsa_close_transport(worker->ba_fd, &worker->transport); 
 | 
        close(worker->pcm_fd); 
 | 
        worker->pcm_fd = -1; 
 | 
    } 
 | 
    if (worker->ba_fd != -1) { 
 | 
        close(worker->ba_fd); 
 | 
        worker->ba_fd = -1; 
 | 
    } 
 | 
    if (worker->pcm != NULL) { 
 | 
        snd_pcm_close(worker->pcm); 
 | 
        worker->pcm = NULL; 
 | 
    } 
 | 
    worker->eviction = true; 
 | 
    debug("Exiting PCM worker %s", worker->addr); 
 | 
} 
 | 
  
 | 
static int rockchip_send_underrun_to_deviceiolib() 
 | 
{ 
 | 
    struct sockaddr_un serverAddr; 
 | 
    int sockfd; 
 | 
  
 | 
    sockfd = socket(AF_UNIX, SOCK_DGRAM, 0); 
 | 
    if (sockfd < 0) { 
 | 
        printf("FUNC:%s create sockfd failed!\n", __func__); 
 | 
        return -1; 
 | 
    } 
 | 
  
 | 
    serverAddr.sun_family = AF_UNIX; 
 | 
    strcpy(serverAddr.sun_path, "/tmp/rk_deviceio_a2dp_underrun"); 
 | 
  
 | 
    sendto(sockfd, "a2dp underrun;", strlen("a2dp underrun;"), MSG_DONTWAIT, (struct sockaddr *)&serverAddr, sizeof(serverAddr)); 
 | 
    usleep(1000); 
 | 
  
 | 
    close(sockfd); 
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static void *pcm_worker_routine(void *arg) { 
 | 
    struct pcm_worker *w = (struct pcm_worker *)arg; 
 | 
  
 | 
    size_t pcm_1s_samples = w->transport.sampling * w->transport.channels; 
 | 
    ffb_int16_t buffer = { 0 }; 
 | 
  
 | 
    /* Cancellation should be possible only in the carefully selected place 
 | 
     * in order to prevent memory leaks and resources not being released. */ 
 | 
    pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); 
 | 
  
 | 
    pthread_cleanup_push(PTHREAD_CLEANUP(pcm_worker_routine_exit), w); 
 | 
    pthread_cleanup_push(PTHREAD_CLEANUP(ffb_int16_free), &buffer); 
 | 
  
 | 
    /* create buffer big enough to hold 100 ms of PCM data */ 
 | 
    if (ffb_init(&buffer, pcm_1s_samples / 10) == NULL) { 
 | 
        error("Couldn't create PCM buffer: %s", strerror(ENOMEM)); 
 | 
        goto fail; 
 | 
    } 
 | 
  
 | 
    if ((w->ba_fd = bluealsa_open(ba_interface)) == -1) { 
 | 
        error("Couldn't open BlueALSA: %s", strerror(errno)); 
 | 
        goto fail; 
 | 
    } 
 | 
  
 | 
    w->transport.stream = BA_PCM_STREAM_CAPTURE; 
 | 
    if ((w->pcm_fd = bluealsa_open_transport(w->ba_fd, &w->transport)) == -1) { 
 | 
        error("Couldn't open PCM FIFO: %s", strerror(errno)); 
 | 
        goto fail; 
 | 
    } 
 | 
  
 | 
    /* Initialize the max read length to 10 ms. Later, when the PCM device 
 | 
     * will be opened, this value will be adjusted to one period size. */ 
 | 
    size_t pcm_max_read_len = pcm_1s_samples / 100; 
 | 
    size_t pcm_open_retries = 0; 
 | 
  
 | 
    /* These variables determine how and when the pause command will be send 
 | 
     * to the device player. In order not to flood BT connection with AVRCP 
 | 
     * packets, we are going to send pause command every 0.5 second. */ 
 | 
    size_t pause_threshold = pcm_1s_samples / 2 * sizeof(int16_t); 
 | 
    size_t pause_counter = 0; 
 | 
    size_t pause_bytes = 0; 
 | 
  
 | 
    struct pollfd pfds[] = {{ w->pcm_fd, POLLIN, 0 }}; 
 | 
    int timeout = -1; 
 | 
  
 | 
    debug("Starting PCM loop"); 
 | 
    while (main_loop_on) { 
 | 
        pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); 
 | 
  
 | 
        ssize_t ret; 
 | 
  
 | 
        /* Reading from the FIFO won't block unless there is an open connection 
 | 
         * on the writing side. However, the server does not open PCM FIFO until 
 | 
         * a transport is created. With the A2DP, the transport is created when 
 | 
         * some clients (BT device) requests audio transfer. */ 
 | 
        switch (poll(pfds, ARRAYSIZE(pfds), timeout)) { 
 | 
        case -1: 
 | 
            if (errno == EINTR) 
 | 
                continue; 
 | 
            error("PCM FIFO poll error: %s", strerror(errno)); 
 | 
            goto fail; 
 | 
        case 0: 
 | 
            debug("Device marked as inactive: %s", w->addr); 
 | 
            pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); 
 | 
            pcm_max_read_len = pcm_1s_samples / 100; 
 | 
            pause_counter = pause_bytes = 0; 
 | 
            ffb_rewind(&buffer); 
 | 
            if (w->pcm != NULL) { 
 | 
                snd_pcm_close(w->pcm); 
 | 
                w->pcm = NULL; 
 | 
            } 
 | 
            w->active = false; 
 | 
            timeout = -1; 
 | 
            continue; 
 | 
        } 
 | 
  
 | 
        /* FIFO has been terminated on the writing side */ 
 | 
        if (pfds[0].revents & POLLHUP) 
 | 
            break; 
 | 
  
 | 
        size_t _in = MIN(pcm_max_read_len, ffb_len_in(&buffer)); 
 | 
        if ((ret = read(w->pcm_fd, buffer.tail, _in * sizeof(int16_t))) == -1) { 
 | 
            if (errno == EINTR) 
 | 
                continue; 
 | 
            error("PCM FIFO read error: %s", strerror(errno)); 
 | 
            goto fail; 
 | 
        } 
 | 
  
 | 
        pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); 
 | 
  
 | 
        /* If PCM mixer is disabled, check whether we should play audio. */ 
 | 
        if (!pcm_mixer) { 
 | 
            struct pcm_worker *worker = get_active_worker(); 
 | 
            if (worker != NULL && worker != w) { 
 | 
                if (pause_counter < 5 && (pause_bytes += ret) > pause_threshold) { 
 | 
                    if (pause_device_player(&w->transport.addr) == -1) 
 | 
                        /* pause command does not work, stop further requests */ 
 | 
                        pause_counter = 5; 
 | 
                    pause_counter++; 
 | 
                    pause_bytes = 0; 
 | 
                    timeout = 100; 
 | 
                } 
 | 
                continue; 
 | 
            } 
 | 
        } 
 | 
  
 | 
        if (w->pcm == NULL) { 
 | 
  
 | 
            unsigned int buffer_time = pcm_buffer_time; 
 | 
            unsigned int period_time = pcm_period_time; 
 | 
            snd_pcm_uframes_t buffer_size; 
 | 
            snd_pcm_uframes_t period_size; 
 | 
            char *tmp; 
 | 
  
 | 
            /* After PCM open failure wait one second before retry. This can not be 
 | 
             * done with a single sleep() call, because we have to drain PCM FIFO. */ 
 | 
            if (pcm_open_retries++ % 20 != 0) { 
 | 
                usleep(50000); 
 | 
                continue; 
 | 
            } 
 | 
  
 | 
            if (pcm_open(&w->pcm, w->transport.channels, w->transport.sampling, 
 | 
                        &buffer_time, &period_time, &tmp) != 0) { 
 | 
                warn("Couldn't open PCM: %s", tmp); 
 | 
                pcm_max_read_len = buffer.size; 
 | 
                usleep(50000); 
 | 
                free(tmp); 
 | 
                continue; 
 | 
            } 
 | 
  
 | 
            snd_pcm_get_params(w->pcm, &buffer_size, &period_size); 
 | 
            pcm_max_read_len = period_size * w->transport.channels; 
 | 
            pcm_open_retries = 0; 
 | 
  
 | 
            if (verbose >= 2) { 
 | 
                printf("Used configuration for %s:\n" 
 | 
                        "  PCM buffer time: %u us (%zu bytes)\n" 
 | 
                        "  PCM period time: %u us (%zu bytes)\n" 
 | 
                        "  Sampling rate: %u Hz\n" 
 | 
                        "  Channels: %u\n", 
 | 
                        w->addr, 
 | 
                        buffer_time, snd_pcm_frames_to_bytes(w->pcm, buffer_size), 
 | 
                        period_time, snd_pcm_frames_to_bytes(w->pcm, period_size), 
 | 
                        w->transport.sampling, w->transport.channels); 
 | 
            } 
 | 
  
 | 
        } 
 | 
  
 | 
        /* mark device as active and set timeout to 500ms */ 
 | 
        w->active = true; 
 | 
        timeout = 500; 
 | 
  
 | 
        /* calculate the overall number of frames in the buffer */ 
 | 
        ffb_seek(&buffer, ret / sizeof(*buffer.data)); 
 | 
        snd_pcm_sframes_t frames = ffb_len_out(&buffer) / w->transport.channels; 
 | 
  
 | 
        if ((frames = snd_pcm_writei(w->pcm, buffer.data, frames)) < 0) 
 | 
            switch (-frames) { 
 | 
            case EPIPE: 
 | 
                debug("An underrun has occurred"); 
 | 
                /* Send underrun msg to rockchip deviceio */ 
 | 
                rockchip_send_underrun_to_deviceiolib(); 
 | 
                snd_pcm_prepare(w->pcm); 
 | 
                usleep(50000); 
 | 
                frames = 0; 
 | 
                break; 
 | 
            default: 
 | 
                error("Couldn't write to PCM: %s", snd_strerror(frames)); 
 | 
                goto fail; 
 | 
            } 
 | 
  
 | 
        /* move leftovers to the beginning and reposition tail */ 
 | 
        ffb_shift(&buffer, frames * w->transport.channels); 
 | 
  
 | 
    } 
 | 
  
 | 
fail: 
 | 
    pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); 
 | 
    pthread_cleanup_pop(1); 
 | 
    pthread_cleanup_pop(1); 
 | 
    return NULL; 
 | 
} 
 | 
  
 | 
int main(int argc, char *argv[]) { 
 | 
  
 | 
    int opt; 
 | 
    const char *opts = "hVvi:d:"; 
 | 
    const struct option longopts[] = { 
 | 
        { "help", no_argument, NULL, 'h' }, 
 | 
        { "version", no_argument, NULL, 'V' }, 
 | 
        { "verbose", no_argument, NULL, 'v' }, 
 | 
        { "hci", required_argument, NULL, 'i' }, 
 | 
        { "pcm", required_argument, NULL, 'd' }, 
 | 
        { "pcm-buffer-time", required_argument, NULL, 3 }, 
 | 
        { "pcm-period-time", required_argument, NULL, 4 }, 
 | 
        { "profile-a2dp", no_argument, NULL, 1 }, 
 | 
        { "profile-sco", no_argument, NULL, 2 }, 
 | 
        { "single-audio", no_argument, NULL, 5 }, 
 | 
        { 0, 0, 0, 0 }, 
 | 
    }; 
 | 
  
 | 
    while ((opt = getopt_long(argc, argv, opts, longopts, NULL)) != -1) 
 | 
        switch (opt) { 
 | 
        case 'h' /* --help */ : 
 | 
usage: 
 | 
            printf("Usage:\n" 
 | 
                    "  %s [OPTION]... <BT-ADDR>...\n" 
 | 
                    "\nOptions:\n" 
 | 
                    "  -h, --help\t\tprint this help and exit\n" 
 | 
                    "  -V, --version\t\tprint version and exit\n" 
 | 
                    "  -v, --verbose\t\tmake output more verbose\n" 
 | 
                    "  -i, --hci=hciX\tHCI device to use\n" 
 | 
                    "  -d, --pcm=NAME\tPCM device to use\n" 
 | 
                    "  --pcm-buffer-time=INT\tPCM buffer time\n" 
 | 
                    "  --pcm-period-time=INT\tPCM period time\n" 
 | 
                    "  --profile-a2dp\tuse A2DP profile\n" 
 | 
                    "  --profile-sco\t\tuse SCO profile\n" 
 | 
                    "  --single-audio\tsingle audio mode\n" 
 | 
                    "\nNote:\n" 
 | 
                    "If one wants to receive audio from more than one Bluetooth device, it is\n" 
 | 
                    "possible to specify more than one MAC address. By specifying any/empty MAC\n" 
 | 
                    "address (00:00:00:00:00:00), one will allow connections from any Bluetooth\n" 
 | 
                    "device.\n", 
 | 
                    argv[0]); 
 | 
            return EXIT_SUCCESS; 
 | 
  
 | 
        case 'V' /* --version */ : 
 | 
            printf("%s\n", PACKAGE_VERSION); 
 | 
            return EXIT_SUCCESS; 
 | 
  
 | 
        case 'v' /* --verbose */ : 
 | 
            verbose++; 
 | 
            break; 
 | 
  
 | 
        case 'i' /* --hci */ : 
 | 
            ba_interface = optarg; 
 | 
            break; 
 | 
        case 'd' /* --pcm */ : 
 | 
            device = optarg; 
 | 
            break; 
 | 
  
 | 
        case 1 /* --profile-a2dp */ : 
 | 
            ba_type = BA_PCM_TYPE_A2DP; 
 | 
            break; 
 | 
        case 2 /* --profile-sco */ : 
 | 
            ba_type = BA_PCM_TYPE_SCO; 
 | 
            break; 
 | 
  
 | 
        case 3 /* --pcm-buffer-time */ : 
 | 
            pcm_buffer_time = atoi(optarg); 
 | 
            break; 
 | 
        case 4 /* --pcm-period-time */ : 
 | 
            pcm_period_time = atoi(optarg); 
 | 
            break; 
 | 
  
 | 
        case 5 /* --single-audio */ : 
 | 
            pcm_mixer = false; 
 | 
            break; 
 | 
  
 | 
        default: 
 | 
            fprintf(stderr, "Try '%s --help' for more information.\n", argv[0]); 
 | 
            return EXIT_FAILURE; 
 | 
        } 
 | 
  
 | 
    if (optind == argc) 
 | 
        goto usage; 
 | 
  
 | 
    log_open(argv[0], false, false); 
 | 
  
 | 
    bdaddr_t *ba_addrs = NULL; 
 | 
    size_t ba_addrs_count = 0; 
 | 
    bool ba_addr_any = false; 
 | 
  
 | 
    int status = EXIT_SUCCESS; 
 | 
    int ba_fd = -1; 
 | 
    int ba_event_fd = -1; 
 | 
    size_t i; 
 | 
  
 | 
    ba_addrs_count = argc - optind; 
 | 
    if ((ba_addrs = malloc(sizeof(*ba_addrs) * ba_addrs_count)) == NULL) { 
 | 
        error("Couldn't allocate memory for BT addresses"); 
 | 
        goto fail; 
 | 
    } 
 | 
    for (i = 0; i < ba_addrs_count; i++) { 
 | 
        if (str2ba(argv[i + optind], &ba_addrs[i]) != 0) { 
 | 
            error("Invalid BT device address: %s", argv[i + optind]); 
 | 
            goto fail; 
 | 
        } 
 | 
        if (bacmp(&ba_addrs[i], BDADDR_ANY) == 0) 
 | 
            ba_addr_any = true; 
 | 
    } 
 | 
  
 | 
    if (verbose >= 1) { 
 | 
  
 | 
        char *ba_str = malloc(19 * ba_addrs_count + 1); 
 | 
        char *tmp = ba_str; 
 | 
        size_t i; 
 | 
  
 | 
        for (i = 0; i < ba_addrs_count; i++, tmp += 19) 
 | 
            ba2str(&ba_addrs[i], stpcpy(tmp, ", ")); 
 | 
  
 | 
        printf("Selected configuration:\n" 
 | 
                "  HCI device: %s\n" 
 | 
                "  PCM device: %s\n" 
 | 
                "  PCM buffer time: %u us\n" 
 | 
                "  PCM period time: %u us\n" 
 | 
                "  Bluetooth device(s): %s\n" 
 | 
                "  Profile: %s\n", 
 | 
                ba_interface, device, pcm_buffer_time, pcm_period_time, 
 | 
                ba_addr_any ? "ANY" : &ba_str[2], 
 | 
                ba_type == BA_PCM_TYPE_A2DP ? "A2DP" : "SCO"); 
 | 
  
 | 
        free(ba_str); 
 | 
    } 
 | 
  
 | 
    GError *err = NULL; 
 | 
    if ((dbus = g_dbus_connection_new_for_address_sync( 
 | 
                    g_dbus_address_get_for_bus_sync(G_BUS_TYPE_SYSTEM, NULL, NULL), 
 | 
                    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); 
 | 
        goto fail; 
 | 
    } 
 | 
  
 | 
    if ((ba_fd = bluealsa_open(ba_interface)) == -1 || 
 | 
            (ba_event_fd = bluealsa_open(ba_interface)) == -1) { 
 | 
        error("BlueALSA connection failed: %s", strerror(errno)); 
 | 
        goto fail; 
 | 
    } 
 | 
  
 | 
    if (bluealsa_subscribe(ba_event_fd, BA_EVENT_TRANSPORT_ADDED | BA_EVENT_TRANSPORT_REMOVED) == -1) { 
 | 
        error("BlueALSA subscription failed: %s", strerror(errno)); 
 | 
        goto fail; 
 | 
    } 
 | 
  
 | 
    struct sigaction sigact = { .sa_handler = main_loop_stop }; 
 | 
    sigaction(SIGTERM, &sigact, NULL); 
 | 
    sigaction(SIGINT, &sigact, NULL); 
 | 
  
 | 
    debug("Starting main loop"); 
 | 
    goto init; 
 | 
  
 | 
    while (main_loop_on) { 
 | 
  
 | 
        struct ba_msg_event event; 
 | 
        struct ba_msg_transport *transports; 
 | 
        ssize_t ret; 
 | 
        size_t i; 
 | 
  
 | 
        struct pollfd pfds[] = {{ ba_event_fd, POLLIN, 0 }}; 
 | 
        if (poll(pfds, ARRAYSIZE(pfds), -1) == -1 && errno == EINTR) 
 | 
            continue; 
 | 
  
 | 
        while ((ret = recv(ba_event_fd, &event, sizeof(event), MSG_DONTWAIT)) == -1 && errno == EINTR) 
 | 
            continue; 
 | 
        if (ret != sizeof(event)) { 
 | 
            error("Couldn't read event: %s", strerror(ret == -1 ? errno : EBADMSG)); 
 | 
            goto fail; 
 | 
        } 
 | 
  
 | 
init: 
 | 
        debug("Fetching available transports"); 
 | 
        if ((ret = bluealsa_get_transports(ba_fd, &transports)) == -1) { 
 | 
            error("Couldn't get transports: %s", strerror(errno)); 
 | 
            goto fail; 
 | 
        } 
 | 
  
 | 
        for (i = 0; i < workers_count; i++) 
 | 
            workers[i].eviction = true; 
 | 
  
 | 
        for (i = 0; i < (unsigned)ret; i++) { 
 | 
  
 | 
            size_t ii; 
 | 
  
 | 
            /* filter available transports by BT address (this check is omitted if 
 | 
             * any address can be used), transport type and stream direction */ 
 | 
            if (transports[i].type != ba_type) 
 | 
                continue; 
 | 
            if (transports[i].stream != BA_PCM_STREAM_CAPTURE && transports[i].stream != BA_PCM_STREAM_DUPLEX) 
 | 
                continue; 
 | 
            if (!ba_addr_any) { 
 | 
                bool matched = false; 
 | 
                for (ii = 0; ii < ba_addrs_count; ii++) 
 | 
                    if (bacmp(&ba_addrs[ii], &transports[i].addr) == 0) { 
 | 
                        matched = true; 
 | 
                        break; 
 | 
                    } 
 | 
                if (!matched) 
 | 
                    continue; 
 | 
            } 
 | 
  
 | 
            bool matched = false; 
 | 
            for (ii = 0; ii < workers_count; ii++) 
 | 
                if (bacmp(&workers[ii].transport.addr, &transports[i].addr) == 0) { 
 | 
                    workers[ii].eviction = false; 
 | 
                    matched = true; 
 | 
                    break; 
 | 
                } 
 | 
  
 | 
            /* start PCM worker thread */ 
 | 
            if (!matched) { 
 | 
                workers_count++; 
 | 
  
 | 
                if (workers_size < workers_count) { 
 | 
  
 | 
                    pthread_rwlock_wrlock(&workers_lock); 
 | 
  
 | 
                    workers_size += 4; /* coarse-grained realloc */ 
 | 
                    if ((workers = realloc(workers, sizeof(*workers) * workers_size)) == NULL) { 
 | 
                        error("Couldn't (re)allocate memory for PCM workers"); 
 | 
                        goto fail; 
 | 
                    } 
 | 
  
 | 
                    pthread_rwlock_unlock(&workers_lock); 
 | 
  
 | 
                } 
 | 
  
 | 
                struct pcm_worker *worker = &workers[workers_count - 1]; 
 | 
                memcpy(&worker->transport, &transports[i], sizeof(worker->transport)); 
 | 
                ba2str(&worker->transport.addr, worker->addr); 
 | 
                worker->eviction = false; 
 | 
                worker->active = false; 
 | 
                worker->pcm_fd = -1; 
 | 
                worker->ba_fd = -1; 
 | 
                worker->pcm = NULL; 
 | 
  
 | 
                debug("Creating PCM worker %s", worker->addr); 
 | 
  
 | 
                int ret; 
 | 
                if ((ret = pthread_create(&worker->thread, NULL, pcm_worker_routine, worker)) != 0) { 
 | 
                    warn("Couldn't create PCM worker %s: %s", worker->addr, strerror(ret)); 
 | 
                    workers_count--; 
 | 
                } 
 | 
  
 | 
            } 
 | 
  
 | 
        } 
 | 
  
 | 
        /* stop PCM workers designated for eviction */ 
 | 
        for (i = workers_count; i > 0; i--) { 
 | 
            struct pcm_worker *worker = &workers[i - 1]; 
 | 
            if (worker->eviction) { 
 | 
                pthread_cancel(worker->thread); 
 | 
                pthread_join(worker->thread, NULL); 
 | 
                memcpy(worker, &workers[workers_count - 1], sizeof(*worker)); 
 | 
                workers_count--; 
 | 
            } 
 | 
        } 
 | 
  
 | 
    } 
 | 
  
 | 
    goto success; 
 | 
  
 | 
fail: 
 | 
    status = EXIT_FAILURE; 
 | 
  
 | 
success: 
 | 
    if (ba_fd != -1) 
 | 
        close(ba_fd); 
 | 
    if (ba_event_fd != -1) 
 | 
        close(ba_event_fd); 
 | 
    return status; 
 | 
} 
 |