summaryrefslogtreecommitdiffstats
path: root/rbutil/rbutilqt/base
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
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')
-rw-r--r--rbutil/rbutilqt/base/encoderbase.cpp6
-rw-r--r--rbutil/rbutilqt/base/encoderlame.cpp280
-rw-r--r--rbutil/rbutilqt/base/encoderlame.h71
-rw-r--r--rbutil/rbutilqt/base/voicefile.cpp2
4 files changed, 358 insertions, 1 deletions
diff --git a/rbutil/rbutilqt/base/encoderbase.cpp b/rbutil/rbutilqt/base/encoderbase.cpp
index e2668febd1..05ccae3011 100644
--- a/rbutil/rbutilqt/base/encoderbase.cpp
+++ b/rbutil/rbutilqt/base/encoderbase.cpp
@@ -20,6 +20,7 @@
#include "utils.h"
#include "rbsettings.h"
#include "encoderrbspeex.h"
+#include "encoderlame.h"
#include "encoderexe.h"
/*********************************************************************
@@ -55,7 +56,12 @@ EncoderBase* EncoderBase::getEncoder(QObject* parent,QString encoder)
EncoderBase* enc;
if(encoder == "lame")
{
+#if defined(Q_OS_MACX)
+ /* currently not on OS X */
enc = new EncoderExe(encoder,parent);
+#else
+ enc = new EncoderLame(parent);
+#endif
return enc;
}
else // rbspeex is default
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 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ *
+ * Copyright (C) 2012 Dominik Riebeling
+ *
+ * All files in this archive are subject to the GNU General Public License.
+ * See the file COPYING in the source tree root for full license agreement.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+
+#include <QtCore>
+#include "encoderlame.h"
+#include "rbsettings.h"
+#include "lame/lame.h"
+
+/** Resolve a symbol from loaded library.
+ */
+#define SYMBOLRESOLVE(symbol, type) \
+ do { m_##symbol = (type)lib->resolve(#symbol); \
+ if(!m_##symbol) return; \
+ qDebug() << "[EncoderLame] Resolved symbol " #symbol; } \
+ while(0)
+
+EncoderLame::EncoderLame(QObject *parent) : EncoderBase(parent)
+{
+ m_symbolsResolved = false;
+ lib = new QLibrary("libmp3lame", this);
+
+ SYMBOLRESOLVE(get_lame_short_version, const char* (*)());
+ SYMBOLRESOLVE(lame_set_out_samplerate, int (*)(lame_global_flags*, int));
+ SYMBOLRESOLVE(lame_set_in_samplerate, int (*)(lame_global_flags*, int));
+ SYMBOLRESOLVE(lame_set_num_channels, int (*)(lame_global_flags*, int));
+ SYMBOLRESOLVE(lame_set_scale, int (*)(lame_global_flags*, float));
+ SYMBOLRESOLVE(lame_set_mode, int (*)(lame_global_flags*, MPEG_mode));
+ SYMBOLRESOLVE(lame_set_VBR, int (*)(lame_global_flags*, vbr_mode));
+ SYMBOLRESOLVE(lame_set_VBR_quality, int (*)(lame_global_flags*, float));
+ SYMBOLRESOLVE(lame_set_VBR_max_bitrate_kbps, int (*)(lame_global_flags*, int));
+ SYMBOLRESOLVE(lame_set_bWriteVbrTag, int (*)(lame_global_flags*, int));
+ SYMBOLRESOLVE(lame_init, lame_global_flags* (*)());
+ SYMBOLRESOLVE(lame_init_params, int (*)(lame_global_flags*));
+ SYMBOLRESOLVE(lame_encode_buffer, int (*)(lame_global_flags*, short int*, short int*, int, unsigned char*, int));
+ SYMBOLRESOLVE(lame_encode_flush, int (*)(lame_global_flags*, unsigned char*, int));
+ SYMBOLRESOLVE(lame_close, int (*)(lame_global_flags*));
+
+ qDebug() << "[EncoderLame] libmp3lame loaded:" << lib->isLoaded();
+ m_symbolsResolved = true;
+}
+
+void EncoderLame::generateSettings()
+{
+ // no settings for now.
+ // show lame version.
+ if(m_symbolsResolved) {
+ insertSetting(eVOLUME, new EncTtsSetting(this, EncTtsSetting::eREADONLYSTRING,
+ tr("LAME"), QString(m_get_lame_short_version())));
+ }
+ else {
+ insertSetting(eVOLUME, new EncTtsSetting(this, EncTtsSetting::eREADONLYSTRING,
+ tr("LAME"), tr("Could not find libmp3lame!")));
+ }
+}
+
+void EncoderLame::saveSettings()
+{
+ // no user settings right now.
+}
+
+bool EncoderLame::start()
+{
+ if(!m_symbolsResolved) {
+ return false;
+ }
+ // try to get config from settings
+ return true;
+}
+
+bool EncoderLame::encode(QString input,QString output)
+{
+ qDebug() << "[EncoderLame] Encoding" << input;
+ if(!m_symbolsResolved) {
+ qDebug() << "[EncoderLame] Symbols not successfully resolved, cannot run!";
+ return false;
+ }
+
+ QFile fin(input);
+ QFile fout(output);
+ // initialize encoder
+ lame_global_flags *gfp;
+ unsigned char header[12];
+ unsigned char chunkheader[8];
+ unsigned int datalength = 0;
+ unsigned int channels = 0;
+ unsigned int samplerate = 0;
+ unsigned int samplesize = 0;
+ int num_samples = 0;
+ int ret;
+ unsigned char* mp3buf;
+ int mp3buflen;
+ short int* wavbuf;
+ int wavbuflen;
+
+
+ gfp = m_lame_init();
+ m_lame_set_out_samplerate(gfp, 12000); // resample to 12kHz
+ m_lame_set_scale(gfp, 1.0); // scale input volume
+ m_lame_set_mode(gfp, MONO); // mono output mode
+ m_lame_set_VBR(gfp, vbr_default); // enable default VBR mode
+ m_lame_set_VBR_quality(gfp, 9.999); // VBR quality
+ m_lame_set_VBR_max_bitrate_kbps(gfp, 64); // maximum bitrate 64kbps
+ m_lame_set_bWriteVbrTag(gfp, 0); // disable LAME tag.
+
+ fin.open(QIODevice::ReadOnly);
+
+ // read RIFF header
+ fin.read((char*)header, 12);
+ if(memcmp("RIFF", header, 4) != 0) {
+ qDebug() << "[EncoderLame] RIFF header not found!"
+ << header[0] << header[1] << header[2] << header[3];
+ fin.close();
+ return false;
+ }
+ if(memcmp("WAVE", &header[8], 4) != 0) {
+ qDebug() << "[EncoderLame] WAVE FOURCC not found!"
+ << header[8] << header[9] << header[10] << header[11];
+ fin.close();
+ return false;
+ }
+
+ // search for fmt chunk
+ do {
+ // read fmt
+ fin.read((char*)chunkheader, 8);
+ int chunkdatalen = chunkheader[4] | chunkheader[5]<<8
+ | chunkheader[6]<<16 | chunkheader[7]<<24;
+ if(memcmp("fmt ", chunkheader, 4) == 0) {
+ // fmt found, read rest of chunk.
+ // NOTE: This code ignores the format tag value.
+ // Ideally this should be checked as well. However, rbspeex doesn't
+ // check the format tag either when reading wave files, so if
+ // problems arise we should notice pretty soon. Furthermore, the
+ // input format used should be known. In case some TTS uses a
+ // different wave encoding some time this needs to get adjusted.
+ if(chunkdatalen < 16) {
+ qDebug() << "fmt chunk too small!";
+ }
+ else {
+ unsigned char *buf = new unsigned char[chunkdatalen];
+ fin.read((char*)buf, chunkdatalen);
+ channels = buf[2] | buf[3]<<8;
+ samplerate = buf[4] | buf[5]<<8 | buf[6]<<16 | buf[7]<<24;
+ samplesize = buf[14] | buf[15]<<8;
+ delete buf;
+ }
+ }
+ // read data
+ else if(memcmp("data", chunkheader, 4) == 0) {
+ datalength = chunkdatalen;
+ break;
+ }
+ else {
+ // unknown chunk, just skip its data.
+ qDebug() << "[EncoderLame] unknown chunk, skipping."
+ << chunkheader[0] << chunkheader[1]
+ << chunkheader[2] << chunkheader[3];
+ fin.seek(fin.pos() + chunkdatalen);
+ }
+ } while(!fin.atEnd());
+
+ // check format
+ if(channels == 0 || samplerate == 0 || samplesize == 0 || datalength == 0) {
+ qDebug() << "[EncoderLame] invalid format. Channels:" << channels
+ << "Samplerate:" << samplerate << "Samplesize:" << samplesize
+ << "Data chunk length:" << datalength;
+ fin.close();
+ return false;
+ }
+ num_samples = (datalength / channels / (samplesize/8));
+
+ // set input format values
+ m_lame_set_in_samplerate(gfp, samplerate);
+ m_lame_set_num_channels(gfp, channels);
+
+ // initialize encoder.
+ ret = m_lame_init_params(gfp);
+ if(ret != 0) {
+ qDebug() << "[EncoderLame] lame_init_params() failed with" << ret;
+ fin.close();
+ return false;
+ }
+
+ // we're dealing with rather small files here (100kB-ish), so don't care
+ // about the possible output size and simply allocate the same number of
+ // bytes the input file has. This wastes space but should be ok.
+ // Put an upper limit of 8MiB.
+ if(datalength > 8*1024*1024) {
+ qDebug() << "[EncoderLame] Input file too large:" << datalength;
+ fin.close();
+ return false;
+ }
+ mp3buflen = datalength;
+ wavbuflen = datalength;
+ mp3buf = new unsigned char[mp3buflen];
+ wavbuf = new short int[wavbuflen];
+#if defined(Q_OS_MACX)
+ // handle byte order -- the host might not be LE.
+ if(samplesize == 8) {
+ // no need to convert.
+ fin.read((char*)wavbuf, wavbuflen);
+ }
+ else if(samplesize == 16) {
+ // read LE 16bit words. Since the input format is either mono or
+ // interleaved there's no need to care for that.
+ unsigned int pos = 0;
+ char word[2];
+ while(pos < datalength) {
+ fin.read(word, 2);
+ wavbuf[pos++] = (word[0]&0xff) | ((word[1]<<8)&0xff00);
+ }
+ }
+ else {
+ qDebug() << "[EncoderLame] Unknown samplesize:" << samplesize;
+ fin.close();
+ delete mp3buf;
+ delete wavbuf;
+ return false;
+ }
+#else
+ // all systems but OS X are considered LE.
+ fin.read((char*)wavbuf, wavbuflen);
+#endif
+ fin.close();
+ // encode data.
+ fout.open(QIODevice::ReadWrite);
+ ret = m_lame_encode_buffer(gfp, wavbuf, wavbuf, num_samples, mp3buf, mp3buflen);
+ if(ret < 0) {
+ qDebug() << "[EncoderLame] Error during encoding:" << ret;
+ }
+ if(fout.write((char*)mp3buf, ret) != (unsigned int)ret) {
+ qDebug() << "[EncoderLame] Writing mp3 data failed!" << ret;
+ fout.close();
+ delete mp3buf;
+ delete wavbuf;
+ return false;
+ }
+ // flush remaining data
+ ret = m_lame_encode_flush(gfp, mp3buf, mp3buflen);
+ if(fout.write((char*)mp3buf, ret) != (unsigned int)ret) {
+ qDebug() << "[EncoderLame] Writing final mp3 data failed!";
+ fout.close();
+ delete mp3buf;
+ delete wavbuf;
+ return false;
+ }
+ // shut down encoder and clean up.
+ m_lame_close(gfp);
+ fout.close();
+ delete mp3buf;
+ delete wavbuf;
+
+ return true;
+}
+
+/** Check if the current configuration is usable.
+ * Since we're loading a library dynamically in the constructor test if that
+ * succeeded. Otherwise the "configuration" is not usable, even though the
+ * problem is not necessarily related to configuration values set by the user.
+ */
+bool EncoderLame::configOk()
+{
+ return (lib->isLoaded() && m_symbolsResolved);
+}
+
diff --git a/rbutil/rbutilqt/base/encoderlame.h b/rbutil/rbutilqt/base/encoderlame.h
new file mode 100644
index 0000000000..9f87188d9b
--- /dev/null
+++ b/rbutil/rbutilqt/base/encoderlame.h
@@ -0,0 +1,71 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ *
+ * Copyright (C) 2012 Dominik Riebeling
+ *
+ * All files in this archive are subject to the GNU General Public License.
+ * See the file COPYING in the source tree root for full license agreement.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+
+#ifndef ENCODERLAME_H
+#define ENCODERLAME_H
+
+#include <QtCore>
+#include "encoderbase.h"
+#include "lame/lame.h"
+
+class EncoderLame : public EncoderBase
+{
+ enum ESettings
+ {
+ eVOLUME,
+ eQUALITY,
+ eCOMPLEXITY,
+ eNARROWBAND
+ };
+
+ Q_OBJECT
+ public:
+ EncoderLame(QObject *parent = NULL);
+ bool encode(QString input,QString output);
+ bool start();
+ bool stop() {return true;}
+
+ // for settings view
+ bool configOk();
+ void generateSettings();
+ void saveSettings();
+
+ private:
+ QLibrary *lib;
+ const char*(*m_get_lame_short_version)(void);
+ int (*m_lame_set_out_samplerate)(lame_global_flags*, int);
+ int (*m_lame_set_in_samplerate)(lame_global_flags*, int);
+ int (*m_lame_set_num_channels)(lame_global_flags*, int);
+ int (*m_lame_set_scale)(lame_global_flags*, float);
+ int (*m_lame_set_mode)(lame_global_flags*, MPEG_mode);
+ int (*m_lame_set_VBR)(lame_global_flags*, vbr_mode);
+ int (*m_lame_set_VBR_quality)(lame_global_flags*, float);
+ int (*m_lame_set_VBR_max_bitrate_kbps)(lame_global_flags*, int);
+ int (*m_lame_set_bWriteVbrTag)(lame_global_flags*, int);
+ lame_global_flags*(*m_lame_init)(void);
+ int (*m_lame_init_params)(lame_global_flags*);
+ int (*m_lame_encode_buffer)(lame_global_flags*, short int[], short
+ int[], int, unsigned char*, int);
+ int (*m_lame_encode_flush)(lame_global_flags*, unsigned char*, int);
+ int (*m_lame_close)(lame_global_flags*);
+
+ bool m_symbolsResolved;
+};
+
+#endif
+
diff --git a/rbutil/rbutilqt/base/voicefile.cpp b/rbutil/rbutilqt/base/voicefile.cpp
index ba23bfea89..ebabf9e8f6 100644
--- a/rbutil/rbutilqt/base/voicefile.cpp
+++ b/rbutil/rbutilqt/base/voicefile.cpp
@@ -254,7 +254,7 @@ void VoiceFileCreator::cleanup()
QCoreApplication::processEvents();
}
emit logItem(tr("Finished"),LOGINFO);
-
+
return;
}