1 /* GStreamer
2 * Copyright (C) 2008 Sebastian Dröge <slomo@circular-chaos.org>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23
24 /* FIXME: workaround for SoundTouch.h of version 1.3.1 defining those
25 * variables while it shouldn't. */
26 #undef VERSION
27 #undef PACKAGE_VERSION
28 #undef PACKAGE_TARNAME
29 #undef PACKAGE_STRING
30 #undef PACKAGE_NAME
31 #undef PACKAGE_BUGREPORT
32 #undef PACKAGE
33
34 #include <soundtouch/BPMDetect.h>
35
36 #include <gst/audio/audio.h>
37 #include <gst/audio/gstaudiofilter.h>
38 #include <math.h>
39 #include <string.h>
40 #include "gstbpmdetect.hh"
41
42 GST_DEBUG_CATEGORY_STATIC (gst_bpm_detect_debug);
43 #define GST_CAT_DEFAULT gst_bpm_detect_debug
44
45 #define GST_BPM_DETECT_GET_PRIVATE(o) (o->priv)
46
47 struct _GstBPMDetectPrivate
48 {
49 gfloat bpm;
50 #ifdef HAVE_SOUNDTOUCH_1_4
51 soundtouch::BPMDetect * detect;
52 #else
53 BPMDetect *detect;
54 #endif
55 };
56
57 /* For soundtouch 1.4 */
58 #if defined(INTEGER_SAMPLES)
59 #define SOUNDTOUCH_INTEGER_SAMPLES 1
60 #elif defined(FLOAT_SAMPLES)
61 #define SOUNDTOUCH_FLOAT_SAMPLES 1
62 #endif
63
64 #if defined(SOUNDTOUCH_FLOAT_SAMPLES)
65 #define ALLOWED_CAPS \
66 "audio/x-raw, " \
67 "format = (string) " GST_AUDIO_NE (F32) ", " \
68 "rate = (int) [ 8000, MAX ], " \
69 "channels = (int) [ 1, 2 ]"
70 #elif defined(SOUNDTOUCH_INTEGER_SAMPLES)
71 #define ALLOWED_CAPS \
72 "audio/x-raw, " \
73 "format = (string) " GST_AUDIO_NE (S16) ", " \
74 "rate = (int) [ 8000, MAX ], " \
75 "channels = (int) [ 1, 2 ]"
76 #else
77 #error "Only integer or float samples are supported"
78 #endif
79
80 #define gst_bpm_detect_parent_class parent_class
81 G_DEFINE_TYPE_WITH_PRIVATE (GstBPMDetect, gst_bpm_detect, GST_TYPE_AUDIO_FILTER);
82 GST_ELEMENT_REGISTER_DEFINE (bpmdetect, "bpmdetect", GST_RANK_NONE,
83 GST_TYPE_BPM_DETECT);
84
85 static void gst_bpm_detect_finalize (GObject * object);
86 static gboolean gst_bpm_detect_stop (GstBaseTransform * trans);
87 static gboolean gst_bpm_detect_event (GstBaseTransform * trans,
88 GstEvent * event);
89 static GstFlowReturn gst_bpm_detect_transform_ip (GstBaseTransform * trans,
90 GstBuffer * in);
91 static gboolean gst_bpm_detect_setup (GstAudioFilter * filter,
92 const GstAudioInfo * info);
93
94 static void
gst_bpm_detect_class_init(GstBPMDetectClass * klass)95 gst_bpm_detect_class_init (GstBPMDetectClass * klass)
96 {
97 GstCaps *caps;
98 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
99 GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
100 GstBaseTransformClass *trans_class = GST_BASE_TRANSFORM_CLASS (klass);
101 GstAudioFilterClass *filter_class = GST_AUDIO_FILTER_CLASS (klass);
102
103 GST_DEBUG_CATEGORY_INIT (gst_bpm_detect_debug, "bpm_detect", 0,
104 "audio bpm detection element");
105
106 gobject_class->finalize = gst_bpm_detect_finalize;
107
108 gst_element_class_set_static_metadata (element_class, "BPM Detector",
109 "Filter/Analyzer/Audio", "Detect the BPM of an audio stream",
110 "Sebastian Dröge <slomo@circular-chaos.org>");
111
112 caps = gst_caps_from_string (ALLOWED_CAPS);
113 gst_audio_filter_class_add_pad_templates (GST_AUDIO_FILTER_CLASS (klass),
114 caps);
115 gst_caps_unref (caps);
116
117 trans_class->stop = GST_DEBUG_FUNCPTR (gst_bpm_detect_stop);
118 trans_class->sink_event = GST_DEBUG_FUNCPTR (gst_bpm_detect_event);
119 trans_class->transform_ip = GST_DEBUG_FUNCPTR (gst_bpm_detect_transform_ip);
120 trans_class->passthrough_on_same_caps = TRUE;
121
122 filter_class->setup = GST_DEBUG_FUNCPTR (gst_bpm_detect_setup);
123 }
124
125 static void
gst_bpm_detect_init(GstBPMDetect * bpm_detect)126 gst_bpm_detect_init (GstBPMDetect * bpm_detect)
127 {
128 bpm_detect->priv =
129 (GstBPMDetectPrivate *) gst_bpm_detect_get_instance_private (bpm_detect);
130 bpm_detect->priv->detect = NULL;
131 bpm_detect->bpm = 0.0;
132 }
133
134 static void
gst_bpm_detect_finalize(GObject * object)135 gst_bpm_detect_finalize (GObject * object)
136 {
137 GstBPMDetect *bpm_detect = GST_BPM_DETECT (object);
138
139 if (bpm_detect->priv->detect) {
140 delete bpm_detect->priv->detect;
141
142 bpm_detect->priv->detect = NULL;
143 }
144
145 G_OBJECT_CLASS (parent_class)->finalize (object);
146 }
147
148 static gboolean
gst_bpm_detect_stop(GstBaseTransform * trans)149 gst_bpm_detect_stop (GstBaseTransform * trans)
150 {
151 GstBPMDetect *bpm_detect = GST_BPM_DETECT (trans);
152
153 if (bpm_detect->priv->detect) {
154 delete bpm_detect->priv->detect;
155
156 bpm_detect->priv->detect = NULL;
157 }
158 bpm_detect->bpm = 0.0;
159
160 return TRUE;
161 }
162
163 static gboolean
gst_bpm_detect_event(GstBaseTransform * trans,GstEvent * event)164 gst_bpm_detect_event (GstBaseTransform * trans, GstEvent * event)
165 {
166 GstBPMDetect *bpm_detect = GST_BPM_DETECT (trans);
167
168 switch (GST_EVENT_TYPE (event)) {
169 case GST_EVENT_FLUSH_STOP:
170 case GST_EVENT_EOS:
171 case GST_EVENT_SEGMENT:
172 if (bpm_detect->priv->detect) {
173 delete bpm_detect->priv->detect;
174
175 bpm_detect->priv->detect = NULL;
176 }
177 bpm_detect->bpm = 0.0;
178 break;
179 default:
180 break;
181 }
182
183 return GST_BASE_TRANSFORM_CLASS (parent_class)->sink_event (trans, event);
184 }
185
186 static gboolean
gst_bpm_detect_setup(GstAudioFilter * filter,const GstAudioInfo * info)187 gst_bpm_detect_setup (GstAudioFilter * filter, const GstAudioInfo * info)
188 {
189 GstBPMDetect *bpm_detect = GST_BPM_DETECT (filter);
190
191 if (bpm_detect->priv->detect) {
192 delete bpm_detect->priv->detect;
193
194 bpm_detect->priv->detect = NULL;
195 }
196
197 return TRUE;
198 }
199
200 static GstFlowReturn
gst_bpm_detect_transform_ip(GstBaseTransform * trans,GstBuffer * in)201 gst_bpm_detect_transform_ip (GstBaseTransform * trans, GstBuffer * in)
202 {
203 GstBPMDetect *bpm_detect = GST_BPM_DETECT (trans);
204 GstAudioFilter *filter = GST_AUDIO_FILTER (trans);
205 gint nsamples;
206 gfloat bpm;
207 GstMapInfo info;
208
209 if (G_UNLIKELY (!bpm_detect->priv->detect)) {
210 if (GST_AUDIO_INFO_FORMAT (&filter->info) == GST_AUDIO_FORMAT_UNKNOWN) {
211 GST_ERROR_OBJECT (bpm_detect, "No channels or rate set yet");
212 return GST_FLOW_ERROR;
213 }
214 #ifdef HAVE_SOUNDTOUCH_1_4
215 bpm_detect->priv->detect =
216 new soundtouch::BPMDetect (GST_AUDIO_INFO_CHANNELS (&filter->info),
217 GST_AUDIO_INFO_RATE (&filter->info));
218 #else
219 bpm_detect->priv->detect =
220 new BPMDetect (GST_AUDIO_INFO_CHANNELS (&filter->info),
221 GST_AUDIO_INFO_RATE (&filter->info));
222 #endif
223 }
224
225 gst_buffer_map (in, &info, GST_MAP_READ);
226
227 nsamples = info.size / (GST_AUDIO_INFO_BPF (&filter->info) * GST_AUDIO_INFO_CHANNELS (&filter->info));
228
229 /* For stereo BPMDetect->inputSamples() does downmixing into the input
230 * data but our buffer data shouldn't be modified.
231 */
232 if (GST_AUDIO_INFO_CHANNELS (&filter->info) == 1) {
233 soundtouch::SAMPLETYPE *inbuf = (soundtouch::SAMPLETYPE *) info.data;
234
235 while (nsamples > 0) {
236 bpm_detect->priv->detect->inputSamples (inbuf, MIN (nsamples, 2048));
237 nsamples -= 2048;
238 inbuf += 2048;
239 }
240 } else {
241 soundtouch::SAMPLETYPE *inbuf, *intmp, data[2 * 2048];
242
243 inbuf = (soundtouch::SAMPLETYPE *) info.data;
244 intmp = data;
245
246 while (nsamples > 0) {
247 memcpy (intmp, inbuf, sizeof (soundtouch::SAMPLETYPE) * 2 * MIN (nsamples, 2048));
248 bpm_detect->priv->detect->inputSamples (intmp, MIN (nsamples, 2048));
249 nsamples -= 2048;
250 inbuf += 2048 * 2;
251 }
252 }
253 gst_buffer_unmap (in, &info);
254
255 bpm = bpm_detect->priv->detect->getBpm ();
256 if (bpm >= 1.0 && fabs (bpm_detect->bpm - bpm) >= 1.0) {
257 GstTagList *tags = gst_tag_list_new_empty ();
258
259 gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE_ALL, GST_TAG_BEATS_PER_MINUTE,
260 bpm, (void *) NULL);
261 gst_pad_push_event (trans->srcpad, gst_event_new_tag (tags));
262
263 GST_INFO_OBJECT (bpm_detect, "Detected BPM: %lf", bpm);
264 bpm_detect->bpm = bpm;
265 }
266
267 return GST_FLOW_OK;
268 }
269