Basesrc源码分析(一)

概述

通过Gstreamer官方文档,我们可以知道很多src类型的element都是Basesrc类的子类,因此对Basesrc源码的了解,有助于我们知pipeline数据源头是如何触发数据产生的。本文承接上文,以Filesrc element作为示例进行解说。管道命令如下:

gst-launch-1.0 --gst-debug=filesrc:5,basesrc:5 filesrc location=/oem/200frames_count.h264 ! filesink location=/tmp/flc.mp4

上一篇Filesrc源码分析中,我们只解析了函数调用的流程。本节我们将会看到Filesrc实现的funcs是如何被触发的。本文将结合log和源码尽可能详细的进行解析。由于Basesrc(Filesrc)仍被包含在Pipeline中,因此还有些函数调用的源头和gstreamer的一些机制仍然是透明的,我们暂且把这些透明的逻辑看做是“上帝之手”,随着笔者后面分析,应该能把这个“上帝之手”也给解剖开来。

关于GObject类的继承知识点,可参考
【GObject 对象-类-实例概念介绍】
【GObject:自定义类创建实例(继承、重载)】

对象创建

【Log文件】

FLC-DBG:[BaseSrc] <gst_base_src_class_init> is called!
FLC-DBG:[GstFileSrc] <gst_file_src_class_init> is called!
FLC-DBG:[BaseSrc] <gst_base_src_init> is called!
0:00:00.047699750   728     0x362e5a90 DEBUG                basesrc gstbasesrc.c:447:gst_base_src_init:<GstBaseSrc@0x362f4160> creating src pad
0:00:00.048931750   728     0x362e5a90 DEBUG                basesrc gstbasesrc.c:450:gst_base_src_init:<GstBaseSrc@0x362f4160> setting functions on src pad
0:00:00.050285083   728     0x362e5a90 DEBUG                basesrc gstbasesrc.c:458:gst_base_src_init:<GstBaseSrc@0x362f4160> adding src pad
FLC-DBG:[BaseSrc] <gst_base_src_set_format> is called!
0:00:00.051997166   728     0x362e5a90 DEBUG                basesrc gstbasesrc.c:474:gst_base_src_init:<GstBaseSrc@0x362f4160> init done
FLC-DBG:[GstFileSrc] <gst_file_src_init> is called!
FLC-DBG:[BaseSrc] <gst_base_src_set_blocksize> is called!

【对应代码解析】

在管道命令gst-launch-1.0 gst-launch-1.0 --gst-debug=filesrc:5,basesrc:5 filesrc location=/oem/200frames_count.h264 ! filesink location=/tmp/flc.mp4中,首先需要创建filesrc对象,filesrc类是basesrc的子类(如下类继承关系所示),因此上述log中我们能看到先创建的是basesrc对象。

GObject
    ╰──GInitiallyUnowned
        ╰──GstObject
            ╰──GstElement
                ╰──GstBaseSrc
                    ╰──filesrc
  • filesrcsrc对象的创建
static void
gst_base_src_class_init (GstBaseSrcClass * klass)
{
  GObjectClass *gobject_class;
  GstElementClass *gstelement_class;

  //获取不同层级的父类指针。
  gobject_class = G_OBJECT_CLASS (klass);
  gstelement_class = GST_ELEMENT_CLASS (klass);

  // 注册Debug信息,调试时可通过“basesrc”进行设置该element的debug等级
  // 可参考上述管道命令。
  GST_DEBUG_CATEGORY_INIT (gst_base_src_debug, "basesrc", 0, "basesrc element");

  // 初始化四有指针,指向GstBaseSrcPrivate结构。
  g_type_class_add_private (klass, sizeof (GstBaseSrcPrivate));

  // 获取GstElementClass指针,该变量为全局变量,可通过该变量调用对应的finalize函数。
  parent_class = g_type_class_peek_parent (klass);

  /* 设置gobject class的成员函数,对象析构时最终会调用finalize,另外设置属性设置和获取api,
   * 这些属性分别对应下面针对basesrc object额外声明的属性,比如“blocksize”、"num-buffers"等
   */
  gobject_class->finalize = gst_base_src_finalize;
  gobject_class->set_property = gst_base_src_set_property;
  gobject_class->get_property = gst_base_src_get_property;

  // 声明(创建)basesrc object的属性。
  g_object_class_install_property (gobject_class, PROP_BLOCKSIZE,
      g_param_spec_uint ("blocksize", "Block size",
          "Size in bytes to read per buffer (-1 = default)", 0, G_MAXUINT,
          DEFAULT_BLOCKSIZE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
  g_object_class_install_property (gobject_class, PROP_NUM_BUFFERS,
      g_param_spec_int ("num-buffers", "num-buffers",
          "Number of buffers to output before sending EOS (-1 = unlimited)",
          -1, G_MAXINT, DEFAULT_NUM_BUFFERS, G_PARAM_READWRITE |
          G_PARAM_STATIC_STRINGS));
#ifndef GST_REMOVE_DEPRECATED
  g_object_class_install_property (gobject_class, PROP_TYPEFIND,
      g_param_spec_boolean ("typefind", "Typefind",
          "Run typefind before negotiating (deprecated, non-functional)", FALSE,
          G_PARAM_READWRITE | G_PARAM_DEPRECATED | G_PARAM_STATIC_STRINGS));
#endif
  g_object_class_install_property (gobject_class, PROP_DO_TIMESTAMP,
      g_param_spec_boolean ("do-timestamp", "Do timestamp",
          "Apply current stream time to buffers", DEFAULT_DO_TIMESTAMP,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  /* 设置gstelement_class的成员函数。
   * change_state为状态切换处理函数,比如“NULL TO READY”的状态切换。
   * 状态切换是“上帝之手”触发的。
   * send_event用于向下游element发送event
   */
  gstelement_class->change_state =
      GST_DEBUG_FUNCPTR (gst_base_src_change_state);
  gstelement_class->send_event = GST_DEBUG_FUNCPTR (gst_base_src_send_event);

  /* 设置GstBaseSrcClass的成员函数。具体函数功能可参考函数名,另外在流开始过程我们
   * 将会展开这些函数逐个分析。
   */
  klass->get_caps = GST_DEBUG_FUNCPTR (gst_base_src_default_get_caps);
  klass->negotiate = GST_DEBUG_FUNCPTR (gst_base_src_default_negotiate);
  klass->fixate = GST_DEBUG_FUNCPTR (gst_base_src_default_fixate);
  klass->prepare_seek_segment =
      GST_DEBUG_FUNCPTR (gst_base_src_default_prepare_seek_segment);
  klass->do_seek = GST_DEBUG_FUNCPTR (gst_base_src_default_do_seek);
  klass->query = GST_DEBUG_FUNCPTR (gst_base_src_default_query);
  klass->event = GST_DEBUG_FUNCPTR (gst_base_src_default_event);
  klass->create = GST_DEBUG_FUNCPTR (gst_base_src_default_create);
  klass->alloc = GST_DEBUG_FUNCPTR (gst_base_src_default_alloc);
  klass->decide_allocation =
      GST_DEBUG_FUNCPTR (gst_base_src_decide_allocation_default);

  // 搜了下面宏定义的说明,但笔者不了解其用途,只是模糊知道在Debug时函数名相关,跳过。
  /* Registering debug symbols for function pointers */
  GST_DEBUG_REGISTER_FUNCPTR (gst_base_src_activate_mode);
  GST_DEBUG_REGISTER_FUNCPTR (gst_base_src_event);
  GST_DEBUG_REGISTER_FUNCPTR (gst_base_src_query);
  GST_DEBUG_REGISTER_FUNCPTR (gst_base_src_getrange);
  GST_DEBUG_REGISTER_FUNCPTR (gst_base_src_fixate);
}

上述代码完成了GstBaseSrcClass的初始化。再叨叨下GObject类的继承特点(可参考概述中的博文):对象创建时,先初始化父类,再初始化子类。因此basesrc class初始化之后,开始filesrc class的初始化。

static void
gst_file_src_class_init (GstFileSrcClass * klass)
{
  GObjectClass *gobject_class;
  GstElementClass *gstelement_class;
  GstBaseSrcClass *gstbasesrc_class;

  // 获取各层父类指针。
  gobject_class = G_OBJECT_CLASS (klass);
  gstelement_class = GST_ELEMENT_CLASS (klass);
  gstbasesrc_class = GST_BASE_SRC_CLASS (klass);

  // 【代码块标记:gst_file_src_class_init-01】. 
  gobject_class->set_property = gst_file_src_set_property;
  gobject_class->get_property = gst_file_src_get_property;

  g_object_class_install_property (gobject_class, PROP_LOCATION,
      g_param_spec_string ("location", "File Location",
          "Location of the file to read", NULL,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
          GST_PARAM_MUTABLE_READY));

  gobject_class->finalize = gst_file_src_finalize;

  / * 对gstelement_class的成员进行设置。
    * 首先设置element 的metadata,这个metadata主要用于描述该element的基本信息。
    * 然后注册静态pad模板,这个pad模板的定义为:
    * static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
    * GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS_ANY);
    * /
  gst_element_class_set_static_metadata (gstelement_class,
      "File Source",
      "Source/File",
      "Read from arbitrary point in a file",
      "Erik Walthinsen <omega@cse.ogi.edu>");
  gst_element_class_add_static_pad_template (gstelement_class, &srctemplate);

  /* 重载gstbasesrc类成员函数 */
  // 【代码块标记:gst_file_src_class_init-02. 】
  gstbasesrc_class->start = GST_DEBUG_FUNCPTR (gst_file_src_start);
  gstbasesrc_class->stop = GST_DEBUG_FUNCPTR (gst_file_src_stop);
  gstbasesrc_class->is_seekable = GST_DEBUG_FUNCPTR (gst_file_src_is_seekable);
  gstbasesrc_class->get_size = GST_DEBUG_FUNCPTR (gst_file_src_get_size);
  gstbasesrc_class->fill = GST_DEBUG_FUNCPTR (gst_file_src_fill);

  // 判断off_t类型的位宽,如果太小则声明不支持大文件的读写。
  if (sizeof (off_t) < 8) {
    GST_LOG ("No large file support, sizeof (off_t) = %" G_GSIZE_FORMAT "!",
        sizeof (off_t));
  }
}

【代码块标记:gst_file_src_class_init-01】处的代码是针对gobject_class层的操作。其完成如下功能:
1、设置属性操作函数。
2、安装(声明)属性。
3、设置成员函数。
在basesrc类初始化函数gst_base_src_class_init中已经对gobject_class层的set_property和get_property函数进行设置。此处再次赋值岂不是相当于重载?而事实上却并非是“重载”,而更像是功能的扩展。gst-inspect-1.0 filesrc我们可以看到父类basesrc和filesrc中声明的属性都存在。并且同在gst_base_src_class_init中声明的属性和属性操作函数相互对应;同在gst_file_src_class_init声明的属性和熟悉操作函数相互对应。
在这里插入图片描述
笔者在这里有些困惑,对于【代码块标记:gst_file_src_class_init-02】处的代码来说确实是重载了父类basesrc的接口。而【代码块标记:gst_file_src_class_init-01】的代码可以理解为是重载了祖父类(父类的父类)的接口。在gst_base_src_class_init中已经重载了一次,那么在gst_file_src_class_init中再次重载不应该是顶替掉父类的重载吗?

笔者为此写了个测试Demo,事实证明确实没出现顶替,而确实是类似功能扩展了。博主这里的疑惑如果有同学知道,还请告知。

上述代码则是完成了filesrc 类的初始化。特别要注意模板类的设置,在实例初始化时我们将能看到其用途。

static void
gst_base_src_init (GstBaseSrc * basesrc, gpointer g_class)
{
  GstPad *pad;
  GstPadTemplate *pad_template;

  // 获取GstBaseSrcClass中注册的private对象地址。
  basesrc->priv = GST_BASE_SRC_GET_PRIVATE (basesrc);

  // 初始化实例的成员变量。
  basesrc->is_live = FALSE;
  g_mutex_init (&basesrc->live_lock);
  g_cond_init (&basesrc->live_cond);
  basesrc->num_buffers = DEFAULT_NUM_BUFFERS;
  basesrc->num_buffers_left = -1;
  basesrc->priv->automatic_eos = TRUE;

  basesrc->can_activate_push = TRUE;

  // 获取pad模板,这个模板是在上文中有见到。
  pad_template =
      gst_element_class_get_pad_template (GST_ELEMENT_CLASS (g_class), "src");
  g_return_if_fail (pad_template != NULL);

  // 创建pad
  GST_DEBUG_OBJECT (basesrc, "creating src pad");
  pad = gst_pad_new_from_template (pad_template, "src");

  // 设置pad相关函数,这些函数目前也都是由“上帝之手”触发的。
  GST_DEBUG_OBJECT (basesrc, "setting functions on src pad");
  gst_pad_set_activatemode_function (pad, gst_base_src_activate_mode);
  gst_pad_set_event_function (pad, gst_base_src_event);
  gst_pad_set_query_function (pad, gst_base_src_query);
  gst_pad_set_getrange_function (pad, gst_base_src_getrange);

  /* hold pointer to pad */
  basesrc->srcpad = pad;

  // 安装pad,从这里切入的话,应该能看到“上帝之手”的一些逻辑。对本文仍是透明的。
  GST_DEBUG_OBJECT (basesrc, "adding src pad");
  gst_element_add_pad (GST_ELEMENT (basesrc), pad);

  // 设置属性。特别关注下下DEFAULT_DO_TIMESTAMP,这应该跟pipeline的时间戳有关。
  // 此处设个提醒,如果后续看到该变量,再重点分析。
  basesrc->blocksize = DEFAULT_BLOCKSIZE;
  basesrc->clock_id = NULL;
  /* we operate in BYTES by default */
  gst_base_src_set_format (basesrc, GST_FORMAT_BYTES);
  basesrc->priv->do_timestamp = DEFAULT_DO_TIMESTAMP;
  g_atomic_int_set (&basesrc->priv->have_events, FALSE);

  // FLUSHING我们也设个特别关注。因为这个词常在pipeline运行时能看到。
  g_cond_init (&basesrc->priv->async_cond);
  basesrc->priv->start_result = GST_FLOW_FLUSHING;
  GST_OBJECT_FLAG_UNSET (basesrc, GST_BASE_SRC_FLAG_STARTED);
  GST_OBJECT_FLAG_UNSET (basesrc, GST_BASE_SRC_FLAG_STARTING);
  GST_OBJECT_FLAG_SET (basesrc, GST_ELEMENT_FLAG_SOURCE);

  GST_DEBUG_OBJECT (basesrc, "init done");
}

上述代码是basesrc实例初始化函数,主要完成各个状态的初始化以及pad创建和安装。

static void
gst_file_src_init (GstFileSrc * src)
{
  src->filename = NULL;
  src->fd = 0;
  src->uri = NULL;

  src->is_regular = FALSE;

  gst_base_src_set_blocksize (GST_BASE_SRC (src), DEFAULT_BLOCKSIZE);
}

上述代码是filesrc实例的初始化函数,并且通过父类basesrc接口gst_base_src_set_blocksize设置了块大小。这个块大小控制我们每次读取多少数据量。在后续代码中我们将能看到这个变量的作用。

属性设置

【Log文件】

0:00:00.054261666   728     0x362e5a90 INFO                 filesrc gstfilesrc.c:269:gst_file_src_set_location: filename : /oem/200frames_count.h264
0:00:00.055463333   728     0x362e5a90 INFO                 filesrc gstfilesrc.c:270:gst_file_src_set_location: uri      : file:///oem/200frames_count.h264

【代码解析】

static void
gst_file_src_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec)
{
  GstFileSrc *src;

  g_return_if_fail (GST_IS_FILE_SRC (object));

  src = GST_FILE_SRC (object);

  switch (prop_id) {
    case PROP_LOCATION:
      gst_file_src_set_location (src, g_value_get_string (value), NULL);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

上面这个函数是在filesrc class init时注册的属性操作函数。我们管道命令中有对filesrc的location属性进行设置,因此会触发到这里。不过本篇中仍然是“上帝之手”触发的这个设置。

static gboolean
gst_file_src_set_location (GstFileSrc * src, const gchar * location,
    GError ** err)
{
  GstState state;

  /* 重点关注下这段代码,只有状态为READY和NULL状态时,才能更新location属性。
   * 使用playbin时可能会遇到uri更新失败的情况,从而导致播放列表更新下一首失败。
   * 从这里可以看出,切换uri之前,一定得关注playbin的状态是否真正切换到READY和NULL?
   */
  /* the element must be stopped in order to do this */
  GST_OBJECT_LOCK (src);
  state = GST_STATE (src);
  if (state != GST_STATE_READY && state != GST_STATE_NULL)
    goto wrong_state;
  GST_OBJECT_UNLOCK (src);

  g_free (src->filename);
  g_free (src->uri);

  /* clear the filename if we get a NULL */
  if (location == NULL) {
    src->filename = NULL;
    src->uri = NULL;
  } else {
    /* we store the filename as received by the application. On Windows this
     * should be UTF8 */
    src->filename = g_strdup (location);
    src->uri = gst_filename_to_uri (location, NULL);
    GST_INFO ("filename : %s", src->filename);
    GST_INFO ("uri      : %s", src->uri);
  }
  g_object_notify (G_OBJECT (src), "location");
  /* FIXME 2.0: notify "uri" property once there is one */

  return TRUE;

  /* ERROR */
wrong_state:
  {
    g_warning ("Changing the `location' property on filesrc when a file is "
        "open is not supported.");
    if (err)
      g_set_error (err, GST_URI_ERROR, GST_URI_ERROR_BAD_STATE,
          "Changing the `location' property on filesrc when a file is "
          "open is not supported.");
    GST_OBJECT_UNLOCK (src);
    return FALSE;
  }
}

上述代码逻辑简单明了,就设置filesrc对象的location和uri属性。

CAPS查询

【Log文件】

FLC-DBG:[BaseSrc] <gst_base_src_query> is called!
FLC-DBG:[BaseSrc] <gst_base_src_default_query> is called! type=0xaa03
FLC-DBG:[BaseSrc] <gst_base_src_default_get_caps> is called!
0:00:00.058564916   728     0x362e5a90 DEBUG                basesrc gstbasesrc.c:1337:gst_base_src_default_query:<filesrc0> query caps returns 1

FLC-DBG:[BaseSrc] <gst_base_src_query> is called!
FLC-DBG:[BaseSrc] <gst_base_src_default_query> is called! type=0xaa03
FLC-DBG:[BaseSrc] <gst_base_src_default_get_caps> is called!
0:00:00.061256416   728     0x362e5a90 DEBUG                basesrc gstbasesrc.c:1337:gst_base_src_default_query:<filesrc0> query caps returns 1

上述log中为何两次相同逻辑调用gst_base_src_query尚不清楚,属于“上帝之手”范畴。我们当下只分析函眼前能看到的。gst_base_src_query函数是在pad生成时注册的,从名称来看应该是查询PAD的信息用的。这个pad信息查询应该是用于element连接的。我当前猜测有两种思路,后续我们代码中再去求证。

  • 1、pipeline分别查询filesrc和filesink的PAD信息。如果有交集则Link成功,否则Link失败。
  • 2、filesink主动查询filesrc的PAD信息,用于判断是否能够Link。

【代码解析】

static gboolean
gst_base_src_query (GstPad * pad, GstObject * parent, GstQuery * query)
{
  GstBaseSrc *src;
  GstBaseSrcClass *bclass;
  gboolean result = FALSE;

  // pad的parent是basesrc。
  src = GST_BASE_SRC (parent);
  bclass = GST_BASE_SRC_GET_CLASS (src);

  // 调用basesrc class的query函数。
  if (bclass->query)
    result = bclass->query (src, query);

  return result;
}

上述函数逻辑为:从PAD的query函数,转接到basesrc的gst_base_src_default_query转接函数。

static gboolean
gst_base_src_default_query (GstBaseSrc * src, GstQuery * query)
{
  gboolean res;

  switch (GST_QUERY_TYPE (query)) {

    ......

    case GST_QUERY_CAPS:
    {
      GstBaseSrcClass *bclass;
      GstCaps *caps, *filter;

      bclass = GST_BASE_SRC_GET_CLASS (src);
      if (bclass->get_caps) {
        // query中获取filter,这个filter其实就是请求方的caps。从这里看,
        // 上述猜想很接近2.
        gst_query_parse_caps (query, &filter);
        if ((caps = bclass->get_caps (src, filter))) {
          gst_query_set_caps_result (query, caps);
          gst_caps_unref (caps);
          res = TRUE;
        } else {
          res = FALSE;
        }
      } else
        res = FALSE;
      break;
    }

	......

    default:
      res = FALSE;
      break;
  }
  GST_DEBUG_OBJECT (src, "query %s returns %d", GST_QUERY_TYPE_NAME (query),
      res);

  return res;
}
    

上述代码的bclass->get_capsgst_base_src_default_get_caps。获取到caps之后,通过gst_query_set_caps_result反馈结果。

static GstCaps *
gst_base_src_default_get_caps (GstBaseSrc * bsrc, GstCaps * filter)
{
  GstCaps *caps = NULL;
  GstPadTemplate *pad_template;
  GstBaseSrcClass *bclass;

  bclass = GST_BASE_SRC_GET_CLASS (bsrc);

  pad_template =
      gst_element_class_get_pad_template (GST_ELEMENT_CLASS (bclass), "src");

  if (pad_template != NULL) {
    // 从我们注册的pad模板中获取pad信息
    caps = gst_pad_template_get_caps (pad_template);

    if (filter) {
      GstCaps *intersection;
	  /* 获取filter和caps的交集, GST_CAPS_INTERSECT_FIRST表示
	   * 找到第一个匹配的caps就返回
	   */
      intersection =
          gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
      gst_caps_unref (caps);
      caps = intersection;
    }
  }
  return caps;
}

到这里,管道以及完成初始化,当前处于NULL状态。后续的状态切换,以及数据流的流通,还请参见笔者的后续文章。

资源下载

笔者调试的完整log地址:
https://download.csdn.net/download/lyy901135/11839846

Android网络连接源码分析主要涉及到Android系统中网络连接的相关实现和机制。下面是Android网络连接的源码分析: 1. 网络连接管理类:Android中的网络连接管理由ConnectivityManager类负责。该类提供了获取网络状态、监听网络状态变化等功能。其源码位于frameworks/base/core/java/android/net/ConnectivityManager.java。 2. 网络请求类:Android中的网络请求由HttpClient或HttpURLConnection实现。在Android 6.0及以上版本中,Google推荐使用HttpURLConnection。HttpClient的源码位于frameworks/base/core/java/org/apache/http/impl/client/DefaultHttpClient.java,HttpURLConnection的源码位于libcore/luni/src/main/java/java/net/HttpURLConnection.java。 3. 网络请求处理类:Android中的网络请求处理由AsyncTask或者Thread实现。AsyncTask是一个封装了线程池和Handler的异步任务类,用于在后台执行耗时操作,并在主线程更新UI。其源码位于frameworks/base/core/java/android/os/AsyncTask.java。 4. 网络请求结果处理类:Android中的网络请求结果处理由Handler或者Callback实现。Handler用于在主线程中处理异步任务的结果,Callback则是一种回调机制,用于在异步任务完成后执行相应的操作。 5. 网络缓存类:Android中的网络缓存由DiskLruCache或者LruCache实现。DiskLruCache用于将网络请求结果缓存到本地磁盘,LruCache则是一种内存缓存机制,用于缓存网络请求结果。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值