--- /dev/null
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "a2dp-codec-api.h"
+
+typedef struct codec_info {
+ pa_sample_spec sample_spec;
+} codec_info_t;
+
+static void *init(bool for_encoding, bool for_backchannel, const uint8_t *config_buffer, uint8_t config_size, pa_sample_spec *sample_spec, pa_core *core) {
+ codec_info_t *info;
+
+ info = pa_xnew0(codec_info_t, 1);
+
+ info->sample_spec.format = PA_SAMPLE_S16LE;
+ info->sample_spec.channels = 1;
+ info->sample_spec.rate = 8000;
+
+ *sample_spec = info->sample_spec;
+
+ return info;
+}
+
+static void deinit(void *codec_info) {
+ pa_xfree(codec_info);
+}
+
+static int reset(void *codec_info) {
+ return 0;
+}
+
+static size_t get_block_size(void *codec_info, size_t link_mtu) {
+ codec_info_t *info = (codec_info_t *) codec_info;
+ size_t block_size = link_mtu;
+
+ if (!pa_frame_aligned(block_size, &info->sample_spec)) {
+ pa_log_debug("Got invalid block size: %lu, rounding down", block_size);
+ block_size = pa_frame_align(block_size, &info->sample_spec);
+ }
+
+ return block_size;
+}
+
+static size_t get_encoded_block_size(void *codec_info, size_t input_size) {
+ codec_info_t *info = (codec_info_t *) codec_info;
+
+ /* input size should be aligned to sample spec */
+ pa_assert_fp(pa_frame_aligned(input_size, &info->sample_spec));
+
+ return input_size;
+}
+
+static size_t reduce_encoder_bitrate(void *codec_info, size_t write_link_mtu) {
+ return 0;
+}
+
+static size_t increase_encoder_bitrate(void *codec_info, size_t write_link_mtu) {
+ return 0;
+}
+
+static size_t encode_buffer(void *codec_info, uint32_t timestamp, const uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size, size_t *processed) {
+ pa_assert(input_size <= output_size);
+
+ memcpy(output_buffer, input_buffer, input_size);
+ *processed = input_size;
+
+ return input_size;
+}
+
+static size_t decode_buffer(void *codec_info, const uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size, size_t *processed) {
+ codec_info_t *info = (codec_info_t *) codec_info;
+
+ *processed = input_size;
+
+ /* In some rare occasions, we might receive packets of a very strange
+ * size. This could potentially be possible if the SCO packet was
+ * received partially over-the-air, or more probably due to hardware
+ * issues in our Bluetooth adapter. In these cases, in order to avoid
+ * an assertion failure due to unaligned data, just discard the whole
+ * packet */
+ if (!pa_frame_aligned(input_size, &info->sample_spec)) {
+ pa_log_warn("SCO packet received of unaligned size: %zu", input_size);
+ return 0;
+ }
+
+ memcpy(output_buffer, input_buffer, input_size);
+
+ return input_size;
+}
+
+/* dummy passthrough codec used with HSP/HFP CVSD */
+const pa_a2dp_codec pa_bt_codec_cvsd = {
+ .name = "CVSD",
+ .description = "CVSD",
+ .init = init,
+ .deinit = deinit,
+ .reset = reset,
+ .get_read_block_size = get_block_size,
+ .get_write_block_size = get_block_size,
+ .get_encoded_block_size = get_encoded_block_size,
+ .reduce_encoder_bitrate = reduce_encoder_bitrate,
+ .increase_encoder_bitrate = increase_encoder_bitrate,
+ .encode_buffer = encode_buffer,
+ .decode_buffer = decode_buffer,
+};
/* Run from I/O thread */
static void transport_config_mtu(struct userdata *u) {
- if (u->profile == PA_BLUETOOTH_PROFILE_HSP_HS
- || u->profile == PA_BLUETOOTH_PROFILE_HSP_AG
- || u->profile == PA_BLUETOOTH_PROFILE_HFP_HF
- || u->profile == PA_BLUETOOTH_PROFILE_HFP_AG) {
- u->read_block_size = u->read_link_mtu;
- u->write_block_size = u->write_link_mtu;
+ pa_assert(u->bt_codec);
- if (!pa_frame_aligned(u->read_block_size, &u->source->sample_spec)) {
- pa_log_debug("Got invalid read MTU: %lu, rounding down", u->read_block_size);
- u->read_block_size = pa_frame_align(u->read_block_size, &u->source->sample_spec);
- }
+ if (u->encoder_info) {
+ u->write_block_size = u->bt_codec->get_write_block_size(u->encoder_info, u->write_link_mtu);
if (!pa_frame_aligned(u->write_block_size, &u->sink->sample_spec)) {
pa_log_debug("Got invalid write MTU: %lu, rounding down", u->write_block_size);
u->write_block_size = pa_frame_align(u->write_block_size, &u->sink->sample_spec);
}
- } else {
- pa_assert(u->bt_codec);
- if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK) {
- u->write_block_size = u->bt_codec->get_write_block_size(u->encoder_info, u->write_link_mtu);
- } else {
- u->read_block_size = u->bt_codec->get_read_block_size(u->decoder_info, u->read_link_mtu);
+ }
+
+ if (u->decoder_info) {
+ u->read_block_size = u->bt_codec->get_read_block_size(u->decoder_info, u->read_link_mtu);
+
+ if (!pa_frame_aligned(u->read_block_size, &u->source->sample_spec)) {
+ pa_log_debug("Got invalid read MTU: %lu, rounding down", u->read_block_size);
+ u->read_block_size = pa_frame_align(u->read_block_size, &u->source->sample_spec);
}
}
pa_log_info("Transport %s resuming", u->transport->path);
- if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK) {
- pa_assert(u->bt_codec);
+ pa_assert(u->bt_codec);
+
+ if (u->encoder_info) {
if (u->bt_codec->reset(u->encoder_info) < 0)
return -1;
- } else if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE) {
- pa_assert(u->bt_codec);
+ }
+
+ if (u->decoder_info) {
if (u->bt_codec->reset(u->decoder_info) < 0)
return -1;
}
}
/* Run from main thread */
-static int transport_config(struct userdata *u) {
- /* reset encoder buffer contents */
- u->encoder_buffer_used = 0;
+static pa_direction_t get_profile_direction(pa_bluetooth_profile_t p) {
+ static const pa_direction_t profile_direction[] = {
+ [PA_BLUETOOTH_PROFILE_A2DP_SINK] = PA_DIRECTION_OUTPUT,
+ [PA_BLUETOOTH_PROFILE_A2DP_SOURCE] = PA_DIRECTION_INPUT,
+ [PA_BLUETOOTH_PROFILE_HSP_HS] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT,
+ [PA_BLUETOOTH_PROFILE_HSP_AG] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT,
+ [PA_BLUETOOTH_PROFILE_HFP_HF] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT,
+ [PA_BLUETOOTH_PROFILE_HFP_AG] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT,
+ [PA_BLUETOOTH_PROFILE_OFF] = 0
+ };
- if (u->profile == PA_BLUETOOTH_PROFILE_HSP_HS
- || u->profile == PA_BLUETOOTH_PROFILE_HSP_AG
- || u->profile == PA_BLUETOOTH_PROFILE_HFP_HF
- || u->profile == PA_BLUETOOTH_PROFILE_HFP_AG) {
- u->encoder_sample_spec.format = PA_SAMPLE_S16LE;
- u->encoder_sample_spec.channels = 1;
- u->encoder_sample_spec.rate = 8000;
- u->decoder_sample_spec.format = PA_SAMPLE_S16LE;
- u->decoder_sample_spec.channels = 1;
- u->decoder_sample_spec.rate = 8000;
- return 0;
- } else {
- bool is_a2dp_sink = u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK;
- void *info;
+ return profile_direction[p];
+}
- pa_assert(u->transport);
+/* Run from main thread */
+static int transport_config(struct userdata *u) {
+ pa_assert(u);
+ pa_assert(u->transport);
+ pa_assert(!u->bt_codec);
+ pa_assert(!u->encoder_info);
+ pa_assert(!u->decoder_info);
- pa_assert(!u->bt_codec);
- pa_assert(!u->encoder_info);
- pa_assert(!u->decoder_info);
+ u->bt_codec = u->transport->bt_codec;
+ pa_assert(u->bt_codec);
- u->bt_codec = u->transport->a2dp_codec;
- pa_assert(u->bt_codec);
+ /* reset encoder buffer contents */
+ u->encoder_buffer_used = 0;
- info = u->bt_codec->init(is_a2dp_sink, false, u->transport->config, u->transport->config_size, is_a2dp_sink ? &u->encoder_sample_spec : &u->decoder_sample_spec, u->core);
- if (is_a2dp_sink)
- u->encoder_info = info;
- else
- u->decoder_info = info;
+ if (get_profile_direction(u->profile) & PA_DIRECTION_OUTPUT) {
+ u->encoder_info = u->bt_codec->init(true, false, u->transport->config, u->transport->config_size, &u->encoder_sample_spec, u->core);
- if (!info)
+ if (!u->encoder_info)
return -1;
+ }
- return 0;
+ if (get_profile_direction(u->profile) & PA_DIRECTION_INPUT) {
+ u->decoder_info = u->bt_codec->init(false, false, u->transport->config, u->transport->config_size, &u->decoder_sample_spec, u->core);
+
+ if (!u->decoder_info) {
+ if (u->encoder_info) {
+ u->bt_codec->deinit(u->encoder_info);
+ u->encoder_info = NULL;
+ }
+ return -1;
+ }
}
+
+ return 0;
}
/* Run from main thread */
}
/* Run from main thread */
-static pa_direction_t get_profile_direction(pa_bluetooth_profile_t p) {
- static const pa_direction_t profile_direction[] = {
- [PA_BLUETOOTH_PROFILE_A2DP_SINK] = PA_DIRECTION_OUTPUT,
- [PA_BLUETOOTH_PROFILE_A2DP_SOURCE] = PA_DIRECTION_INPUT,
- [PA_BLUETOOTH_PROFILE_HSP_HS] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT,
- [PA_BLUETOOTH_PROFILE_HSP_AG] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT,
- [PA_BLUETOOTH_PROFILE_HFP_HF] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT,
- [PA_BLUETOOTH_PROFILE_HFP_AG] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT,
- [PA_BLUETOOTH_PROFILE_OFF] = 0
- };
-
- return profile_direction[p];
-}
-
-/* Run from main thread */
static int init_profile(struct userdata *u) {
int r = 0;
pa_assert(u);
skip_bytes -= bytes_to_render;
}
- if (u->write_index > 0 && u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK) {
+ if (u->write_index > 0 && (get_profile_direction(u->profile) & PA_DIRECTION_OUTPUT)) {
size_t new_write_block_size = u->bt_codec->reduce_encoder_bitrate(u->encoder_info, u->write_link_mtu);
if (new_write_block_size) {
u->write_block_size = new_write_block_size;
sleep_for = time_passed < next_write_at ? next_write_at - time_passed : 0;
/* pa_log("Sleeping for %lu; time passed %lu, next write at %lu", (unsigned long) sleep_for, (unsigned long) time_passed, (unsigned long)next_write_at); */
- if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK && u->write_memchunk.memblock == NULL) {
+ if ((get_profile_direction(u->profile) & PA_DIRECTION_OUTPUT) && u->write_memchunk.memblock == NULL) {
/* write_block() is keeping up with input, try increasing bitrate */
if (u->bt_codec->increase_encoder_bitrate
&& pa_timeval_age(&tv_last_output_rate_change) >= u->device->output_rate_refresh_interval_ms * PA_USEC_PER_MSEC) {