1

我正在研究在 RPi3 上运行的 Python 脚本,并使用 gstreamer 连接到我的 IP 摄像机的 RTSP 源,并将解码的 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)')

任何人都可以阐明这个问题吗?谢谢!

4

3 回答 3

0

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

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

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

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

在普通的 gstreamer 示例中,他们倾向于显示的只是添加了 pad 的处理程序,如果未连接 sink pad,则在 source 和 sink pad 之间创建链接。

事实上,在这些动态创建的焊盘之一中添加焊盘的处理程序调用的情况下,源焊盘已更改身份并消失,但接收焊盘仍处于“连接”状态。

我发现有效的是明确断开水槽垫..像这样

// ... 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 处理程序是通过调用带有一些额外元数据的代码来添加的,以允许按名称定位源实例,过滤新的 pad 名称并将其连接到 sink 实例。

于 2020-11-09T10:34:31.360 回答
0

当你这样做

pipline.set_state(Gst.State.NULL)

这并不意味着管道已立即达到此状态。确保您可以调用 pipeline.get_state()。我还建议处理 set_state() 的返回值。最后要回到播放状态,不需要先进入 PAUSED(除非你想在 PUASED 和 PLAYING 之间做点什么)。

于 2016-12-05T15:14:17.003 回答
0

如果使用gst_parse_launch(),您可以使用:而不是!使“添加垫”处理自动发生。

fx: "videotestsrc ! x264enc ! mpegtsmux ! tsdemux : avdec_h264 ! videoconvert ! ximagesink"
于 2021-06-07T09:28:28.153 回答