Можно ли записать в QBuffer, который в данный момент читается? - PullRequest
1 голос
/ 12 июня 2019

Я пишу приложение PyQt5, но я думаю, что этот вопрос подходит и для PySide2 и Qt. Я пытаюсь записать звуковые данные (sinuosids) в буфер, а затем воспроизвести его по бесшовной петле. Однако всегда происходит перерыв, когда я добираюсь до конца буфера и возвращаюсь к началу.

Я думаю, что хочу постоянно читать и записывать в один и тот же буфер, это возможно?

Ниже приведена минимальная версия моего кода:

import struct
import sys

from PyQt5.QtCore import QBuffer, QByteArray, QIODevice
from PyQt5.QtWidgets import QApplication, QWidget
from PyQt5.QtMultimedia import QAudio, QAudioFormat, QAudioOutput

sample_rate = 44100
sample_size = 16
frequency = 1000
volume = 3276


class Window(QWidget):
    def __init__(self, parent=None):

        QWidget.__init__(self, parent)

        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.replay)

        self.buffer = QBuffer()
        self.buffer.open(QIODevice.ReadWrite)
        self.createData()
        self.buffer.seek(0)
        self.output.start(self.buffer)

    def createData(self):
        print("writing")
        data = QByteArray()
        for i in range(round(1 * sample_rate)):
            t = i / sample_rate
            value = int(volume * sin(2 * pi * frequency * t))
            data.append(struct.pack("<h", value))
        self.buffer.write(data)

    def replay(self):
        print("replaying", self.output.state(), QAudio.IdleState)
        if self.output.state() == QAudio.IdleState:
            self.buffer.seek(0)


if __name__ == "__main__":

    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec_())

Ответы [ 2 ]

1 голос
/ 13 июня 2019

Я думаю, что вы немного неправильно поняли, как 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, нопоскольку вы не имеете дело с потоками, это совсем не главное, а также потому, что в вашем примере вы уже закончили запись данных в буфер до того, как даже начали читать из него, и после этого ничего не пишете.

0 голосов
/ 12 июня 2019

В настоящее время PyQt не настроен для его самостоятельного тестирования, но попробуйте следующее:

Используйте сигнал QAudioOutput :: notify () . Рассчитайте продолжительность аудио буфера в миллисекундах. Используйте это как интервал с setNotifyInterval(). Подключите notify вместо stateChanged к вашему replay методу. Не проверяйте QAudio.IdleState, просто перемотайте буфер.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...