gst-play: Support track change on playbin3
authorSeungha Yang <sh.yang@lge.com>
Thu, 16 Mar 2017 08:52:04 +0000 (17:52 +0900)
committerEdward Hervey <bilboed@bilboed.com>
Fri, 27 Oct 2017 07:45:39 +0000 (09:45 +0200)
* playbin3 does not support {current,n}-{audio,video,text}
properties, and they were replaced by GstStreams API.
So, GstStreams API and select-stream event should be used
for track change in case of playbin3.
see also https://bugzilla.gnome.org/show_bug.cgi?id=769079

* By using commend line option "--use-playbin3", gst-play will
use playbin3 regardless of "USE_PLAYBIN" env variable.

https://bugzilla.gnome.org/show_bug.cgi?id=775469

tools/gst-play.c

index 88c51f9..fcca5b7 100644 (file)
@@ -72,6 +72,14 @@ typedef struct
 
   GstElement *playbin;
 
+  /* playbin3 variables */
+  gboolean is_playbin3;
+  GstStreamCollection *collection;
+  gchar *cur_audio_sid;
+  gchar *cur_video_sid;
+  gchar *cur_text_sid;
+  GMutex selection_lock;
+
   GMainLoop *loop;
   guint bus_watch;
   guint timeout;
@@ -139,12 +147,18 @@ gst_play_printf (const gchar * format, ...)
 static GstPlay *
 play_new (gchar ** uris, const gchar * audio_sink, const gchar * video_sink,
     gboolean gapless, gdouble initial_volume, gboolean verbose,
-    const gchar * flags_string)
+    const gchar * flags_string, gboolean use_playbin3)
 {
   GstElement *sink, *playbin;
   GstPlay *play;
 
-  playbin = gst_element_factory_make ("playbin", "playbin");
+
+  if (use_playbin3) {
+    playbin = gst_element_factory_make ("playbin3", "playbin");
+  } else {
+    playbin = gst_element_factory_make ("playbin", "playbin");
+  }
+
   if (playbin == NULL)
     return NULL;
 
@@ -156,6 +170,16 @@ play_new (gchar ** uris, const gchar * audio_sink, const gchar * video_sink,
 
   play->playbin = playbin;
 
+  if (use_playbin3) {
+    play->is_playbin3 = TRUE;
+  } else {
+    const gchar *env = g_getenv ("USE_PLAYBIN3");
+    if (env && g_str_has_prefix (env, "1"))
+      play->is_playbin3 = TRUE;
+  }
+
+  g_mutex_init (&play->selection_lock);
+
   if (audio_sink != NULL) {
     if (strchr (audio_sink, ' ') != NULL)
       sink = gst_parse_bin_from_description (audio_sink, TRUE, NULL);
@@ -244,6 +268,15 @@ play_free (GstPlay * play)
   g_main_loop_unref (play->loop);
 
   g_strfreev (play->uris);
+
+  if (play->collection)
+    gst_object_unref (play->collection);
+  g_free (play->cur_audio_sid);
+  g_free (play->cur_video_sid);
+  g_free (play->cur_text_sid);
+
+  g_mutex_clear (&play->selection_lock);
+
   g_free (play);
 }
 
@@ -498,6 +531,63 @@ play_bus_msg (GstBus * bus, GstMessage * msg, gpointer user_data)
       g_free (val_str);
       break;
     }
+    case GST_MESSAGE_STREAM_COLLECTION:
+    {
+      GstStreamCollection *collection = NULL;
+      gst_message_parse_stream_collection (msg, &collection);
+
+      if (collection) {
+        g_mutex_lock (&play->selection_lock);
+        gst_object_replace ((GstObject **) & play->collection,
+            (GstObject *) collection);
+        g_mutex_unlock (&play->selection_lock);
+      }
+      break;
+    }
+    case GST_MESSAGE_STREAMS_SELECTED:
+    {
+      GstStreamCollection *collection = NULL;
+      guint i, len;
+
+      gst_message_parse_streams_selected (msg, &collection);
+      if (collection) {
+        g_mutex_lock (&play->selection_lock);
+        gst_object_replace ((GstObject **) & play->collection,
+            (GstObject *) collection);
+
+        /* Free all last stream-ids */
+        g_free (play->cur_audio_sid);
+        g_free (play->cur_video_sid);
+        g_free (play->cur_text_sid);
+        play->cur_audio_sid = NULL;
+        play->cur_video_sid = NULL;
+        play->cur_text_sid = NULL;
+
+        len = gst_message_streams_selected_get_size (msg);
+        for (i = 0; i < len; i++) {
+          GstStream *stream = gst_message_streams_selected_get_stream (msg, i);
+          if (stream) {
+            GstStreamType type = gst_stream_get_stream_type (stream);
+            const gchar *stream_id = gst_stream_get_stream_id (stream);
+
+            if (type & GST_STREAM_TYPE_AUDIO) {
+              play->cur_audio_sid = g_strdup (stream_id);
+            } else if (type & GST_STREAM_TYPE_VIDEO) {
+              play->cur_video_sid = g_strdup (stream_id);
+            } else if (type & GST_STREAM_TYPE_TEXT) {
+              play->cur_text_sid = g_strdup (stream_id);
+            } else {
+              g_print ("Unknown stream type with stream-id %s", stream_id);
+            }
+            gst_object_unref (stream);
+          }
+        }
+
+        gst_object_unref (collection);
+        g_mutex_unlock (&play->selection_lock);
+      }
+      break;
+    }
     default:
       if (gst_is_missing_plugin_message (msg)) {
         gchar *desc;
@@ -943,6 +1033,44 @@ play_switch_trick_mode (GstPlay * play)
   }
 }
 
+static GstStream *
+play_get_nth_stream_in_collection (GstPlay * play, guint index,
+    GstPlayTrackType track_type)
+{
+  guint len, i, n_streams = 0;
+  GstStreamType target_type;
+
+  switch (track_type) {
+    case GST_PLAY_TRACK_TYPE_AUDIO:
+      target_type = GST_STREAM_TYPE_AUDIO;
+      break;
+    case GST_PLAY_TRACK_TYPE_VIDEO:
+      target_type = GST_STREAM_TYPE_VIDEO;
+      break;
+    case GST_PLAY_TRACK_TYPE_SUBTITLE:
+      target_type = GST_STREAM_TYPE_TEXT;
+      break;
+    default:
+      return NULL;
+  }
+
+  len = gst_stream_collection_get_size (play->collection);
+
+  for (i = 0; i < len; i++) {
+    GstStream *stream = gst_stream_collection_get_stream (play->collection, i);
+    GstStreamType type = gst_stream_get_stream_type (stream);
+
+    if (type & target_type) {
+      if (index == n_streams)
+        return stream;
+
+      n_streams++;
+    }
+  }
+
+  return NULL;
+}
+
 static void
 play_cycle_track_selection (GstPlay * play, GstPlayTrackType track_type)
 {
@@ -950,6 +1078,48 @@ play_cycle_track_selection (GstPlay * play, GstPlayTrackType track_type)
   gint cur = -1, n = -1;
   guint flag, cur_flags;
 
+  /* playbin3 variables */
+  GList *selected_streams = NULL;
+  gint cur_audio_idx = -1, cur_video_idx = -1, cur_text_idx = -1;
+  gint nb_audio = 0, nb_video = 0, nb_text = 0;
+  guint len, i;
+
+  g_mutex_lock (&play->selection_lock);
+  if (play->is_playbin3) {
+    if (!play->collection) {
+      g_print ("No stream-collection\n");
+      g_mutex_unlock (&play->selection_lock);
+      return;
+    }
+
+    /* Check the total number of streams of each type */
+    len = gst_stream_collection_get_size (play->collection);
+    for (i = 0; i < len; i++) {
+      GstStream *stream =
+          gst_stream_collection_get_stream (play->collection, i);
+      if (stream) {
+        GstStreamType type = gst_stream_get_stream_type (stream);
+        const gchar *sid = gst_stream_get_stream_id (stream);
+
+        if (type & GST_STREAM_TYPE_AUDIO) {
+          if (play->cur_audio_sid && !g_strcmp0 (play->cur_audio_sid, sid))
+            cur_audio_idx = nb_audio;
+          nb_audio++;
+        } else if (type & GST_STREAM_TYPE_VIDEO) {
+          if (play->cur_video_sid && !g_strcmp0 (play->cur_video_sid, sid))
+            cur_video_idx = nb_video;
+          nb_video++;
+        } else if (type & GST_STREAM_TYPE_TEXT) {
+          if (play->cur_text_sid && !g_strcmp0 (play->cur_text_sid, sid))
+            cur_text_idx = nb_text;
+          nb_text++;
+        } else {
+          g_print ("Unknown stream type with stream-id %s", sid);
+        }
+      }
+    }
+  }
+
   switch (track_type) {
     case GST_PLAY_TRACK_TYPE_AUDIO:
       prop_get = "get-audio-tags";
@@ -957,6 +1127,18 @@ play_cycle_track_selection (GstPlay * play, GstPlayTrackType track_type)
       prop_n = "n-audio";
       name = "audio";
       flag = 0x2;
+      if (play->is_playbin3) {
+        n = nb_audio;
+        cur = cur_audio_idx;
+        if (play->cur_video_sid) {
+          selected_streams =
+              g_list_append (selected_streams, play->cur_video_sid);
+        }
+        if (play->cur_text_sid) {
+          selected_streams =
+              g_list_append (selected_streams, play->cur_text_sid);
+        }
+      }
       break;
     case GST_PLAY_TRACK_TYPE_VIDEO:
       prop_get = "get-video-tags";
@@ -964,6 +1146,18 @@ play_cycle_track_selection (GstPlay * play, GstPlayTrackType track_type)
       prop_n = "n-video";
       name = "video";
       flag = 0x1;
+      if (play->is_playbin3) {
+        n = nb_video;
+        cur = cur_video_idx;
+        if (play->cur_audio_sid) {
+          selected_streams =
+              g_list_append (selected_streams, play->cur_audio_sid);
+        }
+        if (play->cur_text_sid) {
+          selected_streams =
+              g_list_append (selected_streams, play->cur_text_sid);
+        }
+      }
       break;
     case GST_PLAY_TRACK_TYPE_SUBTITLE:
       prop_get = "get-text-tags";
@@ -971,30 +1165,54 @@ play_cycle_track_selection (GstPlay * play, GstPlayTrackType track_type)
       prop_n = "n-text";
       name = "subtitle";
       flag = 0x4;
+      if (play->is_playbin3) {
+        n = nb_text;
+        cur = cur_text_idx;
+        if (play->cur_audio_sid) {
+          selected_streams =
+              g_list_append (selected_streams, play->cur_audio_sid);
+        }
+        if (play->cur_video_sid) {
+          selected_streams =
+              g_list_append (selected_streams, play->cur_video_sid);
+        }
+      }
       break;
     default:
       return;
   }
 
-  g_object_get (play->playbin, prop_cur, &cur, prop_n, &n, "flags", &cur_flags,
-      NULL);
+  if (play->is_playbin3) {
+    if (n > 0) {
+      if (cur < 0)
+        cur = 0;
+      else
+        cur = (cur + 1) % (n + 1);
+    }
+  } else {
+    g_object_get (play->playbin, prop_cur, &cur, prop_n, &n, "flags",
+        &cur_flags, NULL);
+
+    if (!(cur_flags & flag))
+      cur = 0;
+    else
+      cur = (cur + 1) % (n + 1);
+  }
 
   if (n < 1) {
     g_print ("No %s tracks.\n", name);
+    g_mutex_unlock (&play->selection_lock);
   } else {
     gchar *lcode = NULL, *lname = NULL;
     const gchar *lang = NULL;
     GstTagList *tags = NULL;
 
-    if (!(cur_flags & flag))
-      cur = 0;
-    else
-      cur = (cur + 1) % (n + 1);
-
     if (cur >= n && track_type != GST_PLAY_TRACK_TYPE_VIDEO) {
       cur = -1;
       g_print ("Disabling %s.           \n", name);
-      if (cur_flags & flag) {
+      if (play->is_playbin3) {
+        /* Just make it empty for the track type */
+      } else if (cur_flags & flag) {
         cur_flags &= ~flag;
         g_object_set (play->playbin, "flags", cur_flags, NULL);
       }
@@ -1002,11 +1220,27 @@ play_cycle_track_selection (GstPlay * play, GstPlayTrackType track_type)
       /* For video we only want to switch between streams, not disable it altogether */
       if (cur >= n)
         cur = 0;
-      if (!(cur_flags & flag) && track_type != GST_PLAY_TRACK_TYPE_VIDEO) {
-        cur_flags |= flag;
-        g_object_set (play->playbin, "flags", cur_flags, NULL);
+
+      if (play->is_playbin3) {
+        GstStream *stream;
+
+        stream = play_get_nth_stream_in_collection (play, cur, track_type);
+        if (stream) {
+          selected_streams = g_list_append (selected_streams,
+              (gchar *) gst_stream_get_stream_id (stream));
+          tags = gst_stream_get_tags (stream);
+        } else {
+          g_print ("Collection has no stream for track %d of %d.\n",
+              cur + 1, n);
+        }
+      } else {
+        if (!(cur_flags & flag) && track_type != GST_PLAY_TRACK_TYPE_VIDEO) {
+          cur_flags |= flag;
+          g_object_set (play->playbin, "flags", cur_flags, NULL);
+        }
+        g_signal_emit_by_name (play->playbin, prop_get, cur, &tags);
       }
-      g_signal_emit_by_name (play->playbin, prop_get, cur, &tags);
+
       if (tags != NULL) {
         if (gst_tag_list_get_string (tags, GST_TAG_LANGUAGE_CODE, &lcode))
           lang = gst_tag_get_language_name (lcode);
@@ -1020,10 +1254,20 @@ play_cycle_track_selection (GstPlay * play, GstPlayTrackType track_type)
       else
         g_print ("Switching to %s track %d of %d.\n", name, cur + 1, n);
     }
-    g_object_set (play->playbin, prop_cur, cur, NULL);
     g_free (lcode);
     g_free (lname);
+    g_mutex_unlock (&play->selection_lock);
+
+    if (play->is_playbin3) {
+      gst_element_send_event (play->playbin,
+          gst_event_new_select_streams (selected_streams));
+    } else {
+      g_object_set (play->playbin, prop_cur, cur, NULL);
+    }
   }
+
+  if (selected_streams)
+    g_list_free (selected_streams);
 }
 
 static void
@@ -1184,6 +1428,7 @@ main (int argc, char **argv)
   GError *err = NULL;
   GOptionContext *ctx;
   gchar *playlist_file = NULL;
+  gboolean use_playbin3 = FALSE;
   GOptionEntry options[] = {
     {"verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose,
         N_("Output status information and property notifications"), NULL},
@@ -1209,6 +1454,10 @@ main (int argc, char **argv)
         N_("Playlist file containing input media files"), NULL},
     {"quiet", 'q', 0, G_OPTION_ARG_NONE, &quiet,
         N_("Do not print any output (apart from errors)"), NULL},
+    {"use-playbin3", 0, 0, G_OPTION_ARG_NONE, &use_playbin3,
+          N_("Use playbin3 pipeline")
+          N_("(default varies depending on 'USE_PLAYBIN' env variable)"),
+        NULL},
     {G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &filenames, NULL},
     {NULL}
   };
@@ -1314,8 +1563,8 @@ main (int argc, char **argv)
     shuffle_uris (uris, num);
 
   /* prepare */
-  play =
-      play_new (uris, audio_sink, video_sink, gapless, volume, verbose, flags);
+  play = play_new (uris, audio_sink, video_sink, gapless, volume, verbose,
+      flags, use_playbin3);
 
   if (play == NULL) {
     g_printerr