Gstreamer でスペクトラムアナライザーもどきを作ってみた

久々の更新ということに気づいた。

今回は、基本的に gst-plugins-good の spectrum を使っただけで、しかもほとんどが devhelp からのコピペです。なのでソースの説明はしません。

今回困ったのは、 gst-plugins-base の audiotestsrc のプロパティ "wave" と "freq" の設定。"wave" は enum GstAudioTestSrcWave で定義されているのですが、ヘッダがインストールされないので、折角命名している名前があっても使えないんです。だから、GST_AUDIO_TEST_SRC_WAVE_SINE ではなく 0 にする必要があります。つまり、それぞれ数字で指定することになるみたい。gst-launch-1.0 では名前が使えるのにね。"freq"は、#define する時に小数点を明示しないとダメみたいです。880Hzにしたい場合は、880ではなく880.0にする必要があります。

その他では、サンプルでは、caps(ケーパビリティ?)で audio/x-raw-int と指定していますが、このままでは以下のエラーになります。

GStreamer-WARNING **: 0.10-style raw audio caps are being created. Should be audio/x-raw,format=(string).. now.
Error: gst_element_link_filterd audioconvert spectrun caps

そのため、ソースでは、 audio/x-raw にしています。以下、ソースです。wave を blue-noise にしているので、freq に意味がなかったりします。

#include <stdlib.h>
#include <gst/gst.h>
#include <gtk/gtk.h>

#define NUM_BANDS 20
#define MIN_LEVEL -60
#define MAX_LEVEL 0
#define STEP 1

#define AUDIOFREQ 32000
#define WAVE_NAME 11
#define WAVE_FREQ 8800.0

// #define G_DISABLE_ASSERT

/* GstBusFunc */
static gboolean
gst_bus_handler (GstBus *bus, GstMessage *message, gpointer user_data)
{
	GtkWidget ***vscale = user_data;
	
	if (message->type == GST_MESSAGE_ELEMENT)
	{
		const GstStructure *structure = gst_message_get_structure (message);
		const gchar *name = gst_structure_get_name (structure);

		if (g_strcmp0 (name, "spectrum") == 0)
		{
			const GValue *magnitude = gst_structure_get_value (structure, "magnitude");
			guint i;

			for (i = 0; i < NUM_BANDS; i++)
			{
				const GValue *mag = gst_value_list_get_value (magnitude, i);

				if (mag != NULL && GTK_IS_SCALE (vscale[i]))
				{
					gtk_range_set_value (GTK_RANGE (vscale[i]), g_value_get_float (mag));
				}
			}
		}
	}
	return TRUE;
}

int
main (int argc, char *argv[])
{
	GtkWidget *vscale[NUM_BANDS];
	GstElement *bin;

	gtk_init (&argc, &argv);
	gst_init (&argc, &argv);

	/* init gui */
	{
		GtkWidget *win, *hbox;
		guint i;

		win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
		g_assert (win);
		g_signal_connect (win, "destroy", G_CALLBACK (gtk_main_quit), NULL);
		
		hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
		g_assert (hbox);
		
		for (i = 0; i < NUM_BANDS; i++)
		{
			vscale[i] = gtk_scale_new_with_range (GTK_ORIENTATION_VERTICAL, MIN_LEVEL, MAX_LEVEL, STEP);
			g_assert (vscale[i]);
			gtk_range_set_inverted (GTK_RANGE (vscale[i]), TRUE);
			gtk_range_set_min_slider_size (GTK_RANGE (vscale[i]), (MAX_LEVEL - MIN_LEVEL) * 10);
			gtk_box_pack_start (GTK_BOX (hbox), vscale[i], TRUE, TRUE, 0);
		}
		gtk_container_add (GTK_CONTAINER (win), hbox);
		gtk_widget_show_all (win);
	}
	
	/* setup gstreamer */
	{
		GstElement *src, *audioconvert, *spectrum, *sink;
		GstBus *bus;
		GstCaps *caps;
		
		bin = gst_pipeline_new ("bin");
		g_assert (bin);
		
		src = gst_element_factory_make ("audiotestsrc", "src");
		g_assert (src);
		g_object_set (G_OBJECT (src), "freq", WAVE_FREQ, "wave", WAVE_NAME, "volume", 1.0, NULL);
		
		audioconvert = gst_element_factory_make ("audioconvert", NULL);
		g_assert (audioconvert);
		
		spectrum = gst_element_factory_make ("spectrum", "spectrum");
		g_assert (spectrum);
		g_object_set (G_OBJECT (spectrum), "bands", NUM_BANDS, NULL);
		
		sink = gst_element_factory_make ("autoaudiosink", "sink");
		g_assert (sink);
		
		gst_bin_add_many (GST_BIN (bin), src, audioconvert, spectrum, sink, NULL);
		caps = gst_caps_new_simple ("audio/x-raw", "rate", G_TYPE_INT, AUDIOFREQ, NULL);
		g_assert (caps);
		if (!gst_element_link (src, audioconvert))
		{
			g_printf ("Error: gst_element_link src audioconvert\n");
			return EXIT_FAILURE;
		}
		if (!gst_element_link_filtered (audioconvert, spectrum, caps))
		{
			g_printf ("Error: gst_element_link_filterd audioconvert spectrun caps\n");
			return EXIT_FAILURE;
		}
		if (!gst_element_link (spectrum, sink))
		{
			g_printf ("Error: gst_element_link spectrum sink\n");
			return EXIT_FAILURE;
		}
		gst_caps_unref (caps);

		bus = gst_element_get_bus (bin);
		g_assert (bin);
		gst_bus_add_watch (bus, gst_bus_handler, (gpointer) &vscale);
		gst_object_unref (bus);
		
		gst_element_set_state (bin, GST_STATE_PLAYING);
	}
	
	gtk_main ();
	
	gst_element_set_state (bin, GST_STATE_NULL);
	gst_object_unref (bin);
	
	return EXIT_SUCCESS;
}

オーディオ用だから、横軸を対数にしなきゃダメなんだよな〜。あと、 GtkScale はコントロール用なので、表示用の Widget が欲しいな〜。