| // Authors | |
| // Copyright (C) 2014 Stephan Sundermann <stephansundermann@gmail.com> | |
| 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(); | |
| } | |
| } | |
| } |