1 /*
2  * Strawberry Music Player
3  * This file was part of Clementine.
4  * Copyright 2012, David Sansome <me@davidsansome.com>
5  * Copyright 2019-2021, Jonas Kvinge <jonas@jkvinge.net>
6  *
7  * Strawberry is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation, either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * Strawberry is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with Strawberry.  If not, see <http://www.gnu.org/licenses/>.
19  *
20  */
21 
22 #include "config.h"
23 
24 #include <glib-object.h>
25 #include <cstdint>
26 #include <cstdlib>
27 #include <cstring>
28 #include <sys/types.h>
29 #include <chromaprint.h>
30 #include <gst/gst.h>
31 
32 #include <QtGlobal>
33 #include <QCoreApplication>
34 #include <QThread>
35 #include <QIODevice>
36 #include <QBuffer>
37 #include <QByteArray>
38 #include <QString>
39 #include <QElapsedTimer>
40 #include <QtDebug>
41 
42 #include "chromaprinter.h"
43 #include "core/logging.h"
44 #include "core/signalchecker.h"
45 
46 #ifndef u_int32_t
47 typedef unsigned int u_int32_t;
48 #endif
49 
50 static const int kDecodeRate = 11025;
51 static const int kDecodeChannels = 1;
52 static const int kPlayLengthSecs = 30;
53 static const int kTimeoutSecs = 10;
54 
55 Chromaprinter::Chromaprinter(const QString &filename)
56     : filename_(filename),
57       convert_element_(nullptr) {}
58 
59 GstElement *Chromaprinter::CreateElement(const QString &factory_name, GstElement *bin) {
60 
61   GstElement *ret = gst_element_factory_make(factory_name.toLatin1().constData(), factory_name.toLatin1().constData());
62 
63   if (ret && bin) gst_bin_add(GST_BIN(bin), ret);
64 
65   if (!ret) {
66     qLog(Warning) << "Couldn't create the gstreamer element" << factory_name;
67   }
68 
69   return ret;
70 
71 }
72 
73 QString Chromaprinter::CreateFingerprint() {
74 
75   Q_ASSERT(QThread::currentThread() != qApp->thread());
76 
77   if (!buffer_.open(QIODevice::WriteOnly)) return QString();
78 
79   GstElement *pipeline = gst_pipeline_new("pipeline");
80   if (!pipeline) {
81     buffer_.close();
82     return QString();
83   }
84 
85   GstElement *src = CreateElement("filesrc", pipeline);
86   GstElement *decode = CreateElement("decodebin", pipeline);
87   GstElement *convert = CreateElement("audioconvert", pipeline);
88   GstElement *resample = CreateElement("audioresample", pipeline);
89   GstElement *sink = CreateElement("appsink", pipeline);
90 
91   if (!src || !decode || !convert || !resample || !sink) {
92     gst_object_unref(pipeline);
93     buffer_.close();
94     return QString();
95   }
96 
97   convert_element_ = convert;
98 
99   // Connect the elements
100   gst_element_link_many(src, decode, nullptr);
101   gst_element_link_many(convert, resample, nullptr);
102 
103   // Chromaprint expects mono 16-bit ints at a sample rate of 11025Hz.
104   GstCaps *caps = gst_caps_new_simple("audio/x-raw", "format", G_TYPE_STRING, "S16LE", "channels", G_TYPE_INT, kDecodeChannels, "rate", G_TYPE_INT, kDecodeRate, nullptr);
105   gst_element_link_filtered(resample, sink, caps);
106   gst_caps_unref(caps);
107 
108   GstAppSinkCallbacks callbacks;
109   memset(&callbacks, 0, sizeof(callbacks));
110   callbacks.new_sample = NewBufferCallback;
111   gst_app_sink_set_callbacks(reinterpret_cast<GstAppSink*>(sink), &callbacks, this, nullptr);
112   g_object_set(G_OBJECT(sink), "sync", FALSE, nullptr);
113   g_object_set(G_OBJECT(sink), "emit-signals", TRUE, nullptr);
114 
115   // Set the filename
116   g_object_set(src, "location", filename_.toUtf8().constData(), nullptr);
117 
118   // Connect signals
119   GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline));
120   CHECKED_GCONNECT(decode, "pad-added", &NewPadCallback, this);
121 
122   // Play only first x seconds
123   gst_element_set_state(pipeline, GST_STATE_PAUSED);
124   // wait for state change before seeking
125   gst_element_get_state(pipeline, nullptr, nullptr, kTimeoutSecs * GST_SECOND);
126   gst_element_seek(pipeline, 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, GST_SEEK_TYPE_SET, 0 * GST_SECOND, GST_SEEK_TYPE_SET, kPlayLengthSecs * GST_SECOND);
127 
128   QElapsedTimer time;
129   time.start();
130 
131   // Start playing
132   gst_element_set_state(pipeline, GST_STATE_PLAYING);
133 
134   // Wait until EOS or error
135   GstMessage *msg = gst_bus_timed_pop_filtered(bus, kTimeoutSecs * GST_SECOND, static_cast<GstMessageType>(GST_MESSAGE_EOS | GST_MESSAGE_ERROR));
136   if (msg) {
137     if (msg->type == GST_MESSAGE_ERROR) {
138       // Report error
139       GError *error = nullptr;
140       gchar *debugs = nullptr;
141       gst_message_parse_error(msg, &error, &debugs);
142       if (error) {
143         QString message = QString::fromLocal8Bit(error->message);
144         g_error_free(error);
145         qLog(Debug) << "Error processing" << filename_ << ":" << message;
146       }
147       if (debugs) free(debugs);
148     }
149     gst_message_unref(msg);
150   }
151 
152   const int decode_time = time.restart();
153 
154   buffer_.close();
155 
156   // Generate fingerprint from recorded buffer data
157   QByteArray data = buffer_.data();
158 
159   ChromaprintContext *chromaprint = chromaprint_new(CHROMAPRINT_ALGORITHM_DEFAULT);
160   chromaprint_start(chromaprint, kDecodeRate, kDecodeChannels);
161   chromaprint_feed(chromaprint, reinterpret_cast<int16_t*>(data.data()), static_cast<int>(data.size() / 2));
162   chromaprint_finish(chromaprint);
163 
164   u_int32_t *fprint = nullptr;
165   int size = 0;
166   int ret = chromaprint_get_raw_fingerprint(chromaprint, &fprint, &size);
167   QByteArray fingerprint;
168   if (ret == 1) {
169     char *encoded = nullptr;
170     int encoded_size = 0;
171     ret = chromaprint_encode_fingerprint(fprint, size, CHROMAPRINT_ALGORITHM_DEFAULT, &encoded, &encoded_size, 1);
172     if (ret == 1) {
173       fingerprint.append(reinterpret_cast<char*>(encoded), encoded_size);
174       chromaprint_dealloc(encoded);
175     }
176     chromaprint_dealloc(fprint);
177   }
178   chromaprint_free(chromaprint);
179 
180   const int codegen_time = time.elapsed();
181 
182   qLog(Debug) << "Decode time:" << decode_time << "Codegen time:" << codegen_time;
183 
184   // Cleanup
185   callbacks.new_sample = nullptr;
186   gst_object_unref(bus);
187   gst_element_set_state(pipeline, GST_STATE_NULL);
188   gst_object_unref(pipeline);
189 
190   return fingerprint;
191 
192 }
193 
194 void Chromaprinter::NewPadCallback(GstElement*, GstPad *pad, gpointer data) {
195 
196   Chromaprinter *instance = reinterpret_cast<Chromaprinter*>(data);
197   GstPad *const audiopad = gst_element_get_static_pad(instance->convert_element_, "sink");
198 
199   if (GST_PAD_IS_LINKED(audiopad)) {
200     qLog(Warning) << "audiopad is already linked, unlinking old pad";
201     gst_pad_unlink(audiopad, GST_PAD_PEER(audiopad));
202   }
203 
204   gst_pad_link(pad, audiopad);
205   gst_object_unref(audiopad);
206 
207 }
208 
209 GstFlowReturn Chromaprinter::NewBufferCallback(GstAppSink *app_sink, gpointer self) {
210 
211   Chromaprinter *me = reinterpret_cast<Chromaprinter*>(self);
212 
213   GstSample *sample = gst_app_sink_pull_sample(app_sink);
214   if (!sample) return GST_FLOW_ERROR;
215   GstBuffer *buffer = gst_sample_get_buffer(sample);
216   if (buffer) {
217     GstMapInfo map;
218     if (gst_buffer_map(buffer, &map, GST_MAP_READ)) {
219       me->buffer_.write(reinterpret_cast<const char*>(map.data), map.size);
220       gst_buffer_unmap(buffer, &map);
221     }
222   }
223   gst_sample_unref(sample);
224 
225   return GST_FLOW_OK;
226 
227 }
228 
229