在EOS上用Python重新启动GStreamer管道

时间:2016-12-04 23:49:01

标签: python raspberry-pi gstreamer python-gstreamer gstreamer-1.0

我正在研究在RPi3上运行的Python脚本,并使用gstreamer连接到我的IP Camera的RTSP feed,并将解码的H264帧提供给我的Python脚本。

以下是用于从相机获取帧的gstreamear管道:

rtspsrc location=rtsp://ip:port/path ! rtph264depay ! h264parse ! decodebin ! videoconvert ! video/x-raw, format=BGR ! appsink name=sink

问题:由于相机处于慢速/不可靠的互联网连接上,我偶尔会丢帧,这会导致生成EOS信号。互联网连接带宽可能是一个问题,有时会引起流的兴趣。

目标:在EOS信号上,我想重新启动管道,以便gstreamer可以继续为我的程序提供帧。

我尝试了什么:我有一个附加到总线的回调函数,它使用

来侦听消息
bus.connect("message", on_message)

在“on_message”功能中,我能够成功确定该消息是否为EOS信号。如果我检测到EOS信号,我会尝试重新启动管道:

pipline.set_state(Gst.State.NULL)
pipline.set_state(Gst.State.PAUSED)
pipline.set_state(Gst.State.PLAYING)

不幸的是,这不起作用。一旦我的scipt尝试使用上面的代码片段重新启动管道,我就会在总线上出现以下错误。我知道相机在线,所以这不是问题。

('ERROR!!!:', 'source', '!:!', 'Could not read from resource.')
('Debug info:', 'gstrtspsrc.c(5583): gst_rtspsrc_send (): /GstPipeline:pipeline0/GstRTSPSrc:source:\nGot error response: 400 (Bad Request).')
('ERROR!!!:', 'source', '!:!', 'Could not write to resource.')
('Debug info:', 'gstrtspsrc.c(6933): gst_rtspsrc_close (): /GstPipeline:pipeline0/GstRTSPSrc:source:\nCould not send message. (Generic error)')
('ERROR!!!:', 'udpsrc2', '!:!', 'Internal data stream error.')
('Debug info:', 'gstbasesrc.c(2951): gst_base_src_loop (): /GstPipeline:pipeline0/GstRTSPSrc:source/GstUDPSrc:udpsrc2:\nstreaming stopped, reason not-linked (-1)')

如果问题出在rtspsrc上,我还尝试使用filesrc使用短的本地视频,并使用当gstreamer到达视频文件末尾时生成的EOS信号来测试我是否能够重新启动管道。以下是我用来播放本地视频的示例管道:

filesrc location=file.mp4 ! qtdemux ! decodebin ! videoconvert ! video/x-raw, format=BGR ! appsink name=sink

如果成功,它应该再次开始浏览视频,但没有运气......相反,我得到以下错误,这让我觉得filesrc需要重置。与rtsp示例相同,其中rtspsrc

生成错误
('ERROR!!!:', 'qtdemux0', '!:!', 'Internal data stream error.')
('Debug info:', 'qtdemux.c(5847): gst_qtdemux_loop (): /GstPipeline:pipeline0/GstQTDemux:qtdemux0:\nstreaming stopped, reason not-linked (-1)')

有人可以解决这个问题吗?谢谢!

3 个答案:

答案 0 :(得分:0)

当你这样做时

pipline.set_state(Gst.State.NULL)

这并不意味着管道立即达到了这种状态。确保您可以调用pipeline.get_state()。另外我建议处理set_state()的返回值。最后回到比赛,没有必要先去PAUSED(除非你想在PUASED和PLAYING之间做点什么)。

答案 1 :(得分:0)

我在块已动态创建源焊盘的管道上遇到了(-1)错误,例如rtpsrc,rtpbin,decodebin块只是执行此操作的三个块。

我发现您总是需要一个“填充键盘”的处理程序。即使在“脚本”管道中。 您必须有程序代码在管道上安装填充填充的处理程序-在我的管道中,我为块实例分配了名称(rtpbin name =“ rtpbin”),以便可以通过已知名称查找它们。

这是因为,例如,使用gst-launch-1.0的一般gstreamer启动会正确构造一次管道,但不允许使用动态源垫。如果为已经存在的连接创建了一个新的填充板,那么它所做的就是退出。

尽管文档似乎暗示着进入NULL状态并返回到PLAYING将重新连接管道,但对于这些动态创建的打击垫而言,情况并非如此,这些打击垫出现在诸如“ src_%u_%u_%u”之类的文档中,其中第二个和格式字符串的第三个参数是从相关块内部生成的,并且用户无法预测(第一个%u只是从零开始的实例号)。

在普通的gstreamer示例中,它们倾向于显示的是添加了填充的处理程序,如果未连接接收垫,则在源和接收垫之间创建链接。

实际上,在这些动态创建的填充板之一中添加填充板的处理程序调用的情况下,源填充板已更改标识并消失,但接收器填充板仍处于“已连接”状态。

我发现有效的方法是明确断开水槽垫..类似这样的

// ... snip from pad_added_handler 

// If the pad believes it is linked, correctly or not, disconnect it 
if(gst_pad_is_linked(sinkPad))
{
   GstPad * peer = gst_pad_get_peer(sinkPad);
   if(peer) // unlink it if it has a peer
   { 
      gst_pad_unlink(peer,sinkPad);
      g_object_unref(peer); 
   }
}
// and link new or re-link existing pad
gst_pad_link( newPad, sinkPad); 
// .. 

在我的情况下,管道是通过调用gst_parse_bin_from_description()构造的,而pad_added处理程序是通过调用带有一些其他元数据的代码来添加的,以允许按名称查找源实例,过滤新的填充名称并将其连接到接收器实例。

答案 2 :(得分:0)

如果使用 gst_parse_launch(),您可以使用 : 代替 ! 使“添加填充”处理自动发生。

fx: "videotestsrc ! x264enc ! mpegtsmux ! tsdemux : avdec_h264 ! videoconvert ! ximagesink"