编辑语:
技术解码栏目:是面向开发者详细解读芯片开放社区(OCC)上关于处理器、芯片、基础软件平台、集成开发环境及应用开发平台的相关技术,方便开发者学习及快速上手,提升开发效率。
一. 前言
RVB2601是基于平头哥RISC-V生态芯片的开发套件,开发者基于RVB2601可进行端云一体的物联网应用开发及音频方案开发。本周将为大家介绍基于RVB2601套件的应用开发实战:网络播放器设计(二),也是RVB2601应用开发实战系列的最后一篇。后续我们将为大家推荐YoC基础软件平台系列内容。
本例程基于YoC基础软件平台av组件采用http协议播放一首网络mp3歌曲。当开发板成功通过sal(底层通过at指令连接内置的网卡芯片)连接网络后,可输入相应串口命令行从web服务器上拉取mp3歌曲实现边拉取音频源数据边播放的功能。开发者可基于该例程实现更为丰富的网络播放功能。建议在在看本文之前,先详细看下新手必看 | RVB2601开发板快速上手指南。本例程名为ch2601_webplayer_demo,可以通过剑池CDK直接下载。
二. 如何使用
2.1 下载代码并编译运行
- 通过cdk搜索
ch2601_webplayer_demo
并下载工程代码打开后,会有如下界面。其中框1为解决方案组件,框2中是该解决方案依赖的子功能组件。 - 在本例程中,主要依赖av(音视频软件框架)、pvmp3dec(mp3解码器)、drv_wifi_at_w800(wifi驱动)等组件。
- 在IDE上编译通过后,点击下载进行烧录。烧录成功后,复位运行。成功运行后,串口会有如何打印输出:
2.2 网络连接
通过ifconfig命令可配置需要连接的热点。具体命令为:
ifconfig ap wifi_ssid wifi_psk
热点配置成功后,会有下图如下打印:
2.3 命令行播放控制
可通过在串口下输入如下命令来控制歌曲的播放
1. # player help 2. player play welcom/url[http://] #播放内置开机音频或网络歌曲 3. player pause #暂停播放 4. player resume #恢复播放 5. player stop #停止播放 6. player help #播放器帮助命令 bash
播放http歌曲player play http://yocbook.oss-cn-hangzhou.aliyuncs.com/av_repo/alibaba.mp3
,示例如下:
1. player play http://yocbook.oss-cn-hangzhou.aliyuncs.com/av_repo/alibaba.mp3 2. 3. # [ 13.620]<E>w800_api domain to ip: 47.110.23.146 4. [ 13.630]<D>sals remote_port -- : 80 5. [ 13.710]<D>WEB http request: 6. GET /av_repo/alibaba.mp3 HTTP/1.0 7. Host: yocbook.oss-cn-hangzhou.aliyuncs.com 8. User-Agent: CSKY/YOC 9. 10. [ 15.000]<D>stream upto cache threshold2, pos = 553, cache_pos = 809, diff = 256 11. [ 15.420]<D>avparser find a parser, name = mp3, id = 1 12. [ 15.440]<D>ad find a decode, name = pvmp3dec, id = 1 13. [ 15.450]<D>filter_swr open a avfilter, name = swr 14. [ 15.470]<D>filter_vol open a avfilter, name = vol 15. [ 15.470]<D>ao_alsa ao open 16. [ 15.490]<D>ao ao ref: openref = 1, startref = 0, fun = __ao_open 17. [ 15.510]<D>ao ori sf ==> sf = 90317074, rate = 44100, ch = 2, bits = 16, siged = 1, float = 0, endian = 0 18. [ 15.540]<D>ao ao sf ==> sf = 90316946, rate = 44100, ch = 1, bits = 16, siged = 1, float = 0, endian = 0 19. [ 15.810]<D>ao ao ref: openref = 1, startref = 1, fun = __ao_start 20. [ 15.820]<D>player_demo =====_player_event, 24, type = 2 21. [ 15.820]<D>player player_get_media_info, 809 enter. player = 20009E00 22. [ 15.830]<D>player player_get_media_info, 821 leave. player = 20009E00 23. [ 15.830]<D>player_demo =====rc = 0, duration = 415807ms, bps = 64000, size = 3326462 bash
三. 例程开发
3.1 主要代码解析
3.1.1 主函数流程
主函数位于ch2601_webplayer_demo/app/src/app_main.c中。详细的解释如下:
1. static void network_event(uint32_t event_id, const void *param, void *context) 2. { 3. switch(event_id) { 4. case EVENT_NETMGR_GOT_IP: 5. LOGD(TAG, "net got ip"); 6. break; 7. case EVENT_NETMGR_NET_DISCON: 8. LOGD(TAG, "net disconnect"); 9. break; 10. } 11. 12. /*do exception process */ 13. app_exception_event(event_id); 14. } 15. 16. int main(void) 17. { 18. board_yoc_init(); // 板级配置、kv文件系统、声卡驱动、网卡驱动等初始化 19. 20. player_init(); // 播放器模块初始化 21. 22. cli_reg_cmd_player(); // 播放器命令行注册 23. 24. /* Subscribe */ 25. event_subscribe(EVENT_NETMGR_GOT_IP, network_event, NULL); // 订阅网络连接事件 26. event_subscribe(EVENT_NETMGR_NET_DISCON, network_event, NULL); // 订阅网络断开事件 27. } c
3.1.2 声卡、网卡驱动注册等
代码位于ch2601_webplayer_demo/app/src/init.c中。
1. static void network_init() 2. { 3. w800_wifi_param_t w800_param; 4. /* init wifi driver and network */ 5. w800_param.reset_pin = PA21; 6. w800_param.baud = 1*1000000; 7. w800_param.cs_pin = PA15; 8. w800_param.wakeup_pin = PA25; 9. w800_param.int_pin = PA22; 10. w800_param.channel_id = 0; 11. w800_param.buffer_size = 4*1024; 12. 13. wifi_w800_register(NULL, &w800_param); 14. app_netmgr_hdl = netmgr_dev_wifi_init(); 15. 16. if (app_netmgr_hdl) { 17. utask_t *task = utask_new("netmgr", 2 * 1024, QUEUE_MSG_COUNT, AOS_DEFAULT_APP_PRI); 18. netmgr_service_init(task); 19. netmgr_start(app_netmgr_hdl); 20. } 21. } 22. 23. void board_yoc_init(void) 24. { 25. board_init(); // 板级初始化 26. event_service_init(NULL); // 发布订阅服务初始化 27. console_init(CONSOLE_UART_IDX, 115200, 512); // 串口初始化 28. ulog_init(); // 日志初始化 29. aos_set_log_level(AOS_LL_DEBUG); // 配置默认日志打印级别 30. 31. int ret = partition_init(); // 分区初始化 32. if (ret <= 0) { 33. LOGE(TAG, "partition init failed"); 34. } else { 35. LOGI(TAG, "find %d partitions", ret); 36. } 37. 38. aos_kv_init("kv"); // kv文件系统初始化,可用于保存网络ssid&psk 39. snd_card_alkaid_register(NULL); // 声卡初始化,可用于播放&采集 40. network_init(); // 网络初始化 41. 42. board_cli_init(); // 命令行初始化并注册默认的命令 43. } c
3.1.3 网络底层通信
CH2601主芯片是通过spi与无线网卡芯片w800通信的。w800中运行有完整的lwip网络协议栈。drv_wifi_at_w800组件将底层spi收到的网络数据(采用at协议封装)处理后递交到sal(socket abstract layer)组件中。2601通过sal来屏蔽底层网卡驱动的差异,向上提供标准的BSD网络套接字接口。此部分代码位于components/drv_wifi_at_w800/w800_at_port.c中。
1. static int spi_resp_len(void) 2. { 3. uint16_t temp = 0; 4. uint8_t a,b; 5. uint8_t cmd = SPI_REG_INT_STTS; 6. int recv_len = 0; 7. 8. while (1) { 9. CS_LOW; 10. csi_spi_send(&spi_handle, &cmd, 1, AOS_WAIT_FOREVER); // 检查是否存在有效数据 11. csi_spi_receive(&spi_handle, &a, 1, AOS_WAIT_FOREVER); 12. csi_spi_receive(&spi_handle, &b, 1, AOS_WAIT_FOREVER); 13. CS_HIGH; 14. 15. temp = a | (b << 8); 16. if((temp != 0xffff) && (temp & 0x01)) { 17. cmd = SPI_REG_RX_DAT_LEN; 18. CS_LOW; 19. csi_spi_send(&spi_handle, &cmd, 1, AOS_WAIT_FOREVER); // 获取接收数据长度 20. csi_spi_receive(&spi_handle, &a, 1, AOS_WAIT_FOREVER); 21. csi_spi_receive(&spi_handle, &b, 1, AOS_WAIT_FOREVER); 22. CS_HIGH; 23. recv_len = a | (b << 8); 24. 25. // printf("recv len:%d\r\n", recv_len); 26. break; 27. } 28. aos_msleep(100); 29. } 30. 31. return recv_len; 32. } 33. 34. static void at_spi_recv_task(void *priv) 35. { 36. int len = 0; 37. uint8_t *recv = NULL; 38. 39. while(1) { 40. aos_sem_wait(&spi_recv_sem, AOS_WAIT_FOREVER); // 是否有中断过来,通过GIIO来触发中断 41. 42. len = spi_resp_len(); // 获取对端发送过来的数据长度 43. if (len) 44. recv = aos_malloc_check(len); 45. else 46. continue; 47. 48. spi_recv(recv, len); // 获取实际有效数据 49. 50. while (ringbuffer_available_write_space(&spi_ringbuffer) < (len -1)) { 51. aos_msleep(100); 52. } 53. 54. int w_len = ringbuffer_write(&spi_ringbuffer, recv, len-1); // 写入到环形缓冲中 55. if (w_len != (len-1)) { 56. LOGD(TAG, "spi buffer is full\r\n"); 57. } else { 58. spi_channel_cb(AT_CHANNEL_EVENT_READ, spi_channel_priv); 59. } 60. 61. if (recv) { 62. aos_free(recv); 63. recv = NULL; 64. } 65. } 66. } 67. 68. static void *at_spi_init(const char *name, void *config) 69. { 70. int ret = 0; 71. 72. csi_pin_set_mux(PA16, PA16_SPI0_SCK); // 配置管脚复用 73. csi_pin_set_mux(PA17, PA17_SPI0_MOSI); 74. csi_pin_set_mux(PA18, PA18_SPI0_MISO); 75. // csi_pin_set_mux(PA15, PA15_SPI0_CS); // CS 76. csi_pin_set_mux(PA15, PIN_FUNC_GPIO); // CS 77. csi_pin_set_mux(PA22, PIN_FUNC_GPIO); // INT 78. 79. csi_gpio_pin_init(&spi_int_pin, PA22); // gpio配置 80. csi_gpio_pin_dir(&spi_int_pin,GPIO_DIRECTION_INPUT); 81. csi_gpio_pin_mode(&spi_int_pin,GPIO_MODE_PULLNONE); 82. csi_gpio_pin_debounce(&spi_int_pin, true); 83. csi_gpio_pin_attach_callback(&spi_int_pin, spi_in_int_cb, NULL); // 根据gpio来通知是否存在网络数据 84. csi_gpio_pin_irq_mode(&spi_int_pin,GPIO_IRQ_MODE_FALLING_EDGE); 85. csi_gpio_pin_irq_enable(&spi_int_pin, 1); 86. 87. csi_gpio_pin_init(&spi_cs_pin, PA15); 88. csi_gpio_pin_mode(&spi_cs_pin,GPIO_MODE_PULLUP); 89. csi_gpio_pin_dir(&spi_cs_pin,GPIO_DIRECTION_OUTPUT); 90. CS_HIGH; 91. 92. csi_gpio_pin_init(&spi_wakeup_pin, PA25); 93. csi_gpio_pin_mode(&spi_wakeup_pin,GPIO_MODE_PULLUP); 94. csi_gpio_pin_dir(&spi_wakeup_pin,GPIO_DIRECTION_OUTPUT); 95. csi_gpio_pin_write(&spi_wakeup_pin, GPIO_PIN_HIGH); 96. 97. ret = csi_spi_init(&spi_handle, 0); 98. if (ret < 0) { 99. printf("csi spi init failed\r\n"); 100. return NULL; 101. } 102. 103. csi_spi_mode(&spi_handle, SPI_MASTER); // 2601侧作为master 104. ret = csi_spi_baud(&spi_handle, 1*1000000); // 波特率配置默认1M 105. 106. LOGD(TAG, "#######################spi speed:%d\r\n", ret); 107. csi_spi_cp_format(&spi_handle, SPI_FORMAT_CPOL0_CPHA0); 108. csi_spi_frame_len(&spi_handle, SPI_FRAME_LEN_8); 109. csi_spi_select_slave(&spi_handle, 0); // 建立与w800间的spi通信,w800网卡作为slave 0 110. 111. aos_task_t task; 112. 113. ret = aos_sem_new(&spi_recv_sem, 0); // 用于gpio中断通知 114. // aos_check(ret, NULL); 115. 116. ret = aos_task_new_ext(&task, "spi_recv", at_spi_recv_task, NULL, 1536, 9); 117. // aos_check(ret, NULL); 118. 119. spi_recv_buffer = (char *)aos_malloc_check(SPI_RX_BUFFER_LEN); // 创建环形buffer,用于接收网络数据 120. 121. ringbuffer_create(&spi_ringbuffer, spi_recv_buffer, SPI_RX_BUFFER_LEN); 122. 123. return (void*)1; 124. } 125. 126. at_channel_t spi_channel = { 127. .init = at_spi_init, 128. .set_event = at_spi_set_event, 129. .send = at_spi_send, 130. .recv = at_spi_recv, 131. }; c
3.2 网络播放器使用及配置
YoC平台中的播放器可以支持wav、mp3、m4a、amrnb、amrwb、flac、adts等多种音频格式的播放。同时也支持sd卡、http(s)、fifo、mem等多种取流方式。url格式的详细定义如下:
流类型 | URL前缀 | URL格式 |
网络流 | http(s):// | http(s)://ip:port/xx.mp3 |
文件流(SD卡) | file:// | file:///fatfs0/xx.mp3?avformat=%s&avcodec=%s&channel=%u&rate=%u |
内存流 | mem:// | mem://addr=%u&size=%u&avformat=%s&avcodec=%s&channel=%u&rate=%u |
fifo流 | fifo:// | fifo://tts/1?avformat=%s&avcodec=%s&channel=%u&rate=%u |
加密流 | crypto:// | crypto://http://ip:port/xx.mp3?key=%s&iv=%s |
hls流 | http(s):// | http(s)://ip:port/xx.m3u8 |
播放器相关组件详细的设计和使用方法请访问以下链接:https://yoc.docs.t-head.cn/yocbook/Chapter5-%E7%BB%84%E4%BB%B6/%E5%A4%9A%E5%AA%92%E4%BD%93%E6%92%AD%E6%94%BE%E5%99%A8/av.html
3.2.1 网络播放器在2601芯片上的应用
网络播放器典型代码解析如下:
1. static player_t *g_player; 2. 3. static void _player_event(player_t *player, uint8_t type, const void *data, uint32_t len) 4. { 5. int rc; 6. UNUSED(len); 7. UNUSED(data); 8. UNUSED(handle); 9. LOGD(TAG, "=====%s, %d, type = %d", __FUNCTION__, __LINE__, type); 10. 11. switch (type) { 12. case PLAYER_EVENT_ERROR: // 播放出错事件 13. rc = player_stop(player); 14. break; 15. 16. case PLAYER_EVENT_START: { // 开始播放事件 17. media_info_t minfo; 18. memset(&minfo, 0, sizeof(media_info_t)); 19. rc = player_get_media_info(player, &minfo); // 获取媒体时长、大小等信息 20. LOGD(TAG, "=====rc = %d, duration = %llums, bps = %llu, size = %u", rc, minfo.duration, minfo.bps, minfo.size); 21. break; 22. } 23. 24. case PLAYER_EVENT_FINISH: // 播放结束事件 25. player_stop(player); // 停止播放 26. break; 27. 28. default: 29. break; 30. } 31. } 32. 33. player_t *get_player_demo() 34. { 35. if (!g_player) { 36. ply_conf_t ply_cnf; 37. 38. player_conf_init(&ply_cnf); // 初始化播放器默认配置 39. ply_cnf.vol_en = 1; // 使能数字音量功能 40. ply_cnf.vol_index = 160; // 0~255 41. ply_cnf.event_cb = _player_event; // 播放事件回调函数 42. ply_cnf.period_num = 12; // 底层音频输出缓冲周期,用于控制音频输出缓冲大小 43. ply_cnf.cache_size = 32 * 1024; // 网络时的播放缓冲大小 44. 45. g_player = player_new(&ply_cnf); // 创建播放器 46. } 47. 48. return g_player; 49. } c
3.2.2 网络播放器相关宏配置
鉴于2601的硬件资源比较受限,而网络播放器又提供了很多的功能。所以不太可能将播放器提供的所有功能都能够包含进去。此时就需要开发根据具体产品需要开启或配置相关功能。例程中典型宏定义配置如下:
1. CONFIG_AEFXER_IPC=0 #音效处理,2601不涉及 2. CONFIG_AEFXER_SONA=0 #音效处理,2601不涉及 3. CONFIG_AO_MIXER_SUPPORT=0 #混音播放,默认关闭 4. CONFIG_ATEMPOER_IPC=0 #核间变速播放,2601不涉及 5. CONFIG_ATEMPOER_SONIC=1 #变速播放 6. CONFIG_AV_AO_CHANNEL_NUM=1 #单声道音频输出 7. CONFIG_AV_PROBE_SIZE_MAX=1024 #音频格式探测最大长度 8. CONFIG_AV_SAMPLE_NUM_PER_FRAME_MAX=80 #控制wav音频帧的最大采样数 9. CONFIG_AV_STREAM_INNER_BUF_SIZE=256 #stream内部buf大小,用于性能优化 10. CONFIG_DECODER_ADPCM_MS=0 #adpcm_ms解码 11. CONFIG_DECODER_ALAW=0 #alaw解码 12. CONFIG_DECODER_AMRNB=0 #amrnb解码 13. CONFIG_DECODER_AMRWB=0 #amrwb解码 14. CONFIG_DECODER_FLAC=0 #flac解码 15. CONFIG_DECODER_IPC=0 #核间解码,2601不涉及 16. CONFIG_DECODER_MULAW=0 #ulaw解码 17. CONFIG_DECODER_OPUS=0 #opus解码 18. CONFIG_DECODER_PCM=1 #pcm裸流解码 19. CONFIG_DECODER_PVMP3=1 #mp3解码 20. CONFIG_DECODER_SPEEX=0 #speex解码 21. CONFIG_DEMUXER_ADTS=0 #adts解复用 22. CONFIG_DEMUXER_AMR=0 #amr解复用 23. CONFIG_DEMUXER_ASF=0 #asf解复用 24. CONFIG_DEMUXER_FLAC=0 #flac解复用 25. CONFIG_DEMUXER_MP3=1 #mp3解复用 26. CONFIG_DEMUXER_MP4=0 #mp4解复用 27. CONFIG_DEMUXER_OGG=0 #ogg解复用 28. CONFIG_DEMUXER_RAWAUDIO=0 #rawaudio解复用 29. CONFIG_DEMUXER_TS=0 #ts解复用 30. CONFIG_DEMUXER_WAV=0 #wav解复用 31. CONFIG_EQXER_IPC=0 #量化器,2601不涉及 32. CONFIG_EQXER_SILAN=0 #量化器,2601不涉及 33. CONFIG_FFTXER_IPC=0 #fft变换,2601不涉及 34. CONFIG_FFTXER_SPEEX=0 #fft变换,2601不涉及 35. CONFIG_PLAYER_TASK_STACK_SIZE=2048 #播放器任务栈大小 36. CONFIG_RESAMPLER_IPC=0 #核间音频重采样 37. CONFIG_RESAMPLER_SPEEX=0 #speex重采样 38. CONFIG_STREAMER_CRYPTO=0 #加密流 39. CONFIG_STREAMER_FIFO=0 #队列流 40. CONFIG_STREAMER_FILE=0 #文件流 41. CONFIG_STREAMER_HLS=0 #http live stream 42. CONFIG_STREAMER_HTTP=1 #http网络流 43. CONFIG_STREAMER_MEM=1 #内存流 44. CONFIG_WEB_CACHE_TASK_STACK_SIZE=2048 #网络流缓冲任务栈大小 c
AV组件中宏配置的具体说明请参考此链接中的功能配置与裁剪小节。该链接中同时会介绍典型音频播放场景的相关配置。
3.2.3 在CDK中如何配置宏
- 在解决方案名称上右击,选择弹出框中第一项,如下图所示:
- 在弹出框中选中Compile选项卡,单击下图中的红色框可配置相关宏
- 在弹出框中,根据功能需要配置对应的宏,保存后重新编译
注意事项:
- Package中的子功能组件在Options选项中会有默认的配置项(如果存在)
- 解决方案在依赖子功能组件时,可通过Options选项自行重新配置相关的宏。其在编译时会覆盖子功能组件的默认配置
四. 参考资料
YoC软件平台:https://yoc.docs.t-head.cn/yocbook/
多媒体播放器组件:
SAL组件:
AT组件:
网络管理器组件: