gstreamer中的无缝视频循环

时间:2018-12-12 16:24:03

标签: gstreamer python-gstreamer

我正在尝试使用gstreamer和python绑定循环播放视频。第一次尝试是挂钩EOS 消息并为管道生成查找消息:

import gi
gi.require_version("Gst", "1.0")
from gi.repository import Gst

import time

if not Gst.init_check()[0]:
    print("gstreamer initialization failed")

source0 = Gst.ElementFactory.make("filesrc", "source0")
assert source0 is not None
source0.set_property("location", "video0.mp4")

qtdemux0 = Gst.ElementFactory.make("qtdemux", "demux0")
assert qtdemux0 is not None

decoder0 = Gst.ElementFactory.make("nxvideodec", "video_decoder0")
assert decoder0 is not None

def demux0_pad_added(demux, pad):
    if pad.name == 'video_0':  # We expect exactly first one video stream
        pad.link(decoder0.get_static_pad("sink"))

qtdemux0.connect("pad-added", demux0_pad_added)

video_sink = Gst.ElementFactory.make("nxvideosink", "video_sink")
assert video_sink is not None

pipeline0 = Gst.Pipeline()
assert pipeline0 is not None
pipeline0.add(source0)
pipeline0.add(qtdemux0)
pipeline0.add(decoder0)
pipeline0.add(video_sink)

source0.link(qtdemux0)
"""qtdemux0 -> decoder0 dynamic linking"""
decoder0.link(video_sink)

######################################################

def main():
    message_bus = pipeline0.get_bus()
    pipeline0.set_state(Gst.State.PLAYING)

    while True:
        if message_bus.have_pending():  # Working without glib mainloop
            message = message_bus.pop()
            if message.type == Gst.MessageType.EOS:  # End-Of-Stream: loop the video, seek to beginning
                pipeline0.seek(1.0,
                              Gst.Format.TIME,
                              Gst.SeekFlags.FLUSH,
                              Gst.SeekType.SET, 0,
                              Gst.SeekType.NONE, 0)
            elif message.type == Gst.MessageType.ERROR:
                print("ERROR", message)
                break
        time.sleep(0.01) # Tried 0.001 - same result

if __name__ == "__main__":
    main()

除了一件事情外,它实际上还可以正常工作-从头开始并不是真正无缝的。我可以看到小故障。由于视频是无限动画,因此这种小故障实际上变得明显。我的第二次尝试是对已解码的帧使用队列并挂接EOS event

import gi
gi.require_version("Gst", "1.0")
from gi.repository import Gst

import time

if not Gst.init_check()[0]:
    print("gstreamer initialization failed")

source0 = Gst.ElementFactory.make("filesrc", "source0")
assert source0 is not None
source0.set_property("location", "video0.mp4")

qtdemux0 = Gst.ElementFactory.make("qtdemux", "demux0")
assert qtdemux0 is not None

decoder0 = Gst.ElementFactory.make("nxvideodec", "video_decoder0")
assert decoder0 is not None

def demux0_pad_added(demux, pad):
    if pad.name == 'video_0':  # We expect exactly first one video stream
        pad.link(decoder0.get_static_pad("sink"))

qtdemux0.connect("pad-added", demux0_pad_added)

queue = Gst.ElementFactory.make("queue", "queue")
assert queue is not None
video_sink = Gst.ElementFactory.make("nxvideosink", "video_sink")
assert video_sink is not None

pipeline0 = Gst.Pipeline()
assert pipeline0 is not None
pipeline0.add(source0)
pipeline0.add(qtdemux0)
pipeline0.add(decoder0)
pipeline0.add(queue)
pipeline0.add(video_sink)

source0.link(qtdemux0)
"""qtdemux0 -> decoder0 dynamic linking"""
decoder0.link(queue)
queue.link(video_sink)

######################################################

def cb_event(pad, info, *user_data):
    event = info.get_event()
    if event is not None and event.type == Gst.EventType.EOS:
        decoder0.seek(1.0,
                      Gst.Format.TIME,
                      Gst.SeekFlags.FLUSH,
                      Gst.SeekType.SET, 0,
                      Gst.SeekType.NONE, 0)
        return Gst.PadProbeReturn.DROP
    return Gst.PadProbeReturn.PASS

def main():
    dec0_src_pad = decoder0.get_static_pad("src")
    dec0_src_pad.add_probe(Gst.PadProbeType.BLOCK | Gst.PadProbeType.EVENT_DOWNSTREAM, cb_event)

    message_bus = pipeline0.get_bus()
    pipeline0.set_state(Gst.State.PLAYING)

    while True:
        # do nothing
        time.sleep(1)

if __name__ == "__main__":
    main()

在第一个EOS事件之后,播放刚刚停止。我已经尝试了几种不同的方法,例如: pass EOS事件, drop EOS并将偏移量添加到解码器的源焊盘,将seek事件发送到管道本身和其他对象。但是我无法正常工作。

为了理解,我还尝试启用调试模式并使用填充探针编写自己的管道活动记录器。调试模式不是很有用,日志非常庞大并且缺少一些细节。我自己的日志包括上游/下游事件和缓冲区定时信息。但是,我仍然不明白哪里出了问题以及如何使它工作。

很明显,我不仅缺少一些东西,而且不了解有关gstreamer管道如何工作的一些基本知识。

所以,问题是:我应该如何处理第二版代码才能使其正常工作?
其他问题:是否有一些工具或技术可以清楚地了解管道及其包含的元素中正在发生的事情?

我将非常感谢详细的答案。对我来说, 了解 对我来说很重要,而不是仅仅使程序正常运行。

p.s。 程序在NanoPi S2板上的GNU / Linux下运行。视频存储在MP4容器中(无音频),并用h264压缩。请随时以任何语言(不一定是Python)发布代码示例。

3 个答案:

答案 0 :(得分:2)

好的,好的。我没有得到答案,所以我继续研究并最终找到解决方案。 下面,我将展示两种不同的方法。首先-使用工作代码示例直接回答问题。第二-不同的方法,对于gstreamer来说似乎更本地化,而且绝对更简单。两者都能提供理想的结果-无缝的视频循环。

正确的代码(答案,但不是最佳方法)

更改:

  1. 添加了视频时长查询。每个循环中,我们都应为视频时长值增加时间偏移。可以模拟无限的连续流。
  2. 发出的寻求事件已移至单独的线程。根据{{​​3}},我们无法从流线程中发出寻求事件。另外,请查看this post(提到文章的链接)。
  3. 事件回调现在会丢弃FLUSH个事件(连续流中不应包含FLUSH个事件)。
  4. 视频解码器从nxvideodec更改为avdec_h264。这与最初的问题无关,已完成this file

代码:

import gi
gi.require_version("Gst", "1.0")
from gi.repository import Gst

import time
import threading

if not Gst.init_check()[0]:
    print("gstreamer initialization failed")

source0 = Gst.ElementFactory.make("filesrc", "source0")
assert source0 is not None
source0.set_property("location", "video0.mp4")

qtdemux0 = Gst.ElementFactory.make("qtdemux", "demux0")
assert qtdemux0 is not None

decoder0 = Gst.ElementFactory.make("avdec_h264", "video_decoder0")
assert decoder0 is not None

def demux0_pad_added(demux, pad):
    if pad.name == 'video_0':  # We expect exactly first one video stream
        pad.link(decoder0.get_static_pad("sink"))

qtdemux0.connect("pad-added", demux0_pad_added)

queue = Gst.ElementFactory.make("queue", "queue")
assert queue is not None
video_sink = Gst.ElementFactory.make("nxvideosink", "video_sink")
assert video_sink is not None

pipeline0 = Gst.Pipeline()
assert pipeline0 is not None
pipeline0.add(source0)
pipeline0.add(qtdemux0)
pipeline0.add(decoder0)
pipeline0.add(queue)
pipeline0.add(video_sink)

source0.link(qtdemux0)
"""qtdemux0 -> decoder0 dynamic linking"""
decoder0.link(queue)
queue.link(video_sink)

# UPD: Get video duration
pipeline0.set_state(Gst.State.PAUSED)
assert pipeline0.get_state(Gst.CLOCK_TIME_NONE).state == Gst.State.PAUSED
duration_ok, duration = pipeline0.query_duration(Gst.Format.TIME)
assert duration_ok

######################################################

seek_requested = threading.Event()
# UPD: Seek thread. Wait for seek request from callback and generate seek event
def seek_thread_func(queue_sink_pad):
    cumulative_offset = 0
    while True:
        seek_requested.wait()
        seek_requested.clear()
        decoder0.seek(1.0,
                      Gst.Format.TIME,
                      Gst.SeekFlags.FLUSH,
                      Gst.SeekType.SET, 0,
                      Gst.SeekType.NONE, 0)
        # Add offset. It is important step to ensure that downstream elements will 'see' infinite contiguous stream
        cumulative_offset += duration
        queue_sink_pad.set_offset(cumulative_offset)

def cb_event(pad, info):
    event = info.get_event()
    if event is not None:
        if event.type == Gst.EventType.EOS:  # UPD: Set 'seek_requested' flag
            seek_requested.set()
            return Gst.PadProbeReturn.DROP
        elif event.type == Gst.EventType.FLUSH_START or event.type == Gst.EventType.FLUSH_STOP:  # UPD: Drop FLUSH
            return Gst.PadProbeReturn.DROP
    return Gst.PadProbeReturn.OK

def main():
    queue_sink_pad = queue.get_static_pad("sink")

    # UPD: Create separate 'seek thread'
    threading.Thread(target=seek_thread_func, daemon=True, args=(queue_sink_pad,)).start()

    dec0_src_pad = decoder0.get_static_pad("src")
    dec0_src_pad.add_probe(Gst.PadProbeType.EVENT_DOWNSTREAM | Gst.PadProbeType.EVENT_FLUSH,
                           cb_event)

    pipeline0.set_state(Gst.State.PLAYING)

    while True:
        # do nothing
        time.sleep(1)

if __name__ == "__main__":
    main()

此代码有效。当队列中的缓冲区仍在播放时,可以有效执行搜索。但是,我相信它可能包含一些缺陷甚至错误。例如,SEGMENT个事件通过RESET标志向下游传递;这似乎不正确。实现此方法的更清晰(并且可能更正确/可靠)的方法是创建一个gstreamer插件。插件将管理事件并调整事件和缓冲区的时间戳。

但是有一个更简单的本机解决方案:

使用分段搜索和SEGMENT_DONE消息

根据for a very special reason

  

(使用GST_SEEK_FLAG_SEGMENT进行细分搜索不会发出EOS   在播放段的末尾,但会发布SEGMENT_DONE   在公共汽车上的消息。此消息是由驱动   在管道中播放,通常是多路分配器。收到后   消息,应用程序可以重新连接管道或发出其他消息   在管道中寻找事件。 自该消息最早发布以来   在可能的管道中,应用程序有一些时间来发布新的   力求使过渡无缝。通常,允许的延迟为   由接收器的缓冲区大小以及任何缓冲区的大小定义   在管道中排队。

消息SEGMENT_DONE实际上是在队列为空之前发布的。这给了足够的时间执行下一次搜索。因此,我们要做的就是在播放开始时发出分段搜索。然后等待SEGMENT_DONE消息并发送下一个非冲洗搜索事件。 这是工作示例:

import gi
gi.require_version("Gst", "1.0")
from gi.repository import Gst

import time

if not Gst.init_check()[0]:
    print("gstreamer initialization failed")

source0 = Gst.ElementFactory.make("filesrc", "source0")
assert source0 is not None
source0.set_property("location", "video0.mp4")

qtdemux0 = Gst.ElementFactory.make("qtdemux", "demux0")
assert qtdemux0 is not None

decoder0 = Gst.ElementFactory.make("nxvideodec", "video_decoder0")
assert decoder0 is not None

def demux0_pad_added(demux, pad):
    if pad.name == 'video_0':  # We expect exactly first one video stream
        pad.link(decoder0.get_static_pad("sink"))

qtdemux0.connect("pad-added", demux0_pad_added)

queue = Gst.ElementFactory.make("queue", "queue")
assert queue is not None
video_sink = Gst.ElementFactory.make("nxvideosink", "video_sink")
assert video_sink is not None

pipeline0 = Gst.Pipeline()
assert pipeline0 is not None
pipeline0.add(source0)
pipeline0.add(qtdemux0)
pipeline0.add(decoder0)
pipeline0.add(queue)
pipeline0.add(video_sink)

source0.link(qtdemux0)
"""qtdemux0 -> decoder0 dynamic linking"""
decoder0.link(queue)
queue.link(video_sink)

######################################################

def main():
    message_bus = pipeline0.get_bus()
    pipeline0.set_state(Gst.State.PLAYING)
    pipeline0.get_state(Gst.CLOCK_TIME_NONE)
    pipeline0.seek(1.0,
                   Gst.Format.TIME,
                   Gst.SeekFlags.SEGMENT,
                   Gst.SeekType.SET, 0,
                   Gst.SeekType.NONE, 0)

    while True:
        if message_bus.have_pending():  # Working without glib mainloop
            message = message_bus.pop()
            if message.type == Gst.MessageType.SEGMENT_DONE:
                pipeline0.seek(1.0,
                              Gst.Format.TIME,
                              Gst.SeekFlags.SEGMENT,
                              Gst.SeekType.SET, 0,
                              Gst.SeekType.NONE, 0)
            elif message.type == Gst.MessageType.ERROR:
                print("bus ERROR", message)
                break
        time.sleep(0.01)

if __name__ == "__main__":
    main()

在默认队列配置下,SEGMENT_DONE消息在播放最后一个视频帧之前大约1秒发布。非冲洗寻求确保不会丢失任何帧。在一起,可以提供完美的结果-真正无缝的视频循环。

注意:我将管道切换到PLAYING状态,然后执行初始的非刷新搜索。或者,我们可以将管道切换到PAUSED状态,执行 flushing 段搜索,然后将管道切换到PLAYING状态。

注2:不同的来源提出了稍微不同的解决方案。请参阅下面的链接。


相关主题和来源:

  1. http://gstreamer-devel.966125.n4.nabble.com/Flushing-the-data-in-partial-pipeline-tp4681893p4681899.html
    • https://cgit.freedesktop.org/gstreamer/gst-editing-services/tree/plugins/nle/nlesource.c
  2. documentation

答案 1 :(得分:0)

我建议看一下gst-play-1.0应用程序以及GStreamer的playbin元素。

参见此处:https://github.com/GStreamer/gst-plugins-base/blob/master/tools/gst-play.c

此文件支持--gapless选项,可以无间隙播放许多文件。它利用了about-to-finish元素的playbin信号。

这个特定的应用程序使用多个文件而不是相同的文件来执行此操作,但是我想您可以尝试多次提供相同的文件以进行测试,如果它确实是无缝的或在您的方法1中出现相同的问题。

基本上,我认为EOS太晚了,因为解码器的deinit / init / process处理和刷新流水线,无法及时准备好第一帧。同样,刷新将重置您的流,管道再次进入预滚动并同步到新时钟。确实不是从内部开始的连续流。

或者,也许GStreamer编辑服务也可以做到这一点。但这可能与多个轨道配合使用,这意味着它可能试图同时实例化多个解码器实例以进行并行处理-这可能是您主板上的问题。

最后的选择是将MP4多路分解为原始的双流,将此比特流连续循环到套接字中并从中解码。然后它将显示为正在播放的无限比特流。

编辑: 尝试multifilesrc的{​​{1}}属性也许值得一试,看看该文件是否运行良好,还是必须在文件之间进行刷新。

答案 2 :(得分:0)

我正在使用SEGMENT_DONE方法:

import sys
import gi
gi.require_version('Gst', '1.0')
from gi.repository import Gst, GLib
Gst.init(None)

pipeline = Gst.parse_launch('uridecodebin uri=file://%s name=d d. ! autovideosink d. ! autoaudiosink' %sys.argv[1])
bus = pipeline.get_bus()
bus.add_signal_watch()

def on_segment_done(bus, msg):
    pipeline.seek(1.0,
        Gst.Format.TIME,
        Gst.SeekFlags.SEGMENT,
        Gst.SeekType.SET, 0,
        Gst.SeekType.NONE, 0)
    return True
bus.connect('message::segment-done', on_segment_done)

pipeline.set_state(Gst.State.PLAYING)
pipeline.get_state(Gst.CLOCK_TIME_NONE)
pipeline.seek(1.0,
    Gst.Format.TIME,
    Gst.SeekFlags.SEGMENT,
    Gst.SeekType.SET, 0,
    Gst.SeekType.NONE, 0)

GLib.MainLoop().run()