Playing .mp3 files with PyAudio

Spu picture Spu · Oct 31, 2014 · Viewed 19.5k times · Source

Can pyaudio play .mp3 files? If yes, may I ask to write an example please. If no, what is the simplest way to convert .mp3 to .wav?

I have tried to use PyDub, could get my .wav file, but when I try to play it with PyAudio I get following error:

  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

With other .wav samples (which where not converted from mp3) if works well.

I am using gTTS library to convert text to speech for my application. It creates short .mp3 files which I need to play then. Right now I am using just

os.system("start english.mp3")

I want to find a better way to do this. First of all I don't want to be restricted about the platform. Secondly, I don't like that the player pops-up when the file begins to play, I'd like it to stay on the background.

I try to find the minimalist solution for this, as I don't need anything more than simple playing.

UPD: I managed to play it with pyglet. Seems fine, except it takes sooo long... I have about 10 seconds delay before I hear the sound. And it doesn't work proper with threading (I want to play the .mp3 while the program is still running). Is there the way just to make the player stay on the background and not pop-up over all other windows?

Answer

Torxed picture Torxed · Nov 5, 2014

Here's the short answer:

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

TL;DR: I'm assuming you want to play an audio file, without a front-end.

There's a library for that, called The Snack Sound Toolkit which does this beautifully:

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

I know I used this with both streams and I think mp3 files, can't remember how or in which project, I might have to look into this though. Think it was mumble related.. anyhow..

If you're completely fine with using a front-end code such as pyglet (which is my pick of the heard), you need some options and some code for this to work as best as possible.

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

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

pyglet.app.run()

Dependencies: * OpenAL (for cross-platform compatibility)

Your problem with threading, is that Pyglet is an OpenGL library. Which doesn't take too kindly to Threading at all. Unless you let Pyglet fetch the data you need. Also, you will most likely bump into the problem of "pyglet blocks my code" (all graphic libraries do. so here's a workaround)

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()

This is totally fine, since you're not trying to update THE graphics from another thread.
Instead, you're fetching data on the graphics terms. You can however from worker() update a variable/list/array/w/e inside AudioWindow() without any problems, the only thing you can't do is call any graphical function from outside the graphic-class.

Without front-end the fun approach:

The most ideal way however, would to be going old-school as hell and use pyaudio and fiddle with audio frames manually. This way you can read literally any audio-file as long as you decode the data properly. I use this one(Tread lightly, cause it ain't pretty) for transporting audio myself:

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)

This should produce something close to audio :)
The reason why wave is so overutilized in examples etc, is because it's basically a unencoded stream of sound, not encoding in any way. mp3 however is a heavily compressed audio format, format being the key-word here. You need some way of read the mp3 data and reverse it from a compact state into a stream of data which you can squeeze into the speakers.

I'm no audio expert, but this is a rough explanation of how audio works from someone fiddling about with it for a bit and got it working.

Last note:

If you're expecting to play compressed audio-files with Pyglet, you can use AVbin. A library used for compressed files.