339 lines
7.8 KiB
C
339 lines
7.8 KiB
C
/*
|
|
* btd.inc
|
|
* vim: ft=c
|
|
*
|
|
* SPDX-FileCopyrightText: 2021-2025 BlueALSA developers
|
|
* SPDX-License-Identifier: MIT
|
|
*/
|
|
|
|
#if HAVE_CONFIG_H
|
|
# include <config.h>
|
|
#endif
|
|
|
|
#include <ctype.h>
|
|
#include <endian.h>
|
|
#include <errno.h>
|
|
#include <pthread.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
|
|
#include "a2dp.h"
|
|
#include "ba-transport.h"
|
|
#include "hfp.h"
|
|
#include "io.h"
|
|
#include "utils.h"
|
|
#include "shared/a2dp-codecs.h"
|
|
#include "shared/defs.h"
|
|
#include "shared/ffb.h"
|
|
#include "shared/log.h"
|
|
|
|
#define BT_DUMP_CURRENT_VERSION 2
|
|
|
|
/**
|
|
* BT dump mode. */
|
|
enum bt_dump_mode {
|
|
BT_DUMP_MODE_A2DP_SOURCE = 1,
|
|
BT_DUMP_MODE_A2DP_SINK,
|
|
BT_DUMP_MODE_ASHA_SOURCE,
|
|
BT_DUMP_MODE_ASHA_SINK,
|
|
BT_DUMP_MODE_SCO,
|
|
};
|
|
|
|
/**
|
|
* BT dump handle. */
|
|
struct bt_dump {
|
|
unsigned int version;
|
|
enum bt_dump_mode mode;
|
|
uint32_t transport_codec_id;
|
|
a2dp_t a2dp_configuration;
|
|
size_t a2dp_configuration_size;
|
|
FILE *file;
|
|
};
|
|
|
|
/**
|
|
* Read a single data record from a BT dump file. */
|
|
ssize_t bt_dump_read(struct bt_dump *btd, void *data, size_t size) {
|
|
uint16_t len;
|
|
if (fread(&len, sizeof(len), 1, btd->file) != 1)
|
|
return -1;
|
|
size = MIN(be16toh(len), size);
|
|
if (fread(data, 1, size, btd->file) != size)
|
|
return -1;
|
|
return size;
|
|
}
|
|
|
|
/**
|
|
* Write a single data record to a BT dump file. */
|
|
ssize_t bt_dump_write(struct bt_dump *btd, const void *data, size_t size) {
|
|
uint16_t len = htobe16(size);
|
|
if (fwrite(&len, sizeof(len), 1, btd->file) != 1)
|
|
return -1;
|
|
if (fwrite(data, 1, size, btd->file) != size)
|
|
return -1;
|
|
fflush(btd->file);
|
|
return sizeof(len) + size;
|
|
}
|
|
|
|
/**
|
|
* Close BT dump file. */
|
|
void bt_dump_close(struct bt_dump *btd) {
|
|
if (btd == NULL)
|
|
return;
|
|
if (btd->file != NULL)
|
|
fclose(btd->file);
|
|
free(btd);
|
|
}
|
|
|
|
/**
|
|
* Create BT dump file. */
|
|
struct bt_dump * bt_dump_create(
|
|
const char * path,
|
|
struct ba_transport * t) {
|
|
|
|
struct bt_dump * btd;
|
|
if ((btd = calloc(1, sizeof(*btd))) == NULL)
|
|
return NULL;
|
|
|
|
if ((btd->file = fopen(path, "wb")) == NULL)
|
|
goto fail;
|
|
|
|
enum bt_dump_mode mode = 0;
|
|
const void * a2dp_configuration = NULL;
|
|
size_t a2dp_configuration_size = 0;
|
|
|
|
switch (t->profile) {
|
|
case BA_TRANSPORT_PROFILE_NONE:
|
|
#if ENABLE_MIDI
|
|
case BA_TRANSPORT_PROFILE_MIDI:
|
|
#endif
|
|
break;
|
|
case BA_TRANSPORT_PROFILE_A2DP_SOURCE:
|
|
a2dp_configuration = &t->media.a2dp.configuration;
|
|
a2dp_configuration_size = t->media.a2dp.sep->config.caps_size;
|
|
mode = BT_DUMP_MODE_A2DP_SOURCE;
|
|
break;
|
|
case BA_TRANSPORT_PROFILE_A2DP_SINK:
|
|
a2dp_configuration = &t->media.a2dp.configuration;
|
|
a2dp_configuration_size = t->media.a2dp.sep->config.caps_size;
|
|
mode = BT_DUMP_MODE_A2DP_SINK;
|
|
break;
|
|
#if ENABLE_ASHA
|
|
case BA_TRANSPORT_PROFILE_ASHA_SOURCE:
|
|
mode = BT_DUMP_MODE_ASHA_SOURCE;
|
|
break;
|
|
case BA_TRANSPORT_PROFILE_ASHA_SINK:
|
|
mode = BT_DUMP_MODE_ASHA_SINK;
|
|
break;
|
|
#endif
|
|
case BA_TRANSPORT_PROFILE_HFP_AG:
|
|
case BA_TRANSPORT_PROFILE_HFP_HF:
|
|
case BA_TRANSPORT_PROFILE_HSP_AG:
|
|
case BA_TRANSPORT_PROFILE_HSP_HS:
|
|
mode = BT_DUMP_MODE_SCO;
|
|
break;
|
|
}
|
|
|
|
unsigned int mode_ = mode;
|
|
fprintf(btd->file, "BA.dump-%1x.%1x.", BT_DUMP_CURRENT_VERSION, mode_);
|
|
|
|
btd->transport_codec_id = ba_transport_get_codec(t);
|
|
uint32_t id = htobe32(btd->transport_codec_id);
|
|
if (bt_dump_write(btd, &id, sizeof(id)) == -1)
|
|
goto fail;
|
|
|
|
if (a2dp_configuration != NULL) {
|
|
memcpy(&btd->a2dp_configuration, a2dp_configuration,
|
|
MIN(a2dp_configuration_size, sizeof(btd->a2dp_configuration)));
|
|
btd->a2dp_configuration_size = a2dp_configuration_size;
|
|
if (bt_dump_write(btd, a2dp_configuration, a2dp_configuration_size) == -1)
|
|
goto fail;
|
|
}
|
|
|
|
return btd;
|
|
|
|
fail:
|
|
bt_dump_close(btd);
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Open BT dump file. */
|
|
struct bt_dump * bt_dump_open(const char * path) {
|
|
|
|
struct bt_dump * btd;
|
|
if ((btd = calloc(1, sizeof(*btd))) == NULL)
|
|
return NULL;
|
|
|
|
if ((btd->file = fopen(path, "rb")) == NULL)
|
|
goto fail;
|
|
|
|
unsigned int mode;
|
|
if (fscanf(btd->file, "BA.dump-%1x.%1x.", &btd->version, &mode) != 2) {
|
|
errno = EIO;
|
|
goto fail;
|
|
}
|
|
|
|
if (btd->version > BT_DUMP_CURRENT_VERSION) {
|
|
errno = EINVAL;
|
|
goto fail;
|
|
}
|
|
|
|
uint32_t id;
|
|
if (bt_dump_read(btd, &id, sizeof(id)) == -1)
|
|
goto fail;
|
|
btd->transport_codec_id = be32toh(id);
|
|
|
|
switch (btd->mode = mode) {
|
|
case BT_DUMP_MODE_A2DP_SOURCE:
|
|
case BT_DUMP_MODE_A2DP_SINK: {
|
|
ssize_t size = sizeof(btd->a2dp_configuration);
|
|
if ((size = bt_dump_read(btd, &btd->a2dp_configuration, size)) == -1)
|
|
goto fail;
|
|
btd->a2dp_configuration_size = size;
|
|
} break;
|
|
#if ENABLE_ASHA
|
|
case BT_DUMP_MODE_ASHA_SOURCE:
|
|
case BT_DUMP_MODE_ASHA_SINK:
|
|
#endif
|
|
case BT_DUMP_MODE_SCO:
|
|
break;
|
|
default:
|
|
errno = EINVAL;
|
|
goto fail;
|
|
}
|
|
|
|
return btd;
|
|
|
|
fail:
|
|
bt_dump_close(btd);
|
|
return NULL;
|
|
}
|
|
|
|
static const char * transport_to_fname(const struct ba_transport * t) {
|
|
|
|
const uint32_t codec_id = ba_transport_get_codec(t);
|
|
const char * profile = NULL;
|
|
const char * codec = NULL;
|
|
|
|
switch (t->profile) {
|
|
case BA_TRANSPORT_PROFILE_NONE:
|
|
return "none";
|
|
case BA_TRANSPORT_PROFILE_A2DP_SOURCE:
|
|
profile = "a2dp-source";
|
|
codec = a2dp_codecs_codec_id_to_string(codec_id);
|
|
break;
|
|
case BA_TRANSPORT_PROFILE_A2DP_SINK:
|
|
profile = "a2dp-sink";
|
|
codec = a2dp_codecs_codec_id_to_string(codec_id);
|
|
break;
|
|
#if ENABLE_ASHA
|
|
case BA_TRANSPORT_PROFILE_ASHA_SOURCE:
|
|
profile = "asha-source";
|
|
codec = asha_codec_id_to_string(codec_id);
|
|
break;
|
|
case BA_TRANSPORT_PROFILE_ASHA_SINK:
|
|
profile = "asha-sink";
|
|
codec = asha_codec_id_to_string(codec_id);
|
|
break;
|
|
#endif
|
|
case BA_TRANSPORT_PROFILE_HFP_AG:
|
|
profile = "hfp-ag";
|
|
codec = hfp_codec_id_to_string(codec_id);
|
|
break;
|
|
case BA_TRANSPORT_PROFILE_HFP_HF:
|
|
profile = "hfp-hf";
|
|
codec = hfp_codec_id_to_string(codec_id);
|
|
break;
|
|
case BA_TRANSPORT_PROFILE_HSP_AG:
|
|
profile = "hsp-ag";
|
|
codec = hfp_codec_id_to_string(codec_id);
|
|
break;
|
|
case BA_TRANSPORT_PROFILE_HSP_HS:
|
|
profile = "hsp-hs";
|
|
codec = hfp_codec_id_to_string(codec_id);
|
|
break;
|
|
#if ENABLE_MIDI
|
|
case BA_TRANSPORT_PROFILE_MIDI:
|
|
return "midi";
|
|
#endif
|
|
}
|
|
|
|
char fallback[16];
|
|
if (codec == NULL) {
|
|
snprintf(fallback, sizeof(fallback), "%08x", codec_id);
|
|
codec = fallback;
|
|
}
|
|
|
|
static char buffer[64];
|
|
snprintf(buffer, sizeof(buffer), "%s-%s", profile, codec);
|
|
return buffer;
|
|
}
|
|
|
|
static const char *transport_pcm_to_fname(const struct ba_transport_pcm *t_pcm) {
|
|
static char buffer[64];
|
|
snprintf(buffer, sizeof(buffer), "s%u-%u-%uc",
|
|
BA_TRANSPORT_PCM_FORMAT_WIDTH(t_pcm->format), t_pcm->rate, t_pcm->channels);
|
|
return buffer;
|
|
}
|
|
|
|
/**
|
|
* Dump incoming BT data to a BT dump file. */
|
|
void *bt_dump_io_thread(struct ba_transport_pcm *t_pcm) {
|
|
|
|
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
|
|
pthread_cleanup_push(PTHREAD_CLEANUP(ba_transport_pcm_thread_cleanup), t_pcm);
|
|
|
|
struct ba_transport *t = t_pcm->t;
|
|
struct io_poll io = { .timeout = -1 };
|
|
ffb_t bt = { 0 };
|
|
|
|
struct bt_dump *btd = NULL;
|
|
char fname[256];
|
|
|
|
snprintf(fname, sizeof(fname), "/tmp/bluealsa-%s-%s.btd",
|
|
transport_to_fname(t), transport_pcm_to_fname(t_pcm));
|
|
|
|
debug("Creating BT dump file: %s", fname);
|
|
if ((btd = bt_dump_create(fname, t)) == NULL) {
|
|
error("Couldn't create BT dump file: %s", strerror(errno));
|
|
goto fail_open;
|
|
}
|
|
|
|
pthread_cleanup_push(PTHREAD_CLEANUP(ffb_free), &bt);
|
|
pthread_cleanup_push(PTHREAD_CLEANUP(bt_dump_close), btd);
|
|
|
|
if (ffb_init_uint8_t(&bt, t->mtu_read) == -1) {
|
|
error("Couldn't create data buffer: %s", strerror(errno));
|
|
goto fail_ffb;
|
|
}
|
|
|
|
debug_transport_pcm_thread_loop(t_pcm, "START");
|
|
for (ba_transport_pcm_state_set_running(t_pcm);;) {
|
|
|
|
ssize_t len;
|
|
ffb_rewind(&bt);
|
|
if ((len = io_poll_and_read_bt(&io, t_pcm, &bt)) <= 0) {
|
|
if (len == -1)
|
|
error("BT poll and read error: %s", strerror(errno));
|
|
goto fail;
|
|
}
|
|
|
|
debug("BT read: %zd", len);
|
|
bt_dump_write(btd, bt.data, len);
|
|
|
|
}
|
|
|
|
fail:
|
|
debug_transport_pcm_thread_loop(t_pcm, "EXIT");
|
|
fail_ffb:
|
|
pthread_cleanup_pop(1);
|
|
pthread_cleanup_pop(1);
|
|
fail_open:
|
|
pthread_cleanup_pop(1);
|
|
return NULL;
|
|
}
|