树莓派用 Python 在多个输出设备上播放多个声音文件

准备工作
功放板:https://www.amazon.com/gp/product/B07J4P4FR9
USB 声卡:https://item.taobao.com/item.htm?id=577563502441
DC/DC变压器:https://www.amazon.com/gp/product/B01GJ0SC2C
USB HUB:https://www.amazon.com/gp/product/B076YPRTKF/
DC 接线端子:https://www.amazon.com/gp/product/B06XRT5C2Q
**添加声音设备
声音文件
threading模块(每一个对象代表一个线程 https://blog.csdn.net/goldxwang/article/details/77838072)
argparse模块(argparse是python用于解析命令行参数和选项的标准模块 https://www.2cto.com/kf/201412/363654.html
https://yq.aliyun.com/articles/567308)
pathlib.py模块 (内置pathlib库的常用属性和方法 https://www.cnblogs.com/sigai/p/8074329.html)

argparse 用于将命令行字符串解析为Python对象的对象 添加关注的命令行参数和选项 开始解析
pathlib 检测文件的路径 是否隐藏
确定声音文件路径
文件加载到内存 寻找usb设备
寻找到的usb设备文件的信息
为usb声音设备创建输出索引

检测输出索引是否创建
检测文件是否存在
threading 创建线程 判断线程是否进入
键盘中断模块启动 退出

wait_devices_init.py 设备等待准备
使用对声音设备的外部调用来获取库检测到的设备数量。**

pear.py

"""
"""

import sounddevice
import soundfile
import threading
import argparse
import pathlib
import os


DATA_TYPE = "float32"


def load_sound_file_into_memory(path):
    """

	  获取到wav文件的给定路径的内存版本
	:参数路径:要加载的wav文件
	:返回:audio_data,一个2D numpy数组
    """

    audio_data, _ = soundfile.read(path, dtype=DATA_TYPE)
    return audio_data


def dir_path(path):
    """
	检查给定的路径是否实际上是一个目录
	:参数路径:指向目录的路径 
	:return: path(如果是目录),如果不是,则引发错误
    """

    p = pathlib.Path(path)
    if p.is_dir():
        return path
    else:
        raise NotADirectoryError(path)


def get_device_number_if_usb_soundcard(index_info):
    """
	给定一个设备dict,如果该设备是我们的USB声卡之一,则返回True,否则返回False
	:param index_info:来自PyAudio的设备信息dict。
	:返回:usb声卡为真,否则为假
    """

    index, info = index_info

    if "USB Audio Device" in info["name"]:
        return index
    return False


def play_wav_on_index(audio_data, stream_object):
    """
	播放一个名为load_sound_file_into_memory的音频文件
	:param audio_data:一个二维数字数组
	:param stream_object:声音设备。对象,该对象将立即开始播放写入其中的任何数据。
	:return: None,当所有数据都被使用时返回
    """

    stream_object.write(audio_data)


def create_running_output_stream(index):
    """
	创建一个sounddevice。向准备写入的索引指定的设备写入的OutputStream。
	你可以立即调用'写'对这个对象与数据,它将发挥在设备上。
	:param索引:要写入的音频设备的设备索引
	:返回:已启动的声音设备。准备写入的OutputStream对象
    """

    output = sounddevice.OutputStream(
        device=index,
        dtype=DATA_TYPE
    )

    output.start()
    return output


if __name__ == "__main__":

    parser = argparse.ArgumentParser(description='a simple tool for sound installations') #用于将命令行字符串解析为Python对象的对象
    parser.add_argument("dir", type=dir_path)#添加关注的命令行参数和选项
    args = parser.parse_args() #开始解析

    def good_filepath(path):   #检测文件的路径  是否隐藏
        """
        Macro for returning false if the file is not a non-hidden wav file
        :param path: path to the file
        :return: true if a non-hidden wav, false if not a wav or hidden
        """
        return str(path).endswith(".wav") and (not str(path).startswith("."))

    sound_file_paths = [os.path.join(args.dir, path) for path in sorted(filter(lambda path: good_filepath(path),
                                                                               os.listdir(args.dir)))]    #确定声音文件路径

    print("Discovered the following .wav files:", sound_file_paths) #打印声音文件路径

    files = [load_sound_file_into_memory(path) for path in sound_file_paths]   #文件加载到内存  寻找usb设备

    print("Files loaded into memory, Looking for USB devices.")

    usb_sound_card_indices = list(filter(lambda x: x is not False,
                                         map(get_device_number_if_usb_soundcard,
                                             [index_info for index_info in enumerate(sounddevice.query_devices())])))#寻找到的usb设备文件的信息

    print("Discovered the following usb sound devices", usb_sound_card_indices)#打印寻找到的usb文件设备文件信息

    streams = [create_running_output_stream(index) for index in usb_sound_card_indices]#为usb声音设备创建输出索引

    running = True     

    if not len(streams) > 0:   #检测输出索引是否创建
        running = False
        print("No audio devices found, stopping")

    if not len(files) > 0:    #检测文件是否存在
        running = False
        print("No sound files found, stopping")

    while running:

        print("Playing files")

        threads = [threading.Thread(target=play_wav_on_index, args=[file_path, stream])
                   for file_path, stream in zip(files, streams)]   #创建线程

        try:

            for thread in threads:   #在线程中  开始
                thread.start()

            for thread, device_index in zip(threads, usb_sound_card_indices):
                print("Waiting for device", device_index, "to finish")
                thread.join()#提示阻塞,等待设备完成

        except KeyboardInterrupt:#键盘中断模块启动  停止程序
            running = False
            print("Stopping stream")
            for stream in streams:
                stream.abort(ignore_errors=True)
                stream.close()
            print("Streams stopped")

    print("Bye.")


wait_devices_init.py 设备等待准备

"""
Blocks until the sounddevice module is able to pick up all of the attached usb sound cards as seen by lsusb
块,直到声音设备模块能够接收到lsusb所看到的所有附加的usb声卡
"""

import subprocess


def num_sound_devices():
    """
	使用对声音设备的外部调用来获取库检测到的设备数量。
	这样做很重要,而不仅仅是sounddevice.query_devices(),声音设备询问
	返回:
    """

    return str(subprocess.check_output(["python3", "-m", "sounddevice"])).count("USB Audio Device")


def wait_for_usb_sound_devices_to_be_initialized():
    """
	调用此函数将阻塞,直到lsusb检测到的USB声音声卡设备的数量匹配为止
	声音设备后端初始化的数字。
	返回:
    """

    while num_sound_devices() != str(subprocess.check_output(["lsusb"])).count("C-Media Electronics"):
        pass


if __name__ == "__main__":
    wait_for_usb_sound_devices_to_be_initialized()  #等待usb声音设备初始化


参考:http://shumeipai.nxez.com/2019/02/21/play-multiple-sound-files-on-multiple-output-devices.html

你可能感兴趣的:(树莓派用 Python 在多个输出设备上播放多个声音文件)