mirror of
https://github.com/pschatzmann/arduino-audio-tools.git
synced 2024-09-22 10:57:31 +00:00
FFT window functions & DRAFT Codecs
This commit is contained in:
parent
897103787e
commit
c49fb6ecee
@ -101,7 +101,7 @@ class Vector {
|
|||||||
inline Vector(int size, T value) {
|
inline Vector(int size, T value) {
|
||||||
resize(size);
|
resize(size);
|
||||||
for (int j=0;j< size;j++){
|
for (int j=0;j< size;j++){
|
||||||
data[j] = value;
|
p_data[j] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,7 +109,7 @@ class Vector {
|
|||||||
inline Vector( Vector<T> ©From) {
|
inline Vector( Vector<T> ©From) {
|
||||||
resize_internal(copyFrom.size(), false);
|
resize_internal(copyFrom.size(), false);
|
||||||
for (int j=0;j<copyFrom.size();j++){
|
for (int j=0;j<copyFrom.size();j++){
|
||||||
data[j] = copyFrom[j];
|
p_data[j] = copyFrom[j];
|
||||||
}
|
}
|
||||||
this->len = copyFrom.size();
|
this->len = copyFrom.size();
|
||||||
}
|
}
|
||||||
@ -119,14 +119,14 @@ class Vector {
|
|||||||
this->len = to - from;
|
this->len = to - from;
|
||||||
resize_internal(this->len, false);
|
resize_internal(this->len, false);
|
||||||
for (size_t j=0;j<this->len;j++){
|
for (size_t j=0;j<this->len;j++){
|
||||||
data[j] = from[j];
|
p_data[j] = from[j];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline ~Vector() {
|
inline ~Vector() {
|
||||||
clear();
|
clear();
|
||||||
shrink_to_fit();
|
shrink_to_fit();
|
||||||
delete [] this->data;
|
delete [] this->p_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void clear() {
|
inline void clear() {
|
||||||
@ -143,14 +143,14 @@ class Vector {
|
|||||||
|
|
||||||
inline void push_back(T value){
|
inline void push_back(T value){
|
||||||
resize_internal(len+1, true);
|
resize_internal(len+1, true);
|
||||||
data[len] = value;
|
p_data[len] = value;
|
||||||
len++;
|
len++;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void push_front(T value){
|
inline void push_front(T value){
|
||||||
resize_internal(len+1, true);
|
resize_internal(len+1, true);
|
||||||
memmove(data,data+1,len*sizeof(T));
|
memmove(p_data,p_data+1,len*sizeof(T));
|
||||||
data[0] = value;
|
p_data[0] = value;
|
||||||
len++;
|
len++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,7 +164,7 @@ class Vector {
|
|||||||
if (len>0) {
|
if (len>0) {
|
||||||
len--;
|
len--;
|
||||||
if (len>0){
|
if (len>0){
|
||||||
memmove(data, data+1,len*sizeof(T));
|
memmove(p_data, p_data+1,len*sizeof(T));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -176,7 +176,7 @@ class Vector {
|
|||||||
this->len = newLen;
|
this->len = newLen;
|
||||||
int pos = 0;
|
int pos = 0;
|
||||||
for (auto ptr = v1; ptr != v2; ptr++) {
|
for (auto ptr = v1; ptr != v2; ptr++) {
|
||||||
data[pos++] = *ptr;
|
p_data[pos++] = *ptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,45 +184,45 @@ class Vector {
|
|||||||
resize_internal(number, false);
|
resize_internal(number, false);
|
||||||
this->len = number;
|
this->len = number;
|
||||||
for (int j=0;j<number;j++){
|
for (int j=0;j<number;j++){
|
||||||
data[j]=value;
|
p_data[j]=value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void swap(Vector<T> &in){
|
inline void swap(Vector<T> &in){
|
||||||
// save data
|
// save data
|
||||||
T *dataCpy = data;
|
T *dataCpy = p_data;
|
||||||
int bufferLenCpy = bufferLen;
|
int bufferLenCpy = bufferLen;
|
||||||
int lenCpy = len;
|
int lenCpy = len;
|
||||||
// swap this
|
// swap this
|
||||||
data = in.data;
|
p_data = in.p_data;
|
||||||
len = in.len;
|
len = in.len;
|
||||||
bufferLen = in.bufferLen;
|
bufferLen = in.bufferLen;
|
||||||
// swp in
|
// swp in
|
||||||
in.data = dataCpy;
|
in.p_data = dataCpy;
|
||||||
in.len = lenCpy;
|
in.len = lenCpy;
|
||||||
in.bufferLen = bufferLenCpy;
|
in.bufferLen = bufferLenCpy;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline T &operator[](int index) {
|
inline T &operator[](int index) {
|
||||||
return data[index];
|
return p_data[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Vector<T> &operator=(Vector<T> ©From) {
|
inline Vector<T> &operator=(Vector<T> ©From) {
|
||||||
resize_internal(copyFrom.size(), false);
|
resize_internal(copyFrom.size(), false);
|
||||||
for (int j=0;j<copyFrom.size();j++){
|
for (int j=0;j<copyFrom.size();j++){
|
||||||
data[j] = copyFrom[j];
|
p_data[j] = copyFrom[j];
|
||||||
}
|
}
|
||||||
this->len = copyFrom.size();
|
this->len = copyFrom.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
inline T &operator[] (const int index) const {
|
inline T &operator[] (const int index) const {
|
||||||
return data[index];
|
return p_data[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool resize(int newSize, T value){
|
inline bool resize(int newSize, T value){
|
||||||
if (resize(newSize)){
|
if (resize(newSize)){
|
||||||
for (int j=0;j<newSize;j++){
|
for (int j=0;j<newSize;j++){
|
||||||
data[j]=value;
|
p_data[j]=value;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -245,15 +245,15 @@ class Vector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline iterator begin(){
|
inline iterator begin(){
|
||||||
return iterator(data, 0);
|
return iterator(p_data, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline T& back(){
|
inline T& back(){
|
||||||
return *iterator(data+(len-1), len-1);
|
return *iterator(p_data+(len-1), len-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline iterator end(){
|
inline iterator end(){
|
||||||
return iterator(data+len, len);
|
return iterator(p_data+len, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
// removes a single element
|
// removes a single element
|
||||||
@ -262,31 +262,35 @@ class Vector {
|
|||||||
if (pos<len){
|
if (pos<len){
|
||||||
int lenToEnd = len - pos - 1;
|
int lenToEnd = len - pos - 1;
|
||||||
// call destructor on data to be erased
|
// call destructor on data to be erased
|
||||||
data[pos].~T();
|
p_data[pos].~T();
|
||||||
// shift values by 1 position
|
// shift values by 1 position
|
||||||
memmove((void*) &data[pos],(void*)(&data[pos+1]),lenToEnd*sizeof(T));
|
memmove((void*) &p_data[pos],(void*)(&p_data[pos+1]),lenToEnd*sizeof(T));
|
||||||
// make sure that we have a valid object at the end
|
// make sure that we have a valid object at the end
|
||||||
data[len-1] = T();
|
p_data[len-1] = T();
|
||||||
len--;
|
len--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
T* data(){
|
||||||
|
return p_data;
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
int bufferLen;
|
int bufferLen;
|
||||||
int len = 0;
|
int len = 0;
|
||||||
T *data = nullptr;
|
T *p_data = nullptr;
|
||||||
|
|
||||||
inline void resize_internal(int newSize, bool copy, bool shrink=false) {
|
inline void resize_internal(int newSize, bool copy, bool shrink=false) {
|
||||||
//bool withNewSize = false;
|
//bool withNewSize = false;
|
||||||
if (newSize>bufferLen || this->data==nullptr ||shrink){
|
if (newSize>bufferLen || this->p_data==nullptr ||shrink){
|
||||||
//withNewSize = true;
|
//withNewSize = true;
|
||||||
T* oldData = data;
|
T* oldData = p_data;
|
||||||
int oldBufferLen = this->bufferLen;
|
int oldBufferLen = this->bufferLen;
|
||||||
this->data = new T[newSize+1];
|
this->p_data = new T[newSize+1];
|
||||||
this->bufferLen = newSize;
|
this->bufferLen = newSize;
|
||||||
if (oldData != nullptr) {
|
if (oldData != nullptr) {
|
||||||
if(copy && this->len > 0){
|
if(copy && this->len > 0){
|
||||||
memcpy((void*)data,(void*) oldData, this->len*sizeof(T));
|
memcpy((void*)p_data,(void*) oldData, this->len*sizeof(T));
|
||||||
}
|
}
|
||||||
if (shrink){
|
if (shrink){
|
||||||
cleanup(oldData, newSize, oldBufferLen);
|
cleanup(oldData, newSize, oldBufferLen);
|
||||||
@ -296,9 +300,9 @@ class Vector {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void cleanup(T*data, int from, int to){
|
void cleanup(T*p_data, int from, int to){
|
||||||
for (int j=from;j<to;j++){
|
for (int j=from;j<to;j++){
|
||||||
data[j].~T();
|
p_data[j].~T();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
186
src/AudioCodecs/CodecLC3.h
Normal file
186
src/AudioCodecs/CodecLC3.h
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
/**
|
||||||
|
* @file CodecLC3.h
|
||||||
|
* @author Phil Schatzmann
|
||||||
|
* @brief Codec for aptx using https://github.com/pschatzmann/arduino-liblc3
|
||||||
|
* @version 0.1
|
||||||
|
* @date 2022-04-24
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2022
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "AudioTools/AudioTypes.h"
|
||||||
|
#include "lc3.h"
|
||||||
|
|
||||||
|
namespace audio_tools {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Decoder for OpenAptx. Depends on
|
||||||
|
* https://github.com/pschatzmann/arduino-liblc3
|
||||||
|
* @author Phil Schatzmann
|
||||||
|
* @copyright GPLv3
|
||||||
|
*/
|
||||||
|
class LC3Decoder : public AudioDecoder {
|
||||||
|
public:
|
||||||
|
LC3Decoder(AudioBaseInfo info, int dt_us = 1000,
|
||||||
|
uint16_t inputByteCount = 20) {
|
||||||
|
this->dt_us = dt_us;
|
||||||
|
this->info = info;
|
||||||
|
this->input_byte_count = inputByteCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual AudioBaseInfo audioInfo() { return info; }
|
||||||
|
|
||||||
|
virtual void begin() {
|
||||||
|
if (p_print == nullptr) {
|
||||||
|
LOGE("Output is not defined");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (info.bits_per_sample) {
|
||||||
|
case 16:
|
||||||
|
pcm_format = LC3_PCM_FORMAT_S16;
|
||||||
|
break;
|
||||||
|
case 24:
|
||||||
|
pcm_format = LC3_PCM_FORMAT_S24;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOGE("Bits per sample not supported: %d", info.bits_per_sample);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned dec_size = lc3_decoder_size(dt_us, info.sample_rate);
|
||||||
|
lc3_decoder_mem.resize(dec_size);
|
||||||
|
lc3_decoder =
|
||||||
|
lc3_setup_decoder(dt_us, info.sample_rate, 0, (void*) lc3_decoder_mem.data());
|
||||||
|
num_frames = lc3_frame_samples(dt_us, info.sample_rate);
|
||||||
|
output_buffer.resize(num_frames);
|
||||||
|
input_buffer.resize(input_byte_count);
|
||||||
|
input_pos = 0;
|
||||||
|
|
||||||
|
if (p_notify != nullptr) {
|
||||||
|
p_notify->setAudioInfo(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void end() {
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void setNotifyAudioChange(AudioBaseInfoDependent &bi) {
|
||||||
|
p_notify = &bi;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void setOutputStream(Print &out_stream) { p_print = &out_stream; }
|
||||||
|
|
||||||
|
operator boolean() { return lc3_decoder_mem.size() > 0; }
|
||||||
|
|
||||||
|
virtual size_t write(const void *input, size_t length) {
|
||||||
|
uint8_t *p_ptr8 = (uint8_t *)input;
|
||||||
|
|
||||||
|
for (int j = 0; j < length; j++) {
|
||||||
|
input_buffer[input_pos++] = p_ptr8[j];
|
||||||
|
if (input_pos >= input_buffer.size()) {
|
||||||
|
lc3_decode(lc3_decoder, input_buffer.data(), input_buffer.size(), pcm_format,
|
||||||
|
(int16_t *)output_buffer.data(), 1);
|
||||||
|
p_print->write((const uint8_t *)output_buffer.data(), output_buffer.size());
|
||||||
|
input_pos = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Print *p_print = nullptr;
|
||||||
|
AudioBaseInfo info;
|
||||||
|
AudioBaseInfoDependent *p_notify = nullptr;
|
||||||
|
lc3_decoder_t lc3_decoder = nullptr;
|
||||||
|
lc3_pcm_format pcm_format;
|
||||||
|
Vector<uint8_t> lc3_decoder_mem;
|
||||||
|
Vector<uint16_t> output_buffer;
|
||||||
|
Vector<uint8_t> input_buffer;
|
||||||
|
size_t input_pos = 0;
|
||||||
|
int dt_us;
|
||||||
|
uint16_t input_byte_count = 20; // up to 400
|
||||||
|
uint16_t num_frames;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Encoder for OpenAptx - Depends on
|
||||||
|
* https://github.com/pschatzmann/arduino-liblc3
|
||||||
|
* @author Phil Schatzmann
|
||||||
|
* @copyright GPLv3
|
||||||
|
*/
|
||||||
|
class LC3Encoder : public AudioEncoder {
|
||||||
|
public:
|
||||||
|
LC3Encoder(int dt_us = 1000, uint16_t outputByteCount = 20) {
|
||||||
|
this->dt_us = dt_us;
|
||||||
|
output_byte_count = outputByteCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
void begin() {
|
||||||
|
if (p_print == nullptr) {
|
||||||
|
LOGE("Output is not defined");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (info.bits_per_sample) {
|
||||||
|
case 16:
|
||||||
|
pcm_format = LC3_PCM_FORMAT_S16;
|
||||||
|
break;
|
||||||
|
case 24:
|
||||||
|
pcm_format = LC3_PCM_FORMAT_S24;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOGE("Bits per sample not supported: %d", info.bits_per_sample);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned enc_size = lc3_encoder_size(dt_us, info.sample_rate);
|
||||||
|
lc3_encoder_mem.resize(enc_size);
|
||||||
|
num_frames = lc3_frame_samples(dt_us, info.sample_rate);
|
||||||
|
lc3_encoder =
|
||||||
|
lc3_setup_encoder(dt_us, info.sample_rate, 0, lc3_encoder_mem.data());
|
||||||
|
input.resize(num_frames * 2);
|
||||||
|
output.resize(output_byte_count);
|
||||||
|
input_pos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void end() {}
|
||||||
|
|
||||||
|
virtual const char *mime() { return "audio/lc3"; }
|
||||||
|
|
||||||
|
virtual void setAudioInfo(AudioBaseInfo info) { this->info = info; }
|
||||||
|
|
||||||
|
virtual void setOutputStream(Print &out_stream) { p_print = &out_stream; }
|
||||||
|
|
||||||
|
operator boolean() { lc3_encoder != nullptr; }
|
||||||
|
|
||||||
|
virtual size_t write(const void *in_ptr, size_t in_size) {
|
||||||
|
uint8_t *p_ptr8 = (uint8_t *) in_ptr;
|
||||||
|
for (int j = 0; j < in_size; j++) {
|
||||||
|
input[input_pos++] = p_ptr8[j];
|
||||||
|
if (input_pos >= num_frames * 2) {
|
||||||
|
lc3_encode(lc3_encoder, pcm_format, (const int16_t *)input.data(), 1,
|
||||||
|
output.size(), output.data());
|
||||||
|
p_print->write(output.data(), output.size());
|
||||||
|
input_pos = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return in_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
AudioBaseInfo info;
|
||||||
|
Print *p_print = nullptr;
|
||||||
|
unsigned dt_us = 1000;
|
||||||
|
uint16_t num_frames;
|
||||||
|
lc3_encoder_t lc3_encoder = nullptr;
|
||||||
|
lc3_pcm_format pcm_format;
|
||||||
|
uint16_t output_byte_count = 20;
|
||||||
|
Vector<uint8_t> lc3_encoder_mem;
|
||||||
|
Vector<uint8_t> output;
|
||||||
|
Vector<uint8_t> input;
|
||||||
|
int input_pos = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace audio_tools
|
@ -10,6 +10,7 @@
|
|||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "openaptx.h"
|
#include "openaptx.h"
|
||||||
|
#include "AudioTools/AudioTypes.h"
|
||||||
|
|
||||||
namespace audio_tools {
|
namespace audio_tools {
|
||||||
|
|
||||||
|
320
src/AudioCodecs/CodecSBC.h
Normal file
320
src/AudioCodecs/CodecSBC.h
Normal file
@ -0,0 +1,320 @@
|
|||||||
|
/**
|
||||||
|
* @file CodecSBC.h
|
||||||
|
* @author Phil Schatzmann
|
||||||
|
* @brief SBC Codec using https://github.com/pschatzmann/arduino-libsbc
|
||||||
|
* @version 0.1
|
||||||
|
* @date 2022-04-24
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "sbc.h"
|
||||||
|
#include "sbc/formats.h"
|
||||||
|
#include "AudioTools/AudioTypes.h"
|
||||||
|
|
||||||
|
namespace audio_tools {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Decoder for SBC. Depends on
|
||||||
|
* https://github.com/pschatzmann/arduino-libsbc.
|
||||||
|
* Inspired by sbcdec.c
|
||||||
|
* @author Phil Schatzmann
|
||||||
|
* @copyright GPLv3
|
||||||
|
*/
|
||||||
|
class SBCDecoder : public AudioDecoder {
|
||||||
|
public:
|
||||||
|
SBCDecoder(int bufferSize = 8192) {
|
||||||
|
result_buffer = new uint8_t[bufferSize];
|
||||||
|
result_buffer_size = bufferSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
~SBCDecoder() {
|
||||||
|
if (result_buffer != nullptr) delete[] result_buffer;
|
||||||
|
if (input_buffer != nullptr) delete[] input_buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual AudioBaseInfo audioInfo() { return info; }
|
||||||
|
|
||||||
|
virtual void begin() {
|
||||||
|
is_first = true;
|
||||||
|
is_active = true;
|
||||||
|
sbc_init(&sbc, 0L);
|
||||||
|
sbc.endian = SBC_BE;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void end() {
|
||||||
|
sbc_finish(&sbc);
|
||||||
|
is_active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void setNotifyAudioChange(AudioBaseInfoDependent &bi) {
|
||||||
|
p_notify = &bi;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void setOutputStream(Print &out_stream) { p_print = &out_stream; }
|
||||||
|
|
||||||
|
operator boolean() { return is_active; }
|
||||||
|
|
||||||
|
virtual size_t write(const void *data, size_t length) {
|
||||||
|
uint8_t *start = (uint8_t *)data;
|
||||||
|
int count = length;
|
||||||
|
if (is_first) {
|
||||||
|
size_t result_len = 0;
|
||||||
|
framelen = sbc_decode(&sbc, data, length, result_buffer,
|
||||||
|
result_buffer_size, &result_len);
|
||||||
|
|
||||||
|
// setup input buffer for subsequent decoding stpes
|
||||||
|
if (input_buffer != nullptr) delete[] input_buffer;
|
||||||
|
input_buffer = new uint8_t[framelen];
|
||||||
|
is_first = false;
|
||||||
|
start = start + framelen;
|
||||||
|
count = length - framelen;
|
||||||
|
|
||||||
|
// audio info
|
||||||
|
setup();
|
||||||
|
|
||||||
|
// provide first decoding result
|
||||||
|
if (result_len > 0) {
|
||||||
|
p_print->write(result_buffer, result_len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int j = 0; j < count; j++) {
|
||||||
|
processByte(start[j]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Print *p_print = nullptr;
|
||||||
|
AudioBaseInfo info;
|
||||||
|
AudioBaseInfoDependent *p_notify = nullptr;
|
||||||
|
sbc_t sbc;
|
||||||
|
bool is_first = true;
|
||||||
|
bool is_active = false;
|
||||||
|
uint8_t *result_buffer = nullptr;
|
||||||
|
int result_buffer_size;
|
||||||
|
int framelen;
|
||||||
|
uint8_t *input_buffer = nullptr;
|
||||||
|
int input_pos = 0;
|
||||||
|
|
||||||
|
/// Process audio info
|
||||||
|
void setup() {
|
||||||
|
info.bits_per_sample = 16;
|
||||||
|
info.channels = sbc.mode == SBC_MODE_MONO ? 1 : 2;
|
||||||
|
switch (sbc.frequency) {
|
||||||
|
case SBC_FREQ_16000:
|
||||||
|
info.sample_rate = 16000;
|
||||||
|
break;
|
||||||
|
case SBC_FREQ_32000:
|
||||||
|
info.sample_rate = 32000;
|
||||||
|
break;
|
||||||
|
case SBC_FREQ_44100:
|
||||||
|
info.sample_rate = 44100;
|
||||||
|
break;
|
||||||
|
case SBC_FREQ_48000:
|
||||||
|
info.sample_rate = 48000;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOGE("Unsupported sample rate");
|
||||||
|
info.sample_rate = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (p_notify != nullptr) {
|
||||||
|
p_notify->setAudioInfo(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 >= framelen) {
|
||||||
|
size_t result_len = 0;
|
||||||
|
sbc_decode(&sbc, input_buffer, framelen, result_buffer,
|
||||||
|
result_buffer_size, &result_len);
|
||||||
|
if (result_len > 0) {
|
||||||
|
p_print->write(result_buffer, result_len);
|
||||||
|
}
|
||||||
|
input_pos = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Encoder for SBC - Depends on
|
||||||
|
* https://github.com/pschatzmann/arduino-libsbc.
|
||||||
|
* Inspired by sbcenc.c
|
||||||
|
* @author Phil Schatzmann
|
||||||
|
* @copyright GPLv3
|
||||||
|
*/
|
||||||
|
class SBCEncoder : public AudioEncoder {
|
||||||
|
public:
|
||||||
|
SBCEncoder(int resultBufferSize = 512, int subbands = 4, int blocks = 4,
|
||||||
|
int bitpool = 32, int snr = SBC_AM_LOUDNESS) {
|
||||||
|
this->subbands = subbands;
|
||||||
|
this->blocks = blocks;
|
||||||
|
this->bitpool = bitpool;
|
||||||
|
this->snr = snr;
|
||||||
|
result_buffer = new uint8_t[resultBufferSize];
|
||||||
|
}
|
||||||
|
|
||||||
|
~SBCEncoder() {
|
||||||
|
if (result_buffer != nullptr) delete[] result_buffer;
|
||||||
|
if (buffer != nullptr) delete[] buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void begin() {
|
||||||
|
if (sizeof(au_hdr) != 24) {
|
||||||
|
/* Sanity check just in case */
|
||||||
|
LOGE("FIXME: sizeof(au_hdr) != 24");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
is_first = true;
|
||||||
|
is_active = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void end() {
|
||||||
|
sbc_finish(&sbc);
|
||||||
|
is_active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual const char *mime() { return "audio/sbc"; }
|
||||||
|
|
||||||
|
virtual void setAudioInfo(AudioBaseInfo info) { this->info = info; }
|
||||||
|
|
||||||
|
virtual void setOutputStream(Print &out_stream) { p_print = &out_stream; }
|
||||||
|
|
||||||
|
operator boolean() { is_active; }
|
||||||
|
|
||||||
|
virtual size_t write(const void *in_ptr, size_t in_size) {
|
||||||
|
if (!is_active) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t *start = (const uint8_t *)in_ptr;
|
||||||
|
int size = in_size;
|
||||||
|
|
||||||
|
/// setup from info in header
|
||||||
|
if (is_first) {
|
||||||
|
is_first = false;
|
||||||
|
start = start + sizeof(au_hdr);
|
||||||
|
size = in_size - sizeof(au_hdr);
|
||||||
|
|
||||||
|
if (!setup(in_ptr, in_size)) {
|
||||||
|
is_active = false;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int codesize = sbc_get_codesize(&sbc);
|
||||||
|
if (codesize != current_codesize) {
|
||||||
|
if (buffer != nullptr) delete[] buffer;
|
||||||
|
buffer = new uint8_t[codesize];
|
||||||
|
current_codesize = codesize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// encode bytes
|
||||||
|
for (int j = 0; j < size; j++) {
|
||||||
|
processByte(start[j]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return in_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
AudioBaseInfo info;
|
||||||
|
Print *p_print = nullptr;
|
||||||
|
struct au_header au_hdr;
|
||||||
|
sbc_t sbc;
|
||||||
|
bool is_first = true;
|
||||||
|
bool is_active = false;
|
||||||
|
int current_codesize = 0;
|
||||||
|
uint8_t *buffer = nullptr;
|
||||||
|
int buffer_pos = 0;
|
||||||
|
uint8_t *result_buffer = nullptr;
|
||||||
|
int subbands = 4;
|
||||||
|
int blocks = 4;
|
||||||
|
int bitpool = 32;
|
||||||
|
int snr;
|
||||||
|
|
||||||
|
/// Determines audio information and calls sbc_init;
|
||||||
|
bool setup(const void *in_ptr, size_t len) {
|
||||||
|
if (len < (ssize_t)sizeof(au_hdr)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
memmove(&au_hdr, in_ptr, sizeof(au_hdr));
|
||||||
|
if (au_hdr.magic != AU_MAGIC || BE_INT(au_hdr.hdr_size) > 128 ||
|
||||||
|
BE_INT(au_hdr.hdr_size) < sizeof(au_hdr) ||
|
||||||
|
BE_INT(au_hdr.encoding) != AU_FMT_LIN16) {
|
||||||
|
LOGE("Not in Sun/NeXT audio S16_BE format");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sbc_init(&sbc, 0L);
|
||||||
|
|
||||||
|
switch (BE_INT(info.sample_rate)) {
|
||||||
|
case 16000:
|
||||||
|
sbc.frequency = SBC_FREQ_16000;
|
||||||
|
break;
|
||||||
|
case 32000:
|
||||||
|
sbc.frequency = SBC_FREQ_32000;
|
||||||
|
break;
|
||||||
|
case 44100:
|
||||||
|
sbc.frequency = SBC_FREQ_44100;
|
||||||
|
break;
|
||||||
|
case 48000:
|
||||||
|
sbc.frequency = SBC_FREQ_48000;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOGE("Invalid sample_rate")
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (BE_INT(info.channels)) {
|
||||||
|
case 1:
|
||||||
|
sbc.mode = SBC_MODE_MONO;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
sbc.mode = SBC_MODE_STEREO;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOGE("Invalid channels")
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sbc.subbands = subbands == 4 ? SBC_SB_4 : SBC_SB_8;
|
||||||
|
sbc.endian = SBC_BE;
|
||||||
|
|
||||||
|
sbc.bitpool = bitpool;
|
||||||
|
sbc.allocation = snr ? SBC_AM_SNR : SBC_AM_LOUDNESS;
|
||||||
|
sbc.blocks = blocks == 4 ? SBC_BLK_4
|
||||||
|
: blocks == 8 ? SBC_BLK_8
|
||||||
|
: blocks == 12 ? SBC_BLK_12
|
||||||
|
: SBC_BLK_16;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add byte to decoding buffer and decode if buffer is full
|
||||||
|
void processByte(uint8_t byte) {
|
||||||
|
buffer[buffer_pos++] = byte;
|
||||||
|
if (buffer_pos >= current_codesize) {
|
||||||
|
ssize_t written;
|
||||||
|
// Encodes ONE input block into ONE output block */
|
||||||
|
// ssize_t sbc_encode(sbc_t *sbc, const void *input, size_t input_len,
|
||||||
|
// void *output, size_t output_len, ssize_t *written);
|
||||||
|
sbc_encode(&sbc, buffer, current_codesize, result_buffer, 512, &written);
|
||||||
|
if (written > 0) {
|
||||||
|
p_print->write(result_buffer, written);
|
||||||
|
}
|
||||||
|
buffer_pos = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace audio_tools
|
@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "AudioTools/AudioOutput.h"
|
#include "AudioTools/AudioOutput.h"
|
||||||
|
#include "AudioLibs/FFT/FFTWindows.h"
|
||||||
|
|
||||||
namespace audio_tools {
|
namespace audio_tools {
|
||||||
|
|
||||||
@ -38,6 +39,8 @@ struct AudioFFTConfig : public AudioBaseInfo {
|
|||||||
uint8_t channel_used = 0;
|
uint8_t channel_used = 0;
|
||||||
int length=8192;
|
int length=8192;
|
||||||
int stride=0;
|
int stride=0;
|
||||||
|
/// Optional window function
|
||||||
|
WindowFunction *window_function = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -88,6 +91,10 @@ class AudioFFTBase : public AudioPrint {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
p_driver->begin(cfg.length);
|
p_driver->begin(cfg.length);
|
||||||
|
if (cfg.window_function!=nullptr){
|
||||||
|
cfg.window_function->begin(cfg.length);
|
||||||
|
}
|
||||||
|
|
||||||
current_pos = 0;
|
current_pos = 0;
|
||||||
return p_driver->isValid();
|
return p_driver->isValid();
|
||||||
}
|
}
|
||||||
@ -243,10 +250,16 @@ class AudioFFTBase : public AudioPrint {
|
|||||||
void processSamples(const void *data, size_t byteCount) {
|
void processSamples(const void *data, size_t byteCount) {
|
||||||
T *dataT = (T*) data;
|
T *dataT = (T*) data;
|
||||||
T sample;
|
T sample;
|
||||||
|
float sample_windowed;
|
||||||
int samples = byteCount/sizeof(T);
|
int samples = byteCount/sizeof(T);
|
||||||
for (int j=0; j<samples; j+=cfg.channels){
|
for (int j=0; j<samples; j+=cfg.channels){
|
||||||
sample = dataT[j+cfg.channel_used];
|
sample = dataT[j+cfg.channel_used];
|
||||||
p_driver->setValue(current_pos, sample);
|
sample_windowed = sample;
|
||||||
|
// optionally apply window function
|
||||||
|
if (cfg.window_function!=nullptr){
|
||||||
|
sample_windowed = cfg.window_function->factor(current_pos) * sample;
|
||||||
|
}
|
||||||
|
p_driver->setValue(current_pos, sample_windowed);
|
||||||
writeStrideBuffer((uint8_t*)&sample, sizeof(T));
|
writeStrideBuffer((uint8_t*)&sample, sizeof(T));
|
||||||
if (++current_pos>=cfg.length){
|
if (++current_pos>=cfg.length){
|
||||||
fft();
|
fft();
|
||||||
|
175
src/AudioLibs/FFT/FFTWindows.h
Normal file
175
src/AudioLibs/FFT/FFTWindows.h
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
/**
|
||||||
|
* @file FFTWindows.h
|
||||||
|
* @author Phil Schatzmann
|
||||||
|
* @brief Different Window functions that can be used by FFT
|
||||||
|
* @version 0.1
|
||||||
|
* @date 2022-04-29
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2022
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
namespace audio_tools {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief FFT Window Function
|
||||||
|
* @author Phil Schatzmann
|
||||||
|
* @copyright GPLv3
|
||||||
|
*/
|
||||||
|
|
||||||
|
class WindowFunction {
|
||||||
|
public:
|
||||||
|
WindowFunction() = default;
|
||||||
|
|
||||||
|
virtual void begin(int samples) {
|
||||||
|
this->samples_minus_1 = -1.0f + samples;
|
||||||
|
this->i_samples = samples;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float ratio(int idx) {
|
||||||
|
return (static_cast<float>(idx) - 1.0 / samples_minus_1);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int samples() { return i_samples; }
|
||||||
|
virtual float factor(int idx) = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
float samples_minus_1 = 0;
|
||||||
|
int i_samples = 0;
|
||||||
|
const float twoPi = 6.28318531;
|
||||||
|
const float fourPi = 12.56637061;
|
||||||
|
const float sixPi = 18.84955593;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Buffered window function, so that we do not need to re-calculate the
|
||||||
|
* values
|
||||||
|
* @author Phil Schatzmann
|
||||||
|
* @copyright GPLv3
|
||||||
|
*/
|
||||||
|
class BufferedWindow : public WindowFunction {
|
||||||
|
public:
|
||||||
|
BufferedWindow(WindowFunction* wf) { p_wf = wf; }
|
||||||
|
|
||||||
|
virtual void begin(int samples) {
|
||||||
|
// process only if there is a change
|
||||||
|
if (p_wf->samples() != samples) {
|
||||||
|
p_wf->begin(samples);
|
||||||
|
len = samples / 2;
|
||||||
|
if (p_buffer != nullptr) delete[] p_buffer;
|
||||||
|
p_buffer = new float[len];
|
||||||
|
for (int j = 0; j < len; j++) {
|
||||||
|
p_buffer[j] = p_wf->factor(j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~BufferedWindow() {
|
||||||
|
if (p_buffer != nullptr) delete[] p_buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float factor(int idx) {
|
||||||
|
return idx < len ? p_buffer[idx] : p_buffer[i_samples - idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
WindowFunction* p_wf = nullptr;
|
||||||
|
float* p_buffer = nullptr;
|
||||||
|
int len;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Rectange : public WindowFunction {
|
||||||
|
public:
|
||||||
|
Rectange() = default;
|
||||||
|
float factor(int idx) { return 1.0; }
|
||||||
|
};
|
||||||
|
|
||||||
|
class Hamming : public WindowFunction {
|
||||||
|
public:
|
||||||
|
Hamming() = default;
|
||||||
|
float factor(int idx) {
|
||||||
|
return 0.54 - (0.46 * cos(twoPi * ratio(idx)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class Hann : public WindowFunction {
|
||||||
|
public:
|
||||||
|
Hann() = default;
|
||||||
|
float factor(int idx) {
|
||||||
|
return 0.54 * (1.0 - cos(twoPi * ratio(idx)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class Triangle : public WindowFunction {
|
||||||
|
public:
|
||||||
|
Triangle() = default;
|
||||||
|
float factor(int idx) {
|
||||||
|
return 1.0 - ((2.0 * fabs((idx - 1) -
|
||||||
|
(static_cast<float>(i_samples - 1) / 2.0))) /
|
||||||
|
samples_minus_1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class Nuttall : public WindowFunction {
|
||||||
|
public:
|
||||||
|
Nuttall() = default;
|
||||||
|
float factor(int idx) {
|
||||||
|
float r = ratio(idx);
|
||||||
|
return 0.355768 - (0.487396 * (cos(twoPi * r))) +
|
||||||
|
(0.144232 * (cos(fourPi * r))) - (0.012604 * (cos(sixPi * r)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class Blackman : public WindowFunction {
|
||||||
|
public:
|
||||||
|
Blackman() = default;
|
||||||
|
float factor(int idx) {
|
||||||
|
float r = ratio(idx);
|
||||||
|
return 0.42323 - (0.49755 * (cos(twoPi * r))) +
|
||||||
|
(0.07922 * (cos(fourPi * r)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class BlackmanNuttall : public WindowFunction {
|
||||||
|
public:
|
||||||
|
BlackmanNuttall() = default;
|
||||||
|
float factor(int idx) {
|
||||||
|
float r = ratio(idx);
|
||||||
|
return 0.3635819 - (0.4891775 * (cos(twoPi * r))) +
|
||||||
|
(0.1365995 * (cos(fourPi * r))) - (0.0106411 * (cos(sixPi * r)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class BlackmanHarris : public WindowFunction {
|
||||||
|
public:
|
||||||
|
BlackmanHarris() = default;
|
||||||
|
float factor(int idx) {
|
||||||
|
float r = ratio(idx);
|
||||||
|
return 0.35875 - (0.48829 * (cos(twoPi * r))) +
|
||||||
|
(0.14128 * (cos(fourPi * r))) - (0.01168 * (cos(sixPi * r)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class FlatTop : public WindowFunction {
|
||||||
|
public:
|
||||||
|
FlatTop() = default;
|
||||||
|
float factor(int idx) {
|
||||||
|
float r = ratio(idx);
|
||||||
|
return 0.2810639 - (0.5208972 * cos(twoPi * r)) +
|
||||||
|
(0.1980399 * cos(fourPi * r));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class Welch : public WindowFunction {
|
||||||
|
public:
|
||||||
|
Welch() = default;
|
||||||
|
float factor(int idx) {
|
||||||
|
float tmp = (((idx - 1) - samples_minus_1 / 2.0) / (samples_minus_1 / 2.0));
|
||||||
|
return 1.0 - (tmp*tmp);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace audio_tools
|
Loading…
Reference in New Issue
Block a user