0

我想就如何进一步调查问题的原因征求意见。

我正在开发一个音频分析器 python 应用程序。预期的硬件由具有 64 位操作系统的树莓派 4、8 GB 内存、32 GB sd 卡和外部声卡 (hifi-berry adc+dac) 组成。有趣的代码位于 Capture.py 模块中,它将执行以下任务

  1. 加载实验设置
  2. 在 NumPy 数组中配置 output_buffer(纯正弦波加包络)和 input_buffer(零数组)
  3. 设置 sounddevice 并启动流以输出 output_buffer 块并记录输入
  4. 一些用于测试的 FFT 计算(将移至另一个模块)

我面临一些偶尔的回调状态:(输入溢出,输出下溢),主要是在捕获时间很短(小于两秒)时。大多数情况下,代码按预期工作,但有时这种行为开始发生,就好像回调正在使用大量 CPU 资源一样。有趣的是,如果我增加实验持续时间,超过两到三秒(这意味着更大的缓冲区和更多的内存使用),问题似乎就消失了。这似乎很奇怪(从我的角度来看)。由于它们是预先分配和预先计算的,缓冲区大小对回调函数应该是透明的,对吧?到目前为止,我已经尝试过:

  • 将应用程序的友好度降低到 -10;
  • 降低采样率(从 192000 Hz 到 48000 Hz 和 44100 Hz);
  • 重新启动系统

这些行动似乎都没有对问题产生任何影响。

我的 Capture.py 模块代码供参考。捕获实验是通过start方法启动的,但是问题发生在回调方法上。

from .Config import Config

import sounddevice as sd
import numpy as np

class Capture:
    def __init__(self, config):
        self.paa_config = config
        self.capturing = False
        pass
    def start(self):
        self.getCaptureTime()
        
        self.time = np.arange(self.total_frames)*1/self.paa_config.SamplingFrequency
        self.computeTones()
        
        self.frame_counter = 0
        print("Start capture")
        self.capturing = True
        with sd.Stream(channels=2, device=self.paa_config.SoundDevice, samplerate=self.paa_config.SamplingFrequency, dtype='float32', callback=self.callback, finished_callback=self.finished_callback):
            sd.sleep(int(self.capture_time)*1100)
            while self.capturing:
                pass
        # capture ended... perform some testing... to be moved to Process.py module
        start_index = self.smooth_frames+self.latency_frames
        end_index = self.process_frames + start_index
        print("input_buffer shape is ", self.input_buffer.shape)
        self.process_data = self.input_buffer[start_index:end_index]
        self.fft_signal = abs(np.fft.rfft(self.process_data, axis=0)/self.process_frames*2)
        self.fft_signal[0,:] /= 2
        self.fft_signal = 20 * np.log10(self.fft_signal)
        print("fft_signal shape is", self.fft_signal.shape)
        print("self.process_data.size is: ", self.process_data.size)
        self.fft_frequency = np.fft.rfftfreq(int(self.process_data.size/2), d=1/self.paa_config.SamplingFrequency)
        print("fft_frequency shape is", self.fft_frequency.shape)
        pass
    
    def callback(self, indata, outdata, frames, time, status):
        #print("new chunk capture, ", frames, " frames to process")
        if status:
            print("At frame ", self.frame_counter, "of ", self.total_frames, "frames")
            print(status)
            #raise sd.CallbackStop
        if (self.frame_counter+frames) <= self.total_frames:
            self.input_buffer[self.frame_counter:self.frame_counter+frames, :] = indata
            outdata[:] = self.output_buffer[self.frame_counter:self.frame_counter+frames,:]
            self.frame_counter += frames
        else:
            aframes = self.total_frames-self.frame_counter
            self.input_buffer[self.frame_counter:self.total_frames, :] = indata[aframes,:]
            outdata[0:aframes,:] = self.output_buffer[self.frame_counter:self.total_frames,:]
            self.frame_counter += aframes
            raise sd.CallbackStop
        pass
    
    def finished_callback(self):
        self.capturing = False
        print("End capture")
        pass
    
    def getCaptureTime(self):
        self.smooth_frames = int(self.paa_config.SmoothTime * self.paa_config.SamplingFrequency)
        self.process_frames = int((1/self.paa_config.Resolution.resolution)*self.paa_config.Averaging.averaging * self.paa_config.SamplingFrequency)
        self.latency_frames = int(self.paa_config.LatencyExtraTime * self.paa_config.SamplingFrequency)
        self.total_frames = 2*self.smooth_frames+self.process_frames+self.latency_frames
        
        self.capture_time = self.total_frames/self.paa_config.SamplingFrequency
        return self.capture_time
        pass
    
    def smoothEnvelope(self):
        t = self.time
        smooth_time = self.paa_config.SmoothTime
        start_time = t[0]+smooth_time
        end_time = t[-1]-smooth_time
        ret = np.ones(t.shape)
        x_smooth = ((t[t<start_time]-t[0])/smooth_time)
        ret[t<start_time] = ( 6*(x_smooth**5) - 15*(x_smooth**4) + 10*(x_smooth**3) )
        x_smooth = ((t[-1]-t[t>end_time])/smooth_time)
        ret[t>end_time] = ( 6*(x_smooth**5) - 15*(x_smooth**4) +10*(x_smooth**3) )
        return ret
        pass
    
    def computeTones(self):
        self.output_buffer = np.zeros((self.total_frames, 2))
        self.input_buffer = np.zeros((self.total_frames, 2))
        self.envelope = self.smoothEnvelope()
        for it_g in range(len(self.paa_config.Generators)):            
            for t in self.paa_config.Generators[it_g].Tones:
                if t.Enabled:
                    if t.Wave == Config.Enums.Wave.Sine:
                        self.output_buffer[:,it_g] +=  t.Amplitude*np.sin(2*np.pi*t.Frequency*self.time)
                    elif t.Wave == Config.Enums.Wave.Square:
                        #self.output_buffer[:,it_g] +=  t.Amplitude*np.sign(np.sin(2*np.pi*t.Frequency*self.time))
                        #print(((t.Frequency*self.time)%1)>0.0, flush=True)
                        self.output_buffer[((t.Frequency*self.time)%1)>0.5,it_g] += -t.Amplitude
                        self.output_buffer[((t.Frequency*self.time)%1)<0.5,it_g] += t.Amplitude
                    elif t.Wave == Config.Enums.Wave.Triangle:
                        # TODO
                        #self.output_buffer[:,it_g] +=  t.Amplitude*np.sin(2*np.pi*t.Frequency*self.time)
                        self.output_buffer[:,it_g] += 4*(np.abs(((t.Frequency*self.time-0.25)%1)-0.5)-0.25)*t.Amplitude
                    elif t.Wave == Config.Enums.Wave.Sawthoot:
                        # TODO
                        #self.output_buffer[:,it_g] +=  t.Amplitude*np.sin(2*np.pi*t.Frequency*self.time)
                        self.output_buffer[:,it_g] += -(((t.Frequency*self.time)%1)-0.5)*2*t.Amplitude
                    elif t.Wave == Config.Enums.Wave.Pulse:
                        # TODO
                        #self.output_buffer[:,it_g] +=  t.Amplitude*np.sin(2*np.pi*t.Frequency*self.time)
                        self.output_buffer[((t.Frequency*self.time)%1)>0.225,it_g] += t.Amplitude
                        self.output_buffer[((t.Frequency*self.time)%1)>0.275,it_g] += -t.Amplitude
                        self.output_buffer[((t.Frequency*self.time)%1)>0.725,it_g] += -t.Amplitude
                        self.output_buffer[((t.Frequency*self.time)%1)>0.775,it_g] += t.Amplitude
                    else:
                        raise ValueError("Invalid value for Wave Enum")
                    pass
                pass
            pass
            self.output_buffer[:,it_g] *= self.envelope
        pass
        
    pass
4

0 回答 0