/*
|
* BlueALSA - io.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.
|
*
|
*/
|
|
#include "io.h"
|
|
#include <ctype.h>
|
#include <errno.h>
|
#include <fcntl.h>
|
#include <math.h>
|
#include <poll.h>
|
#include <signal.h>
|
#include <stdlib.h>
|
#include <string.h>
|
#include <time.h>
|
#include <unistd.h>
|
#include <sys/ioctl.h>
|
#include <sys/socket.h>
|
#include <sys/types.h>
|
#include <sys/timerfd.h>
|
#include <bluetooth/bluetooth.h>
|
#include <bluetooth/hci.h>
|
#include <bluetooth/hci_lib.h>
|
#include <bluetooth/sco.h>
|
|
#include <sbc/sbc.h>
|
#if ENABLE_AAC
|
# include <fdk-aac/aacdecoder_lib.h>
|
# include <fdk-aac/aacenc_lib.h>
|
# define AACENCODER_LIB_VERSION LIB_VERSION( \
|
AACENCODER_LIB_VL0, AACENCODER_LIB_VL1, AACENCODER_LIB_VL2)
|
#endif
|
#if ENABLE_APTX
|
# include <openaptx.h>
|
#endif
|
#if ENABLE_LDAC
|
# include <ldacBT.h>
|
# include <ldacBT_abr.h>
|
#endif
|
|
#include "a2dp-codecs.h"
|
#include "a2dp-rtp.h"
|
#include "bluealsa.h"
|
#include "transport.h"
|
#include "utils.h"
|
#include "shared/defs.h"
|
#include "shared/ffb.h"
|
#include "shared/log.h"
|
#include "shared/rt.h"
|
|
|
/**
|
* Scale PCM signal according to the transport audio properties. */
|
static void io_thread_scale_pcm(const struct ba_transport *t, int16_t *buffer,
|
size_t samples, int channels) {
|
|
/* Get a snapshot of audio properties. Please note, that mutex is not
|
* required here, because we are not modifying these variables. */
|
uint8_t ch1_volume = t->a2dp.ch1_volume;
|
uint8_t ch2_volume = t->a2dp.ch2_volume;
|
|
double ch1_scale = 0;
|
double ch2_scale = 0;
|
|
if (!t->a2dp.ch1_muted)
|
ch1_scale = pow(10, (-64 + 64.0 * ch1_volume / 127) / 20);
|
if (!t->a2dp.ch2_muted)
|
ch2_scale = pow(10, (-64 + 64.0 * ch2_volume / 127) / 20);
|
|
snd_pcm_scale_s16le(buffer, samples, channels, ch1_scale, ch2_scale);
|
}
|
|
/**
|
* Read PCM signal from the transport PCM FIFO. */
|
static ssize_t io_thread_read_pcm(struct ba_pcm *pcm, int16_t *buffer, size_t samples) {
|
|
ssize_t ret;
|
|
/* If the passed file descriptor is invalid (e.g. -1) is means, that other
|
* thread (the controller) has closed the connection. If the connection was
|
* closed during this call, we will still read correct data, because Linux
|
* kernel does not decrement file descriptor reference counter until the
|
* read returns. */
|
while ((ret = read(pcm->fd, buffer, samples * sizeof(int16_t))) == -1 && errno == EINTR)
|
continue;
|
|
if (ret > 0)
|
return ret / sizeof(int16_t);
|
|
if (ret == 0)
|
debug("FIFO endpoint has been closed: %d", pcm->fd);
|
if (errno == EBADF)
|
ret = 0;
|
if (ret == 0)
|
transport_release_pcm(pcm);
|
|
return ret;
|
}
|
|
/**
|
* Write PCM signal to the transport PCM FIFO. */
|
static ssize_t io_thread_write_pcm(struct ba_pcm *pcm, const int16_t *buffer, size_t samples) {
|
|
const uint8_t *head = (uint8_t *)buffer;
|
size_t len = samples * sizeof(int16_t);
|
ssize_t ret;
|
|
do {
|
if ((ret = write(pcm->fd, head, len)) == -1) {
|
if (errno == EINTR)
|
continue;
|
if (errno == EPIPE) {
|
/* This errno value will be received only, when the SIGPIPE
|
* signal is caught, blocked or ignored. */
|
debug("FIFO endpoint has been closed: %d", pcm->fd);
|
transport_release_pcm(pcm);
|
return 0;
|
}
|
return ret;
|
}
|
head += ret;
|
len -= ret;
|
} while (len != 0);
|
|
/* It is guaranteed, that this function will write data atomically. */
|
return samples;
|
}
|
|
/**
|
* Write data to the BT SEQPACKET socket. */
|
static ssize_t io_thread_write_bt(const struct ba_transport *t,
|
const uint8_t *buffer, size_t len, int *coutq) {
|
|
struct pollfd pfd = { t->bt_fd, POLLOUT, 0 };
|
ssize_t ret;
|
|
if (ioctl(pfd.fd, TIOCOUTQ, coutq) == -1)
|
warn("Couldn't get BT queued bytes: %s", strerror(errno));
|
else
|
*coutq = abs(t->a2dp.bt_fd_coutq_init - *coutq);
|
|
retry:
|
if ((ret = write(pfd.fd, buffer, len)) == -1)
|
switch (errno) {
|
case EINTR:
|
goto retry;
|
case EAGAIN:
|
poll(&pfd, 1, -1);
|
/* set coutq to some arbitrary big value */
|
*coutq = 1024 * 16;
|
goto retry;
|
}
|
|
return ret;
|
}
|
|
/**
|
* Initialize RTP headers.
|
*
|
* @param s The memory area where the RTP headers will be initialized.
|
* @param hdr The address where the pointer to the RTP header will be stored.
|
* @param mhdr The address where the pointer to the RTP media payload header
|
* will be stored. This parameter might be NULL in order to omit RTP media
|
* payload header.
|
* @return This function returns the address of the RTP payload region. */
|
static uint8_t *io_thread_init_rtp(void *s, rtp_header_t **hdr, rtp_media_header_t **mhdr) {
|
|
rtp_header_t *header = *hdr = (rtp_header_t *)s;
|
memset(header, 0, RTP_HEADER_LEN);
|
header->paytype = 96;
|
header->version = 2;
|
header->seq_number = random();
|
header->timestamp = random();
|
|
uint8_t *data = (uint8_t *)&header->csrc[header->cc];
|
|
if (mhdr != NULL) {
|
memset(data, 0, sizeof(rtp_media_header_t));
|
*mhdr = (rtp_media_header_t *)data;
|
data += sizeof(rtp_media_header_t);
|
}
|
|
return data;
|
}
|
|
void *io_thread_a2dp_sink_sbc(void *arg) {
|
struct ba_transport *t = (struct ba_transport *)arg;
|
|
/* 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(transport_pthread_cleanup), t);
|
|
/* Lock transport during initialization stage. This lock will ensure,
|
* that no one will modify critical section until thread state can be
|
* known - initialization has failed or succeeded. */
|
bool locked = !transport_pthread_cleanup_lock(t);
|
|
if (t->bt_fd == -1) {
|
error("Invalid BT socket: %d", t->bt_fd);
|
goto fail_init;
|
}
|
|
/* Check for invalid (e.g. not set) reading MTU. If buffer allocation does
|
* not return NULL (allocating zero bytes might return NULL), we will read
|
* zero bytes from the BT socket, which will be wrongly identified as a
|
* "connection closed" action. */
|
if (t->mtu_read <= 0) {
|
error("Invalid reading MTU: %zu", t->mtu_read);
|
goto fail_init;
|
}
|
|
sbc_t sbc;
|
|
if ((errno = -sbc_init_a2dp(&sbc, 0, t->a2dp.cconfig, t->a2dp.cconfig_size)) != 0) {
|
error("Couldn't initialize SBC codec: %s", strerror(errno));
|
goto fail_init;
|
}
|
|
const unsigned int channels = transport_get_channels(t);
|
|
ffb_uint8_t bt = { 0 };
|
ffb_int16_t pcm = { 0 };
|
pthread_cleanup_push(PTHREAD_CLEANUP(sbc_finish), &sbc);
|
pthread_cleanup_push(PTHREAD_CLEANUP(ffb_uint8_free), &bt);
|
pthread_cleanup_push(PTHREAD_CLEANUP(ffb_int16_free), &pcm);
|
|
if (ffb_init(&pcm, sbc_get_codesize(&sbc)) == NULL ||
|
ffb_init(&bt, t->mtu_read) == NULL) {
|
error("Couldn't create data buffers: %s", strerror(ENOMEM));
|
goto fail_ffb;
|
}
|
|
/* Lock transport during thread cancellation. This handler shall be at
|
* the top of the cleanup stack - lastly pushed. */
|
pthread_cleanup_push(PTHREAD_CLEANUP(transport_pthread_cleanup_lock), t);
|
|
uint16_t seq_number = -1;
|
|
struct pollfd pfds[] = {
|
{ t->sig_fd[0], POLLIN, 0 },
|
{ -1, POLLIN, 0 },
|
};
|
|
transport_pthread_cleanup_unlock(t);
|
locked = false;
|
|
debug("Starting IO loop: %s (%s)",
|
bluetooth_profile_to_string(t->profile),
|
bluetooth_a2dp_codec_to_string(t->codec));
|
for (;;) {
|
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
|
|
ssize_t len;
|
|
/* add BT socket to the poll if transport is active */
|
pfds[1].fd = t->state == TRANSPORT_ACTIVE ? t->bt_fd : -1;
|
|
if (poll(pfds, ARRAYSIZE(pfds), -1) == -1) {
|
if (errno == EINTR)
|
continue;
|
error("Transport poll error: %s", strerror(errno));
|
goto fail;
|
}
|
|
if (pfds[0].revents & POLLIN) {
|
/* dispatch incoming event */
|
enum ba_transport_signal sig = -1;
|
if (read(pfds[0].fd, &sig, sizeof(sig)) != sizeof(sig))
|
warn("Couldn't read signal: %s", strerror(errno));
|
continue;
|
}
|
|
if ((len = read(pfds[1].fd, bt.tail, ffb_len_in(&bt))) == -1) {
|
debug("BT read error: %s", strerror(errno));
|
continue;
|
}
|
|
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
|
|
/* it seems that zero is never returned... */
|
if (len == 0) {
|
debug("BT socket has been closed: %d", pfds[1].fd);
|
/* Prevent sending the release request to the BlueZ. If the socket has
|
* been closed, it means that BlueZ has already closed the connection. */
|
close(pfds[1].fd);
|
t->bt_fd = -1;
|
goto fail;
|
}
|
|
if (t->a2dp.pcm.fd == -1) {
|
seq_number = -1;
|
continue;
|
}
|
|
const rtp_header_t *rtp_header = (rtp_header_t *)bt.data;
|
const rtp_media_header_t *rtp_media_header = (rtp_media_header_t *)&rtp_header->csrc[rtp_header->cc];
|
const uint8_t *rtp_payload = (uint8_t *)(rtp_media_header + 1);
|
size_t rtp_payload_len = len - ((void *)rtp_payload - (void *)rtp_header);
|
|
#if ENABLE_PAYLOADCHECK
|
if (rtp_header->paytype < 96) {
|
warn("Unsupported RTP payload type: %u", rtp_header->paytype);
|
continue;
|
}
|
#endif
|
|
uint16_t _seq_number = ntohs(rtp_header->seq_number);
|
if (++seq_number != _seq_number) {
|
if (seq_number != 0)
|
warn("Missing RTP packet: %u != %u", _seq_number, seq_number);
|
seq_number = _seq_number;
|
}
|
|
/* decode retrieved SBC frames */
|
size_t frames = rtp_media_header->frame_count;
|
while (frames--) {
|
|
ssize_t len;
|
size_t decoded;
|
|
if ((len = sbc_decode(&sbc, rtp_payload, rtp_payload_len,
|
pcm.data, ffb_blen_in(&pcm), &decoded)) < 0) {
|
error("SBC decoding error: %s", strerror(-len));
|
break;
|
}
|
|
rtp_payload += len;
|
rtp_payload_len -= len;
|
|
const size_t samples = decoded / sizeof(int16_t);
|
io_thread_scale_pcm(t, pcm.data, samples, channels);
|
if (io_thread_write_pcm(&t->a2dp.pcm, pcm.data, samples) == -1)
|
error("FIFO write error: %s", strerror(errno));
|
|
}
|
|
}
|
|
fail:
|
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
|
pthread_cleanup_pop(!locked);
|
fail_ffb:
|
pthread_cleanup_pop(1);
|
pthread_cleanup_pop(1);
|
pthread_cleanup_pop(1);
|
fail_init:
|
pthread_cleanup_pop(1);
|
return NULL;
|
}
|
|
void *io_thread_a2dp_source_sbc(void *arg) {
|
struct ba_transport *t = (struct ba_transport *)arg;
|
|
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
|
pthread_cleanup_push(PTHREAD_CLEANUP(transport_pthread_cleanup), t);
|
|
bool locked = !transport_pthread_cleanup_lock(t);
|
|
sbc_t sbc;
|
|
if ((errno = -sbc_init_a2dp(&sbc, 0, t->a2dp.cconfig, t->a2dp.cconfig_size)) != 0) {
|
error("Couldn't initialize SBC codec: %s", strerror(errno));
|
goto fail_init;
|
}
|
|
ffb_uint8_t bt = { 0 };
|
ffb_int16_t pcm = { 0 };
|
pthread_cleanup_push(PTHREAD_CLEANUP(ffb_uint8_free), &bt);
|
pthread_cleanup_push(PTHREAD_CLEANUP(ffb_int16_free), &pcm);
|
pthread_cleanup_push(PTHREAD_CLEANUP(sbc_finish), &sbc);
|
|
const size_t sbc_pcm_samples = sbc_get_codesize(&sbc) / sizeof(int16_t);
|
const size_t sbc_frame_len = sbc_get_frame_length(&sbc);
|
const unsigned int channels = transport_get_channels(t);
|
const unsigned int samplerate = transport_get_sampling(t);
|
|
/* Writing MTU should be big enough to contain RTP header, SBC payload
|
* header and at least one SBC frame. In general, there is no constraint
|
* for the MTU value, but the speed might suffer significantly. */
|
const size_t mtu_write_payload = t->mtu_write - RTP_HEADER_LEN - sizeof(rtp_media_header_t);
|
if (mtu_write_payload < sbc_frame_len) {
|
warn("Writing MTU too small for one single SBC frame: %zu < %zu",
|
t->mtu_write, RTP_HEADER_LEN + sizeof(rtp_media_header_t) + sbc_frame_len);
|
t->mtu_write = RTP_HEADER_LEN + sizeof(rtp_media_header_t) + sbc_frame_len;
|
}
|
|
if (ffb_init(&pcm, sbc_pcm_samples * (mtu_write_payload / sbc_frame_len)) == NULL ||
|
ffb_init(&bt, t->mtu_write) == NULL) {
|
error("Couldn't create data buffers: %s", strerror(ENOMEM));
|
goto fail_ffb;
|
}
|
|
pthread_cleanup_push(PTHREAD_CLEANUP(transport_pthread_cleanup_lock), t);
|
|
rtp_header_t *rtp_header;
|
rtp_media_header_t *rtp_media_header;
|
|
/* initialize RTP headers and get anchor for payload */
|
uint8_t *rtp_payload = io_thread_init_rtp(bt.data, &rtp_header, &rtp_media_header);
|
uint16_t seq_number = ntohs(rtp_header->seq_number);
|
uint32_t timestamp = ntohl(rtp_header->timestamp);
|
|
/* array with historical data of queued bytes for BT socket */
|
int coutq_history[IO_THREAD_COUTQ_HISTORY_SIZE] = { 0 };
|
size_t coutq_i = 0;
|
|
int poll_timeout = -1;
|
struct asrsync asrs = { .frames = 0 };
|
struct pollfd pfds[] = {
|
{ t->sig_fd[0], POLLIN, 0 },
|
{ -1, POLLIN, 0 },
|
};
|
|
transport_pthread_cleanup_unlock(t);
|
locked = false;
|
|
debug("Starting IO loop: %s (%s)",
|
bluetooth_profile_to_string(t->profile),
|
bluetooth_a2dp_codec_to_string(t->codec));
|
for (;;) {
|
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
|
|
ssize_t samples;
|
|
/* add PCM socket to the poll if transport is active */
|
pfds[1].fd = t->state == TRANSPORT_ACTIVE ? t->a2dp.pcm.fd : -1;
|
|
switch (poll(pfds, ARRAYSIZE(pfds), poll_timeout)) {
|
case 0:
|
pthread_cond_signal(&t->a2dp.pcm.drained);
|
poll_timeout = -1;
|
locked = !transport_pthread_cleanup_lock(t);
|
if (t->a2dp.pcm.fd == -1)
|
goto final;
|
transport_pthread_cleanup_unlock(t);
|
locked = false;
|
continue;
|
case -1:
|
if (errno == EINTR)
|
continue;
|
error("Transport poll error: %s", strerror(errno));
|
goto fail;
|
}
|
|
if (pfds[0].revents & POLLIN) {
|
/* dispatch incoming event */
|
enum ba_transport_signal sig = -1;
|
if (read(pfds[0].fd, &sig, sizeof(sig)) != sizeof(sig))
|
warn("Couldn't read signal: %s", strerror(errno));
|
switch (sig) {
|
case TRANSPORT_PCM_OPEN:
|
case TRANSPORT_PCM_RESUME:
|
poll_timeout = -1;
|
asrs.frames = 0;
|
break;
|
case TRANSPORT_PCM_CLOSE:
|
poll_timeout = config.a2dp.keep_alive * 1000;
|
break;
|
case TRANSPORT_PCM_SYNC:
|
poll_timeout = 100;
|
break;
|
default:
|
break;
|
}
|
continue;
|
}
|
|
/* read data from the FIFO - this function will block */
|
if ((samples = io_thread_read_pcm(&t->a2dp.pcm, pcm.tail, ffb_len_in(&pcm))) <= 0) {
|
if (samples == -1)
|
error("FIFO read error: %s", strerror(errno));
|
goto fail;
|
}
|
|
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
|
|
/* When the thread is created, there might be no data in the FIFO. In fact
|
* there might be no data for a long time - until client starts playback.
|
* In order to correctly calculate time drift, the zero time point has to
|
* be obtained after the stream has started. */
|
if (asrs.frames == 0)
|
asrsync_init(&asrs, samplerate);
|
|
if (!config.a2dp.volume)
|
/* scale volume or mute audio signal */
|
io_thread_scale_pcm(t, pcm.tail, samples, channels);
|
|
/* get overall number of input samples */
|
ffb_seek(&pcm, samples);
|
samples = ffb_len_out(&pcm);
|
|
/* anchor for RTP payload */
|
bt.tail = rtp_payload;
|
|
const int16_t *input = pcm.data;
|
size_t input_len = samples;
|
size_t output_len = ffb_len_in(&bt);
|
size_t pcm_frames = 0;
|
size_t sbc_frames = 0;
|
|
/* Generate as many SBC frames as possible to fill the output buffer
|
* without overflowing it. The size of the output buffer is based on
|
* the socket MTU, so such a transfer should be most efficient. */
|
while (input_len >= sbc_pcm_samples && output_len >= sbc_frame_len) {
|
|
ssize_t len;
|
ssize_t encoded;
|
|
if ((len = sbc_encode(&sbc, input, input_len * sizeof(int16_t),
|
bt.tail, output_len, &encoded)) < 0) {
|
error("SBC encoding error: %s", strerror(-len));
|
break;
|
}
|
|
len = len / sizeof(int16_t);
|
input += len;
|
input_len -= len;
|
ffb_seek(&bt, encoded);
|
output_len -= encoded;
|
pcm_frames += len / channels;
|
sbc_frames++;
|
|
}
|
|
rtp_header->seq_number = htons(++seq_number);
|
rtp_header->timestamp = htonl(timestamp);
|
rtp_media_header->frame_count = sbc_frames;
|
|
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
|
|
coutq_i = (coutq_i + 1) % ARRAYSIZE(coutq_history);
|
if (io_thread_write_bt(t, bt.data, ffb_len_out(&bt), &coutq_history[coutq_i]) == -1) {
|
if (errno == ECONNRESET || errno == ENOTCONN) {
|
/* exit thread upon BT socket disconnection */
|
debug("BT socket disconnected: %d", t->bt_fd);
|
goto fail;
|
}
|
error("BT socket write error: %s", strerror(errno));
|
}
|
|
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
|
|
/* keep data transfer at a constant bit rate, also
|
* get a timestamp for the next RTP frame */
|
asrsync_sync(&asrs, pcm_frames);
|
timestamp += pcm_frames * 10000 / samplerate;
|
|
/* update busy delay (encoding overhead) */
|
t->delay = asrsync_get_busy_usec(&asrs) / 100;
|
|
/* If the input buffer was not consumed (due to codesize limit), we
|
* have to append new data to the existing one. Since we do not use
|
* ring buffer, we will simply move unprocessed data to the front
|
* of our linear buffer. */
|
ffb_shift(&pcm, samples - input_len);
|
|
}
|
|
fail:
|
final:
|
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
|
pthread_cleanup_pop(!locked);
|
fail_ffb:
|
pthread_cleanup_pop(1);
|
pthread_cleanup_pop(1);
|
pthread_cleanup_pop(1);
|
fail_init:
|
pthread_cleanup_pop(1);
|
return NULL;
|
}
|
|
#if ENABLE_AAC
|
void *io_thread_a2dp_sink_aac(void *arg) {
|
struct ba_transport *t = (struct ba_transport *)arg;
|
|
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
|
pthread_cleanup_push(PTHREAD_CLEANUP(transport_pthread_cleanup), t);
|
|
bool locked = !transport_pthread_cleanup_lock(t);
|
|
if (t->bt_fd == -1) {
|
error("Invalid BT socket: %d", t->bt_fd);
|
goto fail_open;
|
}
|
if (t->mtu_read <= 0) {
|
error("Invalid reading MTU: %zu", t->mtu_read);
|
goto fail_open;
|
}
|
|
HANDLE_AACDECODER handle;
|
AAC_DECODER_ERROR err;
|
|
if ((handle = aacDecoder_Open(TT_MP4_LATM_MCP1, 1)) == NULL) {
|
error("Couldn't open AAC decoder");
|
goto fail_open;
|
}
|
|
pthread_cleanup_push(PTHREAD_CLEANUP(aacDecoder_Close), handle);
|
|
const unsigned int channels = transport_get_channels(t);
|
#ifdef AACDECODER_LIB_VL0
|
if ((err = aacDecoder_SetParam(handle, AAC_PCM_MIN_OUTPUT_CHANNELS, channels)) != AAC_DEC_OK) {
|
error("Couldn't set min output channels: %s", aacdec_strerror(err));
|
goto fail_init;
|
}
|
if ((err = aacDecoder_SetParam(handle, AAC_PCM_MAX_OUTPUT_CHANNELS, channels)) != AAC_DEC_OK) {
|
error("Couldn't set max output channels: %s", aacdec_strerror(err));
|
goto fail_init;
|
}
|
#else
|
if ((err = aacDecoder_SetParam(handle, AAC_PCM_OUTPUT_CHANNELS, channels)) != AAC_DEC_OK) {
|
error("Couldn't set output channels: %s", aacdec_strerror(err));
|
goto fail_init;
|
}
|
#endif
|
|
ffb_uint8_t bt = { 0 };
|
ffb_uint8_t latm = { 0 };
|
ffb_int16_t pcm = { 0 };
|
pthread_cleanup_push(PTHREAD_CLEANUP(ffb_uint8_free), &bt);
|
pthread_cleanup_push(PTHREAD_CLEANUP(ffb_uint8_free), &latm);
|
pthread_cleanup_push(PTHREAD_CLEANUP(ffb_int16_free), &pcm);
|
|
if (ffb_init(&pcm, 2048 * channels) == NULL ||
|
ffb_init(&latm, t->mtu_read) == NULL ||
|
ffb_init(&bt, t->mtu_read) == NULL) {
|
error("Couldn't create data buffers: %s", strerror(ENOMEM));
|
goto fail_ffb;
|
}
|
|
pthread_cleanup_push(PTHREAD_CLEANUP(transport_pthread_cleanup_lock), t);
|
|
uint16_t seq_number = -1;
|
int markbit_quirk = -3;
|
|
struct pollfd pfds[] = {
|
{ t->sig_fd[0], POLLIN, 0 },
|
{ -1, POLLIN, 0 },
|
};
|
|
transport_pthread_cleanup_unlock(t);
|
locked = false;
|
|
debug("Starting IO loop: %s (%s)",
|
bluetooth_profile_to_string(t->profile),
|
bluetooth_a2dp_codec_to_string(t->codec));
|
for (;;) {
|
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
|
|
CStreamInfo *aacinf;
|
ssize_t len;
|
|
/* add BT socket to the poll if transport is active */
|
pfds[1].fd = t->state == TRANSPORT_ACTIVE ? t->bt_fd : -1;
|
|
if (poll(pfds, ARRAYSIZE(pfds), -1) == -1) {
|
if (errno == EINTR)
|
continue;
|
error("Transport poll error: %s", strerror(errno));
|
goto fail;
|
}
|
|
if (pfds[0].revents & POLLIN) {
|
/* dispatch incoming event */
|
enum ba_transport_signal sig = -1;
|
if (read(pfds[0].fd, &sig, sizeof(sig)) != sizeof(sig))
|
warn("Couldn't read signal: %s", strerror(errno));
|
continue;
|
}
|
|
if ((len = read(pfds[1].fd, bt.tail, ffb_len_in(&bt))) == -1) {
|
debug("BT read error: %s", strerror(errno));
|
continue;
|
}
|
|
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
|
|
/* it seems that zero is never returned... */
|
if (len == 0) {
|
debug("BT socket has been closed: %d", pfds[1].fd);
|
/* Prevent sending the release request to the BlueZ. If the socket has
|
* been closed, it means that BlueZ has already closed the connection. */
|
close(pfds[1].fd);
|
t->bt_fd = -1;
|
goto fail;
|
}
|
|
if (t->a2dp.pcm.fd == -1) {
|
seq_number = -1;
|
continue;
|
}
|
|
const rtp_header_t *rtp_header = (rtp_header_t *)bt.data;
|
uint8_t *rtp_latm = (uint8_t *)&rtp_header->csrc[rtp_header->cc];
|
size_t rtp_latm_len = len - ((void *)rtp_latm - (void *)rtp_header);
|
|
#if ENABLE_PAYLOADCHECK
|
if (rtp_header->paytype < 96) {
|
warn("Unsupported RTP payload type: %u", rtp_header->paytype);
|
continue;
|
}
|
#endif
|
|
/* If in the first N packets mark bit is not set, it might mean, that
|
* the mark bit will not be set at all. In such a case, activate mark
|
* bit quirk workaround. */
|
if (markbit_quirk < 0) {
|
if (rtp_header->markbit)
|
markbit_quirk = 0;
|
else if (++markbit_quirk == 0) {
|
warn("Activating RTP mark bit quirk workaround");
|
markbit_quirk = 1;
|
}
|
}
|
|
uint16_t _seq_number = ntohs(rtp_header->seq_number);
|
if (++seq_number != _seq_number) {
|
if (seq_number != 0)
|
warn("Missing RTP packet: %u != %u", _seq_number, seq_number);
|
seq_number = _seq_number;
|
}
|
|
if (ffb_len_in(&latm) < rtp_latm_len) {
|
debug("Resizing LATM buffer: %zd -> %zd", latm.size, latm.size + t->mtu_read);
|
size_t prev_len = ffb_len_out(&latm);
|
ffb_init(&latm, latm.size + t->mtu_read);
|
ffb_seek(&latm, prev_len);
|
}
|
|
memcpy(latm.tail, rtp_latm, rtp_latm_len);
|
ffb_seek(&latm, rtp_latm_len);
|
|
if (markbit_quirk != 1 && !rtp_header->markbit) {
|
debug("Fragmented RTP packet [%u]: LATM len: %zd", seq_number, rtp_latm_len);
|
continue;
|
}
|
|
unsigned int data_len = ffb_len_out(&latm);
|
unsigned int valid = ffb_len_out(&latm);
|
|
if ((err = aacDecoder_Fill(handle, &latm.data, &data_len, &valid)) != AAC_DEC_OK)
|
error("AAC buffer fill error: %s", aacdec_strerror(err));
|
else if ((err = aacDecoder_DecodeFrame(handle, pcm.tail, ffb_blen_in(&pcm), 0)) != AAC_DEC_OK)
|
error("AAC decode frame error: %s", aacdec_strerror(err));
|
else if ((aacinf = aacDecoder_GetStreamInfo(handle)) == NULL)
|
error("Couldn't get AAC stream info");
|
else {
|
const size_t samples = aacinf->frameSize * aacinf->numChannels;
|
io_thread_scale_pcm(t, pcm.data, samples, channels);
|
if (io_thread_write_pcm(&t->a2dp.pcm, pcm.data, samples) == -1)
|
error("FIFO write error: %s", strerror(errno));
|
ffb_rewind(&latm);
|
}
|
|
}
|
|
fail:
|
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
|
pthread_cleanup_pop(!locked);
|
fail_ffb:
|
pthread_cleanup_pop(1);
|
pthread_cleanup_pop(1);
|
pthread_cleanup_pop(1);
|
fail_init:
|
pthread_cleanup_pop(1);
|
fail_open:
|
pthread_cleanup_pop(1);
|
return NULL;
|
}
|
#endif
|
|
#if ENABLE_AAC
|
void *io_thread_a2dp_source_aac(void *arg) {
|
struct ba_transport *t = (struct ba_transport *)arg;
|
const a2dp_aac_t *cconfig = (a2dp_aac_t *)t->a2dp.cconfig;
|
|
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
|
pthread_cleanup_push(PTHREAD_CLEANUP(transport_pthread_cleanup), t);
|
|
bool locked = !transport_pthread_cleanup_lock(t);
|
|
HANDLE_AACENCODER handle;
|
AACENC_InfoStruct aacinf;
|
AACENC_ERROR err;
|
|
/* create AAC encoder without the Meta Data module */
|
const unsigned int channels = transport_get_channels(t);
|
if ((err = aacEncOpen(&handle, 0x07, channels)) != AACENC_OK) {
|
error("Couldn't open AAC encoder: %s", aacenc_strerror(err));
|
goto fail_open;
|
}
|
|
pthread_cleanup_push(PTHREAD_CLEANUP(aacEncClose), &handle);
|
|
unsigned int aot = AOT_NONE;
|
unsigned int bitrate = AAC_GET_BITRATE(*cconfig);
|
unsigned int samplerate = transport_get_sampling(t);
|
unsigned int channelmode = channels == 1 ? MODE_1 : MODE_2;
|
|
switch (cconfig->object_type) {
|
case AAC_OBJECT_TYPE_MPEG2_AAC_LC:
|
#if AACENCODER_LIB_VERSION <= 0x03040C00 /* 3.4.12 */
|
aot = AOT_MP2_AAC_LC;
|
break;
|
#endif
|
case AAC_OBJECT_TYPE_MPEG4_AAC_LC:
|
aot = AOT_AAC_LC;
|
break;
|
case AAC_OBJECT_TYPE_MPEG4_AAC_LTP:
|
aot = AOT_AAC_LTP;
|
break;
|
case AAC_OBJECT_TYPE_MPEG4_AAC_SCA:
|
aot = AOT_AAC_SCAL;
|
break;
|
}
|
|
if ((err = aacEncoder_SetParam(handle, AACENC_AOT, aot)) != AACENC_OK) {
|
error("Couldn't set audio object type: %s", aacenc_strerror(err));
|
goto fail_init;
|
}
|
if ((err = aacEncoder_SetParam(handle, AACENC_BITRATE, bitrate)) != AACENC_OK) {
|
error("Couldn't set bitrate: %s", aacenc_strerror(err));
|
goto fail_init;
|
}
|
if ((err = aacEncoder_SetParam(handle, AACENC_SAMPLERATE, samplerate)) != AACENC_OK) {
|
error("Couldn't set sampling rate: %s", aacenc_strerror(err));
|
goto fail_init;
|
}
|
if ((err = aacEncoder_SetParam(handle, AACENC_CHANNELMODE, channelmode)) != AACENC_OK) {
|
error("Couldn't set channel mode: %s", aacenc_strerror(err));
|
goto fail_init;
|
}
|
if (cconfig->vbr) {
|
if ((err = aacEncoder_SetParam(handle, AACENC_BITRATEMODE, config.aac_vbr_mode)) != AACENC_OK) {
|
error("Couldn't set VBR bitrate mode %u: %s", config.aac_vbr_mode, aacenc_strerror(err));
|
goto fail_init;
|
}
|
}
|
if ((err = aacEncoder_SetParam(handle, AACENC_AFTERBURNER, config.aac_afterburner)) != AACENC_OK) {
|
error("Couldn't enable afterburner: %s", aacenc_strerror(err));
|
goto fail_init;
|
}
|
if ((err = aacEncoder_SetParam(handle, AACENC_TRANSMUX, TT_MP4_LATM_MCP1)) != AACENC_OK) {
|
error("Couldn't enable LATM transport type: %s", aacenc_strerror(err));
|
goto fail_init;
|
}
|
if ((err = aacEncoder_SetParam(handle, AACENC_HEADER_PERIOD, 1)) != AACENC_OK) {
|
error("Couldn't set LATM header period: %s", aacenc_strerror(err));
|
goto fail_init;
|
}
|
|
if ((err = aacEncEncode(handle, NULL, NULL, NULL, NULL)) != AACENC_OK) {
|
error("Couldn't initialize AAC encoder: %s", aacenc_strerror(err));
|
goto fail_init;
|
}
|
if ((err = aacEncInfo(handle, &aacinf)) != AACENC_OK) {
|
error("Couldn't get encoder info: %s", aacenc_strerror(err));
|
goto fail_init;
|
}
|
|
ffb_uint8_t bt = { 0 };
|
ffb_int16_t pcm = { 0 };
|
pthread_cleanup_push(PTHREAD_CLEANUP(ffb_uint8_free), &bt);
|
pthread_cleanup_push(PTHREAD_CLEANUP(ffb_int16_free), &pcm);
|
|
if (ffb_init(&pcm, aacinf.inputChannels * aacinf.frameLength) == NULL ||
|
ffb_init(&bt, RTP_HEADER_LEN + aacinf.maxOutBufBytes) == NULL) {
|
error("Couldn't create data buffers: %s", strerror(ENOMEM));
|
goto fail_ffb;
|
}
|
|
pthread_cleanup_push(PTHREAD_CLEANUP(transport_pthread_cleanup_lock), t);
|
|
rtp_header_t *rtp_header;
|
|
/* initialize RTP header and get anchor for payload */
|
uint8_t *rtp_payload = io_thread_init_rtp(bt.data, &rtp_header, NULL);
|
uint16_t seq_number = ntohs(rtp_header->seq_number);
|
uint32_t timestamp = ntohl(rtp_header->timestamp);
|
|
int in_bufferIdentifiers[] = { IN_AUDIO_DATA };
|
int out_bufferIdentifiers[] = { OUT_BITSTREAM_DATA };
|
int in_bufSizes[] = { pcm.size * sizeof(*pcm.data) };
|
int out_bufSizes[] = { aacinf.maxOutBufBytes };
|
int in_bufElSizes[] = { sizeof(*pcm.data) };
|
int out_bufElSizes[] = { sizeof(*bt.data) };
|
|
AACENC_BufDesc in_buf = {
|
.numBufs = 1,
|
.bufs = (void **)&pcm.data,
|
.bufferIdentifiers = in_bufferIdentifiers,
|
.bufSizes = in_bufSizes,
|
.bufElSizes = in_bufElSizes,
|
};
|
AACENC_BufDesc out_buf = {
|
.numBufs = 1,
|
.bufs = (void **)&rtp_payload,
|
.bufferIdentifiers = out_bufferIdentifiers,
|
.bufSizes = out_bufSizes,
|
.bufElSizes = out_bufElSizes,
|
};
|
AACENC_InArgs in_args = { 0 };
|
AACENC_OutArgs out_args = { 0 };
|
|
/* array with historical data of queued bytes for BT socket */
|
int coutq_history[IO_THREAD_COUTQ_HISTORY_SIZE] = { 0 };
|
size_t coutq_i = 0;
|
|
int poll_timeout = -1;
|
struct asrsync asrs = { .frames = 0 };
|
struct pollfd pfds[] = {
|
{ t->sig_fd[0], POLLIN, 0 },
|
{ -1, POLLIN, 0 },
|
};
|
|
transport_pthread_cleanup_unlock(t);
|
locked = false;
|
|
debug("Starting IO loop: %s (%s)",
|
bluetooth_profile_to_string(t->profile),
|
bluetooth_a2dp_codec_to_string(t->codec));
|
for (;;) {
|
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
|
|
ssize_t samples;
|
|
/* add PCM socket to the poll if transport is active */
|
pfds[1].fd = t->state == TRANSPORT_ACTIVE ? t->a2dp.pcm.fd : -1;
|
|
switch (poll(pfds, ARRAYSIZE(pfds), poll_timeout)) {
|
case 0:
|
pthread_cond_signal(&t->a2dp.pcm.drained);
|
poll_timeout = -1;
|
locked = !transport_pthread_cleanup_lock(t);
|
if (t->a2dp.pcm.fd == -1)
|
goto final;
|
transport_pthread_cleanup_unlock(t);
|
locked = false;
|
continue;
|
case -1:
|
if (errno == EINTR)
|
continue;
|
error("Transport poll error: %s", strerror(errno));
|
goto fail;
|
}
|
|
if (pfds[0].revents & POLLIN) {
|
/* dispatch incoming event */
|
enum ba_transport_signal sig = -1;
|
if (read(pfds[0].fd, &sig, sizeof(sig)) != sizeof(sig))
|
warn("Couldn't read signal: %s", strerror(errno));
|
switch (sig) {
|
case TRANSPORT_PCM_OPEN:
|
case TRANSPORT_PCM_RESUME:
|
poll_timeout = -1;
|
asrs.frames = 0;
|
break;
|
case TRANSPORT_PCM_CLOSE:
|
poll_timeout = config.a2dp.keep_alive * 1000;
|
break;
|
case TRANSPORT_PCM_SYNC:
|
poll_timeout = 100;
|
break;
|
default:
|
break;
|
}
|
continue;
|
}
|
|
/* read data from the FIFO - this function will block */
|
if ((samples = io_thread_read_pcm(&t->a2dp.pcm, pcm.tail, ffb_len_in(&pcm))) <= 0) {
|
if (samples == -1)
|
error("FIFO read error: %s", strerror(errno));
|
goto fail;
|
}
|
|
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
|
|
if (asrs.frames == 0)
|
asrsync_init(&asrs, samplerate);
|
|
if (!config.a2dp.volume)
|
/* scale volume or mute audio signal */
|
io_thread_scale_pcm(t, pcm.tail, samples, channels);
|
|
/* move tail pointer */
|
ffb_seek(&pcm, samples);
|
|
while ((in_args.numInSamples = ffb_len_out(&pcm)) > 0) {
|
|
if ((err = aacEncEncode(handle, &in_buf, &out_buf, &in_args, &out_args)) != AACENC_OK)
|
error("AAC encoding error: %s", aacenc_strerror(err));
|
|
if (out_args.numOutBytes > 0) {
|
|
size_t payload_len_max = t->mtu_write - RTP_HEADER_LEN;
|
size_t payload_len = out_args.numOutBytes;
|
rtp_header->timestamp = htonl(timestamp);
|
|
/* If the size of the RTP packet exceeds writing MTU, the RTP payload
|
* should be fragmented. According to the RFC 3016, fragmentation of
|
* the audioMuxElement requires no extra header - the payload should
|
* be fragmented and spread across multiple RTP packets. */
|
for (;;) {
|
|
ssize_t ret;
|
size_t len;
|
|
len = payload_len > payload_len_max ? payload_len_max : payload_len;
|
rtp_header->markbit = payload_len <= payload_len_max;
|
rtp_header->seq_number = htons(++seq_number);
|
|
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
|
|
coutq_i = (coutq_i + 1) % ARRAYSIZE(coutq_history);
|
if ((ret = io_thread_write_bt(t, bt.data, RTP_HEADER_LEN + len, &coutq_history[coutq_i])) == -1) {
|
if (errno == ECONNRESET || errno == ENOTCONN) {
|
/* exit thread upon BT socket disconnection */
|
debug("BT socket disconnected: %d", t->bt_fd);
|
goto fail;
|
}
|
error("BT socket write error: %s", strerror(errno));
|
break;
|
}
|
|
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
|
|
/* account written payload only */
|
ret -= RTP_HEADER_LEN;
|
|
/* break if the last part of the payload has been written */
|
if ((payload_len -= ret) == 0)
|
break;
|
|
/* move rest of data to the beginning of the payload */
|
debug("Payload fragmentation: extra %zd bytes", payload_len);
|
memmove(rtp_payload, rtp_payload + ret, payload_len);
|
|
}
|
|
}
|
|
/* keep data transfer at a constant bit rate, also
|
* get a timestamp for the next RTP frame */
|
unsigned int frames = out_args.numInSamples / channels;
|
asrsync_sync(&asrs, frames);
|
timestamp += frames * 10000 / samplerate;
|
|
/* update busy delay (encoding overhead) */
|
t->delay = asrsync_get_busy_usec(&asrs) / 100;
|
|
/* If the input buffer was not consumed, we have to append new data to
|
* the existing one. Since we do not use ring buffer, we will simply
|
* move unprocessed data to the front of our linear buffer. */
|
ffb_shift(&pcm, out_args.numInSamples);
|
|
}
|
|
}
|
|
fail:
|
final:
|
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
|
pthread_cleanup_pop(!locked);
|
fail_ffb:
|
pthread_cleanup_pop(1);
|
pthread_cleanup_pop(1);
|
fail_init:
|
pthread_cleanup_pop(1);
|
fail_open:
|
pthread_cleanup_pop(1);
|
return NULL;
|
}
|
#endif
|
|
#if ENABLE_APTX
|
void *io_thread_a2dp_source_aptx(void *arg) {
|
struct ba_transport *t = (struct ba_transport *)arg;
|
|
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
|
pthread_cleanup_push(PTHREAD_CLEANUP(transport_pthread_cleanup), t);
|
|
bool locked = !transport_pthread_cleanup_lock(t);
|
|
APTXENC handle = malloc(SizeofAptxbtenc());
|
pthread_cleanup_push(PTHREAD_CLEANUP(free), handle);
|
|
if (handle == NULL || aptxbtenc_init(handle, __BYTE_ORDER == __LITTLE_ENDIAN) != 0) {
|
error("Couldn't initialize apt-X encoder: %s", strerror(errno));
|
goto fail_init;
|
}
|
|
ffb_uint8_t bt = { 0 };
|
ffb_int16_t pcm = { 0 };
|
pthread_cleanup_push(PTHREAD_CLEANUP(ffb_uint8_free), &bt);
|
pthread_cleanup_push(PTHREAD_CLEANUP(ffb_int16_free), &pcm);
|
|
const unsigned int channels = transport_get_channels(t);
|
const size_t aptx_pcm_samples = 4 * channels;
|
const size_t aptx_code_len = 2 * sizeof(uint16_t);
|
const size_t mtu_write = t->mtu_write;
|
|
if (ffb_init(&pcm, aptx_pcm_samples * (mtu_write / aptx_code_len)) == NULL ||
|
ffb_init(&bt, mtu_write) == NULL) {
|
error("Couldn't create data buffers: %s", strerror(ENOMEM));
|
goto fail_ffb;
|
}
|
|
pthread_cleanup_push(PTHREAD_CLEANUP(transport_pthread_cleanup_lock), t);
|
|
/* array with historical data of queued bytes for BT socket */
|
int coutq_history[IO_THREAD_COUTQ_HISTORY_SIZE] = { 0 };
|
size_t coutq_i = 0;
|
|
int poll_timeout = -1;
|
struct asrsync asrs = { .frames = 0 };
|
struct pollfd pfds[] = {
|
{ t->sig_fd[0], POLLIN, 0 },
|
{ -1, POLLIN, 0 },
|
};
|
|
transport_pthread_cleanup_unlock(t);
|
locked = false;
|
|
debug("Starting IO loop: %s (%s)",
|
bluetooth_profile_to_string(t->profile),
|
bluetooth_a2dp_codec_to_string(t->codec));
|
for (;;) {
|
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
|
|
ssize_t samples;
|
|
/* add PCM socket to the poll if transport is active */
|
pfds[1].fd = t->state == TRANSPORT_ACTIVE ? t->a2dp.pcm.fd : -1;
|
|
switch (poll(pfds, ARRAYSIZE(pfds), poll_timeout)) {
|
case 0:
|
pthread_cond_signal(&t->a2dp.pcm.drained);
|
poll_timeout = -1;
|
locked = !transport_pthread_cleanup_lock(t);
|
if (t->a2dp.pcm.fd == -1)
|
goto final;
|
transport_pthread_cleanup_unlock(t);
|
locked = false;
|
continue;
|
case -1:
|
if (errno == EINTR)
|
continue;
|
error("Transport poll error: %s", strerror(errno));
|
goto fail;
|
}
|
|
if (pfds[0].revents & POLLIN) {
|
/* dispatch incoming event */
|
enum ba_transport_signal sig = -1;
|
if (read(pfds[0].fd, &sig, sizeof(sig)) != sizeof(sig))
|
warn("Couldn't read signal: %s", strerror(errno));
|
switch (sig) {
|
case TRANSPORT_PCM_OPEN:
|
case TRANSPORT_PCM_RESUME:
|
poll_timeout = -1;
|
asrs.frames = 0;
|
break;
|
case TRANSPORT_PCM_CLOSE:
|
poll_timeout = config.a2dp.keep_alive * 1000;
|
break;
|
case TRANSPORT_PCM_SYNC:
|
poll_timeout = 100;
|
break;
|
default:
|
break;
|
}
|
continue;
|
}
|
|
/* read data from the FIFO - this function will block */
|
if ((samples = io_thread_read_pcm(&t->a2dp.pcm, pcm.tail, ffb_len_in(&pcm))) <= 0) {
|
if (samples == -1)
|
error("FIFO read error: %s", strerror(errno));
|
goto fail;
|
}
|
|
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
|
|
if (asrs.frames == 0)
|
asrsync_init(&asrs, transport_get_sampling(t));
|
|
if (!config.a2dp.volume)
|
/* scale volume or mute audio signal */
|
io_thread_scale_pcm(t, pcm.tail, samples, channels);
|
|
/* get overall number of input samples */
|
ffb_seek(&pcm, samples);
|
samples = ffb_len_out(&pcm);
|
|
int16_t *input = pcm.data;
|
size_t input_len = samples;
|
|
/* encode and transfer obtained data */
|
while (input_len >= aptx_pcm_samples) {
|
|
size_t output_len = ffb_len_in(&bt);
|
size_t pcm_frames = 0;
|
|
/* Generate as many apt-X frames as possible to fill the output buffer
|
* without overflowing it. The size of the output buffer is based on
|
* the socket MTU, so such a transfer should be most efficient. */
|
while (input_len >= aptx_pcm_samples && output_len >= aptx_code_len) {
|
|
int32_t pcm_l[4];
|
int32_t pcm_r[4];
|
size_t i;
|
|
for (i = 0; i < 4; i++) {
|
pcm_l[i] = input[2 * i];
|
pcm_r[i] = input[2 * i + 1];
|
}
|
|
if (aptxbtenc_encodestereo(handle, pcm_l, pcm_r, (uint16_t *)bt.tail) != 0) {
|
error("Apt-X encoding error: %s", strerror(errno));
|
break;
|
}
|
|
input += 4 * channels;
|
input_len -= 4 * channels;
|
ffb_seek(&bt, aptx_code_len);
|
output_len -= aptx_code_len;
|
pcm_frames += 4;
|
|
}
|
|
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
|
|
coutq_i = (coutq_i + 1) % ARRAYSIZE(coutq_history);
|
if (io_thread_write_bt(t, bt.data, ffb_len_out(&bt), &coutq_history[coutq_i]) == -1) {
|
if (errno == ECONNRESET || errno == ENOTCONN) {
|
/* exit thread upon BT socket disconnection */
|
debug("BT socket disconnected: %d", t->bt_fd);
|
goto fail;
|
}
|
error("BT socket write error: %s", strerror(errno));
|
}
|
|
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
|
|
/* keep data transfer at a constant bit rate */
|
asrsync_sync(&asrs, pcm_frames);
|
|
/* update busy delay (encoding overhead) */
|
t->delay = asrsync_get_busy_usec(&asrs) / 100;
|
|
/* reinitialize output buffer */
|
ffb_rewind(&bt);
|
|
}
|
|
/* If the input buffer was not consumed (due to codesize limit), we
|
* have to append new data to the existing one. Since we do not use
|
* ring buffer, we will simply move unprocessed data to the front
|
* of our linear buffer. */
|
ffb_shift(&pcm, samples - input_len);
|
|
}
|
|
fail:
|
final:
|
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
|
pthread_cleanup_pop(!locked);
|
fail_ffb:
|
pthread_cleanup_pop(1);
|
pthread_cleanup_pop(1);
|
fail_init:
|
pthread_cleanup_pop(1);
|
pthread_cleanup_pop(1);
|
return NULL;
|
}
|
#endif
|
|
#if ENABLE_LDAC
|
void *io_thread_a2dp_source_ldac(void *arg) {
|
struct ba_transport *t = (struct ba_transport *)arg;
|
const a2dp_ldac_t *cconfig = (a2dp_ldac_t *)t->a2dp.cconfig;
|
|
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
|
pthread_cleanup_push(PTHREAD_CLEANUP(transport_pthread_cleanup), t);
|
|
bool locked = !transport_pthread_cleanup_lock(t);
|
|
HANDLE_LDAC_BT handle;
|
HANDLE_LDAC_ABR handle_abr;
|
|
if ((handle = ldacBT_get_handle()) == NULL) {
|
error("Couldn't open LDAC encoder: %s", strerror(errno));
|
goto fail_open_ldac;
|
}
|
|
pthread_cleanup_push(PTHREAD_CLEANUP(ldacBT_free_handle), handle);
|
|
if ((handle_abr = ldac_ABR_get_handle()) == NULL) {
|
error("Couldn't open LDAC ABR: %s", strerror(errno));
|
goto fail_open_ldac_abr;
|
}
|
|
pthread_cleanup_push(PTHREAD_CLEANUP(ldac_ABR_free_handle), handle_abr);
|
|
const unsigned int channels = transport_get_channels(t);
|
const unsigned int samplerate = transport_get_sampling(t);
|
const size_t ldac_pcm_samples = LDACBT_ENC_LSU * channels;
|
|
if (ldacBT_init_handle_encode(handle, t->mtu_write - RTP_HEADER_LEN - sizeof(rtp_media_header_t),
|
config.ldac_eqmid, cconfig->channel_mode, LDACBT_SMPL_FMT_S16, samplerate) == -1) {
|
error("Couldn't initialize LDAC encoder: %s", ldacBT_strerror(ldacBT_get_error_code(handle)));
|
goto fail_init;
|
}
|
|
if (ldac_ABR_Init(handle_abr, 1000 * ldac_pcm_samples / channels / samplerate) == -1) {
|
error("Couldn't initialize LDAC ABR");
|
goto fail_init;
|
}
|
if (ldac_ABR_set_thresholds(handle_abr, 6, 4, 2) == -1) {
|
error("Couldn't set LDAC ABR thresholds");
|
goto fail_init;
|
}
|
|
ffb_uint8_t bt = { 0 };
|
ffb_int16_t pcm = { 0 };
|
pthread_cleanup_push(PTHREAD_CLEANUP(ffb_uint8_free), &bt);
|
pthread_cleanup_push(PTHREAD_CLEANUP(ffb_int16_free), &pcm);
|
|
if (ffb_init(&pcm, ldac_pcm_samples) == NULL ||
|
ffb_init(&bt, t->mtu_write) == NULL) {
|
error("Couldn't create data buffers: %s", strerror(ENOMEM));
|
goto fail_ffb;
|
}
|
|
pthread_cleanup_push(PTHREAD_CLEANUP(transport_pthread_cleanup_lock), t);
|
|
rtp_header_t *rtp_header;
|
rtp_media_header_t *rtp_media_header;
|
|
/* initialize RTP headers and get anchor for payload */
|
bt.tail = io_thread_init_rtp(bt.data, &rtp_header, &rtp_media_header);
|
uint16_t seq_number = ntohs(rtp_header->seq_number);
|
uint32_t timestamp = ntohl(rtp_header->timestamp);
|
size_t ts_frames = 0;
|
|
/* number of queued bytes in the BT socket */
|
int coutq = 0;
|
|
int poll_timeout = -1;
|
struct asrsync asrs = { .frames = 0 };
|
struct pollfd pfds[] = {
|
{ t->sig_fd[0], POLLIN, 0 },
|
{ -1, POLLIN, 0 },
|
};
|
|
transport_pthread_cleanup_unlock(t);
|
locked = false;
|
|
debug("Starting IO loop: %s (%s)",
|
bluetooth_profile_to_string(t->profile),
|
bluetooth_a2dp_codec_to_string(t->codec));
|
for (;;) {
|
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
|
|
ssize_t samples;
|
|
/* add PCM socket to the poll if transport is active */
|
pfds[1].fd = t->state == TRANSPORT_ACTIVE ? t->a2dp.pcm.fd : -1;
|
|
switch (poll(pfds, ARRAYSIZE(pfds), poll_timeout)) {
|
case 0:
|
pthread_cond_signal(&t->a2dp.pcm.drained);
|
poll_timeout = -1;
|
locked = !transport_pthread_cleanup_lock(t);
|
if (t->a2dp.pcm.fd == -1)
|
goto final;
|
transport_pthread_cleanup_unlock(t);
|
locked = false;
|
continue;
|
case -1:
|
if (errno == EINTR)
|
continue;
|
error("Transport poll error: %s", strerror(errno));
|
goto fail;
|
}
|
|
if (pfds[0].revents & POLLIN) {
|
/* dispatch incoming event */
|
enum ba_transport_signal sig = -1;
|
if (read(pfds[0].fd, &sig, sizeof(sig)) != sizeof(sig))
|
warn("Couldn't read signal: %s", strerror(errno));
|
switch (sig) {
|
case TRANSPORT_PCM_OPEN:
|
case TRANSPORT_PCM_RESUME:
|
poll_timeout = -1;
|
asrs.frames = 0;
|
break;
|
case TRANSPORT_PCM_CLOSE:
|
poll_timeout = config.a2dp.keep_alive * 1000;
|
break;
|
case TRANSPORT_PCM_SYNC:
|
poll_timeout = 100;
|
break;
|
default:
|
break;
|
}
|
continue;
|
}
|
|
/* read data from the FIFO - this function will block */
|
if ((samples = io_thread_read_pcm(&t->a2dp.pcm, pcm.tail, ffb_len_in(&pcm))) <= 0) {
|
if (samples == -1)
|
error("FIFO read error: %s", strerror(errno));
|
goto fail;
|
}
|
|
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
|
|
if (asrs.frames == 0)
|
asrsync_init(&asrs, samplerate);
|
|
if (!config.a2dp.volume)
|
/* scale volume or mute audio signal */
|
io_thread_scale_pcm(t, pcm.tail, samples, channels);
|
|
/* get overall number of input samples */
|
ffb_seek(&pcm, samples);
|
samples = ffb_len_out(&pcm);
|
|
int16_t *input = pcm.data;
|
size_t input_len = samples;
|
|
/* encode and transfer obtained data */
|
while (input_len >= ldac_pcm_samples) {
|
|
int len;
|
int encoded;
|
int frames;
|
|
if (ldacBT_encode(handle, input, &len, bt.tail, &encoded, &frames) != 0) {
|
error("LDAC encoding error: %s", ldacBT_strerror(ldacBT_get_error_code(handle)));
|
break;
|
}
|
|
rtp_media_header->frame_count = frames;
|
|
frames = len / sizeof(int16_t);
|
input += frames;
|
input_len -= frames;
|
|
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
|
|
if (encoded &&
|
io_thread_write_bt(t, bt.data, ffb_len_out(&bt) + encoded, &coutq) == -1) {
|
if (errno == ECONNRESET || errno == ENOTCONN) {
|
/* exit thread upon BT socket disconnection */
|
debug("BT socket disconnected: %d", t->bt_fd);
|
goto fail;
|
}
|
error("BT socket write error: %s", strerror(errno));
|
}
|
|
if (config.ldac_abr)
|
ldac_ABR_Proc(handle, handle_abr, coutq / t->mtu_write, 1);
|
|
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
|
|
/* keep data transfer at a constant bit rate */
|
asrsync_sync(&asrs, frames / channels);
|
ts_frames += frames;
|
|
/* update busy delay (encoding overhead) */
|
t->delay = asrsync_get_busy_usec(&asrs) / 100;
|
|
if (encoded) {
|
timestamp += ts_frames / channels * 10000 / samplerate;
|
rtp_header->timestamp = htonl(timestamp);
|
rtp_header->seq_number = htons(++seq_number);
|
ts_frames = 0;
|
}
|
|
}
|
|
/* If the input buffer was not consumed (due to codesize limit), we
|
* have to append new data to the existing one. Since we do not use
|
* ring buffer, we will simply move unprocessed data to the front
|
* of our linear buffer. */
|
ffb_shift(&pcm, samples - input_len);
|
|
}
|
|
fail:
|
final:
|
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
|
pthread_cleanup_pop(!locked);
|
fail_ffb:
|
pthread_cleanup_pop(1);
|
pthread_cleanup_pop(1);
|
fail_init:
|
pthread_cleanup_pop(1);
|
fail_open_ldac_abr:
|
pthread_cleanup_pop(1);
|
fail_open_ldac:
|
pthread_cleanup_pop(1);
|
return NULL;
|
}
|
#endif
|
|
static void close_lsocket(struct ba_transport *t)
|
{
|
if (t->sco.listen_fd > 0) {
|
close(t->sco.listen_fd);
|
t->sco.listen_fd = -1;
|
}
|
}
|
|
static int bind_sco(struct ba_transport *t, int sock)
|
{
|
struct sockaddr_sco addr;
|
struct hci_dev_info di;
|
|
if (hci_devinfo(t->device->hci_dev_id, &di) == -1) {
|
error("Couldn't get HCI device info: %s", strerror(errno));
|
return -1;
|
}
|
|
memset(&addr, 0, sizeof(addr));
|
addr.sco_family = AF_BLUETOOTH;
|
bacpy(&addr.sco_bdaddr, &di.bdaddr);
|
|
if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
|
error("Couldn't bind sco socket");
|
return -1;
|
}
|
|
return 0;
|
}
|
|
static int timeout_set(int fd, unsigned int msec)
|
{
|
struct itimerspec itimer;
|
unsigned int sec = msec / 1000;
|
|
memset(&itimer, 0, sizeof(itimer));
|
itimer.it_interval.tv_sec = 0;
|
itimer.it_interval.tv_nsec = 0;
|
itimer.it_value.tv_sec = sec;
|
itimer.it_value.tv_nsec = (msec - (sec * 1000)) * 1000 * 1000;
|
|
return timerfd_settime(fd, 0, &itimer, NULL);
|
}
|
|
static void close_timerfd(void *data)
|
{
|
int fd = (int)data;
|
|
close(fd);
|
}
|
|
void *io_thread_sco(void *arg) {
|
struct ba_transport *t = (struct ba_transport *)arg;
|
int defer = 1;
|
int sco_timer;
|
int timer_added = 0;
|
|
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
|
pthread_cleanup_push(PTHREAD_CLEANUP(transport_pthread_cleanup), t);
|
|
/* buffers for transferring data to and fro SCO socket */
|
ffb_uint8_t bt_in = { 0 };
|
ffb_uint8_t bt_out = { 0 };
|
pthread_cleanup_push(PTHREAD_CLEANUP(ffb_uint8_free), &bt_in);
|
pthread_cleanup_push(PTHREAD_CLEANUP(ffb_uint8_free), &bt_out);
|
|
/* these buffers shall be bigger than the SCO MTU */
|
if (ffb_init(&bt_in, 128) == NULL ||
|
ffb_init(&bt_out, 128) == NULL) {
|
error("Couldn't create data buffer: %s", strerror(ENOMEM));
|
goto fail_ffb;
|
}
|
|
t->sco.listen_fd = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO);
|
if (t->sco.listen_fd == -1) {
|
error("Couldn't open sco socket: %s", strerror(errno));
|
goto fail_ffb;
|
}
|
pthread_cleanup_push(PTHREAD_CLEANUP(close_lsocket), t);
|
if (bind_sco(t, t->sco.listen_fd) < 0) {
|
error("Couldn't bind sco socket");
|
goto fail_sock;
|
}
|
if (setsockopt(t->sco.listen_fd, SOL_BLUETOOTH, BT_DEFER_SETUP,
|
&defer, sizeof(defer)) < 0) {
|
error("Couldn't set defer for sco");
|
/* Ignore this error */
|
}
|
if (listen(t->sco.listen_fd, 1) < 0) {
|
error("Couldn't listen %s", strerror(errno));
|
goto fail_sock;
|
}
|
|
sco_timer = timerfd_create(CLOCK_REALTIME, 0);
|
if (sco_timer == -1) {
|
error("Couldn't create sco timer %s", strerror(errno));
|
goto fail_sock;
|
}
|
pthread_cleanup_push(PTHREAD_CLEANUP(close_timerfd), (void *)sco_timer);
|
|
int poll_timeout = -1;
|
struct asrsync asrs = { .frames = 0 };
|
struct pollfd pfds[] = {
|
{ t->sig_fd[0], POLLIN, 0 },
|
/* SCO socket */
|
{ -1, POLLIN, 0 },
|
{ -1, POLLOUT, 0 },
|
/* PCM FIFO */
|
{ -1, POLLIN, 0 },
|
{ -1, POLLOUT, 0 },
|
{ t->sco.listen_fd, POLLIN, 0 },
|
{ sco_timer, POLLIN, 0 }, /* [6] for sco timer */
|
};
|
|
debug("Starting IO loop: %s",
|
bluetooth_profile_to_string(t->profile));
|
for (;;) {
|
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
|
|
/* fresh-start for file descriptors polling */
|
pfds[1].fd = pfds[2].fd = -1;
|
pfds[3].fd = pfds[4].fd = -1;
|
|
switch (t->codec) {
|
case HFP_CODEC_CVSD:
|
default:
|
if (t->mtu_read > 0 && ffb_len_in(&bt_in) >= t->mtu_read)
|
pfds[1].fd = t->bt_fd;
|
if (t->mtu_write > 0 && ffb_len_out(&bt_out) >= t->mtu_write)
|
pfds[2].fd = t->bt_fd;
|
if (t->mtu_write > 0 && ffb_len_in(&bt_out) >= t->mtu_write)
|
pfds[3].fd = t->sco.spk_pcm.fd;
|
if (ffb_len_out(&bt_in) > 0)
|
pfds[4].fd = t->sco.mic_pcm.fd;
|
}
|
|
if (t->sco.mic_pcm.fd == -1)
|
pfds[1].fd = -1;
|
|
switch (poll(pfds, ARRAYSIZE(pfds), poll_timeout)) {
|
case 0:
|
pthread_cond_signal(&t->sco.spk_pcm.drained);
|
poll_timeout = -1;
|
continue;
|
case -1:
|
if (errno == EINTR)
|
continue;
|
error("Transport poll error: %s", strerror(errno));
|
goto fail;
|
}
|
|
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
|
|
if (pfds[0].revents & POLLIN) {
|
/* dispatch incoming event */
|
|
enum ba_transport_signal sig = -1;
|
if (read(pfds[0].fd, &sig, sizeof(sig)) != sizeof(sig))
|
warn("Couldn't read signal: %s", strerror(errno));
|
|
/* FIXME: Drain functionality for speaker.
|
* XXX: Right now it is not possible to drain speaker PCM (in a clean
|
* fashion), because poll() will not timeout if we've got incoming
|
* data from the microphone (BT SCO socket). In order not to hang
|
* forever in the transport_drain_pcm() function, we will signal
|
* PCM drain right now. */
|
if (sig == TRANSPORT_PCM_SYNC)
|
pthread_cond_signal(&t->sco.spk_pcm.drained);
|
|
/* Received TRANSPORT_PCM_OPEN from client or rfcomm
|
* thread (callsetup=0/1, call=1).
|
* So at least three open events will be received when
|
* answer a call
|
*/
|
const enum hfp_ind *inds = t->sco.rfcomm->rfcomm.hfp_inds;
|
if (sig == TRANSPORT_PCM_OPEN) {
|
info("Received transport pcm open event");
|
}
|
|
if (sig == TRANSPORT_PCM_CLOSE) {
|
info("Received transport pcm close event");
|
/* Don't disconnect SCO link positively */
|
/* transport_release_bt_sco(t); */
|
asrs.frames = 0;
|
}
|
|
continue;
|
}
|
|
if (asrs.frames == 0)
|
asrsync_init(&asrs, transport_get_sampling(t));
|
|
/* t->bt_fd */
|
if (pfds[1].revents & POLLIN) {
|
/* dispatch incoming SCO data */
|
|
uint8_t *buffer;
|
size_t buffer_len;
|
ssize_t len;
|
|
switch (t->codec) {
|
case HFP_CODEC_CVSD:
|
default:
|
buffer = bt_in.tail;
|
buffer_len = ffb_len_in(&bt_in);
|
}
|
|
retry_sco_read:
|
errno = 0;
|
if ((len = read(pfds[1].fd, buffer, buffer_len)) <= 0)
|
switch (errno) {
|
case EINTR:
|
goto retry_sco_read;
|
case 0:
|
case ECONNABORTED:
|
case ECONNRESET:
|
transport_release_bt_sco(t);
|
continue;
|
default:
|
error("SCO read error: %s", strerror(errno));
|
continue;
|
}
|
|
switch (t->codec) {
|
case HFP_CODEC_CVSD:
|
default:
|
ffb_seek(&bt_in, len);
|
}
|
|
}
|
else if (pfds[1].revents & (POLLERR | POLLHUP)) {
|
debug("SCO poll error status: %#x", pfds[1].revents);
|
transport_release_bt_sco(t);
|
}
|
|
/* t->bt_fd */
|
if (pfds[2].revents & POLLOUT) {
|
/* write-out SCO data */
|
|
uint8_t *buffer;
|
size_t buffer_len;
|
ssize_t len;
|
|
switch (t->codec) {
|
case HFP_CODEC_CVSD:
|
default:
|
buffer = bt_out.data;
|
buffer_len = t->mtu_write;
|
}
|
|
retry_sco_write:
|
errno = 0;
|
if ((len = write(pfds[2].fd, buffer, buffer_len)) <= 0)
|
switch (errno) {
|
case EINTR:
|
goto retry_sco_write;
|
case 0:
|
case ECONNABORTED:
|
case ECONNRESET:
|
transport_release_bt_sco(t);
|
continue;
|
default:
|
error("SCO write error: %s", strerror(errno));
|
continue;
|
}
|
|
switch (t->codec) {
|
case HFP_CODEC_CVSD:
|
default:
|
ffb_shift(&bt_out, len);
|
}
|
|
}
|
|
/* t->sco.spk_pcm.fd */
|
if (pfds[3].revents & POLLIN) {
|
/* dispatch incoming PCM data */
|
|
int16_t *buffer;
|
ssize_t samples;
|
|
switch (t->codec) {
|
case HFP_CODEC_CVSD:
|
default:
|
buffer = (int16_t *)bt_out.tail;
|
samples = ffb_len_in(&bt_out) / sizeof(int16_t);
|
}
|
|
/* read data from the FIFO - this function will block */
|
if ((samples = io_thread_read_pcm(&t->sco.spk_pcm, buffer, samples)) <= 0) {
|
if (samples == -1)
|
error("FIFO read error: %s", strerror(errno));
|
continue;
|
}
|
|
if (t->sco.spk_muted)
|
snd_pcm_scale_s16le(buffer, samples, 1, 0, 0);
|
|
switch (t->codec) {
|
case HFP_CODEC_CVSD:
|
default:
|
ffb_seek(&bt_out, samples * sizeof(int16_t));
|
}
|
|
}
|
else if (pfds[3].revents & (POLLERR | POLLHUP)) {
|
debug("PCM poll error status: %#x", pfds[3].revents);
|
close(t->sco.spk_pcm.fd);
|
t->sco.spk_pcm.fd = -1;
|
}
|
|
/* t->sco.mic_pcm.fd */
|
if (pfds[4].revents & POLLOUT) {
|
/* write-out PCM data */
|
|
int16_t *buffer;
|
ssize_t samples;
|
|
switch (t->codec) {
|
case HFP_CODEC_CVSD:
|
default:
|
buffer = (int16_t *)bt_in.data;
|
samples = ffb_len_out(&bt_in) / sizeof(int16_t);
|
}
|
|
if (t->sco.mic_muted)
|
snd_pcm_scale_s16le(buffer, samples, 1, 0, 0);
|
|
if (io_thread_write_pcm(&t->sco.mic_pcm, buffer, samples) == -1)
|
error("FIFO write error: %s", strerror(errno));
|
|
switch (t->codec) {
|
case HFP_CODEC_CVSD:
|
default:
|
ffb_shift(&bt_in, samples * sizeof(int16_t));
|
}
|
|
}
|
|
/* sco_timer */
|
if (pfds[6].revents & POLLIN) {
|
bool release = false;
|
const enum hfp_ind *inds = t->sco.rfcomm->rfcomm.hfp_inds;
|
|
info("SCO timer expired");
|
|
if (t->bt_fd != -1) {
|
warn("Timer expired but sco link was created");
|
continue;
|
}
|
|
/* TODO: Should we need to check client */
|
/* if (t->sco.spk_pcm.fd == -1 && t->sco.mic_pcm.fd == -1)
|
* release = true;
|
*/
|
|
if (t->profile == BLUETOOTH_PROFILE_HFP_HF &&
|
inds[HFP_IND_CALL] == HFP_IND_CALL_ACTIVE)
|
transport_acquire_bt_sco(t);
|
|
continue;
|
}
|
|
/* t->sco.listen_fd */
|
if (pfds[5].revents & POLLIN) {
|
int sock;
|
struct sockaddr_sco addr;
|
socklen_t len = sizeof(addr);
|
const enum hfp_ind *inds = t->sco.rfcomm->rfcomm.hfp_inds;
|
bool release = false;
|
|
info("SCO connection created by peer");
|
sock = accept(pfds[5].fd, (struct sockaddr *)&addr,
|
&len);
|
if (sock < 0) {
|
error("Couldn't accept sco connection");
|
continue;
|
}
|
|
transport_acquire_bt_sco2(t, sock);
|
/* Kill timer that is used to create sco link */
|
timeout_set(sco_timer, 0);
|
|
continue;
|
}
|
|
/* keep data transfer at a constant bit rate */
|
asrsync_sync(&asrs, 48 / 2);
|
/* update busy delay (encoding overhead) */
|
t->delay = asrsync_get_busy_usec(&asrs) / 100;
|
|
}
|
|
fail:
|
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
|
pthread_cleanup_pop(1); /* for sco timer */
|
fail_sock:
|
pthread_cleanup_pop(1); /* for sco listen sock */
|
fail_ffb:
|
pthread_cleanup_pop(1);
|
pthread_cleanup_pop(1);
|
pthread_cleanup_pop(1);
|
return NULL;
|
}
|
|
#if DEBUG
|
/**
|
* Dump incoming BT data to a file. */
|
void *io_thread_a2dp_sink_dump(void *arg) {
|
struct ba_transport *t = (struct ba_transport *)arg;
|
|
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
|
pthread_cleanup_push(PTHREAD_CLEANUP(transport_pthread_cleanup), t);
|
|
ffb_uint8_t bt = { 0 };
|
FILE *f = NULL;
|
char fname[64];
|
char *ptr;
|
|
sprintf(fname, "/tmp/ba-%s-%s.dump",
|
bluetooth_profile_to_string(t->profile),
|
bluetooth_a2dp_codec_to_string(t->codec));
|
for (ptr = fname; *ptr != '\0'; ptr++) {
|
*ptr = tolower(*ptr);
|
if (*ptr == ' ')
|
*ptr = '-';
|
}
|
|
debug("Opening BT dump file: %s", fname);
|
if ((f = fopen(fname, "wb")) == NULL) {
|
error("Couldn't create dump file: %s", strerror(errno));
|
goto fail_open;
|
}
|
|
pthread_cleanup_push(PTHREAD_CLEANUP(ffb_uint8_free), &bt);
|
pthread_cleanup_push(PTHREAD_CLEANUP(fclose), f);
|
|
if (ffb_init(&bt, t->mtu_read) == NULL) {
|
error("Couldn't create data buffer: %s", strerror(ENOMEM));
|
goto fail_ffb;
|
}
|
|
struct pollfd pfds[] = {
|
{ t->sig_fd[0], POLLIN, 0 },
|
{ t->bt_fd, POLLIN, 0 },
|
};
|
|
for (;;) {
|
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
|
|
ssize_t len;
|
|
if (poll(pfds, ARRAYSIZE(pfds), -1) == -1) {
|
if (errno == EINTR)
|
continue;
|
error("Transport poll error: %s", strerror(errno));
|
goto fail;
|
}
|
|
if (pfds[0].revents & POLLIN) {
|
if (read(pfds[0].fd, bt.data, ffb_blen_in(&bt)) == -1)
|
warn("Couldn't read signal: %s", strerror(errno));
|
continue;
|
}
|
|
if ((len = read(pfds[1].fd, bt.tail, ffb_len_in(&bt))) == -1) {
|
debug("BT read error: %s", strerror(errno));
|
continue;
|
}
|
|
debug("BT read: %zd", len);
|
fwrite(bt.data, 1, len, f);
|
}
|
|
fail:
|
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
|
fail_ffb:
|
pthread_cleanup_pop(1);
|
pthread_cleanup_pop(1);
|
fail_open:
|
pthread_cleanup_pop(1);
|
return NULL;
|
}
|
#endif
|