sounddevice和raspberry pi 4导致零星输入溢出

2024-04-29 20:38:33 发布

您现在位置:Python中文网/ 问答频道 /正文

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

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

  1. 负载实验设置
  2. 在NumPy数组中配置输出缓冲区(纯正弦波加包络)和输入缓冲区(零数组)
  3. 设置sounddevice和start stream以输出输出缓冲区块并记录输入
  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

Tags: selfconfigoutputframestimebuffernpcounter