301 lines
7.7 KiB
C++
301 lines
7.7 KiB
C++
/**
|
|
* @file CodecCodec2.h
|
|
* @author Phil Schatzmann
|
|
* @brief Codec2 Codec using https://github.com/pschatzmann/arduino-codec2
|
|
* The codec was developed by David Grant Rowe, with support and cooperation of
|
|
* other researchers (e.g., Jean-Marc Valin from Opus). Codec 2 consists of
|
|
* 3200, 2400, 1600, 1400, 1300, 1200, 700 and 450 bit/s codec modes. It
|
|
* outperforms most other low-bitrate speech codecs. For example, it uses half
|
|
* the bandwidth of Advanced Multi-Band Excitation to encode speech with similar
|
|
* quality. The speech codec uses 16-bit PCM sampled audio, and outputs packed
|
|
* digital bytes. When sent packed digital bytes, it outputs PCM sampled audio.
|
|
* The audio sample rate is fixed at 8 kHz.
|
|
*
|
|
* @version 0.1
|
|
* @date 2022-04-24
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include "AudioTools/AudioCodecs/AudioCodecsBase.h"
|
|
#include "codec2.h"
|
|
|
|
|
|
namespace audio_tools {
|
|
|
|
/// Convert bits per sample to Codec2 mode
|
|
int getCodec2Mode(int bits_per_second) {
|
|
switch (bits_per_second) {
|
|
case 3200:
|
|
return CODEC2_MODE_3200;
|
|
case 2400:
|
|
return CODEC2_MODE_2400;
|
|
case 1600:
|
|
return CODEC2_MODE_1600;
|
|
case 1400:
|
|
return CODEC2_MODE_1400;
|
|
case 1300:
|
|
return CODEC2_MODE_1300;
|
|
case 1200:
|
|
return CODEC2_MODE_1200;
|
|
case 700:
|
|
return CODEC2_MODE_700C;
|
|
case 450:
|
|
return CODEC2_MODE_450;
|
|
default:
|
|
LOGE(
|
|
"Unsupported sample rate: use 3200, 2400, 1600, 1400, 1300, 1200, "
|
|
"700 or 450");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Decoder for Codec2. Depends on
|
|
* https://github.com/pschatzmann/arduino-libcodec2.
|
|
* @ingroup codecs
|
|
* @ingroup decoder
|
|
* @author Phil Schatzmann
|
|
* @copyright GPLv3
|
|
*/
|
|
class Codec2Decoder : public AudioDecoder {
|
|
public:
|
|
Codec2Decoder(int bps = 3200) {
|
|
info.sample_rate = 8000;
|
|
info.channels = 1;
|
|
info.bits_per_sample = 16;
|
|
setBitsPerSecond(bps);
|
|
}
|
|
/// sets bits per second: 3200, 2400, 1600, 1400, 1300, 1200, 700 and 450
|
|
/// bit/s
|
|
virtual void setBitsPerSecond(int bps) { bits_per_second = bps; }
|
|
|
|
int bitsPerSecond() { return bits_per_second; }
|
|
|
|
virtual bool begin() {
|
|
TRACEI();
|
|
|
|
int mode = getCodec2Mode(bits_per_second);
|
|
if (mode == -1) {
|
|
LOGE("invalid bits_per_second")
|
|
return false;
|
|
}
|
|
if (info.channels != 1) {
|
|
LOGE("Only 1 channel supported")
|
|
return false;
|
|
}
|
|
if (info.bits_per_sample != 16) {
|
|
LOGE("Only 16 bps are supported")
|
|
return false;
|
|
}
|
|
if (info.sample_rate != 8000) {
|
|
LOGW("Sample rate should be 8000: %d", info.sample_rate);
|
|
}
|
|
|
|
p_codec2 = codec2_create(mode);
|
|
if (p_codec2 == nullptr) {
|
|
LOGE("codec2_create");
|
|
return false;
|
|
}
|
|
|
|
result_buffer.resize(bytesCompressed());
|
|
input_buffer.resize(bytesCompressed() );
|
|
|
|
assert(input_buffer.size()>0);
|
|
assert(result_buffer.size()>0);
|
|
|
|
notifyAudioChange(info);
|
|
LOGI("bytesCompressed:%d", bytesCompressed());
|
|
LOGI("bytesUncompressed:%d", bytesUncompressed());
|
|
is_active = true;
|
|
return true;
|
|
}
|
|
|
|
int bytesCompressed() {
|
|
return p_codec2 != nullptr ? codec2_bytes_per_frame(p_codec2) : 0;
|
|
}
|
|
|
|
int bytesUncompressed() {
|
|
return p_codec2 != nullptr
|
|
? codec2_samples_per_frame(p_codec2) * sizeof(int16_t)
|
|
: 0;
|
|
}
|
|
|
|
|
|
virtual void end() {
|
|
TRACEI();
|
|
codec2_destroy(p_codec2);
|
|
is_active = false;
|
|
}
|
|
|
|
virtual void setOutput(Print &out_stream) { p_print = &out_stream; }
|
|
|
|
operator bool() { return is_active; }
|
|
|
|
size_t write(const uint8_t *data, size_t len) override {
|
|
LOGD("write: %d", len);
|
|
if (!is_active) {
|
|
LOGE("inactive");
|
|
return 0;
|
|
}
|
|
|
|
uint8_t *p_byte = (uint8_t *)data;
|
|
for (int j = 0; j < len; j++) {
|
|
processByte(p_byte[j]);
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
protected:
|
|
Print *p_print = nullptr;
|
|
struct CODEC2 *p_codec2;
|
|
bool is_active = false;
|
|
Vector<uint8_t> input_buffer;
|
|
Vector<uint8_t> result_buffer;
|
|
int input_pos = 0;
|
|
int bits_per_second = 0;
|
|
|
|
/// Build decoding buffer and decode when frame is full
|
|
void processByte(uint8_t byte) {
|
|
// add byte to buffer
|
|
input_buffer[input_pos++] = byte;
|
|
|
|
// decode if buffer is full
|
|
if (input_pos >= input_buffer.size()) {
|
|
codec2_decode(p_codec2, (short*)result_buffer.data(), input_buffer.data());
|
|
int written = p_print->write((uint8_t *)result_buffer.data(), result_buffer.size());
|
|
if (written != result_buffer.size()){
|
|
LOGE("write: %d written: %d", result_buffer.size(), written);
|
|
} else {
|
|
LOGD("write: %d written: %d", result_buffer.size(), written);
|
|
}
|
|
delay(2);
|
|
input_pos = 0;
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @brief Encoder for Codec2 - Depends on
|
|
* https://github.com/pschatzmann/arduino-libcodec2.
|
|
* @ingroup codecs
|
|
* @ingroup encoder
|
|
* @author Phil Schatzmann
|
|
* @copyright GPLv3
|
|
*/
|
|
class Codec2Encoder : public AudioEncoder {
|
|
public:
|
|
Codec2Encoder(int bps = 3200) {
|
|
info.sample_rate = 8000;
|
|
info.channels = 1;
|
|
info.bits_per_sample = 16;
|
|
setBitsPerSecond(bps);
|
|
}
|
|
|
|
/// sets bits per second: 3200, 2400, 1600, 1400, 1300, 1200, 700 and 450
|
|
/// bit/s
|
|
virtual void setBitsPerSecond(int bps) { bits_per_second = bps; }
|
|
|
|
int bitsPerSecond() { return bits_per_second; }
|
|
|
|
int bytesCompressed() {
|
|
return p_codec2 != nullptr ? codec2_bytes_per_frame(p_codec2) : 0;
|
|
}
|
|
|
|
int bytesUncompressed() {
|
|
return p_codec2 != nullptr
|
|
? codec2_samples_per_frame(p_codec2) * sizeof(int16_t)
|
|
: 0;
|
|
}
|
|
|
|
bool begin() {
|
|
TRACEI();
|
|
|
|
int mode = getCodec2Mode(bits_per_second);
|
|
if (mode == -1) {
|
|
LOGE("invalid bits_per_second")
|
|
return false;
|
|
}
|
|
if (info.channels != 1) {
|
|
LOGE("Only 1 channel supported")
|
|
return false;
|
|
}
|
|
if (info.bits_per_sample != 16) {
|
|
LOGE("Only 16 bps are supported")
|
|
return false;
|
|
}
|
|
if (info.sample_rate != 8000) {
|
|
LOGW("Sample rate should be 8000: %d", info.sample_rate);
|
|
}
|
|
|
|
p_codec2 = codec2_create(mode);
|
|
if (p_codec2 == nullptr) {
|
|
LOGE("codec2_create");
|
|
return false;
|
|
}
|
|
|
|
input_buffer.resize(bytesCompressed());
|
|
result_buffer.resize(bytesUncompressed());
|
|
assert(input_buffer.size()>0);
|
|
assert(result_buffer.size()>0);
|
|
LOGI("bytesCompressed:%d", bytesCompressed());
|
|
LOGI("bytesUncompressed:%d", bytesUncompressed());
|
|
is_active = true;
|
|
return true;
|
|
}
|
|
|
|
virtual void end() {
|
|
TRACEI();
|
|
codec2_destroy(p_codec2);
|
|
is_active = false;
|
|
}
|
|
|
|
virtual const char *mime() { return "audio/codec2"; }
|
|
|
|
virtual void setOutput(Print &out_stream) { p_print = &out_stream; }
|
|
|
|
operator bool() { return is_active; }
|
|
|
|
size_t write(const uint8_t *in_ptr, size_t in_size) override {
|
|
LOGD("write: %d", in_size);
|
|
if (!is_active) {
|
|
LOGE("inactive");
|
|
return 0;
|
|
}
|
|
// encode bytes
|
|
uint8_t *p_byte = (uint8_t *)in_ptr;
|
|
for (int j = 0; j < in_size; j++) {
|
|
processByte(p_byte[j]);
|
|
}
|
|
return in_size;
|
|
}
|
|
|
|
protected:
|
|
Print *p_print = nullptr;
|
|
struct CODEC2 *p_codec2 = nullptr;
|
|
bool is_active = false;
|
|
int buffer_pos = 0;
|
|
Vector<uint8_t> input_buffer;
|
|
Vector<uint8_t> result_buffer;
|
|
int bits_per_second = 0;
|
|
|
|
// add byte to decoding buffer and decode if buffer is full
|
|
void processByte(uint8_t byte) {
|
|
input_buffer[buffer_pos++] = byte;
|
|
if (buffer_pos >= input_buffer.size()) {
|
|
// encode
|
|
codec2_encode(p_codec2, result_buffer.data(),
|
|
(short*)input_buffer.data());
|
|
int written = p_print->write(result_buffer.data(), result_buffer.size());
|
|
if(written!=result_buffer.size()){
|
|
LOGE("write: %d written: %d", result_buffer.size(), written);
|
|
} else {
|
|
LOGD("write: %d written: %d", result_buffer.size(), written);
|
|
}
|
|
buffer_pos = 0;
|
|
}
|
|
}
|
|
};
|
|
|
|
} // namespace audio_tools
|