我爱下载 发表于 2021-8-21 16:03

RVB2601评估板试用5: 远程音频采集系统

<p style="text-align: center;"><span style="font-size:26px;">远程音频采集系统</span></p>

<p><span style="font-size:24px;">1、概述</span></p>

<p><span style="font-size:16px;">&nbsp; &nbsp; 远程音频采集系统利用RVB2601的ES7210麦克风数字化采样,W800的WiFi通讯和OLED液晶显示相关信息,进行音频收集和存储。开发专用的上位软件,通过以太网通讯启动下位机完成一定数据量的音频收集后,传输到上位机,并存储为RAW文件。</span></p>

<p><span style="font-size:16px;">&nbsp; &nbsp; 系统中使用了麦克风音频采集ADC,W800无线以太网通讯,OLED液晶显示,指示灯等几个外设设备。</span></p>

<p><span style="font-size:24px;">2、测试程序</span></p>

<p><span style="font-size:16px;">&nbsp;&nbsp;&nbsp;&nbsp;通过概述的描述,利用之前的外设测试和学习经验,本次开发包括<span style="font-family: Consolas;">下位机软件,和上位机软件。</span></span></p>

<p><span style="font-size:20px;">2.1 下位机软件</span></p>

<p><span style="font-size:16px;">&nbsp;&nbsp;&nbsp;&nbsp;下位机软件主要包含wifi接口处理,音频采集、缓存管理和OLED三个主要部分。</span></p>

<p><span style="font-size:20px;">1) 初始化</span></p>

<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;根<span style="font-size:16px;">据以太网通讯的研究,《以太网通讯测试》连接&nbsp;https://bbs.eeworld.com.cn/thread-1174636-1-1.html&nbsp;&nbsp;</span><span style="font-size:16px;">建立wifi连接,获取ip地址,这里为了后面的处理方便,路由器中设置了固定的ip地址和mac的绑定关系,保证ip地址的获取唯一。</span></p>

<p><span style="font-size:16px;">&nbsp;&nbsp;&nbsp;&nbsp;根据音频测试的研究,《麦克风录音测试》 连接&nbsp;https://bbs.eeworld.com.cn/thread-1175631-1-1.html&nbsp; 完成音频采样音频初始化,并进入等待状态。</span></p>

<p><span style="font-size:16px;">&nbsp;&nbsp;&nbsp;&nbsp;为了更好的管理采样数据,建立一个RingBuffer缓冲队列。开辟一个48K的缓冲区,用来存储音频采样数据。</span></p>

<pre>
<code class="language-cpp">ringbuffer_t mic_ring_buffer;
#define MIC_RECORDER_BUF_SIZE 49152
uint8_t repeater_data_addr;

//设置录音缓冲区环形队列
mic_ring_buffer.buffer = repeater_data_addr;
mic_ring_buffer.size = MIC_RECORDER_BUF_SIZE;
ringbuffer_reset(&amp;mic_ring_buffer);</code></pre>

<p><span style="font-size:16px;">&nbsp;&nbsp;&nbsp;&nbsp;创建一个画面,画面中包换本次试用程序的标题&ldquo;THEAD RVB2601 RECORDER&rdquo;和2个显示标签。显示标签中实时显示当前的运行状态。</span></p>

<pre>
<code class="language-cpp">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");
}</code></pre>

<p>&nbsp;&nbsp;<span style="font-size:16px;">&nbsp;&nbsp;建立了以太网接收函数,音频采集任务,音频数据发送任务这3个主要任务,并利用信号量完成任务间的协调工作。</span></p>

<ul>
        <li>音频采集任务</li>
</ul>

<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;aos_task_new(&quot;mic_recorder&quot;, main_mic_task, NULL, 2 * 1024);</p>

<ul>
        <li>音频数据发送任务</li>
</ul>

<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;aos_task_new(&quot;mic_recorder_send&quot;, wifi_send_task, NULL, 4 * 1024);</p>

<ul>
        <li>图形显示任务</li>
</ul>

<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;aos_task_new(&quot;gui&quot;, gui_lvgl_task, NULL, 10 * 1024);</p>

<ul>
        <li>以太网接收函数通过串口终端启动。</li>
</ul>

<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int mic_tcpclient(void);</p>

<p><span style="font-size:20px;">2) 以太网数据接收</span></p>

<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;根据前面的研究,RVB2601只能作为以太网的Client端,所以,这里必须以以太网的客户端方式建立连接,完成数据收发。</p>

<pre>
<code class="language-cpp">int mic_tcpclient(void)
{
    struct sockaddr_insAddr;
    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(&amp;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 &lt; 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 *)&amp;sAddr, iAddrSize);

    if (iStatus &lt; 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)) &lt; 0)
      {
            break;
      }
                //解析数据
                if(cBsdBuf == 1)        //起动录音
                {
                        LOGD(TAG, "get record start command!");
                        mic_recorder_start();
                        wifi_send_run_flag = 1;
                }
//                else if(cBsdBuf == 2)         //起动传输
//                {
//                        LOGD(TAG, "get stop command!");
//                        mic_recorder_stop();
//                        wifi_send_task_run_status = 0;

//                }
                else if(cBsdBuf == 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;
}
</code></pre>

<p><span style="font-size:20px;">3)以太网数据发送</span></p>

<p><span style="font-size:20px;">&nbsp; &nbsp;</span><span style="font-size:16px;"> 每帧发送1024字节数据,连续发送,直到所有数据发送完成为止。</span></p>

<pre>
<code class="language-cpp">void wifi_send_task(void *arg)
{
    int               iStatus;
        int len;
        LOGD(TAG, "MIC Recorder send thread begin!");
        aos_sem_new(&amp;mic_data_send_sem, 0);       
       
    while (1)
    {
      aos_sem_wait(&amp;mic_data_send_sem, AOS_WAIT_FOREVER);
                while(wifi_send_run_flag)
                {
                        len = mic_recorder_getdata_len();
                        if(len &gt; 0)
                        {
                                len = mic_recorder_getdata((uint8_t *)lan_buf,1024);
                                if(len &gt; 0)
                                {
                                        record_sta = REC_SEND;

                                        // sending packet
                                        iStatus = send(iSockFD, lan_buf, len, 0);

                                        if (iStatus &lt;= 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;
               
    }
}
</code></pre>

<p><span style="font-size:20px;">4)音频采样</span></p>

<p><span style="font-size:16px;">音频启动中负责复位缓冲队列,然后启动音频采样。</span></p>

<pre>
<code class="language-cpp">void mic_recorder_start(void)
{
    ringbuffer_reset(&amp;mic_ring_buffer);
    csi_codec_input_start(&amp;codec_input_ch);
}</code></pre>

<p><span style="font-size:16px;">录取到一定数量音频后,产生触发事件,事件中利用信号量环形音频采样任务,将数据读出并存入缓冲队列。</span></p>

<pre>
<code class="language-cpp">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(&amp;mic_input_sem);
   }
}</code></pre>

<p>通过音频采集任务,接收事件信号触发,完成数据读取</p>

<pre>
<code class="language-cpp">static void main_mic_task(void *arg)
{
    LOGD(TAG, "MIC Recorder thread begin!");
    aos_sem_new(&amp;mic_input_sem, 0); //创建音频采样信号
    mic_recorder_init(); //初始化音频采样
   
    while (1)
    {
       aos_sem_wait(&amp;mic_input_sem, AOS_WAIT_FOREVER); //等待一组音频数据采集完成
       mic_recorder_to_buff(); //将采集到的音频数据压入缓冲队列中
    }
}</code></pre>

<p><span style="font-size:16px;">读取音频数据压入缓冲队列,判断采样结束,并触发音频数据发送</span></p>

<pre>
<code class="language-cpp">void mic_recorder_to_buff(void)
{
   csi_codec_input_read_async(&amp;codec_input_ch, mic_input_buf, 1024);
   ringbuffer_in(&amp;mic_ring_buffer, mic_input_buf, 1024);
   uint32_t len = ringbuffer_len(&amp;mic_ring_buffer);
   if(len &gt;= MIC_RECORDER_BUF_SIZE)
   {
       csi_codec_input_stop(&amp;codec_input_ch);
       aos_sem_signal(&amp;mic_data_send_sem);
   }
}</code></pre>

<p><span style="font-size:20px;">5)运行状态显示</span></p>

<p><span style="font-size:16px;">首先创建了一个枚举类型,包换所有待显示的状态定义。</span></p>

<pre>
<code class="language-cpp">typedef enum _RECORDER_STA
{
   IDEL = 0, //空闲状态
   NET_CONNECT, //网络连接
   NET_DISCON, //网络断开
   NET_ERROR, //网络数据发送错误
   REC_INIT, //录音初始化
   REC_START, //录音启动
   REC_RUNNING, //录音采样中
   REC_SEND, //发送录音数据
}RECORDER_STA;</code></pre>

<p><span style="font-size:16px;">根据当前系统运行的状态,显示在OLED的画面上。</span></p>

<pre>
<code class="language-cpp">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);
    }
}</code></pre>

<p>&nbsp;</p>

<p><span style="font-size:20px;">2.2 上位机软件</span></p>

<p><span style="font-size:16px;">&nbsp; &nbsp; &nbsp; 软件是本次测试工程的上位机软件,软件功能包括网络通讯,声音采样控制,波形展示,录音数据存储,状态显示等几个部分。利用TCPServer在端口为26666建立一个监听,接收来自下位机的连接申请。通过上位机的采样启动按钮发送启动命令,下位机接收到启动命令后启动录音采样并存储,采样结束后,触发数据发送,上位机接收到数据后存入缓存中,同时刷新波形显示。点击文件保存按钮,在弹出的对话框中输入名称和选择存储路径,完成接收到的数据保存到文件的工作。</span></p>

<p><span style="font-size:20px;">1)界面布局</span></p>

<p><span style="font-size:16px;">&nbsp; &nbsp; &nbsp; 如图所示,界面中包括标题栏&ldquo;RVB2601声音采集系统&rdquo;,按钮控制区,包括录音启动控制和录音文件保存。状态显示区,在录音过程中,显示包括网络通讯在内的信息显示。波形展示区,将接收到的波形实时显示出来,支持波形的局部放大查看等基本功能。</span></p>

<p></p>

<p><span style="font-size:20px;">2)波形数据分析</span></p>

<p>&nbsp; &nbsp; &nbsp;利用软件Audition,打开保存的录音数据,可以播放和对数据进行分析。</p>

<p></p>

<p>&nbsp;</p>

<p><span style="font-size:24px;">3、实测效果演示</span></p>

<p><span style="font-size:20px;">3.1 连接建立</span></p>

<p></p>

<p>上位机在和下位机完成以太网连接后,小灯由绿色变为红色,如果连接断开,小灯由红色变为绿色。</p>

<p></p>

<p>&nbsp; &nbsp; &nbsp; 通过串口中断,如果连接成功建立,屏幕打印连接服务器成功提示。</p>

<p></p>

<p>液晶显示屏上显示&ldquo;connect&quot;字样。</p>

<p>&nbsp;</p>

<p><span style="font-size:20px;">3.2 启动录音和数据传输</span></p>

<p></p>

<p>接到上位机启动录音采样命令后,终端打印录音开始。当录音采样结束后,自动进入发送程序,完成录音数据发送。液晶显示屏上也有相应的显示,由于切换速度较快,具体细节参见后面的视频部分。</p>

<p><span style="font-size:20px;">3.3 波形展示</span></p>

<p></p>

<p>通过软件打开形成的录音文件和上位软件采集的数据比对,是相同的。</p>

<p></p>

<p>上位软件实时显示的录音数据波形。</p>

<p><span style="font-size:20px;">3.4 视频</span></p>

<p>1) RVB2601录音过程中,液晶显示屏上状态变化显示</p>

<p><iframe allowfullscreen="true" frameborder="0" height="450" src="http://player.youku.com/embed/XNTgwMDE3NTM4NA" style="background:#eee;margin-bottom:10px;" width="700"></iframe><br />
2)下位机和上位机联调全过程视频</p>

<p><iframe allowfullscreen="true" frameborder="0" height="450" src="http://player.youku.com/embed/XNTgwMDE4NjcwNA" style="background:#eee;margin-bottom:10px;" width="700"></iframe></p>

<p>&nbsp;</p>

<p><span style="font-size:24px;">4、写在最后总结的话:</span></p>

<p><span style="font-size:16px;">&nbsp; &nbsp; 通过本次的测试,体验到了国产RSIC-V的发展,尤其是平头哥的这款处理器,和CDK开发环境的组合,给我的感觉是丝般顺滑的开发过程。而且在遇到问题的时候,钉钉上的技术支持和网站上的工单问题解决方式,都非常有效率,非常不错的一次测试。</span><br />
&nbsp;</p>

<p><br />
&nbsp;</p>

我爱下载 发表于 2021-8-21 16:04

<p>视频不知道可以显示吗?我这里看不到啊</p>

我爱下载 发表于 2021-8-21 16:05

<p>两个视频</p>

<p>1)https://v.youku.com/v_show/id_XNTgwMDE3NTM4NA==.html?spm=a2h0c.8166622.PhoneSokuUgc_2.dtitle</p>

<p>2)https://v.youku.com/v_show/id_XNTgwMDE4NjcwNA==.html?spm=a2h0c.8166622.PhoneSokuUgc_1.dtitle</p>

我爱下载 发表于 2021-8-21 16:06

视频拍的不太好,主要是一个人用手机操作有点费劲

soso 发表于 2021-8-23 10:10

我爱下载 发表于 2021-8-21 16:04
视频不知道可以显示吗?我这里看不到啊

<p>视频可以显示。</p>

我爱下载 发表于 2021-8-24 07:56

soso 发表于 2021-8-23 10:10
视频可以显示。

<p>我在不同的地方看到的不一样,有些可以显示,有些不能显示</p>

soso 发表于 2021-8-24 10:02

我爱下载 发表于 2021-8-24 07:56
我在不同的地方看到的不一样,有些可以显示,有些不能显示

<p>比如说呢?在哪些地方不能显示呢?</p>

w494143467 发表于 2021-8-24 10:22

<p>最近也在研究音频,参考一下。</p>

freebsder 发表于 2021-8-24 17:17

<p>这个有点意思,做的工作不小,很完整。</p>

我爱下载 发表于 2021-8-25 08:06

soso 发表于 2021-8-24 10:02
比如说呢?在哪些地方不能显示呢?

<p>我在单位的网络上就看不到,家里就可以,不知道是不是单位对视频网站有限制了</p>

dql2016 发表于 2021-8-26 21:42

<p>上位机用啥做的</p>

我爱下载 发表于 2021-8-27 08:01

dql2016 发表于 2021-8-26 21:42
上位机用啥做的

<p>C++ builder</p>

太白金星 发表于 2021-9-2 20:37

<p>声音播放过吗?效果如何?</p>
页: [1]
查看完整版本: RVB2601评估板试用5: 远程音频采集系统