DeepStream5.0系列之動態增減輸入源

點擊查看系列文章目錄

0 背景

從 DS4.0 開始就調試了 deepstream 動態增減源的功能,好長時間沒接觸,又生疏了。好記性不如爛筆頭,趁着今天回顧代碼,把過程記錄一下

1 先跑起來

NVIDIA 官方其實已經發布過 runtime_source_add_delete 模塊,見 https://github.com/NVIDIA-AI-IOT/deepstream_reference_apps

我們先把他跑起來,然後我再介紹一下這代碼的思路,以及我調試過程中遇到的一些問題

在 /opt/nvidia/deepstream/deepstream-5.0/source/app/samples 路徑下克隆工程

git clone https://github.com/NVIDIA-AI-IOT/deepstream_reference_apps.git
cd runtime_source_add_delete
make

make 完生成可執行文件,可以使用以下下邊的方式測試

$ ./deepstream-test-rt-src-add-del file:///opt/nvidia/deepstream/deepstream-5.0/samples/streams/sample_1080p_h264.mp4

運行起來後的效果是:每隔 10s 新增一路輸入,通過 tiler 模式顯示,最多增加到 4 路,然後開始每隔 10s 刪除一路,直到刪完後,結束整個進程。

2 實現思路

在 deepstream_test_rt_src_add_del.c 中,首先會創建一條 pipeline,順序如下

uridecodebin -> streammux -> pgie -> tracker -> sgie1 -> sgie2 -> sgie3 -> tiler -> nvvideoconvert -> nvosd -> sink

然後增加一個定時函數, 

g_timeout_add_seconds (10, add_sources, (gpointer) g_source_bin_list);

表示每隔 10s 執行一遍 add_sources 函數,進入該函數

static gboolean
add_sources (gpointer data)
{
  gint source_id = g_num_sources;
  GstElement *source_bin;
  GstStateChangeReturn state_return;

  // 生成隨機 source_id
  do {
    source_id = rand () % MAX_NUM_SOURCES;
  } while (g_source_enabled[source_id]);
  g_source_enabled[source_id] = TRUE;

  // 創建 uridecodebin
  // uridecodebin 是一種能自動識別 url 類別並對應解碼的一種source bin
  g_print ("Calling Start %d \n", source_id);
  source_bin = create_uridecode_bin (source_id, uri);
  if (!source_bin) {
    g_printerr ("Failed to create source bin. Exiting.\n");
    return -1;
  }
  // 更新全局變量,保存 source 列表
  g_source_bin_list[source_id] = source_bin;
  // 將生成的 source_bin 加到 pipeline 中
  gst_bin_add (GST_BIN (pipeline), source_bin);

  // 設置新加的 source_bin 狀態爲 playing,並得到狀態返回值
  state_return =
      gst_element_set_state (g_source_bin_list[source_id], GST_STATE_PLAYING);
  switch (state_return) {
    case GST_STATE_CHANGE_SUCCESS:
      g_print ("STATE CHANGE SUCCESS\n\n");
      source_id++;
      break;
    case GST_STATE_CHANGE_FAILURE:
      g_print ("STATE CHANGE FAILURE\n\n");
      break;
    case GST_STATE_CHANGE_ASYNC:
      g_print ("STATE CHANGE ASYNC\n\n");
      state_return =
          gst_element_get_state (g_source_bin_list[source_id], NULL, NULL,
          GST_CLOCK_TIME_NONE);
      source_id++;
      break;
    case GST_STATE_CHANGE_NO_PREROLL:
      g_print ("STATE CHANGE NO PREROLL\n\n");
      break;
    default:
      break;
  }
  // 計數
  g_num_sources++;

  if (g_num_sources == MAX_NUM_SOURCES) {
    // 當達到設置的最大輸入數量時,開始刪除 source
    g_timeout_add_seconds (10, delete_sources, (gpointer) g_source_bin_list);
    // 返回 FALSE,會終止該定時任務
    return FALSE;
  }

  // 返回 TRUE,會繼續下一次定時任務
  return TRUE;
}

如代碼中說明的,當達到設置的最大輸入數量時,結束新增,開始刪除,也是通過 g_timeout_add_seconds 定時任務,進入 delete_source 函數來完成

delete_source 函數主要是對列表的一些判斷,真正執行刪除的是 stop_release_source 函數


static void
stop_release_source (gint source_id)
{
  GstStateChangeReturn state_return;
  gchar pad_name[16];
  GstPad *sinkpad = NULL;
  // 設置source bin 爲 NULL 狀態
  state_return =
      gst_element_set_state (g_source_bin_list[source_id], GST_STATE_NULL);
  switch (state_return) {
    case GST_STATE_CHANGE_SUCCESS:
      g_print ("STATE CHANGE SUCCESS\n\n");

      // 得到 streammux 的 sink pad 
      g_snprintf (pad_name, 15, "sink_%u", source_id);
      sinkpad = gst_element_get_static_pad (streammux, pad_name);
      // 給 streammux 的 sink pad 發送 flush stop 信號,停止視頻流傳輸
      gst_pad_send_event (sinkpad, gst_event_new_flush_stop (FALSE));
      gst_element_release_request_pad (streammux, sinkpad);
      g_print ("STATE CHANGE SUCCESS %p\n\n", sinkpad);
      // 刪除 bin 操作
      gst_object_unref (sinkpad);
      gst_bin_remove (GST_BIN (pipeline), g_source_bin_list[source_id]);
      source_id--;
      g_num_sources--;
      break;
    case GST_STATE_CHANGE_FAILURE:
      g_print ("STATE CHANGE FAILURE\n\n");
      break;
    case GST_STATE_CHANGE_ASYNC:
      g_print ("STATE CHANGE ASYNC\n\n");
      state_return =
          gst_element_get_state (g_source_bin_list[source_id], NULL, NULL,
          GST_CLOCK_TIME_NONE);
      g_snprintf (pad_name, 15, "sink_%u", source_id);
      sinkpad = gst_element_get_static_pad (streammux, pad_name);
      gst_pad_send_event (sinkpad, gst_event_new_flush_stop (FALSE));
      gst_element_release_request_pad (streammux, sinkpad);
      g_print ("STATE CHANGE ASYNC %p\n\n", sinkpad);
      gst_object_unref (sinkpad);
      gst_bin_remove (GST_BIN (pipeline), g_source_bin_list[source_id]);
      source_id--;
      g_num_sources--;
      break;
    case GST_STATE_CHANGE_NO_PREROLL:
      g_print ("STATE CHANGE NO PREROLL\n\n");
      break;
    default:
      break;
  }


}

原理其實很簡單,關於動態替換 element 的過程,我在 gstreamer 專欄中有過介紹,可以參考《Gstreamer應用開發手冊14:替換管道元件

3 改進

經過上述的步驟,可以初步實現一個對視頻文件源的動態增減,但是我在輸入 rtsp 執行刪除操作的時候會報錯如下

Calling Stop 3 
STATE CHANGE SUCCESS

STATE CHANGE SUCCESS 0x7e640052c0

ERROR from element source: Unhandled error
Error details: gstrtspsrc.c(6161): gst_rtspsrc_send (): /GstPipeline:dstest-pipeline/GstURIDecodeBin:source-bin-03/GstRTSPSrc:source:
Option not supported (551)
Returned, stopping playback
Deleting pipeline

這個問題我曾經在論壇中提問過,可惜沒得到好的解決方案

https://forums.developer.nvidia.com/t/error-happend-when-run-runtime-source-add-delete/115285

在論壇中查了一下類似的問題,大概原因是說 rtsp 輸入不支持暫停等操作,如下

https://forums.developer.nvidia.com/t/delete-source-dynamically-error/146830/7

這裏提供一個“暴力”的方法,就是註釋掉 bus_call 裏邊的 g_main_loop_quit (loop) 函數,也能運行起來,但依然會輸出相關的錯誤信息。

另外一種方法是修改 rtsp source bin 的源碼,因爲涉及的內容比較細且通用,我會在另外一邊博文中介紹,參考《DeepStream5.0系列之修改rtsp source源碼》。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章