I hate to say how long it took me to get to this point but I had real trouble completely understanding PyQt5 and how it relates to the C++ code I was seeing on the Qt website but I think .. I get it, or... at least I thought I did until this completely failed to work. I'll start with the output I'm getting, that tells me I have a file that really exists. I've tried mp3 and ogg version in case for some reason AudioDecoder can't decode the MP3 even though other parts of QtMultimedia have been able to play it (I'm trying to get lower level so I can apply panning to the audio and shift the left/right balance, and maybe other fun things once I figure that out).
Debug output:
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.
Code:
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()
Revised code, attempting WAV, still not working though:
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()
My update code post answer from below, and it works with 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_()
Here is my (Linux) working version of your original script:
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_()
Diff:
--- 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_()
Output:
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