Skip to content

Commit ad67052

Browse files
committed
Allocator that forces IRAM
1 parent d821e55 commit ad67052

File tree

3 files changed

+157
-74
lines changed

3 files changed

+157
-74
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
2+
#include "AudioTools.h"
3+
#include "AudioTools/AudioLibs/AudioSourceSD.h" // or AudioSourceIdxSD.h
4+
#include "AudioTools/AudioLibs/AudioBoardStream.h" // for SD pins
5+
6+
AudioSourceSD source("/", "", PIN_AUDIO_KIT_SD_CARD_CS);
7+
MP3HeaderParser mp3;
8+
9+
void setup() {
10+
Serial.begin(115200);
11+
AudioToolsLogger.begin(Serial, AudioToolsLogLevel::Info);
12+
13+
SPI.begin(PIN_AUDIO_KIT_SD_CARD_CLK, PIN_AUDIO_KIT_SD_CARD_MISO, PIN_AUDIO_KIT_SD_CARD_MOSI, PIN_AUDIO_KIT_SD_CARD_CS);
14+
while (!SD.begin(PIN_AUDIO_KIT_SD_CARD_CS)) {
15+
Serial.println("SD.begin failed");
16+
delay(1000);
17+
}
18+
19+
source.begin();
20+
21+
}
22+
23+
void loop() {
24+
25+
File* p_file = (File*) source.nextStream();
26+
if (p_file==nullptr) stop();
27+
File& file = *p_file;
28+
29+
uint8_t tmp[1024];
30+
int len = file.readBytes((char*)tmp, 1024);
31+
32+
// check if mp3 file
33+
bool is_mp3 = mp3.isValid(tmp, len);
34+
35+
// print result
36+
Serial.print(" ==> ");
37+
Serial.print(file.name());
38+
Serial.println(is_mp3 ? " +":" -");
39+
file.close();
40+
41+
}

src/AudioTools/AudioCodecs/MP3HeaderParser.h

+94-74
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ namespace audio_tools {
66
/**
77
* @brief MP3 header parser to check if the data is a valid mp3 and
88
* to extract some relevant audio information.
9+
* See https://www.codeproject.com/KB/audio-video/mpegaudioinfo.aspx
910
* @ingroup codecs
1011
* @ingroup decoder
1112
* @author Phil Schatzmann
@@ -61,7 +62,7 @@ class MP3HeaderParser {
6162
bool Protection : 1;
6263

6364
// sample & bitrate indexes meaning differ depending on MPEG version
64-
// use getBitrate() and GetSamplerate()
65+
// use getBitRate() and GetSamplerate()
6566
bool BitrateIndex : 4;
6667
bool SampleRateIndex : 2;
6768

@@ -89,8 +90,6 @@ class MP3HeaderParser {
8990
// indicates whether the frame is located on the original media or a copy
9091
bool Original : 1;
9192

92-
uint16_t crc; // crc data if Protection is true
93-
9493
// indicates to the decoder that the file must be de-emphasized, ie the
9594
// decoder must 're-equalize' the sound after a Dolby-like noise supression.
9695
// It is rarely used.
@@ -106,7 +105,7 @@ class MP3HeaderParser {
106105
ANY = 0,
107106
};
108107

109-
signed int getBitrate() const {
108+
signed int getBitRate() const {
110109
// version, layer, bit index
111110
static signed char rateTable[4][4][16] = {
112111
// version[00] = MPEG_2_5
@@ -153,8 +152,12 @@ class MP3HeaderParser {
153152
{0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, -1},
154153
},
155154
};
156-
157-
return rateTable[AudioVersion][Layer][BitrateIndex] * 8000;
155+
char rate_byte = rateTable[AudioVersion][Layer][BitrateIndex];
156+
if (rate_byte == -1) {
157+
LOGE("Unsupported bitrate");
158+
return 0;
159+
}
160+
return rate_byte * 8000;
158161
}
159162

160163
enum SpecialSampleRate {
@@ -178,71 +181,94 @@ class MP3HeaderParser {
178181
}
179182

180183
int getFrameLength() {
181-
return int((144 * getBitrate() / getSampleRate()) + Padding);
184+
int sample_rate = getSampleRate();
185+
if (sample_rate == 0) return 0;
186+
return int((144 * getBitRate() / sample_rate) + Padding);
182187
}
183-
184188
};
185189

186190
public:
187191
/// parses the header string and returns true if this is a valid mp3 file
188192
bool isValid(const uint8_t* data, int len) {
189193
memset(&header, 0, sizeof(header));
190-
StrView str((char*)data, len);
191194

192-
if (str.startsWith("ID3")) {
195+
// if we start with ID3 -> valid mp3
196+
if (memcmp(data, "ID3", 3) == 0) {
197+
LOGI("ID3 found");
193198
return true;
194199
}
195200

196-
int pos = seekFrameSync(str);
197-
if (pos == -1) {
201+
int sync_pos = seekFrameSync(data, len);
202+
if (sync_pos == -1) {
198203
LOGE("Could not find FrameSync");
199204
return false;
200205
}
201206

202-
// xing header
203-
if (pos > 0 && str.contains("Xing")) {
207+
// xing header -> valid mp3
208+
if (sync_pos >= 0 && contains(data, "Xing", len)) {
209+
LOGI("Xing found");
204210
return true;
205211
}
206212

207-
// xing header
208-
if (pos > 0 && str.contains("Info")) {
213+
// xing header -> valid mp3
214+
if (sync_pos >= 0 && contains(data, "Info", len)) {
215+
LOGI("Xing Info found");
209216
return true;
210217
}
211218

212-
int len_available = len - pos;
213-
if (len_available < sizeof(header)) {
214-
LOGE("Not enough data to determine mp3 header");
215-
return false;
216-
}
217-
218-
// fill header with data
219-
StrView header_str((char*)data + pos, len_available);
220-
header = readFrameHeader(header_str);
219+
// find valid segement in available data
220+
bool is_valid_mp3 = false;
221+
while (true) {
222+
LOGI("checking header at %d", sync_pos);
223+
int len_available = len - sync_pos;
221224

222-
// check end of frame: it must contains a sync word
223-
int end_pos = findSyncWord((uint8_t*)header_str.c_str(), header_str.length());
224-
int pos_expected = getFrameLength();
225-
if (pos_expected < header_str.length()){
226-
if (end_pos != pos_expected){
227-
LOGE("Expected SynchWord missing");
228-
return false;
225+
// check if we have enough data for header
226+
if (len_available < sizeof(header)) {
227+
LOGE("Not enough data to determine mp3 header");
228+
break;
229229
}
230-
}
231230

232-
// calculate crc
233-
uint16_t crc = crc16((uint8_t*)header_str.c_str(),
234-
sizeof(FrameHeader) - sizeof(uint16_t));
235-
// validate
236-
return FrameReason::VALID == validateFrameHeader(header, crc);
231+
readFrameHeader(data);
232+
is_valid_mp3 = validate(data + sync_pos, len_available);
233+
234+
// find end sync
235+
int pos = seekFrameSync(data + sync_pos + 2, len_available - 2);
236+
// no more data to be validated
237+
if (pos == -1) break;
238+
// calculate new sync_pos
239+
sync_pos = pos + sync_pos + 2;
240+
241+
// success and we found an end sync with a bit rate
242+
if (is_valid_mp3 && getSampleRate() != 0) break;
243+
}
244+
if (is_valid_mp3) {
245+
LOGI("-------------------");
246+
LOGI("is mp3: %s", is_valid_mp3 ? "yes" : "no");
247+
LOGI("frame size: %d", getFrameLength());
248+
LOGI("sample rate: %u", getSampleRate());
249+
LOGI("bit rate index: %d", getFrameHeader().BitrateIndex);
250+
LOGI("bit rate: %d", getBitRate());
251+
LOGI("Padding: %d", getFrameHeader().Padding);
252+
LOGI("Version: %s (0x%x)", getVersionStr(),
253+
getFrameHeader().AudioVersion);
254+
LOGI("Layer: %s (0x%x)", getLayerStr(), getFrameHeader().Layer);
255+
}
256+
return is_valid_mp3;
237257
}
238258

239259
uint16_t getSampleRate() const { return header.getSampleRate(); }
240260

241-
int getBitrate() const { return header.getBitrate(); }
261+
int getBitRate() const { return header.getBitRate(); }
242262

243263
/// Determines the frame length
244-
int getFrameLength() {
245-
return header.getFrameLength();
264+
int getFrameLength() { return header.getFrameLength(); }
265+
266+
/// Provides the estimated playing time in seconds based on the bitrate of the
267+
/// first segment
268+
size_t getPlayingTime(size_t fileSizeBytes) {
269+
int bitrate = getBitRate();
270+
if (bitrate == 0) return 0;
271+
return fileSizeBytes / bitrate;
246272
}
247273

248274
const char* getVersionStr() const {
@@ -264,7 +290,7 @@ class MP3HeaderParser {
264290
FrameHeader getFrameHeader() { return header; }
265291

266292
/// Finds the mp3/aac sync word
267-
int findSyncWord(uint8_t* buf, int nBytes, uint8_t SYNCWORDH = 0xFF,
293+
int findSyncWord(const uint8_t* buf, int nBytes, uint8_t SYNCWORDH = 0xFF,
268294
uint8_t SYNCWORDL = 0xF0) {
269295
for (int i = 0; i < nBytes - 1; i++) {
270296
if ((buf[i + 0] & SYNCWORDH) == SYNCWORDH &&
@@ -277,34 +303,31 @@ class MP3HeaderParser {
277303
protected:
278304
FrameHeader header;
279305

280-
uint16_t crc16(const uint8_t* data_p, size_t length) {
281-
uint8_t x;
282-
uint16_t crc = 0xFFFF;
306+
bool validate(const uint8_t* data, size_t len) {
307+
assert(header.FrameSyncByte = 0xFF);
308+
// check end of frame: it must contains a sync word
309+
return FrameReason::VALID == validateFrameHeader(header);
310+
}
283311

284-
while (length--) {
285-
x = crc >> 8 ^ *data_p++;
286-
x ^= x >> 4;
287-
crc = (crc << 8) ^ ((unsigned short)(x << 12)) ^
288-
((unsigned short)(x << 5)) ^ ((unsigned short)x);
312+
bool contains(const uint8_t* data, const char* toFind, size_t len) {
313+
int find_str_len = strlen(toFind);
314+
for (int j = 0; j < len - find_str_len; j++) {
315+
if (memcmp(data + j, toFind, find_str_len) == 0) return true;
289316
}
290-
return crc;
317+
return false;
291318
}
292319

293320
// Seeks to the byte at the end of the next continuous run of 11 set bits.
294321
//(ie. after seeking the cursor will be on the byte of which its 3 most
295322
// significant bits are part of the frame sync)
296-
int seekFrameSync(StrView str) {
323+
int seekFrameSync(const uint8_t* str, size_t len) {
297324
char cur;
298-
for (int j = 0; j < str.length() - 1; j++) {
325+
for (int j = 0; j < len - 1; j++) {
299326
cur = str[j];
300327
// read bytes until EOF or a byte with all bits set is encountered
301328
if ((cur & 0b11111111) != 0b11111111) continue;
302329

303-
// peek next byte, ensure its not past EOF, and check that its 3 most
304-
// significant bits are set to complete the continuous run of 11
305-
char next = str[j + 1];
306-
307-
if ((next & 0b11100000) != 0b11100000) {
330+
if ((str[j + 1] & 0b11100000) != 0b11100000) {
308331
// if the next byte does not have its 3 most significant bits set it is
309332
// not the end of the framesync and it also cannot be the start of a
310333
// framesync so just skip over it here without the check
@@ -316,10 +339,14 @@ class MP3HeaderParser {
316339
return -1;
317340
}
318341

319-
FrameHeader readFrameHeader(StrView in) {
320-
FrameHeader header;
321-
memcpy(&header, in.c_str(), sizeof(header));
322-
return header;
342+
void readFrameHeader(const uint8_t* data) {
343+
assert(data[0] == 0xFF);
344+
assert((data[1] & 0b11100000) == 0b11100000);
345+
346+
memcpy(&header, data, sizeof(header));
347+
348+
LOGI("- sample rate: %u", getSampleRate());
349+
LOGI("- bit rate: %d", getBitRate());
323350
}
324351

325352
enum class FrameReason {
@@ -333,14 +360,7 @@ class MP3HeaderParser {
333360
INVALID_CRC,
334361
};
335362

336-
FrameReason validateFrameHeader(const FrameHeader& header, uint16_t crc) {
337-
if (header.Protection) {
338-
if (header.crc != crc) {
339-
LOGI("invalid CRC");
340-
return FrameReason::INVALID_CRC;
341-
}
342-
}
343-
363+
FrameReason validateFrameHeader(const FrameHeader& header) {
344364
if (header.AudioVersion == FrameHeader::AudioVersionID::INVALID) {
345365
LOGI("invalid mpeg version");
346366
return FrameReason::INVALID_MPEG_VERSION;
@@ -351,7 +371,7 @@ class MP3HeaderParser {
351371
return FrameReason::INVALID_LAYER;
352372
}
353373

354-
if (header.getBitrate() == FrameHeader::SpecialBitrate::INVALID) {
374+
if (header.getBitRate() == FrameHeader::SpecialBitrate::INVALID) {
355375
LOGI("invalid bitrate");
356376
return FrameReason::INVALID_BITRATE_FOR_VERSION;
357377
}
@@ -365,17 +385,17 @@ class MP3HeaderParser {
365385
// not allowed
366386
if (header.Layer == FrameHeader::LayerID::LAYER_2) {
367387
if (header.ChannelMode == FrameHeader::ChannelModeID::SINGLE) {
368-
if (header.getBitrate() >= 224000) {
388+
if (header.getBitRate() >= 224000) {
369389
LOGI("invalid bitrate >224000");
370390
return FrameReason::INVALID_LAYER_II_BITRATE_AND_MODE;
371391
}
372392
} else {
373-
if (header.getBitrate() >= 32000 && header.getBitrate() <= 56000) {
393+
if (header.getBitRate() >= 32000 && header.getBitRate() <= 56000) {
374394
LOGI("invalid bitrate >32000");
375395
return FrameReason::INVALID_LAYER_II_BITRATE_AND_MODE;
376396
}
377397

378-
if (header.getBitrate() == 80000) {
398+
if (header.getBitRate() == 80000) {
379399
LOGI("invalid bitrate >80000");
380400
return FrameReason::INVALID_LAYER_II_BITRATE_AND_MODE;
381401
}

src/AudioTools/CoreAudio/AudioBasic/Collections/Allocator.h

+22
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,28 @@ class AllocatorExt : public Allocator {
106106
}
107107
};
108108

109+
/**
110+
* @brief Memory allocateator which forces the allocation in RAM.
111+
* @ingroup memorymgmt
112+
* @author Phil Schatzmann
113+
* @copyright GPLv3
114+
*/
115+
116+
class AllocatorIRAM : public Allocator {
117+
void* do_allocate(size_t size) {
118+
void* result = nullptr;
119+
if (size == 0) size = 1;
120+
if (result == nullptr) result = heap_caps_calloc(1, size, MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
121+
if (result == nullptr) {
122+
LOGE("IRAM alloc failed for %zu bytes", size);
123+
stop();
124+
}
125+
// initialize object
126+
memset(result, 0, size);
127+
return result;
128+
}
129+
};
130+
109131
#if (defined(RP2040) || defined(ESP32)) && defined(ARDUINO)
110132

111133
/**

0 commit comments

Comments
 (0)