Я думаю, что вы немного неправильно поняли, как QAudioOutput (и объекты аудиоустройства в целом) ведет себя, читает и воспроизводит аудиоданные.
Когда вы play()
QIODevice, экземпляр QAudioOutput считывает порцию данныхв соответствии с настройками буфера аудиоустройства (но оно не всегда совпадает с bufferSize()
) и «отправляет» его на аппаратное устройство, которое фактически воспроизводит его: чтение данных и «воспроизведение» являются асинхронными.play()
выполняет вызов QIODevice.readData (maxLen), где maxLen - это некоторая длина данных, необходимая аудиоустройству, чтобы гарантировать непрерывное заполнение аудио буфера, в противном случае вы получите опустошение буфера, означающее, что устройствопытается воспроизвести, но не имеет данных для этого.
В вашем случае это также означает, что в определенный момент аудиоустройство может запросить некоторые данные в буфер данных на его длину,поэтому вам нужно добавить дополнительные данные для возврата.
Кроме того, если вы ждете сигнала stateChanged, это означает, что больше нет данных для чтения из буфера данных (который не буфер аудиоустройства);в этот момент QAudioDevice останавливает аудиоустройство и очищает его буфер, поэтому, если вы «переиграете», вы, очевидно, услышите пробел, поскольку устройство «перезагружается».
Если вы хотите воспроизвести некоторые данныев цикле вам нужно будет реализовать свой собственный QIODevice, так как он должен непрерывно подавать звуковое устройство, как только оно достигло своего конца.Обратите внимание, что это минимальный пример, вы можете захотеть дополнительно реализовать запись в буфер данных (и обновить его позицию поиска)
class AudioBuffer(QIODevice):
def __init__(self):
QIODevice.__init__(self)
self.bytePos = 0
self.data = QByteArray()
for i in range(round(1 * sample_rate)):
t = i / sample_rate
value = int(volume * sin(2 * pi * frequency * t))
self.data.append(struct.pack("<h", value))
def seek(self, pos):
self.bytePos = pos
return True
def readData(self, maxLen):
data = self.data[self.bytePos:self.bytePos + maxLen]
if len(data) < maxLen:
# we've reached the end of the data, restart from 0
# so the wave is continuing from its beginning
self.bytePos = maxLen - len(data)
data += self.data[:self.bytePos]
else:
self.bytePos += maxLen
return data.data()
class Window(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
layout = QHBoxLayout()
self.setLayout(layout)
self.playButton = QPushButton('Play')
self.playButton.setCheckable(True)
self.playButton.toggled.connect(self.togglePlay)
layout.addWidget(self.playButton)
format = QAudioFormat()
format.setChannelCount(1)
format.setSampleRate(sample_rate)
format.setSampleSize(sample_size)
format.setCodec("audio/pcm")
format.setByteOrder(QAudioFormat.LittleEndian)
format.setSampleType(QAudioFormat.SignedInt)
self.output = QAudioOutput(format, self)
self.output.stateChanged.connect(self.stateChanged)
self.buffer = AudioBuffer()
self.buffer.open(QIODevice.ReadWrite)
def togglePlay(self, state):
self.buffer.seek(0)
if state:
self.output.start(self.buffer)
else:
self.output.reset()
def stateChanged(self, state):
self.playButton.blockSignals(True)
self.playButton.setChecked(state == QAudio.ActiveState)
self.playButton.blockSignals(False)
Тем не менее, я немного поиграл с QAudioDevice и я 'боюсь, это не очень надежно, по крайней мере, под PyQt / PySide.Хотя он отлично работает для небольших примеров и простых случаев, он становится ненадежным, если вам нужно сделать что-то еще, что требует некоторой обработки во время воспроизведения аудио (например, сложные виджеты / рисунки QGraphics), и использование QThreads не поможет вам, как вы думаете: например, в MacOS вы не можете moveToThread()
a QAudioOutput.
Я настоятельно рекомендую вам использовать PyAudio, классы которого ведут себя аналогично QAudioOutput, но могут работать в другом потоке.Очевидно, что если вам все еще нужно непрерывное воспроизведение, проблема «readData» остается той же, так как вам понадобится какой-то объект данных, который может циклически сам себя.
PS: название этого вопроса немного не в темупод рукой, вы можете подумать об изменении его.Кстати, ответ «нет», поскольку чтение и запись IODevice не могут быть параллельными: чтение должно «блокировать» запись (но не дальнейшее чтение) и наоборот, и обе операции внутренне перемещают поиск pos
IODevice, нопоскольку вы не имеете дело с потоками, это совсем не главное, а также потому, что в вашем примере вы уже закончили запись данных в буфер до того, как даже начали читать из него, и после этого ничего не пишете.