Python读取输出声音而非输入声音。

12

有没有什么简单的方法可以读取系统音量级别?

我计划使用短 LED 条创建一个均衡器,并将其连接到 Arduino 或 RaspberryPi 上。我看过很多如何用输入、麦克风或类似设备实现此目的的示例,但是涉及总体输出方面的资料较少。 我现在无法关心特定于程序或系统范围,只要能够获得读数即可。 欢迎任何建议。

-- 编辑 --

根据How get sound input from microphone in python, and process it on the fly?,我将 PCM_CAPTURE 更改为 PCM_PLAYBACK,结果 Python 说输出/播放上无法进行读取。

我现在正在尝试创建一个回路设备,这个想法是将播放内容管道传输到另一个捕捉设备中并进行读取,但是目前还没有成功,而且我不知道这种方式的整洁程度。 我无法想象这是正确的方法。


请提供更多关于您已尝试的信息。 - Luceos
基于https://dev59.com/UnI-5IYBdhLWcg3wR2Jr,我将PCM_CAPTURE更改为PCM_PLAYBACK,这导致python说无法在输出/播放上进行读取。现在我正在尝试创建一个回路设备,想法是将播放管道返回到单独的捕获并读取它,但迄今为止没有运气,而且我不知道这种方式有多么整洁/干净。我真的无法想象这是正确的方法。 - PvdL
请尽可能更新您的问题,而不是在评论中提出。这样新读者可以更轻松地分析问题并回答。 - Luceos
3个回答

3
有人使用Python和PulseAudio实现了这一点,详情请参见此博客文章。作者编写了一个脚本来轮询系统范围内的峰值音量,并将其提供给VU仪表。他还在Bitbucket上提供了源代码,点击这里
我已经在Ubuntu上测试过它,对我起作用(尽管仅从命令行中使用,无法在交互式Python会话中使用)。需要额外的工作是安装ctypes PulseAudio包装器以与Python进行接口交互(链接在源代码中),并可能将SINK_NAME设置为脚本中正确的值。
从博客中我得到的信息是,PulseAudio非常适合这个任务,但也许有更好的方法。

我已经看过并测试过了,但效果不好。在我的接收器名称设置正确的情况下,我只得到0。在Ubuntu上使用pulseaudio pref程序(paman)设置一个组合设备后,我能够读取一些内容。奇怪的是,即使使用paman,我几乎无法从任何设备中读取声音级别,除了组合设备。我将尝试在不同的设置上进行尝试,但我还没有时间。 - PvdL
如果您想使用PulseAudio,似乎必须先确保pavumeter显示您正在播放的声音的音量。您确定您的声音是通过Pulse而不是直接通过Alsa或其他方式播放的吗? - user2379410
是的,即使我的系统实际上有问题(这很可能是真的),我也可以访问具有不同设置的多个系统来测试它。 - PvdL

0
几乎任何类型的声音混合器(软件或硬件)都应该能够将声音输出重新路由到输入。对于Windows,有内置的Stereo Mix,而对于Linux,则有几种解决方案(我听说PulseAudio不错,但我不能亲自证明- this tutorial中的一些步骤可能会有所帮助)。
这样,您就可以将其路由到Python-您已经阅读过的读取麦克风级别的示例也应该同样有效。

据我所知,pulseaudio是在alsa之上的软件层,不是每个发行版都使用pulse,但大多数都使用alsa。由于我想在带有XBMC的RasberryPi上运行它,因此我认为alsa是正确的选择。 - PvdL

0
我找到了这个Github解决方案

mserve mouse button hover.gif

我将Python程序设置为单独的实例,以避免影响我的主程序。然后每33毫秒(30 fps)轮询IPC pickles。我稍微修改了代码,如下所示:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

#==============================================================================
#
#       vu_meter.py - Listen to microphone left/right and generate vu level
#
#==============================================================================
"""

FROM: https://github.com/kmein/vu-meter

.gitignore          NOT INSTALLED
LICENSE             NOT INSTALLED
README.md           NOT INSTALLED
amplitude.py                        THIS MODULE
record.py           NOT INSTALLED
vu_constants.py                     THIS MODULE
vu_meter.py                         THIS MODULE

ENHANCEMENTS:

    1) Support Python environment variable for automatic python 2 / 3 selection
    2) Add UTF-8 to support Python 2 (not necessary in this program though)
    3) Reset maximal between songs (gap when 10 samples of zero strength)
    4) Remove console output and write values to ramdisk (/run/user/1000)
    5) Optional 'stereo' parameter to measure left & right channels
    6) Utilize numpy which is usually auto-installed on distros
    7) Include separate amplitute.py & vu_constants.py in main
    8) Remove unused functions from Amplitude class

"""

from __future__ import print_function       # Must be first import
from __future__ import with_statement       # Error handling for file opens

import pyaudio
import numpy as np              # January 24, 2021 support separate 2 channels
import sys
import math
import struct
#from amplitude import Amplitude

RATE = 44100
INPUT_BLOCK_TIME = 0.05
INPUT_FRAMES_PER_BLOCK = int(RATE*INPUT_BLOCK_TIME)
SHORT_NORMALIZE = 1.0 / 32768.0
# Mono output
VU_METER_FNAME  = "/run/user/1000/mserve.vu-meter-mono.txt"
# Stereo output (Left and Right)
VU_METER_LEFT_FNAME  = "/run/user/1000/mserve.vu-meter-left.txt"
VU_METER_RIGHT_FNAME  = "/run/user/1000/mserve.vu-meter-right.txt"


class Amplitude(object):
    ''' an abstraction for Amplitudes (with an underlying float value)
    that packages a display function and many more
    
    January 25, 2021 - Remove unused add, subb, gt, eq, int & str functions
    '''

    def __init__(self, value=0):
        self.value = value

    def __lt__(self, other):
        return self.value < other.value

    def to_int(self, scale=1):
        ''' convert an amplitude to an integer given a scale such that one can
        choose the precision of the resulting integer '''
        return int(self.value * scale)

    @staticmethod
    def from_data(block):
        ''' generate an Amplitude object based on a block of audio input data '''
        count = len(block) / 2
        shorts = struct.unpack("%dh" % count, block)
        sum_squares = sum(s**2 * SHORT_NORMALIZE**2 for s in shorts)
        return Amplitude(math.sqrt(sum_squares / count))

    def display(self, mark, scale=50, fn=VU_METER_FNAME):
        ''' display an amplitude and another (marked) maximal Amplitude
        graphically '''
        int_val = self.to_int(scale)
        mark_val = mark.to_int(scale)
        delta = abs(int_val - mark_val)
        # print(int_val * '*', (delta-1) * ' ', '|',mark_val,int_val,delta)
        # January 23, 2021: Write values to ramdisk instead of displaying
        with open(fn, "w") as vufile:
            vufile.write(str(mark_val) + " " + str(int_val))


def parse_data(data, channel_ndx, channel_cnt, maximal):
    '''
        Process data from one channel
    '''
    data = np.fromstring(data,dtype=np.int16)[channel_ndx::channel_cnt]
    data = data.tostring()
    amp = Amplitude.from_data(data)
    gap = amp.value      # For signal test below.
    if amp > maximal:
        maximal = amp
    return amp, maximal, gap


def main():

    # January 24, 2021 separate left and right channels
    parameter = 'mono'
    if (len(sys.argv)) == 2:
        parameter = sys.argv[1]     # Null = 'mono', 'stereo' = Left & Right

    audio = pyaudio.PyAudio()
    reset_baseline_count = 0
    try:
        stream = audio.open(format=pyaudio.paInt16,
                            channels=2,
                            rate=RATE,
                            input=True,
                            frames_per_buffer=INPUT_FRAMES_PER_BLOCK
                           )

        maximal = Amplitude()
        maximal_l = maximal_r = maximal

        while True:
            data = stream.read(INPUT_FRAMES_PER_BLOCK)

            # January 24, 2021 separate left and right channels
            if parameter == 'stereo':
                ampl, maximal_l, gap = parse_data(data, 0, 2, maximal_l)
                ampr, maximal_r, gap = parse_data(data, 1, 2, maximal_r)
                if maximal_r < maximal_l:
                    # A momentary spike to left channel inherited by right
                    maximal_r = maximal_l
                if maximal_l < maximal_r:
                    # A momentary spike to right channel inherited by left
                    maximal_l = maximal_r
            else:
                # Mono - processing all dadta
                amp = Amplitude.from_data(data)
                gap = amp.value      # For signal test below.
                if amp > maximal:
                    maximal = amp

            # New code January 23, to reset next song's maximal during gap
            if gap == 0.0:
                reset_baseline_count += 1
                if reset_baseline_count == 10:
                    maximal = Amplitude()
                    maximal_l = maximal_r = maximal
                    # print('maximual reset', maximal.value)
            else:
                reset_baseline_count = 0

            # January 24, 2021 separate left and right channels
            if parameter == 'stereo':
                ampl.display(scale=200, mark=maximal_l, fn=VU_METER_LEFT_FNAME)
                ampr.display(scale=200, mark=maximal_r, fn=VU_METER_RIGHT_FNAME)
            else:
                # Mono processing one channel combined sound
                amp.display(scale=200, mark=maximal, fn=VU_METER_FNAME)

    finally:
        stream.stop_stream()
        stream.close()
        audio.terminate()

if __name__ == "__main__":
    main()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接