• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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