Сообщения не принимаются при создании конвейера GStreamer в QThread - PullRequest
0 голосов
/ 27 октября 2019

У меня есть приложение PyQt, которое создает конвейер GStreamer, когда пользователь нажимает кнопку и прослушивает сообщения на шине этого конвейера.

import gi

gi.require_version("Gst", "1.0")

from gi.repository import Gst, GLib
from PyQt5.QtWidgets import QApplication, QPushButton


Gst.init()

pipeline = None


def on_pipeline_message(bus, message):
    print("Got a message from pipeline:", message.type)
    return True


def on_button_press():
    global pipeline

    pipeline = Gst.parse_launch("videotestsrc ! xvimagesink")
    pipeline.bus.add_watch(GLib.PRIORITY_DEFAULT, on_pipeline_message)
    pipeline.set_state(Gst.State.PLAYING)


app = QApplication([])

playback_button = QPushButton("Press to Start Playback", None)
playback_button.clicked.connect(on_button_press)
playback_button.show()

app.exec()

Приведенный выше код работает, как и ожидалось, и моя функция обратного вызова on_pipeline_messageназывается. Однако, если я решу переместить код создания конвейера в отдельный QThread:

class MakePipelineThread(QThread):
    def run(self):
        global pipeline

        pipeline = Gst.parse_launch("videotestsrc ! xvimagesink")
        pipeline.bus.add_watch(GLib.PRIORITY_DEFAULT, on_pipeline_message)
        pipeline.set_state(Gst.State.PLAYING)

... и запустить этот QThread при нажатии кнопки вместо:

make_pipeline_thread = MakePipelineThread()


def on_button_press():
    make_pipeline_thread.start()

My on_pipeline_message обратный вызов больше не выполняется. Почему это важно, если я создаю конвейер в отдельном QThread? Как я могу продолжать получать сообщения?

1 Ответ

0 голосов
/ 27 октября 2019

GStreamer и Qt оба используют тип GLib.MainContext для асинхронной обработки сообщений между отправителями и получателями 1 . По умолчанию сообщения GStreamer и Qt передаются через глобальный экземпляр MainContext по умолчанию, доступный через GLib.MainContext.default(). Когда сообщения отправляются, будь то из пользовательского ввода, из конвейера или где-либо еще, они изначально хранятся в очереди сообщений. Qt регулярно выполняет итерацию MainContext, который извлекает сообщения из очереди и отправляет их любым слушателям. Вот почему вы можете получать сообщения от вашего конвейера GStreamer, когда конвейер запускается в потоке пользовательского интерфейса.

Однако, когда Qt запускает новый QThread, также создает новый объект MainContext и устанавливаетэто как контекст по умолчанию для этого потока . Когда вы создаете конвейер GStreamer в QThread, ваш конвейер и наблюдатель регистрируются в этом контексте вместо глобального значения по умолчанию. Qt не выполняет итерацию MainContext QThread автоматически для вас, поэтому сообщения не будут получены, если вы сами не выполните итерацию контекста . Это можно сделать, вызвав QCoreApplication.processEvents() в QThread.

class MakePipelineThread(QThread):
    def run(self):
        global pipeline

        pipeline = Gst.parse_launch("videotestsrc ! xvimagesink")
        pipeline.bus.add_watch(GLib.PRIORITY_DEFAULT, on_pipeline_message)
        pipeline.set_state(Gst.State.PLAYING)

        # Process events until the pipeline reaches the null state
        _, state, _ = pipeline.get_state(Gst.CLOCK_TIME_NONE)
        while state != Gst.State.NULL:
            QCoreApplication.processEvents()
            _, state, _ = pipeline.get_state(Gst.CLOCK_TIME_NONE)

Это, конечно, означает, что QThread будет работать до тех пор, пока работает конвейер, а не останавливаться, как только конвейер будет построен.

В качестве альтернативы вы можете использовать set_sync_handler вместо add_watch. Это заставляет шину немедленно выполнить обратный вызов в том же потоке, который отправил сообщение, а не отправлять сообщение асинхронно через MainContext.

class MakePipelineThread(QThread):
    def run(self):
        global pipeline

        pipeline = Gst.parse_launch("videotestsrc ! xvimagesink")
        pipeline.bus.set_sync_handler(on_pipeline_message)
        pipeline.set_state(Gst.State.PLAYING)

Это устраняет необходимость полной итерации MainContext, но это означает, чтоВаш обратный вызов будет выполняться в одном из потоковых потоков GStreamer и заблокирует этот поток от выполнения другой работы во время выполнения обратного вызова.


1 Некоторые платформы могут компилировать Qt безПоддержка GLib, в этом случае Qt будет использовать свою собственную систему обработки событий. В этом случае эта проблема не возникнет, пока приложение выполняет итерацию самого глобального контекста по умолчанию. Вы можете установить переменную окружения QT_NO_GLIB в 1, чтобы Qt не использовал GLib во время выполнения.

...