本文作者:我爱下载
1、概述
远程音频采集系统利用RVB2601的ES7210麦克风数字化采样,W800的WiFi通讯和OLED液晶显示相关信息,进行音频收集和存储。开发专用的上位软件,通过以太网通讯启动下位机完成一定数据量的音频收集后,传输到上位机,并存储为RAW文件。
系统中使用了麦克风音频采集ADC,W800无线以太网通讯,OLED液晶显示,指示灯等几个外设设备。
2、测试程序
通过概述的描述,利用之前的外设测试和学习经验,本次开发包括下位机软件,和上位机软件。
2.1 下位机软件
下位机软件主要包含wifi接口处理,音频采集、缓存管理和OLED三个主要部分。
1) 初始化
根据以太网通讯的研究,《以太网通讯测试》连接 http://bbs.eeworld.com.cn/thread-1174636-1-1.html 建立wifi连接,获取ip地址,这里为了后面的处理方便,路由器中设置了固定的ip地址和mac的绑定关系,保证ip地址的获取唯一。
根据音频测试的研究,《麦克风录音测试》 连接 http://bbs.eeworld.com.cn/thread-1175631-1-1.html 完成音频采样音频初始化,并进入等待状态。
为了更好的管理采样数据,建立一个RingBuffer缓冲队列。开辟一个48K的缓冲区,用来存储音频采样数据。
ringbuffer_t mic_ring_buffer; #define MIC_RECORDER_BUF_SIZE 49152 uint8_t repeater_data_addr[MIC_RECORDER_BUF_SIZE]; //设置录音缓冲区环形队列 mic_ring_buffer.buffer = repeater_data_addr; mic_ring_buffer.size = MIC_RECORDER_BUF_SIZE; ringbuffer_reset(&mic_ring_buffer);
创建一个画面,画面中包换本次试用程序的标题“THEAD RVB2601 RECORDER”和2个显示标签。显示标签中实时显示当前的运行状态。
static void gui_label_create(void) { lv_obj_t* scr = lv_scr_act();//获取当前活跃的屏幕对象 lv_obj_t *p = lv_label_create(scr, NULL); lv_label_set_long_mode(p, LV_LABEL_LONG_BREAK); lv_label_set_align(p, LV_LABEL_ALIGN_CENTER); lv_obj_set_pos(p, 0, 4); lv_obj_set_size(p, 128, 60); lv_label_set_text(p, "THEAD RVB2601\nRECORDER"); p = lv_label_create(scr, NULL); lv_label_set_align(p, LV_LABEL_ALIGN_LEFT); lv_obj_set_pos(p, 0, 40); lv_obj_set_size(p, 60, 20); lv_label_set_text(p, "STATUS:"); pLabel = lv_label_create(scr, NULL); lv_label_set_align(pLabel, LV_LABEL_ALIGN_CENTER); lv_obj_set_pos(pLabel, 65, 40); lv_obj_set_size(pLabel, 50, 20); lv_label_set_text(pLabel, "idel"); }
建立了以太网接收函数,音频采集任务,音频数据发送任务,图形显示这4个主要任务,并利用信号量完成任务间的协调工作。
- 音频采集任务
aos_task_new("mic_recorder", main_mic_task, NULL, 2 * 1024);
- 音频数据发送任务
aos_task_new("mic_recorder_send", wifi_send_task, NULL, 4 * 1024);
- 图形显示任务
aos_task_new("gui", gui_lvgl_task, NULL, 10 * 1024);
- 以太网接收函数通过串口终端启动。
int mic_tcpclient(void);
2) 以太网数据接收
根据前面的研究,RVB2601只能作为以太网的Client端,所以,这里必须以以太网的客户端方式建立连接,完成数据收发。
int mic_tcpclient(void) { struct sockaddr_in sAddr; int iAddrSize; int iStatus; char *cBsdBuf = NULL; // int time_ms = aos_now_ms(); // int time_ms_step = aos_now_ms(); running = 1; cBsdBuf = lan_buf; //filling the TCP server socket address FD_ZERO(&sAddr); sAddr.sin_family = AF_INET; sAddr.sin_port = htons(26666); sAddr.sin_addr.s_addr = inet_addr("192.168.1.3"); iAddrSize = sizeof(struct sockaddr_in); // creating a TCP socket iSockFD = socket(AF_INET, SOCK_STREAM, 0); if (iSockFD < 0) { LOGE(TAG, "TCP ERROR: create tcp client socket fd error!"); goto Exit1; } LOGD(TAG, "ServerIP=%s port=%d.", "192.168.1.3", 26666); LOGD(TAG, "Create socket %d.", iSockFD); // connecting to TCP server iStatus = connect(iSockFD, (struct sockaddr *)&sAddr, iAddrSize); if (iStatus < 0) { LOGE(TAG, "TCP ERROR: tcp client connect server error! "); goto Exit; } LOGD(TAG, "TCP: Connect server successfully."); record_sta = NET_CONNECT; // sending multiple packets to the TCP server while (running) { if ( (iStatus = read(iSockFD, cBsdBuf, 1024)) < 0) { break; } //解析数据 if(cBsdBuf[0] == 1) //起动录音 { LOGD(TAG, "get record start command!"); mic_recorder_start(); wifi_send_run_flag = 1; } // else if(cBsdBuf[0] == 2) //起动传输 // { // LOGD(TAG, "get stop command!"); // mic_recorder_stop(); // wifi_send_task_run_status = 0; // } else if(cBsdBuf[0] == 3) { wifi_send_run_flag = 0; LOGD(TAG, "quit command!"); break; } } LOGD(TAG, "RECORDER: Quit!"); record_sta = NET_DISCON; Exit: //closing the socket after sending 1000 packets close(iSockFD); Exit1: return 0; }
3)以太网数据发送
每帧发送1024字节数据,连续发送,直到所有数据发送完成为止。
void wifi_send_task(void *arg) { int iStatus; int len; LOGD(TAG, "MIC Recorder send thread begin!"); aos_sem_new(&mic_data_send_sem, 0); while (1) { aos_sem_wait(&mic_data_send_sem, AOS_WAIT_FOREVER); while(wifi_send_run_flag) { len = mic_recorder_getdata_len(); if(len > 0) { len = mic_recorder_getdata((uint8_t *)lan_buf,1024); if(len > 0) { record_sta = REC_SEND; // sending packet iStatus = send(iSockFD, lan_buf, len, 0); if (iStatus <= 0) { printf("TCP ERROR: tcp client send data error! iStatus:%d", iStatus); record_sta = NET_ERROR; } aos_msleep(100); } } else { wifi_send_run_flag = 0; } } record_sta = IDEL; } }
4)音频采样
音频启动中负责复位缓冲队列,然后启动音频采样。
void mic_recorder_start(void) { ringbuffer_reset(&mic_ring_buffer); csi_codec_input_start(&codec_input_ch); }
录取到一定数量音频后,产生触发事件,事件中利用信号量环形音频采样任务,将数据读出并存入缓冲队列。
static void codec_input_event_cb_fun(csi_codec_input_t *i2s, csi_codec_event_t event, void *arg) { if (event == CODEC_EVENT_PERIOD_READ_COMPLETE) { aos_sem_signal(&mic_input_sem); } }
通过音频采集任务,接收事件信号触发,完成数据读取
static void main_mic_task(void *arg) { LOGD(TAG, "MIC Recorder thread begin!"); aos_sem_new(&mic_input_sem, 0); //创建音频采样信号 mic_recorder_init(); //初始化音频采样 while (1) { aos_sem_wait(&mic_input_sem, AOS_WAIT_FOREVER); //等待一组音频数据采集完成 mic_recorder_to_buff(); //将采集到的音频数据压入缓冲队列中 } }
读取音频数据压入缓冲队列,判断采样结束,并触发音频数据发送
void mic_recorder_to_buff(void) { csi_codec_input_read_async(&codec_input_ch, mic_input_buf, 1024); ringbuffer_in(&mic_ring_buffer, mic_input_buf, 1024); uint32_t len = ringbuffer_len(&mic_ring_buffer); if(len >= MIC_RECORDER_BUF_SIZE) { csi_codec_input_stop(&codec_input_ch); aos_sem_signal(&mic_data_send_sem); } }
5)运行状态显示
首先创建了一个枚举类型,包换所有待显示的状态定义。
typedef enum _RECORDER_STA { IDEL = 0, //空闲状态 NET_CONNECT, //网络连接 NET_DISCON, //网络断开 NET_ERROR, //网络数据发送错误 REC_INIT, //录音初始化 REC_START, //录音启动 REC_RUNNING, //录音采样中 REC_SEND, //发送录音数据 }RECORDER_STA;
根据当前系统运行的状态,显示在OLED的画面上。
static void gui_lvgl_task(void *arg) { lv_init(); /*Initialize for LittlevGL*/ oled_init(); /*Select display 1*/ // demo_create(); gui_label_create(); while (1) { switch(record_sta) { case IDEL: //空闲状态 lv_label_set_text(pLabel, "idel"); break; case NET_CONNECT: //网络连接 lv_label_set_text(pLabel, "connect"); break; case NET_DISCON: //网络断开 lv_label_set_text(pLabel, "discon"); break; case NET_ERROR: //网络数据发送错误 lv_label_set_text(pLabel, "neterr"); break; case REC_INIT: //录音初始化 lv_label_set_text(pLabel, "init"); break; case REC_START: //录音启动 lv_label_set_text(pLabel, "start"); break; case REC_RUNNING: //录音采样中 lv_label_set_text(pLabel, "rec..."); break; case REC_SEND: //发送录音数据 lv_label_set_text(pLabel, "send"); break; default: break; }; /* Periodically call the lv_task handler. * It could be done in a timer interrupt or an OS task too.*/ lv_task_handler(); aos_msleep(5); lv_tick_inc(1); } }
2.2 上位机软件
软件是本次测试工程的上位机软件,软件功能包括网络通讯,声音采样控制,波形展示,录音数据存储,状态显示等几个部分。利用TCPServer在端口为26666建立一个监听,接收来自下位机的连接申请。通过上位机的采样启动按钮发送启动命令,下位机接收到启动命令后启动录音采样并存储,采样结束后,触发数据发送,上位机接收到数据后存入缓存中,同时刷新波形显示。点击文件保存按钮,在弹出的对话框中输入名称和选择存储路径,完成接收到的数据保存到文件的工作。
1)界面布局
如图所示,界面中包括标题栏“RVB2601声音采集系统”,按钮控制区,包括录音启动控制和录音文件保存。状态显示区,在录音过程中,显示包括网络通讯在内的信息显示。波形展示区,将接收到的波形实时显示出来,支持波形的局部放大查看等基本功能。
2)波形数据分析
利用软件Audition,打开保存的录音数据,可以播放和对数据进行分析。
3、实测效果演示
3.1 连接建立
上位机在和下位机完成以太网连接后,小灯由绿色变为红色,如果连接断开,小灯由红色变为绿色。
通过串口中断,如果连接成功建立,屏幕打印连接服务器成功提示。
液晶显示屏上显示“connect"字样。
3.2 启动录音和数据传输
接到上位机启动录音采样命令后,终端打印录音开始。当录音采样结束后,自动进入发送程序,完成录音数据发送。液晶显示屏上也有相应的显示,由于切换速度较快,具体细节参见后面的视频部分。
3.3 波形展示
通过软件打开形成的录音文件和上位软件采集的数据比对,是相同的。
上位软件实时显示的录音数据波形。
3.4 视频
4、写在最后总结的话
通过本次的测试,体验到了国产RSIC-V的发展,尤其是平头哥的这款处理器,和CDK开发环境的组合,给我的感觉是丝般顺滑的开发过程。而且在遇到问题的时候,钉钉上的技术支持和网站上的工单问题解决方式,都非常有效率,非常不错的一次测试。
注:联调视频拍的不太好,主要是手机拍摄,画面范围小,而且一个人拍不太好操作。
本文源自:平头哥芯片开放社区