Files
klubhaus-doorbell/libraries/audio-tools/src/AudioTools/AudioCodecs/CodecAPTX.h
2026-02-12 21:00:02 -08:00

300 lines
8.1 KiB
C++

/**
* @file CodecAptx.h
* @author Phil Schatzmann
* @brief Codec for aptx using https://github.com/pschatzmann/libopenaptx
* @version 0.1
* @date 2022-04-24
*
* @copyright Copyright (c) 2022
*
*/
#pragma once
#include "AudioToolsConfig.h"
#include "AudioTools/AudioCodecs/AudioCodecsBase.h"
#include "openaptx.h"
namespace audio_tools {
/**
* @brief Decoder for OpenAptx. Depends on
* https://github.com/pschatzmann/libopenaptx
* @ingroup codecs
* @ingroup decoder
* @author Phil Schatzmann
* @copyright GPLv3
*/
class APTXDecoder : public AudioDecoder {
public:
APTXDecoder(bool isHd = false) {
is_hd = isHd;
info.sample_rate = 44100;
info.channels = 2;
info.bits_per_sample = isHd ? 24 : 16;
}
bool begin() override {
TRACEI();
ctx = aptx_init(is_hd);
is_first_write = true;
notifyAudioChange(info);
return ctx != nullptr;
}
void end() override {
TRACEI();
bool dropped = aptx_decode_sync_finish(ctx);
aptx_finish(ctx);
ctx = nullptr;
}
virtual void setOutput(Print &out_stream) { p_print = &out_stream; }
operator bool() { return ctx != nullptr; }
virtual size_t write(const uint8_t *data, size_t len) {
LOGI("write: %d", len);
bool is_ok = true;
size_t dropped;
int synced;
if (is_first_write) {
is_first_write = false;
if (!checkPrefix(data, len)) {
return 0;
}
}
output_buffer.resize(len * 10);
memset(output_buffer.data(), 0, output_buffer.size());
processed = aptx_decode_sync(ctx, (const uint8_t *)data, len,
output_buffer.data(), output_buffer.size(),
&written, &synced, &dropped);
checkSync(synced, dropped, is_ok);
// If we have not decoded all supplied samples then decoding unrecoverable
// failed
if (processed != len) {
LOGE("aptX decoding reqested: %d eff: %d", len, processed);
is_ok = false;
}
writeData(written, is_ok);
return is_ok ? len : 0;
}
protected:
struct aptx_context *ctx = nullptr;
Print *p_print = nullptr;
bool is_first_write = true;
Vector<uint8_t> output_buffer;
bool is_hd;
size_t processed;
size_t written;
bool syncing;
/// Converts the data to 16 bit and writes it to final output
void writeData(size_t written, bool &is_ok) {
if (written > 0) {
int samples = written / 3;
LOGI("written: %d", written);
LOGI("samples: %d", samples);
int24_t *p_int24 = (int24_t *)output_buffer.data();
int16_t *p_int16 = (int16_t *)output_buffer.data();
for (int j = 0; j < samples; j++) {
p_int16[j] = p_int24[j].getAndScale16();
}
if (p_print->write((uint8_t *)output_buffer.data(), samples * 2) !=
samples * 2) {
LOGE("aptX decoding failed to write decoded data");
is_ok = false;
}
}
}
/// Checks the syncronization
void checkSync(bool synced, bool dropped, bool &is_ok) {
/* Check all possible states of synced, syncing and dropped status */
if (!synced) {
if (!syncing) {
LOGE("aptX decoding failed, synchronizing");
syncing = true;
is_ok = false;
}
if (dropped) {
LOGE("aptX synchronization successful, dropped %lu byte%s",
(unsigned long)dropped, (dropped != 1) ? "s" : "");
syncing = false;
is_ok = true;
}
if (!syncing) {
LOGE("aptX decoding failed, synchronizing");
syncing = true;
is_ok = false;
}
} else {
if (dropped) {
if (!syncing) LOGE("aptX decoding failed, synchronizing");
LOGE("aptX synchronization successful, dropped %lu byte%s",
(unsigned long)dropped, (dropped != 1) ? "s" : "");
syncing = false;
is_ok = false;
} else if (syncing) {
LOGI("aptX synchronization successful");
syncing = false;
is_ok = true;
}
}
}
/// Checks the prefix of the received data
bool checkPrefix(const void *input_buffer, size_t length) {
bool result = true;
if (length >= 4 && memcmp(input_buffer, "\x4b\xbf\x4b\xbf", 4) == 0) {
if (is_hd) {
LOGE("aptX audio stream (not aptX HD)");
result = false;
}
} else if (length >= 6 &&
memcmp(input_buffer, "\x73\xbe\xff\x73\xbe\xff", 6) == 0) {
if (!is_hd) {
LOGE("aptX HD audio stream");
result = false;
}
} else {
if (length >= 4 && memcmp(input_buffer, "\x6b\xbf\x6b\xbf", 4) == 0) {
LOGE("standard aptX audio stream - not supported");
result = false;
} else {
LOGE("No aptX nor aptX HD audio stream");
result = false;
}
}
return result;
}
};
/**
* @brief Encoder for OpenAptx - Depends on
* https://github.com/pschatzmann/libopenaptx
* @ingroup codecs
* @ingroup encoder
* @author Phil Schatzmann
* @copyright GPLv3
*/
class APTXEncoder : public AudioEncoder {
public:
APTXEncoder(bool isHd = false) {
is_hd = isHd;
info.sample_rate = 44100;
info.channels = 2;
info.bits_per_sample = isHd ? 24 : 16;
}
bool begin() {
TRACEI();
input_buffer.resize(4 * 2);
output_buffer.resize(100 * (is_hd ? 6 : 4));
LOGI("input_buffer.size: %d", input_buffer.size());
LOGI("output_buffer.size: %d", output_buffer.size());
LOGI("is_hd: %s", is_hd ? "true" : "false");
ctx = aptx_init(is_hd);
return ctx!=nullptr;
}
virtual void end() {
TRACEI();
if (ctx != nullptr) {
size_t output_written = 0;
aptx_encode_finish(ctx, output_buffer.data(), output_buffer.size(),
&output_written);
if (output_written > 0) {
// write result to final output
int written = p_print->write((const uint8_t *)output_buffer.data(),
output_written);
if (written != output_written) {
LOGE("write requested: %d eff: %d", output_written, written);
}
}
aptx_finish(ctx);
ctx = nullptr;
}
}
virtual const char *mime() { return "audio/aptx"; }
virtual void setAudioInfo(AudioInfo info) {
AudioEncoder::setAudioInfo(info);
switch (info.bits_per_sample) {
case 16:
is_hd = false;
break;
case 24:
is_hd = true;
break;
default:
LOGE("invalid bits_per_sample: %d", info.bits_per_sample);
}
}
virtual void setOutput(Print &out_stream) { p_print = &out_stream; }
operator bool() { return ctx != nullptr; }
virtual size_t write(const uint8_t *data, size_t len) {
LOGI("write: %d", len);
if (ctx == nullptr) return 0;
size_t output_written = 0;
// process all bytes
int16_t *in_ptr16 = (int16_t *)data;
int in_samples = len / 2;
for (int j = 0; j < in_samples; j++) {
input_buffer[input_pos++].setAndScale16(in_ptr16[j]);
// if input_buffer is full we encode
if (input_pos >= input_buffer.size()) {
size_t result = aptx_encode(
ctx, (const uint8_t *)input_buffer.data(), input_buffer.size() * 3,
output_buffer.data() + output_pos,
output_buffer.size() - output_pos, &output_written);
output_pos += output_written;
if (result != input_buffer.size() * 3) {
LOGW("encode requested: %d, eff: %d", input_buffer.size() * 3,
result);
}
// if output buffer is full we write the result
if (output_pos + output_pos >= output_buffer.size()) {
int written =
p_print->write((const uint8_t *)output_buffer.data(), output_pos);
if (written != output_pos) {
LOGE("write requested: %d eff: %d", output_pos, written);
}
// restart at beginning of output buffer
output_pos = 0;
}
// restart at beginning of input buffer
input_pos = 0;
}
}
return len;
}
protected:
bool is_hd;
Vector<int24_t> input_buffer{4 * 2};
Vector<uint8_t> output_buffer;
int input_pos = 0;
int output_pos = 0;
Print *p_print = nullptr;
struct aptx_context *ctx = nullptr;
};
} // namespace audio_tools