mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-05 09:00:54 +00:00
319 lines
9.2 KiB
C
319 lines
9.2 KiB
C
/* GStreamer chromaprint audio fingerprinting element
|
|
* Copyright (C) 2006 M. Derezynski
|
|
* Copyright (C) 2008 Eric Buehl
|
|
* Copyright (C) 2008 Sebastian Dröge <slomo@circular-chaos.org>
|
|
* Copyright (C) 2011 Lukáš Lalinský <lalinsky@gmail.com>
|
|
* Copyright (C) 2012 Collabora Ltd. <tim.muller@collabora.co.uk>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library 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
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this library; if not, write to the
|
|
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
/**
|
|
* SECTION:element-chromaprint
|
|
*
|
|
* The chromaprint element calculates an acoustic fingerprint for an
|
|
* audio stream which can be used to identify a song and look up
|
|
* further metadata from the <ulink url="http://acoustid.org/">Acoustid</ulink>
|
|
* and Musicbrainz databases.
|
|
*
|
|
* <refsect2>
|
|
* <title>Example launch line</title>
|
|
* |[
|
|
* gst-launch -m uridecodebin uri=file:///path/to/song.ogg ! audioconvert ! chromaprint ! fakesink
|
|
* ]|
|
|
* </refsect2>
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include "gstchromaprint.h"
|
|
|
|
#define DEFAULT_MAX_DURATION 120
|
|
|
|
#define PAD_CAPS \
|
|
"audio/x-raw, " \
|
|
"format = (string) " GST_AUDIO_NE(S16) ", "\
|
|
"rate = (int) [ 1, MAX ], " \
|
|
"channels = (int) [ 1, 2 ]"
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (gst_chromaprint_debug);
|
|
#define GST_CAT_DEFAULT gst_chromaprint_debug
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_FINGERPRINT,
|
|
PROP_MAX_DURATION
|
|
};
|
|
|
|
#define parent_class gst_chromaprint_parent_class
|
|
G_DEFINE_TYPE (GstChromaprint, gst_chromaprint, GST_TYPE_AUDIO_FILTER);
|
|
|
|
static void gst_chromaprint_finalize (GObject * object);
|
|
static void gst_chromaprint_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec);
|
|
static void gst_chromaprint_get_property (GObject * object, guint prop_id,
|
|
GValue * value, GParamSpec * pspec);
|
|
static GstFlowReturn gst_chromaprint_transform_ip (GstBaseTransform * trans,
|
|
GstBuffer * buf);
|
|
static gboolean gst_chromaprint_sink_event (GstBaseTransform * trans,
|
|
GstEvent * event);
|
|
|
|
static void
|
|
gst_chromaprint_class_init (GstChromaprintClass * klass)
|
|
{
|
|
GObjectClass *gobject_class;
|
|
GstBaseTransformClass *gstbasetrans_class;
|
|
GstCaps *caps;
|
|
|
|
gobject_class = G_OBJECT_CLASS (klass);
|
|
gstbasetrans_class = GST_BASE_TRANSFORM_CLASS (klass);
|
|
|
|
gobject_class->set_property = gst_chromaprint_set_property;
|
|
gobject_class->get_property = gst_chromaprint_get_property;
|
|
|
|
/* FIXME: do we need this in addition to the tag message ? */
|
|
g_object_class_install_property (gobject_class, PROP_FINGERPRINT,
|
|
g_param_spec_string ("fingerprint", "Resulting fingerprint",
|
|
"Resulting fingerprint", NULL, G_PARAM_READABLE));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_MAX_DURATION,
|
|
g_param_spec_uint ("duration", "Duration limit",
|
|
"Number of seconds of audio to use for fingerprinting",
|
|
0, G_MAXUINT, DEFAULT_MAX_DURATION,
|
|
G_PARAM_READABLE | G_PARAM_WRITABLE));
|
|
|
|
gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_chromaprint_finalize);
|
|
|
|
gstbasetrans_class->transform_ip =
|
|
GST_DEBUG_FUNCPTR (gst_chromaprint_transform_ip);
|
|
gstbasetrans_class->sink_event =
|
|
GST_DEBUG_FUNCPTR (gst_chromaprint_sink_event);
|
|
gstbasetrans_class->passthrough_on_same_caps = TRUE;
|
|
|
|
gst_element_class_set_static_metadata (GST_ELEMENT_CLASS (klass),
|
|
"Chromaprint fingerprinting element",
|
|
"Filter/Analyzer/Audio",
|
|
"Find an audio fingerprint using the Chromaprint library",
|
|
"Lukáš Lalinský <lalinsky@gmail.com>");
|
|
|
|
caps = gst_caps_from_string (PAD_CAPS);
|
|
gst_audio_filter_class_add_pad_templates (GST_AUDIO_FILTER_CLASS (klass),
|
|
caps);
|
|
gst_caps_unref (caps);
|
|
}
|
|
|
|
static void
|
|
gst_chromaprint_reset (GstChromaprint * chromaprint)
|
|
{
|
|
if (chromaprint->fingerprint) {
|
|
chromaprint_dealloc (chromaprint->fingerprint);
|
|
chromaprint->fingerprint = NULL;
|
|
}
|
|
|
|
chromaprint->nsamples = 0;
|
|
chromaprint->duration = 0;
|
|
chromaprint->record = TRUE;
|
|
}
|
|
|
|
static void
|
|
gst_chromaprint_create_fingerprint (GstChromaprint * chromaprint)
|
|
{
|
|
GstTagList *tags;
|
|
|
|
if (chromaprint->duration <= 3)
|
|
return;
|
|
|
|
GST_DEBUG_OBJECT (chromaprint,
|
|
"Generating fingerprint based on %d seconds of audio",
|
|
chromaprint->duration);
|
|
|
|
chromaprint_finish (chromaprint->context);
|
|
chromaprint_get_fingerprint (chromaprint->context, &chromaprint->fingerprint);
|
|
chromaprint->record = FALSE;
|
|
|
|
tags = gst_tag_list_new (GST_TAG_CHROMAPRINT_FINGERPRINT,
|
|
chromaprint->fingerprint, NULL);
|
|
|
|
gst_pad_push_event (GST_BASE_TRANSFORM_SRC_PAD (chromaprint),
|
|
gst_event_new_tag (tags));
|
|
}
|
|
|
|
static void
|
|
gst_chromaprint_init (GstChromaprint * chromaprint)
|
|
{
|
|
gst_base_transform_set_passthrough (GST_BASE_TRANSFORM (chromaprint), TRUE);
|
|
|
|
chromaprint->context = chromaprint_new (CHROMAPRINT_ALGORITHM_DEFAULT);
|
|
chromaprint->fingerprint = NULL;
|
|
chromaprint->max_duration = DEFAULT_MAX_DURATION;
|
|
gst_chromaprint_reset (chromaprint);
|
|
}
|
|
|
|
static void
|
|
gst_chromaprint_finalize (GObject * object)
|
|
{
|
|
GstChromaprint *chromaprint = GST_CHROMAPRINT (object);
|
|
|
|
chromaprint->record = FALSE;
|
|
|
|
if (chromaprint->context) {
|
|
chromaprint_free (chromaprint->context);
|
|
chromaprint->context = NULL;
|
|
}
|
|
|
|
if (chromaprint->fingerprint) {
|
|
chromaprint_dealloc (chromaprint->fingerprint);
|
|
chromaprint->fingerprint = NULL;
|
|
}
|
|
|
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_chromaprint_transform_ip (GstBaseTransform * trans, GstBuffer * buf)
|
|
{
|
|
GstChromaprint *chromaprint = GST_CHROMAPRINT (trans);
|
|
GstAudioFilter *filter = GST_AUDIO_FILTER (trans);
|
|
GstMapInfo map_info;
|
|
guint nsamples;
|
|
gint rate, channels;
|
|
|
|
rate = GST_AUDIO_INFO_RATE (&filter->info);
|
|
channels = GST_AUDIO_INFO_CHANNELS (&filter->info);
|
|
|
|
if (G_UNLIKELY (rate <= 0 || channels <= 0))
|
|
return GST_FLOW_NOT_NEGOTIATED;
|
|
|
|
if (!chromaprint->record)
|
|
return GST_FLOW_OK;
|
|
|
|
if (!gst_buffer_map (buf, &map_info, GST_MAP_READ))
|
|
return GST_FLOW_ERROR;
|
|
|
|
nsamples = map_info.size / (channels * 2);
|
|
|
|
if (nsamples == 0)
|
|
goto end;
|
|
|
|
if (chromaprint->nsamples == 0) {
|
|
chromaprint_start (chromaprint->context, rate, channels);
|
|
}
|
|
chromaprint->nsamples += nsamples;
|
|
chromaprint->duration = chromaprint->nsamples / rate;
|
|
|
|
chromaprint_feed (chromaprint->context, map_info.data,
|
|
map_info.size / sizeof (guint16));
|
|
|
|
if (chromaprint->duration >= chromaprint->max_duration
|
|
&& !chromaprint->fingerprint) {
|
|
gst_chromaprint_create_fingerprint (chromaprint);
|
|
}
|
|
|
|
end:
|
|
gst_buffer_unmap (buf, &map_info);
|
|
|
|
return GST_FLOW_OK;
|
|
}
|
|
|
|
static gboolean
|
|
gst_chromaprint_sink_event (GstBaseTransform * trans, GstEvent * event)
|
|
{
|
|
GstChromaprint *chromaprint = GST_CHROMAPRINT (trans);
|
|
|
|
switch (GST_EVENT_TYPE (event)) {
|
|
case GST_EVENT_FLUSH_STOP:
|
|
case GST_EVENT_SEGMENT:
|
|
GST_DEBUG_OBJECT (trans, "Got %s event, clearing buffer",
|
|
GST_EVENT_TYPE_NAME (event));
|
|
gst_chromaprint_reset (chromaprint);
|
|
break;
|
|
case GST_EVENT_EOS:
|
|
if (!chromaprint->fingerprint) {
|
|
gst_chromaprint_create_fingerprint (chromaprint);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return GST_BASE_TRANSFORM_CLASS (parent_class)->sink_event (trans, event);
|
|
}
|
|
|
|
static void
|
|
gst_chromaprint_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstChromaprint *chromaprint = GST_CHROMAPRINT (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_MAX_DURATION:
|
|
chromaprint->max_duration = g_value_get_uint (value);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_chromaprint_get_property (GObject * object, guint prop_id,
|
|
GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstChromaprint *chromaprint = GST_CHROMAPRINT (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_FINGERPRINT:
|
|
g_value_set_string (value, chromaprint->fingerprint);
|
|
break;
|
|
case PROP_MAX_DURATION:
|
|
g_value_set_uint (value, chromaprint->max_duration);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
plugin_init (GstPlugin * plugin)
|
|
{
|
|
gboolean ret;
|
|
|
|
GST_DEBUG_CATEGORY_INIT (gst_chromaprint_debug, "chromaprint",
|
|
0, "chromaprint element");
|
|
|
|
GST_INFO ("libchromaprint %s", chromaprint_get_version ());
|
|
|
|
ret = gst_element_register (plugin, "chromaprint", GST_RANK_NONE,
|
|
GST_TYPE_CHROMAPRINT);
|
|
|
|
if (ret) {
|
|
gst_tag_register (GST_TAG_CHROMAPRINT_FINGERPRINT, GST_TAG_FLAG_META,
|
|
G_TYPE_STRING, "chromaprint fingerprint", "Chromaprint fingerprint",
|
|
NULL);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
|
|
GST_VERSION_MINOR,
|
|
chromaprint,
|
|
"Calculate Chromaprint fingerprint from audio files",
|
|
plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
|