GStreamer Probe 探针的妙用一

Posted on Sat 30 December 2023 in Journal

Abstract GStreamer Probe 探针的妙用一
Authors Walter Fan
 Category    learning note  
Status v1.0
Updated 2023-12-30
License CC-BY-NC-ND 4.0

探针 Probe 是一种可由应用程序安装的回调,可将数据流的状态及内容回调给应用程序,在实现中妙用无穷,既可以用来检查在 GStreamer Pipeline 中流传的 Data, Query 和 Event, 还可以用来检查和修改在回调函数中收到的数据,以及阻塞或传递在 pipeline 流传的数据。

先总结一下我在 GStreamer Probe 看到的内容,然后写一个简单的小程序演示一下探针的妙用。

用法

应用程序应该能够监视和控制 pad 上的数据流,有以下用法:

  • 当 pad 空闲时收到通知,并确保 pad 保持空闲。这对于能够实现元件的动态重新链接至关重要,从而不会中断数据流。
  • 当数据 data、事件 event 或查询 query 被发送到 pad 上就会被通知,这样就可以在回调函数中检查和修改数据。
  • 能够根据回调函数返回的结果,在 pad 上丢弃、传递和阻塞数据
  • 能够在被阻塞的 pad 中由应用线程执行的方法丢弃,传递数据

概述

函数 gst_pad_add_probe() 用于在 pad 上添加探针 probe. 它接受的参数有 probe type mask 和一个 callback 函数.

    gulong  gst_pad_add_probe    (GstPad *pad,
                                  GstPadProbeType mask,
                                  GstPadProbeCallback callback,
                                  gpointer user_data,
                                  GDestroyNotify destroy_data);

此函数返回 gulong 数据类型来标识这个探针,这个 probe_id 可以用 gst_pad_remove_probe() 来移除探针

    void    gst_pad_remove_probe (GstPad *pad, gulong id);

而 mask 掩码参数是以下 flags 的按位或操作 "bitwise or"

typedef enum
{
  GST_PAD_PROBE_TYPE_INVALID          = 0,

  /* flags to control blocking */
  GST_PAD_PROBE_TYPE_IDLE             = (1 << 0),
  GST_PAD_PROBE_TYPE_BLOCK            = (1 << 1),

  /* flags to select datatypes */
  GST_PAD_PROBE_TYPE_BUFFER           = (1 << 4),
  GST_PAD_PROBE_TYPE_BUFFER_LIST      = (1 << 5),
  GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM = (1 << 6),
  GST_PAD_PROBE_TYPE_EVENT_UPSTREAM   = (1 << 7),
  GST_PAD_PROBE_TYPE_EVENT_FLUSH      = (1 << 8),
  GST_PAD_PROBE_TYPE_QUERY_DOWNSTREAM = (1 << 9),
  GST_PAD_PROBE_TYPE_QUERY_UPSTREAM   = (1 << 10),

  /* flags to select scheduling mode */
  GST_PAD_PROBE_TYPE_PUSH             = (1 << 12),
  GST_PAD_PROBE_TYPE_PULL             = (1 << 13),
} GstPadProbeType;

添加带有 IDLE 或 BLOCK 标志的探针时,探针将成为阻塞探针 (blocking probe)。否则,探针将是数据探针 (Data Probe)。

数据类型和调度选择器标志用于选择在回调函数中可以使用哪种类型的数据类型和调度模式。

Blocking flags 必须与所触发的 probe 完全匹配。

探测回调定义为:

GstPadProbeReturn (*GstPadProbeCallback) (GstPad *pad, GstPadProbeInfo *info,
    gpointer user_data);

probe info 结构作为参数传递,其类型必须匹配于注册回调时使用的掩码。Probe info 中包含的数据项包含特定类型的数据,通常是被阻止的数据项或者是 NULL(当不存在数据项时)

探针可以返回以下任何返回值:

typedef enum
{
  GST_PAD_PROBE_DROP,
  GST_PAD_PROBE_OK,
  GST_PAD_PROBE_REMOVE,
  GST_PAD_PROBE_PASS,
  GST_PAD_PROBE_HANDLED
} GstPadProbeReturn;
  • GST_PAD_PROBE_OK 是正常的返回值。 _DROP 将丢弃目前正在探测的数据, GST_PAD_PROBE_REMOVE 将从探针列表中移除当前所正在执行的探针

  • GST_PAD_PROBE_PASS 与阻塞探针相关,并将暂时取消阻塞, 让数据项通过,然后它会再次阻塞下一个数据项。

阻塞探针

阻塞探针是设置了 BLOCKIDLE 标志的探针。它们将一直阻止数据流并按照以下规则触发回调:

  • IDLE 标志设置后,一旦没有数据经过 pad,就会调用探针的回调函数。如果在探针注册时,pad 处于空闲状态, 回调函数会立即从当前线程调用。否则, 一旦 pad 的状态变为空闲 (Idle),就会在数据流线程中调用探针的回调函数。

IDLE 探针在执行动态链接时很有用,允许应用程序等待 "unlink/link" 操作的正确执行。由于探针是阻挡探针,它还将确保 pad 保持空闲状态,直到此探针被删除。

  • BLOCK 标志设置后,当有新数据时到达 Pad 时将调用探针的回调函数,也就在 pad 即将进入阻塞状态之前。因此只有当 pad 上有新数据时才会调用此探针的回调。

阻塞探针由 gst_pad_remove_probe() 函数移除, 或当探针的回调函数返回 GST_PAD_PROBE_REMOVE 时移除此探针。

在这两种情况下,如果这是 pad 上的最后一个阻塞探针,pad 就会被解锁 (unblocked),数据流可以继续。

非阻塞探针

非阻塞探针或数据探针是在 Pad 中数据流动时触发的探针。它们在阻塞探针运行之后调用,并且始终针对于数据。

推送数据流

推送探针在回调中设置了 GST_PAD_PROBE_TYPE_PUSH 标志。

在基于推送的调度中,首先使用数据项调用阻塞探针 (blocking probe)。然后,在对等 pad 的 chainevent 函数调用之前 调用数据探针 (data probe)

在对等的 pad 被检查之前会调用 data probe, 它允许链接 pad 以阻塞探针 (BLOCK probe) 或者数据探针 (DATA probe)

在对等的 pad 的 chain 或 event 函数调用之前, 对等的 pad 的阻塞探针 (BLOCK probe) 或者数据探针 (DATA probe) 会被调用。

最后,在数据发送给对等的 pad 之后 IDEL 探针被调用

推送数据流探针行为与数据缓冲和双向事件的行为相同

                    pad                           peerpad
                     |                               |
gst_pad_push() /     |                               |
gst_pad_push_event() |                               |
-------------------->O                               |
                     O                               |
       flushing?     O                               |
       FLUSHING      O                               |
       < - - - - - - O                               |
                     O-> do BLOCK probes             |
                     O                               |
                     O-> do DATA probes              |
        no peer?     O                               |
       NOT_LINKED    O                               |
       < - - - - - - O                               |
                     O   gst_pad_chain() /           |
                     O   gst_pad_send_event()        |
                     O------------------------------>O
                     O                   flushing?   O
                     O                   FLUSHING    O
                     O< - - - - - - - - - - - - - - -O
                     O                               O-> do BLOCK probes
                     O                               O
                     O                               O-> do DATA probes
                     O                               O
                     O                               O---> chainfunc /
                     O                               O     eventfunc
                     O< - - - - - - - - - - - - - - -O
                     O                               |
                     O-> do IDLE probes              |
                     O                               |
       < - - - - - - O                               |
                     |                               |

拉取数据流

拉取探针在回调中设置了 GST_PAD_PROBE_TYPE_PULL 标志。而 gst_pad_pull_range() 调用先触发 BLOCK 探针, 并不包含数据项。它允许对等的 pad 解析成功前进行连接此 pad, 还允许在回调函数中设置数据项

在对等的 pad 上调用阻塞探针 blocking probe 和 getrange 并有数据项传递之后,此 pad 上的 数据探针 Data Probe 将会被调用。

当控制权返回到 sink pad, IDLE 回调会被调用,没有数据项就会调用 IDLE 回调,所以如果发生错误时也会调用 IDLE 回调

如果有了有效的数据项, DATA 探针也会被调用

                srcpad                          sinkpad
                  |                               |
                  |                               | gst_pad_pull_range()
                  |                               O<---------------------
                  |                               O
                  |                               O  flushing?
                  |                               O  FLUSHING
                  |                               O - - - - - - - - - - >
                  |             do BLOCK probes <-O
                  |                               O   no peer?
                  |                               O  NOT_LINKED
                  |                               O - - - - - - - - - - >
                  |          gst_pad_get_range()  O
                  O<------------------------------O
                  O                               O
                  O flushing?                     O
                  O FLUSHING                      O
                  O- - - - - - - - - - - - - - - >O
do BLOCK probes <-O                               O
                  O                               O
 getrangefunc <---O                               O
                  O  flow error?                  O
                  O- - - - - - - - - - - - - - - >O
                  O                               O
 do DATA probes <-O                               O
                  O- - - - - - - - - - - - - - - >O
                  |                               O
                  |              do IDLE probes <-O
                  |                               O   flow error?
                  |                               O - - - - - - - - - - >
                  |                               O
                  |              do DATA probes <-O
                  |                               O - - - - - - - - - - >
                  |                               |

查询 Queries

查询探针 (Query probes) 在回调中设置了 GST_PAD_PROBE_TYPE_QUERY_* 标志.

                    pad                           peerpad
                     |                               |
gst_pad_peer_query() |                               |
-------------------->O                               |
                     O                               |
                     O-> do BLOCK probes             |
                     O                               |
                     O-> do QUERY | PUSH probes      |
        no peer?     O                               |
          FALSE      O                               |
       < - - - - - - O                               |
                     O   gst_pad_query()             |
                     O------------------------------>O
                     O                               O-> do BLOCK probes
                     O                               O
                     O                               O-> do QUERY | PUSH probes
                     O                               O
                     O                               O---> queryfunc
                     O                    error      O
       <- - - - - - - - - - - - - - - - - - - - - - -O
                     O                               O
                     O                               O-> do QUERY | PULL probes
                     O< - - - - - - - - - - - - - - -O
                     O                               |
                     O-> do QUERY | PULL probes      |
                     O                               |
       < - - - - - - O                               |
                     |                               |

对于查询 queries, 当查询到达了要回答这个查询的对象, ProbeType 设置为 PUSH 而当 query 中已经包含了答案 , ProbeType 将设置为 PULL

用例

预滚动部分管道

    .---------.      .---------.                .----------.
    | filesrc |      | demuxer |     .-----.    | decoder1 |
    |        src -> sink      src1 ->|queue|-> sink       src
    '---------'      |         |     '-----'    '----------' X
                     |         |                .----------.
                     |         |     .-----.    | decoder2 |
                     |        src2 ->|queue|-> sink       src
                     '---------'     '-----'    '----------' X

其目的是动态创建管道,一直连到解码器,但尚未将它们连接到接收器 sink, 并且不会丢失任何数据。

为此,解码器的 source pad 被阻止,以便没有 event 或 buffer 可以逃脱,并且我们也不会中断数据流。

当所有动态 pad 都被创建(分支点不再创建出新的 pad,即解复用器 demuxer 或队列 queue 已满)并且 pad 被阻止(收到 blocked callback )时,管道将完全预滚动 (preroll)。

然后应该可以在预滚动的管道上执行以下操作:

  • 查询时长/位置

  • 执行刷新搜索以预滚动到新位置

  • 连接其他元件并疏通堵塞的 pads

在一个 PLAYING 的管道中动态切换一个元件

 .----------.      .----------.      .----------.
 | element1 |      | element2 |      | element3 |
...        src -> sink       src -> sink       ...
 '----------'      '----------'      '----------'
                   .----------.
                   | element4 |
                  sink       src
                   '----------'

其目的是在 PLAYING 的管道中将 element4 替换为 element2

  1. 阻塞 element1 的 src pad.
  2. 在阻塞回调函数的内部,element1 和 element2 之间没有任何数据流动,并且在解除阻塞之前不会有任何数据流动。
  3. 将 element1 和 element2 解除连接
  4. 可选步骤:确保数据从 element2 中清除:
  5. 4a) 在 element2 src pad上添加事件探针
  6. 4b) 将 EOS 事件发送到 element2,这可确保 element2 清除其保存的最后一位数据。
  7. 4c) 等待 EOS 出现在探针中,丢弃 EOS 事件
  8. 4d) 删除 pad 上的 EOS 事件探针。

  9. 解除 element2 和 element3 的连接

  10. 5a) 现在可以选择将 element2 设置为“NULL”和/或从管道中将其删除。
  11. 连接 element4 和 element3
  12. 连接 element1 和 element4
  13. 确保 element4 与其余元素处于相同的状态。 该元素至少应该是 PAUSED 状态
  14. 解除 element1 src pad 的阻塞

相同的流程可用于替换“PAUSED”管道中的元素。 当然,在 “PAUSED” 管道中可能没有数据流,因此阻塞可能不会立即发生。

示例

1. 探查 pad 上的收到的 event, query 和 data

#include <gst/gst.h>
#include <string>
#include <iostream>
#include <sstream>
#include <iomanip>

static std::string bytes_to_hex(uint8_t* bytes, size_t len) {
    std::stringstream ss;

    // Ensure the output is in uppercase and has two characters for each byte
    ss << std::uppercase << std::setfill('0');
    for (size_t i = 0; i < len; ++i) {
        ss << std::setw(2) << std::hex << static_cast<int>(bytes[i]);
    }

    return ss.str();
}

static gboolean handoff_callback(GstElement *sink, GstBuffer *buffer, gpointer user_data)
{
  // This function will be called when the handoff signal is emitted
  g_print("Handoff Callback - Received buffer with size: %zu\n", gst_buffer_get_size(buffer));
  return TRUE;
}

GstPadProbeReturn cb_have_data_list(GstPad *pad, GstPadProbeInfo *info,
                                    gpointer user_data)
{

    GstBufferList *buflist = gst_pad_probe_info_get_buffer_list(info);
    if (buflist)
        g_print("cb_hava_data_list from %s\n", (char *)user_data);
    return GST_PAD_PROBE_OK;
}

GstPadProbeReturn cb_have_data(GstPad *pad, GstPadProbeInfo *info,
                                gpointer user_data)
{

    //GstBuffer *buf = gst_pad_probe_info_get_buffer(info);
    GstBuffer *buf = (GstBuffer *) info->data;
    GstMapInfo in_map_info;
    memset (&in_map_info, 0, sizeof (in_map_info));
    static uint32_t data_count = 0;

    if (buf) {
      g_print("cb_hava_data: offet=%lu from %s\n", buf->offset,
              (char *)user_data);
      if (gst_buffer_map (buf, &in_map_info, GST_MAP_READWRITE))
      {
        std::string hexstr = bytes_to_hex(in_map_info.data, in_map_info.size);
        std::cout << "cb_hava_data, count=" <<(++data_count) << ", size=" << in_map_info.size;
        if (data_count % 5 == 0) {
          std::cout << "receive data=" << hexstr << std::endl;
        }

      }
    }
    return GST_PAD_PROBE_OK;
}

GstPadProbeReturn cb_have_event(GstPad *pad, GstPadProbeInfo *info,
                                    gpointer user_data)
{

    GstEvent *event = gst_pad_probe_info_get_event(info);
    if (event)
      g_print("cb_hava_event: event type=%s from %s\n", GST_EVENT_TYPE_NAME(event),
              (char *)user_data);

    return GST_PAD_PROBE_OK;
}

GstPadProbeReturn cb_have_query(GstPad *pad, GstPadProbeInfo *info,
                                gpointer user_data)
{

    GstQuery *query = gst_pad_probe_info_get_query(info);

    g_print("cb_hava_query: query type=%s from %s\n", GST_QUERY_TYPE_NAME(query),
          (char *)user_data);

    return GST_PAD_PROBE_OK;
}

void add_pad_probe(GstPad *pad_to_probe, const char *evt_pad_name)
{
    // GST_PAD_PROBE_TYPE_BUFFER_LIST
    gulong probe_id_0 = gst_pad_add_probe(pad_to_probe,
                                          GST_PAD_PROBE_TYPE_BUFFER_LIST, cb_have_data_list,
                                          (gpointer)evt_pad_name, NULL);

    // GST_PAD_PROBE_TYPE_BUFFER
    gulong probe_id_1 = gst_pad_add_probe(pad_to_probe,
                                          GST_PAD_PROBE_TYPE_BUFFER, cb_have_data, (gpointer)evt_pad_name,
                                          NULL);

    // GST_PAD_PROBE_TYPE_EVENT_BOTH
    gulong probe_id_2 = gst_pad_add_probe(pad_to_probe,
                                          GST_PAD_PROBE_TYPE_EVENT_BOTH, cb_have_event,
                                          (gpointer)evt_pad_name, NULL);

    // GST_PAD_PROBE_TYPE_QUERY_BOTH
    gulong probe_id_3 = gst_pad_add_probe(pad_to_probe,
                                          GST_PAD_PROBE_TYPE_QUERY_BOTH, cb_have_query,
                                          (gpointer)evt_pad_name, NULL);

}

int main(int argc, char *argv[])
{

  GstElement *pipeline, *source, *sink;
  GstBus *bus;
  GstMessage *msg;
  GstStateChangeReturn ret;

  gst_init(&argc, &argv);

  /* Create the elements */
  source = gst_element_factory_make("audiotestsrc", "source");
  sink = gst_element_factory_make("fakesink", "sink");

  /* Create the empty pipeline */
  pipeline = gst_pipeline_new("test-pipeline");

  if (!pipeline || !source || !sink)
  {
    g_printerr("Not all elements could be created.\n");
    return -1;
  }

  /* Build the pipeline */
  gst_bin_add_many(GST_BIN(pipeline), source, sink, NULL);
  if (gst_element_link(source, sink) != TRUE)
  {
    g_printerr("Elements could not be linked.\n");
    gst_object_unref(pipeline);
    return -1;
  }

  /* Modify the source's properties */
  g_object_set(source, "num-buffers", 10, NULL);
  g_object_set(source, "wave", 5, NULL);

  // Connect the handoff signal to the callback function
  g_signal_connect(sink, "handoff", G_CALLBACK(handoff_callback), NULL);

  GstPad* audioSinkPad = gst_element_get_static_pad(sink, "sink");
  add_pad_probe(audioSinkPad, "fakesink");

  /* Start playing */
  ret = gst_element_set_state(pipeline, GST_STATE_PLAYING);
  if (ret == GST_STATE_CHANGE_FAILURE)
  {
    g_printerr("Unable to set the pipeline to the playing state.\n");
    gst_object_unref(pipeline);
    return -1;
  }

  /* Wait until error or EOS */
  bus = gst_element_get_bus(pipeline);
  msg = gst_bus_timed_pop_filtered(bus, GST_CLOCK_TIME_NONE,
                                 (GstMessageType)(GST_MESSAGE_ERROR | GST_MESSAGE_EOS));

  /* Parse message */
  if (msg != NULL)
  {
    GError *err;
    gchar *debug_info;

    switch (GST_MESSAGE_TYPE(msg))
    {
    case GST_MESSAGE_ERROR:
      gst_message_parse_error(msg, &err, &debug_info);
      g_printerr("Error received from element %s: %s\n",
                 GST_OBJECT_NAME(msg->src), err->message);
      g_printerr("Debugging information: %s\n",
                 debug_info ? debug_info : "none");
      g_clear_error(&err);
      g_free(debug_info);
      break;
    case GST_MESSAGE_EOS:
      g_print("End-Of-Stream reached.\n");
      break;
    default:
      /* We should not reach here because we only asked for ERRORs and EOS */
      g_printerr("Unexpected message received.\n");
      break;
    }
    gst_message_unref(msg);
  }

  /* Free resources */
  gst_object_unref(bus);
  gst_element_set_state(pipeline, GST_STATE_NULL);
  gst_object_unref(pipeline);
  return 0;
}

执行结果如下

../bin/gst-fakesink
cb_hava_event: event type=stream-start from fakesink
cb_hava_query: query type=caps from fakesink
cb_hava_query: query type=caps from fakesink
cb_hava_event: event type=caps from fakesink
cb_hava_query: query type=accept-caps from fakesink
cb_hava_query: query type=accept-caps from fakesink
cb_hava_query: query type=allocation from fakesink
cb_hava_event: event type=segment from fakesink
cb_hava_event: event type=tag from fakesink
cb_hava_data: offet=0 from fakesink
cb_hava_data, count=1, size=2048cb_hava_query: query type=latency from fakesink
cb_hava_query: query type=latency from fakesink
cb_hava_event: event type=latency from fakesink
cb_hava_data: offet=1024 from fakesink
cb_hava_data, count=2, size=2048cb_hava_data: offet=2048 from fakesink
cb_hava_data, count=3, size=2048cb_hava_data: offet=3072 from fakesink
cb_hava_data, count=4, size=2048cb_hava_data: offet=4096 from fakesink
cb_hava_data, count=5, size=2048receive data=6436810BACB8DA15DBDBA62FF7BB9A1D9D4481D1AA9C154BC0DB4FBB235F6FDD4742E2D256EA17AB2E5642BFF34591F6CB9F8E0EDA61DB5D0C0185BC529F31B6C2DA23E9D9D42CF9BF3AFAF6CDAC29DC8A0EE05F3CD572E29F9D1C01CF2DC81C4CE8563371101A3915291852C66126D8BAFF73EAACFAC323142516DFCB65F89F922CE100692AABFCE019EA4F010156CD54A82DBBD2466FCBC3A5E1B75FC2B8C409E200FC419EFBBA4256321BBFAB8E1B6F22D82E85FB1BFE38A8C122B16253D876BCC9D2CA3E595C445D89FC89EF55FD3FFBEF06D438E017CAC2702842F865EE120D53029F3FAB53A8415C463E28E9DF7BEEC5D984ADACC74D56711EAA0F5A0F4B2A633C6CF02D4621242814B7A07216E8BA8EF666D2069AA42C2B0397305D0B65DD7EFE67CC5C55A03E07B6D79A74EBAFA4FD2D7E37435EF8DCC13ED1596EE7F4F04CEB410F352D4C1569E7039C99504147AE034A12A9D767B19CFEE69B2BC788F75266360AC13857B53C037DA11EB37DA19FD8FB5F1DFD48A34F1F1F2C305B133BE94CD72447B6BBCA620D1E3FF536C91B545FDE5E661F3BD628DE919DFAFA0F25CE5618393513C64698A78D06173B59366ABD30546BC44228BDE0BEDB8127A5609B17FA1BC62C79FAB1BAC402573EF6228931F902D2D2B59BAB65AB5DA80788B96EDEB2AA292A474CD9C268C505A3C01520B72AFFE1CDFE2DF032AAFA8907DC49993FBAD755A1FFCA8D4EED6143E129F09AA89C0E434EC214D435B99A9B1670A3C8CB933743E92E053FE06A9F8B5F91FDE2C984FBA25440C021B3380B83325762CD222CCB17E5173CE837279AC9DECA1893B080BA0EB9C621B062C0F3CA5905D99D48865D14D01A14B637DDC111CC463A8C3399E5F99FC6C33D304A280262AAE640BF9D235D1F89136912ADA7444741ADA850FC52D1C7A000484122AE04B5C3B1F661480D78C54407D1D7A6EB5831D9D4AF04C5C1D819B86160E6460190B12BE629FD1F028F495A3084B1541BA801AF1E1AF9C7ED4A01905AAF0217D017C4F7D866E09FF22DF9D00128C56240A81FFC16FD112BD3EB2521C4A09E63421DBE0D3BE0F9C59C475BCA45339E7B06792AE74F01355300F0FB4F4B3C193CFB76C21563C7473018981E1E608526A6A1FB2ADE9C01F3A363C91840CA98F847DCBC22C51CC50CCF4634AB1E56C04058B9311190A9AFC421A9D69E722CF6CFAB2F5CE32702E30CE6392AE3500F95C09ABD19A19EA55E27DD14F31C2DBF7201AE249CEFF3D331B1BCBBB3B2ECED292B2A0AA138DB4C55A1D34AA0AB9F2B2FAD33C0981ACC5F40DE4EB47FCB2DC474B4094C5BD5462A1462201EEBB67855F7D5B3AD061434DF493922D4BD01055F190EA752F6CBD1AAE53C6DE7289AF2FF4BA73CEE5B3BBCC430C50B134AA03C4AAE4E0BEEF29FB89F16575F068C128058A9CFC7CF29AA2153631C3BA9CBC69464F0AE731EE7ACE0F987C3322F8EA245354F1DBB0A5764CBA29B1748E6EB484B553848033C16DFDFD211D435E1ED10172EA44153B2D9AF6F415550AABA033210FBC79F26C4F04F76CDC9B9E0530244D1EB6C355F0238B853D3051AB7B7873C542D0C048A5E4FEF95D2514601E452BFB3D8D006EF04EFE0DC1373B6DFFCABA5B40CCA06305A86325A206CED6A534631BFB1FA4B7DCC7832493821ABFD9C0C1233AEAAA9EDF7A4401E06D1517B5A423E9C9A22A7943324DBD1D4A4245DE03CD937DE14370DA41F543E01F9184FC782483BF311AD53D09CBBAC9934C72CD2CD40AA9A28C62C2FB0C5BCF3B5F1C40219DB09ECAFC707AC6406B43EF749F829714FFBAB3028F9CC625FE730AB126F2CEF636D0FA29F3EB2B333CB2345B2873A611B66EFA508B225FA4B00BB19BA2CAF512D3EE2CB5AEA5C022D12C851EB88D991B227E20CB4379EEE5DF4BC2D100D1263A3B6A2450746141EBEE6FA801BB4CB9FAEB7FC5A5DAB23EC21D7FD779F4FEDA64541B28EC2C65DFEB96BC90E33EBDAFCE2EE0E8065A52595CE293522CD13C68EED18B884B18FC5EDEA5F5C975F085758A1C522CE31833C3B1B8019842314E3B3B6D80AD0F52D33A02252E1D82DDAD09C0116EA992298FBB5A34B4638576CBB56BCAC4DA558169FBC51E3AEEAE6EF24CA9B3531B9DA67A79C23B0BD3CA459271950801011E168C5A2E674C6973BBDECE6FCF310A9C7C24A8E4AD9117F16E119FA4CD1D23AE21CA5DFCC7D36EC2494AC9A4C319F4FD2EAEB4EFA27F570C156FAA355DCD231DF0D2F5DDE1131A8A70B54CE58BC515AC0ECE7A1E53B407D5700B337DD9EC3314A8353FD2B82AD34566C24095B2AD4C39A29D8F1BF58528C1E242204C685B6EE3CD73F85A2D7E48013C4098013342FB12E0DCCAAA228233CA828E5ED247BE58EA53706D8255B19A4A5B4C2AEA5E7EC9B538FA59AC9EDDEFEB91959F46026AACDF232A70A25293E855A549B6F074DC36331D603E059E230F8581E55C317635546ECE336F2DED65ACF3D0138F44C07FF114B75A741EBE555DA4954EDBF9E869A9C1931A971BFCE0165F1369F4E461D2CA6FA32DC232887B26AAD8FDB96369A566EFF621FC34B9840FE0DE73D77A332F0144832C179CE90D237BBEBEAF03533E2E4F15846824A2DD2BEEBD3D0F9DFE0FDC2282F2C59C5A50B8915E003F322B2E8D101E40141B6C44775FAF2FC20D8E9FB89B04BA90901F63222DD3FBBC4ACC8F45EB06EC34B371000245FA604EB21A3246B58BBB1D9FE144EFC50FAE4BF56CCC2DBA4EF5783AD534CBFCAD01268FAE0AF0AE8305B8CE7BFBA49FB40A077C0AD1FE665150E7AC64CD4C7F63560941F24EE420899DAA3548A34EA02E15C8CBA955349D26FCE07EB2437CDB352F4890B43DD3EE098150BEC2807126295F06461A79AD2D9BDA7CECF
cb_hava_data: offet=5120 from fakesink
cb_hava_data, count=6, size=2048cb_hava_data: offet=6144 from fakesink
cb_hava_data, count=7, size=2048cb_hava_data: offet=7168 from fakesink
cb_hava_data, count=8, size=2048cb_hava_data: offet=8192 from fakesink
cb_hava_data, count=9, size=2048cb_hava_data: offet=9216 from fakesink
cb_hava_data, count=10, size=2048receive data=E52F5ED3E9F2FEE91EFD2ED5719BC101D2CD2C031007C1F506ADB335F83DE902FA0936B32E2ED4215552BDF5835977EBB1FFB42B1A5E6BAE5CD9A7B5AA102038B7FB8458C74847595C36FC0AA05A2BE9C7E36CCB1E5F37D991E55FB2764CAE9DBFD0862292AAA8ADB25306F9BA37AEA4D113BAD15DD3642CB25B5AF2131522F8D2EC05417A402FFED3DF6AC002A09C333BD2162D7C5EE4D1D3EF0635E72724E3250A9532BE33C0A3C82843AF531D150B84AC91CE4BDDD8178111993F62574D3EEC51DD4DE800EF3EDB3D04BB9BA1754F255D24BEFF2942DC1D44630CB525264186AAC243CAD462D7AB9BD63E264ECFF01E0AD8A55BE776FED8287AE750B89EBFE21FC333700930EB7ACD282D1E0AAA37C105BDF1180EDBD699A9C0AFC74F184CA414E005DD9913BB7C104D63430DD642EF0D55D388456CE797C290CA0D190B47A2BC8621FF3BCF5C2B23E74E3EE5C10ADBED779D2F55B01D176419C4FCE20702C70098AAB900D52061FE9321C75B74384CFE7DC32C35D7B21E61D050683D7FC84ABD330C3C25195ACCB920F88A49C69CEF528AE2B2D780A47EE56736655A7354114A9F07F0EAD39DA75A8B1A763CED29593A7333DC3CC6F7DC38613C97F6660E33AA15FE6AB3BEBCDF3126ED3AB88FF83B120A3C5D552EB69EA11A494AB830EE3AE8D2A02C9AE04985FFFCE933C11644C81EF709FC4491BE24E7C01DB0142BADBB61BA31985C4FDBF365A15C78EB2F0AD5059B1100FE02C3F5C6975FBF28F03CAAB712E9A2F679E770EEDFD4FE1718D672AF7DD240C384A83FBA53537824AEE099FB415A1CB4710C94C9FED370C3E50FC9CF964A38EA5E21D2DACBBC7C3161C6E44F9A1CAFA4AA9F0AA110BD3AB90AB55766BDEC17FCEF2DED427F2A775CF3F34BE6D8FC29EB295EC50C2A410B50E83FADEE1DC1CD3B55367764FDF23FE02EFDE8B5D1A0F1E57B48B8F008A8490580F0A7F6F51D06A99C63D90B6BF1E1340F48CA63ADE0FAF869B0CD28B924BE3AC549BB28AE1231F8C52FEAD0DDCE24B4A9197EABBF2947B4A5FD9300D904ED6066B881CEC4FDC29955A6F4BC645BB7BE583B09A52536B9336A1CE5F9F450741A6613FF47344A9262AF1216A012F176D010602AAA200291D893461CC492044CC32A25EA9C9E227D17E7BC3FC49A04754FDCA3C2E2764E35A636E56FBD38C2D20A60EDBFC29F53B3253BBA654FD74089FF960D02C1E5A4AAB6FA3CF1B2AECC10A731667EE86022E0B410552137CA5CF5EA00A5D9E828AC481CBC17562637E6530E9B9F2C0D0354B9C3D81E43AE5EFCB1F1601666F00BA985A1C7B528125BAAADA13728C1C34543F2BEDEA62FBFE7A0DB49DCAD0DE562C11DEFC6C0C8C9D65E2E04C11F80509CAA8B545FB357EE14E33CD418EEF149E639740B03D0E5A8D90279A3FBE4D60F33D06B159EC203FE1E0E1F45E0C81E6441EC5D04A3ACE2E84EAC06C25F1D78A6835336561F21C0E2B39B0247285D7E1FE5AD3CB355130504B7BA51192943B107A86210471F3F5ED1E6CD8BADBA4ED44FF642E9F5F1A2652621A0DF53A9FF2601E1F27AA3A0A40357744123EAC64A68C5FE0AEEACBFA3DEA691D4E23045B59C514D09BAA1CA40FCB3CEBEB61D90A4B8494EC1692A000F825CECBF5214133592200625F4643AF6170927C6C8A9255102245AF5B33EFC53AE43FCFD67AC77F17CD16727BE44C4A6A5C9A6FDB76274E474FAF25EB2D94F46FB4D0EDE2A478BAD0352CD3C1ABFD2407306F25CF8C2ECF271372D5560F3283078E03F3BA0B81F06A53F543F27E981E9AB1CBB4D91542A654C3C9B9BDB525FB74C1112BCCCA363EE54E37D595F3EC1EE07C08414BAD31EB808EF7CCD9306E44645BDA042A6BE25E6ED142027633B673DC52DBA4761C81A371D4E3053B63306C8FF55870BD82E7BC26625C0E6794B28CC0E17953A0C2C922B7A186B050FF9ED508624E8B0909E8830D25AECD884E6C5F3955CF25BB144EDE651E2F417A41D1B10720C4254AF339653202AC6F5CEB25CA0A4A72106CCB7D7EAAF38D2E12D64E103FA3F39E987C9EC4EA64E1EFFCDD6A0E777F30E63F2014541C10CE9285C205D01DE44ED1EE42B2DF5D6DA75B556B312E0A60954B38DF301513AD4481FFDA5E61628EA8AA9432BE99E794792DF23D79AF45EA2F1CAED23E433553F54F2CB511448C9CB13FBBFAE40F58FE8954A05FA073341BC18EE7B5AFCC781B357EB50B0DCCEBBFC5E10ADB63147F9F94C109ACBE0DAB951970F0363AD0F1A66F446E0A69BB410F83CAEDE3F505712EE7E2683BAC8DF74B398C6A4BE649C2CF52411CB01E6FDDF4B455AA00112E2081CC95D1E11505806B8CD9CAF20E856455E3BD0D165C35D2D26B1E68E43C328B308EAC567A17F1373393914604A98F023565834ACF94ACE9340C263FD5A83279C0D2902C05B95F514DCC0CD2456B89CE1BA090550C52933C32BC35700581962FBFF260B9903CB399F3B12D4942E0B1792C9CA58CEE7A45C020478D21A1D14E2D3DD9E1F259C67A61211EF175B03325F64B0C7E969BFBECA546193D6AA40AF628D5BECE7A7C927CD1B0C115364D54F2DA2B9A6A8F1BDBFA051C51054A60569247EB3D9AA97633534C9473CE35C3A33C9B3A9C241F2E7EFFD40FFC93325C7B8E75F9C9916E8E381C6D948000629B590BFA6B2A8E5BA3F58BB5F1E28038408BE495C5E70D553DDA29FBAAB2012D4249ABCF0F4F7BF51D774DD6D376124A3CEE0C6200ADBB62AE17EE487358BE9BAF537CB3A04C15B6CC1655F39356CD1ECF08D9DEDC27C58A318F44BC5366936A4CC27EBF4B5E0BFCCC97B1A38666CE849123546A2E2DF451FE70EE029EB790C7D5DE3EB53DB521910F4B5D3AE0F03F585BD05AE1C3B8544783BABCCDA241BE7E951511A
cb_hava_event: event type=eos from fakesink
End-Of-Stream reached.
  • 源代码: https://github.com/walterfan/gstreamer-cookbook/blob/master/example/gst-fakesink.cpp
  • 编译脚本: https://github.com/walterfan/gstreamer-cookbook/blob/master/example/CMakeLists.txt

参考资料


本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。