spek

Acoustic spectrum analyser
git clone http://git.hanabi.in/repos/spek.git
Log | Files | Refs | README

commit 68cbf6335f9265fe0593b3969718559b9d5e6da7
parent 0c0f7c29701e7f1ecb20867c244ffa5fc977fa10
Author: Alexander Kojevnikov <alexander@kojevnikov.com>
Date:   Sat,  3 Jul 2010 13:28:50 +1000

Remove Spek.Source and gst dependencies, use Spek.Pipeline

Diffstat:
MNEWS | 6++++--
Mconfigure.ac | 4++--
Msrc/Makefile.am | 1-
Msrc/spek-audio.c | 9+++++++++
Msrc/spek-audio.h | 7+++++++
Msrc/spek-pipeline.vala | 49++++++++++++++++++++++++++++++++++++-------------
Dsrc/spek-source.vala | 206-------------------------------------------------------------------------------
Msrc/spek-spectrogram.vala | 47++++++++++-------------------------------------
Msrc/spek.vala | 1-
Mvapi/spek-audio.vapi | 6++++++
10 files changed, 74 insertions(+), 262 deletions(-)

diff --git a/NEWS b/NEWS @@ -2,8 +2,10 @@ Spek 0.5 - Released 2010-06-26 ============================== Spek is an acoustic spectrum analyser written in Vala. -It uses the GNOME platform: GLib, GTK+, Cairo and GStreamer. -Spek is available on GNU/Linux and Windows. +It uses the GNOME platform: GLib, GTK+, Cairo and Pango +as well as FFmpeg and FFTW libraries. + +Spek is available on GNU/Linux, Windows and Mac OS X. Find out more about Spek on its website: http://spek-project.org/ diff --git a/configure.ac b/configure.ac @@ -24,12 +24,12 @@ AM_PROG_VALAC([0.7.0]) AC_PROG_INSTALL AC_PROG_INTLTOOL([0.35]) -pkg_modules="gtk+-2.0 >= 2.14.0 gstreamer-0.10 >= 0.10.17 libavformat libavcodec fftw3f" +pkg_modules="gtk+-2.0 >= 2.14.0 libavformat libavcodec >= 52.23.0 fftw3f" PKG_CHECK_MODULES(SPEK, [$pkg_modules]) AC_SUBST(SPEK_CFLAGS) AC_SUBST(SPEK_LIBS) -SPEK_PACKAGES="--pkg gtk+-2.0 --pkg gstreamer-0.10" +SPEK_PACKAGES="--pkg gtk+-2.0 --pkg posix" AC_SUBST(SPEK_PACKAGES) AC_CHECK_LIB(m, log10f) diff --git a/src/Makefile.am b/src/Makefile.am @@ -6,7 +6,6 @@ spek_SOURCES = \ spek-fft.c \ spek-pipeline.vala \ spek-ruler.vala \ - spek-source.vala \ spek-spectrogram.vala \ spek-window.vala diff --git a/src/spek-audio.c b/src/spek-audio.c @@ -72,6 +72,7 @@ SpekAudioContext * spek_audio_open (const char *file_name) { cx->bits_per_sample = cx->codec_context->bits_per_coded_sample; } cx->channels = cx->codec_context->channels; + cx->duration = cx->stream->duration * av_q2d (cx->stream->time_base); if (cx->channels <= 0) { cx->error = _("No audio channels"); return cx; @@ -107,6 +108,14 @@ SpekAudioContext * spek_audio_open (const char *file_name) { return cx; } +void spek_audio_start (SpekAudioContext *cx, gint samples) { + gint64 rate = cx->sample_rate * (gint64) cx->stream->time_base.num; + cx->error_base = samples * (gint64) cx->stream->time_base.den; + cx->frames_per_interval = av_rescale_rnd ( + cx->stream->duration, rate, cx->error_base, AV_ROUND_DOWN); + cx->error_per_interval = (cx->stream->duration * rate) % cx->error_base; +} + gint spek_audio_read (SpekAudioContext *cx, guint8 *buffer) { gint buffer_size; gint len; diff --git a/src/spek-audio.h b/src/spek-audio.h @@ -43,7 +43,11 @@ typedef struct { gint width; /* number of bits used to store a sample */ gboolean fp; /* floating-point sample representation */ gint channels; + gdouble duration; gint buffer_size; /* minimum buffer size for spek_audio_read() */ + gint64 frames_per_interval; + gint64 error_per_interval; + gint64 error_base; } SpekAudioContext; /* Initialise FFmpeg, should be called once on start up */ @@ -54,6 +58,9 @@ void spek_audio_init (); */ SpekAudioContext * spek_audio_open (const gchar *file_name); +/* Prepare the context for reading audio samples. */ +void spek_audio_start (SpekAudioContext *cx, gint samples); + /* Read and decode the opened audio stream. * Returns -1 on error, 0 if there's nothing left to read * or the number of bytes decoded into the buffer. diff --git a/src/spek-pipeline.vala b/src/spek-pipeline.vala @@ -27,6 +27,7 @@ namespace Spek { public class Pipeline { public string description { get; private set; } public int sample_rate { get; private set; } + public double duration { get { return cx.duration; } } public delegate void Callback (int sample, float[] values); private Audio.Context cx; @@ -34,7 +35,13 @@ namespace Spek { private int samples; private int threshold; private Callback cb; + private uint8[] buffer; + private Fft.Plan fft; + private int nfft; + float[] input; + float[] output; + float[] values; public Pipeline (string file_name, int bands, int samples, int threshold, Callback cb) { this.cx = new Audio.Context (file_name); @@ -72,17 +79,22 @@ namespace Spek { this.sample_rate = cx.sample_rate; this.buffer = new uint8[cx.buffer_size]; + this.nfft = 2 * bands - 2; + this.fft = new Fft.Plan (nfft); + this.input = new float[nfft]; + this.output = new float[bands]; + this.values = new float[bands]; + this.cx.start (samples); } public void start () { - int nfft = 2 * bands - 2; - var input = new float[nfft]; - var output = new float[bands]; int pos = 0; int frames = 0; + int num_fft = 0; + int sample = 0; int size; - // TODO: make it a static class variable and experiment with FFTW_MEASURE. - var fft = new Fft.Plan (nfft); + + clear_buffers (); while ((size = cx.read (this.buffer)) > 0) { uint8 *buffer = (uint8 *) this.buffer; @@ -98,27 +110,38 @@ namespace Spek { // have all frames required for the interval run // an FFT. In the last case we probably take the // FFT of frames that we already handled. - if (frames % nfft == 0 - // TODO: error correction -// || ((spectrum->accumulated_error < GST_SECOND -// && spectrum->num_frames == spectrum->frames_per_interval) -// || -// (spectrum->accumulated_error >= GST_SECOND -// && spectrum->num_frames - 1 == spectrum->frames_per_interval)) - ) { + // TODO: error correction + if (frames % nfft == 0) { for (int i = 0; i < nfft; i++) { fft.input[i] = input[(pos + i) % nfft]; } fft.execute (); + num_fft++; for (int i = 0; i < bands; i++) { output[i] += fft.output[i]; } } + // Do we have the FFTs for one interval? + // TODO: error correction + if (frames == cx.frames_per_interval) { + for (int i = 0; i < bands; i++) { + output[i] /= num_fft; + } + cb (sample++, output); + clear_buffers (); + frames = 0; + num_fft = 0; + } } assert (size == 0); } } + private void clear_buffers () { + Posix.memset (input, 0, sizeof (float) * nfft); + Posix.memset (output, 0, sizeof (float) * bands); + } + private float average_input (uint8 *buffer) { float res = 0f; float max_value = cx.bits_per_sample > 1 ? (1UL << (cx.bits_per_sample - 1)) - 1 : 0; diff --git a/src/spek-source.vala b/src/spek-source.vala @@ -1,205 +0,0 @@ -/* spek-source.vala - * - * Copyright (C) 2010 Alexander Kojevnikov <alexander@kojevnikov.com> - * - * Spek is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Spek is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Spek. If not, see <http://www.gnu.org/licenses/>. - */ - -using Gst; - -namespace Spek { - public class Source : GLib.Object { - - public string file_name { get; construct; } - public int bands { get; construct; } - public int samples { get; construct; } - public int threshold { get; construct; } - // TODO: file a bug, cannot s/set/construct/ - public DataCallback data_cb { get; set; } - public InfoCallback info_cb { get; set; } - - public int64 duration { get; private set; default = 0; } - public int rate { get; private set; default = 0; } - public string audio_codec { get; private set; default = null; } - public uint bitrate { get; private set; default = 0; } - public int channels { get; private set; default = 0; } - public int depth { get; private set; default = 0; } - - public delegate void DataCallback (int sample, float[] values); - public delegate void InfoCallback (); - - private Gst.Pipeline pipeline = null; - private Element spectrum = null; - private Pad pad = null; - private int sample; - private float[] values; - private uint watch_id; - - public Source ( - string file_name, int bands, int samples, int threshold, - DataCallback data_cb, InfoCallback info_cb) { - GLib.Object (file_name: file_name, bands: bands, samples: samples, threshold: threshold); - this.data_cb = data_cb; - this.info_cb = info_cb; - } - - public void stop () { - // Need to manually remove the bus watch, otherwise `this` is never unreferenced. - if (watch_id > 0) { - GLib.Source.remove (watch_id); - watch_id = 0; - } - if (pipeline != null) { - pipeline.set_state (State.NULL); - pipeline = null; - } - } - - construct { - sample = 0; - values = new float[bands]; - - // TODO: Check for gst errors, in particular test the situation when - // `spectrum` (good), `decodebin` (base) or `fakesink` (core) plugins are not available. - pipeline = new Gst.Pipeline ("pipeline"); - var filesrc = ElementFactory.make ("filesrc", null); - var decodebin = ElementFactory.make ("decodebin", null); - pipeline.add_many (filesrc, decodebin); - filesrc.link (decodebin); - filesrc.set ("location", file_name); - - // decodebin's src pads are not constructed immediately. - // See gst-plugins-base/tree/gst/playback/test6.c - Signal.connect ( - decodebin, "new-decoded-pad", - (GLib.Callback) on_new_decoded_pad, this); - - this.watch_id = pipeline.get_bus ().add_watch (on_bus_watch); - if (pipeline.set_state (State.PAUSED) == StateChangeReturn.ASYNC) { - pipeline.get_state (null, null, -1); - } - - // Get the sample rate. - var caps = pad.get_caps (); - for (int i = 0; i < caps.get_size (); i++) { - var structure = caps.get_structure (i); - Gst.Value? val; - int n; - if (rate == 0 && (val = structure.get_value ("rate")) != null) { - if (val.type () == typeof (int)) { - rate = val.get_int (); - } else if (val.type () == typeof (IntRange)) { - rate = val.get_int_range_max (); - } - } - if (channels == 0 && (val = structure.get_value ("channels")) != null) { - if (val.type () == typeof (int)) { - channels = val.get_int (); - } else if (val.type () == typeof (IntRange)) { - channels = val.get_int_range_max (); - } - } - if (depth == 0 && structure.get_int ("depth", out n)) { - depth = n; - } - } - - // Get the duration. - // TODO: replace with Pad.query_duration when bgo#617260 is fixed - var query = new Query.duration (Format.TIME); - pad.query (query); - Format format; - int64 duration; - query.parse_duration (out format, out duration); - this.duration = duration; - spectrum.set ("interval", duration / (samples + 1)); - - pipeline.set_state (State.PLAYING); - } - - // TODO: get rid of the last parameter when bgo#615979 is fixed - private static void on_new_decoded_pad ( - Element decodebin, Pad new_pad, bool last, Source source) { - if (spectrum != null) { - // We want to construct the spectrum element only for the first decoded pad. - return; - } - - // The audioconvert takes care of caps that `spectrum` can't handle, - // for example streams with 24-bit depth. - var convert = ElementFactory.make ("audioconvert", null); - source.pipeline.add (convert); - var sinkpad = convert.get_static_pad ("sink"); - source.pad = new_pad; - source.pad.link (sinkpad); - convert.set_state (State.PAUSED); - - source.spectrum = ElementFactory.make ("spectrum", null); - source.pipeline.add (source.spectrum); - convert.link (source.spectrum); - source.spectrum.set ("bands", source.bands); - source.spectrum.set ("threshold", source.threshold); - source.spectrum.set ("message-magnitude", true); - source.spectrum.set ("post-messages", true); - source.spectrum.set_state (State.PAUSED); - - var fakesink = ElementFactory.make ("fakesink", null); - source.pipeline.add (fakesink); - source.spectrum.link (fakesink); - fakesink.set_state (State.PAUSED); - } - - private bool on_bus_watch (Bus bus, Message message) { - var structure = message.get_structure (); - switch (message.type ) { - case MessageType.ELEMENT: - if (structure.get_name () == "spectrum") { - var magnitudes = structure.get_value ("magnitude"); - for (int i = 0; i < bands; i++) { - values[i] = magnitudes.list_get_value (i).get_float (); - } - if (sample < samples) { - data_cb (sample++, values); - } - } - break; - case MessageType.TAG: - TagList tag_list; - message.parse_tag (out tag_list); - tag_list.foreach (on_tag); - break; - } - return true; - } - - private void on_tag (TagList tag_list, string tag) { - switch (tag) { - case TAG_AUDIO_CODEC: - string s = null; - if (audio_codec == null && tag_list.get_string (tag, out s)) { - audio_codec = s; - info_cb (); - } - break; - case TAG_BITRATE: - uint u = 0; - if (bitrate == 0 && tag_list.get_uint (tag, out u)) { - bitrate = u; - info_cb (); - } - break; - } - } - } -} -\ No newline at end of file diff --git a/src/spek-spectrogram.vala b/src/spek-spectrogram.vala @@ -25,7 +25,7 @@ namespace Spek { class Spectrogram : DrawingArea { public string file_name { get; private set; } - private Source source; + private Pipeline pipeline; private string info; private const int THRESHOLD = -92; // For faster FFT BANDS*2-2 should be a multiple of 2,3,5 @@ -73,8 +73,8 @@ namespace Spek { } private void start () { - if (this.source != null) { - this.source.stop (); + if (pipeline != null) { +// pipeline.stop (); } // The number of samples is the number of pixels available for the image. @@ -83,17 +83,14 @@ namespace Spek { int samples = allocation.width - LPAD - RPAD; if (samples > 0) { image = new ImageSurface (Format.RGB24, samples, BANDS); - source = new Source (file_name, BANDS, samples, THRESHOLD, data_cb, info_cb); + pipeline = new Pipeline (file_name, BANDS, samples, THRESHOLD, data_cb); + pipeline.start (); + info = pipeline.description; } else { image = null; - source = null; + pipeline = null; } - // TODO - var pipeline = new Pipeline (file_name, BANDS, samples, THRESHOLD, data_cb); - pipeline.start (); - print ("\n%s:\n%s\n", file_name, pipeline.description); - queue_draw (); } @@ -106,6 +103,7 @@ namespace Spek { } private void data_cb (int sample, float[] values) { + print ("%d: %f %f %f %f\n", sample, values[50], values[100], values[150], values[200]); for (int y = 0; y < values.length; y++) { var level = double.min ( 1.0, Math.log10 (1.0 - THRESHOLD + values[y]) / Math.log10 (-THRESHOLD)); @@ -114,31 +112,6 @@ namespace Spek { queue_draw_area (LPAD + sample, TPAD, 1, allocation.height - TPAD - BPAD); } - private void info_cb () { - string[] items = {}; - if (source.audio_codec != null) { - items += source.audio_codec; - } - if (source.bitrate != 0) { - items += _("%d kbps").printf (source.bitrate / 1000); - } - if (source.rate != 0) { - items += _("%d Hz").printf (source.rate); - } - // Show sample rate only if there is no bitrate. - if (source.depth != 0 && source.bitrate == 0) { - items += ngettext ("%d bit", "%d bits", source.depth).printf (source.depth); - } - if (source.channels != 0) { - items += ngettext ("%d channel", "%d channels", source.channels). - printf (source.channels); - } - if (items.length > 0) { - info = string.joinv (", ", items); - queue_draw_area (LPAD, 0, allocation.width - LPAD - RPAD, TPAD); - } - } - private override bool expose_event (EventExpose event) { var cr = cairo_create (this.window); @@ -176,7 +149,7 @@ namespace Spek { layout.set_width (-1); // Time ruler. - var duration_seconds = (int) (source.duration / 1000000000); + var duration_seconds = (int) pipeline.duration; var time_ruler = new Ruler ( "00:00", {1, 2, 5, 10, 20, 30, 1*60, 2*60, 5*60, 10*60, 20*60, 30*60}, @@ -189,7 +162,7 @@ namespace Spek { cr.identity_matrix (); // Frequency ruler. - var freq = source.rate / 2; + var freq = pipeline.sample_rate / 2; var rate_ruler = new Ruler ( "00 kHz", {1000, 2000, 5000, 10000, 20000}, diff --git a/src/spek.vala b/src/spek.vala @@ -52,7 +52,6 @@ namespace Spek { } Audio.init (); - Gst.init (ref args); var window = new Window (files == null ? null : files[0]); Gtk.main (); window.destroy (); diff --git a/vapi/spek-audio.vapi b/vapi/spek-audio.vapi @@ -12,10 +12,16 @@ namespace Spek.Audio { public int width; public bool fp; public int channels; + public double duration; public int buffer_size; + public int64 frames_per_interval; + public int64 error_per_interval; + public int64 error_base; [CCode (cname = "spek_audio_open")] public Context (string file_name); + [CCode (cname = "spek_audio_start")] + public int start (int samples); [CCode (cname = "spek_audio_read")] public int read ([CCode (array_length = false)] uint8[] buffer); }