0

我需要从 rtsp 流中读取视频/音频并将它们显示在我的应用程序的 UI 中。显然,我应该在后台线程中读取流并从主线程显示视频。我编写了以下代码。它运行,但视频播放滞后。如果 pixbuf 是在后台线程中构建的,它仍然是滞后的。问题是构建和显示视频帧的 pixbuf 需要花费太多时间。

什么是最合适和最有效的方法?

from typing import Any, Dict, Optional, Tuple
from threading import Thread, Lock
from time import sleep, perf_counter

from ffpyplayer.player import MediaPlayer
from ffpyplayer.pic import Image, SWScale
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, Gdk, GLib, GdkPixbuf
import cairo


PATH = "rtsp://192.168.0.100:554/"


class MainWindow(Gtk.Window):

    def __on_realize(self, widget: Gtk.Widget):
        self.__player_thread.start()

    def __on_delete(self, widget: Gtk.Widget, event: Gdk.Event):
        if self.__player_thread.is_alive():
            with self.__player_thread_lock:
                self.__player_thread_exit_request = True

    def __on_destroy(self, widget: Gtk.Widget):
        Gtk.main_quit()

    def __on_media_error(self):
        pass

    def __on_media_end_reached(self):
        pass

    def __on_media_loaded(self, media_meta_data: Dict[str, Any]):
        self.__media_meta_data = media_meta_data
        self.__video_frame_size = media_meta_data["size"]
        self.__video_frame_width = self.__video_frame_size[0]
        self.__video_frame_height = self.__video_frame_size[1]
        self.__video_frame_stride = self.__video_frame_width * 3

    def __on_media_frame_read(self, frame: Image, pts: float):
        pixbuf = GdkPixbuf.Pixbuf.new_from_data(
            frame.to_memoryview()[0],
            GdkPixbuf.Colorspace.RGB,
            False,
            8,
            self.__video_frame_width,
            self.__video_frame_height,
            self.__video_frame_stride,
            None
        )
        self.__video_frame_image.set_from_pixbuf(pixbuf)

    @staticmethod
    def __get_media_meta_data(player: MediaPlayer, timeout: float) -> Dict[str, Any]:
        start_time = perf_counter()
        # Read the first frame
        while True:
            if (perf_counter() - start_time) >= timeout:
                break
            frame, value = player.get_frame()
            if value == "eof":
                break
            if frame is None:
                sleep(0.1)
                continue
            # The first frame is read; meta-data is ready
            meta_data = player.get_metadata()
            duration = meta_data.get("duration")
            fps = meta_data.get("frame_rate")
            size = meta_data.get("src_vid_size")
            # Return the meta-data
            if duration is not None and fps is not None and size is not None:
                fps = fps[0] / fps[1] if fps[1] > 0 else fps[0]
                return {
                    "duration": duration,
                    "fps": fps,
                    "size": size,
                    "is_video": duration > 0 and size[0] > 0 and size[1] > 0
                }
            break
        return None  # The meta-data is not read

    def __player_thread_method(self):
        player = MediaPlayer(PATH)
        media_meta_data = MainWindow.__get_media_meta_data(player, 2)
        if media_meta_data is None:
            player.close_player()
            GLib.idle_add(self.__on_media_error)
            return
        GLib.idle_add(self.__on_media_loaded, media_meta_data, priority=GLib.PRIORITY_HIGH)
        while True:
            with self.__player_thread_lock:
                if self.__player_thread_exit_request:
                    break
            frame, value = player.get_frame()
            if value == "eof":
                break
            if frame is None:
                sleep(0.1)
                continue
            img, pts = frame
            GLib.idle_add(self.__on_media_frame_read, img, pts, priotity=GLib.PRIORITY_HIGH)
            sleep(value)
        player.close_player()
        GLib.idle_add(self.__on_media_end_reached)

    def __init__(self) -> None:
        super().__init__()
        self.set_size_request(200, 200)
        self.connect("realize", self.__on_realize)
        self.connect("delete-event", self.__on_delete)
        self.connect("destroy", self.__on_destroy)
        self.__video_frame_image: Gtk.Image = Gtk.Image()
        self.add(self.__video_frame_image)
        self.__media_meta_data: Optional[Dict[str, Any]] = None
        self.__video_frame: Optional[Image] = None
        self.__video_frame_size: Optional[Tuple[int, int]] = None
        self.__video_frame_width: int = 0
        self.__video_frame_height: int = 0
        self.__video_frame_stride: int = 0
        self.__player_thread_lock: Lock = Lock()
        self.__player_thread_exit_request: bool = False
        self.__player_thread: Thread = Thread(target=self.__player_thread_method, args=[])



if __name__ == "__main__":
    win = MainWindow()
    win.show_all()
    Gtk.main()

编辑 1
在我的机器上,读取一帧几乎不需要 1 毫秒,但从帧的 RGB 缓冲区创建一个 pixbuf 大约需要 1100 毫秒。

4

0 回答 0