Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

play sound file in PyQt

Tags:

python

audio

pyqt

I've developed a software in PyQt which plays sound.I'm using Phonon Library to play the sound but it has some lag.So how can I play a sound file in PyQt without using Phonon Library.

This is how I am currently using Phonon:

def Playnote(self,note_id):
    global note    
    note = note_id
    self.PlayThread = PlayThread()
    self.PlayThread.start()




class PlayThread(QtCore.QThread):
  def __init__(self):
  QtCore.QThread.__init__(self)

  def __del__(self):
    self.wait()     
  def run(self):
    global note
    self.m_media = Phonon.MediaObject(self)
    audioOutput = Phonon.AudioOutput(Phonon.MusicCategory, self)
    Phonon.createPath(self.m_media, audioOutput)
    self.m_media.setCurrentSource(Phonon.MediaSource(Phonon.MediaSource(note)))
    self.m_media.play()

Now the lag is reduced. But the problem is is I'm pressing two or more keys in short time that's the new note overheads and stops the previous note. I need to play the previous note till it ends.

class PlayThread(QtCore.QThread):
   def __init__(self):
    QtCore.QThread.__init__(self)
    self.m_media = Phonon.MediaObject(self)
    self.audioOutput = Phonon.AudioOutput(Phonon.MusicCategory, self)
    Phonon.createPath(self.m_media, self.audioOutput)    
   def __del__(self):
      self.wait()       
   def play(self, note):
      self.m_media.setCurrentSource(Phonon.MediaSource(Phonon.MediaSource(note)))
      self.m_media.play()
   def run(self):pass
like image 912
Hemanth Raveendran Avatar asked Mar 25 '12 16:03

Hemanth Raveendran


1 Answers

I have rewritten this answer since I think your question has begun to diverge

First, addressing your code examples

In your first PlayThread example, you are starting a new thread every single time you want to play a key, which then has to completely set up a media player, and open the source file, and then play. This is definitely causing you overhead.

In your second example, you are passing on the run() method which basically makes the thread end right after you start it. And then you are directly calling play() on that QThread. Essentially what you are doing is using the QThread like a basic QObject class, and calling play within the same main thread. I also don't understand why you create a MediaSource from a MediaSource (redundant?). But its replacing the sound each time you call play, which is why you hear it restarting.

I do not think you actually need QThreads for this.

QSound

At the higher level, you can use QSound. To reduce the amount of lag you could potentially incur you should not use the static method of play() to start a file on the fly. Instead you should pre-create these QSound objects at the startup of your application:

notes = {
    'c': QtGui.QSound("c.wav"),
    'd': QtGui.QSound("d.wav"),
    'e': QtGui.QSound("e.wav"),
}

notes['c'].play()

Calling play() will not block and you do not need a QThread separately to run these. You can also call play multiple times on the same QSound object, but it has the downside of not being able to stop all of the multiple streams. They will have to play out. If this method results in acceptable performance than you are done. You would simply just connect the clicked signal from the piano button to the play slot of the proper key.

Phonon

If QSound does end up producing too much lag, then your next step would be to try Phonon. Again, to reduce the overhead of disk IO and object creation, you would need to pre-create these media objects. You cannot use a single media object to play multiple streams at the same time. So you have to choose whether you want to try and create one media object for every sound, or use a kind of pool of media objects. To do a small pool of media object, it would require that you grab a free one, set its source to the proper media source object, then play. Once it is finished it would have to be returned to the pool.

Using Phonon is lower level than QSound, so a single media object cannot play the same sound multiple times when calling play. It will ignore subsequent calles to play if its already in a play state. Regardless, the basic approach might be to create a Key class to help organize the entity of a player:

class Key(QtCore.QObject):

    def __init__(self, soundFile, parent=None):
        super(Key, self).__init__(parent)

        self.soundFile = soundFile

        self.mediaObject = Phonon.MediaObject(self)
        self._audioOutput = Phonon.AudioOutput(Phonon.MusicCategory, self)
        self._path = Phonon.createPath(self.mediaObject, self._audioOutput)
        self.mediaSource = Phonon.MediaSource(soundFile)
        self.mediaObject.setCurrentSource(self.mediaSource)   

    def play(self):
        self.mediaObject.stop()
        self.mediaObject.seek(0)
        self.mediaObject.play()

This would again get you almost back to the point of being like QSound, except for the difference being that calling play() more than once would reset the sound over again instead of playing them on top of each other:

notes = {
    'c': Key("c.wav"),
    'd': Key("d.wav"),
    'e': Key("e.wav"),
}

notes['c'].play()

Phonon with concurrent streams from same source

I mentioned having a pool of media objects that you would use to play multiple concurrent sounds. While I won't get into that area, I can suggest a simple way to get your keys playing concurrently that might be a little less efficient since you have to open more resources at once, but easier to get running for now.

The simple approach is to use a small predetermined pool of media objects per key, and rotate through playing them each time you call play

from collections import deque

class Key(QtCore.QObject):

    POOL_COUNT = 3

    def __init__(self, soundFile, parent=None):
        super(Key, self).__init__(parent)
        self.soundFile = soundFile

        self.resourcePool = deque()

        mediaSource = Phonon.MediaSource(soundFile)

        for i in xrange(self.POOL_COUNT):
            mediaObject = Phonon.MediaObject(self)
            audioOutput = Phonon.AudioOutput(Phonon.MusicCategory, self)
            Phonon.createPath(mediaObject, audioOutput)
            mediaObject.setCurrentSource(mediaSource)
            self.resourcePool.append(mediaObject)

    def play(self):
        self.resourcePool.rotate(1)
        m = self.resourcePool[0]
        m.stop()
        m.seek(0)
        m.play()

What we did here is created a deque, which comes with a really handy ability to rotate the list by n-amount. So in the init, we create 3 media objects from the same source and place them in our deque. Then, every time you call play, we rotate the deque by one and take the first index and play it. This will give you 3 concurrent streams.

At this point, if the lag is still a problem then you might have to investigate loading all your audio into QBuffer's at the start of your app, and then use them from memory to phonon. I don't know enough about the phonon source to know if it loads the whole file in memory already when you create a source from file, or if its always going out to disk. But if it is always going out to disk, reducing this IO would be the way to again reduce lag.

Hope this completely answers your question!

like image 103
jdi Avatar answered Sep 22 '22 01:09

jdi