[banshee] gst#: Add visualisation support



commit 7c89beef84e63d8e0835881d0d8c5753912b7a4c
Author: Olivier Dufour <olivier duff gmail com>
Date:   Fri Jun 3 21:34:17 2011 +0200

    gst#: Add visualisation support
    
    Signed-off-by: Bertrand Lorentz <bertrand lorentz gmail com>

 .../Banshee.GStreamerSharp.csproj                  |    1 +
 .../Banshee.GStreamerSharp.dll.config              |    1 +
 .../Banshee.GStreamerSharp/PlayerEngine.cs         |   39 +++-
 .../Banshee.GStreamerSharp/Visualization.cs        |  336 ++++++++++++++++++++
 src/Backends/Banshee.GStreamerSharp/Makefile.am    |    3 +-
 5 files changed, 378 insertions(+), 2 deletions(-)
---
diff --git a/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp.csproj b/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp.csproj
index 34da1e5..772d044 100644
--- a/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp.csproj
+++ b/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp.csproj
@@ -76,6 +76,7 @@
     <Compile Include="Banshee.GStreamerSharp\AudioCdRipper.cs" />
     <Compile Include="Banshee.GStreamerSharp\Transcoder.cs" />
     <Compile Include="Banshee.GStreamerSharp\VideoManager.cs" />
+    <Compile Include="Banshee.GStreamerSharp\Visualization.cs" />
   </ItemGroup>
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
 </Project>
diff --git a/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp.dll.config b/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp.dll.config
index f57ff6c..55c270c 100644
--- a/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp.dll.config
+++ b/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp.dll.config
@@ -1,4 +1,5 @@
 <configuration>
   <dllmap dll="libgdk-x11-2.0.so.0" target="libgdk-x11-2.0.so" os="!windows"/>
   <dllmap dll="libgdk-win32-2.0-0.dll" target="libgdk-win32-2.0-0.dll" os="windows"/>
+  <dllmap dll="libgstfft-0.10.dll" target="libgstfft-0.10.so"/>
 </configuration>
diff --git a/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp/PlayerEngine.cs b/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp/PlayerEngine.cs
index cf0e130..6a4adb9 100644
--- a/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp/PlayerEngine.cs
+++ b/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp/PlayerEngine.cs
@@ -51,7 +51,7 @@ using Banshee.Preferences;
 
 namespace Banshee.GStreamerSharp
 {
-    public class PlayerEngine : Banshee.MediaEngine.PlayerEngine, IEqualizer
+    public class PlayerEngine : Banshee.MediaEngine.PlayerEngine, IEqualizer, IVisualizationDataSource
     {
         private class AudioSinkBin : Bin
         {
@@ -62,14 +62,26 @@ namespace Banshee.GStreamerSharp
             Element preamp;
             Element first;
             GhostPad visible_sink;
+            Element audiotee;
             object pipeline_lock = new object ();
 
+            public AudioSinkBin (IntPtr o) : base(o)
+            {
+
+            }
+
             public AudioSinkBin (string elementName) : base(elementName)
             {
                 hw_audio_sink = SelectAudioSink ();
                 Add (hw_audio_sink);
                 first = hw_audio_sink;
 
+                // Our audio sink is a tee, so plugins can attach their own pipelines
+                audiotee = ElementFactory.Make ("tee", "audiotee");
+                if (audiotee == null) {
+                    Log.Error ("Can not create audio tee!");
+                }
+
                 volume = FindVolumeProvider (hw_audio_sink);
                 if (volume != null) {
                     // If the sink provides its own volume property we assume that it will
@@ -96,6 +108,13 @@ namespace Banshee.GStreamerSharp
                     Log.Debug ("Built and linked Equalizer");
                 }
 
+                // Link the first tee pad to the primary audio sink queue
+                Pad sinkpad = first.GetStaticPad ("sink");
+                Pad pad = audiotee.GetRequestPad ("src%d");
+                audiotee ["alloc-pad"] = pad;
+                pad.Link (sinkpad);
+                first = audiotee;
+
                 visible_sink = new GhostPad ("sink", first.GetStaticPad ("sink"));
                 AddPad (visible_sink);
             }
@@ -273,6 +292,10 @@ namespace Banshee.GStreamerSharp
                 }
             }
 
+            public Pad RequestTeePad ()
+            {
+                return audiotee.GetRequestPad ("src%d");
+            }
         }
 
 
@@ -283,6 +306,7 @@ namespace Banshee.GStreamerSharp
         ManualResetEvent next_track_set;
         CddaManager cdda_manager;
         VideoManager video_manager;
+        Visualization visualization;
 
         public PlayerEngine ()
         {
@@ -321,6 +345,9 @@ namespace Banshee.GStreamerSharp
                 Volume = (ushort)PlayerEngineService.VolumeSchema.Get ();
             }
 
+            Pad teepad = audio_sink.RequestTeePad ();
+            visualization = new Visualization (audio_sink, teepad);
+
             playbin.AddNotification ("volume", OnVolumeChanged);
             playbin.Bus.AddWatch (OnBusMessage);
             playbin.AboutToFinish += OnAboutToFinish;
@@ -352,6 +379,16 @@ namespace Banshee.GStreamerSharp
             base.Dispose ();
         }
 
+        public event VisualizationDataHandler DataAvailable {
+            add {
+                visualization.DataAvailable += value;
+            }
+
+            remove {
+                visualization.DataAvailable -= value;
+            }
+        }
+
         public override void VideoExpose (IntPtr window, bool direct)
         {
             video_manager.WindowExpose (window, direct);
diff --git a/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp/Visualization.cs b/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp/Visualization.cs
new file mode 100644
index 0000000..f55cb7e
--- /dev/null
+++ b/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp/Visualization.cs
@@ -0,0 +1,336 @@
+ï//
+// Visualization.cs
+//
+// Author:
+//   olivier dufour <olivier duff gmail com>
+//
+// Copyright (C) 2011 olivier dufour.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Runtime.InteropServices;
+
+using Gst;
+using Gst.Base;
+using Gst.CorePlugins;
+
+using Hyena;
+
+using Banshee.MediaEngine;
+
+namespace Banshee.GStreamerSharp
+{
+    public class Visualization
+    {
+        private readonly int SLICE_SIZE  = 735;
+        Element vis_resampler;
+        Adapter vis_buffer;
+        bool active;
+        bool vis_thawing;
+        IntPtr vis_fft;
+        GstFFTF32Complex[] vis_fft_buffer;
+        float[] vis_fft_sample_buffer;
+        uint wanted_size;
+
+        [StructLayout(LayoutKind.Sequential)]
+        struct GstFFTF32Complex {
+          public float r;
+          public float i;
+        };
+
+        enum FFTWindow
+        {
+          Rectangular,
+          Hamming,
+          Hann,
+          Bartlett,
+          Blackman
+        }
+
+        [DllImport ("libgstfft-0.10.dll")]
+        private static extern IntPtr gst_fft_f32_new (int len, bool inverse);
+
+        [DllImport ("libgstfft-0.10.dll")]
+        private static extern void gst_fft_f32_window (IntPtr self, [MarshalAs (UnmanagedType.LPArray)] float [] timedata, FFTWindow window);
+
+        [DllImport ("libgstfft-0.10.dll")]
+        private static extern void gst_fft_f32_fft (IntPtr self, [MarshalAs (UnmanagedType.LPArray)] float [] timedata, [MarshalAs (UnmanagedType.LPArray, ArraySubType=UnmanagedType.Struct)] GstFFTF32Complex [] freqdata);
+
+        [DllImport ("libgstfft-0.10.dll")]
+        private static extern void gst_fft_f32_free (IntPtr self);
+
+        public Visualization (Bin audiobin, Pad teepad)
+        {
+            // The basic pipeline we're constructing is:
+            // .audiotee ! queue ! audioresample ! audioconvert ! fakesink
+
+            Element converter, resampler;
+            Queue audiosinkqueue;
+            Pad pad;
+
+            vis_buffer = null;
+            vis_fft = gst_fft_f32_new (SLICE_SIZE * 2, false);
+            vis_fft_buffer = new GstFFTF32Complex [SLICE_SIZE + 1];
+            vis_fft_sample_buffer = new float [SLICE_SIZE];
+            
+            // Core elements, if something fails here, it's the end of the world
+            audiosinkqueue = (Queue)ElementFactory.Make ("queue", "vis-queue");
+        
+            pad = audiosinkqueue.GetStaticPad ("sink");
+            pad.AddEventProbe (new PadEventProbeCallback (EventProbe));
+        
+            resampler = ElementFactory.Make ("audioresample", "vis-resample");
+            converter = ElementFactory.Make ("audioconvert", "vis-convert");
+            FakeSink fakesink = ElementFactory.Make ("fakesink", "vis-sink") as FakeSink;
+        
+            // channels * slice size * float size = size of chunks we want
+            wanted_size = (uint)(2 * SLICE_SIZE * sizeof(float));
+        
+            if (audiosinkqueue == null || resampler == null || converter == null || fakesink == null) {
+                Log.Debug ("Could not construct visualization pipeline, a fundamental element could not be created");
+                return;
+            }
+        
+            // Keep around the 5 most recent seconds of audio so that when resuming
+            // visualization we have something to show right away.
+            audiosinkqueue.Leaky = Queue.LeakyType.Downstream;
+            audiosinkqueue.MaxSizeBuffers = 0;
+            audiosinkqueue.MaxSizeBytes = 0;
+            audiosinkqueue.MaxSizeTime = Clock.Second * 5;
+            
+            fakesink.Handoff += PCMHandoff;
+        
+        
+            // This enables the handoff signal.
+            fakesink.SignalHandoffs = true;
+            // Synchronize so we see vis at the same time as we hear it.
+            fakesink.Sync = true;
+            // Drop buffers if they come in too late.  This is mainly used when
+            // thawing the vis pipeline.
+            fakesink.MaxLateness = (long)(Clock.Second / 120);
+            // Deliver buffers one frame early.  This allows for rendering
+            // time.  (TODO: It would be great to calculate this on-the-fly so
+            // we match the rendering time.
+            fakesink.TsOffset = -(long)(Clock.Second / 60);
+            // Don't go to PAUSED when we freeze the pipeline.
+            fakesink.Async = false;
+            
+            audiobin.Add (audiosinkqueue, resampler, converter, fakesink);
+            
+            pad = audiosinkqueue.GetStaticPad ("sink");
+            teepad.Link (pad);
+            
+            Element.Link (audiosinkqueue, resampler, converter);
+            
+            converter.LinkFiltered (fakesink, caps);
+            
+            vis_buffer = new Adapter ();
+            vis_resampler = resampler;
+            vis_thawing = false;
+            active = false;
+        
+            // Disable the pipeline till we hear otherwise from managed land.
+            Blocked = true;
+        }
+
+        ~Visualization ()
+        {
+            if (vis_fft != IntPtr.Zero)
+                gst_fft_f32_free (vis_fft);
+        }
+
+        public bool Active
+        {
+            set {
+                Blocked = !value;
+                active = value;
+            }
+        }
+
+        private Caps caps = Caps.FromString (
+            "audio/x-raw-float, " +
+            "rate = (int) 44100, " +
+            "channels = (int) 2, " +
+            "endianness = (int) BYTE_ORDER, " +
+            "width = (int) 32");
+
+        private void BlockCallback (Pad pad, bool blocked)
+        {
+            if (!blocked) {
+                // Set thawing mode (discards buffers that are too old from the queue).
+                vis_thawing = true;
+            }
+        }
+        
+        private bool Blocked
+        {
+            set {
+                if (vis_resampler == null)
+                    return;
+                Pad queue_sink = vis_resampler.GetStaticPad ("src");
+                queue_sink.SetBlocked (value, new PadBlockCallback (BlockCallback));
+            }
+        }
+        private event VisualizationDataHandler OnDataAvailable = null;
+        public event VisualizationDataHandler DataAvailable {
+            add {
+                if (value == null) {
+                    return;
+                } else if (OnDataAvailable == null) {
+                    Active = true;
+                }
+
+                OnDataAvailable += value;
+            }
+
+            remove {
+                if (value == null) {
+                    return;
+                }
+
+                OnDataAvailable -= value;
+
+                if (OnDataAvailable == null) {
+                    Active = false;
+                }
+            }
+        }
+
+        private void PCMHandoff (object o, FakeSink.HandoffArgs args)
+        {
+            Gst.Buffer data;
+        
+            if (OnDataAvailable == null) {
+                return;
+            }
+        
+            if (vis_thawing) {
+                // Flush our buffers out.
+                vis_buffer.Clear ();
+                System.Array.Clear (vis_fft_sample_buffer, 0, vis_fft_sample_buffer.Length);
+        
+                vis_thawing = false;
+            }
+        
+            Structure structure = args.Buffer.Caps [0];
+            int channels = (int)structure.GetValue ("channels");
+        
+            wanted_size = (uint)(channels * SLICE_SIZE * sizeof (float));
+
+            //TODO see if buffer need a copy or not
+            //but copy is no available in gst# ;(
+            vis_buffer.Push (args.Buffer);
+            int i, j;
+            while ((data = vis_buffer.Peek (wanted_size)) != null) {
+                float[] buff = new float[data.Size];
+                Marshal.Copy (data.Data, buff, 0, (int) data.Size);
+                float[] deinterlaced = new float [channels * SLICE_SIZE];
+                float[] specbuf = new float [SLICE_SIZE * 2];
+
+                System.Array.Copy (specbuf, vis_fft_sample_buffer, SLICE_SIZE);
+                
+                for (i = 0; i < SLICE_SIZE; i++) {
+                    float avg = 0.0f;
+        
+                    for (j = 0; j < channels; j++) {
+                        float sample = buff[i * channels + j];
+        
+                        deinterlaced[j * SLICE_SIZE + i] = sample;
+                        avg += sample;
+                    }
+        
+                    avg /= channels;
+                    specbuf[i + SLICE_SIZE] = avg;
+                }
+        
+                System.Array.Copy (vis_fft_sample_buffer, 0, specbuf, SLICE_SIZE, SLICE_SIZE);
+        
+                gst_fft_f32_window (vis_fft, specbuf, FFTWindow.Hamming);
+                gst_fft_f32_fft (vis_fft, specbuf, vis_fft_buffer);
+        
+                for (i = 0; i < SLICE_SIZE; i++) {
+                    float val;
+        
+                    GstFFTF32Complex cplx = vis_fft_buffer[i];
+        
+                    val = cplx.r * cplx.r + cplx.i * cplx.i;
+                    val /= SLICE_SIZE * SLICE_SIZE;
+                    val = (float)(10.0f * System.Math.Log10 ((double)val));
+        
+                    val = (val + 60.0f) / 60.0f;
+                    if (val < 0.0f)
+                        val = 0.0f;
+        
+                    specbuf[i] = val;
+                }
+        
+                float [] flat = new float[channels * SLICE_SIZE];
+                System.Array.Copy (deinterlaced, flat, flat.Length);
+        
+                float [][] cbd = new float[channels][];
+                for (int k = 0; k < channels; k++) {
+                    float [] channel = new float[SLICE_SIZE];
+                    System.Array.Copy (flat, k * SLICE_SIZE, channel, 0, SLICE_SIZE);
+                    cbd [k] = channel;
+                }
+        
+                float [] spec = new float [SLICE_SIZE];
+                System.Array.Copy (specbuf, spec, SLICE_SIZE);
+        
+                try {
+                    OnDataAvailable (cbd, new float[][] { spec });
+                } catch (System.Exception e) {
+                    Log.Exception ("Uncaught exception during visualization data post.", e);
+                }
+        
+                vis_buffer.Flush ((uint)wanted_size);
+            }
+        }
+        
+        bool EventProbe (Pad pad, Event padEvent)
+        {
+            switch (padEvent.Type) {
+                case EventType.FlushStart:
+                case EventType.FlushStop:
+                case EventType.Seek:
+                case EventType.NewSegment:
+                    vis_thawing = true;
+                break;
+            }
+        
+            if (active)
+                return true;
+        
+            switch (padEvent.Type) {
+            case EventType.Eos:
+                Blocked = false;
+                break;
+        
+            case EventType.NewSegment:
+                Blocked = true;
+                break;
+            }
+        
+            return true;
+        }
+    }
+}
diff --git a/src/Backends/Banshee.GStreamerSharp/Makefile.am b/src/Backends/Banshee.GStreamerSharp/Makefile.am
index 6e8bf9f..0fd65fb 100644
--- a/src/Backends/Banshee.GStreamerSharp/Makefile.am
+++ b/src/Backends/Banshee.GStreamerSharp/Makefile.am
@@ -9,7 +9,8 @@ SOURCES =  \
 	Banshee.GStreamerSharp/CddaManager.cs \
 	Banshee.GStreamerSharp/PlayerEngine.cs \
 	Banshee.GStreamerSharp/Transcoder.cs \
-	Banshee.GStreamerSharp/VideoManager.cs
+	Banshee.GStreamerSharp/VideoManager.cs \
+	Banshee.GStreamerSharp/Visualization.cs
 
 RESOURCES = Banshee.GStreamerSharp.addin.xml
 



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]