【RT500 系列】RT595 开发之 DSP MP3 音乐播放(二)

关键字 :NXPRT500DSPMP3

一、概述

       直接切入主题,这里直接以 RT500 SDK 包的例程进行调试讲解 MP3 音乐的播放过程,以及需要修改的地方。

       了解阅读这篇博文之前,需要大致阅读一下上一篇博文:【RT500 系列】RT595 开发之 DSP MP3 音乐播放(一)

       此篇博文是对上一篇博文的深入讲解,主要讲如何实现 DSP 自动循环播放 MP3 任何一首音乐。

二、调试 DSP播放音乐

       打开 MCUXpresso IDE,打开之前的 evkmimxrt595_dsp_xaf_demo_cm33 例程,编译,没有错误直接进行修改播放代码。

       1. 首先需要读取 EMMC 所存放的音乐文件列表,方便使用音乐列表的歌名进行自动播放,同时记录歌名和歌曲数量方便播放时调用。

           参考代码如下: 

/************************获取 MP3 音乐文件清单 *************************/
shell_status_t music_list(void)
{
const char *dot;
FILINFO fileInformation;
DIR directory;
FRESULT error;
music_info.music_count = 0;
music_info.warring_count = 0;
/*********************getting mp3 music list**************************************/
error = f_opendir(&directory, "3:/song_list");
if (error)
{
PRINTF("Failed to open root directory of MMC mp3_list\r\n");
return kStatus_SHELL_Error;

}
PRINTF("Available audio files: music song_list\r\n");
while (1)
{
error = f_readdir(&directory, &fileInformation);
/* When dir end or error detected, break out */
if ((error != FR_OK) || (fileInformation.fname[0U] == 0U))
{
break;
}
/* Skip root directory */
if (fileInformation.fname[0] == '.')
{
continue;
}
if (!(fileInformation.fattrib & AM_DIR))
{
/* Check file for supported audio extension */
dot = strrchr(fileInformation.fname, '.');
#if XA_MP3_DECODER == 1
if (dot && strncmp(dot + 1, "mp3", 3) == 0)
{
music_info.music_count++;
if(music_info.music_count < SONGS_NUMBER_MAX)
sprintf(music_info.music_name[music_info.music_count], "%s", fileInformation.fname);
sprintf(music_info.P[music_info.music_count], "%s", fileInformation.fname);
PRINTF("歌曲名是: %s\r\n", music_info.music_name[music_info.music_count]);
}
#endif
}
}
if (error == FR_OK)
{
f_closedir(&directory);
}
if (!music_info.music_count)
{
PRINTF("mp3 music none\r\n");
}
return kStatus_SHELL_Success;
}

           测试结果截图如下:

 

  1. 修改代码自动播放音乐,这里直接把 Shell command 的命令配置修改掉,替换如下图的代码。

         

          接着修改播放的逻辑,直接通过之前的音乐清单进行播放的操作,同时设置二值信号量,等待歌曲播放完毕,在 case: File end 对二值信号量赋值,然后通过播放的逻辑播放下一首。

          播放参考代码如下:

/**********************播放音乐 序号按照 emmc 音乐列表序号*************************/
shell_status_t play_music(uint8_t music_num)//music_number from 1 to 20
{
char pathstr[80];
app_handle_t *app = (app_handle_t *)g_handleShellMessageCallbackData;
srtm_message msg = {0};
char file_size[10];
uint32_t total_size = 0;
const char *filename, *dot;
char *file_ptr;
uint32_t count = 0;
FRESULT error;
UINT bytes_read;
music_info.music_number = music_num;//将当前歌曲列表的歌曲第几首 赋值给 播放的信息数据,记录当前播放哪一首
//music_info.file_playing = false;
music_info.nosupport_format = false;
music_info.repeat_format = 0;
initMessage(&msg);
msg.head.category = SRTM_MessageCategory_AUDIO;
if (music_info.file_playing)
{
stop_music();
vTaskDelay(240);
}
if (!music_info.file_playing)
{
filename = music_info.music_name[music_num];//argv[1];
PRINTF("music is %s\r\n", music_info.music_name[music_info.music_number]);
file_ptr = (char *)AUDIO_SHARED_BUFFER_1;
msg.head.command = SRTM_Command_FileStart;
/* Param 0 Encoded input buffer address*/
/* Param 1 Encoded input buffer size*/
/* Param 2 EOF (true/false) */
/* Param 3 Audio codec component type */
msg.param[0] = (uint32_t)file_ptr;
msg.param[1] = FILE_PLAYBACK_INITIAL_READ_SIZE;
msg.param[2] = 0;
dot = strrchr(filename, '.');
#if XA_MP3_DECODER == 1
if (dot && strncmp(dot + 1, "mp3", 3) == 0)
{
msg.param[3] = DSP_COMPONENT_MP3;
count = 1
}
#endif
if (!count)
{
PRINTF("Unsupported file type %s\r\n", filename);
return kStatus_SHELL_Error;

}
sprintf(pathstr, "3:/song_list/%s", filename);
error = f_open(&app->fileObject, pathstr, FA_READ);
if (error)
{
PRINTF("Cannot open file for reading: %s\r\n", filename);
return kStatus_SHELL_Error;
}
error = f_read(&app->fileObject, file_ptr, FILE_PLAYBACK_INITIAL_READ_SIZE, &bytes_read);
if (error)
{
PRINTF("file read fail\r\n");

return kStatus_SHELL_Error;
}
/* Set EOF if file smaller than initial read block size */
if (bytes_read < FILE_PLAYBACK_INITIAL_READ_SIZE)
{
msg.param[2] = 1;
}
music_info.file_playing = true;
g_handleShellMessageCallback(&msg, g_handleShellMessageCallbackData);//handleShellMessage
}
return kStatus_SHELL_Success;

}

 

          自动循环播放代码参考如下:
         

void APP_Shell_Task(void *param)
{
uint8_t music_number = 1;
PRINTF("[APP_Shell_Task] start\r\n");
/* Handle shell commands. Return when 'exit' command entered. */
shellCmd(handleShellMessage, param);
while (1)
{
if( xSemaphoreTake(BinSem_next_song, portMAX_DELAY ) == pdTRUE )
{
play_music(music_number);
PRINTF("Play %d song\r\n",music_number);
music_number++;
if(music_number > music_info.music_count)
{
music_number = 1;
}
}
}
}

            测试的结果:(此处无图)

           就是耳机可以听到音乐,循环播放。

 

  1.  这里要特别说明的一点就是,对于任何一首 MP3 音乐,这里可能读取时,DSP 会出现无法播放的问题,原因就是 MP3 音乐文件包含了作者,作曲,专辑等信息,长度不固定,扩展了ID3V1的信息量,因为MP3 文件大体分为三部分:TAG_V2(ID3V2), Frame, TAG_V1(ID3V1) ,所以之前说过可以人为去掉图片信息占用的文件部分,这里我们同样可以使用软件的两个方法去实现正常的播放。

          第一种方法,通过 DSP 播放音乐的逻辑和反馈的信息,可以直接重复读取 MP3 文件信息,直至读到 MP3 Frame 部分,DSP 返回正常播放的标志,然后退出这个循环重复的代码。

          第二种方法,通过了解 MP3 音乐的文件格式信息,直接读取 Frame 部分的代码。

          这里通过比较,第一种方法也是算比较简单的,但是例程 DSP 播放 MP3 音乐文件有要求,第一次读到的 Frame 部分的数据大小至少需要 16K , 如果使用这种方法会有风险,就是会存在 16 K 数据只有少部分是 Frame 部分,其他大部分是作者的其他信息,  会导致 DSP 返回解码成功的标志,但是无法继续往下播放,而第二种方法不存在这种风险。

 

  1.  这里详细说明 第二种方法播放音乐文件的操作,首先需要了解 TAG_V2(ID3V2) 所占的空间大小,MP3 文件的第 7 ~ 10 记录了这部分的大小,按照 MP3 文件格式的要求,这四个字节最高位不用,所以读取到的字节需要去掉最高位,然后拼接成 28 bit 的数据,代表了 TAG_V2(ID3V2) 的大小,因此按照这个格式要求,读取 MP3 文件信息给到 DSP 做解码,直接可以在 Frame 开始的部分进行读取。

          DSP 播放任何一首 MP3 音乐的参考代码: 

/**********************播放音乐 序号按照 emmc 音乐列表序号*************************/
shell_status_t play_music(uint8_t music_num)//music_number from 1 to 20
{
char pathstr[80];
app_handle_t *app = (app_handle_t *)g_handleShellMessageCallbackData;
srtm_message msg = {0};
char file_size[10];
uint32_t total_size = 0;
char *file_ptr;
const char *filename;
FRESULT error;
UINT bytes_read;
music_info.music_number = music_num;//将当前歌曲列表的歌曲第几首 赋值给 播放的信息数据,记录当前播放哪一首
initMessage(&msg);
msg.head.category = SRTM_MessageCategory_AUDIO;
if (music_info.file_playing)
{
stop_music();
vTaskDelay(240);
}
if (!music_info.file_playing)
{
filename = music_info.music_name[music_num];//argv[1];
PRINTF("music is %s\r\n", music_info.music_name[music_info.music_number]);
file_ptr = (char *)AUDIO_SHARED_BUFFER_1;
msg.head.command = SRTM_Command_FileStart;
/* Param 0 Encoded input buffer address*/
/* Param 1 Encoded input buffer size*/
/* Param 2 EOF (true/false) */
/* Param 3 Audio codec component type */
msg.param[0] = (uint32_t)file_ptr;
msg.param[1] = FILE_PLAYBACK_INITIAL_READ_SIZE;
msg.param[2] = 0;
msg.param[3] = DSP_COMPONENT_MP3;
sprintf(pathstr, "3:/song_list/%s", filename);
error = f_open(&app->fileObject, pathstr, FA_READ);
if (error)
{
PRINTF("Cannot open file for reading: %s\r\n", filename);
return kStatus_SHELL_Error;
}
error = f_read(&app->fileObject, file_size, 10, &bytes_read);
if (error)
{
PRINTF("file read fail\r\n");
return kStatus_SHELL_Error;
}

PRINTF("file_size: %d %d %d %d\r\n", file_size[6],file_size[7],file_size[8],file_size[9]);
total_size = ((file_size[6]&0x7F)<< 21)+((file_size[7]&0x7F)<<14)+((file_size[8]&0x7F)<<7)+(file_size[9]&0x7F);
PRINTF("total_size: %d\r\n", total_size);
if (f_lseek(&app->fileObject, total_size) != FR_OK)
{
PRINTF("lseek file failed.\r\n");
}
error = f_read(&app->fileObject, file_ptr, FILE_PLAYBACK_INITIAL_READ_SIZE, &bytes_read);
if (error)
{
PRINTF("file read fail\r\n");
return kStatus_SHELL_Error;
}
/* Set EOF if file smaller than initial read block size */
if (bytes_read < FILE_PLAYBACK_INITIAL_READ_SIZE)
{
msg.param[2] = 1;
}
music_info.file_playing = true;
g_handleShellMessageCallback(&msg, g_handleShellMessageCallbackData);//handleShellMessage
PRINTF("Play music:open file for reading and playing\r\n");
}
return kStatus_SHELL_Success;

}

          测试结果截图如下,可以正常播放 MP3 音乐

  1.  下一篇会拓展讲 MP3 音乐的控制,例如切歌,暂停,继续播放等,敬请期待

      三、参考
       1.  i.MX RT600 DSP Core 开发环境搭建
       2.  i.MX RT600 DSP Core 调试
       3. 【RT500 系列】RT595 开发之 DSP MP3 音乐播放(一)

★博文内容均由个人提供,与平台无关,如有违法或侵权,请与网站管理员联系。

★文明上网,请理性发言。内容一周内被举报5次,发文人进小黑屋喔~

评论