Files
pyxis/lib/lxst_audio/codec_wrapper.cpp
torlando-tech 06743635eb Fix ES7210 mic capture: use LilyGO library with slave mode
Replace custom ES7210 register-level driver with the verbatim LilyGO
T-Deck Plus ES7210 library. The custom driver was writing MODE_CONFIG
and clock doubler registers (master mode path) which broke the ES7210's
clock chain, causing all-zero PCM output. The LilyGO library in slave
mode leaves those registers at power-on defaults, which is correct since
the ESP32 I2S master provides MCLK/BCLK/LRCK.

Also includes LXST voice call protocol improvements:
- LXST IN destination for receiving calls
- Announce handler for tracking voice-capable peers
- Path request before outgoing calls
- Throttled SPIFFS saves in microReticulum (dirty flag)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 01:42:47 -05:00

199 lines
5.6 KiB
C++

// Copyright (c) 2024 LXST contributors
// SPDX-License-Identifier: MPL-2.0
#include "codec_wrapper.h"
#include <codec2.h>
#include <cstring>
#ifdef ARDUINO
#include <esp_log.h>
static const char* TAG = "LXST:Codec2";
#define LOGI(fmt, ...) ESP_LOGI(TAG, fmt, ##__VA_ARGS__)
#define LOGW(fmt, ...) ESP_LOGW(TAG, fmt, ##__VA_ARGS__)
#define LOGE(fmt, ...) ESP_LOGE(TAG, fmt, ##__VA_ARGS__)
#else
#include <cstdio>
#define LOGI(fmt, ...) printf("[INFO] " fmt "\n", ##__VA_ARGS__)
#define LOGW(fmt, ...) printf("[WARN] " fmt "\n", ##__VA_ARGS__)
#define LOGE(fmt, ...) printf("[ERR] " fmt "\n", ##__VA_ARGS__)
#endif
Codec2Wrapper::Codec2Wrapper() = default;
Codec2Wrapper::~Codec2Wrapper() {
destroy();
}
bool Codec2Wrapper::create(int libraryMode) {
destroy();
codec2_ = codec2_create(libraryMode);
if (!codec2_) {
LOGE("Codec2 create failed for library mode %d", libraryMode);
return false;
}
libraryMode_ = libraryMode;
samplesPerFrame_ = codec2_samples_per_frame(codec2_);
bytesPerFrame_ = codec2_bytes_per_frame(codec2_);
modeHeader_ = libraryModeToHeader(libraryMode);
LOGI("Codec2 created: libMode=%d header=0x%02x samples/frame=%d bytes/frame=%d",
libraryMode, modeHeader_, samplesPerFrame_, bytesPerFrame_);
#ifdef ARDUINO
if (!mutex_) {
mutex_ = xSemaphoreCreateMutex();
}
#endif
return true;
}
void Codec2Wrapper::destroy() {
if (codec2_) {
codec2_destroy(codec2_);
codec2_ = nullptr;
}
#ifdef ARDUINO
if (mutex_) {
vSemaphoreDelete(mutex_);
mutex_ = nullptr;
}
#endif
samplesPerFrame_ = 0;
bytesPerFrame_ = 0;
modeHeader_ = 0;
libraryMode_ = 0;
}
int Codec2Wrapper::decode(const uint8_t* encoded, int encodedBytes,
int16_t* output, int maxOutputSamples) {
if (!codec2_ || encodedBytes < 1) return -1;
#ifdef ARDUINO
if (mutex_) xSemaphoreTake(mutex_, portMAX_DELAY);
#endif
// First byte is mode header -- check if mode changed
uint8_t header = encoded[0];
if (header != modeHeader_) {
int newMode = headerToLibraryMode(header);
if (newMode >= 0) {
LOGI("Codec2 mode switch: header 0x%02x -> libMode %d", header, newMode);
codec2_destroy(codec2_);
codec2_ = codec2_create(newMode);
if (!codec2_) {
LOGE("Codec2 mode switch failed");
#ifdef ARDUINO
if (mutex_) xSemaphoreGive(mutex_);
#endif
return -1;
}
libraryMode_ = newMode;
samplesPerFrame_ = codec2_samples_per_frame(codec2_);
bytesPerFrame_ = codec2_bytes_per_frame(codec2_);
modeHeader_ = header;
} else {
LOGW("Unknown Codec2 header: 0x%02x", header);
#ifdef ARDUINO
if (mutex_) xSemaphoreGive(mutex_);
#endif
return -1;
}
}
// Skip header byte, decode remaining sub-frames
const uint8_t* data = encoded + 1;
int dataLen = encodedBytes - 1;
int numFrames = dataLen / bytesPerFrame_;
int totalSamples = numFrames * samplesPerFrame_;
if (totalSamples > maxOutputSamples) {
LOGW("Codec2 decode: output buffer too small (%d > %d)",
totalSamples, maxOutputSamples);
#ifdef ARDUINO
if (mutex_) xSemaphoreGive(mutex_);
#endif
return -1;
}
for (int i = 0; i < numFrames; i++) {
codec2_decode(codec2_,
output + i * samplesPerFrame_,
data + i * bytesPerFrame_);
}
#ifdef ARDUINO
if (mutex_) xSemaphoreGive(mutex_);
#endif
return totalSamples;
}
int Codec2Wrapper::encode(const int16_t* pcm, int pcmSamples,
uint8_t* output, int maxOutputBytes) {
if (!codec2_) return -1;
int numFrames = pcmSamples / samplesPerFrame_;
int encodedSize = 1 + numFrames * bytesPerFrame_;
if (encodedSize > maxOutputBytes) {
LOGW("Codec2 encode: output buffer too small (%d > %d)",
encodedSize, maxOutputBytes);
return -1;
}
#ifdef ARDUINO
if (mutex_) xSemaphoreTake(mutex_, portMAX_DELAY);
#endif
// Prepend mode header byte
output[0] = modeHeader_;
for (int i = 0; i < numFrames; i++) {
codec2_encode(codec2_,
output + 1 + i * bytesPerFrame_,
const_cast<int16_t*>(pcm + i * samplesPerFrame_));
}
#ifdef ARDUINO
if (mutex_) xSemaphoreGive(mutex_);
#endif
return encodedSize;
}
// Wire format mapping (matches Python LXST and LXST-kt Codec2.kt):
// header 0x00 = 700C -> library mode 8
// header 0x01 = 1200 -> library mode 5
// header 0x02 = 1300 -> library mode 4
// header 0x03 = 1400 -> library mode 3
// header 0x04 = 1600 -> library mode 2
// header 0x05 = 2400 -> library mode 1
// header 0x06 = 3200 -> library mode 0
int Codec2Wrapper::headerToLibraryMode(uint8_t header) {
switch (header) {
case 0x00: return 8; // 700C
case 0x01: return 5; // 1200
case 0x02: return 4; // 1300
case 0x03: return 3; // 1400
case 0x04: return 2; // 1600
case 0x05: return 1; // 2400
case 0x06: return 0; // 3200
default: return -1;
}
}
uint8_t Codec2Wrapper::libraryModeToHeader(int libraryMode) {
switch (libraryMode) {
case 8: return 0x00; // 700C
case 5: return 0x01; // 1200
case 4: return 0x02; // 1300
case 3: return 0x03; // 1400
case 2: return 0x04; // 1600
case 1: return 0x05; // 2400
case 0: return 0x06; // 3200
default: return 0xFF;
}
}