1 /***************************************************************************
2 * Copyright (C) 2013-2021 by Ilya Kotov *
3 * forkotov02@ya.ru *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
20
21 #include <QStringList>
22 #include <math.h>
23 #include <qmmp/inputsourcefactory.h>
24 #include <qmmp/decoderfactory.h>
25 #include <qmmp/audioconverter.h>
26 #include "rgscanner.h"
27
28 #define BUFFER_FRAMES 4096
29
RGScanner()30 RGScanner::RGScanner()
31 {
32 m_gain = 0.;
33 m_peak = 0.;
34 m_user_stop = false;
35 m_is_running = false;
36 m_is_pending = false;
37 m_has_values = false;
38 m_handle = nullptr;
39 m_decoder = nullptr;
40 m_source = nullptr;
41 }
42
~RGScanner()43 RGScanner::~RGScanner()
44 {
45 stop();
46 deinit();
47 if(m_handle)
48 {
49 DeinitGainAnalysis(m_handle);
50 m_handle = nullptr;
51 }
52 }
53
prepare(const QString & url)54 bool RGScanner::prepare(const QString &url)
55 {
56 m_is_pending = false;
57 deinit();
58 m_url = url;
59 QString name = m_url.section("/", -1);
60 InputSource *source = InputSource::create(url, nullptr);
61 if(!source->initialize())
62 {
63 delete source;
64 qWarning("RGScanner: Invalid url");
65 return false;
66 }
67
68 if(source->ioDevice() && !source->ioDevice()->open(QIODevice::ReadOnly))
69 {
70 qWarning("RGScanner: [%s] unable to open input stream, error: %s",
71 qPrintable(name),
72 qPrintable(source->ioDevice()->errorString()));
73 delete source;
74 return false;
75 }
76
77 DecoderFactory *factory = Decoder::findByFilePath(source->path());
78
79 if(!factory)
80 {
81 qWarning("RGScanner: [%s] unable to find factory", qPrintable(name));
82 delete source;
83 return false;
84 }
85 qDebug("RGScanner: [%s] selected decoder: %s",qPrintable(name),
86 qPrintable(factory->properties().shortName));
87
88 if(factory->properties().noInput && source->ioDevice())
89 source->ioDevice()->close();
90
91 Decoder *decoder = factory->create(source->path(), source->ioDevice());
92 if(!decoder->initialize())
93 {
94 qWarning("RGScanner: [%s] invalid file format", qPrintable(name));
95 delete source;
96 delete decoder;
97 return false;
98 }
99 if(decoder->audioParameters().channels() > 2)
100 {
101 qWarning("RGScanner: [%s] unsupported channel number: %d",
102 qPrintable(name),
103 decoder->audioParameters().channels());
104 delete source;
105 delete decoder;
106 return false;
107 }
108 m_decoder = decoder;
109 m_source = source;
110 m_user_stop = false;
111 m_has_values = false;
112 m_is_pending = true;
113 return true;
114 }
115
stop()116 void RGScanner::stop()
117 {
118 m_mutex.lock();
119 m_user_stop = true;
120 m_mutex.unlock();
121 }
122
isRunning() const123 bool RGScanner::isRunning() const
124 {
125 return m_is_running;
126 }
127
isPending() const128 bool RGScanner::isPending() const
129 {
130 return m_is_pending;
131 }
132
hasValues() const133 bool RGScanner::hasValues() const
134 {
135 return m_has_values;
136 }
137
oldReplayGainInfo() const138 QMap<Qmmp::ReplayGainKey, double> RGScanner::oldReplayGainInfo() const
139 {
140 if(!m_decoder)
141 return QMap<Qmmp::ReplayGainKey, double>();
142
143 return m_decoder->replayGainInfo();
144 }
145
gain() const146 double RGScanner::gain() const
147 {
148 return m_gain;
149 }
150
peak() const151 double RGScanner::peak() const
152 {
153 return m_peak;
154 }
155
url() const156 QString RGScanner::url() const
157 {
158 return m_url;
159 }
160
handle()161 GainHandle_t *RGScanner::handle()
162 {
163 return m_handle;
164 }
165
run()166 void RGScanner::run()
167 {
168 if(m_user_stop)
169 {
170 m_is_pending = false;
171 return;
172 }
173 QString name = m_url.section("/", -1);
174 qDebug("RGScanner: [%s] staring thread", qPrintable(name));
175 m_is_running = true;
176 m_is_pending = false;
177 bool error = false;
178
179 AudioParameters ap = m_decoder->audioParameters();
180 AudioConverter converter;
181 converter.configure(ap.format());
182 //buffers
183 double out_left[BUFFER_FRAMES] = {0}, out_right[BUFFER_FRAMES] = {0}; //replay gain buffers
184 float float_buf[BUFFER_FRAMES*ap.channels()]; //float buffer
185 qint64 buf_size = BUFFER_FRAMES*ap.frameSize();
186 unsigned char char_buf[buf_size]; //char buffer
187
188
189 //counters
190 qint64 totalSamples = m_decoder->totalTime() * ap.sampleRate() * ap.channels() / 1000, len = 0;
191 quint64 sample_counter = 0;
192 quint64 samples = 0;
193 double max = 0;
194
195 if(m_handle)
196 DeinitGainAnalysis(m_handle);
197 InitGainAnalysis(&m_handle, ap.sampleRate());
198
199 forever
200 {
201 len = m_decoder->read(char_buf, buf_size);
202
203 if(len < 0)
204 {
205 error = true;
206 break;
207 }
208 else if(len == 0)
209 break;
210
211 samples = len / ap.sampleSize();
212
213 converter.toFloat(char_buf, float_buf, samples);
214
215 if(ap.channels() == 2)
216 {
217 for(uint i = 0; i < (samples >> 1); ++i)
218 {
219 out_left[i] = float_buf[i*2]*32768.0;
220 out_right[i] = float_buf[i*2+1]*32768.0;
221 max = qMax(fabs(out_left[i]), max);
222 max = qMax(fabs(out_right[i]), max);
223 }
224 }
225 else if(ap.channels() == 1)
226 {
227 for(uint i = 0; i < samples; ++i)
228 {
229 out_left[i] = float_buf[i]*32768.0;
230 max = qMax(fabs(out_left[i]), max);
231 }
232 }
233
234 size_t samples_per_channel = samples >> ((ap.channels() == 2) ? 1 : 0);
235
236 AnalyzeSamples(m_handle, out_left, out_right, samples_per_channel, ap.channels());
237 sample_counter += samples;
238 emit progress(100 * sample_counter / totalSamples);
239
240 m_mutex.lock();
241 if(m_user_stop)
242 {
243 m_mutex.unlock();
244 break;
245 }
246 m_mutex.unlock();
247 }
248
249 if(error)
250 {
251 qWarning("RGScanner: [%s] finished with error", qPrintable(name));
252 }
253 else if(m_user_stop)
254 {
255 qDebug("RGScanner: [%s] stopped by user", qPrintable(name));
256 }
257 else
258 {
259 m_gain = GetTitleGain(m_handle);
260 m_peak = max/32768.0;
261 emit progress(100);
262 qDebug("RGScanner: [%s] peak=%f, gain=%f", qPrintable(name), m_peak, m_gain);
263 qDebug("RGScanner: [%s] finished with success ", qPrintable(name));
264 m_has_values = true;
265 }
266 deinit();
267 m_is_running = false;
268 emit finished(m_url);
269 }
270
deinit()271 void RGScanner::deinit()
272 {
273 if(m_decoder)
274 {
275 delete m_decoder;
276 m_decoder = nullptr;
277 }
278 if(m_source)
279 {
280 delete m_source;
281 m_source = nullptr;
282 }
283 }
284