Skip to content

Commit a93af20

Browse files
committed
ALAC Codec final corrections
1 parent eff1536 commit a93af20

File tree

3 files changed

+134
-11
lines changed

3 files changed

+134
-11
lines changed

src/AudioTools/AudioCodecs/CodecALAC.h

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class ALACBinaryConfig {
2929
* https://github.com/pschatzmann/codec-alac. This implementaion is based on
3030
* https://github.com/macosforge/alac
3131
* @note Please note that this codec usually needs a container:
32-
* The write() method also expects a complete frame to be written!
32+
* The write() method expects a complete frame to be written!
3333
* The decoder also expects to get the config from the encoder, however we have
3434
* some fallback functionality that uses the AudioInfo and the frame size
3535
* defined in the constructor.
@@ -38,6 +38,7 @@ class ALACBinaryConfig {
3838
*/
3939
class DecoderALAC : public AudioDecoder {
4040
public:
41+
/// Default constructor: you can define your own optimized frame size
4142
DecoderALAC(int frameSize = kALACDefaultFrameSize) {
4243
// this is used when setCodecConfig() is not called with encoder info
4344
setFrameSize(frameSize);
@@ -46,7 +47,6 @@ class DecoderALAC : public AudioDecoder {
4647

4748
// define ALACSpecificConfig
4849
bool setCodecConfig(ALACSpecificConfig config) {
49-
convert(config);
5050
return setCodecConfig((uint8_t*)&config, sizeof(config));
5151
}
5252

@@ -86,10 +86,15 @@ class DecoderALAC : public AudioDecoder {
8686
dec.mConfig.bitDepth = from.bits_per_sample;
8787
}
8888

89-
9089
/// we expect the write is called for a complete frame!
9190
size_t write(const uint8_t* encodedFrame, size_t encodedLen) override {
9291
LOGI("DecoderALAC::write: %d", (int)encodedLen);
92+
// Safety check
93+
if (!is_init) {
94+
LOGE("Decoder not initialized");
95+
return 0;
96+
}
97+
9398
// Make sure we have the output buffer set up
9499
if (result_buffer.size() != outputBufferSize()) {
95100
result_buffer.resize(outputBufferSize());
@@ -118,11 +123,11 @@ class DecoderALAC : public AudioDecoder {
118123
int open = outputSize;
119124
int processed = 0;
120125
while (open > 0) {
121-
int writeSize = MIN(1024, outputSize);
126+
int writeSize = MIN(1024, open);
122127
size_t written =
123128
p_print->write(result_buffer.data() + processed, writeSize);
124129
if (writeSize != written) {
125-
LOGE("write error: %d -> %d", outputSize, written);
130+
LOGE("write error: %d -> %d", (int)outputSize, (int)written);
126131
}
127132
open -= written;
128133
processed += written;
@@ -132,8 +137,11 @@ class DecoderALAC : public AudioDecoder {
132137

133138
operator bool() { return true; }
134139

140+
/// Set the default frame size: this will be overwritten if you call
141+
/// setCodecConfig()
135142
void setFrameSize(int frames) { dec.mConfig.frameLength = frames; }
136143

144+
/// Provides the actual frame size
137145
int frameSize() { return dec.mConfig.frameLength; }
138146

139147
protected:
@@ -143,7 +151,7 @@ class DecoderALAC : public AudioDecoder {
143151
struct BitBuffer bits;
144152

145153
void setDefaultConfig() {
146-
LOGW("Setting up default ALAC config")
154+
// LOGW("Setting up default ALAC config")
147155
AudioInfo info = audioInfo();
148156
ALACSpecificConfig tmp;
149157
// Essential parameters for ALAC compression
@@ -174,6 +182,7 @@ class DecoderALAC : public AudioDecoder {
174182
tmp.maxFrameBytes =
175183
uncompressedFrameSize + (uncompressedFrameSize / 2) + 64 + 50;
176184

185+
convert(tmp);
177186
setCodecConfig(tmp);
178187
}
179188

@@ -201,6 +210,7 @@ class DecoderALAC : public AudioDecoder {
201210
*/
202211
class EncoderALAC : public AudioEncoder {
203212
public:
213+
/// Default constructor: you can define your own optimized frame size
204214
EncoderALAC(int frameSize = kALACDefaultFrameSize) {
205215
setFrameSize(frameSize);
206216
}
@@ -219,10 +229,18 @@ class EncoderALAC : public AudioEncoder {
219229
enc.SetFrameSize(frame_size);
220230
int rc = enc.InitializeEncoder(out_format);
221231

222-
uint32_t inputBufferSize =
223-
frame_size * info.channels * (info.bits_per_sample / 8);
232+
// Calculate exact buffer sizes based on frame settings
233+
uint32_t bytesPerSample = info.bits_per_sample / 8;
234+
uint32_t inputBufferSize = frame_size * info.channels * bytesPerSample;
235+
// Calculate output buffer size
224236
uint32_t outputBufferSize = inputBufferSize * 2; // Ensure enough space
225237

238+
LOGI(
239+
"ALAC Encoder: frame_size=%d, inputBuf=%d, outputBuf=%d, channels=%d, "
240+
"bits=%d",
241+
frame_size, inputBufferSize, outputBufferSize, info.channels,
242+
info.bits_per_sample);
243+
226244
in_buffer.resize(inputBufferSize);
227245
out_buffer.resize(outputBufferSize);
228246
is_started = rc == 0;
@@ -242,13 +260,13 @@ class EncoderALAC : public AudioEncoder {
242260
in_buffer.write(data[j]);
243261
if (in_buffer.isFull()) {
244262
// provide max output buffer size
245-
int32_t ioNumBytes = out_buffer.size();
263+
int32_t ioNumBytes = in_buffer.size();
246264
int rc =
247265
enc.Encode(input_format, out_format, (uint8_t*)in_buffer.data(),
248266
out_buffer.data(), &ioNumBytes);
249267
size_t written = p_print->write(out_buffer.data(), ioNumBytes);
250268
if (ioNumBytes != written) {
251-
LOGE("write error: %d -> %d", ioNumBytes, written);
269+
LOGE("write error: %d -> %d", (int)ioNumBytes, (int)written);
252270
}
253271
in_buffer.reset();
254272
}
@@ -270,10 +288,19 @@ class EncoderALAC : public AudioEncoder {
270288

271289
operator bool() { return is_started && p_print != nullptr; }
272290

291+
/// Mime type: returns audio/alac
273292
const char* mime() override { return "audio/alac"; }
274293

275-
void setFrameSize(int frames) { frame_size = frames; }
294+
/// Defines the frame size for the decoder: default is 4096 frames
295+
void setFrameSize(int frames) {
296+
if (is_started) {
297+
LOGE("Can't change frame size on started encoder")
298+
return;
299+
}
300+
frame_size = frames;
301+
}
276302

303+
/// Determins the actually defined number of frames
277304
int frameSize() { return frame_size; }
278305

279306
protected:
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
cmake_minimum_required(VERSION 3.20)
2+
3+
# set the project name
4+
project(alac)
5+
set (CMAKE_CXX_STANDARD 11)
6+
set (DCMAKE_CXX_FLAGS "-Werror")
7+
8+
include(FetchContent)
9+
option(BUILD_SHARED_LIBS "Build using shared libraries" OFF)
10+
11+
# Build with arduino-fdk-aac
12+
FetchContent_Declare(codec-alac GIT_REPOSITORY "https://github.com/pschatzmann/codec-alac.git" GIT_TAG main )
13+
FetchContent_GetProperties(codec-alac)
14+
if(NOT codec-alac_POPULATED)
15+
FetchContent_Populate(codec-alac)
16+
add_subdirectory(${codec-alac_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}/codec-alac)
17+
endif()
18+
19+
# provide audio-tools
20+
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
21+
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../../.. ${CMAKE_CURRENT_BINARY_DIR}/arduino-audio-tools )
22+
endif()
23+
24+
# build sketch as executable
25+
set_source_files_properties(alac.ino PROPERTIES LANGUAGE CXX)
26+
add_executable (alac alac.ino)
27+
28+
# set preprocessor defines
29+
target_compile_definitions(arduino_emulator PUBLIC -DDEFINE_MAIN)
30+
target_compile_definitions(alac PUBLIC -DARDUINO -DIS_DESKTOP)
31+
target_compile_options(alac PRIVATE -Wno-multichar)
32+
33+
34+
# set compile optioins
35+
target_compile_options(arduino-audio-tools INTERFACE -Wno-inconsistent-missing-override)
36+
37+
# specify libraries
38+
target_link_libraries(alac PRIVATE codec-alac arduino_emulator arduino-audio-tools )
39+

tests-cmake/codec/alac/alac.ino

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/**
2+
* @file test-codec-alac.ino
3+
* @author Phil Schatzmann
4+
* @brief generate sine wave -> encoder -> decoder -> audiokit (i2s)
5+
* @version 0.1
6+
*
7+
* @copyright Copyright (c) 2025
8+
*
9+
*/
10+
#include "AudioTools.h"
11+
#include "AudioTools/AudioCodecs/CodecALAC.h"
12+
13+
//SET_LOOP_TASK_STACK_SIZE(16*1024); // 16KB
14+
15+
AudioInfo info(44100, 2, 16);
16+
SineWaveGenerator<int16_t> sineWave( 32000); // subclass of SoundGenerator with max amplitude of 32000
17+
GeneratedSoundStream<int16_t> sound( sineWave); // Stream generated from sine wave
18+
CsvOutput<int16_t> out(Serial);
19+
EncoderALAC enc_alac;
20+
DecoderALAC dec_alac;
21+
CodecNOP dec_nop;
22+
EncodedAudioStream decoder(&out, &dec_alac); // encode and write
23+
EncodedAudioStream encoder(&decoder, &enc_alac); // encode and write
24+
StreamCopy copier(encoder, sound);
25+
26+
void setup() {
27+
Serial.begin(115200);
28+
AudioToolsLogger.begin(Serial, AudioToolsLogLevel::Debug);
29+
30+
// start Output
31+
Serial.println("starting Output...");
32+
auto cfgi = out.defaultConfig(TX_MODE);
33+
cfgi.copyFrom(info);
34+
out.begin(cfgi);
35+
36+
// Setup sine wave
37+
sineWave.begin(info, N_B4);
38+
39+
// start encoder
40+
encoder.begin(info);
41+
42+
// optionally copy config from encoder to decoder
43+
// since decoder already has audio info and frames
44+
//dec_alac.setCodecConfig(enc_alac.config());
45+
//dec_alac.setCodecConfig(enc_alac.binaryConfig());
46+
47+
// start decoder
48+
decoder.begin(info);
49+
50+
51+
Serial.println("Test started...");
52+
}
53+
54+
55+
void loop() {
56+
copier.copy();
57+
}

0 commit comments

Comments
 (0)