Buffering의 목적은 재생이 중단 없이 원활하게 이루어질 수 있도록 파이프라인에 충분한 데이터를 축적하는 것입니다. 일반적으로 (느린) non-live 네트워크 source에서 읽을 때 수행되지만 live source에도 사용할 수 있습니다.
GStreamer는 다음 사용 사례를 지원합니다.
- 재생을 시작하기 전 메모리에 특정 양의 데이터까지 buffering하여 네트워크 변동을 최소화합니다. Stream buffering을 참조하세요.
- 다운로드한 데이터를 빠르게 검색하여 네트워크 파일을 로컬 디스크에 다운로드합니다. 이는 Quicktime/YouTube 플레이어와 유사합니다. Download buffering을 참조하세요.
- 캐시된 영역을 검색하여 (semi)-live 스트림을 로컬, 디스크, ringbuffer에 캐싱합니다. 이는 tivo와 같은 timeshifting과 유사합니다. Timeshift buffering을 참조하세요.
GStreamer는 애플리케이션에 현재 Buffering 상태에 대한 진행 보고서를 제공할 수 있을 뿐만 아니라 애플리케이션이 buffering 하는 방법과 buffering 중지 시기를 결정하도록 할 수 있습니다.
가장 간단한 경우, 애플리케이션은 버스에서 BUFFERING 메시지를 수신해야 합니다. BUFFERING 메시지 내의 백분율 표시기가 100보다 작으면 파이프라인이 buffering 중입니다. 메시지가 100% 수신되면 buffering이 완료된 것입니다. Buffering 상태에서 애플리케이션은 파이프라인을 PAUSED 상태로 유지해야 합니다. Buffering이 완료되면 파이프라인을 PLAYING 상태로 되돌릴 수 있습니다.
다음은 메시지 처리기가 BUFFERING 메시지를 처리하는 방법에 대한 예입니다. Buffering strategies에서 더 발전된 방법을 살펴보겠습니다.
[...]
switch (GST_MESSAGE_TYPE (message)) {
case GST_MESSAGE_BUFFERING:{
gint percent;
/* no state management needed for live pipelines */
if (is_live)
break;
gst_message_parse_buffering (message, &percent);
if (percent == 100) {
/* a 100% message means buffering is done */
buffering = FALSE;
/* if the desired state is playing, go back */
if (target_state == GST_STATE_PLAYING) {
gst_element_set_state (pipeline, GST_STATE_PLAYING);
}
} else {
/* buffering busy */
if (!buffering && target_state == GST_STATE_PLAYING) {
/* we were not buffering but PLAYING, PAUSE the pipeline. */
gst_element_set_state (pipeline, GST_STATE_PAUSED);
}
buffering = TRUE;
}
break;
case ...
[...]
Stream buffering
+---------+ +---------+ +-------+
| httpsrc | | buffer | | demux |
| src - sink src - sink ....
+---------+ +---------+ +-------+
이 경우 느린 네트워크 source에서 buffer element (예: queue2)로 읽는 중입니다.
Buffer element에는 바이트 단위로 표시되는 low 워터마크와 high 워터마크가 있습니다. Buffer는 다음과 같이 워터마크를 사용합니다.
- Buffer element는 high 워터마크에 도달할 때까지 BUFFERING 메시지를 게시합니다. 이는 애플리케이션이 파이프라인을 PAUSED 상태로 유지하도록 지시하며, 이는 결국 데이터가 sink에 preroll 되는 동안 srcpad가 push되는 것을 차단합니다.
- High 워터마크에 도달하면 100% buffering 메시지가 게시되어 애플리케이션에 재생을 계속하도록 지시합니다.
- 재생 중에 low 워터마크에 도달하면 queue는 다시 buffering 메시지를 게시하기 시작하여 high 워터마크에 다시 도달할 때까지 애플리케이션이 파이프라인을 다시 일시 중지하게 만듭니다. 이를 rebuffering 단계라고 합니다.
- 재생 중에 queue 레벨은 네트워크 불규칙성을 보상하기 위한 방법으로 high 워터마크와 low 워터마크 사이에서 변동합니다.
이 buffering 방식은 demuxer가 push 모드로 동작할 때 사용 가능합니다. 스트림에서 seeking 하려면 네트워크 source에서 seek이 이루어져야 합니다. 라이브 스트리밍과 같이 파일의 전체 지속 시간을 알 수 없거나, 효율적인 검색이 불가능하거나 요구되는 경우에 가장 바람직합니다.
문제는 좋은 low 워터마크와 high 워터마크를 구성하는 것입니다. 다음은 몇 가지 아이디어입니다.
- Buffering 시간이 일정하게 소요되도록 네트워크 대역폭을 측정하고 low/high 워터마크를 구성하는 것이 가능합니다. GStreamer 코어의 queue2 요소에는 use-rate-estimate 속성과 함께 정확히 이를 수행하는 max-size-time 속성이 있습니다. 또한 playbin buffer-duration 속성은 rate 추정을 사용하여 buffering 되는 데이터의 양을 조정합니다.
- 코덱 bitrate에 따라 재생이 시작되기 전에 고정된 양의 데이터가 buffering 되도록 워터마크를 설정할 수도 있습니다. 일반적으로 buffering element는 스트림의 bitrate를 알지 못하지만 쿼리를 통해 이를 얻을 수 있습니다.
- 고정된 바이트 수로 시작하고, rebuffering 사이의 시간을 측정하고, rebuffering 사이의 시간이 애플리케이션에서 선택한 제한 내에 있을 때까지 queue 크기를 늘립니다.
Buffering element는 파이프라인의 어느 위치에나 삽입할 수 있습니다. 예를 들어 decoder 앞에 buffering element를 삽입할 수 있습니다. 이렇게 하면 시간에 따라 low/high 워터마크를 설정할 수 있습니다.
Playbin의 buffering 플래그는 parsing 된 데이터에 대한 buffering을 수행합니다. 이후 단계에서 buffering을 수행하는 또 다른 이점은 demuxer가 pull 모드에서 작동하도록 할 수 있다는 것입니다. 느린 네트워크 드라이브 (filesrc 포함)에서 데이터를 읽을 때 이는 buffering하는 흥미로운 방법이 될 수 있습니다.
Download buffering
+---------+ +---------+ +-------+
| httpsrc | | buffer | | demux |
| src - sink src - sink ....
+---------+ +----|----+ +-------+
V
file
서버가 고정 길이 파일을 클라이언트로 스트리밍하고 있다는 것을 알고 있으면 애플리케이션은 전체 파일을 디스크에 다운로드하도록 선택할 수 있습니다. Buffer element는 다운로드된 파일을 탐색할 수 있도록 push 또는 pull 기반 srcpad를 demuxer에게 제공합니다.
이 모드는 클라이언트가 서버에 있는 파일의 길이를 측정할 수 있는 경우에만 적합합니다.
이 경우 요청된 범위가 다운로드된 영역 + 버퍼 크기를 벗어나면 평소와 같이 buffering 메시지가 내보내집니다. Buffering 메시지에는 incremental 다운로드가 수행되고 있다는 표시도 포함됩니다. 이 플래그를 사용하면 응용 프로그램이 BUFFERING 쿼리 등을 사용하여 보다 지능적인 방식으로 buffering을 제어할 수 있습니다. Buffering strategies를 참조하세요.
Timeshift buffering
+---------+ +---------+ +-------+
| httpsrc | | buffer | | demux |
| src - sink src - sink ....
+---------+ +----|----+ +-------+
V
file-ringbuffer
이 모드에서는 서버 콘텐츠를 다운로드하기 위해 고정된 크기의 ringbuffer가 유지됩니다. 이를 통해 buffering 된 데이터를 검색할 수 있습니다. Ringbuffer의 크기에 따라 시간을 더 거슬러 올라갈 수 있습니다.
이 모드는 모든 라이브 스트림에 적합합니다. Incremental 다운로드 모드와 마찬가지로 timeshifting 다운로드가 진행 중이라는 표시와 함께 buffering 메시지가 표시됩니다.
Live buffering
라이브 파이프라인에서는 일반적으로 캡처와 playback element 사이에 고정된 latency가 발생합니다. 이 latency는 queue (예: jitterbuffer) 또는 기타 수단 (audiosink에서)에 의해 발생할 수 있습니다.
Buffering 메시지는 해당 라이브 파이프라인에서도 내보내질 수 있으며 사용자에게 latency buffering을 나타내는 역할을 합니다. 애플리케이션은 일반적으로 상태 변경으로 인한 이러한 buffering 메시지에 반응하지 않습니다.
Buffering strategies
다음은 buffering 메시지와 buffering 쿼리를 기반으로 다양한 buffering 전략을 구현하기 위한 몇 가지 아이디어입니다.
No-rebuffer strategy
중단 없이 재생이 계속되도록 파이프라인에 충분한 데이터를 buffering하고 싶을 때가 있습니다. 이를 구현하기 위해 알아야 할 것은 파일의 남은 총 재생 시간과 남은 총 다운로드 시간을 아는 것입니다. Buffering 시간이 재생 시간보다 짧으면 중단 없이 재생을 시작할 수 있습니다.
DURATION, POSITION 및 BUFFERING 쿼리를 통해 이 모든 정보를 사용할 수 있습니다. 현재 buffering 상태를 얻으려면 buffering 쿼리를 주기적으로 실행해야 합니다. 또한 최악의 경우 전체 파일을 저장할 만큼 큰 buffer가 필요합니다. 다운로드 buffering과 함께 이 buffering 전략을 사용하는 것이 가장 좋습니다 (Download buffering 참조).
코드는 다음과 같습니다.
#include <gst/gst.h>
GstState target_state;
static gboolean is_live;
static gboolean is_buffering;
static gboolean
buffer_timeout (gpointer data)
{
GstElement *pipeline = data;
GstQuery *query;
gboolean busy;
gint percent;
gint64 estimated_total;
gint64 position, duration;
guint64 play_left;
query = gst_query_new_buffering (GST_FORMAT_TIME);
if (!gst_element_query (pipeline, query))
return TRUE;
gst_query_parse_buffering_percent (query, &busy, &percent);
gst_query_parse_buffering_range (query, NULL, NULL, NULL, &estimated_total);
if (estimated_total == -1)
estimated_total = 0;
/* calculate the remaining playback time */
if (!gst_element_query_position (pipeline, GST_FORMAT_TIME, &position))
position = -1;
if (!gst_element_query_duration (pipeline, GST_FORMAT_TIME, &duration))
duration = -1;
if (duration != -1 && position != -1)
play_left = GST_TIME_AS_MSECONDS (duration - position);
else
play_left = 0;
g_message ("play_left %" G_GUINT64_FORMAT", estimated_total %" G_GUINT64_FORMAT
", percent %d", play_left, estimated_total, percent);
/* we are buffering or the estimated download time is bigger than the
* remaining playback time. We keep buffering. */
is_buffering = (busy || estimated_total * 1.1 > play_left);
if (!is_buffering)
gst_element_set_state (pipeline, target_state);
return is_buffering;
}
static void
on_message_buffering (GstBus *bus, GstMessage *message, gpointer user_data)
{
GstElement *pipeline = user_data;
gint percent;
/* no state management needed for live pipelines */
if (is_live)
return;
gst_message_parse_buffering (message, &percent);
if (percent < 100) {
/* buffering busy */
if (!is_buffering) {
is_buffering = TRUE;
if (target_state == GST_STATE_PLAYING) {
/* we were not buffering but PLAYING, PAUSE the pipeline. */
gst_element_set_state (pipeline, GST_STATE_PAUSED);
}
}
}
}
static void
on_message_async_done (GstBus *bus, GstMessage *message, gpointer user_data)
{
GstElement *pipeline = user_data;
if (!is_buffering)
gst_element_set_state (pipeline, target_state);
else
g_timeout_add (500, buffer_timeout, pipeline);
}
gint
main (gint argc,
gchar *argv[])
{
GstElement *pipeline;
GMainLoop *loop;
GstBus *bus;
GstStateChangeReturn ret;
/* init GStreamer */
gst_init (&argc, &argv);
loop = g_main_loop_new (NULL, FALSE);
/* make sure we have a URI */
if (argc != 2) {
g_print ("Usage: %s <URI>\n", argv[0]);
return -1;
}
/* set up */
pipeline = gst_element_factory_make ("playbin", "pipeline");
g_object_set (G_OBJECT (pipeline), "uri", argv[1], NULL);
g_object_set (G_OBJECT (pipeline), "flags", 0x697 , NULL);
bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
gst_bus_add_signal_watch (bus);
g_signal_connect (bus, "message::buffering",
(GCallback) on_message_buffering, pipeline);
g_signal_connect (bus, "message::async-done",
(GCallback) on_message_async_done, pipeline);
gst_object_unref (bus);
is_buffering = FALSE;
target_state = GST_STATE_PLAYING;
ret = gst_element_set_state (pipeline, GST_STATE_PAUSED);
switch (ret) {
case GST_STATE_CHANGE_SUCCESS:
is_live = FALSE;
break;
case GST_STATE_CHANGE_FAILURE:
g_warning ("failed to PAUSE");
return -1;
case GST_STATE_CHANGE_NO_PREROLL:
is_live = TRUE;
break;
default:
break;
}
/* now run */
g_main_loop_run (loop);
/* also clean up */
gst_element_set_state (pipeline, GST_STATE_NULL);
gst_object_unref (GST_OBJECT (pipeline));
g_main_loop_unref (loop);
return 0;
}
먼저 파이프라인을 PAUSED 상태로 설정하는 방법을 살펴보세요. Buffering이 필요할 때 preroll 상태에서 buffering 메시지를 받게 됩니다. Preroll (on_message_async_done)되면 buffering이 진행 중인지 확인하고, 그렇지 않은 경우 재생을 시작합니다. Buffering이 진행 중인 경우 buffering 상태를 polling 하기 위해 멈추기 (timeout) 시작합니다. 다운로드 예상 시간이 남은 재생 시간보다 적으면 재생을 시작합니다.
원문: Buffering (gstreamer.freedesktop.org)
Buffering
Buffering The purpose of buffering is to accumulate enough data in a pipeline so that playback can occur smoothly and without interruptions. It is typically done when reading from a (slow) and non-live network source but can also be used for live sources.
gstreamer.freedesktop.org
'IT와 개발 > GStreamer Study' 카테고리의 다른 글
Threads (1) | 2024.06.28 |
---|---|
Dynamic Controllable Parameters (1) | 2024.06.21 |
Clocks and synchronization in GStreamer (3) | 2024.06.07 |
Interfaces (3) | 2024.05.31 |
Metadata (91) | 2024.05.24 |