一、概述
本节我们记录在respeaker core v2 开发板上部署一个完整的声源定位(DOA)系统,演示可以看第一节中的视频。整个模块可以分为三部分,第一部分为控制开发板上的LED灯显示,这样可以实时的测试算法的效果;第二部分为从ALSA上取数据,我们这里需要取六通道的麦克风数据,加上最后两个通道的回采信号,总共八个通道;第三部分为DOA算法部分,这里主要实现了SRP-PHAT算法。LED灯的控制程序和声源定位算法程序是两个进程同时运行,进程之间通过管道来通信,声源定位算法向管道输入定位的角度信息,LED灯的控制程序从管道获取角度信息,进而控制灯的或亮或灭。
二、模块解析
2.1 整体效果
从声源定位(DOA)算法仿真到工程源码实现-第一节-CSDN博客
2.2 LED灯控
(1)代码结构
respeaker_led
├── examples
│ └── led_show.py
└── pixel_ring└── apa102.py
(2)led_show.py
import time
import sys
import os
import timesys.path.append("../pixel_ring/")
from apa102 import APA102class HiPixelRing(object):PIXELS_N = 12def __init__(self):self.dev = APA102(num_led=self.PIXELS_N)def off(self):for i in range(self.PIXELS_N):self.dev.set_pixel(i, 0, 0, 0)self.dev.show()self.dev.show()def wakeup(self, direction=0):led_num = (direction + 15) // 30led_num = led_num % 12#led_num = int(led_num)for i in range(self.PIXELS_N):if i != led_num:self.dev.set_pixel(i, 0, 0, 0)else:self.dev.set_pixel(i, 10, 10, 10)self.dev.show()if __name__ == '__main__':pixel_ring = HiPixelRing()direction = 0input_file = "/tmp/node2_to_node1.tmp"# 1.create fifoif not os.path.exists(input_file):os.mkfifo(input_file, 0o666)# 2.open pipeprint('init read pipe: ' + input_file)fin = os.open(input_file, os.O_RDONLY)val = ""while True:try:#recv_str = os.read(fin, 1024).decode()[:-1]recv_str = os.read(fin, 1).decode()if recv_str != 'T':val += recv_strcontinueelse:recv_str = valval = ""print("recv:{}".format(recv_str))direction = int(recv_str)pixel_ring.wakeup(direction)#direction += 30#time.sleep(0.5)except KeyboardInterrupt:breakos.close(fin)pixel_ring.off()time.sleep(1)
(3)apa102.py
"""
from https://github.com/tinue/APA102_Pi
This is the main driver module for APA102 LEDs
"""
import spidev
from math import ceilRGB_MAP = { 'rgb': [3, 2, 1], 'rbg': [3, 1, 2], 'grb': [2, 3, 1],'gbr': [2, 1, 3], 'brg': [1, 3, 2], 'bgr': [1, 2, 3] }class APA102:"""Driver for APA102 LEDS (aka "DotStar").(c) Martin Erzberger 2016-2017My very first Python code, so I am sure there is a lot to be optimized ;)Public methods are:- set_pixel- set_pixel_rgb- show- clear_strip- cleanupHelper methods for color manipulation are:- combine_color- wheelThe rest of the methods are used internally and should not be used by theuser of the library.Very brief overview of APA102: An APA102 LED is addressed with SPI. The bitsare shifted in one by one, starting with the least significant bit.An LED usually just forwards everything that is sent to its data-in todata-out. While doing this, it remembers its own color and keeps glowingwith that color as long as there is power.An LED can be switched to not forward the data, but instead use the datato change it's own color. This is done by sending (at least) 32 bits ofzeroes to data-in. The LED then accepts the next correct 32 bit LEDframe (with color information) as its new color setting.After having received the 32 bit color frame, the LED changes color,and then resumes to just copying data-in to data-out.The really clever bit is this: While receiving the 32 bit LED frame,the LED sends zeroes on its data-out line. Because a color frame is32 bits, the LED sends 32 bits of zeroes to the next LED.As we have seen above, this means that the next LED is now readyto accept a color frame and update its color.So that's really the entire protocol:- Start by sending 32 bits of zeroes. This prepares LED 1 to updateits color.- Send color information one by one, starting with the color for LED 1,then LED 2 etc.- Finish off by cycling the clock line a few times to get all datato the very last LED on the stripThe last step is necessary, because each LED delays forwarding the dataa bit. Imagine ten people in a row. When you yell the last colorinformation, i.e. the one for person ten, to the first person inthe line, then you are not finished yet. Person one has to turn aroundand yell it to person 2, and so on. So it takes ten additional "dummy"cycles until person ten knows the color. When you look closer,you will see that not even person 9 knows its own color yet. Thisinformation is still with person 2. Essentially the driver sends additionalzeroes to LED 1 as long as it takes for the last color frame to make itdown the line to the last LED."""# ConstantsMAX_BRIGHTNESS = 0b11111 # Safeguard: Set to a value appropriate for your setupLED_START = 0b11100000 # Three "1" bits, followed by 5 brightness bitsdef __init__(self, num_led, global_brightness=MAX_BRIGHTNESS,order='rgb', bus=0, device=1, max_speed_hz=8000000):self.num_led = num_led # The number of LEDs in the Striporder = order.lower()self.rgb = RGB_MAP.get(order, RGB_MAP['rgb'])# Limit the brightness to the maximum if it's set higherif global_brightness > self.MAX_BRIGHTNESS:self.global_brightness = self.MAX_BRIGHTNESSelse:self.global_brightness = global_brightnessself.leds = [self.LED_START,0,0,0] * self.num_led # Pixel bufferself.spi = spidev.SpiDev() # Init the SPI deviceself.spi.open(bus, device) # Open SPI port 0, slave device (CS) 1# Up the speed a bit, so that the LEDs are painted fasterif max_speed_hz:self.spi.max_speed_hz = max_speed_hzdef clock_start_frame(self):"""Sends a start frame to the LED strip.This method clocks out a start frame, telling the receiving LEDthat it must update its own color now."""self.spi.xfer2([0] * 4) # Start frame, 32 zero bitsdef clock_end_frame(self):"""Sends an end frame to the LED strip.As explained above, dummy data must be sent after the last real colourinformation so that all of the data can reach its destination down the line.The delay is not as bad as with the human example above.It is only 1/2 bit per LED. This is because the SPI clock lineneeds to be inverted.Say a bit is ready on the SPI data line. The sender communicatesthis by toggling the clock line. The bit is read by the LEDand immediately forwarded to the output data line. When the clock goesdown again on the input side, the LED will toggle the clock upon the output to tell the next LED that the bit is ready.After one LED the clock is inverted, and after two LEDs it is in syncagain, but one cycle behind. Therefore, for every two LEDs, one bitof delay gets accumulated. For 300 LEDs, 150 additional bits must be fed tothe input of LED one so that the data can reach the last LED.Ultimately, we need to send additional numLEDs/2 arbitrary data bits,in order to trigger numLEDs/2 additional clock changes. This driversends zeroes, which has the benefit of getting LED one partially orfully ready for the next update to the strip. An optimized versionof the driver could omit the "clockStartFrame" method if enough zeroes havebeen sent as part of "clockEndFrame"."""self.spi.xfer2([0xFF] * 4)# Round up num_led/2 bits (or num_led/16 bytes)#for _ in range((self.num_led + 15) // 16):# self.spi.xfer2([0x00])def clear_strip(self):""" Turns off the strip and shows the result right away."""for led in range(self.num_led):self.set_pixel(led, 0, 0, 0)self.show()def set_pixel(self, led_num, red, green, blue, bright_percent=100):"""Sets the color of one pixel in the LED stripe.The changed pixel is not shown yet on the Stripe, it is onlywritten to the pixel buffer. Colors are passed individually.If brightness is not set the global brightness setting is used."""if led_num < 0:return # Pixel is invisible, so ignoreif led_num >= self.num_led:return # again, invisible# Calculate pixel brightness as a percentage of the# defined global_brightness. Round up to nearest integer# as we expect some brightness unless set to 0brightness = int(ceil(bright_percent*self.global_brightness/100.0))# LED startframe is three "1" bits, followed by 5 brightness bitsledstart = (brightness & 0b00011111) | self.LED_STARTstart_index = 4 * led_numself.leds[start_index] = ledstartself.leds[start_index + self.rgb[0]] = redself.leds[start_index + self.rgb[1]] = greenself.leds[start_index + self.rgb[2]] = bluedef set_pixel_rgb(self, led_num, rgb_color, bright_percent=100):"""Sets the color of one pixel in the LED stripe.The changed pixel is not shown yet on the Stripe, it is onlywritten to the pixel buffer.Colors are passed combined (3 bytes concatenated)If brightness is not set the global brightness setting is used."""self.set_pixel(led_num, (rgb_color & 0xFF0000) >> 16,(rgb_color & 0x00FF00) >> 8, rgb_color & 0x0000FF,bright_percent)def rotate(self, positions=1):""" Rotate the LEDs by the specified number of positions.Treating the internal LED array as a circular buffer, rotate it bythe specified number of positions. The number could be negative,which means rotating in the opposite direction."""cutoff = 4 * (positions % self.num_led)self.leds = self.leds[cutoff:] + self.leds[:cutoff]def show(self):"""Sends the content of the pixel buffer to the strip.Todo: More than 1024 LEDs requires more than one xfer operation."""self.clock_start_frame()# xfer2 kills the list, unfortunately. So it must be copied first# SPI takes up to 4096 Integers. So we are fine for up to 1024 LEDs.data = list(self.leds)while data:self.spi.xfer2(data[:32])data = data[32:]self.clock_end_frame()def cleanup(self):"""Release the SPI device; Call this method at the end"""self.spi.close() # Close SPI port@staticmethoddef combine_color(red, green, blue):"""Make one 3*8 byte color value."""return (red << 16) + (green << 8) + bluedef wheel(self, wheel_pos):"""Get a color from a color wheel; Green -> Red -> Blue -> Green"""if wheel_pos > 255:wheel_pos = 255 # Safeguardif wheel_pos < 85: # Green -> Redreturn self.combine_color(wheel_pos * 3, 255 - wheel_pos * 3, 0)if wheel_pos < 170: # Red -> Bluewheel_pos -= 85return self.combine_color(255 - wheel_pos * 3, 0, wheel_pos * 3)# Blue -> Greenwheel_pos -= 170return self.combine_color(0, wheel_pos * 3, 255 - wheel_pos * 3)def dump_array(self):"""For debug purposes: Dump the LED array onto the console."""print(self.leds)
2.3 ALSA取数据
(1)ALSA读数据及DOA算法调用
#define ALSA_PCM_NEW_HW_PARAMS_API#include <alsa/asoundlib.h>
#include <stdio.h>
#include "doa-api.h"
#define NUM_FRAMES 160
#define OUTPUT_PATH_NAME "/tmp/node2_to_node1.tmp"int main() {int fout = open(OUTPUT_PATH_NAME, O_WRONLY);char buffer2[1024]; long loops;int rc;int size;unsigned int val;int dir;char *buffer;snd_pcm_t *handle;snd_pcm_hw_params_t *params;snd_pcm_uframes_t frames;int mic_num = 6;float radius = 0.0465;int channel_num = 8;void *handle1 = api_create_doa_handle(mic_num, radius);//short *input_data = (short*)malloc(sizeof(short) * channel_num * NUM_FRAMES);float *inp_float = (float*)malloc(sizeof(float) * mic_num * NUM_FRAMES);/*以录制模式打开*//* Open PCM device for recording (capture). */rc = snd_pcm_open( &handle, "default", SND_PCM_STREAM_CAPTURE, 0);if (rc < 0) {fprintf(stderr, "unable to open pcm device");exit(EXIT_FAILURE);}/*分配一个参数对象*//* Allocate a hardware parameters object. */snd_pcm_hw_params_alloca(¶ms);/*初始化参数对象*//* Fill it in with default values. */rc = snd_pcm_hw_params_any(handle, params);if (rc < 0) {printf("Err\n");}/* Set the desired hardware parameters. *//*交错模式*//* Interleaved mode */rc = snd_pcm_hw_params_set_access(handle, params,SND_PCM_ACCESS_RW_INTERLEAVED);if (rc < 0) {printf("Err\n");}/*PCM格式*//* Signed 16-bit little-endian format */rc = snd_pcm_hw_params_set_format(handle, params,SND_PCM_FORMAT_S16_LE);if (rc < 0) {printf("Err\n");}/*设置通道数*//* Two channels (stereo) */rc = snd_pcm_hw_params_set_channels(handle, params, 8);if (rc < 0) {printf("Err\n");}/*设置采样率*//* 44100 bits/second sampling rate (CD quality) */val = 16000;rc = snd_pcm_hw_params_set_rate_near(handle, params,&val, &dir);if (rc < 0) {printf("Err\n");}/*没周期的帧数*//* Set period size to 32 frames. */frames = 160;rc = snd_pcm_hw_params_set_period_size_near(handle,params, &frames, &dir);if (rc < 0) {printf("Err\n");}/* Write the parameters to the driver */rc = snd_pcm_hw_params(handle, params);if (rc < 0) {fprintf(stderr,"unable to set hw parameters: %s/n",snd_strerror(rc));exit(1);}/* Use a buffer large enough to hold one period */rc = snd_pcm_hw_params_get_period_size(params,&frames, &dir);if (rc < 0) {printf("Err\n");}size = frames * 2 * 8; /* 2 bytes/sample, 2 channels */buffer = (char *) malloc(size);/* We want to loop for 5 seconds */rc = snd_pcm_hw_params_get_period_time(params, &val, &dir);loops = 5000000 / val;printf("====================:%ld\n", frames);printf("====================\n");printf("====================\n");FILE *fp = fopen("record.pcm", "wb");while (loops > 0) {//loops--;rc = snd_pcm_readi(handle, buffer, frames);if (rc == -EPIPE) {/* EPIPE means overrun */fprintf(stderr, "overrun occurred/n");//把PCM流置于PREPARED状态,这样下次我们向该PCM流中数据时,它就能重新开始处理数据。snd_pcm_prepare(handle);} else if (rc < 0) {fprintf(stderr,"error from read: %s/n",snd_strerror(rc));} else if (rc != (int)frames) {fprintf(stderr, "short read, read %d frames/n", rc);}//rc = write(1, buffer, size);short *input_data_x = (short*)buffer;for (int i = 0; i < NUM_FRAMES; i++) {for (int j = 0; j < mic_num; j++) {inp_float[i*mic_num+j] = input_data_x[i*channel_num+j];}}int angle = api_process_doa(handle1, inp_float, NUM_FRAMES);memset(buffer2, 0, sizeof(buffer2));sprintf(buffer2, "%dT", angle);if (write(fout, buffer2, strlen(buffer2)) <= 0) {break;}printf(">>> %d\n", angle);/*rc = fwrite(buffer, 1, size, fp);if (rc != size)fprintf(stderr,"short write: wrote %d bytes/n", rc);*/}//调用snd_pcm_drain把所有挂起没有传输完的声音样本传输完全rc = snd_pcm_drain(handle);//关闭该音频流,释放之前动态分配的缓冲区,退出rc = snd_pcm_close(handle);free(buffer);fclose(fp);api_destroy_doa_handle(handle1);return 0;
}
2.4 DOA算法
(1)代码结构
doa
├── CMakeLists.txt
├── bin
│ ├── CMakeLists.txt
│ ├── alsa-doa.c
│ └── test-doa.c
├── build
│ ├── CMakeCache.txt
│ ├── CMakeFiles
│ │ ├── 3.22.1
│ │ │ ├── CMakeCCompiler.cmake
│ │ │ ├── CMakeCXXCompiler.cmake
│ │ │ ├── CMakeDetermineCompilerABI_C.bin
│ │ │ ├── CMakeDetermineCompilerABI_CXX.bin
│ │ │ ├── CMakeSystem.cmake
│ │ │ ├── CompilerIdC
│ │ │ │ ├── CMakeCCompilerId.c
│ │ │ │ ├── a.out
│ │ │ │ └── tmp
│ │ │ └── CompilerIdCXX
│ │ │ ├── CMakeCXXCompilerId.cpp
│ │ │ ├── a.out
│ │ │ └── tmp
│ │ ├── CMakeDirectoryInformation.cmake
│ │ ├── CMakeOutput.log
│ │ ├── CMakeTmp
│ │ ├── Makefile.cmake
│ │ ├── Makefile2
│ │ ├── TargetDirectories.txt
│ │ ├── cmake.check_cache
│ │ └── progress.marks
│ ├── Makefile
│ ├── bin
│ │ ├── CMakeFiles
│ │ │ ├── CMakeDirectoryInformation.cmake
│ │ │ ├── alsa-doa.dir
│ │ │ │ ├── DependInfo.cmake
│ │ │ │ ├── alsa-doa.c.o
│ │ │ │ ├── alsa-doa.c.o.d
│ │ │ │ ├── build.make
│ │ │ │ ├── cmake_clean.cmake
│ │ │ │ ├── compiler_depend.make
│ │ │ │ ├── compiler_depend.ts
│ │ │ │ ├── depend.make
│ │ │ │ ├── flags.make
│ │ │ │ ├── link.txt
│ │ │ │ └── progress.make
│ │ │ ├── progress.marks
│ │ │ └── test-doa.dir
│ │ │ ├── DependInfo.cmake
│ │ │ ├── build.make
│ │ │ ├── cmake_clean.cmake
│ │ │ ├── compiler_depend.make
│ │ │ ├── compiler_depend.ts
│ │ │ ├── depend.make
│ │ │ ├── flags.make
│ │ │ ├── link.txt
│ │ │ ├── progress.make
│ │ │ ├── test-doa.c.o
│ │ │ └── test-doa.c.o.d
│ │ ├── Makefile
│ │ ├── alsa-doa
│ │ ├── cmake_install.cmake
│ │ └── test-doa
│ ├── cmake_install.cmake
│ └── src
│ ├── CMakeFiles
│ │ ├── CMakeDirectoryInformation.cmake
│ │ ├── mouse-doa.dir
│ │ │ ├── DependInfo.cmake
│ │ │ ├── build.make
│ │ │ ├── cmake_clean.cmake
│ │ │ ├── cmake_clean_target.cmake
│ │ │ ├── compiler_depend.make
│ │ │ ├── compiler_depend.ts
│ │ │ ├── depend.make
│ │ │ ├── doa-api.c.o
│ │ │ ├── doa-api.c.o.d
│ │ │ ├── doa-blocker.c.o
│ │ │ ├── doa-blocker.c.o.d
│ │ │ ├── doa-complex.c.o
│ │ │ ├── doa-complex.c.o.d
│ │ │ ├── doa-fft.c.o
│ │ │ ├── doa-fft.c.o.d
│ │ │ ├── doa-impl.c.o
│ │ │ ├── doa-impl.c.o.d
│ │ │ ├── doa-matrix.c.o
│ │ │ ├── doa-matrix.c.o.d
│ │ │ ├── fft4g.c.o
│ │ │ ├── fft4g.c.o.d
│ │ │ ├── flags.make
│ │ │ ├── link.txt
│ │ │ ├── progress.make
│ │ │ ├── ring_buffer.c.o
│ │ │ └── ring_buffer.c.o.d
│ │ └── progress.marks
│ ├── Makefile
│ ├── cmake_install.cmake
│ └── libmouse-doa.a
├── src
│ ├── CMakeLists.txt
│ ├── doa-api.c
│ ├── doa-api.h
│ ├── doa-blocker.c
│ ├── doa-blocker.h
│ ├── doa-complex.c
│ ├── doa-complex.h
│ ├── doa-fft.c
│ ├── doa-fft.h
│ ├── doa-impl.c
│ ├── doa-impl.h
│ ├── doa-matrix.c
│ ├── doa-matrix.h
│ ├── doa-types.h
│ ├── fft4g.c
│ ├── fft4g.h
│ ├── ring_buffer.c
│ └── ring_buffer.h
└── test└── 120du-180du.pcm
(2)doa-api.c
/**@author : aflyingwolf*@date : 2025.3.20*@file : doa-api.c* */
#include "doa-api.h"
#include "doa-blocker.h"
#include "doa-fft.h"
#include "doa-types.h"
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>static float mouse_FloatS16ToFloat(float v) {static const float kMaxInt16Inverse = 1.0 / 32767;static const float kMinInt16Inverse = 1.0 / -32768;return v * (v > 0 ? kMaxInt16Inverse : -kMinInt16Inverse);
}/*
static float mouse_FloatToFloatS16(float v) {return v * (v > 0 ? 32767.0 : 32768.0);
}*/typedef struct mouse_DoaHandle_ {mouse_blocker *blocker_;float **in_buf_;int mic_num_;mouse_doa *doa_inst_;
} mouse_DoaHandle;void *api_create_doa_handle(int mic_num, float radius) {mouse_doa *doa_inst = mouse_doa_instance(kSampleRate,mic_num,radius);//hamming windowsfloat window[512] = {0.08,0.08003477,0.08013909,0.08031292,0.08055626,0.08086906,0.08125127,0.08170284,0.0822237,0.08281376,0.08347295,0.08420116,0.08499828,0.08586418,0.08679875,0.08780184,0.08887329,0.09001294,0.09122063,0.09249617,0.09383936,0.09525001,0.09672789,0.0982728,0.09988448,0.10156271,0.10330722,0.10511775,0.10699403,0.10893578,0.1109427,0.11301448,0.11515082,0.11735139,0.11961586,0.12194389,0.12433512,0.12678919,0.12930573,0.13188437,0.13452471,0.13722635,0.13998888,0.14281189,0.14569495,0.14863762,0.15163946,0.15470002,0.15781883,0.16099542,0.16422931,0.16752001,0.17086702,0.17426984,0.17772796,0.18124085,0.18480797,0.1884288,0.19210278,0.19582935,0.19960796,0.20343803,0.20731899,0.21125024,0.2152312,0.21926125,0.2233398,0.22746622,0.2316399,0.23586019,0.24012646,0.24443807,0.24879437,0.25319469,0.25763837,0.26212475,0.26665313,0.27122284,0.27583319,0.28048347,0.28517299,0.28990103,0.29466689,0.29946984,0.30430915,0.3091841,0.31409394,0.31903793,0.32401534,0.32902539,0.33406735,0.33914043,0.34424389,0.34937694,0.35453881,0.35972872,0.36494588,0.37018951,0.37545881,0.38075299,0.38607124,0.39141277,0.39677676,0.4021624,0.40756889,0.41299539,0.4184411,0.42390518,0.42938682,0.43488518,0.44039943,0.44592874,0.45147227,0.45702919,0.46259865,0.46817981,0.47377183,0.47937386,0.48498506,0.49060458,0.49623157,0.50186517,0.50750453,0.51314881,0.51879715,0.5244487,0.5301026,0.53575799,0.54141402,0.54706984,0.55272459,0.55837742,0.56402747,0.56967389,0.57531582,0.58095241,0.58658281,0.59220616,0.59782163,0.60342835,0.60902548,0.61461218,0.6201876,0.62575089,0.63130122,0.63683774,0.64235963,0.64786604,0.65335614,0.6588291,0.6642841,0.66972031,0.67513691,0.68053307,0.68590799,0.69126085,0.69659084,0.70189716,0.707179,0.71243557,0.71766606,0.72286969,0.72804568,0.73319324,0.73831159,0.74339995,0.74845757,0.75348367,0.75847749,0.76343829,0.7683653,0.77325778,0.77811501,0.78293623,0.78772072,0.79246776,0.79717663,0.80184662,0.80647702,0.81106714,0.81561627,0.82012373,0.82458885,0.82901093,0.83338932,0.83772336,0.84201238,0.84625575,0.85045281,0.85460293,0.8587055,0.86275987,0.86676546,0.87072163,0.87462781,0.8784834,0.88228781,0.88604048,0.88974082,0.89338829,0.89698234,0.90052241,0.90400798,0.90743851,0.91081349,0.91413241,0.91739477,0.92060007,0.92374783,0.92683757,0.92986882,0.93284114,0.93575406,0.93860715,0.94139997,0.94413211,0.94680315,0.94941269,0.95196032,0.95444568,0.95686838,0.95922805,0.96152434,0.9637569,0.9659254,0.9680295,0.97006889,0.97204326,0.97395231,0.97579575,0.97757331,0.97928471,0.98092969,0.98250802,0.98401944,0.98546374,0.98684068,0.98815007,0.98939171,0.9905654,0.99167097,0.99270826,0.99367711,0.99457736,0.99540889,0.99617157,0.99686528,0.99748992,0.99804539,0.99853161,0.99894851,0.99929602,0.99957409,0.99978268,0.99992176,0.99999131,0.99999131,0.99992176,0.99978268,0.99957409,0.99929602,0.99894851,0.99853161,0.99804539,0.99748992,0.99686528,0.99617157,0.99540889,0.99457736,0.99367711,0.99270826,0.99167097,0.9905654,0.98939171,0.98815007,0.98684068,0.98546374,0.98401944,0.98250802,0.98092969,0.97928471,0.97757331,0.97579575,0.97395231,0.97204326,0.97006889,0.9680295,0.9659254,0.9637569,0.96152434,0.95922805,0.95686838,0.95444568,0.95196032,0.94941269,0.94680315,0.94413211,0.94139997,0.93860715,0.93575406,0.93284114,0.92986882,0.92683757,0.92374783,0.92060007,0.91739477,0.91413241,0.91081349,0.90743851,0.90400798,0.90052241,0.89698234,0.89338829,0.88974082,0.88604048,0.88228781,0.8784834,0.87462781,0.87072163,0.86676546,0.86275987,0.8587055,0.85460293,0.85045281,0.84625575,0.84201238,0.83772336,0.83338932,0.82901093,0.82458885,0.82012373,0.81561627,0.81106714,0.80647702,0.80184662,0.79717663,0.79246776,0.78772072,0.78293623,0.77811501,0.77325778,0.7683653,0.76343829,0.75847749,0.75348367,0.74845757,0.74339995,0.73831159,0.73319324,0.72804568,0.72286969,0.71766606,0.71243557,0.707179,0.70189716,0.69659084,0.69126085,0.68590799,0.68053307,0.67513691,0.66972031,0.6642841,0.6588291,0.65335614,0.64786604,0.64235963,0.63683774,0.63130122,0.62575089,0.6201876,0.61461218,0.60902548,0.60342835,0.59782163,0.59220616,0.58658281,0.58095241,0.57531582,0.56967389,0.56402747,0.55837742,0.55272459,0.54706984,0.54141402,0.53575799,0.5301026,0.5244487,0.51879715,0.51314881,0.50750453,0.50186517,0.49623157,0.49060458,0.48498506,0.47937386,0.47377183,0.46817981,0.46259865,0.45702919,0.45147227,0.44592874,0.44039943,0.43488518,0.42938682,0.42390518,0.4184411,0.41299539,0.40756889,0.4021624,0.39677676,0.39141277,0.38607124,0.38075299,0.37545881,0.37018951,0.36494588,0.35972872,0.35453881,0.34937694,0.34424389,0.33914043,0.33406735,0.32902539,0.32401534,0.31903793,0.31409394,0.3091841,0.30430915,0.29946984,0.29466689,0.28990103,0.28517299,0.28048347,0.27583319,0.27122284,0.26665313,0.26212475,0.25763837,0.25319469,0.24879437,0.24443807,0.24012646,0.23586019,0.2316399,0.22746622,0.2233398,0.21926125,0.2152312,0.21125024,0.20731899,0.20343803,0.19960796,0.19582935,0.19210278,0.1884288,0.18480797,0.18124085,0.17772796,0.17426984,0.17086702,0.16752001,0.16422931,0.16099542,0.15781883,0.15470002,0.15163946,0.14863762,0.14569495,0.14281189,0.13998888,0.13722635,0.13452471,0.13188437,0.12930573,0.12678919,0.12433512,0.12194389,0.11961586,0.11735139,0.11515082,0.11301448,0.1109427,0.10893578,0.10699403,0.10511775,0.10330722,0.10156271,0.09988448,0.0982728,0.09672789,0.09525001,0.09383936,0.09249617,0.09122063,0.09001294,0.08887329,0.08780184,0.08679875,0.08586418,0.08499828,0.08420116,0.08347295,0.08281376,0.0822237,0.08170284,0.08125127,0.08086906,0.08055626,0.08031292,0.08013909,0.08003477,0.08};mouse_blocker *blocker = mouse_blocker_instance(160,512,mic_num,window,128,mouse_doa_process_block,doa_inst);mouse_DoaHandle *handle = (mouse_DoaHandle*)malloc(sizeof(mouse_DoaHandle));handle->blocker_ = blocker;handle->doa_inst_ = doa_inst;handle->in_buf_ = (float**)malloc(sizeof(float*) * mic_num);handle->mic_num_ = mic_num;for (int i = 0; i < mic_num; i++) {handle->in_buf_[i] = (float*)malloc(sizeof(float) * 160);}return (void*)handle;
}void api_destroy_doa_handle(void *handle) {mouse_DoaHandle *handle_ = (mouse_DoaHandle *)handle;mouse_doa_destroy(handle_->doa_inst_);mouse_blocker_destroy(handle_->blocker_);for (int i = 0; i < handle_->mic_num_; i++) {free(handle_->in_buf_[i]);}free(handle_->in_buf_);free(handle_);
}int api_start_doa(void *handle) {return 0;
}int api_process_doa(void *handle, float *input_data, int chunk_size) {mouse_DoaHandle *handle_ = (mouse_DoaHandle *)handle;for (int j = 0; j < chunk_size; j++) {for (int i = 0; i < handle_->mic_num_; i++) {handle_->in_buf_[i][j] = mouse_FloatS16ToFloat(input_data[j*handle_->mic_num_ + i]);}}int angle = mouse_blocker_process_chunk(handle_->blocker_,handle_->in_buf_,chunk_size,handle_->mic_num_);return angle;
}
int api_end_doa(void *handle) {return 0;
}
(3)doa-blocker.c
/**@author : aflyingwolf*@date : 2025.3.20*@file : doa-blocker.c* */#include "doa-blocker.h"
#include "doa-impl.h"#include <string.h>
#include <stdlib.h>
#include <assert.h>// Pointwise multiplies each channel of |frames| with |window|. Results are
// stored in |frames|.
static void mouse_ApplyWindow(const float* window,size_t num_frames,int num_channels,float* const* frames) {for (int i = 0; i < num_channels; ++i) {for (size_t j = 0; j < num_frames; ++j) {frames[i][j] = frames[i][j] * window[j];}}
}static size_t mouse_gcd(size_t a, size_t b) {size_t tmp;while (b) {tmp = a;a = b;b = tmp % b;}return a;
}mouse_blocker *mouse_blocker_instance(size_t chunk_size,size_t block_size,int num_input_channels,const float* window,size_t shift_amount,mouse_CallbackDoa callback,mouse_doa *doa_inst) {mouse_blocker *inst = (mouse_blocker*)malloc(sizeof(mouse_blocker));inst->chunk_size_ = chunk_size;inst->block_size_ = block_size;inst->num_input_channels_ = num_input_channels;inst->initial_delay_ = block_size - mouse_gcd(chunk_size, shift_amount);inst->frame_offset_ = 0;inst->input_buffer_ = (RingBuffer**)malloc(sizeof(RingBuffer*) * num_input_channels);for (int i = 0; i < num_input_channels; i++) {inst->input_buffer_[i] = WebRtc_CreateBuffer(inst->chunk_size_ + inst->initial_delay_, sizeof(float));}inst->input_block_ = (float**)malloc(sizeof(float*) * num_input_channels);for (int i = 0; i < num_input_channels; i++) {inst->input_block_[i] = (float*)malloc(sizeof(float) * block_size);}inst->window_ = (float*)malloc(sizeof(float) * block_size);inst->shift_amount_ = shift_amount;memcpy(inst->window_, window, block_size * sizeof(float));for (int i = 0; i < num_input_channels; i++) {int moved = WebRtc_MoveReadPtr(inst->input_buffer_[i], -inst->initial_delay_);assert(moved == -inst->initial_delay_);}inst->fft_inst_ = mouse_fft_instance(inst->block_size_);inst->fft_input_block_ = (mouse_complex_f**)malloc(sizeof(mouse_complex_f*) * num_input_channels);for (int i = 0; i < num_input_channels; i++) {inst->fft_input_block_[i] = (mouse_complex_f*)malloc(sizeof(mouse_complex_f) * inst->fft_inst_->complex_length_);}inst->callback_ = callback;inst->doa_inst_ = doa_inst;return inst;
}// destroy a blocker.
void mouse_blocker_destroy(mouse_blocker *inst) {for (int i = 0; i < inst->num_input_channels_; i++) {WebRtc_FreeBuffer(inst->input_buffer_[i]);}free(inst->input_buffer_);for (int i = 0; i < inst->num_input_channels_; i++) {free(inst->input_block_[i]);}free(inst->input_block_);free(inst->window_);mouse_fft_destroy(inst->fft_inst_);for (int i = 0; i < inst->num_input_channels_; i++) {free(inst->fft_input_block_[i]);}free(inst->fft_input_block_);free(inst);
}//process a chunk data.
int mouse_blocker_process_chunk(mouse_blocker *inst,float** input,size_t chunk_size,int num_input_channels) {for (size_t i = 0; i < num_input_channels; ++i) {WebRtc_WriteBuffer(inst->input_buffer_[i], input[i], chunk_size);}size_t first_frame_in_block = inst->frame_offset_;// Loop through blocks.while (first_frame_in_block < inst->chunk_size_) {for (int i = 0; i < inst->num_input_channels_; i++) {WebRtc_ReadBuffer(inst->input_buffer_[i], NULL, inst->input_block_[i], inst->block_size_);WebRtc_MoveReadPtr(inst->input_buffer_[i], -(inst->block_size_ - inst->shift_amount_));}mouse_ApplyWindow(inst->window_, inst->block_size_, inst->num_input_channels_, inst->input_block_);for (int i = 0; i < inst->num_input_channels_; i++) {mouse_fft_forward(inst->fft_inst_, inst->input_block_[i], inst->fft_input_block_[i]);}inst->doa_angle_ = inst->callback_(inst->doa_inst_, inst->fft_input_block_, inst->num_input_channels_, kNumFreqBins);first_frame_in_block += inst->shift_amount_;}// Calculate new starting frames.inst->frame_offset_ = first_frame_in_block - inst->chunk_size_;return inst->doa_angle_;
}
(4)doa-impl.c
/**@author : aflyingwolf*@date : 2025.3.20*@file : doa-impl.c* */
#include "doa-impl.h"
#include <stdlib.h>
#include <math.h>mouse_doa *mouse_doa_instance(int sample_rate_hz,int mic_num,float radius) {mouse_doa *inst = (mouse_doa*)malloc(sizeof(mouse_doa));inst->sample_rate_hz_ = sample_rate_hz;inst->num_input_channels_ = mic_num;inst->radius_ = radius;inst->step_ = 30;inst->num_direction_ = 360.0 / inst->step_;inst->mic_angle_ = (float*)malloc(sizeof(float)*inst->num_input_channels_);inst->mic_angle_[0] = 300;inst->mic_angle_[1] = 0;inst->mic_angle_[2] = 60;inst->mic_angle_[3] = 120;inst->mic_angle_[4] = 180;inst->mic_angle_[5] = 240;inst->smooth_len_ = 20;inst->P_ = (float**)malloc(sizeof(float*) * inst->num_direction_);for (int i = 0; i < inst->num_direction_; i++) {inst->P_[i] = (float*)malloc(sizeof(float) * inst->smooth_len_);for (int j = 0; j < inst->smooth_len_; j++) {inst->P_[i][j] = 0.0;}}inst->H_ = (mouse_complex_matrix_f*)malloc(sizeof(mouse_complex_matrix_f) * inst->num_direction_);for (int i = 0; i < inst->num_direction_; i++) {mouse_complex_matrix_init(&(inst->H_[i]), inst->num_input_channels_, kNumFreqBins);float direction = i * inst->step_;mouse_complex_f **mat_els = inst->H_[i].elements_;for (int j = 0; j < inst->num_input_channels_; j++) {float tao = inst->radius_ * sin(90.0 * M_PI / 180.0) * cos((direction - inst->mic_angle_[j]) * M_PI / 180.0) / 340.0;for (int k = 0; k < kNumFreqBins; k++) {float omega = 2.0 * M_PI * k * inst->sample_rate_hz_ / kFftSize;float x = -1.0 * omega * tao;mat_els[j][k].real_ = cos(x);mat_els[j][k].imag_ = sin(x);}}}inst->data_abs_ = (float**)malloc(sizeof(float*) * inst->num_input_channels_);inst->mid_res_ = (mouse_complex_f**)malloc(sizeof(mouse_complex_f*) * inst->num_input_channels_);for (int k = 0; k < inst->num_input_channels_; k++) {inst->data_abs_[k] = (float*)malloc(sizeof(float) * kNumFreqBins);inst->mid_res_[k] = (mouse_complex_f*)malloc(sizeof(mouse_complex_f) * kNumFreqBins);}inst->frame_id_ = 0;inst->start_ = 0;inst->end_ = 128;return inst;
}void mouse_doa_destroy(mouse_doa *inst) {free(inst->mic_angle_);for (int i = 0; i < inst->num_direction_; i++) {free(inst->P_[i]);}free(inst->P_);for (int i = 0; i < inst->num_direction_; i++) {mouse_complex_matrix_free(&(inst->H_[i]));}free(inst->H_);for (int i = 0; i < inst->num_input_channels_; i++) {free(inst->data_abs_[i]);free(inst->mid_res_[i]);}free(inst->data_abs_);free(inst->mid_res_);free(inst);
}void mouse_doa_reset(mouse_doa *inst) {return;
}int mouse_doa_process_block(mouse_doa *inst,mouse_complex_f** input,int num_input_channels,size_t num_freq_bins) {for (int j = 0; j < inst->num_input_channels_; j++) {for (int k = inst->start_; k < inst->end_; k++) {inst->data_abs_[j][k] = sqrt(input[j][k].real_ * input[j][k].real_ + input[j][k].imag_ * input[j][k].imag_);}}//return 45;int max_id = 0;float max_val = 0.0;for (int i = 0; i < inst->num_direction_; i++) {mouse_complex_f **mat_els = inst->H_[i].elements_;for (int j = 0; j < inst->num_input_channels_; j++) {for (int k = inst->start_; k < inst->end_; k++) {inst->mid_res_[j][k] = mouse_complex_mul(mat_els[j][k], input[j][k]);inst->mid_res_[j][k].real_ /= inst->data_abs_[j][k];inst->mid_res_[j][k].imag_ /= inst->data_abs_[j][k];}}for (int j = 1; j < inst->num_input_channels_; j++) {for (int k = inst->start_; k < inst->end_; k++) {inst->mid_res_[0][k] = mouse_complex_add(inst->mid_res_[0][k], inst->mid_res_[j][k]);}}//continue;for (int k = inst->start_; k < inst->end_; k++) {inst->mid_res_[0][k].real_ /= inst->num_input_channels_;inst->mid_res_[0][k].imag_ /= inst->num_input_channels_;}float energy = 0.0;for (int k = inst->start_; k < inst->end_; k++) {energy += inst->mid_res_[0][k].real_ * inst->mid_res_[0][k].real_ + inst->mid_res_[0][k].imag_ * inst->mid_res_[0][k].imag_;}//energy *= energy;inst->frame_id_ = inst->frame_id_ % inst->smooth_len_;inst->P_[i][inst->frame_id_] = energy;float smooth_val = 0.0;for (int k = 0; k < inst->smooth_len_; k++) {smooth_val += inst->P_[i][k];}if (i == 0) {max_id = i;max_val = smooth_val;} else {if (smooth_val > max_val) {max_id = i;max_val = smooth_val;}}}inst->frame_id_ += 1; int angle = max_id * inst->step_; return angle;
}
(5)doa-complex.c
/**@author : aflyingwolf*@date : 2025.3.20*@file : doa-complex.c* */#include "doa-complex.h"mouse_complex_f mouse_complex_mul(mouse_complex_f a, mouse_complex_f b) {mouse_complex_f c;c.real_ = a.real_ * b.real_ - a.imag_ * b.imag_;c.imag_ = a.real_ * b.imag_ + a.imag_ * b.real_;return c;
}mouse_complex_f mouse_complex_add(mouse_complex_f a, mouse_complex_f b) {mouse_complex_f c;c.real_ = a.real_ + b.real_;c.imag_ = a.imag_ + b.imag_;return c;
}mouse_complex_f mouse_complex_conj(mouse_complex_f a) {mouse_complex_f ret;ret.real_ = a.real_;ret.imag_ = -1 * a.imag_;return ret;
}
(6)doa-matrix.c
/**@author : aflyingwolf*@date : 2025.3.20*@file : doa-matrix.c* */#include "doa-matrix.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>void mouse_complex_matrix_init(mouse_complex_matrix_f *mat, size_t row, size_t col) {mat->num_rows_ = row;mat->num_cols_ = col;mat->data_ = (mouse_complex_f*)malloc(row * col * sizeof(mouse_complex_f));memset(mat->data_, 0, row * col * sizeof(mouse_complex_f));mat->elements_ = (mouse_complex_f**)malloc(row * sizeof(mouse_complex_f*));for(int i = 0; i < row; i++) {mat->elements_[i] = &mat->data_[i * col];}
}void mouse_complex_matrix_free(mouse_complex_matrix_f *mat) {if (mat->data_) {free(mat->data_);free(mat->elements_);}
}
void mouse_complex_matrix_reset(mouse_complex_matrix_f *mat, size_t row, size_t col) {if (mat->data_ != NULL) {free(mat->data_);free(mat->elements_);}mouse_complex_matrix_init(mat, row, col);
}void mouse_complex_matrix_copy_from_column(mouse_complex_matrix_f *mat, size_t column_index, mouse_complex_f **src) {for (int i = 0; i < mat->num_cols_; ++i) {mat->data_[i] = src[i][column_index];}
}void mouse_complex_matrix_copy(mouse_complex_matrix_f *mat, mouse_complex_matrix_f *src) {mouse_complex_matrix_reset(mat, src->num_rows_, src->num_cols_);for (int i = 0; i < mat->num_cols_* mat->num_rows_; ++i) {mat->data_[i] = src->data_[i];}
}float mouse_SumAbs(mouse_complex_matrix_f *mat) {float sum_abs = 0.0;mouse_complex_f **mat_els = mat->elements_;for (int i = 0; i < mat->num_rows_; ++i) {for (int j = 0; j < mat->num_cols_; ++j) {sum_abs += sqrt(mat_els[i][j].real_ * mat_els[i][j].real_ + mat_els[i][j].imag_ * mat_els[i][j].imag_);}}return sum_abs;
}float mouse_SumSquares(mouse_complex_matrix_f *mat) {float sum_squares = 0.f;for (int i = 0; i < mat->num_rows_ * mat->num_cols_; i++) {sum_squares += mat->data_[i].real_ * mat->data_[i].real_ + mat->data_[i].imag_ * mat->data_[i].imag_;}return sum_squares;
}mouse_complex_f mouse_ConjugateDotProduct(mouse_complex_matrix_f *lhs, mouse_complex_matrix_f *rhs) {assert(lhs->num_rows_ == 1);assert(rhs->num_rows_ == 1);assert(rhs->num_cols_ == lhs->num_cols_);mouse_complex_f result;result.real_ = 0.0;result.imag_ = 0.0;mouse_complex_f **lhs_elements = lhs->elements_;mouse_complex_f **rhs_elements = rhs->elements_;for (int i = 0; i < lhs->num_cols_; i++) {mouse_complex_f tmp = mouse_complex_mul(mouse_complex_conj(lhs_elements[0][i]), rhs_elements[0][i]);result = mouse_complex_add(result, tmp);}return result;
}float mouse_Norm(mouse_complex_matrix_f *mat, mouse_complex_matrix_f *norm_mat) {mouse_complex_f first_product, second_product;first_product.real_ = 0.0;first_product.imag_ = 0.0;second_product.real_ = 0.0;second_product.imag_ = 0.0;mouse_complex_f **mat_els = mat->elements_;mouse_complex_f **norm_mat_els = norm_mat->elements_;for (int i = 0; i < norm_mat->num_cols_; i++) {for (int j = 0; j < norm_mat->num_cols_; j++) {first_product = mouse_complex_add(first_product, mouse_complex_mul(mouse_complex_conj(norm_mat_els[0][j]), mat_els[j][i]));}second_product = mouse_complex_add(second_product, mouse_complex_mul(first_product, norm_mat_els[0][i]));first_product.real_ = 0.0;first_product.imag_ = 0.0;}float ret = second_product.real_;if (ret < 0.f) {ret = 0.f;}return ret;
}void mouse_complex_matrix_scale(mouse_complex_matrix_f *mat, mouse_complex_f scale) {int size = mat->num_cols_ * mat->num_rows_;for (int i = 0; i < size; i++) {mat->data_[i] = mouse_complex_mul(mat->data_[i], scale);}
}void mouse_TransposedConjugatedProduct(mouse_complex_matrix_f *in, mouse_complex_matrix_f *out) {assert(in->num_rows_ == 1);assert(out->num_rows_ == in->num_cols_);assert(out->num_cols_ == in->num_cols_);mouse_complex_f* in_elements = in->elements_[0];mouse_complex_f** out_elements = out->elements_;for (int i = 0; i < out->num_rows_; ++i) {for (int j = 0; j < out->num_cols_; ++j) {out_elements[i][j] = mouse_complex_mul(in_elements[i], mouse_complex_conj(in_elements[j]));}}
}mouse_complex_f mouse_Trace(mouse_complex_matrix_f *mat) {assert(mat->num_rows_ == mat->num_cols_);mouse_complex_f trace;trace.real_ = 0.0;trace.imag_ = 0.0;for (int i = 0; i < mat->num_rows_; ++i) {trace = mouse_complex_add(trace, mat->elements_[i][i]);}return trace;
}void mouse_Transpose(mouse_complex_matrix_f *in, mouse_complex_matrix_f *out) {for (int i = 0; i < out->num_rows_; ++i) {for (int j = 0; j < out->num_cols_; ++j) {out->elements_[i][j] = in->elements_[j][i];}}
}void mouse_PointwiseConjugate(mouse_complex_matrix_f *mat) {for (int i = 0; i < mat->num_rows_; ++i) {for (int j = 0; j < mat->num_cols_; ++j) {mat->elements_[i][j].imag_ *= -1;}}
}void mouse_ComplexMatrixMultiply(mouse_complex_matrix_f *interf_cov_vector_transposed, mouse_complex_matrix_f *interf_cov_vector, mouse_complex_matrix_f *mat) {for (int row = 0; row < interf_cov_vector_transposed->num_rows_; ++row) {for (int col = 0; col < interf_cov_vector->num_cols_; ++col) {mouse_complex_f cur_element;cur_element.real_ = 0.0;cur_element.imag_ = 0.0;for (int i = 0; i < interf_cov_vector->num_rows_; ++i) {cur_element = mouse_complex_add(cur_element, mouse_complex_mul(interf_cov_vector_transposed->elements_[row][i], interf_cov_vector->elements_[i][col]));}mat->elements_[row][col] = cur_element;}}
}void mouse_ComplexMatrixAdd(mouse_complex_matrix_f *src1, mouse_complex_matrix_f *src2, mouse_complex_matrix_f *mat) {for (size_t i = 0; i < src1->num_cols_ * src1->num_rows_; ++i) {mat->data_[i] = mouse_complex_add(src1->data_[i], src2->data_[i]);}
}
三、系统部署方式
3.1 运行LED控制进程
3.2 运行数据获取及算法执行进行
四、总结
以上基本上完整记录了整个系统的代码及部署方式。由于篇幅限制,完整代码可以进入https://t.zsxq.com/qgmoN 获取。注意部署时先启动LED控制程序。