點擊查看系列文章目錄
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源碼》。