본문 바로가기

Engineering

zero-copy frame processing

반응형

GStreamer에서 버퍼를 가로채는 3가지 방식 비교

Pad Probe (gst_pad_add_probe) identity 같은 통과용 엘리먼트나 특정 Pad에 콜백(Probe)을 등록 기존 파이프라인 구조를 해치지 않고 제로카피(In-place) 수정 가능 포맷/해상도 변경(Caps 변경) 없이 동일 규격 픽셀 수정에 최적
appsink + appsrc 브릿지 디코더 출력을 appsink로 뽑아 처리 후 appsrc로 다시 주입 GStreamer 스레드와 완전 분리 가능, 비동기 큐/외부 렌더 루프 연동 용이 버퍼 풀 재할당 및 약간의 파이프라인 오버헤드
GstVideoFilter 커스텀 플러그인 GStreamer 기반의 C/C++ 필터 엘리먼트를 직접 작성 GStreamer 표준 방식, Caps 협상 완벽 지원 GObject C 보일러플레이트 코드가 많음


핵심 동작 및 코드 원리 예시

Gstreamer의 identity 엘리먼트는 버퍼를 수정 없이 그대로 통과시키는 더미 엘리먼트
이 엘리먼트의 sink 패드에 probe를 걸면 버퍼가 통과할 때마다 콜백이 호출됨

// 1. identity 엘리먼트 생성
GstElement* filter = gst_element_factory_make("identity", "pixel-processor");

// 2. sink 패드 가져오기
GstPad* pad = gst_element_get_static_pad(filter, "sink");

// 3. Buffer Probe 등록
gst_pad_add_probe(pad, GST_PAD_PROBE_TYPE_BUFFER, 
    [](GstPad* pad, GstPadProbeInfo* info, gpointer user_data) -> GstPadProbeReturn {
        auto* processor = static_cast<MyProcessor*>(user_data);
        
        // 버퍼 추출
        GstBuffer* buffer = GST_PAD_PROBE_INFO_BUFFER(info);
        if (!buffer) return GST_PAD_PROBE_OK;

        // 중요: In-place 수정을 위해 writable 상태 보장
        buffer = gst_buffer_make_writable(buffer);
        GST_PAD_PROBE_INFO_DATA(info) = buffer;

        // 픽셀 데이터 접근 및 처리 함수 호출
        processor->process_frame(pad, buffer);

        return GST_PAD_PROBE_OK;
    }, 
    this, nullptr
);
gst_object_unref(pad);

-> probe는 return 하지 않으면 버퍼 프레임이 파이프 다음 단계로 진행되지않음

지연과 관련해서 60fps의 경우 각 단계에서 16.6ms를 초과하면 안됨
각 단계라고 하는 것은 스레드로 구분되는 것이므로 queue를 구분점으로하는 각 단계가 16.6ms 넘기면 안됨
따라서 processing 로직의 앞 뒤로 queue를 두어서 최대한 시간 확보 필요

-> queue를 너무 많이 두면 컨텍스트 스위칭 오버헤드와, queue 자체 latency 및 버퍼링으로 전체 지연 발생
텔레오퍼레이션 처럼 실시간 민감도가 높은 작업에서는 지연이 길어지면서 어지러움증 발생 가능

픽셀 메모리에 접근

단순 gst_buffer_map() 사용 시 버퍼에서 stride, padding, plane 등을 전부 직접 계산해야함
따라서 GstVideoInfo + GstVideoFrame 쓰는것이 정석

#include <gst/video/video.h>

void MyProcessor::process_frame(GstPad* pad, GstBuffer* buffer) {
    // 1. 현재 Pad의 Caps(해상도, 포맷 등) 정보 가져오기
    GstCaps* caps = gst_pad_get_current_caps(pad);
    GstVideoInfo vinfo;
    if (!gst_video_info_from_caps(&vinfo, caps)) {
        if (caps) gst_caps_unref(caps);
        return;
    }
    gst_caps_unref(caps);

    // 2. 버퍼를 Video Frame으로 매핑 (Read/Write)
    GstVideoFrame frame;
    if (!gst_video_frame_map(&frame, &vinfo, buffer, GST_MAP_READWRITE)) {
        return;
    }

    // 3. 픽셀 포인터 획득
    // 예: RGBA 포맷인 경우:
    uint8_t* pixels = GST_VIDEO_FRAME_PLANE_DATA(&frame, 0); // 픽셀 시작 주소
    int stride = GST_VIDEO_FRAME_PLANE_STRIDE(&frame, 0);      // 한 줄의 바이트 수 (pitch)
    int width = GST_VIDEO_FRAME_WIDTH(&frame);
    int height = GST_VIDEO_FRAME_HEIGHT(&frame);

    // 4. 커널 실행 (예: CPU 병렬 또는 OpenGL / Custom 처리기 호출)
    // kernel->execute(pixels, width, height, stride);

    // 5. 언맵 필수
    gst_video_frame_unmap(&frame);
}

 

파이프라인 구성 예시

[Decoder] -> [videoconvert] -> [capsfilter (video/x-raw, format=RGBA)] -> [identity (Probe로 픽셀 조작)] -> [Sink]

videoconvert + capsfilter 조합으로 색상 포맷 고정 

 

참고 레퍼런스

  1. GStreamer Pad Probes
  2. GStreamer Video Library (gstvideo)
    • 헤더: <gst/video/video.h>
    • 키워드: GstVideoInfo, gst_video_info_from_caps, GstVideoFrame, gst_video_frame_map
    • 주의점: stride != width * bytes_per_pixel일 수 있으므로(메모리 정렬 패딩), 반복문 돌 때 반드시 stride 단위로 오프셋을 계산해야 왜곡이 생기지 않습니다.
  3. CUDA 대체 병렬 처리 구현체 (향후 커널 작성 시)
    • CPU 단순 병렬:
      • OpenMP (#pragma omp parallel for)
      • C++17 Parallel Algorithms (std::for_each(std::execution::par_unseq, ...))
    • GPU (OpenGL):
      • PBO (Pixel Buffer Object): CPU 메모리(pixels)를 glTexSubImage2D로 GPU에 업로드할 때 비동기 DMA 전송 지원.
      • Compute Shader (OpenGL 4.3+ / GLES 3.1+): CUDA 커널과 거의 동일한 방식으로 픽셀을 병렬 조작 가능.

 

 
반응형