【问题标题】:QtMultimedia - QAudioDecoder - Python - state changes, but buffer never made availableQtMultimedia - QAudioDecoder - Python - 状态更改,但缓冲区从未可用
【发布时间】:2017-10-07 06:19:33
【问题描述】:

我不想说我花了多长时间才达到这一点,但我很难完全理解 PyQt5 以及它与我在 Qt 网站上看到的 C++ 代码的关系,但我认为.. 我明白了,或者...至少我以为我做到了,直到这完全失败为止。我将从我得到的输出开始,它告诉我我有一个确实存在的文件。我已经尝试过 mp3 和 ogg 版本,以防由于某种原因 AudioDecoder 无法解码 MP3,即使 QtMultimedia 的其他部分能够播放它(我正在尝试降低级别,以便我可以将平移应用于音频和改变左/右平衡,一旦我弄清楚了,也许还有其他有趣的事情)。

调试输出:

MP3 exists:True
Decoder stopped:True <- expected at this point, just confirming state works 
Decoder state changed? <- this means state change signal is being sent
Decoder stopped?:False <- ok, state did actually change, that's expected
Decoder decoding?:True <- expected, confirming there are only 2 states as documentation indicates 
Init finished, Decoder started? <- after this, i expect to see position changes, buffer availability changes, or errors ... but I get nothing and it just exits the script.

代码:

from PyQt5 import QtCore, QtMultimedia
from PyQt5.QtCore import QObject, pyqtSlot, pyqtSignal, QByteArray, QIODevice, QFileInfo
from PyQt5.QtMultimedia import QAudioDecoder, QAudioFormat, QMediaObject, QAudioBuffer, QAudioOutput, QAudio

class AudioDecoder(QObject):  
  def __init__(self):
    super(AudioDecoder,self).__init__()
    self.desiredFormat = QAudioFormat()
    self.desiredFormat.setChannelCount(2)
    self.desiredFormat.setCodec('audio/pcm')
    self.desiredFormat.setSampleType(QAudioFormat.UnSignedInt)
    self.desiredFormat.setSampleRate(48000)
    self.desiredFormat.setSampleSize(16)

    self.decoder = QAudioDecoder()
    self.decoder.setAudioFormat(self.desiredFormat)
    self.decoder.setSourceFilename('D:\\python\\sounds\\30.mp3')
    fs = QFileInfo()
    print('MP3 exists:' + str(fs.exists('D:\\python\\sounds\\30.mp3')))

    #self.connect(decoder,bufferReady(),None,readBuffer())
    self.decoder.bufferReady.connect(self.readBuffer)
    self.decoder.finished.connect(self.play)
    self.decoder.error.connect(self.error)
    self.decoder.stateChanged.connect(self.stateChanged)
    self.decoder.positionChanged.connect(self.positionChanged)
    self.decoder.bufferAvailableChanged.connect(self.bufferAvailableChanged)

    #using this to determine if we need to start byte array or append to it
    self.readamount = 0 

    #Expect this to be true since we haven't started yet 
    print('Decoder stopped:' + str(self.decoder.state() == QAudioDecoder.StoppedState))    
    self.decoder.start()
    print('Init finished, Decoder started?')
  def bufferAvailableChanged(self):
   print(str(decoder.available))
  def positionChanged(self):
    print(str(decoder.position())+'/'+str(decoder.duration))
  def stateChanged(self):
    #Confirm state is what we expect
    print('Decoder state changed?')
    print('Decoder stopped?:' + str(self.decoder.state() == QAudioDecoder.StoppedState))
    print('Decoder decoding?:' + str(self.decoder.state() == QAudioDecoder.DecodingState))
  def error(self):
    print('Decoder error?')
    print(self.decoder.errorString())
  def readBuffer(self):
    print('Decoder ready for reading?')
    buffer = self.decoder.read()
    print('Bytecount in buffer:' + str(buffer.byteCount))
    if self.readamount == 0:
      self.ba = QByteArray()
      self.ba.fromRawData(buffer.data(),buffer.byteCount())
    else:
      self.ba.append(buffer.data(),buffer.byteCount())
    print('Bytearray size:' + str(self.ba.length()))  
  def play(self):
    print('Decoding finished, ready to play')

ad = AudioDecoder()

修改后的代码,尝试 WAV,但仍然无法正常工作:

from PyQt5 import QtCore, QtMultimedia
from PyQt5.QtTest import QSignalSpy
from PyQt5.QtCore import QObject, pyqtSlot, pyqtSignal, QByteArray, QIODevice, QFileInfo
from PyQt5.QtMultimedia import QAudioDecoder, QAudioFormat, QMediaObject, QAudioBuffer, QAudioOutput, QAudio

class AudioDecoder(QObject):  
  def __init__(self):
    super(AudioDecoder,self).__init__()
    self.desiredFormat = QAudioFormat()
    self.desiredFormat.setChannelCount(2)
    self.desiredFormat.setCodec('audio/pcm')
    self.desiredFormat.setSampleType(QAudioFormat.UnSignedInt)
    self.desiredFormat.setSampleRate(48000)
    self.desiredFormat.setSampleSize(16)

    self.decoder = QAudioDecoder()

    self.decoder.bufferReady.connect(self.readBuffer)
    self.decoder.finished.connect(self.play)
    self.decoder.error.connect(lambda: self.error(self.decoder.error()))
    self.decoder.stateChanged.connect(lambda: self.stateChanged(self.decoder.state()))
    self.decoder.positionChanged.connect(lambda: self.positionChanged(self.decoder.position(),self.decoder.duration()))
    self.decoder.bufferAvailableChanged.connect(lambda: self.bufferAvailableChanged(self.decoder.available()))

    self.decoder.setAudioFormat(self.desiredFormat)
    self.decoder.setSourceFilename('D:\\python\\sounds\\piano2.wav')
    fs = QFileInfo()
    print('File exists:' + str(fs.exists('D:\\python\\sounds\\piano2.wav')))

    #using this to determine if we need to start byte array or append to it
    self.readamount = 0 

    #Expect this to be true since we haven't started yet 
    print('Decoder stopped?:' + str(self.decoder.state() == QAudioDecoder.StoppedState))    
    self.decoder.start()      
    print('Init finished, Decoder started on file:' + self.decoder.sourceFilename())    
  @pyqtSlot()
  def bufferAvailableChanged(self,available):
   print('Available:' + str(available))
  @pyqtSlot() 
  def positionChanged(self,position,duration):
    print('Position:' + str(position())+'/'+str(duration()))  
  @pyqtSlot()   
  def stateChanged(self,state):
    #Confirm state is what we expect
    print('Decoder state changed')
    if state == QAudioDecoder.StoppedState:
      print('Decoder stopped?:' + str(state == QAudioDecoder.StoppedState))
    else:
      print('Decoder decoding?:' + str(state == QAudioDecoder.DecodingState))    
  @pyqtSlot()  
  def error(self,err):
    print('Decoder error')
    print(self.decoder.errorString())  
  def readBuffer(self):
    print('Decoder ready for reading?')
    buffer = self.decoder.read()
    print('Bytecount in buffer:' + str(buffer.byteCount))
    if self.readamount == 0:
      self.ba = QByteArray()
      self.ba.fromRawData(buffer.data(),buffer.byteCount())
    else:
      self.ba.append(buffer.data(),buffer.byteCount())
    self.readamount = self.readamount + 1
    print('Bytearray size:' + str(self.ba.length()))      
  def play(self):
    print('Decoding finished, ready to play')

ad = AudioDecoder()

我的更新代码从下面发布答案,它适用于 mp3 :)

from PyQt5 import QtCore, QtMultimedia, QtWidgets
from PyQt5.QtTest import QSignalSpy
from PyQt5.QtCore import QObject, pyqtSlot, pyqtSignal, QByteArray, QIODevice, QFileInfo
from PyQt5.QtMultimedia import QAudioDecoder, QAudioFormat, QMediaObject, QAudioBuffer, QAudioOutput, QAudio
import signal

class AudioDecoder(QObject):  
  def __init__(self):
    super(AudioDecoder,self).__init__()
    self.desiredFormat = QAudioFormat()
    self.desiredFormat.setChannelCount(2)
    self.desiredFormat.setCodec('audio/pcm')
    self.desiredFormat.setSampleType(QAudioFormat.UnSignedInt)
    self.desiredFormat.setSampleRate(48000)
    self.desiredFormat.setSampleSize(16)

    self.decoder = QAudioDecoder()

    self.decoder.bufferReady.connect(self.readBuffer)
    self.decoder.finished.connect(self.play)
    self.decoder.error.connect(self.error)
    self.decoder.stateChanged.connect(self.stateChanged)
    self.decoder.positionChanged.connect(self.positionChanged)
    self.decoder.bufferAvailableChanged.connect(self.bufferAvailableChanged)

    self.decoder.setAudioFormat(self.desiredFormat)
    self.decoder.setSourceFilename('D:\\python\\sounds\\30.mp3')
    fs = QFileInfo()
    print('File exists:' + str(fs.exists('D:\\python\\sounds\\30.mp3')))

    #using this to determine if we need to start byte array or append to it
    self.readamount = 0 

    #Expect this to be true since we haven't started yet 
    print('Decoder stopped?:' + str(self.decoder.state() == QAudioDecoder.StoppedState))    
    self.decoder.start()      
    print('Init finished, Decoder started on file:' + self.decoder.sourceFilename())      
  def bufferAvailableChanged(self,available):
   print('Available:' + str(available))  
  def positionChanged(self,position):
    print('Position:' + str(position)+'/'+str(self.decoder.duration))     
  def stateChanged(self,state):
    #Confirm state is what we expect
    print('Decoder state changed')
    if state == QAudioDecoder.StoppedState:
      print('Decoder stopped?:' + str(state == QAudioDecoder.StoppedState))
    else:
      print('Decoder decoding?:' + str(state == QAudioDecoder.DecodingState))      
  def error(self,err):
    print('Decoder error')
    print(self.decoder.errorString())  
  def readBuffer(self):
    print('Decoder ready for reading?')
    buffer = self.decoder.read()
    byteCount = buffer.byteCount()
    print('Bytecount in buffer:' + str(byteCount))
    if self.readamount == 0:
      self.ba = QByteArray()
      self.ba.fromRawData(buffer.constData().asstring(byteCount))
    else:
      self.ba.append(buffer.constData().asstring(byteCount))
    self.readamount = self.readamount + 1
    print('Bytearray size:' + str(self.ba.length()))      
  def play(self):
    print('Decoding finished, ready to play')

app = QtWidgets.QApplication([''])    
ad = AudioDecoder()

signal.signal(signal.SIGINT,signal.SIG_DFL)
app.exec_()

【问题讨论】:

  • *我注意到上面的代码中缺少一些东西,没有什么会导致我得到的结果,但是会导致意外行为的东西。例如,我在阅读后忘记更新 readamount,我注意到如果我将状态放入函数调用中,我可以避免在连接的 python 插槽中调用decoder.state,但这并没有改变行为,即使它比以上。
  • 在修复了代码中的许多小错误之后,我能够让它正常工作。不过,我只在 linux 上进行了测试(它使用 gstreamer 后端)。您可能想尝试使用wav 文件,因为保证支持该格式。
  • 后来我确实尝试过使用 wav 文件,也尝试了一些调整(将它们放在下面)。您是否介意通过您对代码所做的细微调整在这里给出答案?
  • 我怀疑我错过了一些基本的东西,你看到我没有,因为否则这只是一个 linux 与 windows 的问题,但我不知道为什么会这样。我会看看我是否可以设置一个虚拟机来测试它。
  • 我添加了一个答案。我怀疑它可能没有多大帮助,但你永远不知道......

标签: python-3.x qt5 pyqt5 qtmultimedia


【解决方案1】:

这是您原始脚本的 (Linux) 工作版本:

from PyQt5 import QtWidgets
from PyQt5 import QtCore, QtMultimedia
from PyQt5.QtCore import QObject, pyqtSlot, pyqtSignal, QByteArray, QIODevice, QFileInfo
from PyQt5.QtMultimedia import QAudioDecoder, QAudioFormat, QMediaObject, QAudioBuffer, QAudioOutput, QAudio

class AudioDecoder(QObject):
  def __init__(self):
    super(AudioDecoder,self).__init__()
    self.desiredFormat = QAudioFormat()
    self.desiredFormat.setChannelCount(2)
    self.desiredFormat.setCodec('audio/pcm')
    self.desiredFormat.setSampleType(QAudioFormat.UnSignedInt)
    self.desiredFormat.setSampleRate(48000)
    self.desiredFormat.setSampleSize(16)

    self.decoder = QAudioDecoder()
    self.decoder.setAudioFormat(self.desiredFormat)
    fs = QFileInfo('test.wav')
    self.decoder.setSourceFilename(fs.absoluteFilePath())
    print('File exists:' + str(fs.exists()))

    #self.connect(decoder,bufferReady(),None,readBuffer())
    self.decoder.bufferReady.connect(self.readBuffer)
    self.decoder.finished.connect(self.play)
    self.decoder.error.connect(self.error)
    self.decoder.stateChanged.connect(self.stateChanged)
    self.decoder.positionChanged.connect(self.positionChanged)
    self.decoder.bufferAvailableChanged.connect(self.bufferAvailableChanged)

    #using this to determine if we need to start byte array or append to it
    self.readamount = 0

    #Expect this to be true since we haven't started yet
    print('Decoder stopped:' + str(self.decoder.state() == QAudioDecoder.StoppedState))
    self.decoder.start()
    print('Init finished, Decoder started?')
  def bufferAvailableChanged(self):
   print(str(self.decoder.bufferAvailable()))
  def positionChanged(self):
    print(str(self.decoder.position())+'/'+str(self.decoder.duration()))
  def stateChanged(self):
    #Confirm state is what we expect
    print('Decoder state changed?')
    print('Decoder stopped?:' + str(self.decoder.state() == QAudioDecoder.StoppedState))
    print('Decoder decoding?:' + str(self.decoder.state() == QAudioDecoder.DecodingState))
  def error(self):
    print('Decoder error?')
    print(self.decoder.errorString())
  def readBuffer(self):
    print('Decoder ready for reading?')
    buffer = self.decoder.read()
    count = buffer.byteCount()
    print('Bytecount in buffer:' + str(count))
    if self.readamount == 0:
      self.ba = QByteArray()
      self.ba.fromRawData(buffer.constData().asstring(count))
      self.readamount = count
    else:
      self.ba.append(buffer.constData().asstring(count))
    print('Bytearray size:' + str(self.ba.length()))
  def play(self):
    print('Decoding finished, ready to play')

app = QtWidgets.QApplication([''])
ad = AudioDecoder()

import signal
signal.signal(signal.SIGINT, signal.SIG_DFL)
# press Ctrl+C to exit

app.exec_()

差异:

--- yours
+++ mine
@@ -1,3 +1,4 @@
+from PyQt5 import QtWidgets
 from PyQt5 import QtCore, QtMultimedia
 from PyQt5.QtCore import QObject, pyqtSlot, pyqtSignal, QByteArray, QIODevice, QFileInfo
 from PyQt5.QtMultimedia import QAudioDecoder, QAudioFormat, QMediaObject, QAudioBuffer, QAudioOutput, QAudio
@@ -14,9 +15,9 @@

     self.decoder = QAudioDecoder()
     self.decoder.setAudioFormat(self.desiredFormat)
-    self.decoder.setSourceFilename('D:\\python\\sounds\\30.mp3')
-    fs = QFileInfo()
-    print('MP3 exists:' + str(fs.exists('D:\\python\\sounds\\30.mp3')))
+    fs = QFileInfo('test.wav')
+    self.decoder.setSourceFilename(fs.absoluteFilePath())
+    print('File exists:' + str(fs.exists()))

     #self.connect(decoder,bufferReady(),None,readBuffer())
     self.decoder.bufferReady.connect(self.readBuffer)
@@ -34,9 +35,9 @@
     self.decoder.start()
     print('Init finished, Decoder started?')
   def bufferAvailableChanged(self):
-   print(str(decoder.available))
+   print(str(self.decoder.bufferAvailable()))
   def positionChanged(self):
-    print(str(decoder.position())+'/'+str(decoder.duration))
+    print(str(self.decoder.position())+'/'+str(self.decoder.duration()))
   def stateChanged(self):
     #Confirm state is what we expect
     print('Decoder state changed?')
@@ -48,14 +49,23 @@
   def readBuffer(self):
     print('Decoder ready for reading?')
     buffer = self.decoder.read()
-    print('Bytecount in buffer:' + str(buffer.byteCount))
+    count = buffer.byteCount()
+    print('Bytecount in buffer:' + str(count))
     if self.readamount == 0:
       self.ba = QByteArray()
-      self.ba.fromRawData(buffer.data(),buffer.byteCount())
+      self.ba.fromRawData(buffer.constData().asstring(count))
+      self.readamount = count
     else:
-      self.ba.append(buffer.data(),buffer.byteCount())
+      self.ba.append(buffer.constData().asstring(count))
     print('Bytearray size:' + str(self.ba.length()))
   def play(self):
     print('Decoding finished, ready to play')

+app = QtWidgets.QApplication([''])
 ad = AudioDecoder()
+
+import signal
+signal.signal(signal.SIGINT, signal.SIG_DFL)
+# press Ctrl+C to exit
+
+app.exec_()

输出:

File exists:True
Decoder stopped:True
Init finished, Decoder started?
Decoder state changed?
Decoder stopped?:False
Decoder decoding?:True
True
Decoder ready for reading?
0/196238
Bytecount in buffer:7680
Bytearray size:0
Decoder ready for reading?
40/196238
Bytecount in buffer:7680
Bytearray size:7680
Decoder ready for reading?
80/196238
Bytecount in buffer:7680
Bytearray size:15360
Decoder ready for reading?
120/196238
Bytecount in buffer:7680
Bytearray size:23040
Decoder ready for reading?
False
160/196238
Bytecount in buffer:7680
Bytearray size:30720
...

Bytecount in buffer:7680
Bytearray size:37662720
Decoder ready for reading?
False
196200/196238
Bytecount in buffer:7364
Bytearray size:37670084
Decoding finished, ready to play
Decoder state changed?
Decoder stopped?:True
Decoder decoding?:False

【讨论】:

  • 好的,我将不得不阅读一些内容以了解您所做的一些差异,但我主要了解问题所在。一切都没有进展的原因似乎与我没有你的 QtWidgets.QApplication 对象和执行我猜想人们可能称之为“应用程序循环”的事实有关。我见过一些需要它的 python 库和一些不需要它的 python 库,我不知道他们为什么这样做,但我想我现在明白了。除非主应用程序循环对象正在运行,否则所有这些信号/插槽内容不一定会发生。
  • 感谢您的回答,这非常有帮助。我知道我需要更多地关注所有这些不同库的整个应用程序循环。一旦我补充说我开始遇到错误,您的其余更改就完美地解决了这些问题。如果我记得从其他地方阅读,signal.signal 行的目的是让我仍然能够使用 ctrl + C 终止应用程序循环?
  • @MatthewSwaringen。啊-我确实想知道缺少 app 对象,但只是假设您的示例是更完整脚本的一部分。信号槽框架与事件框架是分开的,因此它通常不需要运行事件循环。但是,一些重要的例外是计时器和线程,因为它们需要是异步的。我猜多媒体类可能会广泛使用计时器和线程。
  • PS:是的,signal.signal 可以非常方便地测试没有 gui 的应用程序。如果没有要关闭的窗口,事件循环将永远坐在那里旋转。
  • 没有什么复杂的,一次只学一件。我只需要确保我不会不小心扔掉我不完全理解为什么需要它们的碎片……因为也许我需要它们,只是还不知道为什么。感谢您解释为什么可能需要事件循环。异步是我最终想要的,因为我的目标是同时播放多个曲目。
猜你喜欢
  • 2020-11-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多