在PyQt中播放音频文件

7

我使用PyQt开发了一个播放声音的软件。我正在使用Phonon库来播放声音,但它存在一些延迟问题。那么,如何在不使用Phonon库的情况下在PyQt中播放声音文件。

这是我目前使用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()

现在延迟已经减少了。但是问题是,如果我在短时间内按下两个或更多键,新的音符会覆盖并停止之前的音符。我需要让之前的音符一直播放直到结束。

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
1个回答

16

我重新撰写了这个答案,因为我认为你的问题已经开始分歧。

首先,解决您的代码示例

在您的第一个PlayThread示例中,每次想要播放键时,您都会启动一个新线程,然后必须完全设置媒体播放器并打开源文件,然后播放。这肯定会导致额外的开销。

在您的第二个示例中,您正在传递run()方法,该方法基本上使线程在您启动它后立即结束。然后,您直接在该QThread上调用play()。实质上,您正在将QThread用作基本的QObject类,并在同一主线程中调用play。我也不明白为什么要从MediaSource创建MediaSource(多余?)。但是每次调用play时都会替换声音,这就是为什么您听到它重新启动的原因。

我认为您实际上不需要QThreads。

QSound

在更高的级别上,您可以使用QSound。为了减少您可能遇到的延迟量,您不应使用play()的静态方法即时启动文件。相反,您应该在应用程序启动时预先创建这些QSound对象:

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

notes['c'].play()

调用play()不会阻塞,也不需要单独使用QThread来运行它们。您还可以在同一个QSound对象上多次调用play,但缺点是无法停止所有多个流。它们将不得不播放完毕。如果此方法的性能可接受,则完成了。您只需将钢琴按钮的clicked信号连接到适当键的play插槽即可。 Phonon 如果QSound最终产生了太多的延迟,那么您下一步就是尝试Phonon。同样,为了减少磁盘IO和对象创建的开销,您需要预先创建这些媒体对象。您不能使用单个媒体对象同时播放多个流。因此,您必须选择是尝试为每个声音创建一个媒体对象,还是使用一种媒体对象池。要做一个小型媒体对象池,需要抓取一个空闲的媒体对象,将其源设置为正确的媒体源对象,然后播放。一旦完成,它就必须返回到池中。
使用Phonon比QSound更低级,因此在调用play时,单个媒体对象不能多次播放相同的声音。如果它已经处于播放状态,它将忽略后续的play调用。无论如何,基本方法可能是创建一个Key类来帮助组织玩家实体:
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()

这将使你的音效与QSound类似,但不同之处在于多次调用play()将重新播放声音而不是将它们叠加播放:
notes = {
    'c': Key("c.wav"),
    'd': Key("d.wav"),
    'e': Key("e.wav"),
}

notes['c'].play()

使用同时流来播放同一源的声子

我提到过有一个媒体对象池,您可以使用它来播放多个并发声音。虽然我不会深入探讨这个领域,但我可以建议一种简单的方法来使您的键同时播放,这可能效率略低,因为您必须同时打开更多资源,但现在更容易运行。

简单的方法是为每个键使用一个小的预定的媒体对象池,并在每次调用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()

我们在这里创建了一个deque,它具有通过n个数量旋转列表的非常方便的能力。因此,在初始化中,我们从同一源创建3个媒体对象并将它们放入我们的deque中。然后,每次调用play时,我们将deque旋转一个并取第一个索引进行播放。这将为您提供3个并发流。
此时,如果延迟仍然是一个问题,那么您可能需要调查在应用程序启动时将所有音频加载到QBuffer中,然后从内存中使用它们来phonon。我不知道phonon源是否已经在创建文件源时将整个文件加载到内存中,或者是否总是要出去读取磁盘。但是,如果总是要出去读取磁盘,减少这种IO将是再次减少延迟的方法。
希望这完全回答了您的问题!

你能否提供一个使用QSound的代码示例?也许你正在做一些引入延迟的操作?如果 Phonon 和 QSound 不能满足你的需求,那么也许你需要一个更低级别的库。但是如果这些库无法正常运行,我会感到惊讶的。 - jdi
文件“virtualPiano.py”,第214行,__init__函数中: self.playQueue = Queue() 类型错误:'module'对象不可调用 异常属性错误:"<bound method PlayThread.__del__ of <__main__.PlayThread object at 0x1cec270>>"中的"'PlayThread'对象没有属性'playQueue'"被忽略。 - Hemanth Raveendran
@ jdi:谢谢 :) 但是有其他人发布了更简单的方法。它对我有效。感谢您的回复 :) - Hemanth Raveendran
@ jdi:我已经上传了另一个问题 :) - Hemanth Raveendran
5
在PyQt5中,我找到了QSound:from PyQt5.QtMultimedia import QSound - Jabba
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接