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