在不停止管道的情况下重新启动故障

时间:2015-12-08 20:21:08

标签: gstreamer python-gstreamer

今天我决定将基于gst-launch的小脚本转换为真正的Python / GStreamer应用程序,以添加一些功能。

由于shout2send,我开发了一个小程序,将麦克风的音频发送到Icecast(filesink)和本地存储(tee)。

由于网络问题,有时shout2send可能会停止。我想每N秒重新启动一次这个元素,直到连接恢复,而不停止管道,因为本地音频文件不应受到网络条件的影响。

这是我试过的:

  1. 在网络错误后一秒钟停止/启动管道(结果:流式传输工作,本地文件被截断)
  2. 取消与tee的关联,将shout2send状态设置为NULL并将其从管道中移除(结果:GStreamer严重错误,如Trying to dispose element ... but it is in PLAYING instead of the NULL state
  3. 在这种情况下试图了解如何使用打击垫(结果:与上面相同,但涉及更多代码)
  4. 我该怎么办?

    以下是我的代码的样子:

    import gi
    gi.require_version("Gst", "1.0")
    from gi.repository import GLib
    from gi.repository import Gst
    # [...]
    
    def message_handler(bus, message):
        if message.type == Gst.MessageType.ERROR:
            if message.src == shout2send:
                pass # TODO: restart the element
            else:
                print(message.parse_error())
                pipeline.set_state(Gst.State.NULL)
                exit(1)
        else:
            print(message.type)
    
    pipeline = Gst.Pipeline()
    message_bus = pipeline.get_bus()
    message_bus.add_signal_watch()
    message_bus.connect('message', message_handler)
    
    # [...]
    tee.link(queue0)
    queue0.link(filesink)
    tee.link(queue1)
    queue1.link(shout2send)
    

    更新(2015年12月9日):添加了非工作代码+日志

    我尝试关注"Dynamically changing the pipeline" fro GStreamer doc,但我的代码不起作用。

    def event_probe(pad, info, *args):
        Gst.Pad.remove_probe(pad, info)
        queue1.unlink(shout2send)
        tee.unlink(queue1)
        pipeline.remove(shout2send)
        pipeline.remove(queue1)
        return Gst.PadProbeReturn.OK
    
    def message_handler(bus, message):
        if message.type == Gst.MessageType.ERROR:
            if message.src == shout2send:
                pad = queue1.get_static_pad('src')
                pad.add_probe(Gst.PadProbeType.BLOCK_DOWNSTREAM, event_probe, None)
            else:
                print(message.parse_error())
                pipeline.set_state(Gst.State.NULL)
                exit(1)
        else:
            print(message.type)
    

    如果我使用GST_DEBUG=3运行我的脚本并且在流式传输时重新启动Icecast,我会看到以下内容:

    [...]
    0:00:02.142033258  5462 0x55e414d900a0 WARN                  shout2 gstshout2.c:674:gst_shout2send_render:<shout2send> error: shout_send() failed: Socket error
    0:00:02.658137998  5462 0x55e414d90140 WARN                 basesrc gstbasesrc.c:2943:gst_base_src_loop:<pulsesrc> error: Internal data flow error.
    0:00:02.658169752  5462 0x55e414d90140 WARN                 basesrc gstbasesrc.c:2943:gst_base_src_loop:<pulsesrc> error: streaming task paused, reason error (-5)
    (GLib.Error('Internal data flow error.', 'gst-stream-error-quark', 1), 'gstbasesrc.c(2943): gst_base_src_loop (): /GstPipeline:pipeline0/GstPulseSrc:pulsesrc:\nstreaming task paused, reason error (-5)')
    0:00:02.658628129  5462 0x7f6ba8002a30 WARN                audiosrc gstaudiosrc.c:244:audioringbuffer_thread_func:<pulsesrc> error reading data -1 (reason: Success), skipping segment
    

1 个答案:

答案 0 :(得分:2)

感谢otopolsky的评论,我做到了:)

我做错了什么:

  1. 元素必须设置为NULL:这非常重要
  2. oggmux必须在tee之后停留在两个子管道上:否则Icecast将列出该流而无法提供该流。对opusenc
  3. 执行相同操作

    建议:

    1. 没有必要取消您不需要的每个元素的链接:只需在需要的地方打破
    2. 没有必要从管道中删除您不需要的每个元素:如果您想重复使用它们,请保留它们
    3. 最终代码(重新连接正常且独立于本地编码/录制):

      def event_probe2(pad, info, *args):
          Gst.Pad.remove_probe(pad, info.id)
          tee.link(opusenc1)
          opusenc1.set_state(Gst.State.PLAYING)
          oggmux1.set_state(Gst.State.PLAYING)
          queue1.set_state(Gst.State.PLAYING)
          shout2send.set_state(Gst.State.PLAYING)
          return Gst.PadProbeReturn.OK
      
      def reconnect():
          pad = tee.get_static_pad('src_1')
          pad.add_probe(Gst.PadProbeType.BLOCK_DOWNSTREAM, event_probe2, None)
      
      def event_probe(pad, info, *args):
          Gst.Pad.remove_probe(pad, info.id)
          tee.unlink(opusenc1)
          opusenc1.set_state(Gst.State.NULL)
          oggmux1.set_state(Gst.State.NULL)
          queue1.set_state(Gst.State.NULL)
          shout2send.set_state(Gst.State.NULL)
          GLib.timeout_add_seconds(interval, reconnect)
          return Gst.PadProbeReturn.OK
      
      def message_handler(bus, message):
          if message.type == Gst.MessageType.ERROR:
              if message.src == shout2send:
                  pad = tee.get_static_pad('src_1')
                  pad.add_probe(Gst.PadProbeType.BLOCK_DOWNSTREAM, event_probe, None)
              else:
                  print(message.parse_error())
                  pipeline.set_state(Gst.State.NULL)
                  exit(1)
          else:
              print(message.type)
      

      小问题:

      1. 我使用tee.get_static_pad('src_1'),但我想我可以在某处获取src id,而不是使用固定值
      2. 可能整个事情都可以用更好的形式编写(但这是我的第一个使用Python + Gstreamer的程序并且它有效,所以我很好用它)
      3. 为了避免数据丢失,我在pipeline.set_state(Gst.State.NULL)之后拨打pipeline.send_event(Gst.Event.new_eos())一秒钟,但仍然收到WARN audiosrc gstaudiosrc.c:244:audioringbuffer_thread_func:<pulsesrc> error reading data -1 (reason: Success), skipping segment
      4. 等消息

        代码:https://github.com/ViGLug/libre-streaming