// Authors // Copyright (C) 2014 Stephan Sundermann using System; using Gst; using System.Runtime.InteropServices; namespace GstreamerSharp { class Playback { const int ChunkSize = 1024; const int SampleRate = 44100; static Gst.App.AppSink AppSink; static Gst.App.AppSrc AppSource; static Element Pipeline, Tee, AudioQueue, AudioConvert1, AudioResample, AudioSink; static Element VideoQueue, AudioConvert2, Visual, VideoConvert, VideoSink; static Element AppQueue; static long NumSamples; // Number of samples generated so far (for timestamp generation) static float a, b, c, d; // For waveform generation static uint Sourceid; // To control the GSource static GLib.MainLoop MainLoop; // GLib's Main Loop // This method is called by the idle GSource in the mainloop, to feed CHUNK_SIZE bytes into appsrc. // The idle handler is added to the mainloop when appsrc requests us to start sending data (need-data signal) // and is removed when appsrc has enough data (enough-data signal). static bool PushData () { var numSamples = ChunkSize / 2; // Because each sample is 16 bits MapInfo map; // Create a new empty buffer var buffer = new Gst.Buffer (null, ChunkSize, AllocationParams.Zero); // Set its timestamp and duration buffer.Pts = Util.Uint64Scale ((ulong)NumSamples, (ulong)Constants.SECOND, (ulong)SampleRate); buffer.Dts = Util.Uint64Scale ((ulong)NumSamples, (ulong)Constants.SECOND, (ulong)SampleRate); buffer.Duration = Util.Uint64Scale ((ulong)NumSamples, (ulong)Constants.SECOND, (ulong)SampleRate); // Generate some psychodelic waveforms buffer.Map (out map, MapFlags.Write); c += d; d -= c / 1000f; var freq = 1100f + 1000f * d; short[] data = new short[numSamples]; for (int i = 0; i < numSamples; i++) { a += b; b -= a / freq; data[i] = (short)(500f * a); } // convert the short[] to a byte[] by marshalling var native = Marshal.AllocHGlobal (data.Length * sizeof(short)); Marshal.Copy (data, 0, native, data.Length); byte[] bytedata = new byte[2 * data.Length]; Marshal.Copy (native, bytedata, 0, data.Length * sizeof(short)); map.Data = bytedata; buffer.Unmap (map); NumSamples += numSamples; // Push the buffer into the appsrc var ret = AppSource.PushBuffer (buffer); // Free the buffer now that we are done with it buffer.Dispose (); if (ret != FlowReturn.Ok) { // We got some error, stop sending data return false; } return true; } // This signal callback triggers when appsrc needs Here, we add an idle handler // to the mainloop to start pushing data into the appsrc static void StartFeed (object sender, Gst.App.NeedDataArgs args) { if (Sourceid == 0) { Console.WriteLine ("Start feeding"); Sourceid = GLib.Idle.Add (PushData); } } // This callback triggers when appsrc has enough data and we can stop sending. // We remove the idle handler from the mainloop static void StopFeed (object sender, EventArgs args) { if (Sourceid != 0) { Console.WriteLine ("Stop feeding"); GLib.Source.Remove (Sourceid); Sourceid = 0; } } // The appsink has received a buffer static void NewSample (object sender, GLib.SignalArgs args) { var sink = (Gst.App.AppSink)sender; // Retrieve the buffer var sample = sink.PullSample (); if (sample != null) { // The only thing we do in this example is print a * to indicate a received buffer Console.Write ("*"); sample.Dispose (); } } // This function is called when an error message is posted on the bus static void HandleError (object sender, GLib.SignalArgs args) { GLib.GException err; string debug; var msg = (Message) args.Args[0]; // Print error details on the screen msg.ParseError (out err, out debug); Console.WriteLine ("Error received from element {0}: {1}", msg.Src.Name, err.Message); Console.WriteLine ("Debugging information: {0}", debug != null ? debug : "none"); MainLoop.Quit (); } public static void Main (string[] args) { b = 1; d = 1; Gst.Audio.AudioInfo info = new Gst.Audio.AudioInfo(); // Initialize Gstreamer Gst.Application.Init(ref args); // Create the elements AppSource = new Gst.App.AppSrc ("app_src"); Tee = ElementFactory.Make ("tee", "tee"); AudioQueue = ElementFactory.Make ("queue", "audio_queue"); AudioConvert1 = ElementFactory.Make ("audioconvert", "audio_convert1"); AudioResample = ElementFactory.Make ("audioresample", "audio_resample"); AudioSink = ElementFactory.Make ("autoaudiosink", "audio_sink"); VideoQueue = ElementFactory.Make ("queue", "video_queue"); AudioConvert2 = ElementFactory.Make ("audioconvert", "audio_convert2"); Visual = ElementFactory.Make ("wavescope", "visual"); VideoConvert = ElementFactory.Make ("videoconvert", "video_convert"); VideoSink = ElementFactory.Make ("autovideosink", "video_sink"); AppQueue = ElementFactory.Make ("queue", "app_queue"); AppSink = new Gst.App.AppSink ("app_sink"); // Create the empty pipeline var pipeline = new Pipeline ("test-pipeline"); if (AppSource == null || Tee == null || AudioQueue == null || AudioConvert1 == null || AudioResample == null || AudioSink == null || VideoQueue == null || AudioConvert2 == null || Visual == null || VideoConvert == null || AppQueue == null || AppSink == null ||pipeline == null) { Console.WriteLine ("Not all elements could be created."); return; } // Configure wavescope Visual ["shader"] = 0; Visual ["style"] = 0; // Configure appsrc Gst.Audio.AudioChannelPosition[] position = {}; info.SetFormat (Gst.Audio.AudioFormat.S16, SampleRate, 1, position); var audioCaps = info.ToCaps (); AppSource ["caps"] = audioCaps; AppSource ["format"] = Format.Time; AppSource.NeedData += StartFeed; AppSource.EnoughData += StopFeed; // Configure appsink AppSink ["emit-signals"] = true; AppSink ["caps"] = audioCaps; AppSink.NewSample += NewSample; // Link all elements that can be automatically linked because they have "Always" pads pipeline.Add (AppSource, Tee, AudioQueue, AudioConvert1, AudioResample, AudioSink, VideoQueue, AudioConvert2, Visual, VideoConvert, VideoSink, AppQueue, AppSink); if (!Element.Link (AppSource, Tee) || !Element.Link (AudioQueue, AudioConvert1, AudioResample, AudioSink) || !Element.Link (VideoQueue, AudioConvert2, Visual, VideoConvert, VideoSink) || !Element.Link (AppQueue, AppSink)) { Console.WriteLine ("Elements could not be linked."); return; } // Manually link the Tee, which has "Request" pads var teeSrcPadTemplate = Tee.GetPadTemplate ("src_%u"); var teeAudioPad = Tee.RequestPad (teeSrcPadTemplate); Console.WriteLine ("Obtained request pad {0} for audio branch.", teeAudioPad.Name); var queueAudioPad = AudioQueue.GetStaticPad ("sink"); var teeVideoPad = Tee.RequestPad (teeSrcPadTemplate); Console.WriteLine ("Obtained request pad {0} for video branch.", teeVideoPad.Name); var queueVideoPad = VideoQueue.GetStaticPad ("sink"); var teeAppPad = Tee.RequestPad (teeSrcPadTemplate); Console.WriteLine ("Obtained request pad {0} for app branch.", teeAppPad.Name); var queueAppPad = AppQueue.GetStaticPad ("sink"); if (teeAudioPad.Link (queueAudioPad) != PadLinkReturn.Ok || teeVideoPad.Link (queueVideoPad) != PadLinkReturn.Ok || teeAppPad.Link (queueAppPad) != PadLinkReturn.Ok) { Console.WriteLine ("Tee could not be linked"); return; } // Instruct the bus to emit signals for each received message, and connect to the interesting signals var bus = pipeline.Bus; bus.AddSignalWatch (); bus.Connect ("message::error", HandleError); // Start playing the pipeline pipeline.SetState (State.Playing); // Create a GLib Main Loop and set it to run MainLoop = new GLib.MainLoop (); MainLoop.Run (); // Release the request pads from the Tee, and unref them Tee.ReleaseRequestPad(teeAudioPad); Tee.ReleaseRequestPad(teeVideoPad); Tee.ReleaseRequestPad(teeAppPad); // Free resources pipeline.SetState (State.Null); Gst.Global.Deinit(); } } }