編輯語:
技術解碼欄目:是面向開發者詳細解讀晶片開放社群(OCC)上關于處理器、晶片、基礎軟體平台、內建開發環境及應用開發平台的相關技術,友善開發者學習及快速上手,提升開發效率。
一. 前言
RVB2601是基于平頭哥RISC-V生态晶片的開發套件,開發者基于RVB2601可進行端雲一體的物聯網應用開發及音頻方案開發。本周将為大家介紹基于RVB2601套件的應用開發實戰:網絡播放器設計(二),也是RVB2601應用開發實戰系列的最後一篇。後續我們将為大家推薦YoC基礎軟體平台系列内容。
本例程基于YoC基礎軟體平台av元件采用http協定播放一首網絡mp3歌曲。當開發闆成功通過sal(底層通過at指令連接配接内置的網卡晶片)連接配接網絡後,可輸入相應序列槽指令行從web伺服器上拉取mp3歌曲實作邊拉取音頻源資料邊播放的功能。開發者可基于該例程實作更為豐富的網絡播放功能。建議在在看本文之前,先詳細看下
新手必看 | RVB2601開發闆快速上手指南。本例程名為ch2601_webplayer_demo,可以通過劍池CDK直接下載下傳。
二. 如何使用
2.1 下載下傳代碼并編譯運作
- 通過cdk搜尋
并下載下傳工程代碼打開後,會有如下界面。其中框1為解決方案元件,框2中是該解決方案依賴的子功能元件。ch2601_webplayer_demo
- 在本例程中,主要依賴av(音視訊軟體架構)、pvmp3dec(mp3解碼器)、drv_wifi_at_w800(wifi驅動)等元件。
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5yMxMjMhljYzUGO4cTNhVTYhNTO0QDMzcTOhBDN0MWN58CX5d2bs92Yl1iclB3bsVmdlR2LcNWaw9CXt92Yu4GZjlGbh5yYjV3Lc9CX6MHc0RHaiojIsJye.png)
- 在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)://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.html3.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/多媒體播放器元件:
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/SAL元件:
https://yoc.docs.t-head.cn/yocbook/Chapter4-%E6%A0%B8%E5%BF%83%E6%A8%A1%E5%9D%97/%E7%BD%91%E7%BB%9C%E8%BF%9E%E6%8E%A5/%E5%A5%97%E6%8E%A5%E5%AD%97%E9%80%82%E9%85%8D%E5%B1%82SAL.htmlAT元件:
https://yoc.docs.t-head.cn/yocbook/Chapter4-%E6%A0%B8%E5%BF%83%E6%A8%A1%E5%9D%97/AT%E5%91%BD%E4%BB%A4/網絡管理器元件:
https://yoc.docs.t-head.cn/yocbook/Chapter4-%E6%A0%B8%E5%BF%83%E6%A8%A1%E5%9D%97/%E7%BD%91%E7%BB%9C%E8%BF%9E%E6%8E%A5/%E7%BD%91%E7%BB%9C%E7%AE%A1%E7%90%86%E5%99%A8.html