Initial public release of the 2024A software.

This commit is contained in:
Joe Kearney 2025-01-25 14:04:42 -06:00
parent 7b9ad3edfd
commit 303e9e1dad
361 changed files with 60083 additions and 2 deletions

View file

@ -0,0 +1,169 @@
#include <string.h>
#include "audio_log.h"
#include "audio_mp3.h"
static const char *TAG = "mp3";
bool is_mp3(FILE *fp) {
bool is_mp3_file = false;
fseek(fp, 0, SEEK_SET);
// see https://en.wikipedia.org/wiki/List_of_file_signatures
uint8_t magic[3];
if(sizeof(magic) == fread(magic, 1, sizeof(magic), fp)) {
if((magic[0] == 0xFF) &&
(magic[1] == 0xFB))
{
is_mp3_file = true;
} else if((magic[0] == 0xFF) &&
(magic[1] == 0xF3))
{
is_mp3_file = true;
} else if((magic[0] == 0xFF) &&
(magic[1] == 0xF2))
{
is_mp3_file = true;
} else if((magic[0] == 0x49) &&
(magic[1] == 0x44) &&
(magic[2] == 0x33)) /* 'ID3' */
{
fseek(fp, 0, SEEK_SET);
/* Get ID3 head */
mp3_id3_header_v2_t tag;
if (sizeof(mp3_id3_header_v2_t) == fread(&tag, 1, sizeof(mp3_id3_header_v2_t), fp)) {
if (memcmp("ID3", (const void *) &tag, sizeof(tag.header)) == 0) {
is_mp3_file = true;
}
}
}
}
// seek back to the start of the file to avoid
// missing frames upon decode
fseek(fp, 0, SEEK_SET);
return is_mp3_file;
}
/**
* @return true if data remains, false on error or end of file
*/
DECODE_STATUS decode_mp3(HMP3Decoder mp3_decoder, FILE *fp, decode_data *pData, mp3_instance *pInstance) {
MP3FrameInfo frame_info;
size_t unread_bytes = pInstance->bytes_in_data_buf - (pInstance->read_ptr - pInstance->data_buf);
/* somewhat arbitrary trigger to refill buffer - should always be enough for a full frame */
if (unread_bytes < 1.25 * MAINBUF_SIZE && !pInstance->eof_reached) {
uint8_t *write_ptr = pInstance->data_buf + unread_bytes;
size_t free_space = pInstance->data_buf_size - unread_bytes;
/* move last, small chunk from end of buffer to start,
then fill with new data */
memmove(pInstance->data_buf, pInstance->read_ptr, unread_bytes);
size_t nRead = fread(write_ptr, 1, free_space, fp);
pInstance->bytes_in_data_buf = unread_bytes + nRead;
pInstance->read_ptr = pInstance->data_buf;
if ((nRead == 0) || feof(fp)) {
pInstance->eof_reached = true;
}
LOGI_2("pos %ld, nRead %d, eof %d", ftell(fp), nRead, pInstance->eof_reached);
unread_bytes = pInstance->bytes_in_data_buf;
}
LOGI_3("data_buf 0x%p, read 0x%p", pInstance->data_buf, pInstance->read_ptr);
if(unread_bytes == 0) {
LOGI_1("unread_bytes == 0, status done");
return DECODE_STATUS_DONE;
}
/* Find MP3 sync word from read buffer */
int offset = MP3FindSyncWord(pInstance->read_ptr, unread_bytes);
LOGI_2("unread %d, total %d, offset 0x%x(%d)",
unread_bytes, pInstance->bytes_in_data_buf, offset, offset);
if (offset >= 0) {
COMPILE_3(int starting_unread_bytes = unread_bytes);
uint8_t *read_ptr = pInstance->read_ptr + offset; /*!< Data start point */
unread_bytes -= offset;
LOGI_3("read 0x%p, unread %d", read_ptr, unread_bytes);
int mp3_dec_err = MP3Decode(mp3_decoder, &read_ptr, (int*)&unread_bytes, reinterpret_cast<int16_t *>(pData->samples),
0);
pInstance->read_ptr = read_ptr;
if(mp3_dec_err == ERR_MP3_NONE) {
/* Get MP3 frame info */
MP3GetLastFrameInfo(mp3_decoder, &frame_info);
pData->fmt.sample_rate = frame_info.samprate;
pData->fmt.bits_per_sample = frame_info.bitsPerSample;
pData->fmt.channels = frame_info.nChans;
pData->frame_count = (frame_info.outputSamps / frame_info.nChans);
LOGI_3("mp3: channels %d, sr %d, bps %d, frame_count %d, processed %d",
pData->fmt.channels,
pData->fmt.sample_rate,
pData->fmt.bits_per_sample,
frame_info.outputSamps,
starting_unread_bytes - unread_bytes);
} else {
if (pInstance->eof_reached) {
ESP_LOGE(TAG, "status error %d, but EOF", mp3_dec_err);
return DECODE_STATUS_DONE;
} else if (mp3_dec_err == ERR_MP3_MAINDATA_UNDERFLOW) {
// underflow indicates MP3Decode should be called again
LOGI_1("underflow read ptr is 0x%p", read_ptr);
return DECODE_STATUS_NO_DATA_CONTINUE;
} else {
// NOTE: some mp3 files result in misdetection of mp3 frame headers
// and during decode these misdetected frames cannot be
// decoded
//
// Rather than give up on the file by returning
// DECODE_STATUS_ERROR, we ask the caller
// to continue to call us, by returning DECODE_STATUS_NO_DATA_CONTINUE.
//
// The invalid frame data is skipped over as a search for the next frame
// on the subsequent call to this function will start searching
// AFTER the misdetected frmame header, dropping the invalid data.
//
// We may want to consider a more sophisticated approach here at a later time.
ESP_LOGE(TAG, "status error %d", mp3_dec_err);
return DECODE_STATUS_NO_DATA_CONTINUE;
}
}
} else {
// if we are dropping data there were no frames decoded
pData->frame_count = 0;
// drop an even count of words
size_t words_to_drop = unread_bytes / BYTES_IN_WORD;
size_t bytes_to_drop = words_to_drop * BYTES_IN_WORD;
// if the unread bytes is less than BYTES_IN_WORD, we should drop any unread bytes
// to avoid the situation where the file could have a few extra bytes at the end
// of the file that isn't at least BYTES_IN_WORD and decoding would get stuck
if(unread_bytes < BYTES_IN_WORD) {
bytes_to_drop = unread_bytes;
}
// shift the read_ptr to drop the bytes in the buffer
pInstance->read_ptr += bytes_to_drop;
/* Sync word not found in frame. Drop data that was read until a word boundary */
ESP_LOGE(TAG, "MP3 sync word not found, dropping %d bytes", bytes_to_drop);
}
return DECODE_STATUS_CONTINUE;
}