PyAudio欠载 - 每个缓冲区的小块大小/帧

时间:2017-07-12 15:31:57

标签: python pyaudio portaudio

我希望使用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)

0 个答案:

没有答案