【问题标题】:How to change a VideoOverlay's window handle after it has already been set?设置后如何更改 VideoOverlay 的窗口句柄?
【发布时间】:2021-12-29 08:46:28
【问题描述】:

背景

我正在寻找一种方法来更改我的视频被渲染到的窗口。这是必要的,因为在某些情况下窗口可能会被破坏,例如当我的应用程序切换到全屏模式时。

代码

实现画布时,视频源和接收器连接。然后当prepare-window-handle 消息发出时,我存储了对发送它的VideoOverlay 元素的引用。单击“切换画布”按钮会在此元素上调用set_window_handle(new_handle),但视频会继续在原始画布中呈现。

import sys

import gi
gi.require_version('Gtk', '3.0')
gi.require_version('Gst', '1.0')
gi.require_version('GstVideo', '1.0')
from gi.repository import Gtk, Gst, GstVideo
Gst.init(None)


if sys.platform == 'win32':
    import ctypes

    PyCapsule_GetPointer = ctypes.pythonapi.PyCapsule_GetPointer
    
    PyCapsule_GetPointer.restype = ctypes.c_void_p
    PyCapsule_GetPointer.argtypes = [ctypes.py_object]

    gdkdll = ctypes.CDLL('libgdk-3-0.dll')
    gdkdll.gdk_win32_window_get_handle.argtypes = [ctypes.c_void_p]
    
    def get_window_handle(widget):
        window = widget.get_window()
        if not window.ensure_native():
            raise Exception('video playback requires a native window')
        
        window_gpointer = PyCapsule_GetPointer(window.__gpointer__, None)
        handle = gdkdll.gdk_win32_window_get_handle(window_gpointer)
        
        return handle
else:
    from gi.repository import GdkX11

    def get_window_handle(widget):
        return widget.get_window().get_xid()


class VideoPlayer:
    def __init__(self, canvas):
        self._canvas = canvas
        self._setup_pipeline()
    
    def _setup_pipeline(self):
        # The element with the set_window_handle function will be stored here
        self._video_overlay = None
        
        self._pipeline = Gst.ElementFactory.make('pipeline', 'pipeline')
        src = Gst.ElementFactory.make('videotestsrc', 'src')
        video_convert = Gst.ElementFactory.make('videoconvert', 'videoconvert')
        auto_video_sink = Gst.ElementFactory.make('autovideosink', 'autovideosink')

        self._pipeline.add(src)
        self._pipeline.add(video_convert)
        self._pipeline.add(auto_video_sink)
        
        # The source will be linked later, once the canvas has been realized
        video_convert.link(auto_video_sink)
        
        self._video_source_pad = src.get_static_pad('src')
        self._video_sink_pad = video_convert.get_static_pad('sink')
        
        self._setup_signal_handlers()
    
    def _setup_signal_handlers(self):
        self._canvas.connect('realize', self._on_canvas_realize)
        
        bus = self._pipeline.get_bus()
        bus.enable_sync_message_emission()
        bus.connect('sync-message::element', self._on_sync_element_message)
    
    def _on_sync_element_message(self, bus, message):
        if message.get_structure().get_name() == 'prepare-window-handle':
            self._video_overlay = message.src
            self._video_overlay.set_window_handle(self._canvas_window_handle)
    
    def _on_canvas_realize(self, canvas):
        self._canvas_window_handle = get_window_handle(canvas)
        self._video_source_pad.link(self._video_sink_pad)
        
    def start(self):
        self._pipeline.set_state(Gst.State.PLAYING)
    

window = Gtk.Window()
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
window.add(vbox)

canvas_box = Gtk.Box()
vbox.add(canvas_box)

canvas1 = Gtk.DrawingArea()
canvas1.set_size_request(400, 400)
canvas_box.add(canvas1)

canvas2 = Gtk.DrawingArea()
canvas2.set_size_request(400, 400)
canvas_box.add(canvas2)

player = VideoPlayer(canvas1)
canvas1.connect('realize', lambda *_: player.start())

def switch_canvas(btn):
    handle = get_window_handle(canvas2)
    print('Setting handle:', handle)
    player._video_overlay.set_window_handle(handle)

btn = Gtk.Button(label='switch canvas')
btn.connect('clicked', switch_canvas)
vbox.add(btn)

window.connect('destroy', Gtk.main_quit)
window.show_all()
Gtk.main()

问题/疑问

第二次调用set_window_handle() 似乎没有任何效果 - 视频继续呈现到原始窗口中。

在调用 set_window_handle() 之前,我尝试将管道设置为 PAUSED、READY 和 NULL 状态,但这没有帮助。

我也尝试用here 所见的新视频接收器替换自动视频接收器,但这也不起作用。

如何更改窗口句柄而不会过多地中断播放?我必须完全重新创建管道吗?

【问题讨论】:

  • 我在 Windows 上进行测试,所以我不能保证 get_window_handle 可以在 Mac/linux 上运行。如果有什么问题,请告诉我,我会尽力解决。
  • 显然set_window_handle 解决方案适用于某些接收器而不适用于其他接收器。在我的 Windows PC 上,它是 GstD3D11VideoSink,它似乎忽略了对 set_window_handle 的第二次调用,但在不同的(linux)PC 上,此代码有效。我想找到一种可以可靠地与任何水槽配合使用的解决方案。
  • 出于好奇:代码是否在 Linux 上工作?
  • @CristiFati 我不知道它是否适用于每台 linux PC,但一个熟人为我测试了它,它适用于他们的。

标签: python gstreamer window-handles


【解决方案1】:

查看source code,似乎至少基于 GL 的 VideoOverlay 元素实现会在expose 事件上更新窗口 ID。

所以你可以尝试调用:

player._video_overlay.expose()

在窗口句柄改变后重新初始化 GL 场景。

如果这不起作用,您可以创建一个新的 VideoOverlay 元素和add it dynamically,而无需停止图表。

【讨论】:

  • 感谢您的回答。不幸的是.expose() 对我没有任何帮助;我想这可能取决于该元素是否有效。所以看起来我必须创建一个新的 autovideosink,但我还没有完全弄清楚如何在 PLAYING 管道中取消链接元素。据我所知,您链接的教程仅显示了如何在运行时链接元素,而不是如何取消链接。你能帮我解决这个问题吗?我昨天已经发了a related question,所以你可以在那里写一个答案。
  • @Aran-Fey stackoverflow.com/questions/3074145/… 有东西,但我自己没试过。
  • 我已经看到了这个问题,但我还是想不通:(
  • 是的,有时会很困难。在一个应用程序中,我使用了应用程序接收器并在管道之间手动传递缓冲区,以便我可以单独停止它们。但这可能会破坏音频/视频同步。
  • 添加对expose() 的调用使左侧画布中的视频停止,并且右侧画布中的视频看起来更正确,所以几乎在那里但不完全。这一定是在某处...
猜你喜欢
  • 1970-01-01
  • 2010-09-13
  • 2015-10-23
  • 1970-01-01
  • 2016-12-26
  • 1970-01-01
  • 1970-01-01
  • 2013-11-11
  • 2017-10-02
相关资源
最近更新 更多