Fix mismatching C++ new[] / delete calls
[maemo-rb.git] / rbutil / rbutilqt / base / encoderlame.cpp
blob9550eb5681458c6cc28e8866a3fc2326f767d1fd
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
9 * Copyright (C) 2012 Dominik Riebeling
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.
14 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
15 * KIND, either express or implied.
17 ****************************************************************************/
19 #include <QtCore>
20 #include "encoderlame.h"
21 #include "rbsettings.h"
22 #include "lame/lame.h"
24 /** Resolve a symbol from loaded library.
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)
32 EncoderLame::EncoderLame(QObject *parent) : EncoderBase(parent)
34 m_symbolsResolved = false;
35 lib = new QLibrary("libmp3lame", this);
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*));
53 qDebug() << "[EncoderLame] libmp3lame loaded:" << lib->isLoaded();
54 m_symbolsResolved = true;
57 void EncoderLame::generateSettings()
59 // no settings for now.
60 // show lame version.
61 if(m_symbolsResolved) {
62 double quality = RbSettings::subValue("lame",
63 RbSettings::EncoderQuality).toDouble();
64 // default quality is 0.999.
65 if(quality < 0) {
66 quality = 0.99;
68 insertSetting(LAMEVERSION, new EncTtsSetting(this, EncTtsSetting::eREADONLYSTRING,
69 tr("LAME"), QString(m_get_lame_short_version())));
70 insertSetting(VOLUME, new EncTtsSetting(this, EncTtsSetting::eDOUBLE,
71 tr("Volume"),
72 RbSettings::subValue("lame", RbSettings::EncoderVolume).toDouble(),
73 0.0, 1.0));
74 insertSetting(QUALITY, new EncTtsSetting(this, EncTtsSetting::eDOUBLE,
75 tr("Quality"), quality, 0.0, 1.0));
77 else {
78 insertSetting(LAMEVERSION, new EncTtsSetting(this, EncTtsSetting::eREADONLYSTRING,
79 tr("LAME"), tr("Could not find libmp3lame!")));
83 void EncoderLame::saveSettings()
85 if(m_symbolsResolved) {
86 RbSettings::setSubValue("lame", RbSettings::EncoderVolume,
87 getSetting(VOLUME)->current().toDouble());
88 RbSettings::setSubValue("lame", RbSettings::EncoderQuality,
89 getSetting(QUALITY)->current().toDouble());
93 bool EncoderLame::start()
95 if(!m_symbolsResolved) {
96 return false;
98 // try to get config from settings
99 return true;
102 bool EncoderLame::encode(QString input,QString output)
104 qDebug() << "[EncoderLame] Encoding" << QDir::cleanPath(input);
105 if(!m_symbolsResolved) {
106 qDebug() << "[EncoderLame] Symbols not successfully resolved, cannot run!";
107 return false;
110 QFile fin(input);
111 QFile fout(output);
112 // initialize encoder
113 lame_global_flags *gfp;
114 unsigned char header[12];
115 unsigned char chunkheader[8];
116 unsigned int datalength = 0;
117 unsigned int channels = 0;
118 unsigned int samplerate = 0;
119 unsigned int samplesize = 0;
120 int num_samples = 0;
121 int ret;
122 unsigned char* mp3buf;
123 int mp3buflen;
124 short int* wavbuf;
125 int wavbuflen;
128 gfp = m_lame_init();
129 m_lame_set_out_samplerate(gfp, 12000); // resample to 12kHz
130 // scale input volume
131 m_lame_set_scale(gfp,
132 RbSettings::subValue("lame", RbSettings::EncoderVolume).toDouble());
133 m_lame_set_mode(gfp, MONO); // mono output mode
134 m_lame_set_VBR(gfp, vbr_default); // enable default VBR mode
135 // VBR quality
136 m_lame_set_VBR_quality(gfp,
137 RbSettings::subValue("lame", RbSettings::EncoderQuality).toDouble());
138 m_lame_set_VBR_max_bitrate_kbps(gfp, 64); // maximum bitrate 64kbps
139 m_lame_set_bWriteVbrTag(gfp, 0); // disable LAME tag.
141 if(!fin.open(QIODevice::ReadOnly)) {
142 qDebug() << "[EncoderLame] Could not open input file" << input;
143 return false;
146 // read RIFF header
147 fin.read((char*)header, 12);
148 if(memcmp("RIFF", header, 4) != 0) {
149 qDebug() << "[EncoderLame] RIFF header not found!"
150 << header[0] << header[1] << header[2] << header[3];
151 fin.close();
152 return false;
154 if(memcmp("WAVE", &header[8], 4) != 0) {
155 qDebug() << "[EncoderLame] WAVE FOURCC not found!"
156 << header[8] << header[9] << header[10] << header[11];
157 fin.close();
158 return false;
161 // search for fmt chunk
162 do {
163 // read fmt
164 fin.read((char*)chunkheader, 8);
165 int chunkdatalen = chunkheader[4] | chunkheader[5]<<8
166 | chunkheader[6]<<16 | chunkheader[7]<<24;
167 if(memcmp("fmt ", chunkheader, 4) == 0) {
168 // fmt found, read rest of chunk.
169 // NOTE: This code ignores the format tag value.
170 // Ideally this should be checked as well. However, rbspeex doesn't
171 // check the format tag either when reading wave files, so if
172 // problems arise we should notice pretty soon. Furthermore, the
173 // input format used should be known. In case some TTS uses a
174 // different wave encoding some time this needs to get adjusted.
175 if(chunkdatalen < 16) {
176 qDebug() << "fmt chunk too small!";
178 else {
179 unsigned char *buf = new unsigned char[chunkdatalen];
180 fin.read((char*)buf, chunkdatalen);
181 channels = buf[2] | buf[3]<<8;
182 samplerate = buf[4] | buf[5]<<8 | buf[6]<<16 | buf[7]<<24;
183 samplesize = buf[14] | buf[15]<<8;
184 delete[] buf;
187 // read data
188 else if(memcmp("data", chunkheader, 4) == 0) {
189 datalength = chunkdatalen;
190 break;
192 else {
193 // unknown chunk, just skip its data.
194 qDebug() << "[EncoderLame] unknown chunk, skipping."
195 << chunkheader[0] << chunkheader[1]
196 << chunkheader[2] << chunkheader[3];
197 fin.seek(fin.pos() + chunkdatalen);
199 } while(!fin.atEnd());
201 // check format
202 if(channels == 0 || samplerate == 0 || samplesize == 0 || datalength == 0) {
203 qDebug() << "[EncoderLame] invalid format. Channels:" << channels
204 << "Samplerate:" << samplerate << "Samplesize:" << samplesize
205 << "Data chunk length:" << datalength;
206 fin.close();
207 return false;
209 num_samples = (datalength / channels / (samplesize/8));
211 // set input format values
212 m_lame_set_in_samplerate(gfp, samplerate);
213 m_lame_set_num_channels(gfp, channels);
215 // initialize encoder.
216 ret = m_lame_init_params(gfp);
217 if(ret != 0) {
218 qDebug() << "[EncoderLame] lame_init_params() failed with" << ret;
219 fin.close();
220 return false;
223 // we're dealing with rather small files here (100kB-ish), so don't care
224 // about the possible output size and simply allocate the same number of
225 // bytes the input file has. This wastes space but should be ok.
226 // Put an upper limit of 8MiB.
227 if(datalength > 8*1024*1024) {
228 qDebug() << "[EncoderLame] Input file too large:" << datalength;
229 fin.close();
230 return false;
232 mp3buflen = datalength;
233 wavbuflen = datalength;
234 mp3buf = new unsigned char[mp3buflen];
235 wavbuf = new short int[wavbuflen];
236 #if defined(Q_OS_MACX)
237 // handle byte order -- the host might not be LE.
238 if(samplesize == 8) {
239 // no need to convert.
240 fin.read((char*)wavbuf, wavbuflen);
242 else if(samplesize == 16) {
243 // read LE 16bit words. Since the input format is either mono or
244 // interleaved there's no need to care for that.
245 unsigned int pos = 0;
246 char word[2];
247 while(pos < datalength) {
248 fin.read(word, 2);
249 wavbuf[pos++] = (word[0]&0xff) | ((word[1]<<8)&0xff00);
252 else {
253 qDebug() << "[EncoderLame] Unknown samplesize:" << samplesize;
254 fin.close();
255 delete[] mp3buf;
256 delete[] wavbuf;
257 return false;
259 #else
260 // all systems but OS X are considered LE.
261 fin.read((char*)wavbuf, wavbuflen);
262 #endif
263 fin.close();
264 // encode data.
265 fout.open(QIODevice::ReadWrite);
266 ret = m_lame_encode_buffer(gfp, wavbuf, wavbuf, num_samples, mp3buf, mp3buflen);
267 if(ret < 0) {
268 qDebug() << "[EncoderLame] Error during encoding:" << ret;
270 if(fout.write((char*)mp3buf, ret) != (unsigned int)ret) {
271 qDebug() << "[EncoderLame] Writing mp3 data failed!" << ret;
272 fout.close();
273 delete[] mp3buf;
274 delete[] wavbuf;
275 return false;
277 // flush remaining data
278 ret = m_lame_encode_flush(gfp, mp3buf, mp3buflen);
279 if(fout.write((char*)mp3buf, ret) != (unsigned int)ret) {
280 qDebug() << "[EncoderLame] Writing final mp3 data failed!";
281 fout.close();
282 delete[] mp3buf;
283 delete[] wavbuf;
284 return false;
286 // shut down encoder and clean up.
287 m_lame_close(gfp);
288 fout.close();
289 delete[] mp3buf;
290 delete[] wavbuf;
292 return true;
295 /** Check if the current configuration is usable.
296 * Since we're loading a library dynamically in the constructor test if that
297 * succeeded. Otherwise the "configuration" is not usable, even though the
298 * problem is not necessarily related to configuration values set by the user.
300 bool EncoderLame::configOk()
302 return (lib->isLoaded() && m_symbolsResolved);