使用 PyAudio 播放 .mp3 文件

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

pyaudio可以播放.mp3文件吗?如果是的话,我可以要求写一个例子吗?如果不是,将 .mp3 转换为 .wav 的最简单方法是什么?

我尝试使用 PyDub,可以获取我的 .wav 文件,但是当我尝试使用 PyAudio 播放它时,出现以下错误:

  File "C:\Python33\lib\wave.py", line 130, in initfp
    raise Error('file does not start with RIFF id')
wave.Error: file does not start with RIFF id

与其他 .wav 样本(其中 not 从 mp3 转换而来)如果效果良好。

我正在使用 gTTS 库将我的应用程序的文本转换为语音。它创建了我需要播放的短 .mp3 文件。现在我只使用

os.system("start english.mp3")

我想找到更好的方法来做到这一点。首先我不想受到平台的限制。其次,我不喜欢文件开始播放时弹出播放器,我希望它留在后台。

我尝试为此找到简约的解决方案,因为除了简单的演奏之外我不需要任何其他东西。

UPD:我设法用 pyglet 玩它。看起来不错,只是时间太长了……在听到声音之前我有大约 10 秒的延迟。而且它不能与线程一起正常工作(我想在程序仍在运行时播放.mp3)。 有没有办法让播放器停留在后台而不是在所有其他窗口上弹出?

python-3.x mp3 wav pyglet pyaudio
5个回答
8
投票

这是简短的答案:

ffmpeg -i song.mp3 -acodec pcm_u8 -ar 22050 song.wav

TL;DR:我假设您想在没有前端的情况下播放音频文件。

有一个库,名为 The Snack Sound Toolkit,它可以完美地完成此操作:

player = Sound() 
player.read('song.wav') 
player.play()

我知道我在两个流中都使用了它,并且我认为 mp3 文件,不记得如何或在哪个项目中,但我可能需要研究一下。认为这与咕哝有关..无论如何..

如果您完全可以使用 pyglet(这是我听说的)之类的前端代码,那么您需要一些选项和一些代码才能使其尽可能地工作。

import pyglet
from pyglet.gl import *
pyglet.options['audio'] = ('openal', 'directsound', 'silent')

music = pyglet.resource.media('music.mp3')
music.play()

pyglet.app.run()

依赖关系: * OpenAL(用于跨平台兼容性)

您的线程问题是 Pyglet 是一个 OpenGL 库。这对线程来说一点也不友好。除非你让 Pyglet 获取你需要的数据。 另外,您很可能会遇到“pyglet 阻止我的代码”的问题(所有图形库都会这样做。所以这里有一个解决方法)

import pyglet, os
from time import sleep
from threading import *
from pyglet.gl import *

pyglet.options['audio'] = ('openal', 'directsound', 'silent')

class worker(Thread):
    def __init__(self):
        Thread.__init__(self)
        self.audio_frames = []
        self.run()

    def add_frame(self, filename):
        self.audio_frames.append(filename)

    def get_frame(self):
         if len(self.audio_frames) > 0:
             return self.audio_frames.pop(0)
         return None

    def run(self):
        while 1:
            for root, folders, files in os.walk('./audio_files/'):
                for f in file:
                    self.add_frame(f)
            sleep(1)

class AudioWindow(pyglet.window.Window):
    def __init__(self):
        self.audio_worker = worker()

    def render(self):
        frame = self.audio_frames.get_frame()
        if frame:
            music = pyglet.resource.media('music.mp3')
            music.play()
    def run(self):
        while self.alive == 1:
            self.render()

        # -----------> This is key <----------
        # This is what replaces pyglet.app.run()
        # but is required for the GUI to not freeze
        #
        event = self.dispatch_events()

这完全没问题,因为您没有尝试从另一个线程更新图形。
相反,您正在获取有关图形术语的数据。 然而,您可以从worker()中更新AudioWindow()内的变量/列表/数组/w/e,没有任何问题,您唯一不能做的就是从图形类外部调用任何图形函数。

无需前端的有趣方法:

然而,最理想的方法是继续老派,使用 pyaudio 并手动调整音频帧。这样,只要正确解码数据,您就可以读取任何音频文件。 我自己用这个(小心点,因为它不漂亮)来传输音频:

import pyaudio
import wave

CHUNK_SIZE = 1024
FORMAT = pyaudio.paInt16
RATE = 44100

p = pyaudio.PyAudio()
output = p.open(format=FORMAT,
                        channels=1,
                        rate=RATE,
                        output=True) # frames_per_buffer=CHUNK_SIZE
with open('audio.wav', 'rb') as fh:
    while fh.tell() != FILE_SIZE: # get the file-size from the os module
        AUDIO_FRAME = fh.read(CHUNK_SIZE)
        output.write(AUDIO_FRAME)

这应该会产生接近音频的东西:)
波在示例等中被过度利用的原因是因为它基本上是未编码的声音流,而不是以任何方式编码。然而 mp3 是一种高度压缩的音频格式,format 是这里的关键词。您需要某种方式来读取 mp3 数据并将其从紧凑状态反转为可以挤入扬声器的数据流。

我不是音频专家,但这是对音频如何工作的粗略解释,由某人摆弄了一下并让它工作。

最后一点:

如果您希望使用 Pyglet 播放压缩音频文件,您可以使用 AVbin。用于压缩文件的库。


3
投票

我的要点是这里,享受吧

#!/usr/bin/env python3
"""
Play a file continously, and exit gracefully on signal

Based on https://github.com/steveway/papagayo-ng/blob/working_vol/SoundPlayer.py

@author Guy Sheffer (GuySoft) <guysoft at gmail dot com>
"""
import signal
import time
import os
import threading
import pyaudio
from pydub import AudioSegment
from pydub.utils import make_chunks

class GracefulKiller:
    kill_now = False
    def __init__(self):
        signal.signal(signal.SIGINT, self.exit_gracefully)
        signal.signal(signal.SIGTERM, self.exit_gracefully)

    def exit_gracefully(self,signum, frame):
        self.kill_now = True

    def play(self):
        """
        Just another name for self.start()
        """
        self.start()

    def stop(self):
        """
        Stop playback. 
        """
        self.loop = False


class PlayerLoop(threading.Thread):
    """
    A simple class based on PyAudio and pydub to play in a loop in the backgound
    """

    def __init__(self, filepath, loop=True):
        """
        Initialize `PlayerLoop` class.

        PARAM:
            -- filepath (String) : File Path to wave file.
            -- loop (boolean)    : True if you want loop playback. 
                                False otherwise.
        """
        super(PlayerLoop, self).__init__()
        self.filepath = os.path.abspath(filepath)
        self.loop = loop

    def run(self):
        # Open an audio segment
        sound = AudioSegment.from_file(self.filepath)
        player = pyaudio.PyAudio()

        stream = player.open(format = player.get_format_from_width(sound.sample_width),
            channels = sound.channels,
            rate = sound.frame_rate,
            output = True)

        # PLAYBACK LOOP
        start = 0
        length = sound.duration_seconds
        volume = 100.0
        playchunk = sound[start*1000.0:(start+length)*1000.0] - (60 - (60 * (volume/100.0)))
        millisecondchunk = 50 / 1000.0


        while self.loop:
            self.time = start
            for chunks in make_chunks(playchunk, millisecondchunk*1000):
                self.time += millisecondchunk
                stream.write(chunks._data)
                if not self.loop:
                    break
                if self.time >= start+length:
                    break

        stream.close()
        player.terminate()


    def play(self):
        """
        Just another name for self.start()
        """
        self.start()

    def stop(self):
        """
        Stop playback. 
        """
        self.loop = False


def play_audio_background(audio_file):
    """
    Play audio file in the background, accept a SIGINT or SIGTERM to stop
    """
    killer = GracefulKiller()
    player = PlayerLoop(audio_file)
    player.play()
    print(os.getpid())
    while True:      
        time.sleep(0.5)
        # print("doing something in a loop ...")
        if killer.kill_now:
            break
    player.stop()
    print("End of the program. I was killed gracefully :)")
    return



if __name__ == '__main__':
    import argparse
    parser = argparse.ArgumentParser(add_help=True, description="Play a file continously, and exit gracefully on signal")
    parser.add_argument('audio_file', type=str, help='The Path to the audio file (mp3, wav and more supported)')
    args = parser.parse_args()

    play_audio_background(args.audio_file)

0
投票

我们可以使用VLC绑定来支持更多文件格式。

https://gist.github.com/elibroftw/b803191c3b919a67b6a65668572680cc


0
投票

您可以安装 pyav 库,它可以解码任何文件中的音频,包括 mp3、mp4、wav...

然后你用 pyaudio 播放音频帧,如下所示:

import av
import pyaudio

container = av.open("song.mp3")

audio_stream = container.streams.audio[0]

samplerate = audio_stream.rate 
channels = audio_stream.channels

p = pyaudio.PyAudio()

audio_device = p.open(format=pyaudio.paFloat32,
                      channels=channels,
                      rate=samplerate,
                      output=True)
while True:
    try:
        frame = next(container.decode(audio=0))
        audio_data = frame.to_ndarray().astype('float32')
        interleaved_data = audio_data.T.flatten().tobytes()
        audio_device.write(interleaved_data)
        time = round(float(frame.pts * audio_stream.time_base), 2)
        print(time) # display current time
    except (StopIteration, av.error.EOFError):
        break
    
audio_stream.close()
container.close()
audio_device.stop_stream()
audio_device.close()
p.terminate()

pyav 容器中有更多功能,例如查找,这将帮助您从任何部分播放文件。


-5
投票

如果您使用的是安装了 Windows Media Player 的 Windows,这里有一个简单的解决方案:

wmp = Dispatch('WMPlayer.OCX')
wmp.settings.autoStart = True
wmp.settings.volume = 50
wmp.URL = r"tts_cache\gtts.mp3" #auto-start
while wmp.PlayState != 1: #wait until stopped
    pythoncom.PumpWaitingMessages()
    time.sleep(0.1)
wmp.URL = ""

不幸的是,当我尝试用新文件替换 mp3 文件时,有时无法写入。我认为 WMP 会阻止它或其他什么。所以我决定每次都创建一个新文件并将其称为“缓存”。)

© www.soinside.com 2019 - 2024. All rights reserved.