sujingliang 发表于 2024-11-20 11:54

看到有蓝牙歌词播放器在卖,感觉很神奇,用ESP32实现一下

本帖最后由 sujingliang 于 2024-11-20 12:43 编辑

#申请原创#

偶然发现有一个产品,可以通过蓝牙连接手机,在播放音乐的同时,显示歌词。于是查找了一下资料发现有很多用ESP32实现的例子,但是无一例外都基于arduino,调用大神提供的库实现歌词获取和显示。
为了节约硬盘空间,前不久arduino已经被我从硬盘上删掉了,无法利用现成的了,只能用IDF实现一下。

一、蓝牙歌词显示原理
如果没接触过传统蓝牙,可能要实现在一个屏幕显示歌词,需要在手机播放歌曲的同时将歌词字幕通过蓝牙以字符串的形式发送给单片机,然后再由单片机对字符串解析查找字库字模,在LCD上显示。这里面涉及手机上的歌词字幕获取,发送字符串的手机蓝牙编程,感觉难度不小。
但实际上,蓝牙标准化组织早已考虑到大众对显示歌词的需求,在传统蓝牙中提供了相关协议A2DP和AVRCP。利用这两个协议可以实现音频和控制传输,实现蓝牙歌词显示。

A2DP(Advanced Audio Distribution Profile蓝牙音频传输模型协定)和AVRCP(Audio/Video Remote Control Profile音频/视频远程控制协议)是传统蓝牙的两种高层应用协议:
在市面上的应用产品中,支持A2DP的蓝牙产品通常也支持AVRCP。A2DP负责高质量音频数据的传输,而AVRCP则负责对这些音频数据进行远程控制。例如,在使用蓝牙耳机听音乐时,A2DP负责将音乐数据从手机传输到耳机,而AVRCP则允许用户通过耳机上的按钮来控制音乐的播放、暂停和切换等操作。
AVRCP也可以传输歌词、歌名、作者等信息。


而且手机上大部分音乐播放软件都已实现AVRCP,所以只需要在单片机端实现AVRCP协议就可以和手机对话,获取歌词。
二、为什么选择ESP32?
1、ESP32不是乐鑫最高性能的模块,和ESP32 S3相比真的很难用,但是ESP32支持传统蓝牙,而ESP32 S3只支持BLE。
2、ESP32有A2DP的例程可以拿来直接利用。

三、实现方案
为了达到目标,需要完成以下工作:
1、驱动一个屏幕,可以是LED、LCD,这个决定最终的显示效果。
2、需要一个GBK汉字字模库,用来获取显示汉字。
3、需要一个UTF转GBK的函数,因为AVRCP是UTF-8格式的,需要转换成GBK格式才能通过GBK汉字字模库获取字模。
4、读懂A2DP例程,加入适当的功能。

四、屏幕驱动
这部分略,根据自已手头的屏幕情况驱动就好。需要注意的是,ESP32引脚资源有限,并且34-39引脚只能做为输入,00、02、05、12、15不推荐使用,如果还要支持I2S外放音乐,就剩不了太多引脚,可能刚好够一个8位并口屏驱动。


五、GBK汉字字模库
1、汉字库
可以从网上找一个gb2312.bin的标准汉字库,也可以用一些字库工具生成一个,如高通字库。
2、自定义烧录分区表
为了将gb2312.bin烧录到ESP32中,需要为gb2312.bin分配一个分区,在烧录的时候和固件一起烧录到ESP32中
idf.py menuconfig
Partition Table选择"Custom partition table CSV"

设置Custom partition table CSV名称

partitions_singleapp_large.csv内容:

hzk是用于放汉字库的分区,0x50,0x32是自定义的类型和子类型用于和其他分区区别便于查找,300k为分区大小,汉字库200多k,大小足够了。
3、烧录命令和烧录地址
每次idf.py build编译后,都会给出烧录命令和烧录地址,如:
nvs,data,nvs,0x9000,24K,
phy_init,data,phy,0xf000,4K,
factory,app,factory,0x10000,3000K,
hzk,80,50,0x2fe000,300K,其中0x2fe000是烧录hzk的烧录地址

编译后给出的烧录命令
C:\Espressif\python_env\idf5.1_py3.11_env\Scripts\python.exe C:\Espressif\frameworks\esp-idf-v5.1.2\components\esptool_py\esptool\esptool.py -p (PORT) -b 460800 --before default_reset --after hard_reset --chip esp32write_flash --flash_mode dio --flash_size 4MB --flash_freq 40m 0x1000 build\bootloader\bootloader.bin 0x8000 build\partition_table\partition-table.bin 0x10000 build\a2dp-demo.bin需要修改下,把gb2312.bin也一起烧录,为了方便,可以将下面内容放到一个bat文件中。
C:\Espressif\python_env\idf5.1_py3.11_env\Scripts\python.exe C:\Espressif\frameworks\esp-idf-v5.1.2\components\esptool_py\esptool\esptool.py -p COM10 -b 460800 --before default_reset --after hard_reset --chip esp32write_flash --flash_mode dio --flash_size 4MB --flash_freq 40m 0x1000 build\bootloader\bootloader.bin 0x8000 build\partition_table\partition-table.bin 0x10000 build\a2dp-demo.bin 0x2fe000 gb2312_80.binCOM10酌情修改,gb2312_80.bin放到工程根目录。
4、读取汉字库并在LCD上显示
通过前面操作可以在每次烧录时将汉字库烧录到0x2fe000的起始位置。要读取它需要利用2个函数:
esp_partition_find_first:Find first partition based on one or more parameters。通过类型、子类型、标识获取分区句柄

esp_partition_read:Read data from the partition。从分区上读取指定字节数据实现函数:获取分区句柄:void getHzkPartition(void)
{
    hzkPartition= esp_partition_find_first(0x50,0x32,"hzk");
}获取字模数据void getMatrix(const unsigned short nmCode)
{

      uint32_t offset;
      unsigned char GBH,GBL;
      unsigned short nm=nmCode;

      GBH=nm>>8;
      GBL=nm;
      if(GBH>=0xb0)
      {
                offset=((GBH-0xa7)*94+GBL-0xa1)*32;
      }else
      {
                offset=((GBH-0xa1)*94+GBL-0xa1)*32;
      }
    esp_partition_read(hzkPartition,offset,MatrixBuff,32);
}通过GB2312汉字编码获取在字库中的偏移量,然后通过esp_partition_read读取字模数据。将字模在LCD上显示函数:void GUI_Write16CnCharMatrix(unsigned char x, unsigned char y, char *cn, unsigned short wordColor, unsigned short backColor)
{
    uint8_t i=0, j,mx,my,wordByte;
      uint16_t zm;
    uint16_t color;
    uint8_t wordNum;

    mx=x;
   my=y;
    while (*cn != '\0')
    {
      if(mx>170){
            mx=x;
            my+=16;
                }
      if(*cn<128){
            wordNum = *cn - 32;
            setXY(mx,my,mx+7, my+15);

            for (wordByte=0; wordByte<16; wordByte++)
            {
                color = Font_Data.dat;
                for (j=0; j<8; j++)
                {
                  if ((color&0x80) == 0x80)
                  {
                        setPixel(wordColor);
                  }
                  else
                  {
                        setPixel(backColor);
                  }
                  color <<= 1;
                }
            }
            cn++;
            mx +=8;
            }
      else
      {
            setXY(mx, my, mx+15, my+15);
            zm=*cn;
            zm<<=8;
            zm|=*(cn+1);

            getMatrix(zm);
            for(i=0; i<32; i++)
            {   //MSK的位数
                color=MatrixBuff;
                for(j=0;j<8;j++)
                {
                  if((color&0x80)==0x80)
                  {
                        setPixel(wordColor);
                  }
                  else
                  {
                        setPixel(backColor);
                  }
                  color<<=1;
                }//for(j=0;j<8;j++)结束
            }
            cn += 2;
            mx += 16;
      }
    }
}可以显示ASCII和汉字。六、UTF转GBK的函数UTF汉字不能直接转换为GBK,需要先将UTF转换为unicode,再将unicode通过查表法转换为GBK。这部分不用自己实现,网上有现成的。https://gitee.com/zhangkt1995/my-code/tree/master/utf8_gb2312_switch使用起来很方便
七、实现蓝牙歌词功能ESP IDF下有一个传统蓝牙接收设备(sink)例程:esp-idf-v5.1.2\examples\bluetooth\bluedroid\classic_bt\a2dp_sink可以选择这个做为模板新建一个工程。将前面四、五、六步的程序加到这个工程中。打开其中bt_app_av.c,在430行左右,修改

memset(gb2312Data,' ',sizeof(gb2312Data));
      size_t gb2312DataLen = utf8_to_gb2312((uint8_t *)rc->meta_rsp.attr_text, strlen((char*)rc->meta_rsp.attr_text), (uint8_t *)gb2312Data, sizeof(gb2312Data));
      if((rc->meta_rsp.attr_id==0x01)&&(gb2312DataLen>0)){
            gb2312Data=' ';
            gb2312Data='\0';
            printf("%s",gb2312Data);

            GUI_Write16CnCharMatrix(10,90,gb2312Data,VGA_WHITE,VGA_RED);
      }当收到ESP_AVRC_CT_METADATA_RSP_EVT消息后,将收到的AVRCP协议数据显示出来。为什么要在这里添加,如果想深入了解,还是需要参考AVRCP协议相关内容。rc->meta_rsp.attr_id==0x01,标识接收的是歌曲TITLE信息,同时歌词也复用该字段。
通过utf8_to_gb2312将收到的歌词由UTF-8转换为gb2312编码。
通过GUI_Write16CnCharMatrix将歌词显示在LCD上。


八、运行效果
手机打开蓝牙,连接ESP_SPEAKER
打开搜狗音乐,找一首歌播放

这时LCD上歌词会实时显示。







页: [1]
查看完整版本: 看到有蓝牙歌词播放器在卖,感觉很神奇,用ESP32实现一下