diff options
author | Dominik Riebeling <Dominik.Riebeling@gmail.com> | 2021-12-15 21:04:28 +0100 |
---|---|---|
committer | Dominik Riebeling <Dominik.Riebeling@gmail.com> | 2021-12-24 18:05:53 +0100 |
commit | c876d3bbefe0dc00c27ca0c12d29da5874946962 (patch) | |
tree | 69f468a185a369b01998314bc3ecc19b70f4fcaa /utils/rbutilqt/base/encoderlame.cpp | |
parent | 6c6f0757d7a902feb293be165d1490c42bc8e7ad (diff) | |
download | rockbox-c876d3bbefe0dc00c27ca0c12d29da5874946962.tar.gz rockbox-c876d3bbefe0dc00c27ca0c12d29da5874946962.zip |
rbutil: Merge rbutil with utils folder.
rbutil uses several components from the utils folder, and can be
considered part of utils too. Having it in a separate folder is an
arbitrary split that doesn't help anymore these days, so merge them.
This also allows other utils to easily use libtools.make without the
need to navigate to a different folder.
Change-Id: I3fc2f4de19e3e776553efb5dea5f779dfec0dc21
Diffstat (limited to 'utils/rbutilqt/base/encoderlame.cpp')
-rw-r--r-- | utils/rbutilqt/base/encoderlame.cpp | 312 |
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 | |||
33 | EncoderLame::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 | |||
63 | void 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 | |||
89 | void 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 | |||
103 | bool EncoderLame::start() | ||
104 | { | ||
105 | if(!m_symbolsResolved) { | ||
106 | return false; | ||
107 | } | ||
108 | // try to get config from settings | ||
109 | return true; | ||
110 | } | ||
111 | |||
112 | bool 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 | */ | ||
308 | bool EncoderLame::configOk() | ||
309 | { | ||
310 | return m_symbolsResolved; | ||
311 | } | ||
312 | |||