1923|3

15

帖子

0

TA的资源

一粒金砂(中级)

楼主
 

基于RVB2601的网络收音机设计 [复制链接]

 

基于RVB2601的网络收音机设计

一般意义上的收音机主要通过天线接收电磁波,然后对电磁波进行进一步的放大滤波混频中频解调得到声音信号。整个收音机的数据来源主要是空气中的电磁波,接收到电磁波信号的强弱直接决定了音质的好坏。

而网络收音机主要通过网络从特定的服务器中获取数据,然后对获取到的数据进行音频解码,播放。相比于普通收音机,具有音质好,易操作的特点。

图1 网络收音机一般原理

本设计主要基于平头哥的RVB2601开发板。利用开发板上现成的W800网络芯片,ES8156数模转换器设计并完成了一套具有网络收音机功能的作品。

图2 网络收音机基本框架

网络收音机最重要的地方是数据的来源作为网络收音机主流的数据来源是蜻蜓FM,但是在实际操作过程中发现蜻蜓FM的接口比较麻烦,最后选择网易云音乐的接口作为网络收音机的数据来源。

网易云音乐的接口地址为

https://neteasecloudmusicapi.vercel.app/#/?id=neteasecloudmusicapi

通过网易云官方所提供的API接口可以实现完整的网易云音乐功能,包括登录,歌曲查找,下载,VIP用户功能等。但是由于RVB2601的资源比较紧张,在实现对网易云数据库连接的同时还要进行mp3的解码,oled显示,按键操作等诸多功能,因此在本文的设计中主要完成根据音乐名称、歌手查找需要的音乐的功能。网易云音乐查找功能主要通过API接口里面搜索标签的get请求实现。

3 网易云音乐查找接口

通过接口的描述可以知道,如果我们要搜索刘德华的音乐,同时每次只返回3条结果(网易云接口默认每次返回的数据是30条,这样的数据量大概在400k,单片机的内存承受不了),另外RVB2601内部没有中文字库,因此所有的字符都需要使用英文或者拼音表示,而刘德华的拼音为liudehua”,最终生成得到的get请求对应的url

http://cloud-music.pl-fe.cn/search?limit=3&keywords=liudehua&offset=10

使用单片机通过对上面的url完成get请求后会得到一个json数据,下面是浏览器的测试结果。

4 通过接口返回音乐id和详情的json

在返回的json数据里面,json的头是result,每一首音乐后面都会有一个id,然后通过这个id就可以在网易云音乐的曲库中查找到一个音乐的mp3链接,而通过id查找音乐的url

http://music.163.com/api/song/enhance/player/url?id=554191989&ids=%5B554191989%5D&br=3200000

其中ids后面的%5B%5D是固定的,br也是固定的。这样在获取每一首音乐的时候就转化为通过search接口查找曲目的id,再通过上面的url结合id通过GET请求得到一个包含真实音乐链接的jsonjson内容如下

5 通过get请求包含真实音乐信息的json

然后这个json里面的url就是真实的音乐,再把这个url通过mp3播放即可,由于单片机的资源比较紧张,基本遵循能省则省的原则,通过跟踪例程的时候发现已经有一部分get请求的功能,最终经过综合考虑准备复用这部分的功能。

6 原始的get请求功能

通过对这个部分进行分析可以发现,原始的请求通过层层调用,最终在_read_rsp_hdr(,,)中完成数据的接收,由于_read_resp_hdr中只接受了响应头,因此没有办法接收到完整的json段,需要对其进行修改和改进。

首先由于响应的json数据比较大,返回3个搜索结果都需要将近3k的空间,本身给函数的堆空间又比较小,因此需要在.c中定义一个全局数组char dbuf[3*1024]用于缓存json数据。

7 定义的dbuf缓存结构体

对于dbuf缓存结构体,首先对其空间进行清零操作,然后再使用strcat拼接url链接,再调用wjson_get(wsession_t *, const char *, int , char *)函数,wsession_t *类型的指针通过调用wsession_create()为其分配内存,不过这里有一个疑问,CH2601应该没有内存管理单元,如果这样重复分配几次内存估计后面出现许多问题。

wjson_get(,,,)中再调用rc = resp_json_menu(session, 3000,dbuf);获取json,整个原理和前面的过程大同小异,resp_json_menu(,,,)再调用 rc = _read_resp_player_menu(session, dbuf, timeout_ms);然后在_read_resp_player_menu(,,)里面完成对json的接收。

与原始接收响应头有所区别的地方是,这里判断响应中有{就表示json数据开始了,尽管有许多问题和隐患,但是通过对多次网易云接口响应的数据来看,这样设计是可以的,同时也符合能省则省的原则。

8 具体的调用流程

这样收到json数据之后需要从里面解析出歌曲名称和id,最简单的思路是使用cjson来完成,实际发现一旦使用cjson整个程序的内存就爆掉了,本身资源就比较紧张,决定还是自己手动拆解json,从里面拿到曲目名称和id

通过分析可以发现,曲目的id和名称都是在[{"id"或者},{"id"后面,因此重点比对这段字符,具体的比对过程是通过一个指针ch,ch每次只移动1位,然后再一次抓取ch后面7位字符进行比对,如果发现是上面的两个固定字符串那么铁定就是歌曲的id和名称了。

9 json解析过程

这样拿到音乐的id之后,再通过向

http://music.163.com/api/song/enhance/player/url?id=id&ids=%5Bid%5D&br=3200000

请求就可以获取到音乐的播放链接,最后就完成了对网易云音乐的数据请求,网络收音机的核心部分基本上就完成了,整个过程的函数调用可以概括为

10 音乐播放过程的函数调用

完成数据请求之后还需要编写软件界面,系统自带有lvgl图形化界面,但是说实话,单色oledlvgl有点杀鸡焉用牛刀的样子,另外歌曲的播放,另外还需要使用按钮来切换显示等。这里的界面显示使用纯字符界面,采样点阵的思路显示字符。另外还需要制作几个界面用来切换和选择,音量调节等。

而对于oled界面只需要3页即可,第一页是开机显示界面。

图11 开机界面

在上电之后,系统停留在开机界面状态,然后按任意键进入系统,字符SA显示当前的播放状态,播放状态一共有5种,分别为

表1 音乐的播放状态显示

状态1

SA:finding!

正在解析json

状态2

SA:downloading!

正在缓存音乐

状态3

SA:playing!

播放中

状态4

SA:play stop!

播放停止

状态5

SA:play error!

播放错误

主播发界面是网络收音机的主要界面,oledtitle是当前播放曲目的名称,系统默认存档了几首音乐都会在title部分进行显示,如果按下More键,则会进入歌手选择状态。vol是播放的音量,开机默认音量为50%,可以通过单击按键1调整音量。双击按键2则进入歌手选择界面。

在歌手选择页面中按键2双击确认选择,系统则开始播放该歌手的曲目,按键1双击调整歌手播放清单。默认清单为第一页,调整清单范围为1~9页,可以自己修改。整个操作界面的流程如下

图12 播放界面操作流程

RVB2601按键功能主要通过向系统进行注册按键事件,当有按键调用的时候系统会回调button_event(int evt_id, void *priv)函数来完成对按键的处理。在注册按键的时候首先调用button_init();然后再调用button_add_gpio(1,PA11,LOW_LEVEL);添加PA11按键,同时配置按键的触发电平为低电平。同时再使用button_evt_t buttons[]来设置具体的按键参数,包括按键触发类型,按键的id和按键按下的时间等。

最后使用button_add_event(,,,)完成对按键事件的添加。

具体的代码如下

button_init();

button_add_gpio(0, PA11, LOW_LEVEL);//按键1

button_add_gpio(1, PA12, LOW_LEVEL);//按键2

button_evt_t buttons[] = {

{

.event_id = BUTTON_PRESS_DOWN,

.button_id = 0,

.press_time = 0,

},

};

 

button_add_event(0, buttons, sizeof(buttons)/sizeof(button_evt_t), button_event, "BUTTON1_PRESS_DOWN"); //用户事件号为0,事件为按键(0)按下

 

buttons[0].event_id = BUTTON_PRESS_DOUBLE;

buttons[0].button_id = 0;

button_add_event(1, buttons, sizeof(buttons)/sizeof(button_evt_t), button_event, "BUTTON_PRESS_DOUBLE1");//用户事件号为2,事件为按键(0)双击

 

//添加按键事件2

buttons[0].event_id = BUTTON_PRESS_DOWN;

buttons[0].press_time = 0;

buttons[0].button_id = 1,

button_add_event(2, buttons, sizeof(buttons)/sizeof(button_evt_t), button_event, "BUTTON2_PRESS_DOWN"); //用户事件号为3,事件为按键(1)按下

 

buttons[0].event_id = BUTTON_PRESS_DOUBLE;

buttons[0].button_id = 1,

button_add_event(3, buttons, sizeof(buttons)/sizeof(button_evt_t), button_event, "BUTTON_PRESS_DOUBLE2");//用户事件号为4,事件为按键(1)双击

 

为了能够更加直观的看到播放器播放音乐的状态,我们还需要添加一个RGBled灯用来做播放时候流水灯的时候,当有音乐播放的时候,RGB灯就开始闪烁,当没有音乐播放的时候,RGB灯就停止闪烁。

具体的实现方法是在player.c中添加一个led_refresh()的调用。

本文主要讲解了使用平头哥的RVB2601设计并制作一个网络收音机的过程,相比于一般的收音机,网络收音机主要从特定的网站获取数据解码并播放。对于json数据的处理,本设计并没有采用一般的cjson处理思路,而是采用更有针对性的截断法处理json数据。同时响应过来的数据有很大的一部分都没有使用,包括时长,名称,专辑等。但是经过这样处理之后,整个程序具有更小的体积,不会出现死机,堆栈溢出的情况。

演示视频:

WeChat_20220606155429

 

 

 

 

基于RVB2601的网络收音机设计.docx

1.55 MB, 下载次数: 5

最新回复

有意思,期待后续   详情 回复 发表于 2022-5-31 23:42
点赞 关注
 
 

回复
举报

6802

帖子

0

TA的资源

五彩晶圆(高级)

沙发
 

在player.c中添加一个led_refresh()的调用,主要是用来控制音乐播放的时RGB灯的开关与闪烁么

 
 
 

回复

15

帖子

0

TA的资源

一粒金砂(中级)

板凳
 

是的

 
 
 

回复

7608

帖子

2

TA的资源

五彩晶圆(高级)

4
 

有意思,期待后续

个人签名

默认摸鱼,再摸鱼。2022、9、28

 
 
 

回复
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/9 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表