본문 바로가기
잡(job)기술

gst-rtsp-server 시그널 이해하기: media-configure와 media-constructed

by 무니이구나 2025. 5. 16.

GStreamer의 gst-rtsp-server 라이브러리를 사용하여 RTSP 서버를 개발할 때, 클라이언트 요청에 따라 미디어 스트림이 동적으로 생성되고 파이프라인이 구성되는 경우가 많다. 이때 GstRTSPMedia 객체는 클라이언트에게 제공될 미디어 파이프라인을 관리하며, 파이프라인의 특정 부분이 동적으로 생성될 때 media-configure 및 media-constructed 시그널을 발생시킨다.

이 시그널들은 특히 GstRTSPMediaFactory를 사용하여 파이프라인을 생성할 때 유용하며, 사용자가 동적으로 생성되는 파이프라인 부분에 개입하여 추가적인 엘리먼트를 연결하거나 설정을 변경할 수 있도록 한다.

1. media-configure 시그널 (GstRTSPMedia)

발생 시점:

클라이언트로부터 RTSP DESCRIBE 또는 SETUP 요청이 들어와 GstRTSPMedia 객체가 해당 스트림에 대한 정보를 파악하고, 스트림을 처리하기 위한 내부 파이프라인 부분(일반적으로 GstBin 형태)을 생성하기 바로 직전에 발생한다.
이 시그널은 GstRTSPMedia 객체 자체에서 발생하며, 시그널 핸들러는 해당 스트림의 초기 GstCaps 정보(미디어 타입, 해상도, 코덱 등)를 같이 인자로 받는다.

적합한 작업:

이 시점에서는 아직 실제 미디어 처리 엘리먼트가 만들어지기 전이다. 따라서 다음과 같은 작업을 수행할 수 있다.

  • 스트림 Caps 확인: 클라이언트가 요청한 스트림의 미디어 타입, 코덱, 해상도 등의 Caps 정보를 확인한다.
  • 조건에 맞춰 처리: 특정 Caps를 가진 스트림에 대해서만 특정 처리를 수행하거나, 해당 스트림을 거부하는 등의 로직을 구현할 수 있다.
  • 향후 생성될 엘리먼트 설정 준비: GstRTSPMedia의 특정 속성을 설정하여 이후에 생성될 내부 미디어 엘리먼트의 동작에 영향을 줄 수 있다. (이는 GstRTSPMedia 구현에 따라 다를 수 있다.)
  • 주의할 점: 실제 엘리먼트나 패드가 아직 없으므로, 파이프라인에 엘리먼트를 새로 추가하거나 패드를 연결하는 작업은 불가능하다.

2. media-constructed 시그널 (GstRTSPMedia)

발생 시점:

media-configure 시그널 발생 후, GstRTSPMedia가 새 스트림 처리할 내부 미디어 엘리먼트(GstBin 같은 형태)를 성공적으로 생성하고 파이프라인에 추가한 바로 직후에 발생한다.
이 시그널 역시 GstRTSPMedia 객체에서 발생하며, 시그널 핸들러는 새로 생성된 내부 미디어 엘리먼트 (GstBin) 자체의 참조를 인자로 받는다.

적합한 작업:

이 시점에는 실제 미디어 처리를 담당할 GstBin이 만들어져 파이프라인의 일부가 된 상태이다. 따라서 다음과 같은 중요한 동적 파이프라인 구성 작업을 수행할 수 있다.

  • 내부 미디어 엘리먼트 접근: 시그널 인자로 받은 GstBin에 접근하여 해당 Bin 안에 비디오 필터, 오디오 변환, OSD 같은 GStreamer 엘리먼트를 동적으로 생성하여 추가할 수 있다.
  • 패드 연결 (Linking): 새로 만들어진 내부 미디어 엘리먼트 (GstBin)의 특정 패드(주로 Sink Pad)와 사용자가 미리 준비했거나 파이프라인 다른 곳에 존재하는 엘리먼트의 패드를 연결(Link)한다. gst_element_link_pads나 gst_pad_link 함수를 사용하여 수행한다.
  • 엘리먼트 상태 변경: 새로 추가되거나 연결된 엘리먼트들의 상태를 GST_STATE_PLAYING 등으로 변경하여 데이터가 흐를 준비를 시킨다.
  • 핵심: media-constructed 시그널은 gst-rtsp-server가 만든 동적 파이프라인 부분에 사용자의 커스텀 로직이나 외부 소스/싱크 엘리먼트를 연결할 수 있는 가장 중요한 타이밍이다.

3. 구체적인 사례: appsink에서 데이터를 받아서 gst-rtsp-server 미디어 엘리먼트로 보내는 경우

사용자의 애플리케이션에서 이미 appsink를 통해 미디어 데이터를 생성하고 있다고 가정해 본다. 이 데이터를 gst-rtsp-server가 클라이언트에게 스트리밍하기 위해 생성한 동적 미디어 파이프라인 부분으로 보내고 싶다. 이때 appsink는 데이터의 Source 역할을 한다.

이럴 때는 appsink의 Source Pad와 gst-rtsp-server가 media-constructed 시그널을 통해 제공하는 내부 미디어 엘리먼트 (GstBin)의 Sink Pad를 연결해야 한다.

연결하는 타이밍과 과정:

  • GstRTSPMediaFactory 설정: gst-rtsp-server에서 GstRTSPMediaFactory를 생성하고, RTSP 요청이 들어왔을 때 생성될 파이프라인의 기본 템플릿을 정해준다. 이 템플릿은 클라이언트 요청에 따라 동적으로 확장될 부분(디코딩, 디먹싱 같은 것)을 포함하게 된다.

  • appsink 엘리먼트 준비: 사용자의 애플리케이션에서 데이터를 생성하여 공급할 GstElement인 appsink를 미리 만들어 둔다. 이 appsink는 gst-rtsp-server 파이프라인과는 별개로 사용자의 로직에 의해 관리된다.

  • media-constructed 시그널 핸들러 등록: GstRTSPMediaFactory나 생성된 GstRTSPMedia 객체에 media-constructed 시그널이 발생했을 때 호출될 콜백 함수를 등록한다.

// GstRTSPMediaFactory에 시그널 핸들러 등록 예시
g_signal_connect(factory, "media-constructed", G_CALLBACK(on_media_constructed), appsink_element); // appsink 엘리먼트 참조를 user_data로 전달
  • RTSP 클라이언트 요청 발생 및 시그널 발생: RTSP 클라이언트가 서버에 접속하여 스트림을 요청하면, gst-rtsp-server는 해당 스트림에 대한 GstRTSPMedia 객체를 생성하고, media-constructed 시그널을 발생시킨다. 등록해 둔 콜백 함수 on_media_constructed가 호출된다.

  • 핸들러 내에서 패드 연결: on_media_constructed 콜백 함수 안에서 다음을 수행한다.

시그널 인자로 전달받은 새로 생성된 내부 미디어 엘리먼트 (GstBin)의 참조를 가져온다.

이 GstBin 안에서 데이터를 받을 Sink Pad를 찾는다. 이 패드는 보통 sink 패드이거나, 특정 미디어 타입 요청 패드일 수 있다. 정확한 패드 이름은 gst-rtsp-server가 만드는 내부 파이프라인 구조에 따라 다르므로, 디버깅하거나 문서를 확인해야 한다.

user_data로 전달받은 미리 만들어 둔 appsink 엘리먼트의 참조를 가져온다.

appsink 엘리먼트에서 데이터를 내보내는 Source Pad (src 패드)를 찾는다.

appsink의 Source Pad와 내부 미디어 엘리먼트의 Sink Pad를 gst_pad_link 함수를 사용하여 연결한다.

// on_media_constructed 콜백 함수 예시 (의사 코드)
void on_media_constructed(GstRTSPMedia *media, GstElement *media_bin, gpointer user_data) {
    GstElement *appsink = (GstElement *)user_data; // appsink 엘리먼트 참조
    GstPad *appsink_src_pad;
    GstPad *media_sink_pad;

    g_print("media-constructed 시그널을 받았습니다. appsink를 연결합니다...\n");

    // 1. appsink의 Source Pad 찾기
    appsink_src_pad = gst_element_get_static_pad(appsink, "src");
    if (!appsink_src_pad) {
        g_warning("appsink src 패드를 찾을 수 없습니다!");
        return;
    }

    // 2. 새로 생성된 media_bin (내부 미디어 엘리먼트)의 Sink Pad 찾기
    // 이 패드 이름은 media_bin의 종류와 구조에 따라 다릅니다.
    // 예: "sink", "video_sink", "audio_sink" 등
    // 정확한 패드 이름을 확인해야 합니다.
    media_sink_pad = gst_element_get_static_pad(media_bin, "sink"); // 예시: "sink" 패드라고 가정
    if (!media_sink_pad) {
         // "sink" 패드가 없다면 다른 패드 이름을 시도하거나 동적 패드를 찾아야 할 수 있습니다.
         // 예: gst_element_get_request_pad(media_bin, "sink_%u");
        g_warning("media_bin sink 패드를 찾을 수 없습니다! 동적 패드를 시도합니다...");
        gst_object_unref(appsink_src_pad);
        return; // 실제 코드에서는 적절한 패드 찾는 로직 필요
    }


    // 3. 두 패드 연결
    if (gst_pad_link(appsink_src_pad, media_sink_pad) != GST_PAD_LINK_OK) {
        g_warning("appsink src를 media_bin sink에 연결하는데 실패했습니다!");
    } else {
        g_print("appsink src를 media_bin sink에 성공적으로 연결했습니다.\n");
    }

    // 패드 참조 해제
    gst_object_unref(appsink_src_pad);
    gst_object_unref(media_sink_pad);

    // 연결 후에 필요하다면 media_bin의 상태를 PLAYING으로 전환 (일반적으로 서버가 처리하지만 명시적으로 할 수도 있습니다)
    // gst_element_set_state(media_bin, GST_STATE_PLAYING);
}
  • 데이터 흐름 시작: 패드 연결이 성공적으로 완료되면, appsink로 데이터를 push하기 시작하면 해당 데이터가 gst-rtsp-server의 동적 미디어 파이프라인을 통해 RTSP 클라이언트에게 스트리밍된다.

결론:

gst-rtsp-server 개발 시, media-configure는 스트림 정보 기반의 사전 준비 및 조건부 로직에 사용되고, media-constructed는 동적으로 생성된 미디어 엘리먼트에 외부 소스(예: appsink)를 연결하거나 추가적인 파이프라인 엘리먼트를 삽입하는 핵심 시점으로 활용된다. appsink로부터 데이터를 공급받는 시나리오에서는 media-constructed 시그널 핸들러 내에서 appsink의 Source Pad와 새로 생성된 내부 미디어 엘리먼트의 Sink Pad를 연결하는 것이 적절하다.