From 6c5ab2a474d29d5cf1f2b181c7797f9fb5886e89 Mon Sep 17 00:00:00 2001 From: Stephan Sundermann Date: Sun, 3 Aug 2014 17:41:31 +0200 Subject: sample: Add basic tutorial 5 sample --- samples/BasicTutorial5.cs | 368 ++++++++++++++++++++++++++++++++++++++++++++++ samples/Makefile.am | 6 +- 2 files changed, 373 insertions(+), 1 deletion(-) create mode 100644 samples/BasicTutorial5.cs (limited to 'samples') diff --git a/samples/BasicTutorial5.cs b/samples/BasicTutorial5.cs new file mode 100644 index 0000000..1a4d7f5 --- /dev/null +++ b/samples/BasicTutorial5.cs @@ -0,0 +1,368 @@ +// Authors +// Copyright (C) 2014 Stephan Sundermann + +using System; +using Gst; +using Gtk; +using System.Runtime.InteropServices; +using Gst.Video; + +namespace GstreamerSharp +{ + class Playback + { + static Element Playbin; + static Range Slider; + static TextView StreamsList; + static ulong silderUpdateSignalID; + + static State State; + static long Duration = -1; + static int ignoreCount = 0; + + static void HandleValueChanged (object sender, EventArgs e) + { + var range = (Range)sender; + var value = range.Value; + Playbin.SeekSimple (Format.Time, SeekFlags.Flush | SeekFlags.KeyUnit, (long)(value * Gst.Constants.SECOND)); + } + + // This method is called when the STOP button is clicked + static void HandleStop (object sender, EventArgs e) + { + Playbin.SetState (State.Ready); + } + + // This method is called when the PAUSE button is clicked + static void HandlePause (object sender, EventArgs e) + { + Playbin.SetState (State.Paused); + } + + // This method is called when the PLAY button is clicked + static void HandlePlay (object sender, EventArgs e) + { + Playbin.SetState (State.Playing); + + } + + static void HandleRealized (object sender, EventArgs e) + { + var widget = (Widget)sender; + var window = widget.Window; + ulong windowID = 0; + + // Retrieve window handler from GDK + switch (System.Environment.OSVersion.Platform) { + case PlatformID.Unix: + windowID = gdk_x11_window_get_xid (window.Handle); + break; + case PlatformID.Win32NT: + case PlatformID.Win32S: + case PlatformID.Win32Windows: + case PlatformID.WinCE: + windowID = (ulong) gdk_win32_drawable_get_handle (window.Handle); + break; + } + + Element overlay = null; + if(Playbin is Gst.Bin) + overlay = ((Gst.Bin) Playbin).GetByInterface (VideoOverlayAdapter.GType); + + VideoOverlayAdapter adapter = new VideoOverlayAdapter (overlay.Handle); + adapter.WindowHandle = windowID; + adapter.HandleEvents (true); + } + + // This function is called when the main window is closed + static void HandleDelete (object o, DeleteEventArgs args) + { + HandleStop (null, null); + Gtk.Application.Quit (); + } + + //This function is called everytime the video window needs to be redrawn (due to damage/exposure, rescaling, etc). GStreamer takes care of this in the PAUSED and PLAYING states, otherwise, we simply draw a black rectangle to avoid garbage showing up. */ + static void HandleDamage (object o, DamageEventArgs args) + { + var widget = (Widget)o; + + if (State != State.Paused && State != State.Playing) { + var window = widget.Window; + var allocation = widget.Allocation; + + var cr = Gdk.CairoHelper.Create (window); + cr.SetSourceRGB (0, 0, 0); + cr.Rectangle (0, 0, allocation.Width, allocation.Height); + cr.Fill (); + cr.Dispose (); + } + + args.RetVal = false; + } + + static void CreateUI () { + var mainWindow = new Window (WindowType.Toplevel); + mainWindow.DeleteEvent += HandleDelete; + + var videoWindow = new DrawingArea (); + videoWindow.DoubleBuffered = false; + videoWindow.Realized += HandleRealized; + videoWindow.DamageEvent += HandleDamage; + + var playButton = new Button (Stock.MediaPlay); + playButton.Clicked += HandlePlay; + + var pauseButton = new Button (Stock.MediaPause); + pauseButton.Clicked += HandlePause; + + var stopButton = new Button (Stock.MediaStop); + stopButton.Clicked += HandleStop; + + Slider = new HScale (0, 100, 1); + ((Scale)Slider).DrawValue = false; + Slider.ValueChanged += HandleValueChanged; + + StreamsList = new TextView (); + StreamsList.Editable = false; + + var controls = new HBox (false, 0); + controls.PackStart (playButton, false, false, 2); + controls.PackStart (pauseButton, false, false, 2); + controls.PackStart (stopButton, false, false, 2); + controls.PackStart (Slider, true, true, 2); + + var mainHBox = new HBox (false, 0); + mainHBox.PackStart (videoWindow, true, true, 0); + mainHBox.PackStart (StreamsList, false, false, 2); + + var mainBox = new VBox (false, 0); + mainBox.PackStart (mainHBox, true, true, 0); + mainBox.PackStart (controls, false, false, 0); + mainWindow.Add (mainBox); + mainWindow.SetDefaultSize (640, 480); + + mainWindow.ShowAll (); + } + + // This function is called periodically to refresh the GUI + static bool RefreshUI () { + var fmt = Format.Time; + long current = 0; + + // We do not want to update anything nless we are in the PAUSED or PLAYING states + if (State != State.Playing && State != State.Paused) + return true; + + // If we didn't know it yet, query the stream duration + if (Duration < 0) { + if (!Playbin.QueryDuration (fmt, out Duration)) + Console.WriteLine ("Could not query the current duration."); + else { + // Set the range of the silder to the clip duration, in SECONDS + Slider.SetRange (0, Duration / (double)Gst.Constants.SECOND); + } + } + + if (Playbin.QueryPosition (fmt, out current)) { + // Block the "value-changed" signal, so the HandleSlider function is not called (which would trigger a seek the user has not requested) + ignoreCount++; + Slider.ValueChanged -= HandleValueChanged; + // Set the position of the slider to the current pipeline position, in SECONDS + Slider.Value = current / (double)Gst.Constants.SECOND; + Slider.ValueChanged += HandleValueChanged; + + } + return true; + } + + + + // This function is called when an error message is posted on the bus + static void HandleTags (object sender, GLib.SignalArgs args) { + // We are possibly in the Gstreamer working thread, so we notify the main thread of this event through a message in the bus + var s = new Structure ("tags-changed"); + Playbin.PostMessage (new Message (Playbin, s)); + } + + // This function is called when an error message is posted on the bus + static void HandleError (object sender, GLib.SignalArgs args) { + var msg = (Message)args.Args [0]; + string debug; + GLib.GException exc; + msg.ParseError (out exc, out debug); + Console.WriteLine (string.Format ("Error received from element {0}: {1}", msg.Src.Name, exc.Message)); + Console.WriteLine ("Debugging information: {0}", debug); + // Set the pipeline to READY (which stops playback) + Playbin.SetState (State.Ready); + } + + // This function is called when an End-Of-Stream message is posted on the bus. We just set the pipelien to READY (which stops playback) + static void HandleEos (object sender, GLib.SignalArgs args) { + Console.WriteLine ("End-Of-Stream reached."); + Playbin.SetState (State.Ready); + } + + // This function is called when the pipeline changes states. We use it to keep track of the current state. + static void HandleStateChanged (object sender, GLib.SignalArgs args) { + var msg = (Message) args.Args [0]; + State oldState, newState, pendingState; + msg.ParseStateChanged (out oldState, out newState, out pendingState); + if (msg.Src == Playbin) { + State = newState; + Console.WriteLine ("State set to {0}", Element.StateGetName (newState)); + if (oldState == State.Ready && newState == State.Paused) { + // For extra responsiveness, we refresh the GUI as soon as we reach the PAUSED state + RefreshUI (); + } + } + + } + + // Extract metadata from all the streams and write it to the text widget in the GUI + static void AnalyzeStreams () { + TagList tags; + String str, totalStr; + uint rate; + + // Clean current contents of the widget + var text = StreamsList.Buffer; + text.Text = String.Empty; + + // Read some properties + var nVideo = (int) Playbin ["n-video"]; + var nAudio = (int) Playbin ["n-audio"]; + var nText = (int) Playbin ["n-text"]; + + for (int i = 0; i < nVideo; i++) { + // Retrieve the stream's video tags + tags = (TagList)Playbin.Emit ("get-video-tags", i); + + if (tags != null) { + totalStr = string.Format ("video stream {0}:\n", i); + text.InsertAtCursor (totalStr); + tags.GetString (Gst.Constants.TAG_VIDEO_CODEC, out str); + totalStr = string.Format (" codec: {0}\n", str != null ? str : "unknown"); + text.InsertAtCursor (totalStr); + } + } + + for (int i = 0; i < nAudio; i++) { + // Retrieve the stream's audio tags + tags = (TagList)Playbin.Emit ("get-audio-tags", i); + + if (tags != null) { + totalStr = string.Format ("audio stream {0}:\n", i); + text.InsertAtCursor (totalStr); + + str = String.Empty; + if (tags.GetString (Gst.Constants.TAG_AUDIO_CODEC, out str)) { + totalStr = string.Format (" codec: {0}\n", str); + text.InsertAtCursor (totalStr); + } + str = String.Empty; + + if (tags.GetString (Gst.Constants.TAG_LANGUAGE_CODE+"dr", out str)) { + totalStr = string.Format (" language: {0}\n", str); + text.InsertAtCursor (totalStr); + } + str = String.Empty; + + if (tags.GetUint (Gst.Constants.TAG_BITRATE, out rate)) { + totalStr = string.Format (" bitrate: {0}\n", rate); + text.InsertAtCursor (totalStr); + } + } + } + + for (int i = 0; i < nText; i++) { + // Retrieve the stream's text tags + tags = (TagList)Playbin.Emit ("get-text-tags", i); + + if (tags != null) { + totalStr = string.Format ("subtitle stream {0}:\n", i); + text.InsertAtCursor (totalStr); + + if (tags.GetString (Gst.Constants.TAG_LANGUAGE_CODE, out str)) { + totalStr = string.Format (" language: {0}\n", str); + text.InsertAtCursor (totalStr); + } + } + } + } + + // This function is called when an "application" message is posted on the bus. Here we retrieve the message posted by the HandleTags callback + static void HandleApplication (object sender, GLib.SignalArgs args) { + var msg = (Message)args.Args [0]; + + if (msg.Structure.Name.Equals ("tags-changed")) { + // If the message is the "tags-changed" (only one we are currently issuing), update the stream info GUI + AnalyzeStreams (); + } + } + + public static void Main (string[] args) + { + // Initialize GTK + Gtk.Application.Init (); + + // Initialize Gstreamer + Gst.Application.Init(ref args); + + // Create the elements + Playbin = ElementFactory.Make ("playbin", "playbin"); + + if (Playbin == null) { + Console.WriteLine ("Not all elements could be created"); + return; + } + + // Set the URI to play. + //Playbin ["uri"] = "http://download.blender.org/durian/trailer/sintel_trailer-1080p.mp4"; + Playbin ["uri"] = "file:///home/stephan/Downloads/sintel_trailer-1080p.mp4"; + + // Connect to interesting signals in playbin + Playbin.Connect ("video-tags-changed", HandleTags); + Playbin.Connect ("audio-tags-changed", HandleTags); + Playbin.Connect ("text-tags-changed", HandleTags); + + + // Create the GUI + CreateUI (); + + // Instruct the bus to emit signals for each received message, and connect to the interesting signals + var bus = Playbin.Bus; + bus.AddSignalWatch (); + bus.Connect ("message::error", HandleError); + bus.Connect ("message::eos", HandleEos); + bus.Connect ("message::state-changed", HandleStateChanged); + bus.Connect ("message::application", HandleApplication); + + + // Start playing + var ret = Playbin.SetState (State.Playing); + if (ret == StateChangeReturn.Failure) { + Console.WriteLine ("Unable to set the pipeline to the playing state."); + return; + } + + // Register a function that GLib will call every second + GLib.Timeout.Add (1, RefreshUI); + + // Start the GTK main loop- We will not regain control until gtk_main_quit is called + Gtk.Application.Run (); + + // Free resources + Playbin.SetState (State.Null); + + } + + [DllImport ("libgdk-3.so.0") ] + static extern uint gdk_x11_window_get_xid (IntPtr handle); + + [DllImport ("libgdk-win32-3.0-0.dll") ] + static extern IntPtr gdk_win32_drawable_get_handle (IntPtr handle); + + [DllImport ("libX11.so.6")] + static extern int XInitThreads (); + } +} \ No newline at end of file diff --git a/samples/Makefile.am b/samples/Makefile.am index b161e74..06df1e8 100644 --- a/samples/Makefile.am +++ b/samples/Makefile.am @@ -1,4 +1,4 @@ -TARGETS = playback.exe video-overlay.exe basic-tutorial-1.exe basic-tutorial-2.exe basic-tutorial-3.exe basic-tutorial-4.exe +TARGETS = playback.exe video-overlay.exe basic-tutorial-1.exe basic-tutorial-2.exe basic-tutorial-3.exe basic-tutorial-4.exe basic-tutorial-5.exe DEBUGS = $(addsuffix .mdb, $(TARGETS)) assemblies = \ @@ -27,10 +27,14 @@ basic-tutorial-3.exe: $(srcdir)/BasicTutorial3.cs $(assemblies) basic-tutorial-4.exe: $(srcdir)/BasicTutorial4.cs $(assemblies) $(CSC) $(CSFLAGS) -out:basic-tutorial-4.exe $(references) $(GLIB_SHARP_LIBS) $(srcdir)/BasicTutorial4.cs +basic-tutorial-5.exe: $(srcdir)/BasicTutorial5.cs $(assemblies) + $(CSC) $(CSFLAGS) -out:basic-tutorial-5.exe $(references) $(GTK_SHARP_LIBS) $(srcdir)/BasicTutorial5.cs + EXTRA_DIST = \ Playback.cs \ VideoOverlay.cs \ BasicTutorial1.cs \ BasicTutorial2.cs \ BasicTutorial3.cs \ + BasicTutorial4.cs \ BasicTutorial4.cs -- cgit v1.2.3