DeepStream 04: deepstream-app

Sample test application 1

Posted by NAB on December 22, 2022

Cách sử dụng các phần tử của DeepStream cho một luồng stream H.264: filesrc -> decode -> nvstreammux -> nvinfer(primary detector) -> nvdsosd -> renderer. Ứng dụng này sử dụng resnet10.caffemodel để phát hiện đối tượng.

deepstream_test1_app.c

#include <gst/gst.h>
#include <glib.h>
#include <stdio.h>
#include <cuda_runtime_api.h>
#include "gstnvdsmeta.h"
#include "nvds_yml_parser.h"

#define MAX_DISPLAY_LEN 64

#define PGIE_CLASS_ID_VEHICLE 0
#define PGIE_CLASS_ID_PERSON 2

/* The muxer output resolution must be set if the input streams will be of
 * different resolution. The muxer will scale all the input frames to this
 * resolution. */
#define MUXER_OUTPUT_WIDTH 1920
#define MUXER_OUTPUT_HEIGHT 1080

/* Muxer batch formation timeout, for e.g. 40 millisec. Should ideally be set
 * based on the fastest source's framerate. */
#define MUXER_BATCH_TIMEOUT_USEC 40000

gint frame_number = 0;
gchar pgie_classes_str[4][32] = {"Vehicle", "TwoWheeler", "Person",
                                 "Roadsign"};

/* osd_sink_pad_buffer_probe  will extract metadata received on OSD sink pad
 * and update params for drawing rectangle, object information etc. */

static GstPadProbeReturn
osd_sink_pad_buffer_probe(GstPad *pad, GstPadProbeInfo *info,
                          gpointer u_data)
{
    GstBuffer *buf = (GstBuffer *)info->data;
    guint num_rects = 0;
    NvDsObjectMeta *obj_meta = NULL;
    guint vehicle_count = 0;
    guint person_count = 0;
    NvDsMetaList *l_frame = NULL;
    NvDsMetaList *l_obj = NULL;
    NvDsDisplayMeta *display_meta = NULL;

    NvDsBatchMeta *batch_meta = gst_buffer_get_nvds_batch_meta(buf);

    for (l_frame = batch_meta->frame_meta_list; l_frame != NULL;
         l_frame = l_frame->next)
    {
        NvDsFrameMeta *frame_meta = (NvDsFrameMeta *)(l_frame->data);
        int offset = 0;
        for (l_obj = frame_meta->obj_meta_list; l_obj != NULL;
             l_obj = l_obj->next)
        {
            obj_meta = (NvDsObjectMeta *)(l_obj->data);
            if (obj_meta->class_id == PGIE_CLASS_ID_VEHICLE)
            {
                vehicle_count++;
                num_rects++;
            }
            if (obj_meta->class_id == PGIE_CLASS_ID_PERSON)
            {
                person_count++;
                num_rects++;
            }
        }
        display_meta = nvds_acquire_display_meta_from_pool(batch_meta);
        NvOSD_TextParams *txt_params = &display_meta->text_params[0];
        display_meta->num_labels = 1;
        txt_params->display_text = g_malloc0(MAX_DISPLAY_LEN);
        offset = snprintf(txt_params->display_text, MAX_DISPLAY_LEN, "Person = %d ", person_count);
        offset = snprintf(txt_params->display_text + offset, MAX_DISPLAY_LEN, "Vehicle = %d ", vehicle_count);

        /* Now set the offsets where the string should appear */
        txt_params->x_offset = 10;
        txt_params->y_offset = 12;

        /* Font , font-color and font-size */
        txt_params->font_params.font_name = "Serif";
        txt_params->font_params.font_size = 10;
        txt_params->font_params.font_color.red = 1.0;
        txt_params->font_params.font_color.green = 1.0;
        txt_params->font_params.font_color.blue = 1.0;
        txt_params->font_params.font_color.alpha = 1.0;

        /* Text background color */
        txt_params->set_bg_clr = 1;
        txt_params->text_bg_clr.red = 0.0;
        txt_params->text_bg_clr.green = 0.0;
        txt_params->text_bg_clr.blue = 0.0;
        txt_params->text_bg_clr.alpha = 1.0;

        nvds_add_display_meta_to_frame(frame_meta, display_meta);
    }

    g_print("Frame Number = %d Number of objects = %d "
            "Vehicle Count = %d Person Count = %d\n",
            frame_number, num_rects, vehicle_count, person_count);
    frame_number++;
    return GST_PAD_PROBE_OK;
}

static gboolean
bus_call(GstBus *bus, GstMessage *msg, gpointer data)
{
    GMainLoop *loop = (GMainLoop *)data;
    switch (GST_MESSAGE_TYPE(msg))
    {
    case GST_MESSAGE_EOS:
        g_print("End of stream\n");
        g_main_loop_quit(loop);
        break;
    case GST_MESSAGE_ERROR:
    {
        gchar *debug;
        GError *error;
        gst_message_parse_error(msg, &error, &debug);
        g_printerr("ERROR from element %s: %s\n",
                   GST_OBJECT_NAME(msg->src), error->message);
        if (debug)
            g_printerr("Error details: %s\n", debug);
        g_free(debug);
        g_error_free(error);
        g_main_loop_quit(loop);
        break;
    }
    default:
        break;
    }
    return TRUE;
}

int main(int argc, char *argv[])
{
    GMainLoop *loop = NULL;
    GstElement *pipeline = NULL, *source = NULL, *h264parser = NULL,
               *decoder = NULL, *streammux = NULL, *sink = NULL, *pgie = NULL, *nvvidconv = NULL,
               *nvosd = NULL;

    GstElement *transform = NULL;
    GstBus *bus = NULL;
    guint bus_watch_id;
    GstPad *osd_sink_pad = NULL;

    int current_device = -1;
    cudaGetDevice(&current_device);
    struct cudaDeviceProp prop;
    cudaGetDeviceProperties(&prop, current_device);
    /* Check input arguments */
    if (argc != 2)
    {
        g_printerr("Usage: %s <yml file>\n", argv[0]);
        g_printerr("OR: %s <H264 filename>\n", argv[0]);
        return -1;
    }

    /* Standard GStreamer initialization */
    gst_init(&argc, &argv);
    loop = g_main_loop_new(NULL, FALSE);

    /* Create gstreamer elements */
    /* Create Pipeline element that will form a connection of other elements */
    pipeline = gst_pipeline_new("dstest1-pipeline");

    /* Source element for reading from the file */
    source = gst_element_factory_make("filesrc", "file-source");

    /* Since the data format in the input file is elementary h264 stream,
     * we need a h264parser */
    h264parser = gst_element_factory_make("h264parse", "h264-parser");

    /* Use nvdec_h264 for hardware accelerated decode on GPU */
    decoder = gst_element_factory_make("nvv4l2decoder", "nvv4l2-decoder");

    /* Create nvstreammux instance to form batches from one or more sources. */
    streammux = gst_element_factory_make("nvstreammux", "stream-muxer");

    if (!pipeline || !streammux)
    {
        g_printerr("One element could not be created. Exiting.\n");
        return -1;
    }

    /* Use nvinfer to run inferencing on decoder's output,
     * behaviour of inferencing is set through config file */
    pgie = gst_element_factory_make("nvinfer", "primary-nvinference-engine");

    /* Use convertor to convert from NV12 to RGBA as required by nvosd */
    nvvidconv = gst_element_factory_make("nvvideoconvert", "nvvideo-converter");

    /* Create OSD to draw on the converted RGBA buffer */
    nvosd = gst_element_factory_make("nvdsosd", "nv-onscreendisplay");

    /* Finally render the osd output */
    if (prop.integrated)
    {
        transform = gst_element_factory_make("nvegltransform", "nvegl-transform");
    }
    sink = gst_element_factory_make("nveglglessink", "nvvideo-renderer");

    if (!source || !h264parser || !decoder || !pgie || !nvvidconv || !nvosd || !sink)
    {
        g_printerr("One element could not be created. Exiting.\n");
        return -1;
    }

    if (!transform && prop.integrated)
    {
        g_printerr("One tegra element could not be created. Exiting.\n");
        return -1;
    }

    /* we set the input filename to the source element */
    g_object_set(G_OBJECT(source), "location", argv[1], NULL);

    if (g_str_has_suffix(argv[1], ".h264"))
    {
        g_object_set(G_OBJECT(source), "location", argv[1], NULL);

        g_object_set(G_OBJECT(streammux), "batch-size", 1, NULL);

        g_object_set(G_OBJECT(streammux), "width", MUXER_OUTPUT_WIDTH, "height",
                     MUXER_OUTPUT_HEIGHT,
                     "batched-push-timeout", MUXER_BATCH_TIMEOUT_USEC, NULL);

        /* Set all the necessary properties of the nvinfer element,
         * the necessary ones are : */
        g_object_set(G_OBJECT(pgie),
                     "config-file-path", "dstest1_pgie_config.txt", NULL);
    }

    if (g_str_has_suffix(argv[1], ".yml") || g_str_has_suffix(argv[1], ".yaml"))
    {

        nvds_parse_file_source(source, argv[1], "source");
        nvds_parse_streammux(streammux, argv[1], "streammux");

        /* Set all the necessary properties of the nvinfer element,
         * the necessary ones are : */
        g_object_set(G_OBJECT(pgie),
                     "config-file-path", "dstest1_pgie_config.yml", NULL);
    }

    /* we add a message handler */
    bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline));
    bus_watch_id = gst_bus_add_watch(bus, bus_call, loop);
    gst_object_unref(bus);

    /* Set up the pipeline */
    /* we add all elements into the pipeline */
    if (prop.integrated)
    {
        gst_bin_add_many(GST_BIN(pipeline),
                         source, h264parser, decoder, streammux, pgie,
                         nvvidconv, nvosd, transform, sink, NULL);
    }
    else
    {
        gst_bin_add_many(GST_BIN(pipeline),
                         source, h264parser, decoder, streammux, pgie,
                         nvvidconv, nvosd, sink, NULL);
    }

    GstPad *sinkpad, *srcpad;
    gchar pad_name_sink[16] = "sink_0";
    gchar pad_name_src[16] = "src";

    sinkpad = gst_element_get_request_pad(streammux, pad_name_sink);
    if (!sinkpad)
    {
        g_printerr("Streammux request sink pad failed. Exiting.\n");
        return -1;
    }

    srcpad = gst_element_get_static_pad(decoder, pad_name_src);
    if (!srcpad)
    {
        g_printerr("Decoder request src pad failed. Exiting.\n");
        return -1;
    }

    if (gst_pad_link(srcpad, sinkpad) != GST_PAD_LINK_OK)
    {
        g_printerr("Failed to link decoder to stream muxer. Exiting.\n");
        return -1;
    }

    gst_object_unref(sinkpad);
    gst_object_unref(srcpad);

    /* we link the elements together */
    /* file-source -> h264-parser -> nvh264-decoder ->
     * nvinfer -> nvvidconv -> nvosd -> video-renderer */

    if (!gst_element_link_many(source, h264parser, decoder, NULL))
    {
        g_printerr("Elements could not be linked: 1. Exiting.\n");
        return -1;
    }

    if (prop.integrated)
    {
        if (!gst_element_link_many(streammux, pgie,
                                   nvvidconv, nvosd, transform, sink, NULL))
        {
            g_printerr("Elements could not be linked: 2. Exiting.\n");
            return -1;
        }
    }
    else
    {
        if (!gst_element_link_many(streammux, pgie,
                                   nvvidconv, nvosd, sink, NULL))
        {
            g_printerr("Elements could not be linked: 2. Exiting.\n");
            return -1;
        }
    }

    /* Lets add probe to get informed of the meta data generated, we add probe to
     * the sink pad of the osd element, since by that time, the buffer would have
     * had got all the metadata. */
    osd_sink_pad = gst_element_get_static_pad(nvosd, "sink");
    if (!osd_sink_pad)
        g_print("Unable to get sink pad\n");
    else
        gst_pad_add_probe(osd_sink_pad, GST_PAD_PROBE_TYPE_BUFFER,
                          osd_sink_pad_buffer_probe, NULL, NULL);
    gst_object_unref(osd_sink_pad);

    /* Set the pipeline to "playing" state */
    g_print("Using file: %s\n", argv[1]);
    gst_element_set_state(pipeline, GST_STATE_PLAYING);

    /* Wait till pipeline encounters an error or EOS */
    g_print("Running...\n");
    g_main_loop_run(loop);

    /* Out of the main loop, clean up nicely */
    g_print("Returned, stopping playback\n");
    gst_element_set_state(pipeline, GST_STATE_NULL);
    g_print("Deleting pipeline\n");
    gst_object_unref(GST_OBJECT(pipeline));
    g_source_remove(bus_watch_id);
    g_main_loop_unref(loop);
    return 0;
}

deepstream_test1_config.yaml

property:
    gpu-id: 0
    net-scale-factor: 0.0039215697906911373
    model-file: ../../../../samples/models/Primary_Detector/resnet10.caffemodel
    proto-file: ../../../../samples/models/Primary_Detector/resnet10.prototxt
    model-engine-file: ../../../../samples/models/Primary_Detector/resnet10.caffemodel_b1_gpu0_int8.engine
    labelfile-path: ../../../../samples/models/Primary_Detector/labels.txt
    int8-calib-file: ../../../../samples/models/Primary_Detector/cal_trt.bin
    force-implicit-batch-dim: 1
    batch-size: 1
    network-mode: 1
    num-detected-classes: 4
    interval: 0
    gie-unique-id: 1
    output-blob-names: conv2d_bbox;conv2d_cov/Sigmoid
    #scaling-filter=0
    #scaling-compute-hw=0
    cluster-mode: 2

class-attrs-all:
    pre-cluster-threshold: 0.2
    topk: 20
    nms-iou-threshold: 0.5

Walkthrough

Extracting MetaData with DeepStream Probe

osd_sink_pad_buffer_probe() là một callback function, chạy mỗi lần có dữ liệu frame mới. Với probe này, chúng ta có thể lấy được nhanh metadata đi vào plugin Gst-nvdsosd, và đếm số lượng object hiện tại. Metadata đã được thu thập mà chúng ta muốn xem xét sẽ được thu thập trong obj_meta:

NvDsObjectMeta *obj_meta = NULL;

Cấu trúc dữ liệu NvDsObjectMetabao gồm một phần tử cho class_id. Nó giống với số thứ tự class được sử dụng trong config file để xác định loại đối tượng:

  • 0: Vehicle
  • 1: Bycicle
  • 2: Person
  • 3: Road signs

Vòng lặp for trong probe kiểm tra giá trị obj_meta->class_id của mỗi object trong frame và đếm chúng nếu cần.

for (l_obj = frame_meta->obj_meta_list; l_obj != NULL; l_obj = l_obj->next){
    obj_meta = (NvDsObjectMeta *)(l_obj->data);
    if (obj_meta->class_id == PGIE_CLASS_ID_VEHICLE){
        vehicle_count++;
        num_rects++;
    }
    if (obj_meta->class_id == PGIE_CLASS_ID_PERSON){
        person_count++;
        num_rects++;
    }
}

Khai báo các biến class_id của class Vehicle và Person.

#define PGIE_CLASS_ID_VEHICLE 0
#define PGIE_CLASS_ID_PERSON 2
static GstPadProbeReturn osd_sink_pad_buffer_probe(GstPad *pad, GstPadProbeInfo *info, gpointer u_data){
    GstBuffer *buf = (GstBuffer *)info->data;
    guint num_rects = 0;
    NvDsObjectMeta *obj_meta = NULL;
    guint vehicle_count = 0;
    guint person_count = 0;
    NvDsMetaList *l_frame = NULL;
    NvDsMetaList *l_obj = NULL;
    NvDsDisplayMeta *display_meta = NULL;

    NvDsBatchMeta *batch_meta = gst_buffer_get_nvds_batch_meta(buf);

    for (l_frame = batch_meta->frame_meta_list; l_frame != NULL; l_frame = l_frame->next)
    {
        NvDsFrameMeta *frame_meta = (NvDsFrameMeta *)(l_frame->data);
        int offset = 0;
        for (l_obj = frame_meta->obj_meta_list; l_obj != NULL; l_obj = l_obj->next){
            obj_meta = (NvDsObjectMeta *)(l_obj->data);
            if (obj_meta->class_id == PGIE_CLASS_ID_VEHICLE){
                vehicle_count++;
                num_rects++;
            }
            if (obj_meta->class_id == PGIE_CLASS_ID_PERSON){
                person_count++;
                num_rects++;
            }
        }

Số lượng mỗi class sau đó được thêm vào display_meta, được sử dụng để hiển thị trên màn hình.

display_meta = nvds_acquire_display_meta_from_pool(batch_meta);

Sau đó được thêm vào frame metadata.

nvds_add_display_meta_to_frame(frame_meta, display_meta);

Setting hiển thị để hiển thị trên màn hình.

NvOSD_TextParams *txt_params = &display_meta->text_params[0];
display_meta->num_labels = 1;
txt_params->display_text = g_malloc0(MAX_DISPLAY_LEN);
offset = snprintf(txt_params->display_text, MAX_DISPLAY_LEN, "Person = %d ", person_count);
offset = snprintf(txt_params->display_text + offset, MAX_DISPLAY_LEN, "Vehicle = %d ", vehicle_count);

Set vị trí hiển thị trên màn hình.

txt_params->x_offset = 10;
txt_params->y_offset = 12;

Set font, màu, và cỡ chữ

txt_params->font_params.font_name = "Serif";
txt_params->font_params.font_size = 10;
txt_params->font_params.font_color.red = 1.0;
txt_params->font_params.font_color.green = 1.0;
txt_params->font_params.font_color.blue = 1.0;
txt_params->font_params.font_color.alpha = 1.0;

Set background cho text.

txt_params->set_bg_clr = 1;
txt_params->text_bg_clr.red = 0.0;
txt_params->text_bg_clr.green = 0.0;
txt_params->text_bg_clr.blue = 0.0;
txt_params->text_bg_clr.alpha = 1.0;

In ra các thông số

g_print("Frame Number = %d Number of objects = %d "
            "Vehicle Count = %d Person Count = %d\n",
            frame_number, num_rects, vehicle_count, person_count);

Build a Basic DeepStream Pipeline

Framework được sử dụng để xây dựng một ứng dụng DeepStream là một GStreamer pipeline chứa một luồng video input stream, một loạt các phần thử hoặc plugins để xử lý luồng và thông tin luồng đầu ra. Mỗi plugin có một đầu vào xác định và được gọi là sink, và đầu ra xác định được gọi là source. Trong pipeline, source pad của một plugin liên kết với sink pad của phần tử tiếp theo. Source bao gồm thông tin được trích xuất từ quá trình xử lý , metadata, có thể được sử dụng để annotation video và những hiểu biết khác về luồng video đầu vào.

Tạo phần tử pipeline sẽ tạo thành kết nối các phần tử khác:

pipeline = gst_pipeline_new("dstest1-pipeline");

Tạo một phần tử source để đọc file video:

source = gst_element_factory_make("filesrc", "file-source");

Vì định dạng dữ liệu trong file luồng đầu vào là luồng H264 nên cần phải có h264parser để phân tích luồng H264:

h264parser = gst_element_factory_make("h264parse", "h264-parser");

Sử dụng nvdec_h264 để tăng tốc phần cứng để giải mã luồng H264 trên GPU:

decoder = gst_element_factory_make("nvv4l2decoder", "nvv4l2-decoder");

Sử dụng nvstreammux để tạo một luồng đầu ra đa luồng từ các luồng đầu vào đơn lẻ. Điều này cho phép DeepStream xử lý nhiều luồng đầu vào đồng thời. nvstreammux cũng có thể được sử dụng để ghép nối các luồng đầu vào khác nhau với nhau. Ở đây, chúng tôi sử dụng nvstreammux để ghép nối các luồng đầu vào H264 thành một luồng đầu ra đa luồng H264.

streammux = gst_element_factory_make("nvstreammux", "stream-muxer");

Sử dụng nvinfer để thực hiện inference trên các luồng đầu vào.

pgie = gst_element_factory_make("nvinfer", "primary-inference-engine");

nvvideoconvert được sử dụng để chuyển đổi từ định dạng NV12 (output của nvinfer) sang định dạng RGBA (định dạng đầu vào của nvdsosd).

nvvidconv = gst_element_factory_make("nvvideoconvert", "convertor");

nvdsosd được sử dụng để thực hiện annotation trên các luồng đầu vào. nvdsosd cũng có thể được sử dụng để thực hiện các thao tác khác như lọc, cắt, chỉnh sửa, v.v.

nvosd = gst_element_factory_make("nvdsosd", "nv-onscreendisplay");

nvegltransform Trên nền tảng Jetson Gst-nveglglessink hoạt động trên các cấu trúc Eglimage. Gst-nvegltranform là bắt buộc để chuyển đổi dữ liệu đến (được bọc trong cấu trúc NVMM) thành một ví dụ eglimage. Trên nền tảng dGPU, Gst-nveglglessink hoạt động trực tiếp trên dữ liệu được bọc trong cấu trúc NVMM.

transform = gst_element_factory_make("nvegltransform", "nvegl-transform");

Gst-nveglglessink được sử dụng để hiển thị các kết quả trên màn hình.

sink = gst_element_factory_make("nveglglessink", "nvvideo-renderer");

Set input file cho phần tử source:

// Set tên file input
g_object_set(G_OBJECT(source), "location", argv[1], NULL);

// Set số batch của streammux
g_object_set(G_OBJECT(streammux), "batch-size", 1, NULL);

// Set độ phân giải của streammux (đầu ra của streammux) 
g_object_set(G_OBJECT(streammux), "width", MUXER_OUTPUT_WIDTH, "height",
             MUXER_OUTPUT_HEIGHT,
             "batched-push-timeout", MUXER_BATCH_TIMEOUT_USEC, NULL);

Set các thông số cho phần tử nvinfer:

// Set đường dẫn tới file config của model
g_object_set(G_OBJECT(pgie),"config-file-path", "dstest1_pgie_config.txt", NULL);

Add các phần tử vào pipeline:

// Nếu là Jetson thì thêm transform vào pipeline
if (prop.integrated)
{
    gst_bin_add_many(GST_BIN(pipeline),
                     source, h264parser, decoder, streammux, pgie,
                     nvvidconv, nvosd, transform, sink, NULL);
}
else
{
    gst_bin_add_many(GST_BIN(pipeline),
                     source, h264parser, decoder, streammux, pgie,
                     nvvidconv, nvosd, sink, NULL);
}

Một sink pad (input) cho phần tử streammux được tạo ra và được liên kết với source pad của phần tử decoder:

GstPad *sinkpad, *srcpad;
gchar pad_name_sink[16] = "sink_0";
gchar pad_name_src[16] = "src";

sinkpad = gst_element_get_request_pad(streammux, pad_name_sink);
if (!sinkpad)
{
    g_printerr("Streammux request sink pad failed. Exiting.\n");
    return -1;
}

srcpad = gst_element_get_static_pad(decoder, pad_name_src);
if (!srcpad)
{
    g_printerr("Decoder request src pad failed. Exiting.\n");
    return -1;
}

if (gst_pad_link(srcpad, sinkpad) != GST_PAD_LINK_OK)
{
    g_printerr("Failed to link decoder to stream muxer. Exiting.\n");
    return -1;
}

Liên kết các phần tử với nhau bằng hàm gst_element_link_many(). Phần đầu của pipeline thông qua decoder được liên kết với nhau và phần còn lại từ streammux trở đi được liên kết với nhau để tạo thành toàn bộ pipeline:

if (!gst_element_link_many(source, h264parser, decoder, NULL))
{
    g_printerr("Elements could not be linked: 1. Exiting.\n");
    return -1;
}
if (prop.integrated)
{
    if (!gst_element_link_many(streammux, pgie,
                               nvvidconv, nvosd, transform, sink, NULL))
    {
        g_printerr("Elements could not be linked: 2. Exiting.\n");
        return -1;
    }
}
else
{
    if (!gst_element_link_many(streammux, pgie,
                               nvvidconv, nvosd, sink, NULL))
    {
        g_printerr("Elements could not be linked: 2. Exiting.\n");
        return -1;
    }
}

Get information of metadata generated

Add probe vào sink pad của phần tử osd (On Screen Display), vì vào thời điểm đố, buffer có tất cả metadata

osd_sink_pad = gst_element_get_static_pad(nvosd, "sink");
if (!osd_sink_pad)
    g_print("Unable to get sink pad\n");
else
    gst_pad_add_probe(osd_sink_pad, GST_PAD_PROBE_TYPE_BUFFER,
                      osd_sink_pad_buffer_probe, NULL, NULL);

Run pipeline

Set trạng thái pipeline thành PLAYING

// Bắt đầu pipeline
gst_element_set_state(pipeline, GST_STATE_PLAYING);

Đợi tới khi pipeline gặp ERROR hoặc EOS (End Of Stream)

g_main_loop_run(loop);

Clean up

// Dừng pipeline
gst_element_set_state(pipeline, GST_STATE_NULL);

// Xóa các phần tử
gst_object_unref(GST_OBJECT(pipeline));
g_source_remove(bus_watch_id);
g_main_loop_unref(loop);

References