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:
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);
}