summaryrefslogtreecommitdiff
path: root/rbutil/rbutilqt/base/encoderlame.cpp
diff options
context:
space:
mode:
authorDominik Riebeling <Dominik.Riebeling@gmail.com>2012-01-08 11:50:04 +0000
committerDominik Riebeling <Dominik.Riebeling@gmail.com>2012-01-08 11:50:04 +0000
commitf1fc6bae253d55ba4faf0abeeb1c8c3e125627e7 (patch)
treeb5af14bb95660680bdf01f59e29a117cba6a21dd /rbutil/rbutilqt/base/encoderlame.cpp
parent3e014d35231ed66e53c6531d8d0d1e4603f9df93 (diff)
downloadrockbox-f1fc6bae253d55ba4faf0abeeb1c8c3e125627e7.tar.gz
rockbox-f1fc6bae253d55ba4faf0abeeb1c8c3e125627e7.zip
Rockbox Utility: use libmp3lame for voice clips.
Instead of calling the lame executable use libmp3lame directly. As result, this simplifies the prerequisites for creating voice clips for Archos devices to putting the library in the system's search path (Windows: put libmp3lame.dll in the search path or the same folder RockboxUtility.exe is located in. Linux: install the library using your systems package manager) and configuration. This creates a notable encoding speedup on Windows (around factor 6 on my test setup) and a small speedup on Linux (around factor 1.2). The implemenatation currently has the following limitations: - Only enabled on Windows and Linux. On OS X installing the correct dylib is a bit nontrivial, so using the old command line based method is still in use for now. - The encoder parameters are currently hardcoded to use the same values the build system uses. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@31634 a1c6a512-1295-4272-9138-f99709370657
Diffstat (limited to 'rbutil/rbutilqt/base/encoderlame.cpp')
-rw-r--r--rbutil/rbutilqt/base/encoderlame.cpp280
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
32EncoderLame::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
57void 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
71void EncoderLame::saveSettings()
72{
73 // no user settings right now.
74}
75
76bool EncoderLame::start()
77{
78 if(!m_symbolsResolved) {
79 return false;
80 }
81 // try to get config from settings
82 return true;
83}
84
85bool 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 */
276bool EncoderLame::configOk()
277{
278 return (lib->isLoaded() && m_symbolsResolved);
279}
280