通过环回从 PyAudio 中读取音频输出

问题描述 投票:0回答:1

我正在编写一个程序,使用

pyaudio
记录我的扬声器输出。我在树莓派上。我在使用音频插孔通过某些扬声器播放音频时构建了该程序,但最近已切换为通过 HDMI 使用显示器中的扬声器。突然,节目里一片寂静。

from pyaudio import PyAudio


p = PyAudio()

print(p.get_default_input_device_info()['index'], '\n')
print(*[p.get_device_info_by_index(i) for i in range(p.get_device_count())], sep='\n\n')

上面的代码首先输出

pyaudio
默认输入设备的索引,然后输出可用的设备。请参阅下面的结果。

案例A:

2

{'index': 0, 'structVersion': 2, 'name': 'bcm2835 Headphones: - (hw:2,0)', 'hostApi': 0, 'maxInputChannels': 0, 'maxOutputChannels': 8, 'defaultLowInputLatency': -1.0, 'defaultLowOutputLatency': 0.0016099773242630386, 'defaultHighInputLatency': -1.0, 'defaultHighOutputLatency': 0.034829931972789115, 'defaultSampleRate': 44100.0}

{'index': 1, 'structVersion': 2, 'name': 'pulse', 'hostApi': 0, 'maxInputChannels': 32, 'maxOutputChannels': 32, 'defaultLowInputLatency': 0.008684807256235827, 'defaultLowOutputLatency': 0.008684807256235827, 'defaultHighInputLatency': 0.034807256235827665, 'defaultHighOutputLatency': 0.034807256235827665, 'defaultSampleRate': 44100.0}

{'index': 2, 'structVersion': 2, 'name': 'default', 'hostApi': 0, 'maxInputChannels': 32, 'maxOutputChannels': 32, 'defaultLowInputLatency': 0.008684807256235827, 'defaultLowOutputLatency': 0.008684807256235827, 'defaultHighInputLatency': 0.034807256235827665, 'defaultHighOutputLatency': 0.034807256235827665, 'defaultSampleRate': 44100.0}

如果我然后进入终端,输入

sudo raspi-config
并将音频输出更改为耳机插孔,我会得到实际的录音,而不是静音,并收到与上述代码不同的输出。

案例B:

5

{'index': 0, 'structVersion': 2, 'name': 'vc4-hdmi-0: MAI PCM i2s-hifi-0 (hw:0,0)', 'hostApi': 0, 'maxInputChannels': 0, 'maxOutputChannels': 2, 'defaultLowInputLatency': -1.0, 'defaultLowOutputLatency': 0.005804988662131519, 'defaultHighInputLatency': -1.0, 'defaultHighOutputLatency': 0.034829931972789115, 'defaultSampleRate': 44100.0}

{'index': 1, 'structVersion': 2, 'name': 'bcm2835 Headphones: - (hw:2,0)', 'hostApi': 0, 'maxInputChannels': 0, 'maxOutputChannels': 8, 'defaultLowInputLatency': -1.0, 'defaultLowOutputLatency': 0.0016099773242630386, 'defaultHighInputLatency': -1.0, 'defaultHighOutputLatency': 0.034829931972789115, 'defaultSampleRate': 44100.0}

{'index': 2, 'structVersion': 2, 'name': 'sysdefault', 'hostApi': 0, 'maxInputChannels': 0, 'maxOutputChannels': 128, 'defaultLowInputLatency': -1.0, 'defaultLowOutputLatency': 0.005804988662131519, 'defaultHighInputLatency': -1.0, 'defaultHighOutputLatency': 0.034829931972789115, 'defaultSampleRate': 44100.0}

{'index': 3, 'structVersion': 2, 'name': 'hdmi', 'hostApi': 0, 'maxInputChannels': 0, 'maxOutputChannels': 2, 'defaultLowInputLatency': -1.0, 'defaultLowOutputLatency': 0.005804988662131519, 'defaultHighInputLatency': -1.0, 'defaultHighOutputLatency': 0.034829931972789115, 'defaultSampleRate': 44100.0}

{'index': 4, 'structVersion': 2, 'name': 'pulse', 'hostApi': 0, 'maxInputChannels': 32, 'maxOutputChannels': 32, 'defaultLowInputLatency': 0.008684807256235827, 'defaultLowOutputLatency': 0.008684807256235827, 'defaultHighInputLatency': 0.034807256235827665, 'defaultHighOutputLatency': 0.034807256235827665, 'defaultSampleRate': 44100.0}

{'index': 5, 'structVersion': 2, 'name': 'default', 'hostApi': 0, 'maxInputChannels': 32, 'maxOutputChannels': 32, 'defaultLowInputLatency': 0.008684807256235827, 'defaultLowOutputLatency': 0.008684807256235827, 'defaultHighInputLatency': 0.034807256235827665, 'defaultHighOutputLatency': 0.034807256235827665, 'defaultSampleRate': 44100.0}

您可以在案例 B 中看到我现在可以访问许多不同的设备。我尝试在情况 A 中记录所有三个可用输入,但 #0 和 #1 均失败。 #1 也记录静音,#0 返回

OSError: [Errno -9998] Invalid number of channels
。如果你仔细观察案例 A,你会发现 #0 有
['maxInputChannels'] = 0
,所以这就是原因。

我尝试创建环回设备,从声音输出中读取数据,并引入另一个输入来将音频传回。然后我会从该输入进行录制,因为它有输入通道。我已经研究过这个线程here,但唯一的解决方案是针对Windows。

我还尝试使用

pulseaudio
实用程序
pactl
创建环回设备。此链接here演示了我所尝试的内容。成功创建环回后,我无法使用
pyaudio
插入它;它没有显示在设备列表中。

有谁知道吗...

  • 如何使用
    pulseaudio
    pyaudio
    环回进行录制?
  • 在 Linux 上创建环回的另一种方法?
  • 使用
    pyaudio
    解决我的问题的替代方法?

非常感谢。

python pyaudio loopback portaudio pulseaudio
1个回答
0
投票

这个问题花了一段时间。事实证明,

pyaudio
对于录制系统音频来说几乎没用,所以我切换到
pasimple
,它具有
pyaudio
的所有优点,并且,gasp,实际上有效。说到好处,我的意思是 A) 简单,B) 没有依赖项(在 python 中)。

您将在下面找到我的

Recorder
对象。请记住,我使用的是 Raspbery Pi,因此我找到正确的输出设备进行监听的方法可能不适用于其他系统。

pasimple
效果非常好。查看文档这里
tlength
的论点值得研究。

import json
import subprocess
import wave
from threading import Thread, Event

import pasimple as pa


class Recorder(Thread):
    def __init__(self) -> None:
        super().__init__()
        
        default_sink = subprocess.check_output('pactl get-default-sink', shell = True)
        
        self.device = '{}.monitor'.format(default_sink.decode().rstrip())
        
        devices = json.loads(subprocess.check_output('pactl --format="json" list sinks', shell = True))
        
        device = [device for device in devices if device['monitor_source'] == self.device][0]
        
        specs = device['sample_specification'].split()
        
        self.audio = {}
        
        self.audio['format'] = getattr(pa, 'PA_SAMPLE_{}'.format(specs[0].upper()))
        self.audio['channels'] = int(specs[1][:-2])
        self.audio['rate'] = int(specs[2][:-2])
        
        self.audio['sample-width'] = pa.format2width(self.audio['format'])
        
        self.is_recording = Event()
        self.kill = Event()
    
    def _get_sample_length(self, seconds: int) -> int:
        return self.audio['channels'] * self.audio['sample-width'] * self.audio['rate'] * seconds
    
    def _read_audio_data(self, seconds: int) -> bytes:
        return self.stream.read(self._get_sample_length(seconds))
    
    def record_to_file(self, file: str, seconds: int) -> None:
        data = self._read_audio_data(seconds)
        
        with wave.open(file, 'wb') as f:
            f.setnchannels(self.audio['channels'])
            f.setsampwidth(self.audio['sample-width'])
            f.setframerate(self.audio['rate'])
            
            f.writeframes(data)
    
    def run(self) -> None:
        self.stream = pa.PaSimple(
            direction = pa.PA_STREAM_RECORD,
            format = self.audio['format'],
            channels = self.audio['channels'],
            rate = self.audio['rate'],
            device_name = self.device,
            stream_name = 'thingamajiggy'
        )
        
        self.is_recording.set() # change state upon stream initialisation
        self.kill.wait() # await program end
        
        self.stream.flush() # release resources
        self.stream.close()


if __name__ == "__main__":
    recorder = Recorder()
    recorder.start()
    
    recorder.is_recording.wait() # wait for stream to be established
    
    recorder.record_to_file('example.wav', 10)
© www.soinside.com 2019 - 2024. All rights reserved.