Goal
Bài hướng dẫn này là phần còn lại của các khái niệm cơ bản cần thiết để sử dụng GStreamer, cho phép xây dựng pipeline “nhanh chóng”, khi có sẵn thoomng tin, thay vì có một pipeline nguyên khối được định nghĩa khi bắt dầu ứng dụng của bạn
Sau bài hướng dẫn này, bạn sẽ có được kiến thức cần thiết để bắt đầu Playback tutorial. Các điểm được xem xét ở đây sẽ là:
- Cách để đạt được sự kiểm soát ttoots hơn khi liên kết các phần tử
- Cách để được thông báo về một sự kiện quan tâm để bạn có thể phản ứng kịp thời
- Các trạng thái khác nhau một phần tử có thể.
Introduction
Như bạn sắp thấy, pipeline trong bài hướng dẫn này chưa được xây dựng hoàn chỉnh trước khi nó được đặt trạng thái “đang phát - playing”. Điều này vẫn ổn. Nếu chúng ta không làm thêm gì nữa, dữ liệu sẽ đến cuối pipeline và dừng lại.
Trong ví dụ này, chúng ta đang mở một file được ghép kênh - multiplexed (hoặc trộn lẫn - muxed), nghĩa là, âm thanh và hình ảnh được lưu trữ cùng nhau trong file chứa (container file). Các phần tử chịu trách nhiệm mở các containers được gọi là các bộ giải mã (demuxers), một vài ví dụ về các định dạng của container là Matroska(MKV), Quick Time (QT, MOV), Ogg, hoặc Advanced Systems Format (ASF, WMV, WMA).
Nếu một container nhúng nhiều luồng (ví dụ, 1 video và 2 track âm thanh), demuxer sẽ tách chúng và cho chúng ra thông qua các cổng (port) đầu ra khác nhau. Theo cách này, các nhánh khác nhau có thể được tạo trong pipeline, xử lý các loại dữ liệu khác nhau.
Các cổng (port) mà qua đó các phần tử GStreamer giao tiếp với nhau được gọi là pads (GstPad
). Tồn tại các sink pads, qua đó dữ liệu đi vào một phần tử và các source pads, qua đó dữ liệu thoát ra khỏi một phần tử. Theo lẽ tự nhiên, các phần tử source chỉ chứa các source pads, các phần tử sink chỉ chứa các sink pads, và các phần tử bộ lọc (filter) chứa cả hai.
Hình 1: Các phần tử GStreamer và các pads của chúng.
Một bộ giải mã (demuxer) chứa một sink pad, qua đó dữ liệu hỗn hợp (muxed data) đến , và nhiều source pads, mỗi cái cho mỗi luồng được tìm thấy trong container:
Hình 2: Một demuxer với hai source pads.
Để hoàn thiện hơn, dưới đây là một pipeline được đơn giản hóa chứa một bộ giải mã (demuxer) và hai nhánh, một cho âm thanh và một cho hình ảnh. Đây không phải pipeline sẽ được xây dựng trong bài hướng dẫn này.
Hình 3: Ví dụ pipeline với hai nhánh.
Sự phức tạp chính khi xử lý bộ giải mã là chúng không thể tạo ra bất kỳ thông tin nào cho đến khi chúng nhận được một số dữ liệu và có cơ hội để xem container bên trong có gì. Nghĩa là, các bộ giải mã bắt đầu không có các source pads mà các phần tử khác có thể liên kết đến và do đó pipeline nhất định phải kết thúc tại chúng
Giải pháp là xây dựng pipeline từ source xuống bộ giải mã (demuxer), và đặt nó chạy (play). Khi demuxer nhận đủ thông tin để biết về số lượng và loại luồng trong container, nó sẽ bắt đầu tạo các source pads. Đây là lúc thích hợp để để chúng ta hoàn thành việc xây dựng pipeline và gắn nó vào các demuxer pads mới được thêm vào.
Để đơn giản hóa, trong ví dụ này, chúng ta chỉ liên kết với audio pad và bỏ qua video.
Dynamic Hello World
- Copy đoạn code này vào file tên
basic_tutorial_3.c
#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);
}
- Complie code:
gcc basic_tutorial_3.c -o basic_tutorial_3 `pkg-config --cflags --libs gstreamer-1.0`
- Run:
./basic_tutorial_3
Bài hướng dẫn này chỉ chạy âm thanh. Media được tải xuống từ Internet, vì vậy có thể mất vài giây để bắt đầu phụ thuộc vào tốc độ kết nối của bạn.
Walkthrough
typedef struct _CustomData
{
GstElement *pipeline;
GstElement *source;
GstElement *convert;
GstElement *resample;
GstElement *sink;
} CustomData;
Cho đến nay chúng ta đã giữ tất cả thông tin chúng ta cần (về cơ bản là các con trỏ trỏ tới các GstElement
) dưới dạng các biến cục bộ (local variables). Vì bài hướng dẫn này (và hầu hết các ứng dụng thực tế) liên quan đến các callbacks, chúng ta sẽ nhóm tất cả dữ liệu của chúng ta trong một struct để dễ dàng xử lý.
static void pad_added_handler(GstElement *src, GstPad *new_pad, CustomData *data)
Nó cho phép chuyển tiếp tham chiếu, sẽ được sử dụng sau này.
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");
Chúng ta sẽ tạo các phần tử như bình thường. uridencodebin
sẽ khởi tạo bên trong tất cả các phần tử cần thiết (sources, demuxers, decoders) để biến URI thành luồng âm thanh và/hoặc video. Nó thực hiện một nửa công việc mà playbin
làm. Vì nó chứa các bộ giải mã (demuxer), nên các source pads không có sẵn và chúng ta sẽ cần liên kết với chúng một cách nhanh chóng.
audioconvert
hữu ích khi chuyển đổi giữa các định dạng âm thanh khác nhau, đảm bảo rằng ví dụ này sẽ hoạt động trên mọi nền tảng, vì định dạng do bộ giải mã âm thanh tạo ra có thể không giống với định dạng âm thanh sink mong muốn.
audioresample
hữu ích khi chuyển đổi giữa các tốc độ lấy mẫu âm thanh khác nhau, tương tự, đảm bảo rằng ví dụ này sẽ hoạt động trên mọi nền tảng, vì tốc độ lấy mẫu âm thanh do bộ giải mã tạo ra có thể không phải tốc độ mà phần âm thanh sink hỗ trợ.
autoaudiosink
tương đương với autovideosink
đã thấy trong bài hướng dẫn trước. Nó sẽ render luồng âm thanh tới card âm thanh.
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;
}
Ở đây chúng ta liên kết các phần tử converter, resample và sink, nhưng chúng ta KHÔNG liên kết chúng với source, vì tại thời điểm này nó không chứ source pads. Chúng ta chỉ để nhánh này (converter + sink) không được liên kết, cho đến sau này.
g_object_set(data.source, "uri", "https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm", NULL);
Chúng ta đặt URI của file để phát thông qua một thuộc tính, giống với chúng ta đã làm trong bài hướng dẫn trước.
Signals
g_signal_connect(data.source, "pad-added", G_CALLBACK(pad_added_handler), &data);
GSignals
là một điểm quan trọng trong GStreamer. Chúng cho phép bạn được thông báo (bằng một callback) khi một điều gì đó thú vị xảy ra.
Các tín hiệu (Signals) được xác định bằng một cái tên, và mỗi GObject
có tín hiệu riêng của chúng.
Trong dòng này, chúng ta gắn vào tín hiệu “pad-added” của source (một phần tử uridecodebin
). Để làm như vậy, chúng ta sử dụng g_signal_connect()
và cung cấp một callback function sẽ được sử dụng (pad_added_handed
) và một con trỏ dữ liệu, GStreamer không làm gì với con trỏ dữ liệu này, nó chỉ chuyển tiếp chúng vào callback để chúng ta có thể chia sẻ thông với nó. Trong trường hợp này, chúng ta chuyển một con trỏ tới struct CustomData
mà chúng ta đã xây dựng đặc biệt cho mục đích này.
Bạn có thể tìm thấy các signals mà GstElement
tạo ra trong tài liệu của nó hoặc sử dụng công cụ gst-inspect-1.0
như được mô tả trong Basic tutorial 10: GStreamer tools.
Chúng ta bây giờ sẵn sàng. Chỉ cần đặt cho pipeline trạng thái PLAYING
và bắt đầu listening bus các thông báo thú vị (như ERROR
hoặc EOS
), giống như bài hướng dẫn trước.
The callback
Khi phần tử source cuối cùng cũng có đủ thông tin để bắt đầu tạo ra dữ liệu, nó sẽ tạo source pads và kích hoạt tín hiệu “pad-added”. Tại thời điểm này callback function được gọi:
static void pad_added_handler(GstElement *src, GstPad *new_pad, CustomData *data){
src
là GstElement
đã kích hoạt tín hiệu. Trong ví dụ này, nó có thể chỉ là uridecodebin
, vì nó là tín hiệu đuy nhất mà chúng ta đã gắn vào. Tham số đầu tiên của bộ xử lý tín hiêu luôn là đối tượng đã kích hoạt nó.
new_pad
là GstPad
vừa được thêm vào phần tử src
. Đây thường là pad mà chúng ta muốn liên kết.
data
là con trỏ chúng ta đã cung cấp khi gắn vào tín hiệu. Trong ví dụ này, chúng ta sử dụng nó để truyền vào con trỏ CustomData
.
GstPad *sink_pad = gst_element_get_static_pad(data->convert, "sink");
Từ CustomData
chúng ta trích xuất ra phần tử converter, và truy xuất sink pad của nó bằng cách sử dụng gst_element_get_static_pad ()
. Đây là pad mà chúng ta muốn liên kết new pad
. Trong bài hướng dẫn trước, chúng ta đã liên kết các phần tử với phần tử, và để GStreamer chọn ra các pads thích hợp. Bây giờ, chúng ta sẽ liên kết trực tiếp các pads.
if (gst_pad_is_linked(sink_pad))
{
g_print("We are already linked. Ignoring.\n");
goto exit;
}
uridecodebin
có thể tạo ra bao nhiêu pads đối với mỗi phần tùy ý mà nó cảm thấy phù hợp. Hàm callback sẽ được gọi. Những dòng code này sẽ ngăn chúng cố gắng liên kết với một pad mới thêm lần nữa sau khi chúng đã được liên kết.
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;
}
Bây giờ, chúng ta sẽ kiểm tra loại dữ liệu mà pad mới này mới xuất ra, vì chúng chỉ quan tâm đến các pads tạo ra âm thanh. Trước đây, chúng ta tạo đã tạo các phần của pipeline liên quan đến âm thanh (một audioconvert
liên kết với một audioresample
và một autoaudiosink
), và chúng ta sẽ không thể liên kết chúng với một pad tạo ra hình ảnh chẳng hạn.
gst_pad_get_current_caps()
truy xuất các khả năng - capabilities hiện tại của pad (nghĩa là, loại dữ liệu mà pad đang xuất ra), được gói trong cấu trúc GstCaps
. Tất cả các khả năng khả thi của một pad có thể hỗ trợ đều có thể được truy vấn bằng gst_pad_query_caps()
. Một pad có thể cung cấp nhiều khả năng, và do đó GstCaps
có thể chứa nhiều GstStructure
, mỗi cái đại diện cho một khả năng khác nhau. Cấc khả năng hiện tại trên một pad sẽ luôn có một GstStructure
duy nhất và đại diện cho một định dạng media duy nhất hoặc nếu không có khả năng nào hiện tại thì NULL
sẽ được trả về.
Vì trong trường hợp này, chúng ta biết rằng pad mà chúng ta mong muốn chỉ có một khả năng (âm thanh), chúng ta truy xuất GstStructure
đầu tiên bằng gst_caps_get_structure()
.
Cuối cùng, với gst_structure_get_name()
chúng ta khôi phục tên của cấu trúc chứa mô tả chính về định dạng (thực tế là loại media của nó).
Nếu tên không phải là audio/x-raw
, đây không phải là pad âm thanh, và chúng ta không quan tâm đến nó.
Nếu không, hãy thử liên kết với nó:
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);
}
gst_pad_link()
cố gắng liên kết hai pads. Như trường hợp với gst_element_link()
, liên kết phải được chỉ định từ source tơi sink, và cả hai pads phải được sở hữu của các phần tử và nằm trong cùng một bin (hoặc pipeline).
Và chúng ta đã hoàn thành! Khi một pad đúng loại xuất hiện, nó sẽ được liên kết với phần còn lại của quy trình xử lý âm thanh trong pipeline và quá trình thực thi sẽ tiếp tục cho đến khi ERROR
hoặc EOS
. Tuy nhiên, chúng ta sẽ thu thập thêm một chút nội dung từ hướng dẫn này bằng cách giới thiệu khái nhiệm Trạng thái - State.
GStreamer States
Chúng ta đã nối một chút về các trạng thái - states khi chúng ta nói về playback sẽ không bắt đầu tới khi bạn đưa trạng thái của pipeline thành trạng thái PLAYING
. Các trạng thái còn lại và ý nghĩa của chúng sẽ được giới thiệu ở đây. Có 4 trạng thái trong GStreamer:
Trạng thái | Mô tả |
---|---|
NULL |
trạng thái NULL hoặc trạng thái ban đầu của một phần tử |
READY |
phần tử đã sẵn sàng chuyển sang PAUSED . |
PAUSED |
phần tử bị PAUSED , nó đã sẵn sàng nhận và xử lý dữ liệu. Tuy nhiên phần tử sink chỉ chấp nhận một buffer và sau đó chặn. |
PLAYING |
phần tử đang PLAYING , clock đang chạy và đữ liệu đang truyền đi |
Bạn chỉ có thể chuyển giữa những cái liền kề với nhau, nghĩa là, bạn không thể chuyển từ NULL
tới PLAYING
, bạn phải đi qua các trạng thái trung gian là READY
và PAUSED
. Nếu bạn đặt pipeline thành PLAYING
, qua đó, GStreamer sẽ thực hiện các chuyển đổi trung gian cho bạn.
case GST_MESSAGE_STATE_CHANGED:
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;
Chúng ta đã thêm phần code này để lấy các thông báo của bus về các thay đổi trạng thái và in chúng trên màn hình để giúp bạn hiểu các chuyển đổi. Mỗi phần tử đặt thông báo trên bus về trạng thái hiện tại của nó, vì vậy chúng ta lọc chúng ra và nghe các thông báo đến từ pipeline.
Hầu hết các ứng dụng chỉ cần lo lắng về việc chuyển sang PLAYING
để bắt đầu playback, sau đó sang PAUSED
để thực hiện tạm dừng, và quay trở về NULL
khi thoát chương trình để giải phóng tất cả tài nguyên.
Exercise
Liên kết dynamic pad - pad động theo truyền thống là một chủ đề khó đối với nhiều lập trình viên. Chứng rằng bạn đã thành thạo nó bằng cách khởi tạo một autovideosink
(có thể với một videoconvert
ở phía trước) và liên kết nó với bộ giải mã demuxer khi có pad. Gợi ý: Bạn đã in trên màn hình loại của các video pads
Bây giờ bạn sẽ thấy (và nghe) một bộ phim như trong Basic tutorial 1: Hello world!. Trong bài hướng dẫn đấy bạn sử dụng playbin
, đây là một phần tử tiện dụng tự động xử lý tất cả quá trình giải mã và liên kết pad cho bạn. Hầu hết các hướng dẫn Playback được dành cho playbin
.
nabang1010 solve
#include <gst/gst.h>
typedef struct _CustomData
{
GstElement *pipeline;
GstElement *source;
GstElement *convert;
GstElement *sink;
} CustomData;
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;
gst_init(&argc, &argv);
data.source = gst_element_factory_make("uridecodebin", "source");
data.convert = gst_element_factory_make("videoconvert", "convert");
data.sink = gst_element_factory_make("autovideosink", "sink");
data.pipeline = gst_pipeline_new("test-pipeline");
if (!data.pipeline || !data.source || !data.convert || !data.sink)
{
g_printerr("Not all elements could be created.\n");
return -1;
}
gst_bin_add_many(GST_BIN(data.pipeline), data.source, data.convert, data.sink, NULL);
if (!gst_element_link_many(data.convert, data.sink, NULL))
{
g_printerr("Elements could not be linked.\n");
gst_object_unref(data.pipeline);
return -1;
}
g_object_set(data.source, "uri", "https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm", NULL);
g_signal_connect(data.source, "pad-added", G_CALLBACK(pad_added_handler), &data);
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;
}
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);
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:
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:
g_printerr("Unexpected message received.\n");
break;
}
gst_message_unref(msg);
}
} while (!terminate);
gst_object_unref(bus);
gst_element_set_state(data.pipeline, GST_STATE_NULL);
gst_object_unref(data.pipeline);
return 0;
}
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 (gst_pad_is_linked(sink_pad))
{
g_print("We are already linked. Ignoring.\n");
goto exit;
}
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, "video/x-raw"))
{
g_print("It has type '%s' which is not raw video. Ignoring.\n", new_pad_type);
goto exit;
}
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:
if (new_pad_caps != NULL)
gst_caps_unref(new_pad_caps);
gst_object_unref(sink_pad);
}
Conclusion
Trong bài hướng dẫn này, bạn đã học:
- Cách để được thông báo về các sự kiện sử dụng
GstSignals
- Cách để liên kết các
GstPad
trực tiếp thay vì các phần tử gốc của chúng. - Các trạng thái - states khác nhau của một phần tử GStreamer.
Bạn cũng đã kết hợp các thứ này để xây dựng một pipeline động , không được xác định khi bắt đầu chương trình, nhưng được tạo khi có thông tin về phương tiện.
Bây giờ bạn có thể bắt đầu với bài hướng dẫn cơ bản và học về cách thực hiện tìm kiếm và truy vấn liên quan đến thời gian trong Basic tutorial 4: Time management hoặc chuyển tới Playback tutorials, và hiểu rõ hơn về phần tử playbin