GStreamer 基礎教程三: 動態(tài)管道

目標

我們可以在應用程序開始時定義整體管道漂坏,也可以在有足夠信息時“動態(tài)”構(gòu)建管道唤崭。

這篇教程的目標是搞清楚如下問題:

  • 如何在鏈接元件時獲得更精細的控制宴倍?
  • 如何收到感興趣事件的通知身腻,并及時反應诗越?
  • 元件可能處于的哪些不同的狀態(tài)?

介紹

這篇教程中的管道在設置為 “Playing” 狀態(tài)之前并未完全構(gòu)建好嘲恍。
這沒問題足画。如果我們不采取進一步的行動,數(shù)據(jù)將到達管道的盡頭佃牛,管道將會產(chǎn)生一個錯誤消息并停止淹辞。但這里我們將會采取點行動。

在這個例子中俘侠,我們將打開一個包含多路媒體的文件(多路復用muxed), 也就是將音頻和視頻存儲在一個容器文件中象缀。

負責打開這樣的容器的元件稱為分離器(demuxer), 常見的容器格式有 Matroska(MKV), Quick Time(QT, MOV), Ogg 或 Advanced Systems Format(ASF, WMV, WMA).

如果一個容器嵌入了多路媒體流 (例如一路視頻和兩路音頻),分離器 demuxer 會分開它們爷速,并通過不同的輸出端口發(fā)布出去央星。這樣就可以在管道中創(chuàng)建不同的分支,處理不同類型的數(shù)據(jù)惫东。

GStreamer 元件相互通信所通過的端口稱為 Pad(GstPad)莉给,有 Sink Pad(數(shù)據(jù)通過其進入元件) 和 Src Pad(數(shù)據(jù)由其離開元件)。自然地,Src Element 僅包含 Src Pad, Sink Element 僅包含 Sink Pad, 而 Filter element 既包含 src pad 也包含 sink pad.

source element
filter element
sink element

一個分離器 demuxer 包含一個 sink pad (聚合的數(shù)據(jù)通過它到達), 和多個 source pads (對應于在容器中找到的媒體流)

為了完整起見禁谦,這里有一個簡化的管道胁黑,其中包含一個分離器和兩個分支,一個用于音頻州泊,一個用于視頻。 這不是本示例中將構(gòu)建的管道:

處理分離器時的主要復雜性在于漂洋,它們無法產(chǎn)生任何信息遥皂,直到它們收到一些數(shù)據(jù)并且有機會查看容器以了解里面的內(nèi)容。

也就是說刽漂,分離器開始時沒有其他元件可以鏈接的 source pad演训,因此管道必須在分離器處終止。

解決方案是構(gòu)建從源元件到分離器的管道贝咙,并將其設置為運行 (playing 狀態(tài))样悟。 當分離器收到足夠的信息以了解容器中流的數(shù)量和類型時,它將開始創(chuàng)建 source pad庭猩。 現(xiàn)在是我們完成管道構(gòu)建并將其連接到新添加的 demux source pad 的最佳時機窟她。

為簡單起見,在本例中蔼水,我們將僅鏈接到音頻 pad 并忽略視頻流震糖。

示例

大致流程如下

@startuml

start
: 初始化 gst_init();
: 創(chuàng)建元件 gst_element_factory_make();
: 創(chuàng)建管道 gst_pipeline_new();
: 添加元件到管道 gst_bin_add_many();
: 將元件連接起來 gst_element_link 
  (除了 source element);
: 設置元件屬性 g_object_set();
: 設置 source 元件的信號 “pad-added” 的回調(diào);
: 設置管道狀態(tài) gst_element_set_state();
: 等待信號發(fā)生 gst_signal_emit(由 demuxer 觸發(fā))
 等待總線消息 gst_bus_timed_pop_filtered();

fork
  :pad-added signal triggered;
  :pad_added_handler;
fork again
  :gst_bus_timed_pop_filtered
  -> GST_MESSAGE_ERROR;
fork again
  :gst_bus_timed_pop_filtered
  -> GST_MESSAGE_EOS;
fork again
  :gst_bus_timed_pop_filtered
  -> GST_MESSAGE_ERROR;
fork again
  :gst_bus_timed_pop_filtered
  -> GST_MESSAGE_STATE_CHANGED;
end merge
stop

@enduml

源代碼


#include <gst/gst.h>

/* Structure to contain all our information, so we can pass it to callbacks */
typedef struct _CustomData {
  GstElement *pipeline;
  GstElement *source;
  GstElement *convert;
  GstElement *resample;
  GstElement *sink;
} CustomData;

/* Handler for the pad-added signal */
static void pad_added_handler (GstElement *src, GstPad *pad, CustomData *data);

int main(int argc, char *argv[]) {
  CustomData data;
  GstBus *bus;
  GstMessage *msg;
  GstStateChangeReturn ret;
  gboolean terminate = FALSE;

  /* Initialize GStreamer */
  gst_init (&argc, &argv);

  /* Create the elements */
  data.source = gst_element_factory_make ("uridecodebin", "source");
  data.convert = gst_element_factory_make ("audioconvert", "convert");
  data.resample = gst_element_factory_make ("audioresample", "resample");
  data.sink = gst_element_factory_make ("autoaudiosink", "sink");

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

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

  /* Build the pipeline. Note that we are NOT linking the source at this
   * point. We will do it later. */
  gst_bin_add_many (GST_BIN (data.pipeline), data.source, data.convert, data.resample, data.sink, NULL);
  if (!gst_element_link_many (data.convert, data.resample, data.sink, NULL)) {
    g_printerr ("Elements could not be linked.\n");
    gst_object_unref (data.pipeline);
    return -1;
  }

  /* Set the URI to play */
  g_object_set (data.source, "uri", "https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm", NULL);

  /* Connect to the pad-added signal */
  g_signal_connect (data.source, "pad-added", G_CALLBACK (pad_added_handler), &data);

  /* Start playing */
  ret = gst_element_set_state (data.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 (data.pipeline);
    return -1;
  }

  /* Listen to the bus */
  bus = gst_element_get_bus (data.pipeline);
  do {
    msg = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE,
        GST_MESSAGE_STATE_CHANGED | 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);
          terminate = TRUE;
          break;
        case GST_MESSAGE_EOS:
          g_print ("End-Of-Stream reached.\n");
          terminate = TRUE;
          break;
        case GST_MESSAGE_STATE_CHANGED:
          /* We are only interested in state-changed messages from the pipeline */
          if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data.pipeline)) {
            GstState old_state, new_state, pending_state;
            gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
            g_print ("Pipeline state changed from %s to %s:\n",
                gst_element_state_get_name (old_state), gst_element_state_get_name (new_state));
          }
          break;
        default:
          /* We should not reach here */
          g_printerr ("Unexpected message received.\n");
          break;
      }
      gst_message_unref (msg);
    }
  } while (!terminate);

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

/* This function will be called by the pad-added signal */
static void pad_added_handler (GstElement *src, GstPad *new_pad, CustomData *data) {
  GstPad *sink_pad = gst_element_get_static_pad (data->convert, "sink");
  GstPadLinkReturn ret;
  GstCaps *new_pad_caps = NULL;
  GstStructure *new_pad_struct = NULL;
  const gchar *new_pad_type = NULL;

  g_print ("Received new pad '%s' from '%s':\n", GST_PAD_NAME (new_pad), GST_ELEMENT_NAME (src));

  /* If our converter is already linked, we have nothing to do here */
  if (gst_pad_is_linked (sink_pad)) {
    g_print ("We are already linked. Ignoring.\n");
    goto exit;
  }

  /* Check the new pad's type */
  new_pad_caps = gst_pad_get_current_caps (new_pad);
  new_pad_struct = gst_caps_get_structure (new_pad_caps, 0);
  new_pad_type = gst_structure_get_name (new_pad_struct);
  if (!g_str_has_prefix (new_pad_type, "audio/x-raw")) {
    g_print ("It has type '%s' which is not raw audio. Ignoring.\n", new_pad_type);
    goto exit;
  }

  /* Attempt the link */
  ret = gst_pad_link (new_pad, sink_pad);
  if (GST_PAD_LINK_FAILED (ret)) {
    g_print ("Type is '%s' but link failed.\n", new_pad_type);
  } else {
    g_print ("Link succeeded (type '%s').\n", new_pad_type);
  }

exit:
  /* Unreference the new pad's caps, if we got them */
  if (new_pad_caps != NULL)
    gst_caps_unref (new_pad_caps);

  /* Unreference the sink pad */
  gst_object_unref (sink_pad);
}

上述代碼可通過如下命令行編譯

gcc basic-tutorial-3.c -o basic-tutorial-3 `pkg-config --cflags --libs gstreamer-1.0`

關(guān)鍵代碼解析

其中有意思的就是 pad-added 的回調(diào)函數(shù) pad_added_handler,它會將新創(chuàng)建出來的 newpad(urldecodebin 新建出來的) 與 sink_pad( audioconvert 的) 連接起來趴腋。


  /* Connect to the pad-added signal */
  g_signal_connect (data.source, "pad-added", G_CALLBACK (pad_added_handler),
      &data);

/* This function will be called by the pad-added signal */
static void
pad_added_handler (GstElement * src, GstPad * new_pad, CustomData * data)
{
  /* Attempt the link */
  ret = gst_pad_link (new_pad, sink_pad);
  if (GST_PAD_LINK_FAILED (ret)) {
    g_print ("Type is '%s' but link failed.\n", new_pad_type);
  } else {
    g_print ("Link succeeded (type '%s').\n", new_pad_type);
  }
}

原來的 pipeline 是

audioconvert -> audioresample -> autoaudiosink

當 uridecodebin 的 src_pad 按需創(chuàng)建出來后吊说,將 uridecodebin 的 src_pad 與 audioconvert 的 sink_pad 連接起來,變成

urldecodebin -> audioconvert -> audioresample -> autoaudiosink

結(jié)論

這樣我們了解到了如下知識

  • 如何使用 GSignals 收到事件通知

    • 使用 g_signal_connect
  • 如何直接連接 GstPad 而不是其父元素

    • 使用 gst_pad_link
  • GStreamer 元素的各種狀態(tài):

    • 使用 gst_element_set_state 設置狀態(tài)优炬,可以通過觀察 GST_MESSAGE_STATE_CHANGED 類型的消息來觀察管道的狀態(tài)變化

      • GST_STATE_NULL:已停用颁井,元件不占用資源
      • GST_STATE_READY:檢查并分配資源
      • GST_STATE_PAUSED:預滾動,即為每個接收器 sink 獲取一個緩沖區(qū)
      • GST_STATE_PLAYING:活動數(shù)據(jù)流蠢护,運行時間在不斷增加

還有如何組合了這些項目來構(gòu)建動態(tài)管道雅宾,該管道不是在程序啟動時定義的,而是在有關(guān)媒體的信息可用時創(chuàng)建的糊余。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末秀又,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子贬芥,更是在濱河造成了極大的恐慌吐辙,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蘸劈,死亡現(xiàn)場離奇詭異昏苏,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門贤惯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來洼专,“玉大人,你說我怎么就攤上這事孵构∑ㄉ蹋” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵颈墅,是天一觀的道長蜡镶。 經(jīng)常有香客問我,道長恤筛,這世上最難降的妖魔是什么官还? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮毒坛,結(jié)果婚禮上望伦,老公的妹妹穿的比我還像新娘。我一直安慰自己煎殷,他們只是感情好屯伞,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蝌数,像睡著了一般愕掏。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上顶伞,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天饵撑,我揣著相機與錄音,去河邊找鬼唆貌。 笑死滑潘,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的锨咙。 我是一名探鬼主播语卤,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼酪刀!你這毒婦竟也來了粹舵?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤骂倘,失蹤者是張志新(化名)和其女友劉穎眼滤,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體历涝,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡诅需,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年漾唉,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片堰塌。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡赵刑,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出场刑,到底是詐尸還是另有隱情般此,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布牵现,位于F島的核電站恤煞,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏施籍。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一概漱、第九天 我趴在偏房一處隱蔽的房頂上張望丑慎。 院中可真熱鬧,春花似錦瓤摧、人聲如沸竿裂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽腻异。三九已至,卻和暖如春这揣,著一層夾襖步出監(jiān)牢的瞬間悔常,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工给赞, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留机打,地道東北人。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓片迅,卻偏偏與公主長得像残邀,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子柑蛇,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345