Play sound file in PyQt

I have developed software in PyQt that plays sound. I am using Phonon Library to play audio, but it has some lag. Since I can play a sound file in PyQt without using the Phonon library.

This is how I currently use 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()

      

The lag is now decreasing. But the problem is that I press two or more keys in a short time, when a new note overlays and stops the previous note. I need to play the previous note until 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

      

+3


source to share


1 answer


I rewrote this answer as I think your question started to diverge

Let's look at some code examples first

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

In your second example, you are passing a method run()

that basically makes the thread end right after it starts. And then you directly call play () on that QThread. Basically, you are using QThread as the base QObject class and you are invoking the game inside the same main thread. I also don't understand why you are creating MediaSource from MediaSource (redundant?). But it replaces the sound every time you invoke the game, which is why you hear it restarting.

I don't think you really need QThreads for this.

QSound

At a higher level, you can use QSound. To reduce the amount of delays that can occur, you should not use a static method play()

to run a file on the fly. Instead, you must pre-create these QSound objects when the application starts:

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

notes['c'].play()

      

The call play()

will not block and you do not need QThread separately to run them. You can also call play multiple times on the same QSound object, but it has the disadvantage that it cannot stop all multiple streams. They'll have to act out. If this method results in acceptable performance than you did. You just simply connect the signal clicked

from the piano button to the play

corresponding key slot .

Phonon

If QSound ends up producing too much latency, then the next step is to try using Phonon. Again, to reduce the overhead of disk IO creation and object creation, you need to create these media objects first. You cannot use one media object to play multiple streams at the same time. So you have to choose whether you want to try to create one media object for each sound or use a pool of media objects. To make a small pool of media objects would require you grab the free one, set its source to the appropriate media source object, and then play. Once completed, it will need to be returned to the pool.



Using Phonon below QSound level, so one media object cannot play the same sound multiple times when calling play. It will ignore subsequent calls play

if it is already in the playing state. Regardless, the main approach could be to create a Key class that will help organize the player entity:

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 will again lead you to be like QSound, except that the difference between calling play()

more than once will reset the sound 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 parallel streams from a single source

I mentioned having a pool of media objects that you would use to play multiple simultaneous sounds. While I won't go into this area, I can suggest an easy way to access your keys at the same time, which might be a little less efficient since you need to open multiple resources at once, but it's easier to work now.

A simple approach is to use a small predefined pool of media objects per key and rotate through playing them every 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()

      

Here we created a deque that comes with a really handy option to rotate the list by n-amount. So in init we create 3 media objects from one source and put them in our deque. Then, every time you invoke the game, we rotate the deque by one and take the first index and play it back. This will give you 3 parallel streams.

At this point, if lag is still an issue, you might have to investigate loading all of your audio into a QBuffer at the start of your application and then using them from memory to phonon. I don't know enough about the phonon source to know if it loads the entire file into memory already when the source is created from the file, or if it always goes out to disk. But if it always goes to disk, cutting that I / O is a way to reduce latency.

Hope this fully answers your question!

+9


source







All Articles