iT邦幫忙

2026 iThome 鐵人賽

DAY 26
0

我們有提到過GstElement有NULL、READY、PAUSED、PLAY四種狀態,NULL跟READY屬於停止狀態,PAUSED、PLAY屬於開始狀態。在這個狀態轉換的過程中會有對應的資源要分配及釋放,今天就是要來完成這個狀態轉換的資源分配與釋放

新增幾個內部要用的欄位

昨天只教大家新增屬性,今天則是需要分配串流的資源,我們需要的是一個cudaStream_t,因為在串流時進來的GstBuffer是NvBufSurface,所以我們可以先對每個影格呼叫修改顏色的CUDA kernel,因為kernel是可以非同步運算的,呼叫完再透過cudaStream_t等待所有kernel運算完成

所以我們去gstironmancolormagic.h新增欄位

/* some includes */
#include <cuda_runtime_api.h>

/* some codes */

struct _GstIronmanColorMagic
{
    GstBaseTransform base_ironmancolormagic;
    guint operate_on_gie;
    guint operate_on_class;
    guint gpu_id;

    /* private */
    cudaStream_t stream;
}

因為用了CUDA,所以include路徑跟link路徑都要修改,替Makefile加上CUDA的include跟lib路徑

TARGET := libgstironmancolormagic.so
CC := gcc

CUDA_DIR := /usr/local/cuda
PKGS := gstreamer-1.0 gstreamer-base-1.0

CFLAGS := -Wall -Wextra -fPIC $(shell pkg-config --cflags $(PKGS)) \
    -I$(CUDA_DIR)/include
LDFLAGS := -shared $(shell pkg-config --libs $(PKGS)) \
    -L$(CUDA_DIR)/lib64 -lcudart \
    -Wl,-rpath,$(CUDA_DIR)/lib64

SRCS := gstironmancolormagic.c
OBJS := $(SRCS:.c=.o)

all: $(TARGET)

$(TARGET): $(OBJS)
    $(CC) -o $@ $^ $(LDFLAGS)

%.o: %.c
    $(CC) $(CFLAGS) -c $< -o $@

clean:
    rm -f $(OBJS) $(TARGET)

.PHONY: all clean

start

模板有一個函式是gst_ironman_color_magic_start,這個函式會在READY轉PAUSED時被呼叫,我們要在這邊初始化串流需要的資源,本次範例的串流資源就是stream

static gboolean
gst_ironman_color_magic_start (GstBaseTransform * trans)
{
    GstIronmanColorMagic* self = GST_IRONMAN_COLOR_MAGIC (trans);

    /* Set device before allocating resources on the GPU */
    {
        cudaError_t cuda_err;

        cuda_err = cudaSetDevice((int)self->gpu_id);
        if (cuda_err != cudaSuccess) {
            GST_ELEMENT_ERROR(self, RESOURCE, SETTINGS,
                ("Failed to set the GPU device"),
                ("cudaSetDevice failed: %s", cudaGetErrorString(cuda_err)));
            return FALSE;
        }

        cuda_err = cudaStreamCreateWithFlags(&self->stream, cudaStreamNonBlocking);
        if (cuda_err != cudaSuccess) {
            GST_ELEMENT_ERROR(self, RESOURCE, FAILED,
                ("Failed to create a cuda stream"),
                ("cudaStreamCreateWithFlags failed: %s", cudaGetErrorString(cuda_err)));
            return FALSE;
        }
    }

    return TRUE;
}

使用CUDA分配/釋放任何資源時都必須呼叫先cudaSetDevice切換裝置,即使你的GPU只有一個也一樣,第一次呼叫會讓CUDA在API內部分配必要的資源。接下來就是分配cudaStream_t,用的是cudaStreamCreateWithFlags,用這個原因是要帶入cudaStreamNonBlocking,讓stream不會隱式的和預設stream(NULL)進行同步,就是不會在我們控制的範圍外突然卡住

CUDA API的官方文件: Device、Stream

stop

模板有一個函式是gst_ironman_color_magic_stop,這個函式則是在PAUSED轉READY時被呼叫,我們要在這邊釋放串流需要的資源

gboolean
gst_ironman_color_magic_stop (GstBaseTransform * trans)
{
    GstIronmanColorMagic* self = GST_IRONMAN_COLOR_MAGIC (trans);

    if (self->stream) {
        cudaError_t cuda_err;

        /* Should not failed here but we can still treat errors as warning */

        cuda_err = cudaSetDevice(self->gpu_id);
        if (cuda_err != cudaSuccess) {
            GST_ELEMENT_WARNING(self, RESOURCE, SETTINGS,
                ("Failed to set the GPU device"),
                ("cudaSetDevice failed: %s", cudaGetErrorString(cuda_err)));
        }

        cuda_err = cudaStreamDestroy(self->stream);
        if (cuda_err != cudaSuccess) {
            GST_ELEMENT_WARNING(self, RESOURCE, CLOSE,
                ("Failed to destroy the cuda stream"),
                ("cudaStreamDestroy failed: %s", cudaGetErrorString(cuda_err)));
        }

        /* The element may change to PAUSED state again, it is more safe to set 'stream' to NULL */
        self->stream = NULL;
    }

    return TRUE;
}

理論上這邊CUDA API不應該出錯,我選擇攔截錯誤並以警告的方式來處理。同樣地記得在使用任何GPU資源前要呼叫cudaSetDevice

修改Pad Capabilities

我們先到gstironmancolormagic.c的上半部新增#include <gst/video/video.h>

#include <gst/gst.h>
#include <gst/base/gstbasetransform.h>
#include <gst/video/video.h>
#include "gstironmancolormagic.h"

然後往下一些,修改Pad Templates,我們讓Src Pad和Sink Pad都只能接受video/x-raw(memory:NVMM),format=RGBA(支援NV12是在折磨自己)

/* pad templates */

#define FEATURE_NVMM "memory:NVMM"

static GstStaticPadTemplate gst_ironman_color_magic_src_template =
GST_STATIC_PAD_TEMPLATE ("src",
    GST_PAD_SRC,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE_WITH_FEATURES(FEATURE_NVMM, "{RGBA}"))
    );

static GstStaticPadTemplate gst_ironman_color_magic_sink_template =
GST_STATIC_PAD_TEMPLATE ("sink",
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE_WITH_FEATURES(FEATURE_NVMM, "{RGBA}"))
    );

GST_VIDEO_CAPS_MAKE_WITH_FEATURES是一個高階macro,用它來描述Caps會比較簡單

#define GST_VIDEO_CAPS_MAKE_WITH_FEATURES(features,format)              \
    "video/x-raw(" features "), "                                       \
    "format = (string) " format ", "                                    \
    "width = " GST_VIDEO_SIZE_RANGE ", "                                \
    "height = " GST_VIDEO_SIZE_RANGE ", "                               \
    "framerate = " GST_VIDEO_FPS_RANGE

完成之後再用gst-inspect-1.0看一次

Pad Templates:
  SINK template: 'sink'
    Availability: Always
    Capabilities:
      video/x-raw(memory:NVMM)
                 format: { (string)RGBA }
                  width: [ 1, 2147483647 ]
                 height: [ 1, 2147483647 ]
              framerate: [ 0/1, 2147483647/1 ]

  SRC template: 'src'
    Availability: Always
    Capabilities:
      video/x-raw(memory:NVMM)
                 format: { (string)RGBA }
                  width: [ 1, 2147483647 ]
                 height: [ 1, 2147483647 ]
              framerate: [ 0/1, 2147483647/1 ]

登愣!是不是有點樣子了呢

結語

我們示範的目標非常簡單,所以只要覆寫start和stop兩個virtual function就已經很夠用了。最後再補充set_caps這個virtual function,它會在start之後、Caps協商完畢後被呼叫,如果資源分配必須知道最終的Caps才能決定,例如color format、width、height,那麼你就必須實作set_caps


上一篇
[Day 25] 替你的元件新增屬性
下一篇
[Day 27] 元件的核心-Transform
系列文
深入認識DeepStream,不只是停在執行範例 共 29 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言