python - 在PyQt中播放声音文件

标签 python audio pyqt

我已经在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

最佳答案

因为我认为您的问题已经开始分歧,所以我已经重写了此答案

首先,解决您的代码示例

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

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

我认为您实际上不需要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将是再次减少延迟的方法。

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

关于python - 在PyQt中播放声音文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9861592/

相关文章:

python - 尽管改变 y 的值,.plot(y=) 仍给出相同的图表

android - 音频流输入 block 掉线了吗?

android - 让 Android 使用新的音频 HAL 需要什么

qt - 用鼠标旋转QGraphicsPixmapItem

python - 为 PyQt4.QtCore 导入 Hook

python - 如何在 QItemDelegate sizeHint() 中获取 QTreeView 单元格宽度?

python - 禁用 Shutil.copy 中的 SameFileError 异常

python - 如何合并 Jupyter 笔记本中的更改

python - Python 正则表达式评估期间的空分组

debugging - 确定Speex编码的音频与预期设置有何不同