当前位置: 首页 > news >正文

playbin之Source插件加载流程源码剖析

之前我们有讲解过uridecodebin的setup_source中会创建source插件,关键函数:

/* create and configure an element that can handle the uri */

source = gen_source_element (decoder);

/*
 * Generate and configure a source element.
 *
 * Returns: (transfer full): a new #GstElement
 */
static GstElement *
gen_source_element (GstURIDecodeBin * decoder)
{
  GObjectClass *source_class;
  GstElement *source;
  GParamSpec *pspec;
  GstQuery *query;
  GstSchedulingFlags flags;
  GError *err = NULL;

  if (!decoder->uri)
    goto no_uri;

  GST_LOG_OBJECT (decoder, "finding source for %s", decoder->uri);

  if (!gst_uri_is_valid (decoder->uri))
    goto invalid_uri;

  if (IS_BLACKLISTED_URI (decoder->uri))
    goto uri_blacklisted;

  source =
      gst_element_make_from_uri (GST_URI_SRC, decoder->uri, "source", &err);
  if (!source)
    goto no_source;

  GST_LOG_OBJECT (decoder, "found source type %s", G_OBJECT_TYPE_NAME (source));

  source_class = G_OBJECT_GET_CLASS (source);

  pspec = g_object_class_find_property (source_class, "connection-speed");
  if (pspec != NULL) {
    guint64 speed = decoder->connection_speed / 1000;
    gboolean wrong_type = FALSE;

    if (G_PARAM_SPEC_TYPE (pspec) == G_TYPE_PARAM_UINT) {
      GParamSpecUInt *pspecuint = G_PARAM_SPEC_UINT (pspec);

      speed = CLAMP (speed, pspecuint->minimum, pspecuint->maximum);
    } else if (G_PARAM_SPEC_TYPE (pspec) == G_TYPE_PARAM_INT) {
      GParamSpecInt *pspecint = G_PARAM_SPEC_INT (pspec);

      speed = CLAMP (speed, pspecint->minimum, pspecint->maximum);
    } else if (G_PARAM_SPEC_TYPE (pspec) == G_TYPE_PARAM_UINT64) {
      GParamSpecUInt64 *pspecuint = G_PARAM_SPEC_UINT64 (pspec);

      speed = CLAMP (speed, pspecuint->minimum, pspecuint->maximum);
    } else if (G_PARAM_SPEC_TYPE (pspec) == G_TYPE_PARAM_INT64) {
      GParamSpecInt64 *pspecint = G_PARAM_SPEC_INT64 (pspec);

      speed = CLAMP (speed, pspecint->minimum, pspecint->maximum);
    } else {
      GST_WARNING_OBJECT (decoder,
          "The connection speed property %" G_GUINT64_FORMAT
          " of type %s is not useful not setting it", speed,
          g_type_name (G_PARAM_SPEC_TYPE (pspec)));
      wrong_type = TRUE;
    }

    if (!wrong_type) {
      g_object_set (source, "connection-speed", speed, NULL);

      GST_DEBUG_OBJECT (decoder,
          "setting connection-speed=%" G_GUINT64_FORMAT " to source element",
          speed);
    }
  }

  pspec = g_object_class_find_property (source_class, "subtitle-encoding");
  if (pspec != NULL && G_PARAM_SPEC_VALUE_TYPE (pspec) == G_TYPE_STRING) {
    GST_DEBUG_OBJECT (decoder,
        "setting subtitle-encoding=%s to source element", decoder->encoding);
    g_object_set (source, "subtitle-encoding", decoder->encoding, NULL);
  }

  /* Sink reference before passing it to signal handler.
   * Language binding might otherwise sink it and then unref.
   * A floating ref is also tricky for a native signal handler in case
   * of a "transfer floating" call followed by unref
   * (e.g. some container_add and then container_remove).
   * Bottom line; best not have a floating ref boldly going into unknown code.
   */
  g_object_ref_sink (source);

  g_signal_emit (decoder, gst_uri_decode_bin_signals[SIGNAL_SOURCE_SETUP],
      0, source);

  decoder->is_stream = IS_STREAM_URI (decoder->uri);

  query = gst_query_new_scheduling ();
  if (gst_element_query (source, query)) {
    gst_query_parse_scheduling (query, &flags, NULL, NULL, NULL);
    if ((flags & GST_SCHEDULING_FLAG_BANDWIDTH_LIMITED))
      decoder->is_stream = TRUE;
  }
  gst_query_unref (query);

  GST_LOG_OBJECT (decoder, "source is stream: %d", decoder->is_stream);

  decoder->need_queue = IS_QUEUE_URI (decoder->uri);
  GST_LOG_OBJECT (decoder, "source needs queue: %d", decoder->need_queue);

  return source;

  /* ERRORS */
no_uri:
  {
    GST_ELEMENT_ERROR (decoder, RESOURCE, NOT_FOUND,
        (_("No URI specified to play from.")), (NULL));
    return NULL;
  }
invalid_uri:
  {
    GST_ELEMENT_ERROR (decoder, RESOURCE, NOT_FOUND,
        (_("Invalid URI \"%s\"."), decoder->uri), (NULL));
    g_clear_error (&err);
    return NULL;
  }
uri_blacklisted:
  {
    GST_ELEMENT_ERROR (decoder, RESOURCE, FAILED,
        (_("This stream type cannot be played yet.")), (NULL));
    return NULL;
  }
no_source:
  {
    /* whoops, could not create the source element, dig a little deeper to
     * figure out what might be wrong. */
    if (err != NULL && err->code == GST_URI_ERROR_UNSUPPORTED_PROTOCOL) {
      gchar *prot;

      prot = gst_uri_get_protocol (decoder->uri);
      if (prot == NULL)
        goto invalid_uri;

      gst_element_post_message (GST_ELEMENT_CAST (decoder),
          gst_missing_uri_source_message_new (GST_ELEMENT (decoder), prot));

      GST_ELEMENT_ERROR (decoder, CORE, MISSING_PLUGIN,
          (_("No URI handler implemented for \"%s\"."), prot), (NULL));

      g_free (prot);
    } else {
      GST_ELEMENT_ERROR (decoder, RESOURCE, NOT_FOUND,
          ("%s", (err) ? err->message : "URI was not accepted by any element"),
          ("No element accepted URI '%s'", decoder->uri));
    }

    g_clear_error (&err);
    return NULL;
  }
}

关键函数:

(1) gst_uri_is_valid (decoder->uri) 对uri进行校验

static void
gst_uri_protocol_check_internal (const gchar * uri, gchar ** endptr)
{
  gchar *check = (gchar *) uri;

  g_assert (uri != NULL);
  g_assert (endptr != NULL);

  if (g_ascii_isalpha (*check)) {
    check++;
    while (g_ascii_isalnum (*check) || *check == '+'
        || *check == '-' || *check == '.')
      check++;
  }

  *endptr = check;
}

(2)IS_BLACKLISTED_URI (decoder->uri)黑名单校验

/* blacklisted URIs, we know they will always fail. */
static const gchar *blacklisted_uris[] = { NULL };

(3)decoder->is_stream = IS_STREAM_URI (decoder->uri);

/* list of URIs that we consider to be streams and that need buffering.
 * We have no mechanism yet to figure this out with a query. */
static const gchar *stream_uris[] = { "http://", "https://", "mms://",
  "mmsh://", "mmsu://", "mmst://", "fd://", "myth://", "ssh://",
  "ftp://", "sftp://",
  NULL
};

(4)decoder->need_queue = IS_QUEUE_URI (decoder->uri); 

/* list of URIs that need a queue because they are pretty bursty */
static const gchar *queue_uris[] = { "cdda://", NULL };

最关键函数:

  source = gst_element_make_from_uri (GST_URI_SRC, decoder->uri, "source", &err); 

/**
 * gst_element_make_from_uri:
 * @type: Whether to create a source or a sink
 * @uri: URI to create an element for
 * @elementname: (allow-none): Name of created element, can be %NULL.
 * @error: (allow-none): address where to store error information, or %NULL.
 *
 * Creates an element for handling the given URI.
 *
 * Returns: (transfer floating): a new element or %NULL if none
 * could be created
 */
GstElement *
gst_element_make_from_uri (const GstURIType type, const gchar * uri,
    const gchar * elementname, GError ** error)
{
  GList *possibilities, *walk;
  gchar *protocol;
  GstElement *ret = NULL;

  g_return_val_if_fail (gst_is_initialized (), NULL);
  g_return_val_if_fail (GST_URI_TYPE_IS_VALID (type), NULL);
  g_return_val_if_fail (error == NULL || *error == NULL, NULL);

  if (!gst_uri_is_valid (uri)) {
    g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI,
        _("Invalid URI: %s"), uri);
    return NULL;
  }

  GST_DEBUG ("type:%d, uri:%s, elementname:%s", type, uri, elementname);

  protocol = gst_uri_get_protocol (uri);
  possibilities = get_element_factories_from_uri_protocol (type, protocol);

  if (!possibilities) {
    GST_DEBUG ("No %s for URI '%s'", type == GST_URI_SINK ? "sink" : "source",
        uri);
    /* The error message isn't great, but we don't expect applications to
     * show that error to users, but call the missing plugins functions */
    g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_UNSUPPORTED_PROTOCOL,
        _("No URI handler for the %s protocol found"), protocol);
    g_free (protocol);
    return NULL;
  }
  g_free (protocol);

  possibilities = g_list_sort (possibilities, (GCompareFunc) sort_by_rank);
  walk = possibilities;
  while (walk) {
    GstElementFactory *factory = walk->data;
    GError *uri_err = NULL;

    ret = gst_element_factory_create (factory, elementname);
    if (ret != NULL) {
      GstURIHandler *handler = GST_URI_HANDLER (ret);

      if (gst_uri_handler_set_uri (handler, uri, &uri_err))
        break;

      GST_WARNING ("%s didn't accept URI '%s': %s", GST_OBJECT_NAME (ret), uri,
          uri_err->message);

      if (error != NULL && *error == NULL)
        g_propagate_error (error, uri_err);
      else
        g_error_free (uri_err);

      gst_object_unref (ret);
      ret = NULL;
    }
    walk = walk->next;
  }
  gst_plugin_feature_list_free (possibilities);

  GST_LOG_OBJECT (ret, "created %s for URL '%s'",
      type == GST_URI_SINK ? "sink" : "source", uri);

  /* if the first handler didn't work, but we found another one that works */
  if (ret != NULL)
    g_clear_error (error);

  return ret;
}

(1)protocol = gst_uri_get_protocol (uri) 获取协议,例如http/https/ftp等

(2)possibilities = get_element_factories_from_uri_protocol (type, protocol),从注册表中搜索可匹配上的source的lists

类型:GST_URI_SRC 协议:protocols

static GList *
get_element_factories_from_uri_protocol (const GstURIType type,
    const gchar * protocol)
{
  GList *possibilities;
  SearchEntry entry;

  g_return_val_if_fail (protocol, NULL);

  entry.type = type;
  entry.protocol = protocol;
  possibilities = gst_registry_feature_filter (gst_registry_get (),
      search_by_entry, FALSE, &entry);

  return possibilities;
}

(3)对possibilities排序,从中选择最终匹配的source

  possibilities = g_list_sort (possibilities, (GCompareFunc) sort_by_rank);
  walk = possibilities;
  while (walk) {
    GstElementFactory *factory = walk->data;
    GError *uri_err = NULL;

    ret = gst_element_factory_create (factory, elementname);
    if (ret != NULL) {
      GstURIHandler *handler = GST_URI_HANDLER (ret);

      if (gst_uri_handler_set_uri (handler, uri, &uri_err))
        break;

      GST_WARNING ("%s didn't accept URI '%s': %s", GST_OBJECT_NAME (ret), uri,
          uri_err->message);

      if (error != NULL && *error == NULL)
        g_propagate_error (error, uri_err);
      else
        g_error_free (uri_err);

      gst_object_unref (ret);
      ret = NULL;
    }
    walk = walk->next;
  }

a)根据rank排序

b) 对每一个source尝试gst_uri_handler_set_uri ,返回成功则选择该source插件     

    ret = gst_element_factory_create (factory, elementname);
    if (ret != NULL) {
      GstURIHandler *handler = GST_URI_HANDLER (ret);
      if (gst_uri_handler_set_uri (handler, uri, &uri_err))
        break;

c) gst_uri_handler_set_uri主要是从,soruce中获取接口iface = GST_URI_HANDLER_GET_INTERFACE (handler),对比source支持的get_protocols是否与当前uri匹配,匹配则成功。

/**
 * gst_uri_handler_set_uri:
 * @handler: A #GstURIHandler
 * @uri: URI to set
 * @error: (allow-none): address where to store a #GError in case of
 *    an error, or %NULL
 *
 * Tries to set the URI of the given handler.
 *
 * Returns: %TRUE if the URI was set successfully, else %FALSE.
 */
gboolean
gst_uri_handler_set_uri (GstURIHandler * handler, const gchar * uri,
    GError ** error)
{
  GstURIHandlerInterface *iface;
  gboolean ret;
  gchar *protocol;

  g_return_val_if_fail (GST_IS_URI_HANDLER (handler), FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  iface = GST_URI_HANDLER_GET_INTERFACE (handler);
  g_return_val_if_fail (iface != NULL, FALSE);
  g_return_val_if_fail (iface->set_uri != NULL, FALSE);

  if (!gst_uri_is_valid (uri)) {
    g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI,
        _("Invalid URI: %s"), uri);
    return FALSE;
  }

  protocol = gst_uri_get_protocol (uri);

  if (iface->get_protocols) {
    const gchar *const *protocols;
    const gchar *const *p;
    gboolean found_protocol = FALSE;

    protocols = iface->get_protocols (G_OBJECT_TYPE (handler));
    if (protocols != NULL) {
      for (p = protocols; *p != NULL; ++p) {
        if (g_ascii_strcasecmp (protocol, *p) == 0) {
          found_protocol = TRUE;
          break;
        }
      }

      if (!found_protocol) {
        g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_UNSUPPORTED_PROTOCOL,
            _("URI scheme '%s' not supported"), protocol);
        g_free (protocol);
        return FALSE;
      }
    }
  }

  ret = iface->set_uri (handler, uri, error);

  g_free (protocol);

  return ret;
}

备注:

Source插件需要实现接口GstURIHandlerInterface

/**
 * GstURIHandlerInterface:
 * @parent: The parent interface type
 * @get_type: Method to tell whether the element handles source or sink URI.
 * @get_protocols: Method to return the list of protocols handled by the element.
 * @get_uri: Method to return the URI currently handled by the element.
 * @set_uri: Method to set a new URI.
 *
 * Any #GstElement using this interface should implement these methods.
 */
struct _GstURIHandlerInterface {
  GTypeInterface parent;

  /* vtable */
  /*< public >*/
  /* querying capabilities */
  GstURIType             (* get_type)           (GType type);
  const gchar * const *  (* get_protocols)      (GType type);

  /* using the interface */
  gchar *                (* get_uri)            (GstURIHandler * handler);
  gboolean               (* set_uri)            (GstURIHandler * handler,
                                                 const gchar   * uri,
                                                 GError       ** error);
};

souphttpsrc的例子:

 

G_DEFINE_TYPE_WITH_CODE (GstSoupHTTPSrc, gst_soup_http_src, GST_TYPE_PUSH_SRC,
    G_IMPLEMENT_INTERFACE (GST_TYPE_URI_HANDLER,
        gst_soup_http_src_uri_handler_init));

static void
gst_soup_http_src_uri_handler_init (gpointer g_iface, gpointer iface_data)
{
  GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface;

  iface->get_type = gst_soup_http_src_uri_get_type;
  iface->get_protocols = gst_soup_http_src_uri_get_protocols;
  iface->get_uri = gst_soup_http_src_uri_get_uri;
  iface->set_uri = gst_soup_http_src_uri_set_uri;
}

 (6)那我们继续看下gst_element_make_from_uri中的关键步骤 possibilities = get_element_factories_from_uri_protocol (type, protocol)->gst_registry_feature_filter

  possibilities = gst_registry_feature_filter (gst_registry_get (),
      search_by_entry, FALSE, &entry);

主要逻辑就是从注册表中匹配对应的source:

static gboolean
search_by_entry (GstPluginFeature * feature, gpointer search_entry)
{
  const gchar *const *protocols;
  GstElementFactory *factory;
  SearchEntry *entry = (SearchEntry *) search_entry;

  if (!GST_IS_ELEMENT_FACTORY (feature))
    return FALSE;
  factory = GST_ELEMENT_FACTORY_CAST (feature);

  if (factory->uri_type != entry->type)
    return FALSE;

  protocols = gst_element_factory_get_uri_protocols (factory);

  if (protocols == NULL) {
    g_warning ("Factory '%s' implements GstUriHandler interface but returned "
        "no supported protocols!", gst_plugin_feature_get_name (feature));
    return FALSE;
  }

  while (*protocols != NULL) {
    if (g_ascii_strcasecmp (*protocols, entry->protocol) == 0)
      return TRUE;
    protocols++;
  }
  return FALSE;
}
/**
 * gst_element_factory_get_uri_protocols:
 * @factory: a #GstElementFactory
 *
 * Gets a %NULL-terminated array of protocols this element supports or %NULL if
 * no protocols are supported. You may not change the contents of the returned
 * array, as it is still owned by the element factory. Use g_strdupv() to
 * make a copy of the protocol string array if you need to.
 *
 * Returns: (transfer none) (array zero-terminated=1): the supported protocols
 *     or %NULL
 */
const gchar *const *
gst_element_factory_get_uri_protocols (GstElementFactory * factory)
{
  g_return_val_if_fail (GST_IS_ELEMENT_FACTORY (factory), NULL);

  return (const gchar * const *) factory->uri_protocols;
}

看下几个常见的source插件的信息:

gst-inspect-1.0 soupsrc

Factory Details:
  Rank                     primary (256)
  Long-name                HTTP client source
  Klass                    Source/Network
  Description              Receive data as a client over the network via HTTP using SOUP
  Author                   Wouter Cloetens <wouter@mind.be>

Plugin Details:
  Name                     soup
  Description              libsoup HTTP client src/sink
  Filename                 /libgstsoup.so
  Version                  1.20
  License                  LGPL
  Source module            gst-plugins-good
  Binary package           GStreamer Good Plug-ins git
  Origin URL               Unknown package origin

GObject
 +----GInitiallyUnowned
       +----GstObject
             +----GstElement
                   +----GstBaseSrc
                         +----GstPushSrc
                               +----GstSoupHTTPSrc

Implemented Interfaces:
  GstURIHandler

Pad Templates:
  SRC template: 'src'
    Availability: Always
    Capabilities:
      ANY

Element has no clocking capabilities.

URI handling capabilities:
  Element can act as source.
  Supported URI protocols:
    http
    https
    icy
    icyx

Pads:
  SRC: 'src'
    Pad Template: 'src'

Element Properties:
  automatic-redirect  : Automatically follow HTTP redirects (HTTP Status Code 3xx)
                        flags: readable, writable
                        Boolean. Default: true
  blocksize           : Size in bytes to read per buffer (-1 = default)
                        flags: readable, writable
                        Unsigned Integer. Range: 0 - 4294967295 Default: 4096
  compress            : Allow compressed content encodings
                        flags: readable, writable
                        Boolean. Default: false
  cookies             : HTTP request cookies
                        flags: readable, writable
                        Boxed pointer of type "GStrv"
  do-timestamp        : Apply current stream time to buffers
                        flags: readable, writable
                        Boolean. Default: false
  extra-headers       : Extra headers to append to the HTTP request
                        flags: readable, writable
                        Boxed pointer of type "GstStructure"
  http-log-level      : Set log level for soup's HTTP session log
                        flags: readable, writable
                        Enum "SoupLoggerLogLevel" Default: 2, "headers"
                           (0): none             - SOUP_LOGGER_LOG_NONE
                           (1): minimal          - SOUP_LOGGER_LOG_MINIMAL
                           (2): headers          - SOUP_LOGGER_LOG_HEADERS
                           (3): body             - SOUP_LOGGER_LOG_BODY
  iradio-mode         : Enable internet radio mode (ask server to send shoutcast/icecast metadata interleaved with the actual stream data)
                        flags: readable, writable
                        Boolean. Default: true
  is-live             : Act like a live source
                        flags: readable, writable
                        Boolean. Default: false
  keep-alive          : Use HTTP persistent connections
                        flags: readable, writable
                        Boolean. Default: true
  location            : Location to read from
                        flags: readable, writable
                        String. Default: null
  method              : The HTTP method to use (GET, HEAD, OPTIONS, etc)
                        flags: readable, writable
                        String. Default: null
  name                : The name of the object
                        flags: readable, writable, 0x2000
                        String. Default: "souphttpsrc0"
  num-buffers         : Number of buffers to output before sending EOS (-1 = unlimited)
                        flags: readable, writable
                        Integer. Range: -1 - 2147483647 Default: -1
  parent              : The parent of the object
                        flags: readable, writable, 0x2000
                        Object of type "GstObject"
  proxy               : HTTP proxy server URI
                        flags: readable, writable
                        String. Default: ""
  proxy-id            : HTTP proxy URI user id for authentication
                        flags: readable, writable
                        String. Default: null
  proxy-pw            : HTTP proxy URI user password for authentication
                        flags: readable, writable
                        String. Default: null
  retries             : Maximum number of retries until giving up (-1=infinite)
                        flags: readable, writable
                        Integer. Range: -1 - 2147483647 Default: 3
  ssl-ca-file         : Location of a SSL anchor CA file to use
                        flags: readable, writable
                        String. Default: null
  ssl-strict          : Strict SSL certificate checking
                        flags: readable, writable
                        Boolean. Default: true
  ssl-use-system-ca-file: Use system CA file
                        flags: readable, writable
                        Boolean. Default: true
  timeout             : Value in seconds to timeout a blocking I/O (0 = No timeout).
                        flags: readable, writable
                        Unsigned Integer. Range: 0 - 3600 Default: 15
  tls-database        : TLS database with anchor certificate authorities used to validate the server certificate
                        flags: readable, writable
                        Object of type "GTlsDatabase"
  tls-interaction     : A GTlsInteraction object to be used when the connection or certificate database need to interact with the user.
                        flags: readable, writable
                        Object of type "GTlsInteraction"
  typefind            : Run typefind before negotiating (deprecated, non-functional)
                        flags: readable, writable, deprecated
                        Boolean. Default: false
  user-agent          : Value of the User-Agent HTTP request header field
                        flags: readable, writable
                        String. Default: "GStreamer souphttpsrc 1.20.6.1 "
  user-id             : HTTP location URI user id for authentication
                        flags: readable, writable
                        String. Default: null
  user-pw             : HTTP location URI user password for authentication
                        flags: readable, writable
                        String. Default: null

 gst-inspect-1.0 filesrc

Factory Details:
  Rank                     primary (256)
  Long-name                File Source
  Klass                    Source/File
  Description              Read from arbitrary point in a file
  Author                   Erik Walthinsen <omega@cse.ogi.edu>

Plugin Details:
  Name                     coreelements
  Description              GStreamer core elements
  Filename                 /libgstcoreelements.so
  Version                  1.20
  License                  LGPL
  Source module            gstreamer
  Binary package           GStreamer git
  Origin URL               Unknown package origin

GObject
 +----GInitiallyUnowned
       +----GstObject
             +----GstElement
                   +----GstBaseSrc
                         +----GstFileSrc

Implemented Interfaces:
  GstURIHandler

Pad Templates:
  SRC template: 'src'
    Availability: Always
    Capabilities:
      ANY

Element has no clocking capabilities.

URI handling capabilities:
  Element can act as source.
  Supported URI protocols:
    file

Pads:
  SRC: 'src'
    Pad Template: 'src'

Element Properties:
  blocksize           : Size in bytes to read per buffer (-1 = default)
                        flags: readable, writable
                        Unsigned Integer. Range: 0 - 4294967295 Default: 4096
  do-timestamp        : Apply current stream time to buffers
                        flags: readable, writable
  location            : Location of the file to read
                        flags: readable, writable, changeable only in NULL or READY state
                        String. Default: null
  name                : The name of the object
                        flags: readable, writable, 0x2000
                        String. Default: "filesrc0"
  num-buffers         : Number of buffers to output before sending EOS (-1 = unlimited)
                        flags: readable, writable
                        Integer. Range: -1 - 2147483647 Default: -1
  parent              : The parent of the object
                        flags: readable, writable, 0x2000
                        Object of type "GstObject"
  typefind            : Run typefind before negotiating (deprecated, non-functional)
                        flags: readable, writable, deprecated
                        Boolean. Default: false

相关文章:

  • 2024_BUAA数据结构上机题解分享
  • 03.03 QT
  • Android开发Android调web的方法
  • Feign 深度解析
  • 火语言RPA--PDF提取表格
  • 详解LSM树
  • matlab 包围盒中心匹配法实现点云粗配准
  • 【Elasticsearch】Set up a data stream 创建data stream
  • AIP-158 分页
  • Leetcode 215 数组中的第K个最大元素
  • 一、计算机等级考试——标准评分
  • Leetcode 37: 解数独
  • 【数据分析】复杂实验,通过正交表组合来进行实验设计
  • 安全渗透测试的全面解析与实践
  • 虚拟机ip配置
  • 网页制作11-html,css,javascript初认识のCCS样式列表(上)
  • 【Azure 架构师学习笔记】- Azure Databricks (14) -- 搭建Medallion Architecture part 2
  • Vue 3 中 unref 的作用与 Vue Router currentRoute 的知识
  • Spring Boot整合RabbitMQ
  • 蓝桥杯 - 每日打卡(类斐波那契循环数)
  • 当农民跨进流动的世界|劳动者的书信①
  • 央行就《关于规范供应链金融业务引导供应链信息服务机构更好服务中小企业融资有关事宜的通知》答问
  • 神舟十九号航天员乘组平安抵京
  • 赵乐际主持十四届全国人大常委会第十五次会议闭幕会并作讲话
  • 东风着陆场近日气象条件满足神舟十九号安全返回要求
  • 俄罗斯延长非法滞留外国人限期离境时间至9月