我正在尝试使用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)发布代码示例。
答案 0 :(得分:2)
好的,好的。我没有得到答案,所以我继续研究并最终找到解决方案。 下面,我将展示两种不同的方法。首先-使用工作代码示例直接回答问题。第二-不同的方法,对于gstreamer来说似乎更本地化,而且绝对更简单。两者都能提供理想的结果-无缝的视频循环。
更改:
FLUSH
个事件(连续流中不应包含FLUSH
个事件)。代码:
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
消息(使用
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:不同的来源提出了稍微不同的解决方案。请参阅下面的链接。
相关主题和来源:
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
答案 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()