Play MP3 Music from SD Card
1. Introduction
This project is a demo for playing MP3 music files from an SD card. It reads MP3 music files from the SD card, decodes them, and plays them on the onboard speaker through the audio_play component.
1.1 Specifications
- Hardware configuration:
Core board, BK7258_QFN88_9X9_V3.2
Speaker daughter board, BK_Module_Speaker_V1.1
PSRAM 8M/16M
TF card
1.2 Path
Project path:
<bk_avdk source code path>/projects/media/audio_play_sdcard_mp3_music
2. Demonstration description
The MP3 playback is controlled by UART commands. The commands are as follows:
audio_play_sdcard_mp3_music start xxx.mp3
Start playback command, where
xxx.mp3is the music file name.
audio_play_sdcard_mp3_music stop
Stop playback command, used to actively stop playback during music playing.
Note
In the current implementation, when MP3 file data has been completely read and played, resources will be automatically released and playback will stop. If you need to stop playback earlier, you can send the audio_play_sdcard_mp3_music stop command.
3. Code explanation
3.1 Start playback
//Path : projects/media/audio_play_sdcard_mp3_music/main/mp3_play.c
//Loaction : CPU0
//MP3 decode handler, responsible for reading data from SD card and decoding to PCM data
static bk_err_t mp3_decode_handler(void)
{
bk_err_t ret = BK_OK;
FRESULT fr;
uint32 uiTemp = 0;
static bool empty_already_flag = false;
if (!mp3_play_info) {
return BK_FAIL;
}
//Check whether the file has been completely decoded
if (mp3_play_info->mp3_file_is_empty) {
if (empty_already_flag == false) {
empty_already_flag = true;
LOGW("==========================================================\n");
LOGW("%s playback is over\n", mp3_play_info->mp3_file_name);
LOGW("==========================================================\n");
}
return PLAY_FINISH;
}
empty_already_flag = false;
//Continue to read data from file based on remaining unused data from last decoding
if (mp3_play_info->bytesLeft < MAINBUF_SIZE) {
os_memmove(mp3_play_info->readBuf, mp3_play_info->g_readptr, mp3_play_info->bytesLeft);
fr = f_read(&mp3_play_info->mp3file,
(void *)(mp3_play_info->readBuf + mp3_play_info->bytesLeft),
MAINBUF_SIZE - mp3_play_info->bytesLeft,
&uiTemp);
if (fr != FR_OK) {
LOGE("read %s failed\n", mp3_play_info->mp3_file_name);
return fr;
}
if ((uiTemp == 0) && (mp3_play_info->bytesLeft == 0)) {
LOGI("uiTemp = 0 and bytesLeft = 0\n");
mp3_play_info->mp3_file_is_empty = true;
LOGI("the %s is empty\n", mp3_play_info->mp3_file_name);
return ret;
}
mp3_play_info->bytesLeft = mp3_play_info->bytesLeft + uiTemp;
mp3_play_info->g_readptr = mp3_play_info->readBuf;
}
//Find MP3 frame header and decode valid data frame by frame
int offset = MP3FindSyncWord(mp3_play_info->g_readptr, mp3_play_info->bytesLeft);
if (offset < 0) {
LOGE("MP3FindSyncWord not find\n");
mp3_play_info->bytesLeft = 0;
} else {
mp3_play_info->g_readptr += offset;
mp3_play_info->bytesLeft -= offset;
//Decode one frame MP3 data
ret = MP3Decode(mp3_play_info->hMP3Decoder,
&mp3_play_info->g_readptr,
&mp3_play_info->bytesLeft,
mp3_play_info->pcmBuf,
0);
if (ret != ERR_MP3_NONE) {
LOGE("MP3Decode failed, code is %d\n", ret);
return ret;
}
//Get frame info
MP3GetLastFrameInfo(mp3_play_info->hMP3Decoder, &mp3_play_info->mp3FrameInfo);
}
return ret;
}
//MP3 playback main task, responsible for initializing decoder and audio_play component, and loop playback
static void mp3_play_main(beken_thread_arg_t param_data)
{
bk_err_t ret = BK_OK;
uint32 uiTemp = 0;
char tag_header[10];
int tag_size = 0;
char *file_name = (char *)param_data;
if (!file_name) {
LOGE("file_name is NULL\n");
}
//Mount TF card
ret = tf_mount();
if (ret != BK_OK) {
LOGE("mount sdcard fail\n");
}
//Allocate decoder buffers and decoder handle
mp3_play_info = (mp3_play_info_t *)os_malloc(sizeof(mp3_play_info_t));
...
mp3_play_info->readBuf = os_malloc(MAINBUF_SIZE);
...
mp3_play_info->pcmBuf = os_malloc(PCM_SIZE_MAX * 2);
...
mp3_play_info->hMP3Decoder = MP3InitDecoder();
...
LOGI("audio mp3 play decode init complete\n");
//Open specified MP3 file and skip ID3 header
os_memset(mp3_play_info->mp3_file_name, 0, sizeof(mp3_play_info->mp3_file_name));
sprintf(mp3_play_info->mp3_file_name, "%d:/%s", DISK_NUMBER_SDIO_SD, file_name);
FRESULT fr = f_open(&mp3_play_info->mp3file, mp3_play_info->mp3_file_name, FA_OPEN_EXISTING | FA_READ);
...
fr = f_read(&mp3_play_info->mp3file, (void *)tag_header, 10, &uiTemp);
...
if (os_memcmp(tag_header, "ID3", 3) == 0) {
tag_size = ((tag_header[6] & 0x7F) << 21) | ((tag_header[7] & 0x7F) << 14) | ((tag_header[8] & 0x7F) << 7) | (tag_header[9] & 0x7F);
LOGI("tag_size = %d\n", tag_size);
f_lseek(&mp3_play_info->mp3file, tag_size + 10);
LOGI("tag_header has found\n");
} else {
LOGI("tag_header not found\n");
f_lseek(&mp3_play_info->mp3file, 0);
}
//Pre-decode one frame to get MP3 frame info (sample rate, channel, bit depth, etc.)
mp3_play_info->g_readptr = mp3_play_info->readBuf;
ret = mp3_decode_handler();
...
LOGI("Bitrate: %d kb/s, Samprate: %d\r\n", mp3_play_info->mp3FrameInfo.bitrate / 1000, mp3_play_info->mp3FrameInfo.samprate);
LOGI("Channel: %d, Version: %d, Layer: %d\r\n", mp3_play_info->mp3FrameInfo.nChans, mp3_play_info->mp3FrameInfo.version, mp3_play_info->mp3FrameInfo.layer);
LOGI("OutputSamps: %d\r\n", mp3_play_info->mp3FrameInfo.outputSamps);
//Configure audio_play component according to frame info and create playback instance
audio_play_cfg_t audio_play_config = DEFAULT_AUDIO_PLAY_CONFIG();
audio_play_config.port = 0;
audio_play_config.nChans = mp3_play_info->mp3FrameInfo.nChans;
audio_play_config.sampRate = mp3_play_info->mp3FrameInfo.samprate;
audio_play_config.bitsPerSample = mp3_play_info->mp3FrameInfo.bitsPerSample;
audio_play_config.volume = 0x2d;
audio_play_config.play_mode = AUDIO_PLAY_MODE_DIFFEN;
audio_play_config.frame_size = audio_play_config.sampRate * audio_play_config.bitsPerSample / 8 * 20 /1000; //20ms data
audio_play_config.pool_size = audio_play_config.frame_size * 4;
mp3_play_info->play = audio_play_create(AUDIO_PLAY_ONBOARD_SPEAKER, &audio_play_config);
...
ret = audio_play_open(mp3_play_info->play);
...
//Write one frame of pre-decoded PCM data first, then loop decode and write to audio_play for playback
ret = mp3_play_write_spk_data(mp3_play_info->play,
(char *)mp3_play_info->pcmBuf,
mp3_play_info->mp3FrameInfo.outputSamps * 2);
...
mp3_play_run = true;
while (mp3_play_run) {
ret = mp3_decode_handler();
...
ret = mp3_play_write_spk_data(mp3_play_info->play,
(char *)mp3_play_info->pcmBuf,
mp3_play_info->mp3FrameInfo.outputSamps * 2);
...
}
exit:
mp3_play_run = false;
mp3_play_stop();
...
}
//Start playback API, create MP3 playback task
bk_err_t audio_play_sdcard_mp3_music_start(char *file_name)
{
bk_err_t ret = BK_OK;
//Initialize semaphore and create playback task
ret = rtos_init_semaphore(&mp3_play_sem, 1);
...
ret = rtos_create_thread(&mp3_play_task_hdl,
(BEKEN_DEFAULT_WORKER_PRIORITY - 1),
"mp3_play",
(beken_thread_function_t)mp3_play_main,
2048,
(beken_thread_arg_t)file_name);
...
//Wait for playback task to complete initialization
rtos_get_semaphore(&mp3_play_sem, BEKEN_NEVER_TIMEOUT);
LOGI("init mp3 play task complete\n");
return BK_OK;
}
3.2 Stop playback
//Path : projects/media/audio_play_sdcard_mp3_music/main/mp3_play.c
//Loaction : CPU0
//Stop playback API, notify playback task to exit and wait for resources to be released
bk_err_t audio_play_sdcard_mp3_music_stop(void)
{
//If not playing, return directly
if (!mp3_play_run)
{
return BK_OK;
}
//Clear playback flag, let playback task exit in next loop
mp3_play_run = false;
//Wait for playback task to exit completely and release resources
rtos_get_semaphore(&mp3_play_sem, BEKEN_NEVER_TIMEOUT);
rtos_deinit_semaphore(&mp3_play_sem);
mp3_play_sem = NULL;
return BK_OK;
}