272 lines
9.2 KiB
C
272 lines
9.2 KiB
C
#include "audio_mp3.h"
|
|
#include "debug.h"
|
|
#include "delay.h"
|
|
#include "bsp_led.h"
|
|
#include "ff.h"
|
|
#include "tlsf.h"
|
|
|
|
#define MINIMP3_ONLY_MP3
|
|
#define MINIMP3_IMPLEMENTATION //释放minimp3中的函数
|
|
#include "minimp3.h"
|
|
|
|
/**
|
|
* ID3v2 标签头
|
|
*/
|
|
typedef struct {
|
|
char id[3]; //若为有效ID3v2标签 ID应为"ID3"
|
|
uint8_t mversion; //主版本号
|
|
uint8_t sversion; //子版本号
|
|
uint8_t flags; //标签头标志
|
|
uint8_t size[4]; //标签信息大小 采样开始位置为ID3v2标签大小+10字节标签头大小
|
|
} audio_mp3_id3v2_head_t;
|
|
|
|
audio_hal_result_t audio_mp3_get_id3v2_size(FIL* file, uint32_t *id3v2_size)
|
|
{
|
|
if (id3v2_size == NULL) {
|
|
LOG_E("id3v2_size is null");
|
|
return audio_hal_invalid_argument;
|
|
}
|
|
|
|
FRESULT f_result = f_lseek(file, 0); //定位到文件开头
|
|
if (f_result != FR_OK) {
|
|
LOG_E("seeking file failed, error code=%d", f_result);
|
|
return audio_hal_error_read_file;
|
|
}
|
|
|
|
UINT br;
|
|
audio_mp3_id3v2_head_t id3v2_head;
|
|
f_result = f_read(file, &id3v2_head, sizeof(id3v2_head), &br); //读取ID3v2标签头
|
|
if (f_result != FR_OK) {
|
|
LOG_E("error while reading file, error code=%d", f_result);
|
|
return audio_hal_error_read_file;
|
|
}
|
|
|
|
if (strncmp("ID3", id3v2_head.id, 3) == 0) { //标签头包含字符"ID3"
|
|
*id3v2_size = ((uint32_t)(id3v2_head.size[0] & 0x7F) << 21) | ((uint32_t)(id3v2_head.size[1] & 0x7F) << 14) |
|
|
((uint32_t)(id3v2_head.size[2] & 0x7F) << 7) | (id3v2_head.size[3] & 0x7F); //得到ID3v2大小 4个字节是大端模式 只取低7位
|
|
} else {
|
|
*id3v2_size = 0; //不存在ID3v2
|
|
}
|
|
|
|
return audio_hal_ok;
|
|
}
|
|
|
|
audio_hal_result_t audio_mp3_play(const char *file_name, audio_hal_result_t (*playing_routine)(void))
|
|
{
|
|
audio_hal_result_t error_code = audio_hal_ok;
|
|
FIL *file = NULL;
|
|
mp3dec_t *mp3d = NULL;
|
|
|
|
uint8_t *file_buffer = tlsf_malloc(AUDIO_MP3_FILE_BUFFER_SIZE);
|
|
if (file_buffer == NULL) {
|
|
LOG_E("error while allocating memory for mp3 file buffer");
|
|
error_code = audio_hal_error_insufficient_memory;
|
|
goto error;
|
|
}
|
|
|
|
file = tlsf_malloc(sizeof(FIL));
|
|
if (file == NULL) {
|
|
LOG_E("error while allocating memory for mp3 file");
|
|
error_code = audio_hal_error_insufficient_memory;
|
|
goto error;
|
|
}
|
|
|
|
FRESULT f_result = f_open(file, file_name, FA_READ);
|
|
if (f_result == FR_OK) {
|
|
LOG_I("open %s successful", file_name);
|
|
} else {
|
|
LOG_E("open %s failed, error code=%d", file_name, f_result);
|
|
error_code = audio_hal_error_open_file;
|
|
goto error;
|
|
}
|
|
|
|
/* 创建解码器 */
|
|
mp3d = tlsf_malloc(sizeof(mp3dec_t));
|
|
if (mp3d == NULL) {
|
|
LOG_E("error while allocating memory for mp3 decoder");
|
|
error_code = audio_hal_error_insufficient_memory;
|
|
goto error;
|
|
}
|
|
|
|
/* 初始化解码器 */
|
|
mp3dec_init(mp3d);
|
|
|
|
uint32_t id3v2_offset;
|
|
audio_mp3_get_id3v2_size(file, &id3v2_offset);
|
|
LOG_D("skipping %d bytes of id3v2", id3v2_offset);
|
|
|
|
f_result = f_lseek(file, id3v2_offset); //跳过ID3v2 Tag
|
|
if (f_result != FR_OK) {
|
|
LOG_E("seeking file %s failed, error code=%d", file_name, f_result);
|
|
error_code = audio_hal_error_read_file;
|
|
goto error;
|
|
}
|
|
|
|
/* 填满文件缓冲区 */
|
|
UINT br;
|
|
int32_t file_buffer_available = 0; //剩余的缓冲区大小
|
|
f_result = f_read(file, file_buffer, AUDIO_MP3_FILE_BUFFER_SIZE, &br); //读满文件缓冲区
|
|
if (f_result != FR_OK) {
|
|
LOG_E("error while reading file, error code=%d", f_result);
|
|
error_code = audio_hal_error_read_file;
|
|
goto error;
|
|
}
|
|
file_buffer_available += br;
|
|
|
|
/* 尝试解码一帧MP3从而获取文件参数 */
|
|
mp3d_sample_t *sample_temp = tlsf_malloc(MINIMP3_MAX_SAMPLES_PER_FRAME * sizeof(mp3d_sample_t)); //申请临时缓冲区
|
|
if (sample_temp == NULL) {
|
|
LOG_E("error while allocating memory for temporary decoder buffer");
|
|
error_code = audio_hal_error_insufficient_memory;
|
|
goto error;
|
|
}
|
|
|
|
mp3dec_frame_info_t frame_info;
|
|
int decoded_samples = mp3dec_decode_frame(mp3d, file_buffer, file_buffer_available, sample_temp, &frame_info);
|
|
if (decoded_samples == 0) {
|
|
LOG_E("error while decoding mp3 frame");
|
|
error_code = audio_hal_error_unsupported_format;
|
|
tlsf_free(sample_temp);
|
|
goto error;
|
|
}
|
|
|
|
LOG_I("mpeg file layer: %d, bitrate: %dkbps", frame_info.layer, frame_info.bitrate_kbps);
|
|
|
|
tlsf_free(sample_temp); //释放临时缓冲区 准备正式解码
|
|
|
|
#if AUDIO_MP3_DECODE_TIME_DEBUG
|
|
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
|
|
|
|
TIM_TimeBaseInitTypeDef TimeBase_InitStruct;
|
|
TIM_DeInit(TIM4); //首先反初始化定时器
|
|
TimeBase_InitStruct.TIM_Period = 65535; //根据采样率计算定时器溢出时间
|
|
TimeBase_InitStruct.TIM_Prescaler = SystemCoreClock / 10000 - 1;
|
|
TimeBase_InitStruct.TIM_ClockDivision = 0;
|
|
TimeBase_InitStruct.TIM_CounterMode = TIM_CounterMode_Up;
|
|
TimeBase_InitStruct.TIM_RepetitionCounter = 0x0000;
|
|
TIM_TimeBaseInit(TIM4, &TimeBase_InitStruct);
|
|
#endif
|
|
|
|
/* 初始化Audio HAL */
|
|
error_code = audio_hal_start(decoded_samples, sizeof(mp3d_sample_t) * 8, frame_info.hz, frame_info.channels);
|
|
if (error_code != audio_hal_ok) {
|
|
LOG_E("error while starting audio hal, error code=%d", error_code);
|
|
goto error;
|
|
}
|
|
|
|
uint8_t paused = 0;
|
|
while(1) {
|
|
/* 执行播放中用户事件处理代码 */
|
|
if (playing_routine != NULL) {
|
|
error_code = playing_routine();
|
|
if (error_code == audio_hal_result_pause) {
|
|
paused = !paused;
|
|
if (paused != 0) {
|
|
audio_hal_pause();
|
|
LOG_D("play paused");
|
|
} else {
|
|
audio_hal_resume();
|
|
LOG_D("play resumed");
|
|
}
|
|
} else if (error_code != audio_hal_ok) {
|
|
LOG_W("user routine returned %d, exiting", error_code);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (paused) {
|
|
delay_ms(10);
|
|
continue;
|
|
}
|
|
|
|
#if AUDIO_MP3_DECODE_TIME_DEBUG
|
|
TIM_SetCounter(TIM4, 0);
|
|
TIM_Cmd(TIM4, ENABLE); //使能定时器2
|
|
#endif
|
|
|
|
/* 从文件中补充数据到缓冲区 */
|
|
if (file_buffer_available < AUDIO_MP3_FILE_BUFFER_SIZE / 2) { //文件缓冲区剩余不足一半
|
|
int32_t file_buffer_consumed = (AUDIO_MP3_FILE_BUFFER_SIZE - file_buffer_available);
|
|
|
|
f_result = f_read(file, file_buffer + file_buffer_available, file_buffer_consumed, &br); //从文件中读取新的内容
|
|
if (f_result != FR_OK) {
|
|
LOG_E("error while reading file, error code=%d", f_result);
|
|
error_code = audio_hal_error_read_file;
|
|
goto error;
|
|
}
|
|
file_buffer_available += br;
|
|
|
|
// uint32_t bytes_read = file_buffer_consumed + file_buffer_available - AUDIO_MP3_FILE_BUFFER_SIZE;
|
|
// if (bytes_read > 0) {
|
|
// LOG_D("reading %d bytes from file", (int)bytes_read);
|
|
// }
|
|
// bsp_led_toggle(bsp_led_green);
|
|
|
|
if (file_buffer_available <= 0) { //文件已经读取完毕
|
|
#if AUDIO_MP3_DECODE_TIME_DEBUG
|
|
printf("\r\n");
|
|
#endif
|
|
LOG_I("play done");
|
|
break; //结束播放
|
|
}
|
|
}
|
|
|
|
#if AUDIO_MP3_DECODE_TIME_DEBUG
|
|
uint16_t read_file_time = TIM_GetCounter(TIM4);
|
|
#endif
|
|
|
|
/* 等待播放完成并获取空闲缓冲区 */
|
|
mp3d_sample_t *sample = audio_hal_get_free_buffer();
|
|
|
|
#if AUDIO_MP3_DECODE_TIME_DEBUG
|
|
uint16_t wait_play_time = TIM_GetCounter(TIM4);
|
|
#endif
|
|
|
|
/* 解码新一帧MP3数据 */
|
|
mp3dec_decode_frame(mp3d, file_buffer, file_buffer_available, sample, &frame_info);
|
|
|
|
#if AUDIO_MP3_DECODE_TIME_DEBUG
|
|
uint16_t decode_time = TIM_GetCounter(TIM4);
|
|
#endif
|
|
audio_hal_dac_postprocess(sample);
|
|
|
|
#if AUDIO_MP3_DECODE_TIME_DEBUG
|
|
uint16_t process_time = TIM_GetCounter(TIM4);
|
|
#endif
|
|
|
|
/* 从缓冲区中删除解码完毕的帧 */
|
|
file_buffer_available -= frame_info.frame_bytes; //从剩余缓冲区大小中减去被消耗的缓冲区大小
|
|
memmove(file_buffer, file_buffer + frame_info.frame_bytes, file_buffer_available); //删除已消耗的缓冲区内容
|
|
|
|
#if AUDIO_MP3_DECODE_TIME_DEBUG
|
|
uint16_t file_delete_time = TIM_GetCounter(TIM4);
|
|
TIM_Cmd(TIM4, DISABLE);
|
|
printf("read: %2d.%dms, wait: %2d.%dms, decode: %2d.%dms, process: %2d.%dms, remove: %2d.%dms\r",
|
|
read_file_time / 10, read_file_time % 10,
|
|
(wait_play_time - read_file_time) / 10, (wait_play_time - read_file_time) % 10,
|
|
(decode_time - wait_play_time) / 10, (decode_time - wait_play_time) % 10,
|
|
(process_time - decode_time) / 10, (process_time - decode_time) % 10,
|
|
(file_delete_time - process_time) / 10, (file_delete_time - process_time) % 10
|
|
);
|
|
#endif
|
|
}
|
|
|
|
error:
|
|
audio_hal_stop();
|
|
|
|
if (file) {
|
|
f_close(file);
|
|
tlsf_free(file);
|
|
}
|
|
|
|
if (file_buffer) {
|
|
tlsf_free(file_buffer);
|
|
}
|
|
|
|
if (mp3d) {
|
|
tlsf_free(mp3d);
|
|
}
|
|
|
|
return error_code;
|
|
}
|