-
-
Notifications
You must be signed in to change notification settings - Fork 247
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
09f1538
commit a2407de
Showing
2 changed files
with
268 additions
and
0 deletions.
There are no files selected for viewing
53 changes: 53 additions & 0 deletions
53
...mples-communication/http-client/streams-url_flac_foxen-i2s/streams-url_flac_foxen-i2s.ino
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |