이 챕터에서는 애플리케이션에서 파이프라인을 조작할 수 있는 다양한 방법을 제시합니다. 여기서 다룰 주제는 다음과 같습니다.
- 애플리케이션의 데이터를 파이프라인에 삽입하는 방법
- 파이프라인에서 데이터를 읽는 방법
- 파이프라인의 속도, 길이 및 시작점을 조작하는 방법
- 파이프라인의 데이터 처리를 수신하는 방법
이 챕터의 일부는 매우 낮은 수준이므로 이를 따르려면 약간의 프로그래밍 경험과 GStreamer에 대한 좋은 이해가 필요합니다.
Using probes
Probing은 pad 리스너에 액세스하는 것으로 가장 잘 구상됩니다. 기술적으로 probe는 gst_pad_add_probe()를 사용하여 pad에 연결할 수 있는 콜백에 지나지 않습니다. 반대로 gst_pad_remove_probe()를 사용하여 콜백을 제거할 수 있습니다. 부착된 동안 probe는 pad의 모든 활동을 알려줍니다. Probe를 추가할 때 관심 있는 알림 종류를 정의할 수 있습니다.
Probe 유형:
- 버퍼가 push 되거나 pull 됩니다. Probe를 등록할 때 GST_PAD_PROBE_TYPE_BUFFER를 지정하려고 합니다. Pad는 다양한 방식으로 예약될 수 있기 때문입니다. 선택적 GST_PAD_PROBE_TYPE_PUSH 및 GST_PAD_PROBE_TYPE_PULL 플래그를 사용하여 관심 있는 스케줄링 모드를 지정할 수도 있습니다. 이 프로브를 사용하여 버퍼를 검사, 수정 또는 삭제할 수 있습니다. Data probes를 참조하세요.
- 버퍼 목록이 push 됩니다. Probe를 등록할 때 GST_PAD_PROBE_TYPE_BUFFER_LIST를 사용하세요.
- 이벤트는 pad를 통해 이동합니다. GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM 및 GST_PAD_PROBE_TYPE_EVENT_UPSTREAM 플래그를 사용하여 다운스트림 및 업스트림 이벤트를 선택합니다. 양방향으로 진행되는 이벤트에 대해 알림을 받을 수 있는 편리한 GST_PAD_PROBE_TYPE_EVENT_BOTH도 있습니다. 기본적으로 플러시 이벤트는 알림을 발생시키지 않습니다. 플러시 이벤트에서 콜백을 수신하려면 GST_PAD_PROBE_TYPE_EVENT_FLUSH를 명시적으로 활성화해야 합니다. 이벤트는 항상 push 모드에서만 알림을 받습니다. 이 유형의 probe를 사용하여 이벤트를 검사, 수정 또는 삭제할 수 있습니다.
- 쿼리는 pad를 통해 이동합니다. GST_PAD_PROBE_TYPE_QUERY_DOWNSTREAM 및 GST_PAD_PROBE_TYPE_QUERY_UPSTREAM 플래그를 사용하여 다운스트림 및 업스트림 쿼리를 선택합니다. 편리한 GST_PAD_PROBE_TYPE_QUERY_BOTH를 사용하여 양방향을 선택할 수도 있습니다. 쿼리 probe는 쿼리가 업스트림/다운스트림으로 이동할 때와 쿼리 결과가 반환될 때 두 번 알림을 받습니다. 쿼리가 수행될 때와 쿼리 결과가 반환될 때 각각 GST_PAD_PROBE_TYPE_PUSH 및 GST_PAD_PROBE_TYPE_PULL을 사용하여 콜백이 호출될 단계를 선택할 수 있습니다.
쿼리 probe를 사용하여 쿼리를 검사 또는 수정하거나 probe 콜백에서 응답할 수도 있습니다. 쿼리에 응답하려면 결과 값을 쿼리에 배치하고 콜백에서 GST_PAD_PROBE_DROP를 반환합니다. - 데이터 흐름을 알리는 것 외에도 콜백이 반환될 때 데이터 흐름을 차단하도록 probe에 요청할 수도 있습니다. 이를 blocking probe 라고 하며 GST_PAD_PROBE_TYPE_BLOCK 플래그를 지정하여 활성화됩니다. 이 플래그를 다른 플래그와 함께 사용하면 선택한 활동의 데이터 흐름만 차단할 수 있습니다. Probe를 제거하거나 콜백에서 GST_PAD_PROBE_REMOVE를 반환하면 pad가 다시 차단 해제됩니다. 콜백에서 GST_PAD_PROBE_PASS를 반환하여 현재 차단된 항목만 통과하도록 할 수 있으며, 다음 항목에서 다시 차단됩니다.
Blocking probe는 pad가 연결 해제되었거나 연결 해제 예정이므로 pad를 일시적으로 차단하는 데 사용됩니다. 데이터 흐름이 차단되지 않은 경우 연결되지 않은 pad에 데이터가 push 되면 파이프라인은 오류 상태가 됩니다. Blocking probe를 사용하여 파이프라인을 부분적으로 preroll 하는 방법을 살펴보겠습니다. Play a section of a media file도 참조하세요. - Pad에서 활동이 없을 때 알림을 받습니다. GST_PAD_PROBE_TYPE_IDLE 플래그를 사용하여 이 probe를 설치합니다. Pad scheduling mode에 따라 알림만 받도록 GST_PAD_PROBE_TYPE_PUSH 및/또는 GST_PAD_PROBE_TYPE_PULL을 지정할 수 있습니다. IDLE probe는 IDLE probe가 설치되어 있는 동안 pad에 데이터가 전달되지 않는다는 점에서 blocking probe 이기도 합니다.
Idle probe를 사용하여 pad를 동적으로 다시 연결할 수 있습니다. Idle probe를 사용하여 파이프라인의 element를 교체하는 방법을 살펴보겠습니다. Dynamically changing the pipeline도 참조하세요.
Data probes
Data probe는 pad에 데이터가 전달될 때 이를 알려줍니다. 이러한 유형의 probe를 생성하려면 GST_PAD_PROBE_TYPE_BUFFER 및/또는 GST_PAD_PROBE_TYPE_BUFFER_LIST를 gst_pad_add_probe()에 전달하세요. 가장 일반적인 buffer operations elements는 _chain() 함수에서 수행할 수 있으며 probe 콜백에서 수행할 수 있습니다.
Data probe는 파이프라인의 스트리밍 스레드 컨텍스트에서 실행 되므로 콜백은 차단을 피하고 일반적으로 이상한 작업을 피해야 합니다. 그렇게 하면 파이프라인 성능에 부정적인 영향을 미치거나 버그가 있는 경우 교착 상태 또는 충돌이 발생할 수 있습니다. 보다 정확하게는 일반적으로 probe 콜백 내에서 GUI 관련 함수를 호출하거나 파이프라인 상태를 변경하려고 시도하지 않아야 합니다. 애플리케이션은 파이프라인 버스에 사용자 정의 메시지를 게시하여 메인 애플리케이션 스레드와 통신하고 파이프라인 중지와 같은 작업을 수행하도록 할 수 있습니다.
다음은 data probe 사용에 대한 예입니다. 무엇을 찾아야 할지 확실하지 않은 경우 이 프로그램의 출력을 gst-launch-1.0 videotestsrc ! xvimagesink 의 출력과 비교해 보세요:
#include <gst/gst.h>
static GstPadProbeReturn
cb_have_data (GstPad *pad,
GstPadProbeInfo *info,
gpointer user_data)
{
gint x, y;
GstMapInfo map;
guint16 *ptr, t;
GstBuffer *buffer;
buffer = GST_PAD_PROBE_INFO_BUFFER (info);
buffer = gst_buffer_make_writable (buffer);
/* Making a buffer writable can fail (for example if it
* cannot be copied and is used more than once)
*/
if (buffer == NULL)
return GST_PAD_PROBE_OK;
/* Mapping a buffer can fail (non-writable) */
if (gst_buffer_map (buffer, &map, GST_MAP_WRITE)) {
ptr = (guint16 *) map.data;
/* invert data */
for (y = 0; y < 288; y++) {
for (x = 0; x < 384 / 2; x++) {
t = ptr[384 - 1 - x];
ptr[384 - 1 - x] = ptr[x];
ptr[x] = t;
}
ptr += 384;
}
gst_buffer_unmap (buffer, &map);
}
GST_PAD_PROBE_INFO_DATA (info) = buffer;
return GST_PAD_PROBE_OK;
}
gint
main (gint argc,
gchar *argv[])
{
GMainLoop *loop;
GstElement *pipeline, *src, *sink, *filter, *csp;
GstCaps *filtercaps;
GstPad *pad;
/* init GStreamer */
gst_init (&argc, &argv);
loop = g_main_loop_new (NULL, FALSE);
/* build */
pipeline = gst_pipeline_new ("my-pipeline");
src = gst_element_factory_make ("videotestsrc", "src");
if (src == NULL)
g_error ("Could not create 'videotestsrc' element");
filter = gst_element_factory_make ("capsfilter", "filter");
g_assert (filter != NULL); /* should always exist */
csp = gst_element_factory_make ("videoconvert", "csp");
if (csp == NULL)
g_error ("Could not create 'videoconvert' element");
sink = gst_element_factory_make ("xvimagesink", "sink");
if (sink == NULL) {
sink = gst_element_factory_make ("ximagesink", "sink");
if (sink == NULL)
g_error ("Could not create neither 'xvimagesink' nor 'ximagesink' element");
}
gst_bin_add_many (GST_BIN (pipeline), src, filter, csp, sink, NULL);
gst_element_link_many (src, filter, csp, sink, NULL);
filtercaps = gst_caps_new_simple ("video/x-raw",
"format", G_TYPE_STRING, "RGB16",
"width", G_TYPE_INT, 384,
"height", G_TYPE_INT, 288,
"framerate", GST_TYPE_FRACTION, 25, 1,
NULL);
g_object_set (G_OBJECT (filter), "caps", filtercaps, NULL);
gst_caps_unref (filtercaps);
pad = gst_element_get_static_pad (src, "src");
gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BUFFER,
(GstPadProbeCallback) cb_have_data, NULL, NULL);
gst_object_unref (pad);
/* run */
gst_element_set_state (pipeline, GST_STATE_PLAYING);
/* wait until it's up and running or failed */
if (gst_element_get_state (pipeline, NULL, NULL, -1) == GST_STATE_CHANGE_FAILURE) {
g_error ("Failed to go into PLAYING state");
}
g_print ("Running ...\n");
g_main_loop_run (loop);
/* exit */
gst_element_set_state (pipeline, GST_STATE_NULL);
gst_object_unref (pipeline);
return 0;
}
엄밀히 말하면 pad probe 콜백은 버퍼에 쓰기 가능한 경우에만 버퍼 내용을 수정할 수 있습니다. 이것이 사실인지 아닌지는 파이프라인과 관련된 element에 따라 많이 달라집니다. 종종 이러한 경우가 있지만 그렇지 않은 경우도 있습니다. 그렇지 않은 경우 데이터나 메타데이터를 예기치 않게 수정하면 디버깅 및 추적이 매우 어려운 버그가 발생할 수 있습니다. gst_buffer_is_writable()을 통해 버퍼에 쓰기 가능한지 확인할 수 있습니다. 전달된 버퍼와 다른 버퍼를 다시 전달할 수 있으므로 gst_buffer_make_writable()을 사용하여 콜백 함수에서 버퍼를 쓰기 가능하게 만드는 것이 좋습니다.
Pad probe는 파이프라인을 통과하는 데이터를 보는데 가장 적합합니다. 데이터를 수정해야 하는 경우 자체 GStreamer element를 작성해야 합니다. GstAudioFilter, GstVideoFilter 또는 GstBaseTransform과 같은 베이스 클래스를 사용하면 이 작업이 상당히 쉬워집니다.
파이프라인을 통과하는 버퍼를 검사하려는 경우에는 pad probe를 설정할 필요조차 없습니다. 파이프라인에 identity element를 삽입하고 해당 "handoff" 신호에 연결할 수도 있습니다. Identity element는 dump 및 last-message 속성과 같은 몇 가지 유용한 디버깅 도구도 제공합니다. 후자는 '-v' 스위치를 gst-launch에 전달하고 identity의 silent 속성을 FALSE로 설정하여 활성화됩니다.
Play a section of a media file
이 예에서는 미디어 파일의 한 섹션을 재생하는 방법을 보여줍니다. 목표는 2~5초 동안만 해당 역할을 수행하고 종료하는 것입니다.
첫 번째 단계에서는 uridecodebin 요소를 PAUSED 상태로 설정하고 생성된 모든 source pad를 차단하는지 확인합니다. 모든 source pad가 차단되면 모든 pad에 대한 데이터가 있고 uridecodebin이 prerolled 되었다고 말합니다.
Prerolled 된 파이프라인에서는 미디어의 지속 시간을 물어볼 수 있으며 seek을 수행할 수도 있습니다. 우리는 2~5초 섹션을 선택하기 위해 파이프라인에서 seek 작업을 수행하는 데 관심이 있습니다.
원하는 섹션을 구성한 후 sink element를 연결하고 source pad의 차단을 해제하고 파이프라인을 PLAYING 상태로 설정할 수 있습니다. 요청된 영역이 EOS로 이동하기 전에 sink에 의해 정확히 표시되는 것을 볼 수 있습니다.
코드는 다음과 같습니다.
#include <gst/gst.h>
static GMainLoop *loop;
static gint counter;
static GstBus *bus;
static gboolean prerolled = FALSE;
static GstPad *sinkpad;
static void
dec_counter (GstElement * pipeline)
{
if (prerolled)
return;
if (g_atomic_int_dec_and_test (&counter)) {
/* all probes blocked and no-more-pads signaled, post
* message on the bus. */
prerolled = TRUE;
gst_bus_post (bus, gst_message_new_application (
GST_OBJECT_CAST (pipeline),
gst_structure_new_empty ("ExPrerolled")));
}
}
/* called when a source pad of uridecodebin is blocked */
static GstPadProbeReturn
cb_blocked (GstPad *pad,
GstPadProbeInfo *info,
gpointer user_data)
{
GstElement *pipeline = GST_ELEMENT (user_data);
if (prerolled)
return GST_PAD_PROBE_REMOVE;
dec_counter (pipeline);
return GST_PAD_PROBE_OK;
}
/* called when uridecodebin has a new pad */
static void
cb_pad_added (GstElement *element,
GstPad *pad,
gpointer user_data)
{
GstElement *pipeline = GST_ELEMENT (user_data);
if (prerolled)
return;
g_atomic_int_inc (&counter);
gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM,
(GstPadProbeCallback) cb_blocked, pipeline, NULL);
/* try to link to the video pad */
gst_pad_link (pad, sinkpad);
}
/* called when uridecodebin has created all pads */
static void
cb_no_more_pads (GstElement *element,
gpointer user_data)
{
GstElement *pipeline = GST_ELEMENT (user_data);
if (prerolled)
return;
dec_counter (pipeline);
}
/* called when a new message is posted on the bus */
static void
cb_message (GstBus *bus,
GstMessage *message,
gpointer user_data)
{
GstElement *pipeline = GST_ELEMENT (user_data);
switch (GST_MESSAGE_TYPE (message)) {
case GST_MESSAGE_ERROR:
g_print ("we received an error!\n");
g_main_loop_quit (loop);
break;
case GST_MESSAGE_EOS:
g_print ("we reached EOS\n");
g_main_loop_quit (loop);
break;
case GST_MESSAGE_APPLICATION:
{
if (gst_message_has_name (message, "ExPrerolled")) {
/* it's our message */
g_print ("we are all prerolled, do seek\n");
gst_element_seek (pipeline,
1.0, GST_FORMAT_TIME,
GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE,
GST_SEEK_TYPE_SET, 2 * GST_SECOND,
GST_SEEK_TYPE_SET, 5 * GST_SECOND);
gst_element_set_state (pipeline, GST_STATE_PLAYING);
}
break;
}
default:
break;
}
}
gint
main (gint argc,
gchar *argv[])
{
GstElement *pipeline, *src, *csp, *vs, *sink;
/* init GStreamer */
gst_init (&argc, &argv);
loop = g_main_loop_new (NULL, FALSE);
if (argc < 2) {
g_print ("usage: %s <uri>", argv[0]);
return -1;
}
/* build */
pipeline = gst_pipeline_new ("my-pipeline");
bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
gst_bus_add_signal_watch (bus);
g_signal_connect (bus, "message", (GCallback) cb_message,
pipeline);
src = gst_element_factory_make ("uridecodebin", "src");
if (src == NULL)
g_error ("Could not create 'uridecodebin' element");
g_object_set (src, "uri", argv[1], NULL);
csp = gst_element_factory_make ("videoconvert", "csp");
if (csp == NULL)
g_error ("Could not create 'videoconvert' element");
vs = gst_element_factory_make ("videoscale", "vs");
if (vs == NULL)
g_error ("Could not create 'videoscale' element");
sink = gst_element_factory_make ("autovideosink", "sink");
if (sink == NULL)
g_error ("Could not create 'autovideosink' element");
gst_bin_add_many (GST_BIN (pipeline), src, csp, vs, sink, NULL);
/* can't link src yet, it has no pads */
gst_element_link_many (csp, vs, sink, NULL);
sinkpad = gst_element_get_static_pad (csp, "sink");
/* for each pad block that is installed, we will increment
* the counter. for each pad block that is signaled, we
* decrement the counter. When the counter is 0 we post
* an app message to tell the app that all pads are
* blocked. Start with 1 that is decremented when no-more-pads
* is signaled to make sure that we only post the message
* after no-more-pads */
g_atomic_int_set (&counter, 1);
g_signal_connect (src, "pad-added",
(GCallback) cb_pad_added, pipeline);
g_signal_connect (src, "no-more-pads",
(GCallback) cb_no_more_pads, pipeline);
gst_element_set_state (pipeline, GST_STATE_PAUSED);
g_main_loop_run (loop);
gst_element_set_state (pipeline, GST_STATE_NULL);
gst_object_unref (sinkpad);
gst_object_unref (bus);
gst_object_unref (pipeline);
g_main_loop_unref (loop);
return 0;
}
우리는 사용자 정의 애플리케이션 메시지를 사용하여 uridecodebin이 prerolled 되었음을 메인 스레드에 알립니다. 그런 다음 메인 스레드는 요청된 영역에 대한 flushing seek을 발행합니다. Flush는 일시적으로 pad 차단을 해제하고 새 데이터가 도착하면 다시 차단합니다. 우리는 probe를 제거하기 위해 이 두 번째 차단을 감지합니다. 그런 다음 파이프라인을 PLAYING으로 설정하면 선택한 2~5초 섹션이 재생됩니다. 애플리케이션은 EOS 메시지를 기다렸다가 종료됩니다.
Manually adding or removing data from/to a pipeline
많은 사람들이 자신의 소스를 사용하여 파이프라인에 데이터를 주입하고 싶어하고, 다른 사람들은 파이프라인의 출력을 가져와 자신의 애플리케이션에서 처리하고 싶어했습니다. 이러한 방법은 권장되지 않지만 GStreamer는 이를 지원합니다. -- 주의하세요! 당신은 당신이 무엇을 하고 있는지 알아야 합니다 --. 베이스 클래스의 지원이 없으므로 상태 변경 및 동기화를 철저히 이해해야 합니다. 만약 이해하지 못한다면 잘못된 결정을 내리게 될 것입니다. 단순히 플러그인을 작성하고 베이스 클래스에서 이를 관리하도록 하는 것이 항상 더 좋습니다. 이 주제에 대한 자세한 내용은 Plugin Writer's Guide를 참조하세요. 또한 애플리케이션에 플러그인을 정적으로 포함하는 방법을 설명하는 다음 섹션을 검토하세요.
위에서 언급한 목적으로 사용할 수 있는 두 가지 element는 appsrc (가상 source)와 appsink (가상 sink)입니다. 이러한 element에도 동일한 방법이 적용됩니다. 이를 사용하여 파이프라인에서 데이터를 삽입 (appsrc 사용)하거나 가져오는 (appsink 사용) 방법과 협상을 설정하는 방법에 대해 논의합니다.
appsrc와 appsink는 모두 2개의 API 세트를 제공합니다. 하나의 API는 표준 GObject (action) 신호와 속성을 사용합니다. 동일한 API를 일반 C API로도 사용할 수 있습니다. C API는 성능이 더 뛰어나지만 element를 사용하려면 앱 라이브러리에 연결해야 합니다.
Inserting data with appsrc
appsrc를 살펴보고 애플리케이션 데이터를 파이프라인에 삽입하는 방법을 살펴보겠습니다.
appsrc에는 작동 방식을 제어하는 몇 가지 구성 옵션이 있습니다. 다음 사항을 결정해야 합니다.
- appsrc는 push 또는 pull 모드로 작동. stream-type 속성을 사용하여 이를 제어할 수 있습니다. Random-access-stream-type은 appsrc가 pull 모드 스케줄링을 활성화하고 다른 스트림 유형은 push 모드를 활성화합니다.
- appsrc가 push 할 버퍼의 caps. 이는 caps 속성으로 구성되어야 합니다. 이 속성은 고정된 caps로 설정되어야 하며 다운스트림 형식을 협상하는 데 사용됩니다.
- appsrc가 라이브 모드에서의 작동 여부. 이는 is-live 속성으로 구성됩니다. 라이브 모드에서 작동하는 경우 min-latency 및 max-latency 속성을 설정하는 것도 중요합니다. min-latency는 버퍼 캡처와 appsrc 내부로 push 되는 사이에 걸리는 시간으로 설정되어야 합니다. 라이브 모드에서는 버퍼의 첫 번째 바이트가 캡처되었을 때 이를 appsrc에 공급하기 전에 파이프라인 running-time 으로 버퍼에 타임스탬프를 지정해야 합니다. do-timestamp 속성을 사용하여 appsrc에서 타임스탬프를 수행하도록 할 수 있지만, appsrc 타임스탬프는 주어진 버퍼를 받았을 때의 running-time을 기반으로 하기 때문에 min-latency를 0으로 설정해야 합니다.
- appsrc가 push 할 SEGMENT 이벤트의 형식. 이 형식은 버퍼의 running-time이 계산되는 방식에 영향을 미치므로 이를 이해해야 합니다. 라이브 소스의 경우 형식 속성을 GST_FORMAT_TIME으로 설정하는 것이 좋습니다. 라이브가 아닌 소스의 경우 처리하는 미디어 유형에 따라 다릅니다. 버퍼에 타임스탬프를 기록하려는 경우 GST_FORMAT_TIME을 형식으로 사용해야 하며, 그렇지 않은 경우 GST_FORMAT_BYTES가 적합할 수 있습니다.
- appsrc가 random-access 모드로 작동하는 경우 스트림의 바이트 수로 크기 속성을 구성하는 것이 중요합니다. 이를 통해 다운스트림 element는 미디어의 크기를 알고 필요할 때 스트림의 끝을 찾을 수 있습니다.
appsrc에 대한 데이터를 처리하는 주요 방법은 gst_app_src_push_buffer() 함수를 사용하거나 push-buffer action 신호를 내보내는 것입니다. 이렇게 하면 appsrc가 스트리밍 스레드에서 읽을 큐에 버퍼가 배치됩니다. Push-buffer 호출을 수행한 스레드에서는 데이터 전송이 발생하지 않는다는 점에 유의하는 것이 중요합니다.
Max-bytes 속성은 큐가 가득 찼다고 appsrc가 간주하기 전에 appsrc에 대기할 수 있는 데이터의 양을 제어합니다. 채워진 내부 큐는 항상 enough-data 신호를 보내며, 이는 appsrc에 데이터 push를 중지해야 함을 애플리케이션에 알립니다. Block 속성은 free 데이터를 다시 사용할 수 있을 때까지 appsrc가 push-buffer 방법을 차단하도록 합니다.
내부 큐의 데이터가 부족해지면 need-data 신호가 방출되어 appsrc에 더 많은 데이터를 push해야 한다는 신호를 애플리케이션에게 보냅니다.
Need-data 및 enough-data 신호 외에도 stream-mode 속성이 seekable 또는 random-access로 설정된 경우 appsrc는 seek-data를 내보낼 수 있습니다. Signal 인수에는 format 속성과 unit set으로 표현된 스트림에서 원하는 새 위치가 포함됩니다. Seek-data 신호를 받은 후 애플리케이션은 새 위치에서 버퍼를 push 해야 합니다.
마지막 바이트가 appsrc에 push 되면 gst_app_src_end_of_stream()을 호출하여 EOS 다운스트림을 보내도록 해야 합니다.
이러한 신호를 통해 애플리케이션은 다음에 설명할 push 및 pull 모드에서 appsrc를 작동할 수 있습니다.
Using appsrc in push mode
appsrc가 push 모드 (steam-type은 스트림 또는 seekable)로 구성되면 애플리케이션은 새 버퍼를 사용하여 push-buffer 메서드를 반복적으로 호출합니다. 선택적으로, appsrc의 큐 크기는 push-buffer 호출을 각각 중지/시작하여 enough-data 및 need-data 신호로 제어할 수 있습니다. Min-percent 속성의 값은 need-data 신호가 발행되기 전에 내부 appsrc 큐가 얼마나 비어 있어야 하는지를 정의합니다. 큐가 완전히 소모되는 것을 방지하려면 이 값을 양수 값으로 설정할 수 있습니다.
Stream-type이 GST_APP_STREAM_TYPE_SEEKABLE로 설정된 경우 seek-data 콜백을 구현하는 것을 잊지 마십시오.
다양한 네트워크 프로토콜이나 하드웨어 장치를 구현할 때 이 모드를 사용하십시오.
Using appsrc in pull mode
Pull 모드에서는 need-data 신호 처리기에서 appsrc로 데이터가 공급됩니다. Need-data 신호에서 요청된 바이트 양을 정확히 push 해야 합니다. 스트림 끝에 있을 때만 더 적은 바이트를 push 할 수 있습니다.
파일 액세스 또는 기타 randomly accessible 소스에 이 모드를 사용하십시오.
Appsrc example
이 예제 응용 프로그램은 형식을 강제하기 위해 caps가 있는 소스로 appsrc를 사용하여 흑백 (1초마다 전환) 비디오를 Xv-window 출력으로 생성합니다. 우리는 X 서버에 올바른 형식을 제공하기 위해 색상 공간 변환 element를 사용합니다. 가변 프레임 속도 (0/1)로 비디오 스트림을 구성하고 초당 2프레임을 재생하는 방식으로 출력 버퍼에 타임스탬프를 설정합니다.
appsrc가 push 모드에서 실행 중이지만 appsrc에 새 버퍼를 push 하는 pull 모드 방법을 사용하는 방법에 유의하세요.
#include <gst/gst.h>
static GMainLoop *loop;
static void
cb_need_data (GstElement *appsrc,
guint unused_size,
gpointer user_data)
{
static gboolean white = FALSE;
static GstClockTime timestamp = 0;
GstBuffer *buffer;
guint size;
GstFlowReturn ret;
size = 385 * 288 * 2;
buffer = gst_buffer_new_allocate (NULL, size, NULL);
/* this makes the image black/white */
gst_buffer_memset (buffer, 0, white ? 0xff : 0x0, size);
white = !white;
GST_BUFFER_PTS (buffer) = timestamp;
GST_BUFFER_DURATION (buffer) = gst_util_uint64_scale_int (1, GST_SECOND, 2);
timestamp += GST_BUFFER_DURATION (buffer);
g_signal_emit_by_name (appsrc, "push-buffer", buffer, &ret);
gst_buffer_unref (buffer);
if (ret != GST_FLOW_OK) {
/* something wrong, stop pushing */
g_main_loop_quit (loop);
}
}
gint
main (gint argc,
gchar *argv[])
{
GstElement *pipeline, *appsrc, *conv, *videosink;
/* init GStreamer */
gst_init (&argc, &argv);
loop = g_main_loop_new (NULL, FALSE);
/* setup pipeline */
pipeline = gst_pipeline_new ("pipeline");
appsrc = gst_element_factory_make ("appsrc", "source");
conv = gst_element_factory_make ("videoconvert", "conv");
videosink = gst_element_factory_make ("xvimagesink", "videosink");
/* setup */
g_object_set (G_OBJECT (appsrc), "caps",
gst_caps_new_simple ("video/x-raw",
"format", G_TYPE_STRING, "RGB16",
"width", G_TYPE_INT, 384,
"height", G_TYPE_INT, 288,
"framerate", GST_TYPE_FRACTION, 0, 1,
NULL), NULL);
gst_bin_add_many (GST_BIN (pipeline), appsrc, conv, videosink, NULL);
gst_element_link_many (appsrc, conv, videosink, NULL);
/* setup appsrc */
g_object_set (G_OBJECT (appsrc),
"stream-type", 0,
"format", GST_FORMAT_TIME, NULL);
g_signal_connect (appsrc, "need-data", G_CALLBACK (cb_need_data), NULL);
/* play */
gst_element_set_state (pipeline, GST_STATE_PLAYING);
g_main_loop_run (loop);
/* clean up */
gst_element_set_state (pipeline, GST_STATE_NULL);
gst_object_unref (GST_OBJECT (pipeline));
g_main_loop_unref (loop);
return 0;
}
Grabbing data with appsink
appsrc와 달리 appsink는 사용하기가 조금 더 쉽습니다. 또한 파이프라인에서 데이터를 가져오기 위한 pull 및 push 기반 모드도 지원합니다.
appsink에서 샘플을 검색하는 일반적인 방법은 gst_app_sink_pull_sample() 및 gst_app_sink_pull_preroll() 메서드를 사용하거나 pull-sample 및 pull-preroll 신호를 사용하는 것입니다. 이러한 메서드는 sink에서 샘플을 사용할 수 있을 때까지 또는 sink가 종료되거나 EOS에 도달할 때까지 차단됩니다.
appsink는 내부적으로 큐를 사용하여 스트리밍 스레드에서 버퍼를 수집합니다. 애플리케이션이 충분히 빠르게 샘플을 가져오지 않는 경우 이 큐는 시간이 지남에 따라 많은 메모리를 소비하게 됩니다. Max-buffers 속성을 사용하여 큐 크기를 제한할 수 있습니다. Drop 속성은 스트리밍 스레드가 차단되는지 여부 또는 최대 큐 크기에 도달할 때 이전 버퍼가 삭제되는지 여부를 제어합니다. 스트리밍 스레드를 차단하면 실시간 성능에 부정적인 영향을 미칠 수 있으므로 피해야 합니다.
Blocking 동작이 바람직하지 않은 경우 emit-signals 속성을 TRUE로 설정하면 차단 없이 샘플을 가져올 수 있을 때 appsink가 new-sample 및 new-preroll 신호를 내보냅니다.
appsink의 caps 속성을 사용하여 후자가 수신할 수 있는 형식을 제어할 수 있습니다. 이 속성에는 고정되지 않은 caps가 포함될 수 있으며, 가져온 샘플의 형식은 샘플 caps을 가져와서 얻을 수 있습니다.
Pull-preroll 또는 pull-sample 방법 중 하나가 NULL을 반환하면 appsink가 중지되거나 EOS 상태입니다. eos 속성이나 gst_app_sink_is_eos() 메서드를 사용하여 EOS 상태를 확인할 수 있습니다.
eos 신호는 polling을 피하기 위해 EOS 상태에 도달했을 때 알림을 받는 데 사용될 수도 있습니다.
appsink에서 다음 속성을 구성하는 것을 고려해보세요.
- 샘플을 전달하기 전에 sink 베이스 클래스가 파이프라인 clock과 버퍼를 동기화하도록 하려면 sync 속성을 사용하세요.
- qos 속성으로 서비스 품질을 활성화합니다. Raw 비디오 프레임을 처리하고 베이스 클래스가 clock에서 동기화되도록 하는 경우, 베이스 클래스가 QOS 이벤트를 업스트림으로 보내도록 하는 것도 좋은 생각일 수 있습니다.
- 허용되는 caps가 포함된 caps 속성입니다. 업스트림 element는 appsink에 구성된 caps와 일치하도록 형식을 변환하려고 시도합니다. 버퍼의 실제 caps를 얻으려면 GstSample을 확인해야 합니다.
Appsink example
다음은 appsink를 사용하여 비디오 스트림의 스냅샷을 캡처하는 방법에 대한 예입니다.
#include <gst/gst.h>
#ifdef HAVE_GTK
#include <gtk/gtk.h>
#endif
#include <stdlib.h>
#define CAPS "video/x-raw,format=RGB,width=160,pixel-aspect-ratio=1/1"
int
main (int argc, char *argv[])
{
GstElement *pipeline, *sink;
gint width, height;
GstSample *sample;
gchar *descr;
GError *error = NULL;
gint64 duration, position;
GstStateChangeReturn ret;
gboolean res;
GstMapInfo map;
gst_init (&argc, &argv);
if (argc != 2) {
g_print ("usage: %s <uri>\n Writes snapshot.png in the current directory\n",
argv[0]);
exit (-1);
}
/* create a new pipeline */
descr =
g_strdup_printf ("uridecodebin uri=%s ! videoconvert ! videoscale ! "
" appsink name=sink caps=\"" CAPS "\"", argv[1]);
pipeline = gst_parse_launch (descr, &error);
if (error != NULL) {
g_print ("could not construct pipeline: %s\n", error->message);
g_clear_error (&error);
exit (-1);
}
/* get sink */
sink = gst_bin_get_by_name (GST_BIN (pipeline), "sink");
/* set to PAUSED to make the first frame arrive in the sink */
ret = gst_element_set_state (pipeline, GST_STATE_PAUSED);
switch (ret) {
case GST_STATE_CHANGE_FAILURE:
g_print ("failed to play the file\n");
exit (-1);
case GST_STATE_CHANGE_NO_PREROLL:
/* for live sources, we need to set the pipeline to PLAYING before we can
* receive a buffer. We don't do that yet */
g_print ("live sources not supported yet\n");
exit (-1);
default:
break;
}
/* This can block for up to 5 seconds. If your machine is really overloaded,
* it might time out before the pipeline prerolled and we generate an error. A
* better way is to run a mainloop and catch errors there. */
ret = gst_element_get_state (pipeline, NULL, NULL, 5 * GST_SECOND);
if (ret == GST_STATE_CHANGE_FAILURE) {
g_print ("failed to play the file\n");
exit (-1);
}
/* get the duration */
gst_element_query_duration (pipeline, GST_FORMAT_TIME, &duration);
if (duration != -1)
/* we have a duration, seek to 5% */
position = duration * 5 / 100;
else
/* no duration, seek to 1 second, this could EOS */
position = 1 * GST_SECOND;
/* seek to the position in the file. Most files have a black first frame so
* by seeking to somewhere else we have a bigger chance of getting something
* more interesting. An optimisation would be to detect black images and then
* seek a little more */
gst_element_seek_simple (pipeline, GST_FORMAT_TIME,
GST_SEEK_FLAG_KEY_UNIT | GST_SEEK_FLAG_FLUSH, position);
/* get the preroll buffer from appsink, this block untils appsink really
* prerolls */
g_signal_emit_by_name (sink, "pull-preroll", &sample, NULL);
/* if we have a buffer now, convert it to a pixbuf. It's possible that we
* don't have a buffer because we went EOS right away or had an error. */
if (sample) {
GstBuffer *buffer;
GstCaps *caps;
GstStructure *s;
/* get the snapshot buffer format now. We set the caps on the appsink so
* that it can only be an rgb buffer. The only thing we have not specified
* on the caps is the height, which is dependant on the pixel-aspect-ratio
* of the source material */
caps = gst_sample_get_caps (sample);
if (!caps) {
g_print ("could not get snapshot format\n");
exit (-1);
}
s = gst_caps_get_structure (caps, 0);
/* we need to get the final caps on the buffer to get the size */
res = gst_structure_get_int (s, "width", &width);
res |= gst_structure_get_int (s, "height", &height);
if (!res) {
g_print ("could not get snapshot dimension\n");
exit (-1);
}
/* create pixmap from buffer and save, gstreamer video buffers have a stride
* that is rounded up to the nearest multiple of 4 */
buffer = gst_sample_get_buffer (sample);
/* Mapping a buffer can fail (non-readable) */
if (gst_buffer_map (buffer, &map, GST_MAP_READ)) {
#ifdef HAVE_GTK
pixbuf = gdk_pixbuf_new_from_data (map.data,
GDK_COLORSPACE_RGB, FALSE, 8, width, height,
GST_ROUND_UP_4 (width * 3), NULL, NULL);
/* save the pixbuf */
gdk_pixbuf_save (pixbuf, "snapshot.png", "png", &error, NULL);
#endif
gst_buffer_unmap (buffer, &map);
}
gst_sample_unref (sample);
} else {
g_print ("could not make snapshot\n");
}
/* cleanup and exit */
gst_element_set_state (pipeline, GST_STATE_NULL);
gst_object_unref (sink);
gst_object_unref (pipeline);
exit (0);
}
Forcing a format
때로는 특정 형식을 설정하고 싶을 수도 있습니다. capsfilter element를 사용하여 이 작업을 수행할 수 있습니다.
예를 들어 특정 비디오 크기와 색상 형식 또는 오디오 비트 크기와 채널 수를 원하는 경우 filtered caps을 사용하여 파이프라인에서 특정 GstCap을 강제로 적용할 수 있습니다. 두 element 사이에 capsfilter를 넣고 caps 속성에 원하는 GstCaps를 지정하여 연결에 filtered caps를 설정합니다. capsfilter는 이러한 기능과 호환되는 유형만 협상하도록 허용합니다.
Filtering을 위한 capabilities 생성도 참조하세요.
Changing format in a PLAYING pipeline
PLAYING 중에 파이프라인의 형식을 동적으로 변경하는 것도 가능합니다. 이는 capsfilter의 caps 속성을 변경하여 간단하게 수행할 수 있습니다. capsfilter는 업스트림 element가 새로운 형식과 할당자를 재협상하도록 시도하는 RECONFIGURE 이벤트 업스트림을 보냅니다. 이는 업스트림 element가 source pad에서 고정 caps를 사용하지 않는 경우에만 작동합니다.
다음은 PLAYING 상태에 있는 동안 파이프라인의 caps를 변경할 수 있는 방법의 예입니다.
#include <stdlib.h>
#include <gst/gst.h>
#define MAX_ROUND 100
int
main (int argc, char **argv)
{
GstElement *pipe, *filter;
GstCaps *caps;
gint width, height;
gint xdir, ydir;
gint round;
GstMessage *message;
gst_init (&argc, &argv);
pipe = gst_parse_launch_full ("videotestsrc ! capsfilter name=filter ! "
"ximagesink", NULL, GST_PARSE_FLAG_NONE, NULL);
g_assert (pipe != NULL);
filter = gst_bin_get_by_name (GST_BIN (pipe), "filter");
g_assert (filter);
width = 320;
height = 240;
xdir = ydir = -10;
for (round = 0; round < MAX_ROUND; round++) {
gchar *capsstr;
g_print ("resize to %dx%d (%d/%d) \r", width, height, round, MAX_ROUND);
/* we prefer our fixed width and height but allow other dimensions to pass
* as well */
capsstr = g_strdup_printf ("video/x-raw, width=(int)%d, height=(int)%d",
width, height);
caps = gst_caps_from_string (capsstr);
g_free (capsstr);
g_object_set (filter, "caps", caps, NULL);
gst_caps_unref (caps);
if (round == 0)
gst_element_set_state (pipe, GST_STATE_PLAYING);
width += xdir;
if (width >= 320)
xdir = -10;
else if (width < 200)
xdir = 10;
height += ydir;
if (height >= 240)
ydir = -10;
else if (height < 150)
ydir = 10;
message =
gst_bus_poll (GST_ELEMENT_BUS (pipe), GST_MESSAGE_ERROR,
50 * GST_MSECOND);
if (message) {
g_print ("got error \n");
gst_message_unref (message);
}
}
g_print ("done \n");
gst_object_unref (filter);
gst_element_set_state (pipe, GST_STATE_NULL);
gst_object_unref (pipe);
return 0;
}
메시지를 받고 짧은 sleep 모드를 도입하기 위해 짧은 timeout으로 gst_bus_poll()을 사용하는 방법에 유의하세요.
; 로 구분된 capsfilter에 대해 여러 개의 caps를 설정할 수 있습니다. capsfilter는 목록에서 가능한 첫 번째 형식으로 재협상을 시도합니다.
Dynamically changing the pipeline
이 섹션에서는 파이프라인을 동적으로 수정하는 몇 가지 기술에 대해 설명합니다. 우리는 PLAYING 상태에서 데이터 흐름을 중단하지 않고 파이프라인을 변경하는 것에 대해 구체적으로 이야기하고 있습니다.
동적 파이프라인을 구축할 때 고려해야 할 몇 가지 중요한 사항이 있습니다.
- 동적 파이프라인 변경의 일부 사례를 대상으로 하고 사용자의 요구 사항을 충족할 수 있는 insertbin 및 switchbin element가 있습니다.
- 파이프라인에서 element를 제거할 때 연결되지 않은 pad에 데이터 흐름이 없는지 확인하십시오. 그러면 치명적인 파이프라인 오류가 발생할 수 있습니다. Pad를 연결 해제하기 전에 항상 source pad (push 모드) 또는 sink pad (pull 모드)를 차단하십시오. Changing elements in a pipeline도 참조하세요.
- 파이프라인에 element를 추가할 때 데이터 흐름을 허용하기 전에 element를 올바른 상태 (일반적으로 부모 element와 동일한 상태)로 설정해야 합니다. Element가 새로 생성되면 NULL 상태가 되며 데이터를 수신하면 오류를 반환합니다. Changing elements in a pipeline도 참조하세요.
- 파이프라인에 element를 추가할 때 GStreamer는 기본적으로 element의 clock와 base-time을 파이프라인의 현재 값으로 설정합니다. 이는 해당 element가 파이프라인의 다른 element와 동일한 파이프라인 running-time을 구성할 수 있음을 의미합니다. 이는 sink가 파이프라인의 다른 sink와 마찬가지로 버퍼를 동기화하고 source가 다른 source와 일치하는 running-time으로 버퍼를 생성한다는 것을 의미합니다.
- 업스트림 체인에서 element의 연결을 해제할 때 항상 EOS 이벤트를 element sink pad 뒤로 보내고 EOS가 element를 떠날 때까지 기다려 (이벤트 probe를 사용하여) element에 쌓여있는 데이터를 플러시해야 합니다.
플러시를 수행하지 않으면 연결되지 않은 element에 의해 버퍼링된 데이터가 손실됩니다. 이로 인해 간단한 프레임 손실 (몇 개의 비디오 프레임, 몇 밀리초의 오디오 등)이 발생할 수 있지만 muxer (어떤 경우에는 인코더 또는 유사한 element)를 제거하면 파일이 손상될 위험이 있습니다. 일부 관련 메타데이터 (헤더, 검색/인덱스 테이블, 내부 동기화 태그)가 제대로 저장되거나 업데이트되지 않을 수 있으므로 제대로 재생되지 않습니다.
Changing elements in a pipeline도 참조하세요. - 라이브 source는 파이프라인의 현재 running-time과 동일한 running-time으로 버퍼를 생성합니다.
라이브 source가 없는 파이프라인은 running-time이 0부터 시작하는 버퍼를 생성합니다. 마찬가지로 플러시 seek 후 이러한 파이프라인은 running-time을 다시 0으로 재설정합니다.
Running-time은 gst_pad_set_offset()으로 변경할 수 있습니다. 동기화를 유지하려면 파이프라인 element의 running-time을 아는 것이 중요합니다. - Element를 추가하면 파이프라인 상태가 변경될 수 있습니다. 예를 들어 non-prerolled sink를 추가하면 파이프라인이 다시 prerolling 상태로 돌아갑니다. 예를 들어, non-prerolled sink를 제거하면 파이프라인이 PAUSED 및 PLAYING 상태로 변경될 수 있습니다.
라이브 source를 추가하면 preroll 단계가 취소되고 파이프라인이 재생 상태가 됩니다. 라이브 source를 추가하면 파이프라인의 latency도 변경될 수 있습니다.
파이프라인 element를 추가하거나 제거하면 파이프라인의 clock 선택이 변경될 수 있습니다. 새로 추가된 element가 clock을 제공하는 경우 파이프라인이 새 clock을 사용하는 것이 좋을 수 있습니다. 반면에 파이프라인에 clock을 제공하는 element가 제거되면 새 clock을 선택해야 합니다. - Element를 추가하고 제거하면 업스트림 또는 다운스트림 element가 caps 및/또는 할당자를 재협상할 수 있습니다. 애플리케이션에서 실제로 아무것도 할 필요가 없습니다. 플러그인은 형식과 할당 전략을 최적화하기 위해 새로운 파이프라인 토폴로지에 적응합니다.
중요한 것은 파이프라인의 element를 추가, 제거 또는 변경할 때 파이프라인이 새로운 형식을 협상해야 하고 이것이 실패할 수 있다는 것입니다. 일반적으로 필요한 곳에 올바른 converter element를 삽입하여 이 문제를 해결할 수 있습니다. Changing elements in a pipeline도 참조하세요.
GStreamer는 거의 모든 동적 파이프라인 수정을 지원하지만 파이프라인 오류 없이 이 작업을 수행하려면 먼저 몇 가지 세부 사항을 알아야 합니다. 다음 섹션에서는 몇 가지 일반적인 수정 사용 사례를 보여 드리겠습니다.
Changing elements in a pipeline
이 예에는 다음과 같은 요소 체인이 있습니다.
- ----. .----------. .---- -
element1 | | element2 | | element3
src -> sink src -> sink
- ----' '----------' '---- -
파이프라인이 PLAYING 상태에 있는 동안 element2를 element4로 바꾸려고 합니다. Element2가 시각화이고 파이프라인에서 시각화를 전환하려고 한다고 가정해 보겠습니다.
Element1의 source pad에서 element2의 sink pad를 연결 해제할 수는 없습니다. 그렇게 하면 element1의 source pad가 연결 해제된 상태로 유지되고 데이터가 source pad에 푸시될 때 파이프라인에서 스트리밍 오류가 발생할 수 있기 때문입니다. 이 기술은 element2를 element4로 교체하기 전에 element1의 source pad에서 데이터 흐름을 차단한 후 다음 단계에 표시된 대로 데이터 흐름을 재개하는 것입니다.
- Blocking pad probe로 element1의 source pad를 차단합니다. Pad가 차단되면 probe 콜백이 호출됩니다.
- Block 콜백 내에서는 element1과 element2 사이에 아무 것도 흐르지 않으며 차단이 해제될 때까지 아무 것도 흐르지 않습니다.
- Element1과 element2의 연결을 해제합니다.
- 데이터가 element2에서 플러시되는지 확인하세요. 일부 element는 일부 데이터를 내부적으로 보관할 수 있으므로 element2에서 강제로 데이터를 잃어버리지 않도록 해야 합니다. 다음과 같이 EOS를 element2에 푸시하면 됩니다.
- Element2의 source pad에 이벤트 probe를 배치합니다.
- EOS를 element2의 sink pad로 보냅니다. 이렇게 하면 element2 내부의 모든 데이터가 강제로 제거됩니다.
- Element2의 source pad에 EOS 이벤트가 나타날 때까지 기다립니다. EOS가 수신되면 이를 삭제하고 이벤트 probe를 제거합니다.
- Element2의 source pad에 이벤트 probe를 배치합니다.
- Element2와 element3의 연결을 해제합니다. 이제 파이프라인에서 element2를 제거하고 상태를 NULL로 설정할 수도 있습니다.
- 아직 추가되지 않은 경우 element4를 파이프라인에 추가합니다. Element4와 element3을 연결합니다. Element1과 element4를 연결합니다.
- Element4가 파이프라인의 나머지 element와 동일한 상태인지 확인하세요. 버퍼와 이벤트를 수신하려면 최소한 PAUSED 상태여야 합니다.
- Element1의 source pad probe 차단을 해제합니다. 이렇게 하면 새 데이터가 element4에 들어가고 스트리밍이 계속됩니다.
위의 알고리즘은 source pad가 차단되었을 때, 즉 파이프라인에 데이터 흐름이 있을 때 작동합니다. 데이터 흐름이 없으면 (아직은) element를 변경할 필요가 없으므로 이 알고리즘은 PAUSED 상태에서도 사용할 수 있습니다.
이 예에서는 초당 한 번씩 간단한 파이프라인에서 비디오 효과를 변경합니다.
#include <gst/gst.h>
static gchar *opt_effects = NULL;
#define DEFAULT_EFFECTS "identity,exclusion,navigationtest," \
"agingtv,videoflip,vertigotv,gaussianblur,shagadelictv,edgetv"
static GstPad *blockpad;
static GstElement *conv_before;
static GstElement *conv_after;
static GstElement *cur_effect;
static GstElement *pipeline;
static GQueue effects = G_QUEUE_INIT;
static GstPadProbeReturn
event_probe_cb (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
{
GMainLoop *loop = user_data;
GstElement *next;
if (GST_EVENT_TYPE (GST_PAD_PROBE_INFO_DATA (info)) != GST_EVENT_EOS)
return GST_PAD_PROBE_OK;
gst_pad_remove_probe (pad, GST_PAD_PROBE_INFO_ID (info));
/* take next effect from the queue */
next = g_queue_pop_head (&effects);
if (next == NULL) {
GST_DEBUG_OBJECT (pad, "no more effects");
g_main_loop_quit (loop);
return GST_PAD_PROBE_DROP;
}
g_print ("Switching from '%s' to '%s'..\n", GST_OBJECT_NAME (cur_effect),
GST_OBJECT_NAME (next));
gst_element_set_state (cur_effect, GST_STATE_NULL);
/* remove unlinks automatically */
GST_DEBUG_OBJECT (pipeline, "removing %" GST_PTR_FORMAT, cur_effect);
gst_bin_remove (GST_BIN (pipeline), cur_effect);
/* push current effect back into the queue */
g_queue_push_tail (&effects, g_steal_pointer (&cur_effect));
/* add, link and start the new effect */
GST_DEBUG_OBJECT (pipeline, "adding %" GST_PTR_FORMAT, next);
gst_bin_add (GST_BIN (pipeline), next);
GST_DEBUG_OBJECT (pipeline, "linking..");
gst_element_link_many (conv_before, next, conv_after, NULL);
gst_element_set_state (next, GST_STATE_PLAYING);
cur_effect = next;
GST_DEBUG_OBJECT (pipeline, "done");
return GST_PAD_PROBE_DROP;
}
static GstPadProbeReturn
pad_probe_cb (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
{
GstPad *srcpad, *sinkpad;
GST_DEBUG_OBJECT (pad, "pad is blocked now");
/* remove the probe first */
gst_pad_remove_probe (pad, GST_PAD_PROBE_INFO_ID (info));
/* install new probe for EOS */
srcpad = gst_element_get_static_pad (cur_effect, "src");
gst_pad_add_probe (srcpad, GST_PAD_PROBE_TYPE_BLOCK |
GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, event_probe_cb, user_data, NULL);
gst_object_unref (srcpad);
/* push EOS into the element, the probe will be fired when the
* EOS leaves the effect and it has thus drained all of its data */
sinkpad = gst_element_get_static_pad (cur_effect, "sink");
gst_pad_send_event (sinkpad, gst_event_new_eos ());
gst_object_unref (sinkpad);
return GST_PAD_PROBE_OK;
}
static gboolean
timeout_cb (gpointer user_data)
{
gst_pad_add_probe (blockpad, GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM,
pad_probe_cb, user_data, NULL);
return TRUE;
}
static gboolean
bus_cb (GstBus * bus, GstMessage * msg, gpointer user_data)
{
GMainLoop *loop = user_data;
switch (GST_MESSAGE_TYPE (msg)) {
case GST_MESSAGE_ERROR:{
GError *err = NULL;
gchar *dbg;
gst_message_parse_error (msg, &err, &dbg);
gst_object_default_error (msg->src, err, dbg);
g_clear_error (&err);
g_free (dbg);
g_main_loop_quit (loop);
break;
}
default:
break;
}
return TRUE;
}
int
main (int argc, char **argv)
{
GOptionEntry options[] = {
{"effects", 'e', 0, G_OPTION_ARG_STRING, &opt_effects,
"Effects to use (comma-separated list of element names)", NULL},
{NULL}
};
GOptionContext *ctx;
GError *err = NULL;
GMainLoop *loop;
GstElement *src, *q1, *q2, *effect, *filter, *sink;
gchar **effect_names, **e;
ctx = g_option_context_new ("");
g_option_context_add_main_entries (ctx, options, GETTEXT_PACKAGE);
g_option_context_add_group (ctx, gst_init_get_option_group ());
if (!g_option_context_parse (ctx, &argc, &argv, &err)) {
g_error ("Error initializing: %s\n", err->message);
return 1;
}
g_option_context_free (ctx);
if (opt_effects != NULL)
effect_names = g_strsplit (opt_effects, ",", -1);
else
effect_names = g_strsplit (DEFAULT_EFFECTS, ",", -1);
for (e = effect_names; e != NULL && *e != NULL; ++e) {
GstElement *el;
el = gst_element_factory_make (*e, NULL);
if (el) {
g_print ("Adding effect '%s'\n", *e);
g_queue_push_tail (&effects, gst_object_ref_sink (el));
}
}
pipeline = gst_pipeline_new ("pipeline");
src = gst_element_factory_make ("videotestsrc", NULL);
g_object_set (src, "is-live", TRUE, NULL);
filter = gst_element_factory_make ("capsfilter", NULL);
gst_util_set_object_arg (G_OBJECT (filter), "caps",
"video/x-raw, width=320, height=240, "
"format={ I420, YV12, YUY2, UYVY, AYUV, Y41B, Y42B, "
"YVYU, Y444, v210, v216, NV12, NV21, UYVP, A420, YUV9, YVU9, IYU1 }");
q1 = gst_element_factory_make ("queue", NULL);
blockpad = gst_element_get_static_pad (q1, "src");
conv_before = gst_element_factory_make ("videoconvert", NULL);
effect = g_queue_pop_head (&effects);
cur_effect = effect;
conv_after = gst_element_factory_make ("videoconvert", NULL);
q2 = gst_element_factory_make ("queue", NULL);
sink = gst_element_factory_make ("ximagesink", NULL);
gst_bin_add_many (GST_BIN (pipeline), src, filter, q1, conv_before, effect,
conv_after, q2, sink, NULL);
gst_element_link_many (src, filter, q1, conv_before, effect, conv_after,
q2, sink, NULL);
if (gst_element_set_state (pipeline,
GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
g_error ("Error starting pipeline");
return 1;
}
loop = g_main_loop_new (NULL, FALSE);
gst_bus_add_watch (GST_ELEMENT_BUS (pipeline), bus_cb, loop);
g_timeout_add_seconds (1, timeout_cb, loop);
g_main_loop_run (loop);
gst_element_set_state (pipeline, GST_STATE_NULL);
gst_object_unref (blockpad);
gst_bus_remove_watch (GST_ELEMENT_BUS (pipeline));
gst_object_unref (pipeline);
g_main_loop_unref (loop);
g_queue_clear_full (&effects, (GDestroyNotify) gst_object_unref);
gst_object_unref (cur_effect);
g_strfreev (effect_names);
return 0;
}
효과 전후에 videoconvert element를 어떻게 추가했는지 살펴보세요. 이는 일부 element가 다른 색상 공간에서 작동할 수 있기 때문에 필요합니다. Conversion element를 삽입함으로써 적절한 형식이 협상될 수 있도록 도울 수 있습니다.
원문: Pipeline manipulation (gstreamer.freedesktop.org)
Pipeline manipulation
Pipeline manipulation This chapter presents many ways in which you can manipulate pipelines from your application. These are some of the topics that will be covered: How to insert data from an application into a pipeline How to read data from a pipeline Ho
gstreamer.freedesktop.org
'IT와 개발 > GStreamer Study' 카테고리의 다른 글
Programs (1) | 2024.08.02 |
---|---|
Playback Components (0) | 2024.07.26 |
Autoplugging (2) | 2024.07.05 |
Threads (1) | 2024.06.28 |
Dynamic Controllable Parameters (1) | 2024.06.21 |