• gstreamer的caps event和new_segment event


    caps event

    先说下caps event,简单的讲,caps event就是用来配置格式的event,当一个媒体格式被协商时,对等元素会收到CAPS event的GstCaps通知。

    caps event从哪里来?

    这里以pipelinegst-launch-1.0 filesrc location=/home/hui/h264.mp4 ! qtdemux ! avdec_h264 ! xvimagesink为例,在avdec_h264中收到的caps event只有一个可能就是从qtdemux中来的:

    打开log:GST_DEBUG=qtdemux:6,GST_EVENT:6,libav:6获取pipeline log,搜索new_caps可以看到:

      qtdemux qtdemux.c:8877:gst_qtdemux_configure_stream:<qtdemux0> setting caps video/x-h264,
    GST_EVENT gstevent.c:892:gst_event_new_caps: creating caps event video/x-h264, stream-format
    
    • 1
    • 2

    在qtdemux代码中,很快可以找到gst_qtdemux_configure_stream中创建了caps event,具体是在gst_pad_set_caps函数里面通过gst_event_new_caps创建的:

        if (CUR_STREAM (stream)->caps) {
          if (!prev_caps
              || !gst_caps_is_equal_fixed (prev_caps, CUR_STREAM (stream)->caps)) {
            GST_DEBUG_OBJECT (qtdemux, "setting caps %" GST_PTR_FORMAT,
                CUR_STREAM (stream)->caps);
            // 创建event caps 
            gst_pad_set_caps (stream->pad, CUR_STREAM (stream)->caps);
          } else {
            GST_DEBUG_OBJECT (qtdemux, "ignore duplicated caps");
          }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    gst_pad_set_cap函数创建event caps

    在gst_pad_set_caps中,创建了GST_EVENT_CAPS类型的event,然后gst_pad_push_event将这个event发送到pad上,如果pad没有link,event会被存起来,处于pending状态,从后面的log中可以看到这个信息。

    static inline gboolean
    gst_pad_set_caps (GstPad * pad, GstCaps * caps)
    {
      GstEvent *event;
      gboolean res = TRUE;
    
      g_return_val_if_fail (GST_IS_PAD (pad), FALSE);
      g_return_val_if_fail (caps != NULL && gst_caps_is_fixed (caps), FALSE);
    
      event = gst_event_new_caps (caps);
    
      if (GST_PAD_IS_SRC (pad))
        res = gst_pad_push_event (pad, event);
      else
        res = gst_pad_send_event (pad, event);
    
      return res;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    stored sticky:

    store_sticky_event:<'':video_0> stored sticky event caps
    store_sticky_event:<'':video_0> notify caps
    check_sticky:<'':video_0> pushing all sticky events
    push_sticky:<'':video_0> event stream-start was already received
    gst_pad_push_event_unchecked:<'':video_0> Dropping event caps because pad is not linked
    push_sticky:<'':video_0> pad was not linked, mark pending
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    所以在qtdemux中创建了caps event,然后下发给avdec_h264之后,之后就会进入gst_video_decoder_sink_event_default处理,因为videodecoder是avdec_h264(gstavviddec)的基类。

    gst_video_decoder_sink_event_default中又会调用gst_video_decoder_setcaps将caps给avdec_h264,然后下面调用decoder_class->set_format就到了avviddec中的gst_ffmpegviddec_set_format函数,因为avviddec继承自videodecoder:

    // gst_video_decoder_class_init
    // klass->sink_event = gst_video_decoder_sink_event_default;
    
    // gst_video_decoder_sink_event_default:
    case GST_EVENT_CAPS:
    {
      GstCaps *caps;
    
      gst_event_parse_caps (event, &caps);
      ret = gst_video_decoder_setcaps (decoder, caps);
      gst_event_unref (event);
      event = NULL;
      break;
    }
    
    // gst_video_decoder_setcaps
      if (decoder_class->set_format)
        ret = decoder_class->set_format (decoder, state);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    gst_ffmpegviddec_set_format中解析codec_data

    为什么是在gst_ffmpegviddec_set_format函数中,因为在gst_ffmpegviddec_class_init函数中,viddec_class->set_format使用
    gst_ffmpegviddec_set_format函数初始化的。

    通过在子类中赋值父类的函数指针,在父类中调用,达到了C++中的类似动态调用的效果。

    // gst_ffmpegviddec_class_init
    
    viddec_class->set_format = gst_ffmpegviddec_set_format;
    viddec_class->handle_frame = gst_ffmpegviddec_handle_frame;
    viddec_class->start = gst_ffmpegviddec_start;
    viddec_class->stop = gst_ffmpegviddec_stop;
    viddec_class->flush = gst_ffmpegviddec_flush;
    viddec_class->finish = gst_ffmpegviddec_finish;
    viddec_class->drain = gst_ffmpegviddec_drain;
    viddec_class->decide_allocation = gst_ffmpegviddec_decide_allocation;
    viddec_class->propose_allocation = gst_ffmpegviddec_propose_allocation;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    同样的,这些在其他继承了Videodecoder的插件中也都是这样的实现的,下面这段是从v4l2dec中找到的代码:

    // gst_v4l2_video_dec_class_init
    
    video_decoder_class->open = GST_DEBUG_FUNCPTR (gst_v4l2_video_dec_open);
    video_decoder_class->close = GST_DEBUG_FUNCPTR (gst_v4l2_video_dec_close);
    video_decoder_class->start = GST_DEBUG_FUNCPTR (gst_v4l2_video_dec_start);
    video_decoder_class->stop = GST_DEBUG_FUNCPTR (gst_v4l2_video_dec_stop);
    video_decoder_class->finish = GST_DEBUG_FUNCPTR (gst_v4l2_video_dec_finish);
    video_decoder_class->flush = GST_DEBUG_FUNCPTR (gst_v4l2_video_dec_flush);
    video_decoder_class->drain = GST_DEBUG_FUNCPTR (gst_v4l2_video_dec_drain);
    video_decoder_class->set_format = GST_DEBUG_FUNCPTR (gst_v4l2_video_dec_set_format);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    到此,在这个简单例子中,caps event由qtdemux创建,然后gst_pad_push_event将caps event push到pad上,avdec_h264的sink event上收到后进行处理,进而caps event会在gst_ffmpegviddec_set_format里面进行解析。

    new_segment event

    new_segment的创建处理过程

    还是一这个pipeline为例,看下new_segment的创建处理过程,首先,看一下这个过程的调用栈(从下往上看):

    1   gst_ffmpeg_caps_with_codecid         gstavcodecmap.c   3258 0x7ffff6ab1b4a 
    2   gst_ffmpegviddec_set_format          gstavviddec.c     526  0x7ffff6abf9b9 
    3   gst_video_decoder_setcaps            gstvideodecoder.c 897  0x7ffff6ca54a3 
    4   gst_video_decoder_sink_event_default gstvideodecoder.c 1379 0x7ffff6ca7159 
    5   gst_video_decoder_sink_event         gstvideodecoder.c 1690 0x7ffff6ca8327 
    6   gst_pad_send_event_unchecked         gstpad.c          5900 0x7ffff7ed7ba3 
    7   gst_pad_push_event_unchecked         gstpad.c          5544 0x7ffff7ed6420 
    8   push_sticky                          gstpad.c          4047 0x7ffff7ed0257 
    9   events_foreach                       gstpad.c          608  0x7ffff7ec578a 
    10  check_sticky                         gstpad.c          4106 0x7ffff7ed0620 
    11  gst_pad_push_event                   gstpad.c          5675 0x7ffff7ed6c8b 
    12  gst_qtdemux_stream_update_segment    qtdemux.c         4987 0x7ffff6e853dd 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    gst_qtdemux_stream_update_segment中创建new_segment event,然后通过gst_pad_push_event把event push到pad上。

     GST_EVENT_SEGMENT = GST_EVENT_MAKE_TYPE (70, FLAG(DOWNSTREAM) | FLAG(SERIALIZED) | FLAG(STICKY)),
    
    • 1

    gst_pad_push_event将事件发送到给定pad的peer pad上:

    • srcpad: TYPE_EVENT类型是GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM
    • sinkpad: TYPE_EVENT类型是GST_PAD_PROBE_TYPE_EVENT_UPSTREAM

    在gst_pad_send_event_unchecked中从pad上获取eventfullfunceventfunc函数(在gstvideodecoder.c里面的代码是,从这段代码中也可以看到,如果是GST_EVENT_CAPS类型,没有eventfunc处理,那么就会返回GST_FLOW_NOT_NEGOTIATED

      eventfullfunc = GST_PAD_EVENTFULLFUNC (pad);
      eventfunc = GST_PAD_EVENTFUNC (pad);
      // check ...
      
      if (eventfullfunc) {
        ret = eventfullfunc (pad, parent, event);
      } else if (eventfunc (pad, parent, event)) {
        ret = GST_FLOW_OK;
      } else {
        /* something went wrong */
        switch (event_type) {
          case GST_EVENT_CAPS:
            ret = GST_FLOW_NOT_NEGOTIATED;
            break;
          default:
            ret = GST_FLOW_ERROR;
            break;
        }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    gst_pad_set_event_function (pad, GST_DEBUG_FUNCPTR (gst_video_decoder_sink_event))),这个例子中,从pad上获取的eventfunc就是gst_video_decoder_sink_event。

    static gboolean
    gst_video_decoder_sink_event (GstPad * pad, GstObject * parent,
        GstEvent * event)
    {
      GstVideoDecoder *decoder;
      GstVideoDecoderClass *decoder_class;
      gboolean ret = FALSE;
    
      decoder = GST_VIDEO_DECODER (parent);
      decoder_class = GST_VIDEO_DECODER_GET_CLASS (decoder);
    
      GST_DEBUG_OBJECT (decoder, "received event %d, %s", GST_EVENT_TYPE (event),
          GST_EVENT_TYPE_NAME (event));
    
      // gst_video_decoder_sink_event_default
      if (decoder_class->sink_event)
        ret = decoder_class->sink_event (decoder, event);
    
      return ret;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    gst_video_decoder_sink_event中的sink_event函数初始化为gst_video_decoder_sink_event_default,所以接下来gst_video_decoder_sink_event_default是处理event的函数:

    
     case GST_EVENT_SEGMENT:
     {
       GstSegment segment;
    
       gst_event_copy_segment (event, &segment);
       // ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    完成上面的代码分析,下面是对segment的说明。

    gst_event_new_segment的注释

    segment事件只能与buffer同步向downstream发送,并包含buffer的时间信息和播放属性。segment事件标记要处理的buffer范围,全部不在范围内的数据将不被处理。

    这可以被插件智能地使用,以跳过不需要处理的数据。有效范围用@start和@stop值表示。

    @start不能是-1,@stop可以是-1。如果有一个有效的@stop,它必须大于或等于@start,包括当指示的播放@速率<0时。

    @applied_rate值提供了关于任何速率调整的信息。已经对时间戳和缓冲区的内容进行了调整。流的时间戳和内容进行调整的信息。(@rate@applied_rate)应该总是等于被要求播放的速率。请求播放的速率。

    例如,如果一个元素有一个输入segment预期播放速率为2.0,应用速率为1.0,它可以调整为传入的timestamps和buffer内容的一半,并输出一个segment事件速率为1.0,应用速率为2.0。

    segment

    GStreamer中的一个segment表示一组必须被处理的media samples。一个segment有一个开始时间,一个停止时间和一个处理速率。

    一个媒体流有一个开始时间和一个停止时间。开始时间总是0,停止时间是总时长(如果不知道,比如直播流,则是-1)。我们把这称为完整的媒体流。

    完整media stream的片段可以通过在媒体流上发布seek来播放。seek有一个开始时间,一个停止时间和一个处理速率。

                complete stream
    +------------------------------------------------+
    0                                              duration
           segment
       |--------------------------|
     start                       stop
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    segment的播放通过从source或demuxer元素,push包含segment的开始时间、停止时间和速率的SEGMENT事件开始。 目的是通知下游元素请求的segment位置。 某些element可能会产生位于segment之外的buffer,因此可能会被丢弃或剪裁。

  • 相关阅读:
    5. Python 数据类型之整数
    SSM毕设项目 - 基于SSM的毕业设计管理系统(含源码+论文)
    Java基础进阶多线程-四种创建方式
    【YOLO模型】(4)--YOLO V3超超超超详解!!!
    射频模块无线收发RF63U芯片应用数据传输和基建网络
    一张图搞定CSS选择器的优先级
    腾讯云学生用户专享便宜云服务器购买指南
    2017 KDD | metapath2vec: Scalable Representation Learning for Heterogeneous
    Spring Cloud Netflix之OpenFeign
    最全的 ES 重点内容整理(上)
  • 原文地址:https://blog.csdn.net/hongszh/article/details/126074865