1 /* GStreamer ofa fingerprinting element
2  * Copyright (C) 2006 M. Derezynski
3  * Copyright (C) 2008 Eric Buehl
4  * Copyright (C) 2008 Sebastian Dröge <slomo@circular-chaos.org>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19  * Boston, MA  02110-1301 USA.
20  */
21 
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25 
26 #include <gst/gst.h>
27 #include <ofa1/ofa.h>
28 #include "gstofa.h"
29 
30 #define PAD_CAPS \
31 	"audio/x-raw, " \
32         "format = { S16LE, S16BE }, " \
33         "rate = (int) [ 1, MAX ], " \
34         "channels = (int) [ 1, 2 ]"
35 
36 GST_DEBUG_CATEGORY_STATIC (gst_ofa_debug);
37 #define GST_CAT_DEFAULT gst_ofa_debug
38 
39 enum
40 {
41   PROP_0,
42   PROP_FINGERPRINT,
43 };
44 
45 #define parent_class gst_ofa_parent_class
46 G_DEFINE_TYPE (GstOFA, gst_ofa, GST_TYPE_AUDIO_FILTER);
47 
48 static void gst_ofa_finalize (GObject * object);
49 static void gst_ofa_get_property (GObject * object, guint prop_id,
50     GValue * value, GParamSpec * pspec);
51 static GstFlowReturn gst_ofa_transform_ip (GstBaseTransform * trans,
52     GstBuffer * buf);
53 static gboolean gst_ofa_sink_event (GstBaseTransform * trans, GstEvent * event);
54 
55 static void
gst_ofa_finalize(GObject * object)56 gst_ofa_finalize (GObject * object)
57 {
58   GstOFA *ofa = GST_OFA (object);
59 
60   if (ofa->adapter) {
61     g_object_unref (ofa->adapter);
62     ofa->adapter = NULL;
63   }
64 
65   g_free (ofa->fingerprint);
66   ofa->fingerprint = NULL;
67 
68   G_OBJECT_CLASS (parent_class)->finalize (object);
69 }
70 
71 static void
gst_ofa_class_init(GstOFAClass * klass)72 gst_ofa_class_init (GstOFAClass * klass)
73 {
74   GstBaseTransformClass *gstbasetrans_class = GST_BASE_TRANSFORM_CLASS (klass);
75   GstAudioFilterClass *audio_filter_class = GST_AUDIO_FILTER_CLASS (klass);
76   GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
77   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
78   GstCaps *caps;
79 
80   gobject_class->get_property = gst_ofa_get_property;
81 
82   g_object_class_install_property (gobject_class, PROP_FINGERPRINT,
83       g_param_spec_string ("fingerprint", "Resulting fingerprint",
84           "Resulting fingerprint", NULL,
85           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
86 
87   gobject_class->finalize = gst_ofa_finalize;
88 
89   gstbasetrans_class->transform_ip = GST_DEBUG_FUNCPTR (gst_ofa_transform_ip);
90   gstbasetrans_class->sink_event = GST_DEBUG_FUNCPTR (gst_ofa_sink_event);
91   gstbasetrans_class->passthrough_on_same_caps = TRUE;
92 
93   gst_element_class_set_static_metadata (gstelement_class, "OFA",
94       "MusicIP Fingerprinting element",
95       "Find a music fingerprint using MusicIP's libofa",
96       "Milosz Derezynski <internalerror@gmail.com>, "
97       "Eric Buehl <eric.buehl@gmail.com>");
98 
99   caps = gst_caps_from_string (PAD_CAPS);
100   gst_audio_filter_class_add_pad_templates (audio_filter_class, caps);
101   gst_caps_unref (caps);
102 }
103 
104 static void
create_fingerprint(GstOFA * ofa)105 create_fingerprint (GstOFA * ofa)
106 {
107   GstAudioFilter *audiofilter = GST_AUDIO_FILTER (ofa);
108   const guint8 *samples;
109   const gchar *fingerprint;
110   gint rate, channels, endianness;
111   GstTagList *tags;
112   gsize available;
113 
114   available = gst_adapter_available (ofa->adapter);
115 
116   if (available == 0) {
117     GST_WARNING_OBJECT (ofa, "No data to take fingerprint from");
118     ofa->record = FALSE;
119     return;
120   }
121 
122   rate = GST_AUDIO_INFO_RATE (&audiofilter->info);
123   channels = GST_AUDIO_INFO_CHANNELS (&audiofilter->info);
124   if (GST_AUDIO_INFO_ENDIANNESS (&audiofilter->info) == G_BIG_ENDIAN)
125     endianness = OFA_BIG_ENDIAN;
126   else
127     endianness = OFA_LITTLE_ENDIAN;
128 
129 
130   GST_DEBUG_OBJECT (ofa, "Generating fingerprint for %" G_GSIZE_FORMAT
131       " samples", available / sizeof (gint16));
132 
133   samples = gst_adapter_map (ofa->adapter, available);
134 
135   fingerprint = ofa_create_print ((unsigned char *) samples, endianness,
136       available / sizeof (gint16), rate, (channels == 2) ? 1 : 0);
137 
138   gst_adapter_unmap (ofa->adapter);
139   gst_adapter_flush (ofa->adapter, available);
140 
141   if (fingerprint == NULL) {
142     GST_WARNING_OBJECT (ofa, "Failed to generate fingerprint");
143     goto done;
144   }
145 
146   GST_INFO_OBJECT (ofa, "Generated fingerprint: %s", fingerprint);
147   ofa->fingerprint = g_strdup (fingerprint);
148 
149   // FIXME: combine with upstream tags
150   tags = gst_tag_list_new (GST_TAG_OFA_FINGERPRINT, ofa->fingerprint, NULL);
151   gst_pad_push_event (GST_BASE_TRANSFORM_SRC_PAD (ofa),
152       gst_event_new_tag (tags));
153 
154   g_object_notify (G_OBJECT (ofa), "fingerprint");
155 
156 done:
157 
158   ofa->record = FALSE;
159 }
160 
161 static gboolean
gst_ofa_sink_event(GstBaseTransform * trans,GstEvent * event)162 gst_ofa_sink_event (GstBaseTransform * trans, GstEvent * event)
163 {
164   GstOFA *ofa = GST_OFA (trans);
165 
166   switch (GST_EVENT_TYPE (event)) {
167     case GST_EVENT_FLUSH_STOP:
168     case GST_EVENT_SEGMENT:
169       GST_DEBUG_OBJECT (ofa, "Got %s event, clearing buffer",
170           GST_EVENT_TYPE_NAME (event));
171       gst_adapter_clear (ofa->adapter);
172       /* FIXME: should we really always reset this instead of using an
173        * already-existing fingerprint? Assumes fingerprints are always
174        * extracted in a separate pipeline instead of a live playback
175        * situation */
176       ofa->record = TRUE;
177       g_free (ofa->fingerprint);
178       ofa->fingerprint = NULL;
179       break;
180     case GST_EVENT_EOS:
181       /* we got to the end of the stream but never generated a fingerprint
182        * (probably under 135 seconds)
183        */
184       if (!ofa->fingerprint && ofa->record)
185         create_fingerprint (ofa);
186       break;
187     default:
188       break;
189   }
190 
191   return GST_BASE_TRANSFORM_CLASS (parent_class)->sink_event (trans, event);
192 }
193 
194 static void
gst_ofa_init(GstOFA * ofa)195 gst_ofa_init (GstOFA * ofa)
196 {
197   gst_base_transform_set_passthrough (GST_BASE_TRANSFORM (ofa), TRUE);
198 
199   ofa->fingerprint = NULL;
200   ofa->record = TRUE;
201 
202   ofa->adapter = gst_adapter_new ();
203 }
204 
205 static GstFlowReturn
gst_ofa_transform_ip(GstBaseTransform * trans,GstBuffer * buf)206 gst_ofa_transform_ip (GstBaseTransform * trans, GstBuffer * buf)
207 {
208   GstAudioFilter *audiofilter = GST_AUDIO_FILTER (trans);
209   GstOFA *ofa = GST_OFA (trans);
210   guint64 nframes;
211   GstClockTime duration;
212   gint rate, channels;
213 
214   rate = GST_AUDIO_INFO_RATE (&audiofilter->info);
215   channels = GST_AUDIO_INFO_CHANNELS (&audiofilter->info);
216 
217   if (rate == 0 || channels == 0)
218     return GST_FLOW_NOT_NEGOTIATED;
219 
220   if (!ofa->record)
221     return GST_FLOW_OK;
222 
223   gst_adapter_push (ofa->adapter, gst_buffer_copy (buf));
224 
225   nframes = gst_adapter_available (ofa->adapter) / (channels * 2);
226   duration = GST_FRAMES_TO_CLOCK_TIME (nframes, rate);
227 
228   if (duration >= 135 * GST_SECOND && ofa->fingerprint == NULL)
229     create_fingerprint (ofa);
230 
231   return GST_FLOW_OK;
232 }
233 
234 static void
gst_ofa_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)235 gst_ofa_get_property (GObject * object, guint prop_id, GValue * value,
236     GParamSpec * pspec)
237 {
238   GstOFA *ofa = GST_OFA (object);
239 
240   switch (prop_id) {
241     case PROP_FINGERPRINT:
242       g_value_set_string (value, ofa->fingerprint);
243       break;
244     default:
245       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
246       break;
247   }
248 }
249 
250 
251 static gboolean
plugin_init(GstPlugin * plugin)252 plugin_init (GstPlugin * plugin)
253 {
254   gboolean ret;
255   int major, minor, rev;
256 
257   GST_DEBUG_CATEGORY_INIT (gst_ofa_debug, "ofa", 0, "ofa element");
258 
259   ofa_get_version (&major, &minor, &rev);
260 
261   GST_DEBUG ("libofa %d.%d.%d", major, minor, rev);
262 
263   ret = gst_element_register (plugin, "ofa", GST_RANK_NONE, GST_TYPE_OFA);
264 
265   if (ret) {
266     /* TODO: get this into core */
267     gst_tag_register (GST_TAG_OFA_FINGERPRINT, GST_TAG_FLAG_META,
268         G_TYPE_STRING, "ofa fingerprint", "OFA fingerprint", NULL);
269   }
270 
271   return ret;
272 }
273 
274 /* FIXME: someone write a libofa replacement with an LGPL or BSD license */
275 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
276     GST_VERSION_MINOR,
277     ofa,
278     "Calculate MusicIP fingerprint from audio files",
279     plugin_init, VERSION, "GPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
280