2
// banshee-bpmdetector.c
5
// Gabriel Burt <gburt@novell.com>
7
// Copyright (C) 2008 Novell, Inc.
9
// Permission is hereby granted, free of charge, to any person obtaining
10
// a copy of this software and associated documentation files (the
11
// "Software"), to deal in the Software without restriction, including
12
// without limitation the rights to use, copy, modify, merge, publish,
13
// distribute, sublicense, and/or sell copies of the Software, and to
14
// permit persons to whom the Software is furnished to do so, subject to
15
// the following conditions:
17
// The above copyright notice and this permission notice shall be
18
// included in all copies or substantial portions of the Software.
20
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
35
#include <glib/gi18n.h>
37
#include "banshee-gst.h"
38
#include "banshee-tagger.h"
40
typedef struct BansheeBpmDetector BansheeBpmDetector;
42
typedef void (* BansheeBpmDetectorFinishedCallback) ();
43
typedef void (* BansheeBpmDetectorProgressCallback) (double bpm);
44
typedef void (* BansheeBpmDetectorErrorCallback) (const gchar *error, const gchar *debug);
46
// Only analyze 20 seconds of audio per song
47
#define BPM_DETECT_ANALYSIS_DURATION_MS 20*1000
49
struct BansheeBpmDetector {
50
gboolean is_detecting;
53
* You can run this pipeline on the cmd line with:
54
* gst-launch -m filesrc location=/path/to/my.mp3 ! decodebin ! \
55
* audioconvert ! bpmdetect ! fakesink
60
GstElement *decodebin;
61
GstElement *audioconvert;
62
GstElement *bpmdetect;
65
BansheeBpmDetectorProgressCallback progress_cb;
66
BansheeBpmDetectorFinishedCallback finished_cb;
67
BansheeBpmDetectorErrorCallback error_cb;
70
// ---------------------------------------------------------------------------
72
// ---------------------------------------------------------------------------
75
bbd_raise_error (BansheeBpmDetector *detector, const gchar *error, const gchar *debug)
77
printf ("bpm_detect got error: %s %s\n", error, debug);
78
g_return_if_fail (detector != NULL);
80
if (detector->error_cb != NULL) {
81
detector->error_cb (error, debug);
86
bbd_pipeline_process_tag (const GstTagList *tag_list, const gchar *tag_name, BansheeBpmDetector *detector)
92
g_return_if_fail (detector != NULL);
94
if (detector->progress_cb == NULL) {
98
if (strcmp (tag_name, GST_TAG_BEATS_PER_MINUTE)) {
102
value_count = gst_tag_list_get_tag_size (tag_list, tag_name);
103
if (value_count < 1) {
107
value = gst_tag_list_get_value_index (tag_list, tag_name, 0);
108
if (value != NULL && G_VALUE_HOLDS_DOUBLE (value)) {
109
bpm = g_value_get_double (value);
110
detector->progress_cb (bpm);
115
bbd_pipeline_bus_callback (GstBus *bus, GstMessage *message, gpointer data)
117
BansheeBpmDetector *detector = (BansheeBpmDetector *)data;
119
g_return_val_if_fail (detector != NULL, FALSE);
121
switch (GST_MESSAGE_TYPE (message)) {
122
case GST_MESSAGE_TAG: {
124
gst_message_parse_tag (message, &tags);
125
if (GST_IS_TAG_LIST (tags)) {
126
gst_tag_list_foreach (tags, (GstTagForeachFunc)bbd_pipeline_process_tag, detector);
127
gst_tag_list_free (tags);
132
case GST_MESSAGE_ERROR: {
136
gst_message_parse_error (message, &error, &debug);
137
bbd_raise_error (detector, error->message, debug);
138
g_error_free (error);
141
detector->is_detecting = FALSE;
145
case GST_MESSAGE_EOS: {
146
detector->is_detecting = FALSE;
147
gst_element_set_state (GST_ELEMENT (detector->pipeline), GST_STATE_NULL);
149
if (detector->finished_cb != NULL) {
150
detector->finished_cb ();
162
bbd_new_decoded_pad(GstElement *decodebin, GstPad *pad,
163
gboolean last, gpointer data)
168
BansheeBpmDetector *detector = (BansheeBpmDetector *)data;
170
g_return_if_fail(detector != NULL);
172
audiopad = gst_element_get_static_pad(detector->audioconvert, "sink");
174
if(GST_PAD_IS_LINKED(audiopad)) {
175
g_object_unref(audiopad);
179
caps = gst_pad_query_caps(pad, NULL);
180
str = gst_caps_get_structure(caps, 0);
182
if(!g_strrstr(gst_structure_get_name(str), "audio")) {
183
gst_caps_unref(caps);
184
gst_object_unref(audiopad);
188
gst_caps_unref(caps);
189
gst_pad_link(pad, audiopad);
193
bbd_pipeline_construct (BansheeBpmDetector *detector)
195
g_return_val_if_fail (detector != NULL, FALSE);
197
if (detector->pipeline != NULL) {
201
detector->pipeline = gst_pipeline_new ("pipeline");
202
if (detector->pipeline == NULL) {
203
bbd_raise_error (detector, _("Could not create pipeline"), NULL);
207
detector->filesrc = gst_element_factory_make ("filesrc", "filesrc");
208
if (detector->filesrc == NULL) {
209
bbd_raise_error (detector, _("Could not create filesrc element"), NULL);
213
detector->decodebin = gst_element_factory_make ("decodebin", "decodebin");
214
if (detector->decodebin == NULL) {
215
bbd_raise_error (detector, _("Could not create decodebin plugin"), NULL);
219
detector->audioconvert = gst_element_factory_make ("audioconvert", "audioconvert");
220
if (detector->audioconvert == NULL) {
221
bbd_raise_error (detector, _("Could not create audioconvert plugin"), NULL);
225
detector->bpmdetect = gst_element_factory_make ("bpmdetect", "bpmdetect");
226
if (detector->bpmdetect == NULL) {
227
bbd_raise_error (detector, _("Could not create bpmdetect plugin"), NULL);
231
detector->fakesink = gst_element_factory_make ("fakesink", "bpmfakesink");
232
if (detector->fakesink == NULL) {
233
bbd_raise_error (detector, _("Could not create fakesink plugin"), NULL);
237
gst_bin_add_many (GST_BIN (detector->pipeline),
238
detector->filesrc, detector->decodebin, detector->audioconvert,
239
detector->bpmdetect, detector->fakesink, NULL);
241
if (!gst_element_link (detector->filesrc, detector->decodebin)) {
242
bbd_raise_error (detector, _("Could not link pipeline elements"), NULL);
246
// decodebin and audioconvert are linked dynamically when the decodebin creates a new pad
247
g_signal_connect(detector->decodebin, "new-decoded-pad",
248
G_CALLBACK(bbd_new_decoded_pad), detector);
250
if (!gst_element_link_many (detector->audioconvert, detector->bpmdetect, detector->fakesink, NULL)) {
251
bbd_raise_error (detector, _("Could not link pipeline elements"), NULL);
255
gst_bus_add_watch (gst_pipeline_get_bus (GST_PIPELINE (detector->pipeline)), bbd_pipeline_bus_callback, detector);
260
// ---------------------------------------------------------------------------
261
// Internal Functions
262
// ---------------------------------------------------------------------------
267
return g_new0 (BansheeBpmDetector, 1);
271
bbd_cancel (BansheeBpmDetector *detector)
273
g_return_if_fail (detector != NULL);
275
if (detector->pipeline != NULL && GST_IS_ELEMENT (detector->pipeline)) {
276
gst_element_set_state (GST_ELEMENT (detector->pipeline), GST_STATE_NULL);
277
gst_object_unref (GST_OBJECT (detector->pipeline));
278
detector->pipeline = NULL;
283
bbd_destroy (BansheeBpmDetector *detector)
285
g_return_if_fail (detector != NULL);
287
bbd_cancel (detector);
294
bbd_process_file (BansheeBpmDetector *detector, const gchar *path)
296
//static GstFormat format = GST_FORMAT_TIME;
297
//gint64 duration, duration_ms, start_ms, end_ms;
299
g_return_val_if_fail (detector != NULL, FALSE);
301
if (!bbd_pipeline_construct (detector)) {
305
detector->is_detecting = TRUE;
306
gst_element_set_state (detector->fakesink, GST_STATE_NULL);
307
g_object_set (G_OBJECT (detector->filesrc), "location", path, NULL);
309
// TODO listen for transition to STATE_PLAYING, then
310
// Determine how long the file is, and set the detector to base its analysis off the middle 30 seconds of the song
312
/*if (gst_element_query_duration (detector->fakesink, &format, &duration)) {
313
duration_ms = duration / GST_MSECOND;
315
start_ms = CLAMP((duration_ms / 2) - (BPM_DETECT_ANALYSIS_DURATION_MS/2), 0, duration_ms);
316
end_ms = CLAMP(start_ms + BPM_DETECT_ANALYSIS_DURATION_MS, start_ms, duration_ms);
317
printf("Analyzing song %s starting at %d ending at %d\n", path, start_ms/1000, end_ms/1000);
319
if (gst_element_seek (detector->fakesink, 1.0,
320
//GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_SKIP | GST_SEEK_FLAG_KEY_UNIT,
321
GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT,
322
GST_SEEK_TYPE_SET, start_ms * GST_MSECOND,
323
GST_SEEK_TYPE_SET, end_ms * GST_MSECOND))
328
gst_element_set_state (detector->pipeline, GST_STATE_PLAYING);
333
bbd_set_progress_callback (BansheeBpmDetector *detector, BansheeBpmDetectorProgressCallback cb)
335
g_return_if_fail (detector != NULL);
336
detector->progress_cb = cb;
340
bbd_set_finished_callback (BansheeBpmDetector *detector, BansheeBpmDetectorFinishedCallback cb)
342
g_return_if_fail (detector != NULL);
343
detector->finished_cb = cb;
347
bbd_set_error_callback (BansheeBpmDetector *detector, BansheeBpmDetectorErrorCallback cb)
349
g_return_if_fail (detector != NULL);
350
detector->error_cb = cb;
354
bbd_get_is_detecting (BansheeBpmDetector *detector)
356
g_return_val_if_fail (detector != NULL, FALSE);
357
return detector->is_detecting;