1. 程式人生 > >GStreamer基礎教程07 - 播放速率控制

GStreamer基礎教程07 - 播放速率控制

摘要

  在常見的媒體播放器中,通常可以看到快進,快退,慢放等功能,這部分功能被稱為“特技模式(Trick Mode)”,這些模式有個共同點:都通過修改播放的速率來達到相應的目的。 本文將介紹如何通過GStreamer去實現快進,快退,慢放以及單幀播放。

 

GStreamer Seek與Step事件

  快進(Fast-Forward),快退(Fast-Rewind)和慢放(Slow-Motion)都是通過修改播放的速率來達到相應的目的。在GStreamer中,將1倍速作為正常的播放速率,將大於1倍速的2倍,4倍,8倍等倍速稱為快進,慢放則是播放速率的絕對值小於1倍速,當播放速率小於0時,則進行倒放。

在GStreamer中,我們通過seek與step事件來控制Element的播放速率及區域。Step事件允許跳過指定的區域並設定後續的播放速率(此速率必須大於0)。Seek事件允許跳轉到播放檔案中的的任何位置,並且播放速率可以大於0或小於0.
  在播放時間控制中,我們使用gst_element_seek_simple 來快速的跳轉到指定的位置,此函式是對seek事件的封裝。實際使用時,我們首先需要構造一個seek event,設定seek的絕對起始位置和停止位置,停止位置可以設定為0,這樣會執行seek的播放速率直到結束。同時可以支援按buffer的方式進行seek,以及設定不同的標誌指定seek的行為。
  Step事件相較於Seek事件需要更少的引數,更易用於修改播放速率,但是不夠靈活。Step事件只會作用於最終的sink,Seek事件則可以作用於Pipeline中所有的Element。Step操作的效率高於Seek。
  在GStreamer中,單幀播放(Frame Stepping)與快進相同,也是通過事件實現。單幀播放通常在暫停的狀態下,構造併發送step event每次播放一幀。
  需要注意的是,seek event需要直接作用於sink element(eg: audio sink或video sink),如果直接將seek event作用於Pipeline,Pipeline會自動將事件轉發給所有的sink,如果有多個sink,就會造成多次seek。通常是先獲取Pipeline中的video-sink或audio-sink,然後傳送seek event到指定的sink,完成seek的操作。 Seek時間的構造及傳送示例如下:

   GstEvent *event;
   gboolean result;
   ...
   // construct a seek event to play the media from second 2 to 5, flush
   // the pipeline to decrease latency.
   event = gst_event_new_seek (1.0,
      GST_FORMAT_TIME,
      GST_SEEK_FLAG_FLUSH,
      GST_SEEK_TYPE_SET, 2 * GST_SECOND,
      GST_SEEK_TYPE_SET, 5 * GST_SECOND);
   ...
   result = gst_element_send_event (video_sink, event);
   if (!result)
     g_warning ("seek failed");
   ...

示例程式碼

下面通過一個完整的示例,來檢視GStreamer是如何通過seek和step達到相應的播放速度。

#include <string.h>
#include <stdio.h>
#include <gst/gst.h>

typedef struct _CustomData
{
  GstElement *pipeline;
  GstElement *video_sink;
  GMainLoop *loop;

  gboolean playing;             /* Playing or Paused */
  gdouble rate;                 /* Current playback rate (can be negative) */
} CustomData;

/* Send seek event to change rate */
static void
send_seek_event (CustomData * data)
{
  gint64 position;
  GstEvent *seek_event;

  /* Obtain the current position, needed for the seek event */
  if (!gst_element_query_position (data->pipeline, GST_FORMAT_TIME, &position)) {
    g_printerr ("Unable to retrieve current position.\n");
    return;
  }

  /* Create the seek event */
  if (data->rate > 0) {
    seek_event =
        gst_event_new_seek (data->rate, GST_FORMAT_TIME,
        GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE, GST_SEEK_TYPE_SET,
        position, GST_SEEK_TYPE_END, 0);
  } else {
    seek_event =
        gst_event_new_seek (data->rate, GST_FORMAT_TIME,
        GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE, GST_SEEK_TYPE_SET, 0,
        GST_SEEK_TYPE_SET, position);
  }

  if (data->video_sink == NULL) {
    /* If we have not done so, obtain the sink through which we will send the seek events */
    g_object_get (data->pipeline, "video-sink", &data->video_sink, NULL);
  }

  /* Send the event */
  gst_element_send_event (data->video_sink, seek_event);

  g_print ("Current rate: %g\n", data->rate);
}

/* Process keyboard input */
static gboolean
handle_keyboard (GIOChannel * source, GIOCondition cond, CustomData * data)
{
  gchar *str = NULL;

  if (g_io_channel_read_line (source, &str, NULL, NULL,
          NULL) != G_IO_STATUS_NORMAL) {
    return TRUE;
  }

  switch (g_ascii_tolower (str[0])) {
    case 'p':
      data->playing = !data->playing;
      gst_element_set_state (data->pipeline,
          data->playing ? GST_STATE_PLAYING : GST_STATE_PAUSED);
      g_print ("Setting state to %s\n", data->playing ? "PLAYING" : "PAUSE");
      break;
    case 's':
      if (g_ascii_isupper (str[0])) {
        data->rate *= 2.0;
      } else {
        data->rate /= 2.0;
      }
      send_seek_event (data);
      break;
    case 'd':
      data->rate *= -1.0;
      send_seek_event (data);
      break;
    case 'n':
      if (data->video_sink == NULL) {
        /* If we have not done so, obtain the sink through which we will send the step events */
        g_object_get (data->pipeline, "video-sink", &data->video_sink, NULL);
      }

      gst_element_send_event (data->video_sink,
          gst_event_new_step (GST_FORMAT_BUFFERS, 1, ABS (data->rate), TRUE,
              FALSE));
      g_print ("Stepping one frame\n");
      break;
    case 'q':
      g_main_loop_quit (data->loop);
      break;
    default:
      break;
  }

  g_free (str);

  return TRUE;
}

int
main (int argc, char *argv[])
{
  CustomData data;
  GstStateChangeReturn ret;
  GIOChannel *io_stdin;

  /* Initialize GStreamer */
  gst_init (&argc, &argv);

  /* Initialize our data structure */
  memset (&data, 0, sizeof (data));

  /* Print usage map */
  g_print ("USAGE: Choose one of the following options, then press enter:\n"
      " 'P' to toggle between PAUSE and PLAY\n"
      " 'S' to increase playback speed, 's' to decrease playback speed\n"
      " 'D' to toggle playback direction\n"
      " 'N' to move to next frame (in the current direction, better in PAUSE)\n"
      " 'Q' to quit\n");

  /* Build the pipeline */
  data.pipeline =
      gst_parse_launch
      ("playbin uri=https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm",
      NULL);

  /* Add a keyboard watch so we get notified of keystrokes */
#ifdef G_OS_WIN32
  io_stdin = g_io_channel_win32_new_fd (fileno (stdin));
#else
  io_stdin = g_io_channel_unix_new (fileno (stdin));
#endif
  g_io_add_watch (io_stdin, G_IO_IN, (GIOFunc) handle_keyboard, &data);

  /* Start playing */
  ret = gst_element_set_state (data.pipeline, GST_STATE_PLAYING);
  if (ret == GST_STATE_CHANGE_FAILURE) {
    g_printerr ("Unable to set the pipeline to the playing state.\n");
    gst_object_unref (data.pipeline);
    return -1;
  }
  data.playing = TRUE;
  data.rate = 1.0;

  /* Create a GLib Main Loop and set it to run */
  data.loop = g_main_loop_new (NULL, FALSE);
  g_main_loop_run (data.loop);

  /* Free resources */
  g_main_loop_unref (data.loop);
  g_io_channel_unref (io_stdin);
  gst_element_set_state (data.pipeline, GST_STATE_NULL);
  if (data.video_sink != NULL)
    gst_object_unref (data.video_sink);
  gst_object_unref (data.pipeline);
  return 0;
}

  通過下面的命令編譯即可得到可執行檔案,在終端輸入相應指令可修改播放速率。

gcc basic-tutorial-7.c -o basic-tutorial-7 `pkg-config --cflags --libs gstreamer-1.0`

原始碼分析

  本例中,Pipeline的建立與其他示例相同,通過playbin播放檔案,採用GLib的I/O介面來處理鍵盤輸入。

/* Process keyboard input */
static gboolean handle_keyboard (GIOChannel *source, GIOCondition cond, CustomData *data) {
  gchar *str = NULL;

  if (g_io_channel_read_line (source, &str, NULL, NULL, NULL) != G_IO_STATUS_NORMAL) {
    return TRUE;
  }

  switch (g_ascii_tolower (str[0])) {
  case 'p':
    data->playing = !data->playing;
    gst_element_set_state (data->pipeline, data->playing ? GST_STATE_PLAYING : GST_STATE_PAUSED);
    g_print ("Setting state to %s\n", data->playing ? "PLAYING" : "PAUSE");
    break;

  在終端輸入P時,使用gst_element_set_state ()設定播放狀態。

case 's':
  if (g_ascii_isupper (str[0])) {
    data->rate *= 2.0;
  } else {
    data->rate /= 2.0;
  }
  send_seek_event (data);
  break;
case 'd':
  data->rate *= -1.0;
  send_seek_event (data);
  break;

  通過S和s增加和降低播放速度,d用於改變播放方向(倒放),這裡在修改rate後,呼叫send_seek_event實現真正的處理。

/* Send seek event to change rate */
static void send_seek_event (CustomData *data) {
  gint64 position;
  GstEvent *seek_event;

  /* Obtain the current position, needed for the seek event */
  if (!gst_element_query_position (data->pipeline, GST_FORMAT_TIME, &position)) {
    g_printerr ("Unable to retrieve current position.\n");
    return;
  }

  這個函式會構造一個SeekEvent傳送到Pipeline以調節速率。因為Seek Event會跳轉到指定的位置,但我們在此例彙總只想改變速率,不跳轉到其他位置,所以首先通過gst_element_query_position ()獲取當前的播放位置。

/* Create the seek event */
if (data->rate > 0) {
  seek_event = gst_event_new_seek (data->rate, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE,
      GST_SEEK_TYPE_SET, position, GST_SEEK_TYPE_END, 0);
} else {
  seek_event = gst_event_new_seek (data->rate, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE,
      GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_SET, position);
}

  通過gst_event_new_seek()建立SeekEvent,設定新的rate,flag,起始位置,結束位置。需要注意的是,起始位置需要小於結束位置。

if (data->video_sink == NULL) {
  /* If we have not done so, obtain the sink through which we will send the seek events */
  g_object_get (data->pipeline, "video-sink", &data->video_sink, NULL);
}
/* Send the event */
gst_element_send_event (data->video_sink, seek_event);

  正如上文提到的,為了避免Pipeline執行多次的seek,我們在此處獲取video-sink,並向其傳送SeekEvent。我們直到執行Seek時才獲取video-sink是因為實際的sink有可能會根據不同的媒體型別,在PLAYING狀態時才建立。

  以上部分內容就是速率的修改,關於單幀播放的情況,實現方式更加簡單:

case 'n':
  if (data->video_sink == NULL) {
    /* If we have not done so, obtain the sink through which we will send the step events */
    g_object_get (data->pipeline, "video-sink", &data->video_sink, NULL);
  }

  gst_element_send_event (data->video_sink,
      gst_event_new_step (GST_FORMAT_BUFFERS, 1, ABS (data->rate), TRUE, FALSE));
  g_print ("Stepping one frame\n");
  break;

  我們通過gst_event_new_step()建立了StepEvent,並指定只跳轉一幀,且不改變當前速率。單幀播放通常都是先暫停,然後再進行單幀播放。

  

  以上就是通過GStreamer實現播放速率的控制,實際中,有些Element對倒放支援不是很好,不能達到理想的效果。

 

總結

通過本文我們掌握了:

  • 如何通過gst_event_new_seek()構造SeekEvent,通過gst_element_send_event()傳送到sink改變速率。
  • 如何通過gst_event_new_step()實現單幀播放。

 

引用

https://gstreamer.freedesktop.org/documentation/tutorials/basic/playback-speed.html?gi-language=c
https://gstreamer.freedesktop.org/documentation/additional/design/seeking.html?gi-language=c
https://gstreamer.freedesktop.org/documentation/additional/design/framestep.html?gi-language=c

 

作者:John.Leng 出處:http://www.cnblogs.com/xleng/ 本文版權歸作者所有,歡迎轉載。商業轉載請聯絡作者獲得授權,非商業轉載請在文章頁面明顯位置給出原文連線.