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