从PyAudio提取Numpy数组时出现噪音与失真问题
问题描述
我想用支持回环设备的PyAudio补丁录制电脑音频输出,实现实时处理与响应。目前录制音频到文件时音质正常,但转换为Numpy数组后播放却满是噪音,信号严重失真。
改用麦克风输入简化代码后问题依旧,复现代码如下:
import numpy as np import pyaudiowpatch as pyaudio import wave import sounddevice as sd from matplotlib import pyplot as plt FORMAT = pyaudio.paInt16 CHANNELS = 2 RATE = 44100 CHUNK = 1048 RECORD_SECONDS = 5 WAVE_OUTPUT_FILENAME = "file.wav" p = pyaudio.PyAudio() stream = p.open(format=FORMAT, channels=CHANNELS, rate=RATE, input=True, frames_per_buffer=CHUNK) print("recording...") frames = [] fulldata = np.empty(RATE*RECORD_SECONDS*CHANNELS) for i in range(0, int(RATE / CHUNK * RECORD_SECONDS)): print(i) data = stream.read(CHUNK) numpydata = np.frombuffer(data, dtype='int16') fulldata[i*CHUNK*CHANNELS:(i+1)*CHUNK*CHANNELS] = numpydata frames.append(data) print("finished recording") channel0 = fulldata[0::CHANNELS] print('playing') sd.play(channel0, samplerate=RATE, blocking=True) # stop Recording stream.stop_stream() stream.close() p.terminate() waveFile = wave.open(WAVE_OUTPUT_FILENAME, 'wb') waveFile.setnchannels(CHANNELS) waveFile.setsampwidth(p.get_sample_size(FORMAT)) waveFile.setframerate(RATE) waveFile.writeframes(b''.join(frames)) waveFile.close()
我试过相关方法,录制的文件音质没问题,但转Numpy数组后播放就异常。已经按通道交替排列提取了单通道,先存文件再用librosa加载播放是正常的。注意到录制采样率是48000,但加载WAV时是22050,不确定是否有关。也怀疑过延迟,但不知道怎么设置。想问下有没有其他转换Numpy数组的方法,或者我漏了什么关键点?
解决方案
问题核心出在数据类型不匹配和采样率不一致两个关键点:
数据类型转换错误
你用np.empty创建的fulldata默认是float64类型,而从音频流读取的是int16格式的原始音频数据(范围为[-32768, 32767])。直接将int16数值赋值到浮点数组后,sounddevice会把这些数值当成浮点音频样本(正常范围是[-1.0, 1.0]),远超范围的数值会导致严重削波,最终产生噪音。采样率不一致的潜在问题
你代码里配置的录制采样率是44100,但实际录制是48000、加载文件时是22050,这种参数不匹配会导致播放速度、音调异常,甚至加剧失真。必须确保录制、播放环节的采样率完全统一。
修复步骤:
- 要么将存储数组初始化为
int16类型,和原始音频格式匹配;要么将int16数据归一化到[-1.0, 1.0]的浮点范围(推荐,sounddevice对浮点样本支持更稳定) - 核对并统一所有环节的采样率参数
修改后的关键代码片段:
# 方案1:用int16类型存储数组,和原始音频格式匹配 fulldata = np.empty(RATE*RECORD_SECONDS*CHANNELS, dtype=np.int16) # 方案2:归一化到浮点范围(更推荐) fulldata = np.empty(RATE*RECORD_SECONDS*CHANNELS, dtype=np.float32) # 读取后转换并归一化 numpydata = np.frombuffer(data, dtype='int16').astype(np.float32) / 32767.0
另外,提取单通道播放时,务必保证sd.play的samplerate参数和实际录制的采样率一致,比如实际录制是48000,就不能用代码里写的44100。
内容的提问来源于stack exchange,提问作者FerdyG
相关产品推荐
相关产品推荐

