GStreamer基礎教學

GObject 和 GLib

GStreamer建立在GObject和GLib之上,熟悉GObject和GLib對於學習GStreamer會有幫助,要區分目前的函示是屬於GStreamer還是GLib的方法就是GStreamer的函式是gst_開頭,而GLib的函式是g_開頭

1.簡單範例

範例

下面程式碼是一個最基礎的GStreamer範例basic-tutorial-1.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <gst/gst.h>

#ifdef __APPLE__
#include <TargetConditionals.h>
#endif

int
tutorial_main (int argc, char *argv[])
{
GstElement *pipeline;
GstBus *bus;
GstMessage *msg;

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

/* Build the pipeline */
pipeline =
gst_parse_launch
("playbin uri=https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm",
NULL);

/* Start playing */
gst_element_set_state (pipeline, GST_STATE_PLAYING);

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

/* See next tutorial for proper error message handling/parsing */
if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR) {
g_error ("An error occurred! Re-run with the GST_DEBUG=*:WARN environment "
"variable set for more details.");
}

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

int
main (int argc, char *argv[])
{
#if defined(__APPLE__) && TARGET_OS_MAC && !TARGET_OS_IPHONE
return gst_macos_main (tutorial_main, argc, argv, NULL);
#else
return tutorial_main (argc, argv);
#endif
}

{:file=’basic-tutorial-1.c’}

在Linux下可以用以下指令編譯。

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

解說

上面這個範例最重要的只有五個需要注意的地方,其他的程式碼都是程式結束後清理的例行動作。

  1. 首先所有的Gstreamer都必須呼叫gst_init(),他有三個功能
  • 初始化GStreamer
  • 確認plug-ins都可以使用
  • 執行命令列的參數選項,可以直接將main函式的argcargv直接傳入gst_init()
    1
    2
    3
    4
    /* Initialize GStreamer */
    gst_init (&argc, &argv);

    /* Build the pipeline */
  1. gst_parse_launch
    GStreamer 元件像水管一樣接起來組成像水管的pipeline,影音資料像像水流一樣,source元件是pipeline的起頭,像水龍頭一樣流出影音資料。sink元件是pipeline的結尾,是影音資料最後到達的地方。過程中經過中間的處理原件可以對影音資料進行處理。

通常你會需要用程式定義每個元件和串接方式,但是如果你的pipeline很簡單,你可以直接用文字描述的方式作為參數傳給gst_parse_launch來建立pipeline

  1. playbin
    在這個範例中我們用到playbin來建立pipeline,playbin是一個特殊的元件,他可以同時做為source和sink,而且他可以自己建立成一個pipeline。在這個範例我們給他一個影片串流的URL,如果URL有錯或是指定的影片檔不存在,playbin可以回傳錯誤,在這個範例我們遇到錯誤的時候是直接離開程式。

    1
    2
    3
    gst_parse_launch
    ("playbin uri=https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm",
    NULL);
  2. state
    GStreamer 還有一個重要的觀念state。每一個GStreamer element都有state,很像影音撥放器的播放和暫停按鈕。在這個範例裡面,pipeline是我們唯一的element,因此要把他設為撥放才會開始撥放影片。

    1
    2
    /* Start playing */
    gst_element_set_state (pipeline, GST_STATE_PLAYING);
  3. message bus、gst_element_get_bus、gst_bus_timed_pop_filtered
    在下面這兩行,gst_element_get_bus會取得pipeline的bus,而gst_bus_timed_pop_filtered會把main thread停住直到我們感興趣的訊息,在這裡是GST_MESSAGE_ERRORGST_MESSAGE_EOS,而GST_MESSAGE_EOS代表影片結束了,因此當影片結束的時候整個程式就會停止。

1
2
3
4
5
/* Wait until error or EOS */
bus = gst_element_get_bus (pipeline);
msg =
gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE,
GST_MESSAGE_ERROR | GST_MESSAGE_EOS);

2. GStreamer 觀念

這個教學將會示範用程式建立pipeline。在這裡將會學到

  • 介紹GStreamer element並且學習如何建立
  • 串接element
  • 客製化element行為
  • 利用message bus監看錯誤是件並且從中取出錯誤訊息

用程式寫出前一個教學的撥放器

完整程式碼basic-tutorial-2.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#include <gst/gst.h>

#ifdef __APPLE__
#include <TargetConditionals.h>
#endif

int
tutorial_main (int argc, char *argv[])
{
GstElement *pipeline, *source, *sink;
GstBus *bus;
GstMessage *msg;
GstStateChangeReturn ret;

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

/* Create the elements */
source = gst_element_factory_make ("videotestsrc", "source");
sink = gst_element_factory_make ("autovideosink", "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, "pattern", 0, NULL);

/* 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,
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;
}

int
main (int argc, char *argv[])
{
#if defined(__APPLE__) && TARGET_OS_MAC && !TARGET_OS_IPHONE
return gst_macos_main (tutorial_main, argc, argv, NULL);
#else
return tutorial_main (argc, argv);
#endif
}

{:file=’basic-tutorial-2.c’}

GStreamer element

element是GStreamer最根本的元素,影音資料從source流向sink,過程中經過filter對影音資料進行處理,這三個元素組成為pipeline

pipeline

建立element

1
2
3
/* Create the elements */
source = gst_element_factory_make ("videotestsrc", "source");
sink = gst_element_factory_make ("autovideosink", "sink");

建立element可以用gst_element_factory_make()來建立,第一個參數是要建立的element名稱,第二個參數是我們給element取的名字。幫element命名的好處是如果你沒有儲存pointer,你可以用名稱來找到這個element,而且除錯訊息也會變得比較有意義。

這個教學中建立兩個element,videotestsrcautovideosink,然後沒有建立任何filter。所以pipeline長的像下圖。

basic pipeline

videotestsrc是一個source element,他會產生除錯用的影像。

autovideosink是一個sink element,他會將影像顯示到螢幕上。

建立pipeline

1
2
3
4
5
6
7
/* 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;
}

所有的element都必須在pipeline裡面才能運作,利用gst_pipeline_new(),可以建立新的pipeline。

bin

1
2
3
4
5
6
7
/* 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;
}

Gstreamer的bin是也是一個element,它可以拿來成裝其他 GstElement。GstBin的類別關係圖如下

1
2
3
4
5
GObject
╰──GInitiallyUnowned
╰──GstObject
╰──GstElement
╰──GstBin

pipeline也是bin,用來裝入element。

利用gst_bin_add_many()可以將element放入pipeline,他可以一次加入許多element。他的第一個參數是bin,第二個參數之後都是element,也就是要放入的element

連接element

到目前為止element並還沒有連接起來,利用gst_element_link()將element聯接起來。他的第一個參數是來源,第二個參數是目標,也就是第一個參數的element會把資料傳給第二個參數的element,所以是有順序性的。

注意,只有位於同一個bin的element可以互相聯接。

屬性

GStreamer 的element全部都是一種特殊的GObject,因此他們都有property,有些可以讀取有些可以寫入

property必須透過GLib的方法g_object_get()g_object_set()來讀取和寫入,因此注意到這輛個函式是g_開頭。

g_object_set()支援一次修改多個properties

回到我們的程式,我們改變videotestsrc的”pattern” properties,你可以將它改成其他類型來看看輸出的畫面有什麼改變。

1
2
/* Modify the source's properties */
g_object_set (source, "pattern", 0, NULL);

錯誤檢查

到目前為止pipeline都已經建立完成,接下來我們要增加一些程式來應付錯誤發生的情況。

1
2
3
4
5
6
7
/* 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;
}

這是我們一樣呼叫gst_element_set_state來開始撥放,但是我們另外檢查gst_element_set_state回傳的錯誤。

另外,我們還多了一些程式來處理gst_bus_timed_pop_filtered()拿到的錯誤訊息,錯誤訊息是一個GstMessage,如果遇到EOS以外的錯誤訊息,就把他印出來。

GstMessage十分方便,幾乎可以承載任何形式的訊息,而GSstreamer提供了許多解析訊息的函式。

首先我們先利用GST_MESSAGE_TYPE()來取得錯誤的類型,如果不是EOS錯誤,就再用gst_message_parse_error()把錯誤轉型成GLib的GError以及錯誤訊息的文字。注意使用完之後要記得釋放。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/* 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);
}

GStreamer bus

GStreamer bus是用來將元素所產生的GstMessage按順序傳送給應用程式的thread。這裡要強調的是,消息是從處理影像的thread傳遞給應用程式的thread。

訊息可以用gst_bus_timed_pop_filtered()這個函式來同步取得。或是用signals 來非同步取得,應用設必須隨時注意bus上有沒有出現錯誤。

動態pipeline

在這個範例所建立的pipeline並不是完整的,而這裡將示範如何在程式運行時才將完整的pipeline建立好。

程式將打開一個multiplexed (或是 muxed)的檔案,也就是聲音和影像都儲存在一個檔案(container file)。用來打開這種檔案的element稱為demuxers。常見的container file有mp4、MKV、WMV等等。

如果container file裡面包含多個串流(例如一個影片串流,兩個聲音串流),demuxer就會把他們分別分配到不同的出口,而不同的pipeline就可以各自處理這些串流。

在GStreamer裡element用來傳遞資料的接口稱為pad(GstPad),sink pad就是資料流進element的口,以及source pad就是element將資料流出的口。記憶的方式就是source elements只會擁有source pad,而sink element只會擁有sink padfilter element則同時擁有source padsink pad

src

sink

filter

在這個範例裡面,demuxer包含一個sink pad 和多個 source pad,而demuxer複雜的地方就在於在讀取檔案之前沒辦法確定demuxer到底有多少個source pad,因為demuxer要讀取到檔案之後才能決定有多少個source pad

如此一來,demuxers一開始是沒有任何source pad的,因此也沒辦法在編譯時就將demuxers跟其他element連接。當程式開始運作後並且讀取到檔案時,這時候才是連接demuxer的時機。

為了簡單起見這個範例只連接audio pad而忽略video pad。

範例

下面範例basic-tutorial-3.c將示範動態pipeline

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
#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://www.freedesktop.org/software/gstreamer-sdk/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);
}

{:file=’basic-tutorial-3.c’}

解說

首先我們先將資料組成一個struct以便後面使用

1
2
3
4
5
6
7
8
/* 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;

接下來這行是forward reference晚一點會實做這個函式。

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

接下來建立element,在這裡uridecodebin會自動初始化需要的element(sources, demuxers and decoders)以便將URI轉換成影音串流。跟playbin比起來他只完成了一半,因為它包含了demuxers,所以只有到執行階段的時候source pad才會被初始化。

audioconvert用來轉換audio的格式。audioresample用來調整audio的sample rate。

autoaudiosinkautovideosink類似,他將會把聲音串流輸出到音效卡

1
2
3
4
5
/* 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");

串接element

接下來我們將converter, resample and sink這些element連接起來。注意這時候還不可以連接source,因為這時候souce還沒有source pad。

1
2
3
4
5
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;
}

然後設定source要讀取的URI

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

Signals

GSignals是GStreamer的一個重點,他讓我們可以在我們感興趣的事情發生的時候通知我們。GStreamer用名稱來區分signal,而每一個GObject也都有自己的signal。
``
在這個範例我們將會關心source(也就是uridecodebin element)發出來的pad-added這個訊號。我們必須用g_signal_connect()來連接訊號並且給他callback function(pad_added_handler)和我們的data pointer,讓callback functiony在號發生的時候執行。

GStreamer不會對data pointer做任何事情,他只是單純的把data pointer傳進我們的callback function,以便我們可以傳送參數給callback function。

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

在這個範例我們傳入字定義的data struct CustomData

我們的callback function

當source element有足夠的資訊可以產生source pad的時候,就會觸發”pad-added”訊號,而這時候我們的callback就會被呼叫。

1
static void pad_added_handler (GstElement *src, GstPad *new_pad, CustomData *data)

在我們的callback中,第一個參數是觸發訊號的GstElement,也就是uridecodebin

第二個參數是source剛剛產生的pad,也就是我們想要連接的pad。

第三個參數是一個pointer,我們將用他來傳入我麼的參數給callback。

在callback裡面,我們將CustomData裡的converter element,利用gst_element_get_static_pad ()將他的sink pad取出來。他也就是要跟source新產生的pad對接的pad。

1
GstPad *sink_pad = gst_element_get_static_pad (data->convert, "sink");

在上一個範例我們讓GStreamer自己決定要連接的pad,在這裡我們將手動連接pad。首先加入下面這段程式碼以免pad重複被連接

1
2
3
4
5
/* 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;
}

接下來我們檢查新產生的pad他產生的資料是什麼,因為我們只需要連接audio而忽略video。而且我們不能把video的pad和audio的pad對接。

gst_pad_get_current_caps()可以查到pad會輸出什麼資料,pad的”能力”(capabilities)被紀錄在GstCaps裡面。而pad所有可用的能力可以用gst_pad_query_caps()查詢

GstCaps 裡面可能包含許多的GstStructure,每一個都代表不同的”能力”。

由於目前我們知道新產生的pad只會有一個capabilities,所以我們直接用gst_caps_get_structure()取得他的第一個GstStructure。

最後再利用gst_structure_get_name()來取得這個GstStructure的名稱,這裡將會有關於pad傳出來的資料格式。

假如我們拿到的pad輸出的資料格式不是audio/x-raw,那就不是我們要的pad。如果是的話我們就連接他。

gst_element_link()可以直接連接兩個pad,參數的順序是source在來sink,而且這兩個pad所在的element必須要在同一個bin裡面才可以連接。如此一來我們就完成了。

1
2
3
4
5
6
7
/* 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);
}

GStreamer States

GStreamer共有四種狀態

  • NULL: 無狀態或初始狀態
  • READY: element已經準備好進入PAUSED狀態
  • PAUSED: element已經暫停,並且準備好處理資料。sink element這時候只接受一個buffer,之後就阻塞
  • PLAYING: element正在撥放,clock正在運作,資料正在傳輸。

注意,你只能從移動到鄰近的狀態。也就是不可以直接從NUL跳到PLAYING。當你設定pipeline 為PLAYING的時候,GSstreamer自動幫你處理這些事情。

下面這段程式監聽message bus,每當狀態有改變的時候就印出來讓你知道。每一個element都會丟出目前狀態的訊息,所以我們過濾出pipeline的狀態訊息。

1
2
3
4
5
6
7
8
9
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;

時間管理

在這節將會學習到GStreamer時間相關的功能包含

  • 向pipeline詢問資訊例如目前串流的位置和長度。
  • 尋找(跳躍)到串流上不同的位置(時間)

GstQuery

GstQuery是一個用來詢問element或是pad的資訊的機制。在這個範例我們將詢問pipeline是否可以可以搜尋時間(例如如果是串流就沒辦法搜尋時間),如果可以我們就可以在影片時間軸上跳躍。

這這個範例我們每隔一段時間就向pipeline詢問目前的影片時間位置,如此一來我們就可以將時間顯示在我們的螢幕上。

範例

我們將以範例basic-tutorial-4.c為例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#include <gst/gst.h>

/* Structure to contain all our information, so we can pass it around */
typedef struct _CustomData {
GstElement *playbin; /* Our one and only element */
gboolean playing; /* Are we in the PLAYING state? */
gboolean terminate; /* Should we terminate execution? */
gboolean seek_enabled; /* Is seeking enabled for this media? */
gboolean seek_done; /* Have we performed the seek already? */
gint64 duration; /* How long does this media last, in nanoseconds */
} CustomData;

/* Forward definition of the message processing function */
static void handle_message (CustomData *data, GstMessage *msg);

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

data.playing = FALSE;
data.terminate = FALSE;
data.seek_enabled = FALSE;
data.seek_done = FALSE;
data.duration = GST_CLOCK_TIME_NONE;

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

/* Create the elements */
data.playbin = gst_element_factory_make ("playbin", "playbin");

if (!data.playbin) {
g_printerr ("Not all elements could be created.\n");
return -1;
}

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

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

/* Listen to the bus */
bus = gst_element_get_bus (data.playbin);
do {
msg = gst_bus_timed_pop_filtered (bus, 100 * GST_MSECOND,
GST_MESSAGE_STATE_CHANGED | GST_MESSAGE_ERROR | GST_MESSAGE_EOS | GST_MESSAGE_DURATION);

/* Parse message */
if (msg != NULL) {
handle_message (&data, msg);
} else {
/* We got no message, this means the timeout expired */
if (data.playing) {
gint64 current = -1;

/* Query the current position of the stream */
if (!gst_element_query_position (data.playbin, GST_FORMAT_TIME, &current)) {
g_printerr ("Could not query current position.\n");
}

/* If we didn't know it yet, query the stream duration */
if (!GST_CLOCK_TIME_IS_VALID (data.duration)) {
if (!gst_element_query_duration (data.playbin, GST_FORMAT_TIME, &data.duration)) {
g_printerr ("Could not query current duration.\n");
}
}

/* Print current position and total duration */
g_print ("Position %" GST_TIME_FORMAT " / %" GST_TIME_FORMAT "\r",
GST_TIME_ARGS (current), GST_TIME_ARGS (data.duration));

/* If seeking is enabled, we have not done it yet, and the time is right, seek */
if (data.seek_enabled && !data.seek_done && current > 10 * GST_SECOND) {
g_print ("\nReached 10s, performing seek...\n");
gst_element_seek_simple (data.playbin, GST_FORMAT_TIME,
GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, 30 * GST_SECOND);
data.seek_done = TRUE;
}
}
}
} while (!data.terminate);

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

static void handle_message (CustomData *data, GstMessage *msg) {
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);
data->terminate = TRUE;
break;
case GST_MESSAGE_EOS:
g_print ("End-Of-Stream reached.\n");
data->terminate = TRUE;
break;
case GST_MESSAGE_DURATION:
/* The duration has changed, mark the current one as invalid */
data->duration = GST_CLOCK_TIME_NONE;
break;
case GST_MESSAGE_STATE_CHANGED: {
GstState old_state, new_state, pending_state;
gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->playbin)) {
g_print ("Pipeline state changed from %s to %s:\n",
gst_element_state_get_name (old_state), gst_element_state_get_name (new_state));

/* Remember whether we are in the PLAYING state or not */
data->playing = (new_state == GST_STATE_PLAYING);

if (data->playing) {
/* We just moved to PLAYING. Check if seeking is possible */
GstQuery *query;
gint64 start, end;
query = gst_query_new_seeking (GST_FORMAT_TIME);
if (gst_element_query (data->playbin, query)) {
gst_query_parse_seeking (query, NULL, &data->seek_enabled, &start, &end);
if (data->seek_enabled) {
g_print ("Seeking is ENABLED from %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT "\n",
GST_TIME_ARGS (start), GST_TIME_ARGS (end));
} else {
g_print ("Seeking is DISABLED for this stream.\n");
}
}
else {
g_printerr ("Seeking query failed.");
}
gst_query_unref (query);
}
}
} break;
default:
/* We should not reach here */
g_printerr ("Unexpected message received.\n");
break;
}
gst_message_unref (msg);
}

定義資料struct

1
2
3
4
5
6
7
8
9
10
11
12
/* Structure to contain all our information, so we can pass it around */
typedef struct _CustomData {
GstElement *playbin; /* Our one and only element */
gboolean playing; /* Are we in the PLAYING state? */
gboolean terminate; /* Should we terminate execution? */
gboolean seek_enabled; /* Is seeking enabled for this media? */
gboolean seek_done; /* Have we performed the seek already? */
gint64 duration; /* How long does this media last, in nanoseconds */
} CustomData;

/* Forward definition of the message processing function */
static void handle_message (CustomData *data, GstMessage *msg);

首先定義這個範例會用的到資料struct,同時我們也定義一個handle_message來處理我們的資料。

設定監聽消息的timeout

這個範例我們將會設定gst_bus_timed_pop_filtered()的timeout,如果0.1秒鐘內沒有收到任何訊息,就會發出gst_bus_timed_pop_filtered()會回傳一個NULL。timeoout的設定必須用到GstClockTime,因此我們直接用GST_SECOND 或 GST_MSECOND來產生GstClockTime時間。

1
2
msg = gst_bus_timed_pop_filtered (bus, 100 * GST_MSECOND,
GST_MESSAGE_STATE_CHANGED | GST_MESSAGE_ERROR | GST_MESSAGE_EOS | GST_MESSAGE_DURATION);

更新使用者介面

首先檢查pipeline是PLAYING的才對pipeline做查詢以免出錯。

1
2
/* We got no message, this means the timeout expired */
if (data.playing) {

接著用GstElement提供的方法取得時間。

1
2
3
4
/* Query the current position of the stream */
if (!gst_element_query_position (data.pipeline, GST_FORMAT_TIME, &current)) {
g_printerr ("Could not query current position.\n");
}

如果無法取得就改成檢查是否可以詢問stream的長度

1
2
3
4
5
6
/* If we didn't know it yet, query the stream duration */
if (!GST_CLOCK_TIME_IS_VALID (data.duration)) {
if (!gst_element_query_duration (data.pipeline, GST_FORMAT_TIME, &data.duration)) {
g_printerr ("Could not query current duration.\n");
}
}

接下來就可以詢問影片長度

1
2
3
/* Print current position and total duration */
g_print ("Position %" GST_TIME_FORMAT " / %" GST_TIME_FORMAT "\r",
GST_TIME_ARGS (current), GST_TIME_ARGS (data.duration));

下一段是在影片時間軸跳躍的程式,利用gst_element_seek_simple()來達成。

1
2
3
4
5
6
7
/* If seeking is enabled, we have not done it yet, and the time is right, seek */
if (data.seek_enabled && !data.seek_done && current > 10 * GST_SECOND) {
g_print ("\nReached 10s, performing seek...\n");
gst_element_seek_simple (data.pipeline, GST_FORMAT_TIME,
GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, 30 * GST_SECOND);
data.seek_done = TRUE;
}
  1. GST_FORMAT_TIME: 目標時間的格式
  2. GstSeekFlags: 指令跳躍的行為
  • GST_SEEK_FLAG_FLUSH: 直接拋棄掉目標時間之前的所有畫面。
  • GST_SEEK_FLAG_KEY_UNIT: 移動到目標時間附近的key frame
  • GST_SEEK_FLAG_ACCURATE: 精準的移動到目標時間上。
  1. 目標時間: 是指定要跳躍到的時間位置

Message Pump

首先如果影片長度改變我們就先讓pipeline不能被詢問影片時間。

1
2
3
4
case GST_MESSAGE_DURATION:
/* The duration has changed, mark the current one as invalid */
data->duration = GST_CLOCK_TIME_NONE;
break;

接下來這段程式如果pipeline狀態改變,確認pipeline為PAUSED或是PLAYING才可以在時間軸跳躍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
case GST_MESSAGE_STATE_CHANGED: {
GstState old_state, new_state, pending_state;
gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->playbin)) {
g_print ("Pipeline state changed from %s to %s:\n",
gst_element_state_get_name (old_state), gst_element_state_get_name (new_state));

/* Remember whether we are in the PLAYING state or not */
data->playing = (new_state == GST_STATE_PLAYING);

if (data->playing) {
/* We just moved to PLAYING. Check if seeking is possible */
GstQuery *query;
gint64 start, end;
query = gst_query_new_seeking (GST_FORMAT_TIME);
if (gst_element_query (data->playbin, query)) {
gst_query_parse_seeking (query, NULL, &data->seek_enabled, &start, &end);
if (data->seek_enabled) {
g_print ("Seeking is ENABLED from %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT "\n",
GST_TIME_ARGS (start), GST_TIME_ARGS (end));
} else {
g_print ("Seeking is DISABLED for this stream.\n");
}
}
else {
g_printerr ("Seeking query failed.");
}
gst_query_unref (query);
}
}
}

gst_query_new_seeking()建立一個新的query物件,這個query物件利用gst_element_query()函式被發送到pipeline。如果咬讀去回傳結果可以用gst_query_parse_seeking()

媒體格式和pad Capabilities

Pads

Capabilities 顯示在這個Pad上流動的資料的模樣。例如他可能是”解析度300x200的RGB影片,FPS 30”

Pad可以有多種Capabilities,例如一個video sink可以同時支援RGB和YUV格式。

Capabilities也可以是一個範圍,例如audio sink可以支援samples rates 1~48000。

如果要將兩個element連接起來,他們必須要擁有共同的Capabilities。

如果連接的時候Capabilities沒辦法對應,就會出現negotiation error

Pad templates

Pad 是從Pad templates產生的,他可以產生各種Capabilities 的Pad。

Capabilities範例

下面是一個sink pad。他支援整數型態的raw audio,包含unsigned 8-bit或是 signed, 16-bit little endian。[]裡面代表範圍例如channels可能是一個或兩個。

1
2
3
4
5
6
7
8
9
10
11
SINK template: 'sink'
Availability: Always
Capabilities:
audio/x-raw
format: S16LE
rate: [ 1, 2147483647 ]
channels: [ 1, 2 ]
audio/x-raw
format: U8
rate: [ 1, 2147483647 ]
channels: [ 1, 2 ]

注意有些Capabilities跟平台是有相關性的,要直到READY state的時候才能確定能不能用。

範例

basic-tutorial-6.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
#include <gst/gst.h>

/* Functions below print the Capabilities in a human-friendly format */
static gboolean print_field (GQuark field, const GValue * value, gpointer pfx) {
gchar *str = gst_value_serialize (value);

g_print ("%s %15s: %s\n", (gchar *) pfx, g_quark_to_string (field), str);
g_free (str);
return TRUE;
}

static void print_caps (const GstCaps * caps, const gchar * pfx) {
guint i;

g_return_if_fail (caps != NULL);

if (gst_caps_is_any (caps)) {
g_print ("%sANY\n", pfx);
return;
}
if (gst_caps_is_empty (caps)) {
g_print ("%sEMPTY\n", pfx);
return;
}

for (i = 0; i < gst_caps_get_size (caps); i++) {
GstStructure *structure = gst_caps_get_structure (caps, i);

g_print ("%s%s\n", pfx, gst_structure_get_name (structure));
gst_structure_foreach (structure, print_field, (gpointer) pfx);
}
}

/* Prints information about a Pad Template, including its Capabilities */
static void print_pad_templates_information (GstElementFactory * factory) {
const GList *pads;
GstStaticPadTemplate *padtemplate;

g_print ("Pad Templates for %s:\n", gst_element_factory_get_longname (factory));
if (!gst_element_factory_get_num_pad_templates (factory)) {
g_print (" none\n");
return;
}

pads = gst_element_factory_get_static_pad_templates (factory);
while (pads) {
padtemplate = pads->data;
pads = g_list_next (pads);

if (padtemplate->direction == GST_PAD_SRC)
g_print (" SRC template: '%s'\n", padtemplate->name_template);
else if (padtemplate->direction == GST_PAD_SINK)
g_print (" SINK template: '%s'\n", padtemplate->name_template);
else
g_print (" UNKNOWN!!! template: '%s'\n", padtemplate->name_template);

if (padtemplate->presence == GST_PAD_ALWAYS)
g_print (" Availability: Always\n");
else if (padtemplate->presence == GST_PAD_SOMETIMES)
g_print (" Availability: Sometimes\n");
else if (padtemplate->presence == GST_PAD_REQUEST) {
g_print (" Availability: On request\n");
} else
g_print (" Availability: UNKNOWN!!!\n");

if (padtemplate->static_caps.string) {
GstCaps *caps;
g_print (" Capabilities:\n");
caps = gst_static_caps_get (&padtemplate->static_caps);
print_caps (caps, " ");
gst_caps_unref (caps);

}

g_print ("\n");
}
}

/* Shows the CURRENT capabilities of the requested pad in the given element */
static void print_pad_capabilities (GstElement *element, gchar *pad_name) {
GstPad *pad = NULL;
GstCaps *caps = NULL;

/* Retrieve pad */
pad = gst_element_get_static_pad (element, pad_name);
if (!pad) {
g_printerr ("Could not retrieve pad '%s'\n", pad_name);
return;
}

/* Retrieve negotiated caps (or acceptable caps if negotiation is not finished yet) */
caps = gst_pad_get_current_caps (pad);
if (!caps)
caps = gst_pad_query_caps (pad, NULL);

/* Print and free */
g_print ("Caps for the %s pad:\n", pad_name);
print_caps (caps, " ");
gst_caps_unref (caps);
gst_object_unref (pad);
}

int main(int argc, char *argv[]) {
GstElement *pipeline, *source, *sink;
GstElementFactory *source_factory, *sink_factory;
GstBus *bus;
GstMessage *msg;
GstStateChangeReturn ret;
gboolean terminate = FALSE;

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

/* Create the element factories */
source_factory = gst_element_factory_find ("audiotestsrc");
sink_factory = gst_element_factory_find ("autoaudiosink");
if (!source_factory || !sink_factory) {
g_printerr ("Not all element factories could be created.\n");
return -1;
}

/* Print information about the pad templates of these factories */
print_pad_templates_information (source_factory);
print_pad_templates_information (sink_factory);

/* Ask the factories to instantiate actual elements */
source = gst_element_factory_create (source_factory, "source");
sink = gst_element_factory_create (sink_factory, "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;
}

/* Print initial negotiated caps (in NULL state) */
g_print ("In NULL state:\n");
print_pad_capabilities (sink, "sink");

/* 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 (check the bus for error messages).\n");
}

/* Wait until error, EOS or State Change */
bus = gst_element_get_bus (pipeline);
do {
msg = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE, GST_MESSAGE_ERROR | GST_MESSAGE_EOS |
GST_MESSAGE_STATE_CHANGED);

/* 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 (pipeline)) {
GstState old_state, new_state, pending_state;
gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
g_print ("\nPipeline state changed from %s to %s:\n",
gst_element_state_get_name (old_state), gst_element_state_get_name (new_state));
/* Print the current capabilities of the sink element */
print_pad_capabilities (sink, "sink");
}
break;
default:
/* We should not reach here because we only asked for ERRORs, EOS and STATE_CHANGED */
g_printerr ("Unexpected message received.\n");
break;
}
gst_message_unref (msg);
}
} while (!terminate);

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

印出capabilities

print_fieldprint_capsprint_pad_templates可以印出capabilities。

gst_element_get_static_pad()可以用名稱取得Pad,之所以static是因為這個pad永遠都會出現在這個element裡面。

gst_pad_get_current_caps()可以取得Pad目前的capabilities,這個capabilities是固定的也可能之後會改變,甚只有可能目前沒有capabilities,要看negotiation proces的狀態來決定。

我們可以用gst_pad_query_caps()來取得在NULL state時的Capabilities

GstElementFactory

GstElementFactory用來初始化指定的element。

gst_element_factory_make() = gst_element_factory_find()+ gst_element_factory_create()

gst_pad_get_current_caps() 和 gst_pad_query_caps() 的差別

  • gst_pad_get_current_caps() : 目前可用的Capabilities
  • gst_pad_query_caps() : 包含所有可能的Capabilities

多執行續和Pad Availability

通常GStreamer會自己處理多執行續,但有時候會需要手動處理他。

Multithreading

GStreamer 是一個Multithreading的框架。他會自己產生和消滅thread,甚至plugin可以在自己的process裡面產生新的thread,例如video decoder會自己產生四個thread。

Multithreading範例

下面是一個多執行續的pipeline,通常多個sink的pipeline是Multithreading
Alt text

Request pads

在前面的範例我們已經知道uridecodebin在執行時才會確定產生多少個pad,這種pad稱為Sometimes Pads,而固定不變的pad稱為Always Pads

第三中Pad稱為Request Pad,是有需要的時候才建立。在tee裡面就有Request Pad。你必須主動建立才會產生Request PadRequest Pad沒辦法自動連接,必須手動連接。

另外如果在PLAYINGPAUSED的時候建立或是釋放Request Pad要特別小心,因為有時候會造成(Pad blocking)。通常在NULLREADY狀態的時候建立或釋放比較安全。

範例

basic-tutorial-7.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#include <gst/gst.h>

int main(int argc, char *argv[]) {
GstElement *pipeline, *audio_source, *tee, *audio_queue, *audio_convert, *audio_resample, *audio_sink;
GstElement *video_queue, *visual, *video_convert, *video_sink;
GstBus *bus;
GstMessage *msg;
GstPad *tee_audio_pad, *tee_video_pad;
GstPad *queue_audio_pad, *queue_video_pad;

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

/* Create the elements */
audio_source = gst_element_factory_make ("audiotestsrc", "audio_source");
tee = gst_element_factory_make ("tee", "tee");
audio_queue = gst_element_factory_make ("queue", "audio_queue");
audio_convert = gst_element_factory_make ("audioconvert", "audio_convert");
audio_resample = gst_element_factory_make ("audioresample", "audio_resample");
audio_sink = gst_element_factory_make ("autoaudiosink", "audio_sink");
video_queue = gst_element_factory_make ("queue", "video_queue");
visual = gst_element_factory_make ("wavescope", "visual");
video_convert = gst_element_factory_make ("videoconvert", "csp");
video_sink = gst_element_factory_make ("autovideosink", "video_sink");

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

if (!pipeline || !audio_source || !tee || !audio_queue || !audio_convert || !audio_resample || !audio_sink ||
!video_queue || !visual || !video_convert || !video_sink) {
g_printerr ("Not all elements could be created.\n");
return -1;
}

/* Configure elements */
g_object_set (audio_source, "freq", 215.0f, NULL);
g_object_set (visual, "shader", 0, "style", 1, NULL);

/* Link all elements that can be automatically linked because they have "Always" pads */
gst_bin_add_many (GST_BIN (pipeline), audio_source, tee, audio_queue, audio_convert, audio_resample, audio_sink,
video_queue, visual, video_convert, video_sink, NULL);
if (gst_element_link_many (audio_source, tee, NULL) != TRUE ||
gst_element_link_many (audio_queue, audio_convert, audio_resample, audio_sink, NULL) != TRUE ||
gst_element_link_many (video_queue, visual, video_convert, video_sink, NULL) != TRUE) {
g_printerr ("Elements could not be linked.\n");
gst_object_unref (pipeline);
return -1;
}

/* Manually link the Tee, which has "Request" pads */
tee_audio_pad = gst_element_get_request_pad (tee, "src_%u");
g_print ("Obtained request pad %s for audio branch.\n", gst_pad_get_name (tee_audio_pad));
queue_audio_pad = gst_element_get_static_pad (audio_queue, "sink");
tee_video_pad = gst_element_get_request_pad (tee, "src_%u");
g_print ("Obtained request pad %s for video branch.\n", gst_pad_get_name (tee_video_pad));
queue_video_pad = gst_element_get_static_pad (video_queue, "sink");
if (gst_pad_link (tee_audio_pad, queue_audio_pad) != GST_PAD_LINK_OK ||
gst_pad_link (tee_video_pad, queue_video_pad) != GST_PAD_LINK_OK) {
g_printerr ("Tee could not be linked.\n");
gst_object_unref (pipeline);
return -1;
}
gst_object_unref (queue_audio_pad);
gst_object_unref (queue_video_pad);

/* Start playing the pipeline */
gst_element_set_state (pipeline, GST_STATE_PLAYING);

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

/* Release the request pads from the Tee, and unref them */
gst_element_release_request_pad (tee, tee_audio_pad);
gst_element_release_request_pad (tee, tee_video_pad);
gst_object_unref (tee_audio_pad);
gst_object_unref (tee_video_pad);

/* Free resources */
if (msg != NULL)
gst_message_unref (msg);
gst_object_unref (bus);
gst_element_set_state (pipeline, GST_STATE_NULL);

gst_object_unref (pipeline);
return 0;
}

初始化element

1
2
3
4
5
6
7
8
9
10
11
/* Create the elements */
audio_source = gst_element_factory_make ("audiotestsrc", "audio_source");
tee = gst_element_factory_make ("tee", "tee");
audio_queue = gst_element_factory_make ("queue", "audio_queue");
audio_convert = gst_element_factory_make ("audioconvert", "audio_convert");
audio_resample = gst_element_factory_make ("audioresample", "audio_resample");
audio_sink = gst_element_factory_make ("autoaudiosink", "audio_sink");
video_queue = gst_element_factory_make ("queue", "video_queue");
visual = gst_element_factory_make ("wavescope", "visual");
video_convert = gst_element_factory_make ("videoconvert", "video_convert");
video_sink = gst_element_factory_make ("autovideosink", "video_sink");

調整element

為了範例需求,微調屬性

1
2
3
/* Configure elements */
g_object_set (audio_source, "freq", 215.0f, NULL);
g_object_set (visual, "shader", 0, "style", 1, NULL);

將element放進pipeline

將所有element放入pipeline並且連接所有Always Pads

1
2
3
4
5
6
7
8
9
10
/* Link all elements that can be automatically linked because they have "Always" pads */
gst_bin_add_many (GST_BIN (pipeline), audio_source, tee, audio_queue, audio_convert, audio_sink,
video_queue, visual, video_convert, video_sink, NULL);
if (gst_element_link_many (audio_source, tee, NULL) != TRUE ||
gst_element_link_many (audio_queue, audio_convert, audio_sink, NULL) != TRUE ||
gst_element_link_many (video_queue, visual, video_convert, video_sink, NULL) != TRUE) {
g_printerr ("Elements could not be linked.\n");
gst_object_unref (pipeline);
return -1;
}

注意:
gst_element_link_many()其實也可以自動連接Request Pads。因為它會自動請求新的pad。但是問題是你還是必須要手動釋放Request Pads。所以最好的做法是手動連接Request Pads

連接Request Pads

要連接Request Pads必須要先跟element請求,而因為element有時候可以產生不同的request pads,所以要提供所需的Pad Template名稱。

在tee的文件中可以看到他有兩個Pad Template,一個是”sink”,另一個是”src_%u”(也就是Request pads),我們可以用gst_element_request_pad_simple()函式來請求兩個Pads(一個給audio一個給video)。

1
2
3
4
5
6
7
8
9
10
Pad Templates:
SRC template: 'src_%u'
Availability: On request
Capabilities:
ANY

SINK template: 'sink'
Availability: Always
Capabilities:
ANY

接著我們需要取得下游queue element的Always pad,因此用gst_element_get_static_pad()

我們用gst_pad_link()連接Pads。gst_pad_link()內部其實就是gst_element_link()gst_element_link_many()

我們用來儲存下游queue always pad的變數queue_audio_padqueue_video_pad要記得釋放,以免佔用reference count。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* Manually link the Tee, which has "Request" pads */
tee_audio_pad = gst_element_get_request_pad (tee, "src_%u");
g_print ("Obtained request pad %s for audio branch.\n", gst_pad_get_name (tee_audio_pad));
queue_audio_pad = gst_element_get_static_pad (audio_queue, "sink");
tee_video_pad = gst_element_get_request_pad (tee, "src_%u");
g_print ("Obtained request pad %s for video branch.\n", gst_pad_get_name (tee_video_pad));
queue_video_pad = gst_element_get_static_pad (video_queue, "sink");
if (gst_pad_link (tee_audio_pad, queue_audio_pad) != GST_PAD_LINK_OK ||
gst_pad_link (tee_video_pad, queue_video_pad) != GST_PAD_LINK_OK) {
g_printerr ("Tee could not be linked.\n");
gst_object_unref (pipeline);
return -1;
}
gst_object_unref (queue_audio_pad);
gst_object_unref (queue_video_pad);

最後在程式結束後,要記得釋放request pad

1
2
3
4
5
/* Release the request pads from the Tee, and unref them */
gst_element_release_request_pad (tee, tee_audio_pad);
gst_element_release_request_pad (tee, tee_video_pad);
gst_object_unref (tee_audio_pad);
gst_object_unref (tee_video_pad);

gst_element_release_request_pad()從tee釋放requests pads,gst_object_unref釋放tee_audio_pad變數

hort-cutting the pipeline Goal

pipeline的資料並不是封閉的,我們可以從外界注入資料給pipeline,也可以從pipeline內取得資料

appsrc、appsink

把資料注入pipeline的element為appsrc,相反的從pipeline取得資料的element是appsink。這裡sink和source的概念是從GStreamer應用程式的角度來看,你可以想像appsrc也是一個source,只不過他的資料來源是來自於應用程式,相反的appsink就像普通的sink只是他最後流向應用程式。

appsrc有多種模式,在pull模式每當需要的時候他將會向應用程式索取資料。在push模式則是應用程式主動推送資料進去。在push模式中應用程式還可以阻塞push function當已經推入足夠多的資料到pipeline裡面的時候,或者他可以監聽enough-dataneed-data訊號。

Buffers

數據以Chunks方式進入pipeline的方式稱為Buffers,Buffers代表一單位的資料,但是每一個Buffers大小不一定一樣大。注意,我們不應該假設一個Buffers進入element就同時會有一個離開element。element可以隨意地讓Buffers停留在element內部。

Source pad產生Buffers,而sink pad接收Buffers,Gstreamer將這些Buffers一流過每一個element。

GstBuffer 和 GstMemory

GstBuffers 可能包含一個或一個以上的memory buffer,而真正的記憶體buffer被抽像化為GstMemory,因此一個GstBuffer可以包含一個或一個以上的GstMemory

每一個buffer都有時間戳和長度,以及他需要被decode的長度。

範例

下面範例延續Multithreading and Pad Availability的範例並擴展。

首先audiotestsrc被置換成appsrc來產生audio資料。

第二個是增加一個新的tee分支,這隻分支會接著appsinkappsink會將資訊回傳給應用程式。

範例basic-tutorial-8.c的程式如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
#include <gst/gst.h>
#include <gst/audio/audio.h>
#include <string.h>

#define CHUNK_SIZE 1024 /* Amount of bytes we are sending in each buffer */
#define SAMPLE_RATE 44100 /* Samples per second we are sending */

/* Structure to contain all our information, so we can pass it to callbacks */
typedef struct _CustomData {
GstElement *pipeline, *app_source, *tee, *audio_queue, *audio_convert1, *audio_resample, *audio_sink;
GstElement *video_queue, *audio_convert2, *visual, *video_convert, *video_sink;
GstElement *app_queue, *app_sink;

guint64 num_samples; /* Number of samples generated so far (for timestamp generation) */
gfloat a, b, c, d; /* For waveform generation */

guint sourceid; /* To control the GSource */

GMainLoop *main_loop; /* GLib's Main Loop */
} CustomData;

/* This method is called by the idle GSource in the mainloop, to feed CHUNK_SIZE bytes into appsrc.
* The idle handler is added to the mainloop when appsrc requests us to start sending data (need-data signal)
* and is removed when appsrc has enough data (enough-data signal).
*/
static gboolean push_data (CustomData *data) {
GstBuffer *buffer;
GstFlowReturn ret;
int i;
GstMapInfo map;
gint16 *raw;
gint num_samples = CHUNK_SIZE / 2; /* Because each sample is 16 bits */
gfloat freq;

/* Create a new empty buffer */
buffer = gst_buffer_new_and_alloc (CHUNK_SIZE);

/* Set its timestamp and duration */
GST_BUFFER_TIMESTAMP (buffer) = gst_util_uint64_scale (data->num_samples, GST_SECOND, SAMPLE_RATE);
GST_BUFFER_DURATION (buffer) = gst_util_uint64_scale (num_samples, GST_SECOND, SAMPLE_RATE);

/* Generate some psychodelic waveforms */
gst_buffer_map (buffer, &map, GST_MAP_WRITE);
raw = (gint16 *)map.data;
data->c += data->d;
data->d -= data->c / 1000;
freq = 1100 + 1000 * data->d;
for (i = 0; i < num_samples; i++) {
data->a += data->b;
data->b -= data->a / freq;
raw[i] = (gint16)(500 * data->a);
}
gst_buffer_unmap (buffer, &map);
data->num_samples += num_samples;

/* Push the buffer into the appsrc */
g_signal_emit_by_name (data->app_source, "push-buffer", buffer, &ret);

/* Free the buffer now that we are done with it */
gst_buffer_unref (buffer);

if (ret != GST_FLOW_OK) {
/* We got some error, stop sending data */
return FALSE;
}

return TRUE;
}

/* This signal callback triggers when appsrc needs data. Here, we add an idle handler
* to the mainloop to start pushing data into the appsrc */
static void start_feed (GstElement *source, guint size, CustomData *data) {
if (data->sourceid == 0) {
g_print ("Start feeding\n");
data->sourceid = g_idle_add ((GSourceFunc) push_data, data);
}
}

/* This callback triggers when appsrc has enough data and we can stop sending.
* We remove the idle handler from the mainloop */
static void stop_feed (GstElement *source, CustomData *data) {
if (data->sourceid != 0) {
g_print ("Stop feeding\n");
g_source_remove (data->sourceid);
data->sourceid = 0;
}
}

/* The appsink has received a buffer */
static GstFlowReturn new_sample (GstElement *sink, CustomData *data) {
GstSample *sample;

/* Retrieve the buffer */
g_signal_emit_by_name (sink, "pull-sample", &sample);
if (sample) {
/* The only thing we do in this example is print a * to indicate a received buffer */
g_print ("*");
gst_sample_unref (sample);
return GST_FLOW_OK;
}

return GST_FLOW_ERROR;
}

/* This function is called when an error message is posted on the bus */
static void error_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
GError *err;
gchar *debug_info;

/* Print error details on the screen */
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);

g_main_loop_quit (data->main_loop);
}

int main(int argc, char *argv[]) {
CustomData data;
GstPad *tee_audio_pad, *tee_video_pad, *tee_app_pad;
GstPad *queue_audio_pad, *queue_video_pad, *queue_app_pad;
GstAudioInfo info;
GstCaps *audio_caps;
GstBus *bus;

/* Initialize custom data structure */
memset (&data, 0, sizeof (data));
data.b = 1; /* For waveform generation */
data.d = 1;

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

/* Create the elements */
data.app_source = gst_element_factory_make ("appsrc", "audio_source");
data.tee = gst_element_factory_make ("tee", "tee");
data.audio_queue = gst_element_factory_make ("queue", "audio_queue");
data.audio_convert1 = gst_element_factory_make ("audioconvert", "audio_convert1");
data.audio_resample = gst_element_factory_make ("audioresample", "audio_resample");
data.audio_sink = gst_element_factory_make ("autoaudiosink", "audio_sink");
data.video_queue = gst_element_factory_make ("queue", "video_queue");
data.audio_convert2 = gst_element_factory_make ("audioconvert", "audio_convert2");
data.visual = gst_element_factory_make ("wavescope", "visual");
data.video_convert = gst_element_factory_make ("videoconvert", "video_convert");
data.video_sink = gst_element_factory_make ("autovideosink", "video_sink");
data.app_queue = gst_element_factory_make ("queue", "app_queue");
data.app_sink = gst_element_factory_make ("appsink", "app_sink");

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

if (!data.pipeline || !data.app_source || !data.tee || !data.audio_queue || !data.audio_convert1 ||
!data.audio_resample || !data.audio_sink || !data.video_queue || !data.audio_convert2 || !data.visual ||
!data.video_convert || !data.video_sink || !data.app_queue || !data.app_sink) {
g_printerr ("Not all elements could be created.\n");
return -1;
}

/* Configure wavescope */
g_object_set (data.visual, "shader", 0, "style", 0, NULL);

/* Configure appsrc */
gst_audio_info_set_format (&info, GST_AUDIO_FORMAT_S16, SAMPLE_RATE, 1, NULL);
audio_caps = gst_audio_info_to_caps (&info);
g_object_set (data.app_source, "caps", audio_caps, "format", GST_FORMAT_TIME, NULL);
g_signal_connect (data.app_source, "need-data", G_CALLBACK (start_feed), &data);
g_signal_connect (data.app_source, "enough-data", G_CALLBACK (stop_feed), &data);

/* Configure appsink */
g_object_set (data.app_sink, "emit-signals", TRUE, "caps", audio_caps, NULL);
g_signal_connect (data.app_sink, "new-sample", G_CALLBACK (new_sample), &data);
gst_caps_unref (audio_caps);

/* Link all elements that can be automatically linked because they have "Always" pads */
gst_bin_add_many (GST_BIN (data.pipeline), data.app_source, data.tee, data.audio_queue, data.audio_convert1, data.audio_resample,
data.audio_sink, data.video_queue, data.audio_convert2, data.visual, data.video_convert, data.video_sink, data.app_queue,
data.app_sink, NULL);
if (gst_element_link_many (data.app_source, data.tee, NULL) != TRUE ||
gst_element_link_many (data.audio_queue, data.audio_convert1, data.audio_resample, data.audio_sink, NULL) != TRUE ||
gst_element_link_many (data.video_queue, data.audio_convert2, data.visual, data.video_convert, data.video_sink, NULL) != TRUE ||
gst_element_link_many (data.app_queue, data.app_sink, NULL) != TRUE) {
g_printerr ("Elements could not be linked.\n");
gst_object_unref (data.pipeline);
return -1;
}

/* Manually link the Tee, which has "Request" pads */
tee_audio_pad = gst_element_request_pad_simple (data.tee, "src_%u");
g_print ("Obtained request pad %s for audio branch.\n", gst_pad_get_name (tee_audio_pad));
queue_audio_pad = gst_element_get_static_pad (data.audio_queue, "sink");
tee_video_pad = gst_element_request_pad_simple (data.tee, "src_%u");
g_print ("Obtained request pad %s for video branch.\n", gst_pad_get_name (tee_video_pad));
queue_video_pad = gst_element_get_static_pad (data.video_queue, "sink");
tee_app_pad = gst_element_request_pad_simple (data.tee, "src_%u");
g_print ("Obtained request pad %s for app branch.\n", gst_pad_get_name (tee_app_pad));
queue_app_pad = gst_element_get_static_pad (data.app_queue, "sink");
if (gst_pad_link (tee_audio_pad, queue_audio_pad) != GST_PAD_LINK_OK ||
gst_pad_link (tee_video_pad, queue_video_pad) != GST_PAD_LINK_OK ||
gst_pad_link (tee_app_pad, queue_app_pad) != GST_PAD_LINK_OK) {
g_printerr ("Tee could not be linked\n");
gst_object_unref (data.pipeline);
return -1;
}
gst_object_unref (queue_audio_pad);
gst_object_unref (queue_video_pad);
gst_object_unref (queue_app_pad);

/* Instruct the bus to emit signals for each received message, and connect to the interesting signals */
bus = gst_element_get_bus (data.pipeline);
gst_bus_add_signal_watch (bus);
g_signal_connect (G_OBJECT (bus), "message::error", (GCallback)error_cb, &data);
gst_object_unref (bus);

/* Start playing the pipeline */
gst_element_set_state (data.pipeline, GST_STATE_PLAYING);

/* Create a GLib Main Loop and set it to run */
data.main_loop = g_main_loop_new (NULL, FALSE);
g_main_loop_run (data.main_loop);

/* Release the request pads from the Tee, and unref them */
gst_element_release_request_pad (data.tee, tee_audio_pad);
gst_element_release_request_pad (data.tee, tee_video_pad);
gst_element_release_request_pad (data.tee, tee_app_pad);
gst_object_unref (tee_audio_pad);
gst_object_unref (tee_video_pad);
gst_object_unref (tee_app_pad);

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

加入appsrc和appsink

首先要先設定appsrc的caps,他決定了appsrc所輸出的資料類型。我們可以用字串來建立GstCaps物件,只需要用gst_caps_from_string()函式。

另外我們還必須連接need-dataenough-data訊號。這兩個訊號是appsrc發出來的。

1
2
3
4
5
6
/* Configure appsrc */
gst_audio_info_set_format (&info, GST_AUDIO_FORMAT_S16, SAMPLE_RATE, 1, NULL);
audio_caps = gst_audio_info_to_caps (&info);
g_object_set (data.app_source, "caps", audio_caps, NULL);
g_signal_connect (data.app_source, "need-data", G_CALLBACK (start_feed), &data);
g_signal_connect (data.app_source, "enough-data", G_CALLBACK (stop_feed), &data);

new-sample訊號

另外我們還要接上app_sink的訊號new-sample,這個訊號預設是關閉的所以必須手動打開這個訊號,由emit-signals可以設定。

1
2
3
4
/* Configure appsink */
g_object_set (data.app_sink, "emit-signals", TRUE, "caps", audio_caps, NULL);
g_signal_connect (data.app_sink, "new-sample", G_CALLBACK (new_sample), &data);
gst_caps_unref (audio_caps);

callback function

我們的callback function在每當appsrc內部的queue快要沒資料的時候被呼叫。他唯一做的事情就是註冊一個GLib的函式g_idle_add(),他將會給appsrc資料直到appsrc滿了為止。

Glib的main event loop更進一步說明可以參考這裡

我們將g_idle_add()回傳的id做個紀錄以便等一下可以停止他。

1
2
3
4
5
6
7
8
/* This signal callback triggers when appsrc needs data. Here, we add an idle handler
* to the mainloop to start pushing data into the appsrc */
static void start_feed (GstElement *source, guint size, CustomData *data) {
if (data->sourceid == 0) {
g_print ("Start feeding\n");
data->sourceid = g_idle_add ((GSourceFunc) push_data, data);
}
}

下面這個callback function當appsrc內部的queue滿的時候會被呼叫。在這裡我們就直接用g_source_remove()移除idle function

1
2
3
4
5
6
7
8
9
/* This callback triggers when appsrc has enough data and we can stop sending.
* We remove the idle handler from the mainloop */
static void stop_feed (GstElement *source, CustomData *data) {
if (data->sourceid != 0) {
g_print ("Stop feeding\n");
g_source_remove (data->sourceid);
data->sourceid = 0;
}
}

接下來這個function是推送資料給appsrc的callback,是給GLib的g_idle_add()呼叫用的。

首先他的工作是建立一個新的buffer並且給定大小(這個範例設為1024 bytes),設定大小可以用gst_buffer_new_and_alloc()

接下例我們計算我們已經餵給appsrc的資料數目,並且用CustomData.num_samples紀錄,如此一來就可以給這個buffer時間戳,時皆戳可以用GstBufferGST_BUFFER_TIMESTAMP marco。

因為我們每次都提供相同大小的buffer,他的長度都是相同的,我們可以用GstBufferGST_BUFFER_DURATIONmarco來設定duration。

gst_util_uint64_scale()是用來放大或縮小大數字的函式,用這個函是就不用擔心overflows。

buffer的大小可以用GstBuffer 可以用GST_BUFFER_DATA 取得。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* This method is called by the idle GSource in the mainloop, to feed CHUNK_SIZE bytes into appsrc.
* The ide handler is added to the mainloop when appsrc requests us to start sending data (need-data signal)
* and is removed when appsrc has enough data (enough-data signal).
*/
static gboolean push_data (CustomData *data) {
GstBuffer *buffer;
GstFlowReturn ret;
int i;
gint16 *raw;
gint num_samples = CHUNK_SIZE / 2; /* Because each sample is 16 bits */
gfloat freq;

/* Create a new empty buffer */
buffer = gst_buffer_new_and_alloc (CHUNK_SIZE);

/* Set its timestamp and duration */
GST_BUFFER_TIMESTAMP (buffer) = gst_util_uint64_scale (data->num_samples, GST_SECOND, SAMPLE_RATE);
GST_BUFFER_DURATION (buffer) = gst_util_uint64_scale (num_samples, GST_SECOND, SAMPLE_RATE);

/* Generate some psychodelic waveforms */
raw = (gint16 *)GST_BUFFER_DATA (buffer);

最後就是把生成好的資料推送進appsrc裡面,並且會觸發push-buffer訊號。

1
2
3
4
5
/* Push the buffer into the appsrc */
g_signal_emit_by_name (data->app_source, "push-buffer", buffer, &ret);

/* Free the buffer now that we are done with it */
gst_buffer_unref (buffer);

而當appsink接收到資料的時候下面的函式會被呼叫。我們用pull-sample動作訊號來取得buffer並且應到螢幕上。利用GST_BUFFER_DATA取得資料的指針以及GST_BUFFER_SIZE取得資料大小。

注意,在這裡的buffer不一定會跟我們在前面指定的buffer大小一樣,因為任何element都有可能去更動buffer,雖然在這個範例buffer並沒有被改動。

Debugging tools

debug log

debug log是由GST_DEBUG環境變數控制。下面是GST_DEBUG=2的時候的debug log。

1
0:00:00.868050000  1592   09F62420 WARN                 filesrc gstfilesrc.c:1044:gst_file_src_start:<filesrc0> error: No such file "non-existing-file.webm"

通常我們不會把debug log全部打開以免訊息塞爆文件或是訊息視窗。下面是各種等級的debug log會輸出的資料。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| # | Name    | Description                                                    |
|---|---------|----------------------------------------------------------------|
| 0 | none | No debug information is output. |
| 1 | ERROR | Logs all fatal errors. These are errors that do not allow the |
| | | core or elements to perform the requested action. The |
| | | application can still recover if programmed to handle the |
| | | conditions that triggered the error. |
| 2 | WARNING | Logs all warnings. Typically these are non-fatal, but |
| | | user-visible problems are expected to happen. |
| 3 | FIXME | Logs all "fixme" messages. Those typically that a codepath that|
| | | is known to be incomplete has been triggered. It may work in |
| | | most cases, but may cause problems in specific instances. |
| 4 | INFO | Logs all informational messages. These are typically used for |
| | | events in the system that only happen once, or are important |
| | | and rare enough to be logged at this level. |
| 5 | DEBUG | Logs all debug messages. These are general debug messages for |
| | | events that happen only a limited number of times during an |
| | | object's lifetime; these include setup, teardown, change of |
| | | parameters, etc. |
| 6 | LOG | Logs all log messages. These are messages for events that |
| | | happen repeatedly during an object's lifetime; these include |
| | | streaming and steady-state conditions. This is used for log |
| | | messages that happen on every buffer in an element for example.|
| 7 | TRACE | Logs all trace messages. Those are message that happen very |
| | | very often. This is for example is each time the reference |
| | | count of a GstMiniObject, such as a GstBuffer or GstEvent, is |
| | | modified. |
| 9 | MEMDUMP | Logs all memory dump messages. This is the heaviest logging and|
| | | may include dumping the content of blocks of memory. |
+------------------------------------------------------------------------------+

設置element的debug log

如果要設置個別element的debug level,範例如下。如此一來audiotestsrc的level是6,其他的都是2

1
GST_DEBUG=2,audiotestsrc:6

GST_DEBUG格是

GST_DEBUG的第一個參數是optional的,他會設定全域的debug level。參數之間以逗號,區隔,除了第一個參數,每一個設定的格式都是category:level

*也可以被用在GST_DEBUG裡面,例如GST_DEBUG=2,audio*:6會把所有開頭為audio的element都設為level 6,其他保持level 2。

增加自訂除錯訊息

如果要讓category 看起來更有意義,可以加入下面兩行

1
2
GST_DEBUG_CATEGORY_STATIC (my_category);
#define GST_CAT_DEFAULT my_category

以及下面這行在gst_init()被呼叫之後。

1
GST_DEBUG_CATEGORY_INIT (my_category, "my category", 0, "This is my very own");

取得pipeline的圖

gstreamer可以將你的pipeline輸出成.dot檔可以用例如GraphViz來開啟。

如果在程式內想開啟這項功能可以用GST_DEBUG_BIN_TO_DOT_FILE()GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS()marco來開啟這個功能。
範例:
可以在程式中加入下面程式碼來儲存pipeline圖
官方文件
圖案參數
參考來源

1
GST_DEBUG_BIN_TO_DOT_FILE(pipeline, GST_DEBUG_GRAPH_SHOW_ALL, "pipeline"); //三個參數分別為 pipeline的instance, 圖案參數, 檔案名稱

這行程式記得加在將pipeline state設定為playing之前,這樣才能完整看到pipeline的樣子。

然後執行程式之前先設定環境變數GST_DEBUG_DUMP_DOT_DIR來指定儲存位置

1
2
export GST_DEBUG_DUMP_DOT_DIR=./tracing/
mkdir tracing

接下來就會在tracing看到pipeline.dot

安裝xdot來讀取.dot檔

1
2
sudo apt install xdot
xdot pipeline.dot

gst-launch-1.0可以用用GST_DEBUG_DUMP_DOT_DIR來設定儲存圖片的資料夾並開啟這項功能。每當pipeline的狀態改變的時候都會畫一張圖,如此一來就可以看到pipeline的變化

gst-debugger

遠端除錯gstreamer

安裝protoc
http://google.github.io/proto-lens/installing-protoc.html

Streaming

這裡將介紹streaming要注意的點

  • 開啟buffering
  • 斷線重連
    通常網路串流會因為網路連線的關係造成串流封包沒有準時到達而造成跨面卡住。而這個問題的解法就是使用bufferbuffer讓一些影音chunks儲存在queue裡面,如此一來雖然剛開始影片會稍微延遲一點,但是如果網路連線不穩的話話面不會卡住,因為queue裡面還有chunks。

clock

應用程式應該隨時監看buffer的狀態,如果buffer太少,就應該暫停撥放。為了達到所有的sink都可以同步,GStreamer有一個global clock,所有的element都會共用這個global clock。
有時候如果切換streaming或是切換輸出裝置,clock會消失,這時候就必須重選clock,下面的範例將會解說這個步驟。

當clock消失的時候應用程式會接收到訊息,這時候只需要將pipeline設為PAUSED再設為PLAYING就可以選擇新的clock。

範例

範例basic-tutorial-12.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#include <gst/gst.h>
#include <string.h>

typedef struct _CustomData {
gboolean is_live;
GstElement *pipeline;
GMainLoop *loop;
} CustomData;

static void cb_message (GstBus *bus, GstMessage *msg, CustomData *data) {

switch (GST_MESSAGE_TYPE (msg)) {
case GST_MESSAGE_ERROR: {
GError *err;
gchar *debug;

gst_message_parse_error (msg, &err, &debug);
g_print ("Error: %s\n", err->message);
g_error_free (err);
g_free (debug);

gst_element_set_state (data->pipeline, GST_STATE_READY);
g_main_loop_quit (data->loop);
break;
}
case GST_MESSAGE_EOS:
/* end-of-stream */
gst_element_set_state (data->pipeline, GST_STATE_READY);
g_main_loop_quit (data->loop);
break;
case GST_MESSAGE_BUFFERING: {
gint percent = 0;

/* If the stream is live, we do not care about buffering. */
if (data->is_live) break;

gst_message_parse_buffering (msg, &percent);
g_print ("Buffering (%3d%%)\r", percent);
/* Wait until buffering is complete before start/resume playing */
if (percent < 100)
gst_element_set_state (data->pipeline, GST_STATE_PAUSED);
else
gst_element_set_state (data->pipeline, GST_STATE_PLAYING);
break;
}
case GST_MESSAGE_CLOCK_LOST:
/* Get a new clock */
gst_element_set_state (data->pipeline, GST_STATE_PAUSED);
gst_element_set_state (data->pipeline, GST_STATE_PLAYING);
break;
default:
/* Unhandled message */
break;
}
}

int main(int argc, char *argv[]) {
GstElement *pipeline;
GstBus *bus;
GstStateChangeReturn ret;
GMainLoop *main_loop;
CustomData data;

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

/* Initialize our data structure */
memset (&data, 0, sizeof (data));

/* Build the pipeline */
pipeline = gst_parse_launch ("playbin uri=https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm", NULL);
bus = gst_element_get_bus (pipeline);

/* 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;
} else if (ret == GST_STATE_CHANGE_NO_PREROLL) {
data.is_live = TRUE;
}

main_loop = g_main_loop_new (NULL, FALSE);
data.loop = main_loop;
data.pipeline = pipeline;

gst_bus_add_signal_watch (bus);
g_signal_connect (bus, "message", G_CALLBACK (cb_message), &data);

g_main_loop_run (main_loop);

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

說明

在這個範例中比較特別的是下面這一段,注意如果收到GST_STATE_CHANGE_NO_PREROLL而不是GST_STATE_CHANGE_SUCCESS,這代表目前正再撥放直撥串流。

因為直撥串流是不能暫停的,所以就算把pipeline的狀態設為PAUSED他的行為還是跟PLAYING一樣。而且即使我們嘗試將pipeline設為PLAYING也會收到這個訊息。

因為我們想要關閉直撥串流的buffering,所以我們用gst_element_set_state()在data裡面做記號

1
2
3
4
5
6
7
8
9
/* 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;
} else if (ret == GST_STATE_CHANGE_NO_PREROLL) {
data.is_live = TRUE;
}

callback

接下來我們看一下message parsing callback,首先如果發現是直撥串流,就不要打開buffering。接下來用gst_message_parse_buffering()來取得 buffering level。

然後我們印出 buffering level並且設定當 buffering level小於100%的時候就暫停pipeline,如果超過就設為PLAYING。

程式執行的時候會看到buffer慢慢攀升到100%,然後如果網路不穩到buffer小於100%,就會暫停撥放直到回復100%後才重新撥放。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
case GST_MESSAGE_BUFFERING: {
gint percent = 0;

/* If the stream is live, we do not care about buffering. */
if (data->is_live) break;

gst_message_parse_buffering (msg, &percent);
g_print ("Buffering (%3d%%)\r", percent);
/* Wait until buffering is complete before start/resume playing */
if (percent < 100)
gst_element_set_state (data->pipeline, GST_STATE_PAUSED);
else
gst_element_set_state (data->pipeline, GST_STATE_PLAYING);
break;
}

lost clock

另一個我們要處理的消息是遺失clock,我們只需要將pipeline設為PAUSED再設為PLAYING就可以了。

1
2
3
4
5
case GST_MESSAGE_CLOCK_LOST:
/* Get a new clock */
gst_element_set_state (data->pipeline, GST_STATE_PAUSED);
gst_element_set_state (data->pipeline, GST_STATE_PLAYING);
break;

DeepStream動態增減串流

本篇文章參考:
https://developer.nvidia.com/blog/managing-video-streams-in-runtime-with-the-deepstream-sdk/
https://github.com/NVIDIA-AI-IOT/deepstream_reference_apps/tree/master/runtime_source_add_delete

Glib定時執行函式

為了到動態增減串流,必須在main thread之外有一個thread定期的察看目前串流的表。Glib提供了g_timeout_add_seconds這個函式讓我們可以定期呼叫函式。g_timeout_add_seconds可以讓我們設定每間隔多少時間呼叫一次函數。

1
guint g_timeout_add_seconds (guint interval, GSourceFunc function, gpointer data)

g_timeout_add_seconds有三個參數分別是
Interval: 每間隔多少秒呼叫函數一次
function: 要被呼叫的函式
data: 要傳送給函式的參數

在我們動態增減的範例中,我們可以寫一個watchDog函式來讀去資料庫目前有無需要新增或刪減串流。

Linux 核心設計實作摘要

C語言

指標理解

C Traps and Pitfalls 的 “Understanding Declarations”小節提到如何解讀指標。每一個C的變數宣告可以拆成兩個部分。

  1. 型態
  2. 一串將會回傳這個型態的表達式

float f, g;代表f, g將會回傳float
float ff();代表ff()將會回傳float,因此ff是一個function並且會回傳float
float *pf;代表*pf江會回傳float,因此pf是一個pointer
float *g(), (*h)();首先()的優先度大於*因此*g() 可以改寫成*(g()),由此可知g是function並且會回傳一個pointer to a float的指標。而(*h)()代表h是一個pointer to a function指標,而且這個function會回傳float。


知道如何宣告變數後就可以寫出,就可以知道如何寫出這個類型強制轉型的寫法,只需要把變數名稱和分號移除,最後再加上括號
float *g();在這裡的g是一個回傳pointer的function,這個pointer指向float。而g強制轉型的寫法即為(float *())

Byte和Bit

1 Byte = 8 Bits

指標運算符號

  1. Address-of operator
    &稱為 Address-of operator

  2. Dereference operator
    *稱為 Dereference operator
    將pointer所指向的值給另一個變數(This is called a “load” operation.)

    1
    int bar = *foo_ptr;

    將值儲存到pointer所指的位置(This is called a “store” operation.)

    1
    *foo_ptr = 42; Sets foo to 42

->操作符

定義一個struct foo和一個foo的指針foo_ptr

1
2
3
4
5
6
struct foo {
size_t size;
char name[64];
int answer_to_ultimate_question;
unsigned shoe_size;
};

如果要查看foo_ptr所指向的內容,可以用

1
(*foo_ptr).size = new_size;

或者是用->操作符

1
foo_ptr->size = new_size;

陣列Array

宣告陣列

1
int array[] = { 45, 67, 89 };

在C語言,你宣告了一個陣列array之後,當你使用的時候,array這個變數其實是一個指向這個陣列第一個元素的指針,我們把這個行為稱為decaying,因為陣列被decays成指針了。不過他還是和針的指針有一點不同,其中就是如果用sizeof(array)來看陣列的話,回傳的將會是陣列的總長度(在這個範例就是(sizeof(int) = 4) × 3 = 12)而不是單一個指針的長度。
下面這三種狀況對陣列來說都是一樣的

1
array == &array == &array[0]

他們分別代表“array”, “pointer to array”, 和 “pointer to the first element of array”,但在C這三個東西是一樣的

陣列++

對於一般變數來說variable += 1代表對變數+1,但是對於指標來說代表對目前指標的位置加上資料型態的大小。以我們上一個例子來說我們的陣列儲存的是int,而array被decays成pointer了,所以array + 1就是加上sizeof(int),等同於我們把指針移動到下一個元素。

Indexing

首先看一下以下例子

1
2
3
int array[] = { 45, 67, 89 };
int *array_ptr = &array[1];
printf("%i\n", array_ptr[1]);

這段程式宣告的一個三個元素的陣列array,還有一個int指針array_ptr,可以用下面這張圖可以看到array[1]array_ptr[0]指向同一個記憶體。
image info{: w=”700” h=”200” }
我們可以看到其實[]是指針的操作符,array[1]等同於*(array + 1)

參考:
Everything you need to know about pointers in C
https://boredzo.org/pointers/

GDB除錯Python C extension

測試範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <Python.h>

static PyObject *method_myadd(PyObject *self, PyObject *args){
int x, y, z = -1;

/* Parse arguments */
if(!PyArg_ParseTuple(args, "ii", &x, &y)){
return NULL;
}

/* The actual bit of code I need */
z = x + y;

return PyLong_FromLong(z);
}

static PyMethodDef myaddMethods[] = {
{"myadd", method_myadd, METH_VARARGS, "Python interface for myadd C library function"},
{NULL, NULL, 0, NULL}
};

static struct PyModuleDef myaddmodule = {
PyModuleDef_HEAD_INIT,
"myadd",
"Python interface for the myadd C library function",
-1,
myaddMethods
};

PyMODINIT_FUNC PyInit_myadd(void) {
return PyModule_Create(&myaddmodule);
}

{: file=’myadd.cpp’}

1
2
3
4
5
6
7
import myadd

print("going to ADD SOME NUMBERS")

x = myadd.myadd(5,6)

print(x)

{: file=’myscript.py’}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from distutils.core import setup, Extension

def main():
setup(name="myadd",
version="1.0.0",
description="Python interface for the myadd C library function",
author="Nadiah",
author_email="nadiah@nadiah.org",
ext_modules=[Extension("myadd", ["myadd.cpp"])],
)


if __name__ == "__main__":
main()

{: file=’setup.py’}

安裝GCC

1
sudo apt-get install build-essential

安裝python標頭檔

1
apt-get install python3-dev

安裝venv

最好要使用venv來開發c extension,因為如果沒有使用venv,python3 setup.py install編譯好的套件

1
apt install python3-venv

編譯範例程式

1
python3 setup.py install

安裝Python debug symbol

下面指令python的版本可以改成你自己的python版本

1
apt-get install python3.10-dbg

以GDB執行Python

1
gdb python

注意!!必須先正確安裝Python的debug symbol再執行這一步,完成後你應該要可以看到成功載入Python debug symbol,gdb的顯示類似如下

1
2
3
4
5
6
7
8
GNU gdb (Ubuntu 12.1-0ubuntu1~22.04) 12.1
Copyright (C) 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
.....
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from python...
Reading symbols from /usr/lib/debug/.build-id/75/c83caa11a9418b8e5ae8feb0bb8f2e5d00c47b.debug...

如果你看到(No debugging symbols found in python)表示GDB找不到Python debug symbols。

GDB下中斷點

這一步會預先建立一個中斷點,引為此時我們的extension還沒被Python載入。再這個範例我們把中斷點下在myadd.cpp第12行z = x + y的位置

1
(gdb)b myadd.cpp:12

這時候GDB會問你是不是要建立一個未來使用的中斷點,回答是就可以

1
2
3
(gdb) b myadd.cpp:12
No source file named myadd.cpp.
Make breakpoint pending on future shared library load? (y or [n])

Python呼叫extension

最後在GDB裡面執行myscript.py程式,就可以看到程式停在我們的中斷點

1
(gdb) run myscript.py

debug Python 好用的指令

參考:

編譯教學
https://uwpce-pythoncert.github.io/Py300/notes/BuildingExtensions.html

安裝debug symbol
https://wiki.python.org/moin/DebuggingWithGdb

gdb除錯參考:
https://scipy-lectures.org/advanced/debugging/index.html#debugging-segmentation-faults-using-gdb

加入gdb路徑
https://devguide.python.org/advanced-tools/gdb/index.html#gdb-7-and-later

範例程式
https://nadiah.org/2020/03/01/example-debug-mixed-python-c-in-visual-studio-code/

除錯
https://developers.redhat.com/articles/2021/09/08/debugging-python-c-extensions-gdb#python_commands_in_gdb

統計學課本重點摘要

CH1統計學基本元素

Population, Sample, Experimental unit

statistics element

Variable

Variable

Measurement

Measurement

Inferenital Statistical Problem最大的重點,計算reliability

Inferenital Statistical Problem

Quantitative data和Qualitative data

Quantitative data: 可以測量的資料,例如高度、溫度
Qualitative data:無法測量的資料,例如滿意度、車子種類,為了方便計算可以給予數值,但數值本身沒有任何意義,只是一個代號

Representative sample

representative sample的特遮會和母體的特徵一樣。

CH2 描述一組資料

資料集

data set: $$x_1, x_2, x_3, \cdots , x_n$$,每個元素都是一個量測結果。
例如我們量測5個商品的長度,並且記錄結果為$$x_1=5, x_2=3, x_3=8, x_4=5, x_5=4$$

加總符號

如果要表達所有元素的加總,我們可以寫成$$x_1 + x_2 + x_3 + \cdots + x_n$$,或是我們可以用符號
$$\sum$$代表

$$x_1 + x_2 + x_3 + \cdots + x_n = \sum_{i=1}^nx_i$$

如果我們要計算每個元素的平方和,可以表達成下面方式

$$x_1^2 + x_2^2 + x_3^2 + \cdots + x_n^2 = \sum_{i=1}^nx_i^2$$

描述資料的方式

通常描述資料會有兩中方式

  1. 集中趨勢(central tendency)
  2. 變異性(variability)
    (補集中趨勢和變異性的圖)

集中趨勢(Central Tendency)

  1. 我們最常用的Central Tendency計算方式就是平均值,我們用$$\bar{x}$$(音:x bar)代表”樣本平均數”,他的計算方式如下

$$\bar{x} = \frac{\sum_{i=1}^nx_i}{n}$$

而我們用$$\mu$$代表母體平均數。通常我們用英文字母代表樣本,希臘字母代表母體。

  1. 中位數,比起平均數更能夠對付極端值。

變異性(variability)

  • deviation: 每一個資料點和平均的”距離”和”方向”,注意deviation是有正負號的,所以直接相加平均正負會相消。
  • sample variance(變異數): 為了解決正負相消的問題,我們可以將每一個deviation平方後相加再除以元數個數-1。

$$s^2 = \frac{\sum_{i=1}^n(x_i - \bar{x})^2}{n-1}$$

  • sample standard devitaion: 為了得到有意義的變異測量值,將sample variance開根號後就可以得到sample standard devitaion

$$s = \sqrt{s^2}$$

$$s^2 = $$ sample variance(樣本變異數)
$$s = $$ sample standard devitaion(樣本標準差)
$$\sigma^2 =$$ population variance(母體變異數)
$$\sigma =$$ population standard devitaion(母體標準差)

用標準差來描述單一樣本(單一資料集)

前面我們已經知道如果比較兩個樣本,standard devitaion越大表示變異性越大,也就是我們知道用standard devitaion來比較兩個樣本的相對變異程度。這節我們要用standard devitaion來描述單一個樣本。
如果對於frequency distributionj為對稱鐘形,根據經驗法則,

  1. 通常68%的Experimental unit會落在平均的正負一個標準差之內,也就是對於樣本來說$$(\bar{x} - s, \bar{x} + s)$$,對於母體來說$$(\mu - \sigma, \mu + \sigma)$$
  2. 通常95%的Experimental unit會落在平均的正負兩個標準差之內,也就是對於樣本來說$$(\bar{x} - 2s, \bar{x} + 2s)$$,對於母體來說$$(\mu - 2\sigma, \mu + 2\sigma)$$
  3. 通常99.7%的Experimental unit會落在平均的正負三個標準差之內,也就是對於樣本來說$$(\bar{x} - 3s, \bar{x} + 3s)$$,對於母體來說$$(\mu - 3\sigma, \mu + 3\sigma)$$

描述單一個測量值在全部測量值的位置

如果要描述一個測量值在所有測量值的位置,例如個人所得在所有勞工所得的位置。我們可以用z-score來描述。z-score利用平均和標準差來描述一個側量值所在的位置。其公式如下。
對於樣本:

$$z = \frac{x - \bar{x}}{s}$$

對於母體:

$$z = \frac{x - \mu}{\sigma}$$

z-score是有正負號的,正值越大代表這個測量值大於平均值越多,反之越少。
(補上p79的圖)

CH3機率

事件、樣本空間、和機率

以丟硬幣為例

  • observation, measurement:紀錄丟硬幣出現的結果
  • experiment: 完成投擲多次硬幣並且記錄丟硬幣的結果的過程。
  • sample point: 整個experiment最基礎的出現結果,以硬幣來說,正面反面各是一個sample point,以骰子來說1,2,3,4,5,6都分別是一個sample point。
    已丟兩個硬幣為例,總共有4個sample point,分別為正正、正反、反正、反反
  • sample space:包含所有sample point的集合
    丟一個硬幣的sample space為 S:{正, 反}
    丟兩個硬幣的sample space為 S:{正正、正反、反正、反反}
    丟骰子的sample space為 S:{1,2,3,4,5,6}

sample point的機率規定

  1. 每一個sample point的機率一定要介於0和1之間。
  2. sample space裡面所有sample point的機率加總必須為1

event事件

event是一組sample point,他可以只包含一個sample point,也可以包含多個sample point

event的機率

就是event內所有的sample point的總和

聯集Unions和交集Intersections

聯集(or)
(P131的圖)
交集(and)
(P131的圖)

Complementary Event補集合

event A的補集合的事件就是由所有不包含A事件sample point所構成。$$A$$的補集合記為$$A^s$$

$$P(A)+P(A^s)=1$$

互斥事件的加法法則(Mutually Exclusive Events)

加法法則 $$P(A \cup B) = P(A) + P(B) - P(A \cap B)$$
互斥事件代表兩個事件不會同時發生,例如丟硬幣正面和反面不會同時出現,因此$$P(A \cap B)=0$$。因此對於互斥事件,$$P(A \cup B) = P(A) + P(B)$$

條件機率

給定A事件發生的條件下,B事件發生的機率。例如丟一個骰子,在丟出來的數字小於3的條件下,出現偶數的機率。

$$P(A \mid B) = \frac{P(A \cap B)}{P(B)}$$

乘法定律與獨立事件(Independent Event)

從前面的條件機率經過移項後就可以得到

$$P(A \cap B) = P(B)P(A \mid B)$$

而如果A和B的機率不會互相影響,A B兩個事件就是獨立事件,也就是

$$P(A \mid B) = P(A)$$

$$P(B \mid A) = P(B)$$
獨立事件有三個重點

  1. 獨立事件沒辦法用作圖或是直覺判斷,必須透過計算來驗證他是獨立事件。
  2. 互斥事件(Mutually Exclusive Events)不是獨立事件(Independent Event),假設A,B為互斥事件,因為互斥事件的關係,如果B發生則$$P(A \mid B) = 0$$,而因此$$P(A) \neq P(A \mid B)$$,所以不可能滿足獨立事件的條件$$P(A \mid B) = P(A)$$。
  3. 計算獨立事件的交集十分簡單,$$P(A \cap B) = P(A)P(B \mid A)$$,因為獨立事件的關係$$P(B \mid A)=P(B)$$,所以獨立事件的交集事件為$$P(A \cap B) = P(A)P(B)$$

隨機抽樣

每一個樣本被抽中的機率都相同

貝氏定律

$$P(A \mid B) = \frac{P(A \cap B)}{P(B)}$$

CH4隨機變數random variable和機率分布

隨機變數的兩種類型

  • 隨機變數的定義
    隨機變數是把實驗會出現的所有結果用數字表達,每一個sample point都會有一個數字。例如以丟兩個銅板為例,我們可以計算出現人頭的數量,因此我們的random variable就會有0, 1, 2 三個。random在這裡代表的意義是這些數字在每次實驗中都是隨機出現的。

兩種類型的random variable

  • discrete random variable 例如丟兩個銅板出現頭的個數
  • continuous random variable 例如鑽油井挖到多深會挖到石油

probability distribution of diserete random variable

diserete random variable 的probability distribution可以是一個圖、表或是公式。他描述的是每一個random variable的發生的機率。
以丟兩個硬幣為例,我們以觀察到頭的個數作為random variable並計為x,x總共有三種可能,分別是頭出現0次,1次,2次(0, 1, 2)。
丟硬幣可能出現的sampling point共有HH, HT, TH, TT四種(H:頭, T:字)。
計算probability distribution如下

$$P(x=0)=P(TT) = \frac{1}{4}$$

$$P(x=1)=P(TH) + P(HT) = \frac{1}{2}$$

$$P(x=2)=P(HH) = \frac{1}{4}$$

probability distribution的必要條件

Discrete Random Variable x 的probability distribution必要條件

  1. $$p(x) \geq 0$$
  2. $$\sum{p(x)} = 1$$,其中 $$\sum{p(x)}$$ 是把所有可能的random variable x都算進去加總

期望值Expected Value

期望值其實就是population mean
以隨機變數Random Variable x為例
$$\mu = E(x) = \sum{xp(x)}$$
注意,期望值不一定會是Random Variable可能出現的值,以擲骰子為例,期望值不一定會是1~6其中一個整數

期望值的直觀解釋
https://www.probabilisticworld.com/intuitive-explanation-expected-value/

隨機變數的變異數(Variance)

$$\sigma^2 = E[(x - \mu)^2] = \sum{(x - \mu)^2p(x)}$$

隨機變數的標準差(Standard deviation)

$$\sigma = \sqrt{\sigma^2}$$

Sample Distributions

  • parameters: 用來描述母體probability distributions的數值,例如用來描述binomial distributaion的成功機率p, 或者是描述normal distribution的$$\mu$$平均和$$\sigma$$標準差都是parameters。
    因為是描述母體,所以parameters是固定的數值,但是通常也是未知的或是永遠無法確切知道的

  • sample statistic: 用來描述sample的數值,他是由sample的oberservation計算而來的。例如$$\bar{x}$$平均、$$s^2$$以及s標準差。
    藉由這些sample statistic內含的一些資訊,我們可以用來推斷母體的parameter。

sample statistic能夠直接拿來推論parameters嗎?

我們從由sample取得的一個oberservation可以算出sample statistic。每次的oberservation所計算的sample statistic也不完全相同。
舉例來說,我們想要推斷丟公平骰子出現點數的期望值$$\mu$$,而我們也已知$$\mu = 3.5$$(在現實情況下$$\mu$$幾乎都是未知的)。
假設每次oberservation都丟三次,第一次的觀察結果是2, 2, 6,$$\bar{x} = 3.33$$ 中位數m為3,我們可以看到$$\bar{x}$$比較接近母體的$$\mu$$。
第二次oberservation結果為3, 4, 6,$$\bar{x} = 4.33$$,m=4,這次反而是中位數比較接近母體的$$\mu$$。
由此可知我們沒辦法直接比較哪一個sample statistic比較適合拿來推論parameters,而其根本的原因是因為sample statistic本身也是random variable,因為不同的sample本身就會產生出不同的sample statistic。
也因為sample statistic是random variable的原因,要比較他們就必須用他們的probility distribution

sample statistic的sampling distribution

sample statistic的probility distribution稱為sampling distribution。舉例來說,假設一間工廠生產的鐵條長度$$\mu = 0.3$$標準差為0.005。假設一個實驗每次隨機抽出25根鐵條,並且量測每一根的長度後計算平均長度$$\bar{x}$$。假如這個實驗做很多次的話,每一次實驗的$$\bar{x}$$都會不太一樣,而這些大量實驗所產生的$$\bar{x}$$的分布圖就是$$\bar{x}$$的sampling distribution。
sampling distribution是經由重複操作”抽取n個measurements”的實驗所計算的sample statistic的分布

圖例:
https://onlinestatbook.com/stat_sim/sampling_dist/index.html

Sample Distribution形狀的特性

假如被抽樣的母體是常態分布,而且也只有母體是常態分布的情況下,則不管實驗抽樣的n的大小,他的Sample Distribution也一定是常態分佈

中央極限定理(Central Limit Theorem)

假設我們想要推論一個母體的平均數$$\bar{x}$$,於是我們進行抽樣並且每次實驗都抽取n個樣本來計算平均,這個實驗重複非常多次而得到Sample Distribution,我們可以觀察到$$\bar{x}$$的Sample Distribution的母體平均數$$\mu_\bar{x}$$、標準差$$\sigma_\bar{x}$$,以及被抽樣的母體的平均數$$\mu$$和標準差$$\sigma$$的關係為

  1. Sample Distribution的平均 = 母體的平均,$$\mu_\bar{x} = E(\bar{x}) = \mu$$
  2. $$Sample Distribution的標準差等於 = \frac{母體標準差}{\sqrt{n}}$$,也就是
    $$\sigma_\bar{x} = \frac{\sigma}{\sqrt{n}}$$
    不管母體的分布是什麼,當n月大的時候Sample Distribution就越接近常態分佈,而且並議會越小越,資料越集中。

CH5 Inference Based on a Single Sample

本章的重點在於如何運用一組資料(Single Sample)來進行預測

target parameter

對於母體我們有興趣但是未知的參數我們稱為target parameter例如母體平均數。

point estimator

利用樣本的一個數值來預測母體的target parameter稱為point estimator。例如我們利用樣本的平均$$\bar{x}$$來推估

母體平均的信賴區間 : Normal (z) Statistic

假設銀行要推估所有欠債兩個月以上的帳號平均欠債的金額,於是做了一次實驗抽出一百個欠債兩個月以上的帳號並計算樣本平均$$\bar{x}$$。接下來要計算樣本平均$$\bar{x}$$推估母體平均的準確度。
先回顧一下根據中央極限定理,樣本平均的Sample Distribution在每次抽樣的樣本數n夠大的時候會接近常態分佈。而interval estimator如下:
$$\bar{x} \pm 1.96\sigma_\bar{x}= \bar{x} \pm \frac{1.96\sigma}{\sqrt{n}}$$

從Sample Distribution得圖上來看,我們畫了一個上下邊界在Sample Distribution上,而邊界的中心是母體標準差。(根據中央極限定理,Sample Distribution的平均數會近似於母體平均數)。
回到我們計算欠債平均金額的實驗中,我們這次實驗取得的樣本會落在這上下邊界範圍內的機率是多少?因為如果我們取得的樣本可以落在這上下邊界之內,我們所算出來的interval estimator就會包含母體平均,超過邊界則interval estimator內不會包含母體平均。
從常態分佈下抽取一個樣本落在距離平均一個標準差內機率0.95。
可以參考下面網站

簡單來說,我們在這裡算出一個interval estimator,而真正的母體平均數會落在這個interval estimator內的機率是confidence coefficient。
{: .prompt-warning }

confidence level 和 confidence coefficient

confidence coefficient是我們隨機抽取的樣本所匯出的confidence interval包含母體平均的機率,而confidence level則是confidence coefficient以百分比的方式呈現。例如confidence coefficient為0.95,則confidence level為95%。

使用Normal (z) Statistic的條件

  1. 樣本數n要大於30,因為根據中央極限定理當n大於30時,Sample Distribution會接近常態分佈。
  2. 樣本必須是從母體中隨機抽取的

$$\alpha$$與confidence coefficient

利用樣本的一個數值來預測母體的target parameter稱為point estimator。例如我們利用樣本的平均$$\bar{x}$$來推估。
例如:
假設我們想要估計一個城市的平均收入,但我們無法調查每一個人。因此,我們可以從這個城市隨機抽取一些人(樣本),並計算他們的平均收入(樣本平均值)。然後,我們可以使用這個樣本平均值作為整個城市平均收入的點估計。

interval estimator

interval estimator是一個公式,它告訴我們如何使用樣本數據來計算一個區間,以估計目標參數。

Confidence Interval for a Population Mean: Normal (z) Statics

假設銀行想推估呆帳平均所欠下的金額,於是隨機抽樣100個呆帳帳戶出來計算出sample mean $$\bar{x}$$,並且想利用$$\bar{x}$$推估母體的平均$$\mu$$。在這裡$$\bar{x}$$就是$$\mu$$的point estimator。
接下來要計算interval estimator,而根據Central Limit Theorem可以知道如果每次抽樣的數量n夠大,則Sample Distribution接近為常態分布,而且Sample Distribution的平均值

如此一來我們可以改寫confidence interval的公式為:

$$\bar{x} \pm (z_{\frac{\alpha}{2}})\sigma_{\bar{x}}= \bar{x} \pm z_{\frac{\alpha}{2}}(\frac{\sigma}{\sqrt{n}})$$

其中$$z_{\frac{\alpha}{2}}$$為z值在頭部面積為$$\frac{\alpha}{2}$$的時候的值。
而$$\sigma_{\bar{x}}$$ 是sample statistic $$\bar{x}$$ 的Sample Distribution,計算方式是母體標準差除以樣本數的平方根。當樣本數夠大的時候(通常大於30),可以用單次抽樣的標準差sample statistic s代替母體標準差$\sigma$。
也就是說當樣本數大於30時,式子可以改寫成
$$\bar{x} \pm (z_{\frac{\alpha}{2}})\frac{s}{\sqrt{n}}$$

概念釐清

特別注意,這個章節我們的目標是只做一次experiment,進而推斷出母體平均數。所以我們Sample Distribution是未知的,因為Sample Distribution要做很多次實驗才能得到。
這也就是為什麼$$\sigma_{\bar{x}}$$是未知的,而且$$\sigma_{\bar{x}}$$所指的母體是Sample Distribution,跟我們要推估的母體不是同一個母體。
{: .prompt-warning }

5.3 Student’s t Statistic

有些狀況下我們可以抽取的樣本數很少,例如藥物的人體實驗,這是後使用z statistic就會變得不準確。這裡將介紹t Statistic來處理這個狀況。
當樣本數小於30的時候我們面臨兩個問題

  1. 樣本數小於30,不能使用中央極限定理,也因此不能直接假設Sample Distribution為常態分佈。
    • 解法:在前面我們可以發現到,如果母體為常態分佈,那即使樣本很少,Sample Distribution也會接近常態分佈。引此我們假設母體為常態分布。
  2. 母體標準差$\sigma$是未知的而且我們不能再用單次抽樣的標準差s來代替,因為樣本數太少了。所以z statistic的公式也不能使用,因為他需要$\sigma$良好的估計值。
    • 解法:我們定義t statistic來處理這個問題。t statistic的公式如下
      $$t=\frac{\bar{x}-\mu}{s/\sqrt{n}}$$

在這裡sample statistic s是單次抽樣的標準差,取代了母體標準差$\sigma$。
假如我們是從常態分佈的母體抽樣,那麼t statistic的分佈會接近常態分佈。而t statistic和z statistic的差別在於t statistic多了一個random quantities s,也因此他的變動會比z statistic大。

t的Sample Distribution中實際變異量取決於樣本大小n。我們通常將他表示為自由度(degrees of freedom)為n-1的t分佈。回顧一下(n-1)是計算$s^2$的分母,所以如果n越小那sample distribution的變異量就越大。

對於small-sample 的confidence interval有以下結論

  • 對於平均數$\mu$,small-sample的confidence interval如下,其中$t_{\frac{\alpha}{2}}$為(n-1)自由度

$\bar{x} \pm t_{\frac{\alpha}{2}}\frac{s}{\sqrt{n}}$

  • 計算small-sample的confidence interval有以下條件
    • 樣本是隨機從母體抽出
    • 母體的分布必須接近常態分布

5.3 Large-Sample Confidence Interval for a Population Proportion

以市場調查為例,一間公司想知道消費者會選擇自己的品牌或是其他品牌。注意在這裡選擇品牌是一個qualitative variable,所以我們要用proportion來描述,而且這個問題是一個二元問題,因此我們要計算的是binoimal experiment中的p,也就是成功比例。要估計p我們可以利用計算樣本的成功比$\hat p$

$$\hat p=\frac{x}{n}=\frac{消費者選擇這間公司的品牌的人數}{問卷總人數}$$

而為了要計算$\hat p$的可靠度,我們將$\hat p$視為平均數,也就是說選擇這間公司品牌的人p計為1,選擇其他品牌的人q計為0,全部相加後除與總抽樣人數n,如此一來就可以用前面計算平均數的方法來推估$\hat p$的可靠度。

$\hat p$ 的Sampling Distribution

  1. $\hat p$的Sampling Distribution的平均數為p
  2. $\hat p$的Sampling Distribution的標準差為$\sqrt{\frac{pq}{n}}$,也就是$\sigma_{\hat p} = \sqrt{\frac{pq}{n}}$
  3. 對於large samples,$\hat p$的Sampling Distribution接近常態分佈。large samples的定義為np>5且nq>5。

常用參考資料

https://cqeacademy.com/cqe-body-of-knowledge/quantitative-methods-tools/point-estimates-and-confidence-intervals/

BatchNormalization論文閱讀與實作

gdb教學一基本操作

本篇文章修改參考這篇文章製作範例和教學,首先我們先寫一個有bug的程式factorial.c

基本操作

編譯程式給GDB

如果要給GDB除錯,一定要加上-g選項

1
gcc -g <any other flags e.g. -Wall> -o <file> <file.c>

開啟GDB session

在終端機輸入gdb和執行檔名稱即可開啟gdb session

1
2
3
4
5
6
7
8
9
gdb <program_name>
GNU gdb (Ubuntu 12.1-0ubuntu1~22.04) 12.1
Copyright (C) 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
...
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from factorial...
(gdb)

run指令

用run指令執行程式。

1
(gdb) run

如果有程式有接收參數,可以直接放在run後面

1
(gdb) run <arg1> <arg2> ... <arg n>

如果要用檔案作為輸入,可以用<

1
(gdb) run < <data>

start指令

用start指令執行程式,並且在main函式的第一行停下來。

1
(gdb) start

如果有程式有接收參數,可以直接放在run後面

1
(gdb) start <arg1> <arg2> ... <arg n>

如果要用檔案作為輸入,可以用<

1
(gdb) start < <data>

quit指令

離開GDB可以用quit指令

1
(gdb) quit

範例

以下面程式為範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//This program calculates and prints out the factorials of 5 and 17


#include <stdio.h>
#include <stdlib.h>

int factorial(int n);

int main(void) {

int n = 5;
int f = factorial(n);
printf("The factorial of %d is %d.\n", n, f);
n = 17;
f = factorial(n);
printf("The factorial of %d is %d.\n", n, f);

return 0;

}
//A factorial is calculated by n! = n * (n - 1) * (n - 2) * ... * 1
//E.g. 5! = 5 * 4 * 3 * 2 * 1 = 120
int factorial(int n) {
int f = 1;
int i = 1;
while (i <= n) {
f = f * i;
i++;
}
return f;
}

用以下指令編譯

1
gcc -Wall -g -o factorial factorial.c

接下來開啟GDB session

1
gdb factorial

在GDB session中執行程式,會發現程式輸出錯誤的值

1
(gdb) run

如果要結束GDB session指令可以用quit。

1
(gdb) quit

中斷點

break指令

如果要放置中斷點在特定檔案的某一行,可以用break <filename>:<line number>指令

1
(gdb) break <filename>:<line number>

你也可以直接指定要放在哪一個function(gdb) break <filename>:<function>

1
(gdb) break <filename>:<function>

列出全部中斷點

要列出全部的中斷點可以用以下指令

1
(gdb) info break

delete指令

如果要刪除中斷點可以先用info break所有中斷點的號碼再刪除

1
(gdb) delete <breakpoint number>

範例

接著前面的例子,我們懷疑程式的第15行有錯誤,因此在GDB session中把中斷點放在第15行並且執行程式

1
2
(gdb) break 15
(gdb) run

我們應該會看到類似以下的輸出,你可以看到GDB輸出我們的第15行程式,代表現在程式停在第15行。
注意!!這時候程式並還沒有執行第15行。GDB告訴我們的是他下一行將要執行的程式。

1
2
3
4
5
6
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
The factorial of 5 is 120.

Breakpoint 1, main () at factorial.c:15
15 f = factorial(n);

查看資料

print指令

印出變數

1
(gdb) print <variable_name>

info locals指令

印出當前所有的local variable,除了輸入函式的參數以外。

1
(gdb) info locals

info args指令

印出輸入給函式的參數

1
(gdb) info args

範例

接續前面的範例,們想查看程式運作到中斷點時變數的值。首先查看f和n的值

1
2
3
4
(gdb) print f
$1 = 120
(gdb) print n
$2 = 17

我們也可以用info locals來查看所有區域變數。

1
2
3
(gdb) info locals
n = 17
f = 120

查看程式運作

當程式在中斷點停下後,利用step, next and continue可以控制程式運作以便我們逐行觀察程式運作的情形。

step指令

執行下一行程式,如果下一行程式是呼叫function,gdb就會進到function裡面。

1
(gdb) step

next指令

執行下一行程式,與step不同的是,如果下一行是function,則將function執行完而不進入function。

1
(gdb) next

continue指令

執行程式直到碰到下一個中斷點

1
(gdb) continue

where指令

印出function call stack

1
(gdb) where

list指令

印出目前所在的行數以及前後各兩行。

1
(gdb) list

範例

接續前面的範例。我們想要查看我們的factorial函式如何運作的。因此用step指令進入factorial函式。

1
2
3
(gdb) step
factorial (n=17) at factorial.c:24
24 int f = 1;

接下來我們想一步一步看看factorial函式如和運作的

1
2
3
4
5
6
7
8
(gdb) next
25 int i = 1;
(gdb) next
26 while (i <= n) {
(gdb) next
27 f = f * i;
(gdb) next
28 i++;

除了不斷輸入重複的指令,你也可以直接按Enter,GDB會重複你上一個指令。
接下來我們預期執行到這裡,if應該要等於1

1
2
3
4
(gdb) print i
$2 = 1
(gdb) print f
$3 = 1

如果我們想查看目前在程式哪一行,可以用where指令來印出call stack

1
2
3
(gdb) where
#0 factorial (n=17) at factorial.c:28
#1 0x0000555555555196 in main () at factorial.c:15

如果要印出目前行數前後的程式可以用list

1
2
3
4
5
6
7
8
(gdb) list
23 int factorial(int n) {
24 int f = 1;
25 int i = 1;
26 while (i <= n) {
27 f = f * i;
28 i++;
29 }

我們也可以用continue指令和中斷點來加速除錯,首先先下一個中斷點在第28行。

1
2
(gdb) break 28
Breakpoint 2 at 0x5555555551e1: file factorial.c, line 28.

接下來用continue指令直接跳到這個中斷點

1
2
3
4
5
(gdb) continue
Continuing.

Breakpoint 2, factorial (n=17) at factorial.c:28
28 i++;

然後依次印出所有區域變數

1
info locals

我們不斷重複這個動作,可以發現前面都還運作正常,直到i=13時,答案開始出出錯了,如果繼續執行會發現答案越來越小,甚至變成負的。這個錯誤原因是int這個資料型態無法儲存這麼大的值,我們必須使用更大的資料型態才能儲存。

Call Stack

call Stack是由stack frames所組成。stack frames是用來儲存呼叫函式的時候函式的區域變數。如果函式內又呼叫另一個函式,新的一個stack frames會被放當前函式的stack frames的上面。當一個函式完成後,就會移除一個stack frames。

where指令

印出call stack並且包含檔名和行數。

up指令

往上移動stack一層frame

1
2
(gdb) up
(gdb) up <n_frames>

down指令

往下移動stack一層frame

1
2
(gdb) down
(gdb) down <n_frames>

frame指令

移動到指定的frame

1
(gdb) frame <frame_number>

範例

如果錯誤出現在函式庫的程式碼,這時候用call stack來debug就會很有用,我們可以利用call stack來尋找我們的程式在什麼地方出錯。用下面範例corrupted_linked_list.c來講解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
//Makes a linked list of length 7 and prints it out
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>


struct node {
int data;
struct node *next;
};

struct node *create_node(int data);
struct node *create_list(int length);
void print_list(struct node *list);

int main(void){
struct node *list1 = create_list(7);
print_list(list1);

return 0;
}

struct node *create_node(int data){
struct node *new = malloc(sizeof(struct node));
assert(new != NULL);
new->data = data;
new->next = NULL;
return new;
}

struct node *create_list(int length) {

struct node *head = NULL;
if (length > 0) {
head = create_node(0);
int i = 1;
struct node *curr = head;
while (i < length) {
curr->next = create_node(i);
curr = curr->next;
i++;
}
}
return head;
}

void print_list(struct node *list){
struct node *curr = list;

while (curr != NULL) {
printf("%d->", curr->data);

curr == curr->next;
}
printf("X\n");
}

{:file=’corrupted_linked_list.c’}

首先我們編譯並且執行程式,我們會發現程式進入無窮迴圈,於是我們強制程式停下來。

1
2
3
4
$ gcc -g -o corrupted_linked_list corrupted_linked_list.c
./corrupted_linked_list
0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->
**ctrl + c**

我們預期程式的輸出應該像下面這樣

1
2
./corrupted_linked_list
0->1->2->3->4->5->6->X

為了要了解程式到底錯在哪裡,我們在GDB session裡面執行程式。並且中斷程式

1
2
3
4
5
6
7
$ gdb corrupted_linked_list
(gdb) run
0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0
**ctrl + c**
Program received signal SIGINT, Interrupt.
0x00007fffff1272c0 in __write_nocancel () at ../sysdeps/unix/syscall-template.S:84
84 ../sysdeps/unix/syscall-template.S: No such file or directory.

中斷後我們可以用where指令看一下目前所在的位置,輸出會類似如下

1
2
3
4
5
6
7
8
9
10
11
12
(gdb) where
#0 0x00007fffff1272c0 in __write_nocancel () at ../sysdeps/unix/syscall-template.S:84
#1 0x00007fffff0a8bff in _IO_new_file_write (f=0x7fffff3f5620 <_IO_2_1_stdout_>, data=0x6020f0, n=512) at fileops.c:1263
#2 0x00007fffff0aa409 in new_do_write (to_do=512,
data=0x6020f0 "0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0-"..., fp=0x7fffff3f5620 <_IO_2_1_stdout_>) at fileops.c:518
#3 _IO_new_do_write (fp=0x7fffff3f5620 <_IO_2_1_stdout_>,
data=0x6020f0 "0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0-"..., to_do=512) at fileops.c:494
#4 0x00007fffff0a947d in _IO_new_file_xsputn (f=0x7fffff3f5620 <_IO_2_1_stdout_>, data=<optimised out>, n=2) at fileops.c:1331
#5 0x00007fffff07d92d in _IO_vfprintf_internal (s=0x7fffff3f5620 <_IO_2_1_stdout_>, format=<optimised out>, ap=ap@entry=0x7ffffffedf08) at vfprintf.c:1663
#6 0x00007fffff085899 in __printf (format=<optimised out>) at printf.c:33
#7 0x000000000040071b in print_list (list=0x602010) at corrupted_linked_list.c:50
#8 0x0000000000400628 in main () at corrupted_linked_list.c:17

可以看到程式被中斷在標準函式庫的程式,不過我們想看一看輸入函式庫的參數是什麼。因此我們可以用up指令從frame 0 移動到 frame 1。或者我們直接用frame指令移動到我們要的地方

1
2
3
(gdb) frame 7
#7 0x000000000040071b in print_list (list=0x602010) at corrupted_linked_list.c:50
50 printf("%d->", curr->data);

首先我們先看一下區域變數

1
2
(gdb) info locals
curr = 0x602010

可以用ptype指令查看curr的型態,可以發現他是一個node struct的指針。

1
2
3
4
5
(gdb) ptype curr
type = struct node {
int data;
struct node *next;
} *

我們dereference查看一下內容

1
2
(gdb) print *curr
$1 = {data = 0, next = 0x602030}

也可以查看其他的內容

1
2
3
4
5
(gdb) print *(curr->next)
$2 = {data = 1, next = 0x602050}
(gdb) print *(curr->next->next)
$3 = {data = 2, next = 0x602070}
(gdb)

Core Dumps

程式當機當下程式的狀態對於除錯十分有幫助,我們可以利用core dump檔來記錄這些狀態。對於一些不定時發生的錯誤,這些除錯資訊就十分珍貴了。

Core Dumps設定

首先查看Core Dumps記錄功能有沒有被開啟,如果回傳0代表沒有打開Core Dumps記錄功能

1
ulimit -c

用以下指令設定打開Core Dumps記錄功能

1
ulimit -c unlimited

通常Core Dumps檔產生的位置會記路在/proc/sys/kernel/core_pattern這個設定,用以下指令查看Core Dumps檔的位置。

產生Core Dump

當Core Dumps紀錄功能打開後,如果程式遇到Segmentation fault的錯誤,就會產生Core Dump檔。

用GDB查看Core Dump檔

要查看Core Dump檔可以用以下指令。注意當我們利用Core Dump檔來除錯的時候,程式實際上並沒有在運作,所以step, nextcontinue 這些指令這時候是沒有功能的。

1
gdb <binary-file> <core-dump-file>

範例

利用下面範例broken_linked_list.c將說明如何使用Core Dump除錯。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
//Makes a linked list of length 7 and prints it out
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>


struct node {
int data;
struct node *next;
};

struct node *create_node(int data);
struct node *create_list(int length);
void print_list(struct node *list, int length);

int main(void){
int length1 = 7;
struct node *list1 = create_list(length1);
print_list(list1, length1);

return 0;
}

struct node *create_node(int data){
struct node *new = malloc(sizeof(struct node));
assert(new != NULL);
new->data = data;
new->next = NULL;
return new;
}

struct node *create_list(int length) {

struct node *head = NULL;
if (length > 0) {
head = create_node(0);
int i = 1;
struct node *curr = head;
while (i < length) {
curr->next = create_node(i);
curr = curr->next;
i++;
}
}
return head;
}

void print_list(struct node *list, int length){
struct node *curr = list;
int i = 0;
while (i <= length) {
printf("%d->", curr->data);
curr = curr->next;
i++;
}
printf("X\n");
}

{: file=’broken_linked_list.c’}
編譯並且執行程式,可以看到程式出現Segmentation fault (core dumped)錯誤

1
2
3
$ gcc -g -o broken_linked_list broken_linked_list.c
$ ./broken_linked_list
Segmentation fault (core dumped)

接下來讀取Core Dump檔

1
gdb broken_linked_list core

GDB將會顯示程式出錯的位置

1
2
3
4
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x000055be9593e283 in print_list (list=0x55be96c20260, length=7)
at broken_linked_list.c:51
51 printf("%d->", curr->data);

從這裡可以知道我們在第51行正在嘗試存取一個非法的記憶體位置。因此我們可以推測可能是curr的位址不正確或者是data是不可以讀取的位址。於是先嘗試印出curr的值

1
2
(gdb) print curr
$1 = (struct node *) 0x0

可以看到curr是一個NULL址針。接下來我們在印出當前狀況的區域變數

1
2
3
(gdb) info locals
curr = 0x0
i = 7

可以看到i是7,也就是現在是第8次執行for迴圈,但是我們的Linking list只有7個節點,而且依照我們的建立Linking list的方式,第8個節點會是NULL址針,所以程式會出錯。
我們在檢查一下Linking list本身是否有問題。

1
2
(gdb) print *list
$2 = {data = 0, next = 0x55be96c20280}

可以看到Linking list的位址沒問題,因此可以更加確定問題就是迴圈多執行了一次。我們可以用list指令看一下程式現在所在的位置。

1
2
3
4
5
6
7
8
9
10
11
(gdb) list
46
47 void print_list(struct node *list, int length){
48 struct node *curr = list;
49 int i = 0;
50 while (i <= length) {
51 printf("%d->", curr->data);
52 curr = curr->next;
53 i++;
54 }
55 printf("X\n");

GDB Init File

如果單純使用GDB的指令,有些變數就會變得難以查看,例如如果想要查看linked list的所有成員就會變得很麻煩。而GDB提供讓使用者自定義指令讓我們可以容易議處想要的結果。

GDB User Initialization File

GEB啟動時會載入User Initialization File所記錄的指令,你可以建立一個新的User Initialization File,他的檔名是.gdbinit,放置的位置是home directory

1
~/.gdbinit

建立之後在檔案內加入下面指令,如此一來就可以在每一個專案下各自建立專屬的initialization file .gdbinit

GDB

專案自己的initialization file位在專案的跟目錄下,使用者可以自訂義指令或是GDB啟動的行為。

1
~/<file_path>/.gdbinit

基礎用法

例如你的專案想要每次使用GDB的時候都會放一個breakpoint在某一個function,你就可以在.gdbinit寫入下面這行。

1
break <function_name>

GDB腳本語法

定義命令

你可以用以下命令定義一個自訂義命令

1
2
3
define <command>
<code>
end

增加命令說明

你可以用以下命令為自訂義命令增加註解

1
2
3
document <command>
<information about the command>
end

如果要查看命令說明可以用以下命令

1
2
(gdb) help <command>
<information about the command>

命令參數

如果需要傳入參數給自訂義命令可以用以下方式

1
(gdb) <command> <arg0> <arg1> <arg2> ...

如果要在指令內使用參數可以用以下方式

1
2
3
4
5
$argc
$arg0
$arg1
$arg2
...

方便的內建變數

可以用以下方式定義

1
$<variable_name>

設定變數

用以下指令設定變數

1
set $<variable_name> = <value_or_expression>

if 聲明

用以下方式可以做if聲明

1
2
3
4
5
if <condition>
<code>
else
<code>
end

while loop

1
2
3
while <condition>
<code>
end

Printing

GDB的print語法跟c十分相似

1
printf "<format string>", <arg0>, <arg1>, ...

使用自訂義命令

使用者自訂義命令的用法跟內建指令的用法相同。

1
(gdb) <command> <arg0> <arg1> <arg2> ...

範例:除錯Linked lists

下面將以除錯Linked list作為範例linked_list.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//Makes a linked list of length 7 and prints it out
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>


struct node {
int data;
struct node *next;
};

struct node *create_node(int data);
struct node *create_list(int length);
void print_list(struct node *list);

int main(void){
struct node *list1 = create_list(7);
print_list(list1);

return 0;
}

struct node *create_node(int data){
struct node *new = malloc(sizeof(struct node));
assert(new != NULL);
new->data = data;
new->next = NULL;
return new;
}

struct node *create_list(int length) {

struct node *head = NULL;
if (length > 0) {
head = create_node(0);
int i = 1;
struct node *curr = head;
while (i < length) {
curr->next = create_node(i);
curr = curr->next;
i++;
}
}
return head;
}

void print_list(struct node *list){
struct node *curr = list;

while (curr != NULL) {
printf("%d->", curr->data);
curr = curr->next;
}
printf("X\n");
}

{:file=’linked_list.c’}

首先先把下面這行加入~/.gdbinit

1
set auto-load safe-path /

接下來撰寫自訂義命令,在專案根目錄也建立一個.gdbinit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
define p_generic_list
set var $n = $arg0
while $n
print *($n)
set var $n = $n->next
end
end

document p_generic_list
p_generic_list LIST_HEAD_POINTER
Print all the fields of the nodes in the linked list pointed to by LIST_HEAD_POINTER. Assumes there is a next field in the struct.
end



define indentby
printf "\n"
set $i_$arg0 = $arg0
while $i_$arg0 > 10
set $i_$arg0 = $i_$arg0 - 1
printf "%c", ' '
end
end

{:file=’.gdbinit’}

在命令裡面,我們建立一個變數來儲存第一個參數($arg0),也就是linked list的指針

1
set var $n = $arg0

接下來印出 linked list的內容。

1
print *($n)

接下來把把linked list的指針指向下一個元素

1
set var $n = $n->next

運行結果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ gcc -g -o linked_list linked_list.c
$ gdb -q ./linked_list
Reading symbols from ./linked_list...
(gdb) br 18
Breakpoint 1 at 0x11c3: file linked_list.c, line 18.
(gdb) r
Starting program: /home/steven/tmp/gcc_practice/linked_list
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Breakpoint 1, main () at linked_list.c:18
18 print_list(list1);
(gdb) p_generic_list list1
$1 = {data = 0, next = 0x5555555592c0}
$2 = {data = 1, next = 0x5555555592e0}
$3 = {data = 2, next = 0x555555559300}
$4 = {data = 3, next = 0x555555559320}
$5 = {data = 4, next = 0x555555559340}
$6 = {data = 5, next = 0x555555559360}
$7 = {data = 6, next = 0x0}
(gdb)

watch and display

watch可以監看一個變數,每當這個變數數值改變的時候就暫停程式。display 則是每當程式停止的時候顯示變數。

watch

使用以下指令設定想要監看的變數

1
(gdb) watch <variable_name>

查看Watchpoints

查看Watchpoints的方式跟查看breakpoints的方式一樣

1
(gdb) info breakpoints

移除Watchpoints

使用以下指令移除Watchpoints

1
(gdb) disable <watchpoint_number>

display

使用以下指令設定display

1
(gdb) display expression

查看所有display

查看所有display

1
(gdb) info display

移除display

用以下指令移除display

1
(gdb) delete display <display_number>

範例

以下將以計算階乘的程式factorial.c來做示範。程式在計算階層

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//This program calculates and prints out the factorials of 5 and 17


#include <stdio.h>
#include <stdlib.h>

int factorial(int n);

int main(void) {

int n = 5;
int f = factorial(n);
printf("The factorial of %d is %d.\n", n, f);
n = 17;
f = factorial(n);
printf("The factorial of %d is %d.\n", n, f);

return 0;

}
//A factorial is calculated by n! = n * (n - 1) * (n - 2) * ... * 1
//E.g. 5! = 5 * 4 * 3 * 2 * 1 = 120
int factorial(int n) {
int f = 1;
int i = 1;
while (i <= n) {
f = f * i;
i++;
}
return f;
}

接著編譯程式並且用gdb讀取

1
2
3
$ gcc -g -o factorial factorial.c
$ gdb factorial
Reading symbols from factorial...done.

首先先設定中斷點,可以看到n=5的時候晟是正常運作,讓嘗試繼續執行讓n=17

1
2
3
4
5
6
7
8
9
10
11
(gdb) br factorial
Breakpoint 1 at 0x11a5: file factorial.c, line 24.
(gdb) r
Starting program: ~/factorial
Breakpoint 1, factorial (n=5) at factorial.c:24
24 int f = 1;
(gdb) c
Continuing.
The factorial of 5 is 120.
Breakpoint 1, factorial (n=17) at factorial.c:24
24 int f = 1;

接下來設定watch和display,我們希望i初始化之後再設定watch和display,

1
2
3
4
(gdb) n
25 int i = 1;
(gdb) n
26 while (i <= n) {

然後設定watch和display

1
2
3
4
(gdb) watch f
Hardware watchpoint 2: f
(gdb) display i
1: i = 1

然後我們就可以觀察程式計算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 1
New value = 2
factorial (n=17) at factorial.c:28
28 i++;
1: i = 2
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 2
New value = 6
factorial (n=17) at factorial.c:28
28 i++;
1: i = 3
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 6
New value = 24
factorial (n=17) at factorial.c:28
28 i++;
1: i = 4
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 24
New value = 120
factorial (n=17) at factorial.c:28
28 i++;
1: i = 5
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 120
New value = 720
factorial (n=17) at factorial.c:28
28 i++;
1: i = 6
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 720
New value = 5040
factorial (n=17) at factorial.c:28
28 i++;
1: i = 7
(gdb) c

Continuing.

Hardware watchpoint 2: f

Old value = 5040
New value = 40320
factorial (n=17) at factorial.c:28
28 i++;
1: i = 8
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 40320
New value = 362880
factorial (n=17) at factorial.c:28
28 i++;
1: i = 9
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 362880
New value = 3628800
factorial (n=17) at factorial.c:28
28 i++;
1: i = 10
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 3628800
New value = 39916800
factorial (n=17) at factorial.c:28
28 i++;
1: i = 11
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 39916800
New value = 479001600
factorial (n=17) at factorial.c:28
28 i++;
1: i = 12
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 479001600
New value = 1932053504
factorial (n=17) at factorial.c:28
28 i++;
1: i = 13

我們可以觀察到當n=13的時候程式就開始出錯。

條件式Breakpoints

利用conditional breakpoints可以讓程式達到特定條件的時候才停下來。

設定條件式Breakpoints的方法

  1. 建立一個中斷點
    如同前面所教的方法先建立一個中斷點。

    1
    2
    (gdb) break <file_name> : <line_number>
    (gdb) break <function_name>
  2. 查看所有中斷點
    下面指令可以查看目前已經設定過的中斷點。

    1
    (gdb) info breakpoints
  3. 設定條件
    首先我們必須要知道中斷點的編號,並且用以下指令設定條件

    1
    (gdb) condition <breakpoint_number> condition
  4. 移除中斷點的停止條件
    如果想要移除中斷點的停止條件,可以用以下指令

    1
    (gdb) condition <breakpoint_number>

範例

下面將繼續沿用階層計算程式factorial.c來示範conditional breakpoints

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//This program calculates and prints out the factorials of 5 and 17


#include <stdio.h>
#include <stdlib.h>

int factorial(int n);

int main(void) {

int n = 5;
int f = factorial(n);
printf("The factorial of %d is %d.\n", n, f);
n = 17;
f = factorial(n);
printf("The factorial of %d is %d.\n", n, f);

return 0;

}
//A factorial is calculated by n! = n * (n - 1) * (n - 2) * ... * 1
//E.g. 5! = 5 * 4 * 3 * 2 * 1 = 120
int factorial(int n) {
int f = 1;
int i = 1;
while (i <= n) {
f = f * i;
i++;
}
return f;
}

{:file=’factorial.c’}

編譯程式並啟動GDB

1
2
3
$ gcc -g -o factorial factorial.c
$ gdb factorial
Reading symbols from factorial...done.

設定conditional breakpoints

我們已經知道程式在i <= 5 之前都正常運作,所以我們不必在確認i <= 5 之前的輸出結果。因此我們設定的條件是 i > 5。

1
2
3
4
5
$ gdb factorial
Reading symbols from factorial...done.
(gdb) br 28
Breakpoint 1 at 0x11bf: file factorial.c, line 28.
(gdb) condition 1 i > 5

開始除錯

接下來就可以執行程式並且觀察不同i之下的輸出變化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
(gdb) r
Starting program: ~/factorial
The factorial of 5 is 120.

Breakpoint 1, factorial (n=17) at factorial.c:28
28 i++;
(gdb) info locals
f = 720
i = 6
(gdb) c
Continuing.

Breakpoint 1, factorial (n=17) at factorial.c:28
28 i++;
(gdb) info locals
f = 5040
i = 7
(gdb) c
Continuing.

Breakpoint 1, factorial (n=17) at factorial.c:28
28 i++;
(gdb) info locals
f = 40320
i = 8
(gdb) c
Continuing.

Breakpoint 1, factorial (n=17) at factorial.c:28
28 i++;
(gdb) info locals
f = 362880
i = 9
(gdb) c
Continuing.

Breakpoint 1, factorial (n=17) at factorial.c:28
28 i++;
(gdb) info locals
f = 3628800
i = 10
(gdb) c
Continuing.

Breakpoint 1, factorial (n=17) at factorial.c:28
28 i++;
(gdb) info locals
f = 39916800
i = 11
(gdb) c
Continuing.

Breakpoint 1, factorial (n=17) at factorial.c:28
28 i++;
(gdb) info locals
f = 479001600
i = 12
(gdb) c
Continuing.

Breakpoint 1, factorial (n=17) at factorial.c:28
28 i++;
(gdb) info locals
f = 1932053504
i = 13
(gdb)

我們可以發現i=13之後數值就開始出現異常了。

除錯shared library

gcc印出所使用到的shared library

1
gcc -Wl,-t your_program.c -o your_program > ld_output.txt

gdb查看被載入的shared library

1
info share

每當有新的shared library被載入的時候就暫停

set stop-on-solib-events 1

https://jasonblog.github.io/note/gdb/li_yong_gdb_jin_xing_shared_library_de_chu_cuo.html

參考:
https://www.cse.unsw.edu.au/~learn/debugging/modules/gdb_basic_use/