summaryrefslogtreecommitdiff
path: root/utils/rbutilqt/base/encoderlame.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'utils/rbutilqt/base/encoderlame.cpp')
-rw-r--r--utils/rbutilqt/base/encoderlame.cpp312
1 files changed, 312 insertions, 0 deletions
diff --git a/utils/rbutilqt/base/encoderlame.cpp b/utils/rbutilqt/base/encoderlame.cpp
new file mode 100644
index 0000000000..1658a7092d
--- /dev/null
+++ b/utils/rbutilqt/base/encoderlame.cpp
@@ -0,0 +1,312 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 *
9 * Copyright (C) 2012 Dominik Riebeling
10 *
11 * All files in this archive are subject to the GNU General Public License.
12 * See the file COPYING in the source tree root for full license agreement.
13 *
14 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
15 * KIND, either express or implied.
16 *
17 ****************************************************************************/
18
19#include <QtCore>
20#include "encoderlame.h"
21#include "rbsettings.h"
22#include "lame/lame.h"
23#include "Logger.h"
24
25/** Resolve a symbol from loaded library.
26 */
27#define SYMBOLRESOLVE(symbol, type) \
28 do { m_##symbol = (type)lib.resolve(#symbol); \
29 if(!m_##symbol) return; \
30 LOG_INFO() << "Resolved symbol " #symbol; } \
31 while(0)
32
33EncoderLame::EncoderLame(QObject *parent) : EncoderBase(parent),
34 lib("libmp3lame", this), m_symbolsResolved(false)
35{
36 lib.load();
37 if (!lib.isLoaded()) {
38 LOG_WARNING() << "Loading mp3lame lib failed:" << lib.errorString();
39 return;
40 }
41
42 SYMBOLRESOLVE(get_lame_short_version, const char* (*)());
43 SYMBOLRESOLVE(lame_set_out_samplerate, int (*)(lame_global_flags*, int));
44 SYMBOLRESOLVE(lame_set_in_samplerate, int (*)(lame_global_flags*, int));
45 SYMBOLRESOLVE(lame_set_num_channels, int (*)(lame_global_flags*, int));
46 SYMBOLRESOLVE(lame_set_scale, int (*)(lame_global_flags*, float));
47 SYMBOLRESOLVE(lame_set_mode, int (*)(lame_global_flags*, MPEG_mode));
48 SYMBOLRESOLVE(lame_set_VBR, int (*)(lame_global_flags*, vbr_mode));
49 SYMBOLRESOLVE(lame_set_VBR_quality, int (*)(lame_global_flags*, float));
50 SYMBOLRESOLVE(lame_set_VBR_max_bitrate_kbps, int (*)(lame_global_flags*, int));
51 SYMBOLRESOLVE(lame_set_bWriteVbrTag, int (*)(lame_global_flags*, int));
52 SYMBOLRESOLVE(lame_init, lame_global_flags* (*)());
53 SYMBOLRESOLVE(lame_init_params, int (*)(lame_global_flags*));
54 SYMBOLRESOLVE(lame_encode_buffer, int (*)(lame_global_flags*, short int*, short int*, int, unsigned char*, int));
55 SYMBOLRESOLVE(lame_encode_flush, int (*)(lame_global_flags*, unsigned char*, int));
56 SYMBOLRESOLVE(lame_close, int (*)(lame_global_flags*));
57
58 m_encoderVolume = RbSettings::subValue("lame", RbSettings::EncoderVolume).toDouble();
59 m_encoderQuality = RbSettings::subValue("lame", RbSettings::EncoderQuality).toDouble();
60 m_symbolsResolved = true;
61}
62
63void EncoderLame::generateSettings()
64{
65 // no settings for now.
66 // show lame version.
67 if(m_symbolsResolved) {
68 double quality = RbSettings::subValue("lame",
69 RbSettings::EncoderQuality).toDouble();
70 // default quality is 0.999.
71 if(quality < 0) {
72 quality = 0.99;
73 }
74 insertSetting(LAMEVERSION, new EncTtsSetting(this, EncTtsSetting::eREADONLYSTRING,
75 tr("LAME"), QString(m_get_lame_short_version())));
76 insertSetting(VOLUME, new EncTtsSetting(this, EncTtsSetting::eDOUBLE,
77 tr("Volume"),
78 RbSettings::subValue("lame", RbSettings::EncoderVolume).toDouble(),
79 0.0, 2.0));
80 insertSetting(QUALITY, new EncTtsSetting(this, EncTtsSetting::eDOUBLE,
81 tr("Quality"), quality, 0.0, 1.0));
82 }
83 else {
84 insertSetting(LAMEVERSION, new EncTtsSetting(this, EncTtsSetting::eREADONLYSTRING,
85 tr("LAME"), tr("Could not find libmp3lame!")));
86 }
87}
88
89void EncoderLame::saveSettings()
90{
91 if(m_symbolsResolved) {
92 RbSettings::setSubValue("lame", RbSettings::EncoderVolume,
93 getSetting(VOLUME)->current().toDouble());
94 RbSettings::setSubValue("lame", RbSettings::EncoderQuality,
95 getSetting(QUALITY)->current().toDouble());
96 m_encoderVolume =
97 RbSettings::subValue("lame", RbSettings::EncoderVolume).toDouble();
98 m_encoderQuality =
99 RbSettings::subValue("lame", RbSettings::EncoderQuality).toDouble();
100 }
101}
102
103bool EncoderLame::start()
104{
105 if(!m_symbolsResolved) {
106 return false;
107 }
108 // try to get config from settings
109 return true;
110}
111
112bool EncoderLame::encode(QString input,QString output)
113{
114 LOG_INFO() << "Encoding" << QDir::cleanPath(input);
115 if(!m_symbolsResolved) {
116 LOG_ERROR() << "Symbols not successfully resolved, cannot run!";
117 return false;
118 }
119
120 QFile fin(input);
121 QFile fout(output);
122 // initialize encoder
123 lame_global_flags *gfp;
124 unsigned char header[12];
125 unsigned char chunkheader[8];
126 unsigned int datalength = 0;
127 unsigned int channels = 0;
128 unsigned int samplerate = 0;
129 unsigned int samplesize = 0;
130 int num_samples = 0;
131 int ret;
132 unsigned char* mp3buf;
133 int mp3buflen;
134 short int* wavbuf;
135 int wavbuflen;
136
137
138 gfp = m_lame_init();
139 m_lame_set_out_samplerate(gfp, 12000); // resample to 12kHz
140 // scale input volume
141 m_lame_set_scale(gfp, m_encoderVolume);
142 m_lame_set_mode(gfp, MONO); // mono output mode
143 m_lame_set_VBR(gfp, vbr_default); // enable default VBR mode
144 // VBR quality
145 m_lame_set_VBR_quality(gfp, m_encoderQuality);
146 m_lame_set_VBR_max_bitrate_kbps(gfp, 64); // maximum bitrate 64kbps
147 m_lame_set_bWriteVbrTag(gfp, 0); // disable LAME tag.
148
149 if(!fin.open(QIODevice::ReadOnly)) {
150 LOG_ERROR() << "Could not open input file" << input;
151 return false;
152 }
153
154 // read RIFF header
155 fin.read((char*)header, 12);
156 if(memcmp("RIFF", header, 4) != 0) {
157 LOG_ERROR() << "RIFF header not found!"
158 << header[0] << header[1] << header[2] << header[3];
159 fin.close();
160 return false;
161 }
162 if(memcmp("WAVE", &header[8], 4) != 0) {
163 LOG_ERROR() << "WAVE FOURCC not found!"
164 << header[8] << header[9] << header[10] << header[11];
165 fin.close();
166 return false;
167 }
168
169 // search for fmt chunk
170 do {
171 // read fmt
172 fin.read((char*)chunkheader, 8);
173 int chunkdatalen = chunkheader[4] | chunkheader[5]<<8
174 | chunkheader[6]<<16 | chunkheader[7]<<24;
175 if(memcmp("fmt ", chunkheader, 4) == 0) {
176 // fmt found, read rest of chunk.
177 // NOTE: This code ignores the format tag value.
178 // Ideally this should be checked as well. However, rbspeex doesn't
179 // check the format tag either when reading wave files, so if
180 // problems arise we should notice pretty soon. Furthermore, the
181 // input format used should be known. In case some TTS uses a
182 // different wave encoding some time this needs to get adjusted.
183 if(chunkdatalen < 16) {
184 LOG_ERROR() << "fmt chunk too small!";
185 }
186 else {
187 unsigned char *buf = new unsigned char[chunkdatalen];
188 fin.read((char*)buf, chunkdatalen);
189 channels = buf[2] | buf[3]<<8;
190 samplerate = buf[4] | buf[5]<<8 | buf[6]<<16 | buf[7]<<24;
191 samplesize = buf[14] | buf[15]<<8;
192 delete[] buf;
193 }
194 }
195 // read data
196 else if(memcmp("data", chunkheader, 4) == 0) {
197 datalength = chunkdatalen;
198 break;
199 }
200 else {
201 // unknown chunk, just skip its data.
202 LOG_WARNING() << "unknown chunk, skipping."
203 << chunkheader[0] << chunkheader[1]
204 << chunkheader[2] << chunkheader[3];
205 fin.seek(fin.pos() + chunkdatalen);
206 }
207 } while(!fin.atEnd());
208
209 // check format
210 if(channels == 0 || samplerate == 0 || samplesize == 0 || datalength == 0) {
211 LOG_ERROR() << "invalid format. Channels:" << channels
212 << "Samplerate:" << samplerate << "Samplesize:" << samplesize
213 << "Data chunk length:" << datalength;
214 fin.close();
215 return false;
216 }
217 num_samples = (datalength / channels / (samplesize/8));
218
219 // set input format values
220 m_lame_set_in_samplerate(gfp, samplerate);
221 m_lame_set_num_channels(gfp, channels);
222
223 // initialize encoder.
224 ret = m_lame_init_params(gfp);
225 if(ret != 0) {
226 LOG_ERROR() << "lame_init_params() failed with" << ret;
227 fin.close();
228 return false;
229 }
230
231 // we're dealing with rather small files here (100kB-ish), so don't care
232 // about the possible output size and simply allocate the same number of
233 // bytes the input file has. This wastes space but should be ok.
234 // Put an upper limit of 8MiB.
235 if(datalength > 8*1024*1024) {
236 LOG_ERROR() << "Input file too large:" << datalength;
237 fin.close();
238 return false;
239 }
240 mp3buflen = datalength;
241 wavbuflen = datalength;
242 mp3buf = new unsigned char[mp3buflen];
243 wavbuf = new short int[wavbuflen];
244#if defined(Q_OS_MACX)
245 // handle byte order -- the host might not be LE.
246 if(samplesize == 8) {
247 // no need to convert.
248 fin.read((char*)wavbuf, wavbuflen);
249 }
250 else if(samplesize == 16) {
251 // read LE 16bit words. Since the input format is either mono or
252 // interleaved there's no need to care for that.
253 unsigned int pos = 0;
254 char word[2];
255 while(pos < datalength) {
256 fin.read(word, 2);
257 wavbuf[pos++] = (word[0]&0xff) | ((word[1]<<8)&0xff00);
258 }
259 }
260 else {
261 LOG_ERROR() << "Unknown samplesize:" << samplesize;
262 fin.close();
263 delete[] mp3buf;
264 delete[] wavbuf;
265 return false;
266 }
267#else
268 // all systems but OS X are considered LE.
269 fin.read((char*)wavbuf, wavbuflen);
270#endif
271 fin.close();
272 // encode data.
273 fout.open(QIODevice::ReadWrite);
274 ret = m_lame_encode_buffer(gfp, wavbuf, wavbuf, num_samples, mp3buf, mp3buflen);
275 if(ret < 0) {
276 LOG_ERROR() << "Error during encoding:" << ret;
277 }
278 if(fout.write((char*)mp3buf, ret) != (unsigned int)ret) {
279 LOG_ERROR() << "Writing mp3 data failed!" << ret;
280 fout.close();
281 delete[] mp3buf;
282 delete[] wavbuf;
283 return false;
284 }
285 // flush remaining data
286 ret = m_lame_encode_flush(gfp, mp3buf, mp3buflen);
287 if(fout.write((char*)mp3buf, ret) != (unsigned int)ret) {
288 LOG_ERROR() << "Writing final mp3 data failed!";
289 fout.close();
290 delete[] mp3buf;
291 delete[] wavbuf;
292 return false;
293 }
294 // shut down encoder and clean up.
295 m_lame_close(gfp);
296 fout.close();
297 delete[] mp3buf;
298 delete[] wavbuf;
299
300 return true;
301}
302
303/** Check if the current configuration is usable.
304 * Since we're loading a library dynamically in the constructor test if that
305 * succeeded. Otherwise the "configuration" is not usable, even though the
306 * problem is not necessarily related to configuration values set by the user.
307 */
308bool EncoderLame::configOk()
309{
310 return m_symbolsResolved;
311}
312