GStreamer 04: Basic tutorial 2

GStreamer concept

Posted by NAB on December 13, 2022

Goal

Bài hướng dẫn trước cho thấy cách xây dựng pipeline một cách tự động. Bây giờ, chúng ta sẽ xây dựng một pipeline theo cách thủ công bằng cách khởi tạo từng phần tử và liên kết chúng lại với nhau. Trong phần này chúng ta sẽ học:

  • Một phần tử của GStreamer là gì và cách tạo.
  • Cách để liên kết từng phần tử với nhau
  • Cách để tùy chỉnh hoạt động của một phần tử
  • Cách để xem bus để biết tình trạng lỗi và trích xuất thông tin từ thông báo GStreamer.

Manual Hello World

Copy code này vào file có tên basic_tutorial_2.c

#include <gst/gst.h>

int 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;
}

Complie code với câu lệnh

gcc basic_tutorial_2.c -o basic_tutorial_2 `pkg-config --cflags --libs gstreamer-1.0`

Run

./basic_tutorial_2

Walkthrough

Các phần tử là các khối xây cấu thành cơ bản của GStreamer. Chúng xử lý dữ liệu khi chúng chảy xuôi downstream từ phần tử source (data producers) đến phần tử sink (data consumers), đi qua các phần tử bộ lọc (filter elements).

Ví dụ về pipeline

Hình 1: Ví dụ về pipeline

Element creation

Chúng ta sẽ bỏ qua phần khởi tạo GStreamer, vì nó giống với bài hướng dẫn trước.

source = gst_element_factory_make("videotestsrc", "source");
sink = gst_element_factory_make("autovideosink", "sink");

Như đã thấy trong đoạn code này, các phần tử mới có thể được tạo bằng gst_element_factory_make(). Tham số đầu tiên là loại phần tử cần tạo (Basic tutorial 14: Handy elements nêu một vài loại phổ biến và các Basic tutorial 10: GStreamer tools chỉ cách để lấy danh sách các loại có sẵn). Tham số thứ hai là tên mà chúng ta muốn đặt cho các trường hợp cụ thể này. Đặt tên cho các phần tử của bạn rất hứu ích để truy xuất chúng sau này nếu bạn không giữ một con trỏ (và để output debug có ý nghĩa hơn). Nếu bạn truyền NULL cho tên, tuy nhiên, GStreamer sẽ cũng cấp một tên duy nhất cho bạn.

Trong bài hướng dẫn này, chúng ta tạo hai phần tử: một videotestsrcvà một autovideosink Không có phần tử bộ lọc, Do đó, pipeline sẽ trông giống như sau:

Pipeline được xây dựng trong bài hướng dẫn

Hình 2: Pipeline được xây dựng trong bài hướng dẫn

videotestsrc là phần tử source (nó tạo ra dữ liệu), tao ra một mẫu video thử nghiệm. Phần tử này hữu ích cho mục đích gỡ lỗi (và các bài hướng dẫn) và thường không được tìm thấy trong các ứng dụng.

autovideosink là phần tử sink (nó tiêu thụ dữ liệu), hiển thị trên một cửa sổ hình ảnh mà nó nhận được. Tồn tại một số video sink, tùy thuộc vào hệ điều hành, với một loại khả năng đa dạng. autovideosinktự động lựa chọn và khởi tạo tốt nhất, vì vậy bạn không phải lo lắng với chi tiết và code của bạn độc lập với nền tảng hơn.

Pipeline creation

pipeline = gst_pipeline_new("test-pipeline");

Tất cả phần tử trong GStreamer phải được chứa bên trong một pipeline trước khi chúng có thể được sử dụng, vì chúng đảm nhận một số chức nhăng như bấm giờ và thông báo. Chúng ta tạo pipeline bằng gst_pipeline_new().

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;
}

Một pipeline là một loại bin - Gstbin cụ thể, là phần tử được sử dụng để chứ các phần tử khác. Do đó tất cả các phương thức (method) áp dụng cho các bin cũng áp dụng cho các pipeline.

GObject
    ╰──GInitiallyUnowned
        ╰──GstObject
            ╰──GstElement
                ╰──GstBin
                    ╰──GstPipeline

Trong trường hợp này, chúng ta gọi gst_bin_add_many() để thêm các phần tử vào pipeline (lưu ý đến việc truyền). Hàm này chấp nhận một danh sách các phần tử sẽ được thêm vào, kết thúc bằng NULL. Các phần tử riêng lẻ có thể được thêm vào bằng gst_bin_add().

Tuy nhiên, những phần tử này vẫn chưa được liên kết với nhau. Đối với điều này, chúng ta cần sử dụng gst_element_link(). Tham số đầu tiên của nó là nguồn và tham số thứ hai của nó là đích. Thứ tự được tính, bởi vì các liên kết phải được thiết lập theo luồng dữ liệu (nghĩa là phần tử source đến phần tử sink). Hãy nhớ rằng chỉ các phần tử nằm trong cùng một bin mới có thể liên kết với nhau, vì vậy hãy nhớ thêm chúng vào pipeline trước khi thử liên kết chúng!

Properties

Các phần tử GstElement GStreamer đều là một loại GObject cụ thể, là thực thể cung cấp các cơ sở thuộc tính.

Hầu hết các phần tử GStreamer có các thuộc tính có thể tùy chỉnh được: các thuộc tính được đặt tên có thể được sửa đổi để thay đổi hành động của phần tử (thuộc tính có thể ghi) hoặc được hỏi để tìm hiểu trạng thái bên trong của phần tử (thuộc tính có thể đọc được).

Thuộc tính được đọc bằng g_object_get() và được ghi bằng g_object_set().

g_object_get() chấp nhận danh sách các cặp tên thuộc tính, giá trị thuộc tính, thuộc tính kết thúc bằng NULL, vì vậy có thể thay đổi nhiều thuộc tính trong một lần.

Đó là lí do vì sao các phương thức xử lý thuộc tính có tiền tố _g.

Quay trở lại những gì có trong ví dụ ở trên,

g_object_set (source, "pattern", 0, NULL);

Dòng code ở trên thay đổi thuộc tính “pattern” của videotestsrc,thuộc tính kiểm soát loại video thử nghiệm mà phần tử xuất ra. Hãy thử các giá trị khác nhau!

Các tên và giá trị có thể có của tất cả các thuộc tính mà một phần tử có thể được tìm thấy sử dụng công cụ gst-inspect-1.0 được mô tả trong Basic tutorial 10: GStreamer tools hoặc cách khác là trong tài liệu dành cho phần tử đó ở đây là trường hợp của videotestsrc.

Error checking

Tại thời điểm này, chúng tã đã có toàn bộ pipeline đã xây dựng và cài đặt, và trong phần còn lại của bài hướng dẫn rất rống với bài trước, nhưng chúng ta sẽ bổ sung thêm kiểm tra lỗi:

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;
}

Chúng tôi gọi gst_element_set_state(), nhưng bây giờ chúng tôi kiểm tra giá trị trả về của nó để kiểm tra lỗi. Thay đổi các trạng thái là một quá trình phức tạp và một số chi tiết khác được cung cấp trong Basic tutorial 3: Dynamic pipelines.

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);
}

gst_bus_timed_pop_filtered() đợi cho thực thi kết thúc và trả về một GstMessage mà chúng ta đã bỏ qua trước đó. Chúng ta đã yêu cầu gst_bus_timed_pop_filtered() trả về khi GStreamer gặp tình trạng lỗi hoặc EOS, do đó chúng tôi cần kiểm tra xem cái nào đã xảy ra và in một thông báo trên màn hình(Ứng dụng của bạn có thể sẽ muốn thực hiện hành đọng phức tap hơn).

GstMessage là một cấu trúc rất linh hoạt có thể cung cấp hầu như bất kỳ loại thông tin nào. May mắn thay, GStreamer cung cấp một loạt chức năng phân tích cú pháp cho từng loại thông báo.

Trong trường hợp này, khi chúng ta biết thông báo có một lỗi (bằng việc sử dụng GST_MESSAGE_TYPE()), chúng ta có thể sử dụng gst_message_parse_error() trả về một cấu trúc lỗi GLib GError và một chuỗi string hữu ích để debug. Kiểm tra code để xem cách chúng được sử dụng và giải phóng sau đó.

The GStreamer bus

Tại thời điểm này, sẽ là tốt nhất để giới thiệu chính thức về GStreamer bus thêm một chút. Nó là đối tuoenjg chịu trách nhiệm gửi tới ứng dụng GstMessages được tạo bởi các phần tử, theo thứ tự và tới luồng ứng dụng. Điểm cuối cùng này rất quan trọng, bởi vì luồng streaming phương tiện được thực hiện trong một luồng khác ngoài ứng dụng.

Các thông báo có thể được trích xuất từ bus một cách đồng bộ với gst_bus_timed_pop_filtered() và các phần tử tương tự của nó, hoặc không đồng bộ, bằng cách sử dụng các tín hiệu (trong bài hướng dẫn tiếp theo). Ứng dụng của bạn phải luôn theo dõi bus để được thông báo về lỗi và các sự cố khác liên quan đến playback.

Phần còn lại của code là trình tự dọn dẹp tương tự như Basic tutorial 1: Hello world!

Excercise

Nếu bạn cảm thấy muốn luyện tập, hãy thử bài tập này:

  • Thêm phần tử bộ lọc video vào giữa source và sink của pipeline này. Dùng vertigotv để có hiệu ứng đẹp. Bạn sẽ cần tạo nó, thêm nó vào pipeline, và liên kết nó với các phần tử khác.

Tùy thuộc vào nền tảng và các plug-in có sẵn, bạn có thể gặp lỗi “negotiation”, vì phần sink không hiểu bộ lọc tạo ra cái gì (chi tiết hơn về negotiation trong Basic tutorial 6: Media formats and Pad Capabilities). Trong trường hợp này, hãy thử thêm một phần tử gọi là videoconvert sau filter (đây là xây dựng một pipeline gồm 4 phần tử . Chi tiết hơn về videoconvert trong Basic tutorial 14: Handy elements).

nabang1010 solve

#include <gst/gst.h>

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

    gst_init(&argc, &argv);

    source = gst_element_factory_make("videotestsrc", "source");
    filter = gst_element_factory_make("vertigotv", "filter");
    videoconvert = gst_element_factory_make("videoconvert", "videoconvert");
    sink = gst_element_factory_make("autovideosink", "sink");

    pipeline = gst_pipeline_new("test-pipeline");

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

    gst_bin_add_many(GST_BIN(pipeline), source, filter, videoconvert, sink, NULL);
    if (gst_element_link_many(source, filter, videoconvert, sink, NULL) != TRUE)
    {
        g_printerr("Elements could not be linked.\n");
        gst_object_unref(pipeline);
        return -1;
    }

    g_object_set(source, "pattern", 0, NULL);

    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;
    }

    bus = gst_element_get_bus(pipeline);
    msg = gst_bus_timed_pop_filtered(bus, GST_CLOCK_TIME_NONE, 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);
            break;
        case GST_MESSAGE_EOS:
            g_print("End-Of-Stream reached.\n");
            break;
        default:
            g_printerr("Unexpected message received.\n");
            break;
        }
        gst_message_unref(msg);
    }

    gst_object_unref(bus);
    gst_element_set_state(pipeline, GST_STATE_NULL);
    gst_object_unref(pipeline);
    return 0;
}

Conclusion

Bài hướng dẫn này dạy:

Bài này là phần đầu tiên trong số hai hướng dẫn dành cho các khái niệm cơ bản về GStreamer.


References