Skip to content

Commit

Permalink
Foxen FLAC Decoder
Browse files Browse the repository at this point in the history
  • Loading branch information
pschatzmann committed Jan 10, 2025
1 parent 09f1538 commit a2407de
Show file tree
Hide file tree
Showing 2 changed files with 268 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* @file streams-url_flac_foxen-i2s.ino
* @author Phil Schatzmann
* @brief Demo using the Foxen FLAC decoder
* @version 0.1
* @date 2025-01-09
*
* @copyright Copyright (c) 2022
*
* Warning: The WIFI speed is quite limited and the FLAC files are quite big. So there
* is a big chance that the WIFI is just not fast enough. For my test I was downsampling
* the file to 8000 samples per second!
*/
#include "AudioTools.h"
#include "AudioTools/AudioCodecs/CodecFLACFoxen.h"
#include "AudioTools/AudioLibs/AudioBoardStream.h"

const char* ssid = "ssid";
const char* pwd = "password";
URLStream url(ssid, pwd);
AudioBoardStream i2s(AudioKitEs8388V1); // or replace with e.g. I2SStream i2s;

FLACFoxenDecoder flac(5*1024, 2);
EncodedAudioStream dec(&i2s, &flac); // Decoding to i2s
StreamCopy copier(dec, url, 1024); // copy url to decoder

void setup() {
Serial.begin(115200);
AudioToolsLogger.begin(Serial, AudioToolsLogLevel::Warning);
while(!Serial);
Serial.println("starting...");

// Open I2S: if the buffer is too slow audio is breaking up
auto cfg =i2s.defaultConfig(TX_MODE);
cfg.buffer_size= 1024;
cfg.buffer_count = 20;
if (!i2s.begin(cfg)){
Serial.println("i2s error");
stop();
}

// Open url
url.begin("https://pschatzmann.github.io/Resources/audio/audio.flac", "audio/flac");

// Start decoder
if (!dec.begin()){
Serial.println("Decoder failed");
}
}

void loop() {
copier.copy();
}
215 changes: 215 additions & 0 deletions src/AudioTools/AudioCodecs/CodecFLACFoxen.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
#pragma once

#include "AudioTools/AudioCodecs/AudioCodecsBase.h"
#include "AudioTools/CoreAudio/Buffers.h"
#include "foxen-flac.h"

namespace audio_tools {

#define FOXEN_IN_BUFFER_SIZE 1024 * 2
#define FOXEN_OUT_BUFFER_SIZE 1024 * 4

/**
* @brief Foxen FLAC Decoder.
* @ingroup codecs
* @ingroup decoder
* @author Phil Schatzmann
* @copyright GPLv3
*/
class FLACFoxenDecoder : public AudioDecoder {
public:
FLACFoxenDecoder() = default;

/// Default Constructor
FLACFoxenDecoder(int maxBlockSize, int maxChannels,
bool convertTo16Bits = true) {
is_convert_to_16 = convertTo16Bits;
max_block_size = maxBlockSize;
max_channels = maxChannels;
};

/// Destructor - calls end();
~FLACFoxenDecoder() { end(); }

bool begin() {
TRACEI();
is_active = false;
if (flac == nullptr) {
size_t foxen_size = fx_flac_size(max_block_size, max_channels);
if (foxen_size > 0) {
foxen_data.resize(foxen_size);
}
if (foxen_data.data() != nullptr) {
flac = fx_flac_init(foxen_data.data(), max_block_size, max_channels);
}
}
if (flac != nullptr) {
is_active = true;
} else {
LOGE("not enough memory");
if (is_stop_on_error) stop();
}
if (buffer.size() == 0) {
buffer.resize(in_buffer_size);
out.resize(FOXEN_OUT_BUFFER_SIZE);
}

return is_active;
}

void end() {
TRACEI();
flush();
if (flac != nullptr) {
foxen_data.resize(0);
flac = nullptr;
}
buffer.resize(0);
out.resize(0);
is_active = false;
}

size_t write(const uint8_t *data, size_t len) override {
LOGD("write: %d", len);
// no processing if not active
if (!is_active) return 0;

size_t result = buffer.writeArray(data, len);
LOGD("buffer availabe: %d", buffer.available());

while (buffer.available() > 0) {
if (!decode()) break;
}

// if the buffer is full we could not decode anything
if (buffer.available() == buffer.size()) {
LOGE("Decoder did not consume any data");
if (is_stop_on_error) stop();
}

LOGD("write: %d -> %d", len, result);
return result;
}

void flush() { decode(); }

operator bool() override { return is_active; }

/// Defines the input buffer size (default is 2k)
void setInBufferSize(int size){
in_buffer_size = size;
}

/// Defines the number of 32 bit samples for providing the result (default is 4k)
void setOutBufferSize(int size){
out_buffer_size = size;
}

/// Defines the maximum FLAC blocksize: drives the buffer allocation
void setMaxBlockSize(int size){
max_block_size = size;
}

/// Defines the maximum number of channels: drives the buffer allocation
void setMaxChannels(int ch){
max_channels = ch;
}

/// Select between 16 and 32 bit output: the default is 16 bits
void set32Bit(bool flag) {
is_convert_to_16 = !flag;
}

protected:
fx_flac_t *flac = nullptr;
SingleBuffer<uint8_t> buffer{0};
Vector<int32_t> out;
Vector<uint8_t> foxen_data{0};
bool is_active = false;
bool is_convert_to_16 = true;
bool is_stop_on_error = true;
int bits_eff = 0;
int max_block_size = 5 * 1024;
int max_channels = 2;
int in_buffer_size = FOXEN_IN_BUFFER_SIZE;
int out_buffer_size = FOXEN_OUT_BUFFER_SIZE;

bool decode() {
TRACED();
uint32_t out_len = out.size();
uint32_t buf_len = buffer.available();
uint32_t buf_len_result = buf_len;
int rc = fx_flac_process(flac, buffer.data(), &buf_len_result, out.data(),
&out_len);
// assert(out_len <= FOXEN_OUT_BUFFER_SIZE);

switch (rc) {
case FLAC_END_OF_METADATA: {
processMetadata();
} break;

case FLAC_ERR: {
LOGE("FLAC decoder in error state!");
if (is_stop_on_error) stop();
} break;

default: {
if (out_len > 0) {
LOGD("Providing data: %d samples", out_len);
if (is_convert_to_16) {
write16BitData(out_len);
} else {
write32BitData(out_len);
}
}
} break;
}
LOGD("processed: %d bytes of %d -> %d samples", buf_len_result, buf_len,
out_len);
// removed processed bytes from buffer
buffer.clearArray(buf_len_result);
return buf_len_result > 0 || out_len > 0;
}

void write32BitData(int out_len) {
TRACED();
// write the result to the output destination
writeBlocking(p_print, (uint8_t *)out.data(), out_len * sizeof(int32_t));
}

void write16BitData(int out_len) {
TRACED();
// in place convert to 16 bits
int16_t *out16 = (int16_t *)out.data();
for (int j = 0; j < out_len; j++) {
out16[j] = out.data()[j] >> 16; // 65538;
}
// write the result to the output destination
LOGI("writeBlocking: %d", out_len * sizeof(int16_t));
writeBlocking(p_print, (uint8_t *)out.data(), out_len * sizeof(int16_t));
}

void processMetadata() {
bits_eff = fx_flac_get_streaminfo(flac, FLAC_KEY_SAMPLE_SIZE);
int info_blocksize = fx_flac_get_streaminfo(flac, FLAC_KEY_MAX_BLOCK_SIZE);

LOGI("bits: %d", bits_eff);
LOGI("blocksize: %d", info_blocksize);
// assert(bits_eff == 32);
info.sample_rate = fx_flac_get_streaminfo(flac, FLAC_KEY_SAMPLE_RATE);
info.channels = fx_flac_get_streaminfo(flac, FLAC_KEY_N_CHANNELS);
info.bits_per_sample = is_convert_to_16 ? 16 : bits_eff;
info.logInfo();
if (info.channels > max_channels) {
LOGE("max channels too low: %d -> %d", max_channels, info.channels);
if (is_stop_on_error) stop();
}
if (info_blocksize > max_block_size) {
LOGE("max channels too low: %d -> %d", max_block_size, info_blocksize);
if (is_stop_on_error) stop();
}
notifyAudioChange(info);
}
};

} // namespace audio_tools

0 comments on commit a2407de

Please sign in to comment.