我希望使用PyAudio软件包以合理的低延迟从外部设备传输音频。 我正在使用Debian 8的Python 3(3.4.2)和PyAudio(0.2.8)。
我的目标是播放:16,000 Hz,1通道,16位PCM,序列为20 ms(每块320个样本)。尝试此操作会导致欠载,并显示错误消息:
ALSA lib pcm.c:7843:(snd_pcm_recover) underrun occurred
看起来我能做的最好没有欠载是大约100毫秒(每个块1600个样本)。
我想知道是否有人愿意分享他们对PyAudio / PortAudio表现的期望?这是预期的行为还是被认为是错误的?
我在下面提供了一些测试代码。看起来当尝试每个样本使用320帧时,回调每隔2毫秒触发一次,我觉得这是可疑的。当指定块大小为1600时,它有点零星但是回调延迟了大约0.100毫秒。
#! /usr/bin/env python3
import numpy
import pyaudio
import operator
import struct
import time
import time
class AudioSignal(object):
def __init__(self,
sampling_freq,
samples_per_message):
self.bytes_per_msg = samples_per_message * 2
self.init_signal(sampling_freq)
def init_signal(self, sampling_freq):
freqs = [500, 2000, 4000, 7000, 10000, 11000]
audio_signal = [0] * sampling_freq
t = numpy.arange(sampling_freq) / float(sampling_freq)
for freq in freqs:
tone = numpy.cos(t*2*numpy.pi*freq)
audio_signal = list(map(operator.add, audio_signal, tone))
scaling = 32760 / numpy.max(audio_signal)
self.signal = b""
for sample in audio_signal:
sample = sample * scaling
self.signal += struct.pack("h", int(sample))
def get_chunk(self):
# Constantly return the same data from start for now
return self.signal[0:self.bytes_per_msg]
class AudioPlayback(object):
def __init__(self, sampling_freq, frames_per_buffer, signal_source, blocking):
self.pyaudio = pyaudio.PyAudio()
self.print_pyaudio_dbg()
self.signal_source = signal_source
self.last_time = time.time()
if blocking:
cb = None
else:
cb = self._streaming_callback
self.stream = self.pyaudio.open(format=pyaudio.paInt16,
channels=1,
rate=sampling_freq,
output=True,
stream_callback=cb,
frames_per_buffer=frames_per_buffer)
def print_pyaudio_dbg(self):
default_host = self.pyaudio.get_default_host_api_info()
print("PyAudio Version: ", pyaudio.__version__)
print("Port Audio Version: ", pyaudio.get_portaudio_version())
print("Sample size: ", pyaudio.get_sample_size(pyaudio.paInt16))
print("Default Host API Info: ", default_host)
print("Is Format Supported (16 kHz, 1 Channel, 16-bit): ",
self.pyaudio.is_format_supported(rate=16000,
output_device=default_host["index"],
output_channels=1,
output_format=pyaudio.paInt16))
def _streaming_callback(self, in_data, frame_count, time_info, status):
#print("Args: in_data: {} frame_count: {} time_info: {} status: {}".format(in_data, frame_count, time_info, status))
print("Time between callbacks: {:.4f} s".format(time.time() - self.last_time))
self.last_time = time.time()
return (self.signal_source.get_chunk(), pyaudio.paContinue)
def run_blocking_loop(self, timeout):
end_time = time.time() + timeout
while time.time() < end_time:
before_write = time.time()
self.stream.write(self.signal_source.get_chunk())
print("Blocked for {:4f} s".format(time.time() - before_write))
if __name__ == "__main__":
# Sampling frequency of the audio signal, in Hz
sampling_freq = 16000
# Number of 16-bit samples to be provided at each call to PyAudio
samples_per_msg = 1600
# True to use blocking API or False for callbacks
is_blocking = False
# Number of seconds to run the test for
play_time = 3
signal = AudioSignal(sampling_freq=sampling_freq,
samples_per_message=samples_per_msg)
playback = AudioPlayback(sampling_freq=sampling_freq,
frames_per_buffer=samples_per_msg,
signal_source=signal,
blocking=is_blocking)
if is_blocking:
playback.run_blocking_loop(timeout=play_time)
else:
time.sleep(play_time)