我想就如何进一步调查问题的原因征求意见。
我正在开发一个音频分析器 python 应用程序。预期的硬件由具有 64 位操作系统的树莓派 4、8 GB 内存、32 GB sd 卡和外部声卡 (hifi-berry adc+dac) 组成。有趣的代码位于 Capture.py 模块中,它将执行以下任务
- 加载实验设置
- 在 NumPy 数组中配置 output_buffer(纯正弦波加包络)和 input_buffer(零数组)
- 设置 sounddevice 并启动流以输出 output_buffer 块并记录输入
- 一些用于测试的 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