mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-01-31 11:32:38 +00:00
sample: Add basic tutorial 5 sample
This commit is contained in:
parent
54812a36ed
commit
6c5ab2a474
2 changed files with 373 additions and 1 deletions
368
samples/BasicTutorial5.cs
Normal file
368
samples/BasicTutorial5.cs
Normal file
|
@ -0,0 +1,368 @@
|
|||
// Authors
|
||||
// Copyright (C) 2014 Stephan Sundermann <stephansundermann@gmail.com>
|
||||
|
||||
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 ();
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue