【问题标题】:play sound file in PyQt在 PyQt 中播放声音文件
【发布时间】:2012-03-25 16:00:28
【问题描述】:

我在 PyQt 中开发了一个播放声音的软件。我正在使用 Phonon Library 播放声音,但它有一些延迟。那么如何在 PyQt 中播放声音文件而不使用 Phonon Library。

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

【问题讨论】:

    标签: python audio pyqt


    【解决方案1】:

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

    首先,解决您的代码示例

    在您的第一个 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 插槽即可。

    声子

    如果 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 个媒体对象,并将它们放在我们的双端队列中。然后,每次你调用 play 时,我们将双端队列旋转一个并获取第一个索引并播放它。这将为您提供 3 个并发流。

    此时,如果延迟仍然是一个问题,那么您可能需要调查在应用启动时将所有音频加载到 QBuffer 中,然后将它们从内存中使用到声子。我对声子源知之甚少,无法知道当您从文件创建源时它是否已经将整个文件加载到内存中,或者它是否总是输出到磁盘。但如果它总是输出到磁盘,减少这个 IO 将是再次减少延迟的方法。

    希望这完全回答了您的问题!

    【讨论】:

    • 我用过 QtGui.Qsound ,但还是有一些滞后。我实现了一个线程,用于播放声音,但延迟仍然存在。它是一个虚拟钢琴,所以在演奏时会有延迟。你能建议任何方法来减少演奏延迟。
    • 您能否发布一个代码示例来说明您如何使用 QSound。也许您正在做一些事情来引入延迟?如果 phonon 和 QSound 不能满足您的需求,那么您可能需要一个更低级别的库。但如果这些不能正常执行,我会感到惊讶。
    • 文件“virtualPiano.py”,第 214 行,在 init self.playQueue = Queue() TypeError: 'module' object is not callable Exception AttributeError: "'PlayThread'对象在 <__main__.playthread object at>> 的
    • @HemanthRaveendran:我现在已经完全重写了我的答案。希望对您有所帮助
    • 在 PyQt5 中我在这里找到了 QSound:from PyQt5.QtMultimedia import QSound
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-03
    相关资源
    最近更新 更多