回答

收藏

2024 汽车应用创意挑战赛:基于树莓派5 SX1278 模块以及FFT声...

#竞赛 #竞赛 1097 人阅读 | 0 人回复 | 2025-03-03

本帖最后由 donatello 于 2025-3-3 15:31 编辑

2024 汽车应用创意挑战赛基于树莓派5 SX1278 模块以及FFT声纹采集算法的语音识别反馈系统


1. 作品简介
作品采用在得捷商城购买的树莓派5 8GB内存开发板,通过TTL串口与SX1278模块进行通信,并接入一些外部必要硬件模块,软件程序接入百度语音云,使之实现无线通信+语音识别+反馈控制的一个系统。


2.系统框图(图文结合)



树莓派5 8GB内存开发板通过TTL串口与SX1278模块进行通信,模块的模拟输出端接功放模块,功放模块接树莓派5 8GB内存开发板的GPIO模拟输入引脚,另一SX1278模块通过无线信号与SX1278模块进行通信,并接入一个麦克风模块用于声音录入。树莓派5 开发板通过摄像头麦克风录入声音,通过以太网连接百度语音云实现反馈控制。


3.流程图(图文结合)

树莓派5处理无线模块的声音模拟数据是以帧(frame)为单位的,每一帧可以是256/512/1024/2048字节,每一帧数据量越大,则读取一帧数据的时间就越长,以本作品的1024字节为一帧为例,每一秒可以处理大约16帧数据,也就是16384字节的声音模拟数据,这个声音模拟数据的指标也跟设备的码率挂钩,以USB摄像头自带的麦克风为例,市面上常用的USB免驱摄像头采集声音模拟数据一般是16384码率或者44000码率,码率越大,每秒发给USB主机的声音模拟数据量就越多,但是声音质量也会有所下降。树莓派5接收到一帧声音模拟数据之后,立刻进行FFT,众所周知FFT的结果就是频域结果,横轴是频率,纵轴是强度,现在的AI声纹大模型就是对频域结果进行处理,对每个频率的强度分析就可以得出多方面的数据,比如男声还是女声,汉字的平仄,英文的语气等等,这是一套非常复杂且完善的流程,我这里只做简单的声纹判断,也就是区分人的语音和环境杂音,只要有人说话就开始进行语音识别:

有人语音的帧即为有效帧,将所有连续的有效帧保存就能生成语音文件。

百度语音云的短语句识别是通过传输声音WAV文件来实现的,因此在上述步骤,将生成的只有有效帧的语音文件通过TCP协议发送到百度语音云平台,即可完成语音识别,树莓派5开发板再通过平台返回的语音内容进行相应操作。

4.各部分功能说明(图文结合)
首先是将有效帧识别并保存为WAV文件的步骤:
  1. FILE *file = fopen(SAVE_WAV_FILE , "wb");
  2.     if (!file)
  3.     {
  4.         printf("ERROR: Can't open output file.\n");
  5.         return -1;
  6.     }
  7.     Write_Wav_Header(file, CHANNELS, sample_rate, BITS_PER_SAMPLE, 0);

  8.     int buffer_size = frames_per_period * CHANNELS * BITS_PER_SAMPLE / 8;
  9.     //printf("frames_per_period = %d.\n" , frames_per_period);
  10.     //printf("buffer_size = %d.\n" , buffer_size);
  11.     char *buffer = (char *) malloc(buffer_size);

  12.     float buffer_float[1024];
  13.     float buffer_fft[512];
  14.     uint32_t buffer_fft_256[256];
  15.     uint32_t buffer_fft_seg_aver_64[64];

  16.     int total_bytes = 0;
  17.     count = 0;
  18.     while (total_bytes < SAMPLE_RATE * CHANNELS * BITS_PER_SAMPLE / 8)
  19.     //while(1)
  20.     {
  21.         memset(buffer_fft_seg_aver_64_sum , NULL , sizeof(buffer_fft_seg_aver_64_sum));
  22.         pcm = gpio_ad_read(handle , buffer , frames_per_period);

  23.         for(i = 0 ; i < buffer_size ; i ++)
  24.         {
  25.             //printf("%d " , buffer[i]);
  26.             buffer_float[i] = buffer[i] * 1.0;
  27.         }
  28.         FFTW_Mag_Test(buffer_float , buffer_fft);

  29.         //printf("buffer_size = %d.\n" , buffer_size);
  30.         //buffer_size == 1024
  31.         //buffer_fft_32[512]
  32.         //buffer_fft_seg_aver_16[32]

  33.         for(i = 0 ; i < buffer_size / 4 ; i ++)
  34.         {
  35.             buffer_fft_256[i] = (uint32_t)(buffer_fft[i] / 50.0);
  36.         }

  37.         for(i = 0 ; i < buffer_size / 4 ; i += 4)
  38.         {
  39.             buffer_fft_seg_aver_64[i / 4] =
  40.             (uint32_t)
  41.             (
  42.             (buffer_fft[i] +
  43.             buffer_fft[i + 1] +
  44.             buffer_fft[i + 2] +
  45.             buffer_fft[i + 3]
  46.             ) / 320.0
  47.             );

  48.             buffer_fft_seg_aver_64_sum[i / 4] +=
  49.             (uint32_t)
  50.             (
  51.             (buffer_fft[i] +
  52.             buffer_fft[i + 1] +
  53.             buffer_fft[i + 2] +
  54.             buffer_fft[i + 3]
  55.             ) / 200.0
  56.             );
  57.             //printf("buffer_fft_seg_aver_8 i:%d %d.\n" , i / 8 , buffer_fft_seg_aver_8[i / 8]);
  58.         }

  59.         LCD_Waveform_Graph_Whole_Add_Single(100 , 256 , 20 , 300 , buffer_fft_256 , LCD_COLOR_WHITE_32 , 0 , LCD_COLOR_RED_32 , 1);

  60.         LCD_Waveform_Chart(100 , 1700 , 660 , 300 , buffer_fft_seg_aver_64_sum , 64 , 22 , 5 , LCD_COLOR_WHITE_32 , 0 , LCD_COLOR_BLUE_32);
  61.         
  62.         LCD_Show_ASCII_32(100 , 980 , LCD_COLOR_BLUE_32 , LCD_COLOR_CYAN_32 , '0');

  63.         LCD_Show_ASCII_32(222 , 980 , LCD_COLOR_BLUE_32 , LCD_COLOR_CYAN_32 , '5');

  64.         LCD_Show_ASCII_32(330 , 980 , LCD_COLOR_BLUE_32 , LCD_COLOR_CYAN_32 , '1');
  65.         LCD_Show_ASCII_32(342 , 980 , LCD_COLOR_BLUE_32 , LCD_COLOR_CYAN_32 , '0');

  66.         LCD_Show_ASCII_32(444 , 980 , LCD_COLOR_BLUE_32 , LCD_COLOR_CYAN_32 , '1');
  67.         LCD_Show_ASCII_32(460 , 980 , LCD_COLOR_BLUE_32 , LCD_COLOR_CYAN_32 , '5');

  68.         LCD_Show_ASCII_32(558 , 980 , LCD_COLOR_BLUE_32 , LCD_COLOR_CYAN_32 , '2');
  69.         LCD_Show_ASCII_32(574 , 980 , LCD_COLOR_BLUE_32 , LCD_COLOR_CYAN_32 , '0');

  70.         LCD_Show_ASCII_32(788 , 980 , LCD_COLOR_BLUE_32 , LCD_COLOR_CYAN_32 , '3');
  71.         LCD_Show_ASCII_32(804 , 980 , LCD_COLOR_BLUE_32 , LCD_COLOR_CYAN_32 , '0');

  72.         LCD_Show_ASCII_32(1018 , 980 , LCD_COLOR_BLUE_32 , LCD_COLOR_CYAN_32 , '4');
  73.         LCD_Show_ASCII_32(1034 , 980 , LCD_COLOR_BLUE_32 , LCD_COLOR_CYAN_32 , '0');

  74.         LCD_Show_ASCII_32(1248 , 980 , LCD_COLOR_BLUE_32 , LCD_COLOR_CYAN_32 , '5');
  75.         LCD_Show_ASCII_32(1264 , 980 , LCD_COLOR_BLUE_32 , LCD_COLOR_CYAN_32 , '0');

  76.         LCD_Show_ASCII_32(1480 , 980 , LCD_COLOR_BLUE_32 , LCD_COLOR_CYAN_32 , '6');
  77.         LCD_Show_ASCII_32(1496 , 980 , LCD_COLOR_BLUE_32 , LCD_COLOR_CYAN_32 , '0');

  78.         LCD_Show_ASCII_32(1548 , 980 , LCD_COLOR_BLUE_32 , LCD_COLOR_CYAN_32 , '6');
  79.         LCD_Show_ASCII_32(1564 , 980 , LCD_COLOR_BLUE_32 , LCD_COLOR_CYAN_32 , '3');
  80.         
  81.         LCD_Effect_32("/dev/fb0");

  82.         if (pcm == -EPIPE)
  83.         {
  84.             printf("ERROR: XRUN.\n");
  85.             snd_pcm_prepare(pcm_handle);
  86.         } else if (pcm < 0)
  87.         {
  88.             printf("ERROR: Can't read from PCM device.\n");
  89.         }
  90.         else
  91.         {
  92.             fwrite(buffer, 1, buffer_size, file);
  93.             total_bytes += buffer_size;
  94.             count ++;
  95.             //printf("count = %d total_bytes = %d\n" , count , total_bytes);
  96.         }
  97.         //usleep(50*1000);
  98.     }

  99.     uint32_t sum_6_and_8 = 0;
  100.     uint32_t sum_10_31 = 0;
  101.     uint32_t sum_60_62 = 0;

  102.     sum_6_and_8 = buffer_fft_seg_aver_64_sum[6] + buffer_fft_seg_aver_64_sum[8];
  103.    
  104.     // for(i = 1 ; i <= 4 ; i ++)
  105.     // {
  106.     //     //printf("i = %d %d.\n" , i , buffer_fft_seg_aver_8_sum[i]);
  107.     //     sum_1_4 += buffer_fft_seg_aver_8_sum[i];
  108.     // }
  109.     // for(i = 10 ; i <= 31 ; i ++)
  110.     // {
  111.     //     //printf("i = %d %d.\n" , i , buffer_fft_seg_aver_8_sum[i]);
  112.     //     sum_10_31 += buffer_fft_seg_aver_8_sum[i];
  113.     // }

  114.     printf("%d %d %d %d .\n" , buffer_fft_seg_aver_64_sum[2] , buffer_fft_seg_aver_64_sum[4] , buffer_fft_seg_aver_64_sum[6]
  115.      , buffer_fft_seg_aver_64_sum[8]);

  116.     //printf("1 = %d 2 = %d 3 = %d 4 = %d.\n" , buffer_fft_seg_aver_8_sum[1] , buffer_fft_seg_aver_8_sum[2] ,
  117.     //buffer_fft_seg_aver_8_sum[3] , buffer_fft_seg_aver_8_sum[4]);

  118.     // if(sum_1_2 >= 9000 && sum_12_22 >= 17000 && sum_12_22 <= 30000 && sum_49_54 >= 9500 && sum_60_62 >= 9000)
  119.     // {
  120.     //     printf("Human Voice 1 , while.\n");
  121.     // }
  122.     // else if(sum_1_2 >= 7500 && sum_12_22 >= 20000 && sum_12_22 <= 30000 && sum_49_54 >= 11500 && sum_60_62 >= 8000)
  123.     // {
  124.     //     printf("Human Voice 2 , while.\n");
  125.     // }
  126.     // else if(sum_1_2 >= 6500 && sum_12_22 >= 23000 && sum_12_22 <= 25000 && sum_49_54 >= 12000 && sum_60_62 >= 8500)
  127.     // {
  128.     //     printf("Human Voice 3 , while.\n");
  129.     // }
  130.     // else if(sum_1_2 >= 8000 && sum_12_22 >= 21000 && sum_12_22 <= 30000 && sum_49_54 >= 10000 && sum_60_62 >= 9000)
  131.     // {
  132.     //     printf("Human Voice 4 , while.\n");
  133.     // }   

  134.     fseek(file, 0, SEEK_SET);
  135.     Write_Wav_Header(file , CHANNELS , sample_rate , BITS_PER_SAMPLE , total_bytes);

  136.     free(buffer);
  137.     fclose(file);
复制代码

上述代码中FFTW_Mag_Test()函数就是FFT工作的函数,将时域信号转为频域信号,FFT结果数组为buffer_fft[],是浮点类型的,需要转为整型才可以进行汇总,汇总结果数组为buffer_fft_seg_aver_64_sum(),这个数组的意义是buffer_fft[]数组按相邻若干个数据的求和结果,也就是在信号处理领域常见的频谱数组。

然后是接入百度语音云并进行语音识别的函数,这个直接照抄百度语音云给出的示例代码即可:
  1. RETURN_CODE Baidu_TSR(char baidu_api_key[] , char baidu_secret_key[] , char baidu_cuid[] , char baidu_tsr_filename[] , char baidu_tsr_format[] , uint32_t baidu_tsr_dev_pid ,
  2. char baidu_tsr_scope[] , uint32_t baidu_tsr_rate)
  3. {
  4.     char token[MAX_TOKEN_SIZE];
  5.     char url[300];
  6.     char header[50];
  7.     FILE *fp;
  8.     Json::Reader reader;
  9.     Json::Value root;

  10.     RETURN_CODE res;

  11.     curl_global_init(CURL_GLOBAL_ALL);

  12.     fp = fopen(baidu_tsr_filename , "r");
  13.     if (fp == NULL)
  14.     {
  15.         char cwd[200];
  16.         getcwd(cwd,sizeof(cwd));
  17.         snprintf(g_demo_error_msg , BUFFER_ERROR_SIZE,
  18.                  "current running directory does not contain file %s, %s", baidu_tsr_filename , cwd);
  19.         return ERROR_ASR_FILE_NOT_EXIST;
  20.     }
  21.    
  22.     res = speech_get_token(baidu_api_key , baidu_secret_key , baidu_tsr_scope , token);
  23.     if (res == RETURN_OK)
  24.     {
  25.         CURL *curl = curl_easy_init();
  26.         char *cuid = curl_easy_escape(curl , baidu_cuid , strlen(baidu_cuid));

  27.         snprintf(url, sizeof(url), "%s?cuid=%s&token=%s&dev_pid=%d",
  28.                 baidu_tsr_url , baidu_cuid , token , baidu_tsr_dev_pid);

  29.         curl_free(cuid);

  30.         struct curl_slist *headerlist = NULL;

  31.         snprintf(header, sizeof(header), "Content-Type: audio/%s; rate=%d", baidu_tsr_format ,
  32.                 baidu_tsr_rate);
  33.         headerlist = curl_slist_append(headerlist, header);

  34.         int content_len = 0;
  35.         char *result = NULL;
  36.         char *audio_data = read_file_data(fp , &content_len);
  37.         curl_easy_setopt(curl, CURLOPT_URL, url);
  38.         curl_easy_setopt(curl, CURLOPT_POST, 1);
  39.         curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 5); // 连接5s超时
  40.         curl_easy_setopt(curl, CURLOPT_TIMEOUT, 60); // 整体请求60s超时
  41.         curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist); // 添加http header Content-Type
  42.         curl_easy_setopt(curl, CURLOPT_POSTFIELDS, audio_data);
  43.         curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, content_len);
  44.         curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc_tsr);
  45.         curl_easy_setopt(curl, CURLOPT_WRITEDATA, &result);

  46.         CURLcode res_curl = curl_easy_perform(curl);
  47.         baidu_xtts_string.clear();

  48.         if (res_curl != CURLE_OK)
  49.         {
  50.             snprintf(g_demo_error_msg, BUFFER_ERROR_SIZE, "perform curl error:%d, %s.\n" , res , curl_easy_strerror(res_curl));
  51.             res = ERROR_ASR_CURL;
  52.         }
  53.         else
  54.         {
  55.             //printf("result = %s\n", result);
  56.             if (reader.parse(result , root))
  57.             {
  58.                 baidu_xtts_string = root["result"][0].asString();
  59.                 printf("baidu_xtts_string = %s.\n"  , baidu_xtts_string.c_str());
  60.             }
  61.         }

  62.         curl_slist_free_all(headerlist);
  63.         free(audio_data);
  64.         free(result);
  65.         curl_easy_cleanup(curl);
  66.     }
  67.    
  68.     if (fp != NULL)
  69.     {
  70.         fclose(fp);
  71.     }

  72.     curl_global_cleanup();
  73.     return res;
  74. }
复制代码

最后就是根据语音返回结果来进行各种操作了,这个就不多赘述,只是简单的字符串判断。

5. 结语
总的来说,树莓派5的强大性能,使得它在处理语音数据输入,FFT算法,还是访问百度语音云的时候都非常轻松,整体流程非常顺畅不存在卡顿的情况,更由于其本身就是MPU,比起MCU平台的性能强大得多。非常感谢主办方得捷和协办方与非网,也感谢协办方工作人员&联络员Seven,日天兄辛苦了。祝得捷平台,与非网平台今后举办的活动更加红火,让广大的电子爱好者充分发挥才能。
分享到:
回复

使用道具 举报

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

本版积分规则

关闭

站长推荐上一条 /3 下一条