我需要从 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 毫秒。