mirror of
https://github.com/pschatzmann/arduino-audio-tools.git
synced 2024-09-22 02:47: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) {
|
||||
resize(size);
|
||||
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) {
|
||||
resize_internal(copyFrom.size(), false);
|
||||
for (int j=0;j<copyFrom.size();j++){
|
||||
data[j] = copyFrom[j];
|
||||
p_data[j] = copyFrom[j];
|
||||
}
|
||||
this->len = copyFrom.size();
|
||||
}
|
||||
@ -119,14 +119,14 @@ class Vector {
|
||||
this->len = to - from;
|
||||
resize_internal(this->len, false);
|
||||
for (size_t j=0;j<this->len;j++){
|
||||
data[j] = from[j];
|
||||
p_data[j] = from[j];
|
||||
}
|
||||
}
|
||||
|
||||
inline ~Vector() {
|
||||
clear();
|
||||
shrink_to_fit();
|
||||
delete [] this->data;
|
||||
delete [] this->p_data;
|
||||
}
|
||||
|
||||
inline void clear() {
|
||||
@ -143,14 +143,14 @@ class Vector {
|
||||
|
||||
inline void push_back(T value){
|
||||
resize_internal(len+1, true);
|
||||
data[len] = value;
|
||||
p_data[len] = value;
|
||||
len++;
|
||||
}
|
||||
|
||||
inline void push_front(T value){
|
||||
resize_internal(len+1, true);
|
||||
memmove(data,data+1,len*sizeof(T));
|
||||
data[0] = value;
|
||||
memmove(p_data,p_data+1,len*sizeof(T));
|
||||
p_data[0] = value;
|
||||
len++;
|
||||
}
|
||||
|
||||
@ -164,7 +164,7 @@ class Vector {
|
||||
if (len>0) {
|
||||
len--;
|
||||
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;
|
||||
int pos = 0;
|
||||
for (auto ptr = v1; ptr != v2; ptr++) {
|
||||
data[pos++] = *ptr;
|
||||
p_data[pos++] = *ptr;
|
||||
}
|
||||
}
|
||||
|
||||
@ -184,45 +184,45 @@ class Vector {
|
||||
resize_internal(number, false);
|
||||
this->len = number;
|
||||
for (int j=0;j<number;j++){
|
||||
data[j]=value;
|
||||
p_data[j]=value;
|
||||
}
|
||||
}
|
||||
|
||||
inline void swap(Vector<T> &in){
|
||||
// save data
|
||||
T *dataCpy = data;
|
||||
T *dataCpy = p_data;
|
||||
int bufferLenCpy = bufferLen;
|
||||
int lenCpy = len;
|
||||
// swap this
|
||||
data = in.data;
|
||||
p_data = in.p_data;
|
||||
len = in.len;
|
||||
bufferLen = in.bufferLen;
|
||||
// swp in
|
||||
in.data = dataCpy;
|
||||
in.p_data = dataCpy;
|
||||
in.len = lenCpy;
|
||||
in.bufferLen = bufferLenCpy;
|
||||
}
|
||||
|
||||
inline T &operator[](int index) {
|
||||
return data[index];
|
||||
return p_data[index];
|
||||
}
|
||||
|
||||
inline Vector<T> &operator=(Vector<T> ©From) {
|
||||
resize_internal(copyFrom.size(), false);
|
||||
for (int j=0;j<copyFrom.size();j++){
|
||||
data[j] = copyFrom[j];
|
||||
p_data[j] = copyFrom[j];
|
||||
}
|
||||
this->len = copyFrom.size();
|
||||
}
|
||||
|
||||
inline T &operator[] (const int index) const {
|
||||
return data[index];
|
||||
return p_data[index];
|
||||
}
|
||||
|
||||
inline bool resize(int newSize, T value){
|
||||
if (resize(newSize)){
|
||||
for (int j=0;j<newSize;j++){
|
||||
data[j]=value;
|
||||
p_data[j]=value;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -245,15 +245,15 @@ class Vector {
|
||||
}
|
||||
|
||||
inline iterator begin(){
|
||||
return iterator(data, 0);
|
||||
return iterator(p_data, 0);
|
||||
}
|
||||
|
||||
inline T& back(){
|
||||
return *iterator(data+(len-1), len-1);
|
||||
return *iterator(p_data+(len-1), len-1);
|
||||
}
|
||||
|
||||
inline iterator end(){
|
||||
return iterator(data+len, len);
|
||||
return iterator(p_data+len, len);
|
||||
}
|
||||
|
||||
// removes a single element
|
||||
@ -262,31 +262,35 @@ class Vector {
|
||||
if (pos<len){
|
||||
int lenToEnd = len - pos - 1;
|
||||
// call destructor on data to be erased
|
||||
data[pos].~T();
|
||||
p_data[pos].~T();
|
||||
// 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
|
||||
data[len-1] = T();
|
||||
p_data[len-1] = T();
|
||||
len--;
|
||||
}
|
||||
}
|
||||
|
||||
T* data(){
|
||||
return p_data;
|
||||
}
|
||||
|
||||
protected:
|
||||
int bufferLen;
|
||||
int len = 0;
|
||||
T *data = nullptr;
|
||||
T *p_data = nullptr;
|
||||
|
||||
inline void resize_internal(int newSize, bool copy, bool shrink=false) {
|
||||
//bool withNewSize = false;
|
||||
if (newSize>bufferLen || this->data==nullptr ||shrink){
|
||||
if (newSize>bufferLen || this->p_data==nullptr ||shrink){
|
||||
//withNewSize = true;
|
||||
T* oldData = data;
|
||||
T* oldData = p_data;
|
||||
int oldBufferLen = this->bufferLen;
|
||||
this->data = new T[newSize+1];
|
||||
this->p_data = new T[newSize+1];
|
||||
this->bufferLen = newSize;
|
||||
if (oldData != nullptr) {
|
||||
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){
|
||||
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++){
|
||||
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
|
||||
#include "openaptx.h"
|
||||
#include "AudioTools/AudioTypes.h"
|
||||
|
||||
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
|
||||
|
||||
#include "AudioTools/AudioOutput.h"
|
||||
#include "AudioLibs/FFT/FFTWindows.h"
|
||||
|
||||
namespace audio_tools {
|
||||
|
||||
@ -38,6 +39,8 @@ struct AudioFFTConfig : public AudioBaseInfo {
|
||||
uint8_t channel_used = 0;
|
||||
int length=8192;
|
||||
int stride=0;
|
||||
/// Optional window function
|
||||
WindowFunction *window_function = nullptr;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -88,6 +91,10 @@ class AudioFFTBase : public AudioPrint {
|
||||
return false;
|
||||
}
|
||||
p_driver->begin(cfg.length);
|
||||
if (cfg.window_function!=nullptr){
|
||||
cfg.window_function->begin(cfg.length);
|
||||
}
|
||||
|
||||
current_pos = 0;
|
||||
return p_driver->isValid();
|
||||
}
|
||||
@ -243,10 +250,16 @@ class AudioFFTBase : public AudioPrint {
|
||||
void processSamples(const void *data, size_t byteCount) {
|
||||
T *dataT = (T*) data;
|
||||
T sample;
|
||||
float sample_windowed;
|
||||
int samples = byteCount/sizeof(T);
|
||||
for (int j=0; j<samples; j+=cfg.channels){
|
||||
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));
|
||||
if (++current_pos>=cfg.length){
|
||||
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