From c876d3bbefe0dc00c27ca0c12d29da5874946962 Mon Sep 17 00:00:00 2001 From: Dominik Riebeling Date: Wed, 15 Dec 2021 21:04:28 +0100 Subject: 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 --- utils/rbutilqt/base/encoderlame.cpp | 312 ++++++++++++++++++++++++++++++++++++ 1 file changed, 312 insertions(+) create mode 100644 utils/rbutilqt/base/encoderlame.cpp (limited to 'utils/rbutilqt/base/encoderlame.cpp') 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 @@ +/*************************************************************************** + * __________ __ ___. + * 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 +#include "encoderlame.h" +#include "rbsettings.h" +#include "lame/lame.h" +#include "Logger.h" + +/** Resolve a symbol from loaded library. + */ +#define SYMBOLRESOLVE(symbol, type) \ + do { m_##symbol = (type)lib.resolve(#symbol); \ + if(!m_##symbol) return; \ + LOG_INFO() << "Resolved symbol " #symbol; } \ + while(0) + +EncoderLame::EncoderLame(QObject *parent) : EncoderBase(parent), + lib("libmp3lame", this), m_symbolsResolved(false) +{ + lib.load(); + if (!lib.isLoaded()) { + LOG_WARNING() << "Loading mp3lame lib failed:" << lib.errorString(); + return; + } + + 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*)); + + m_encoderVolume = RbSettings::subValue("lame", RbSettings::EncoderVolume).toDouble(); + m_encoderQuality = RbSettings::subValue("lame", RbSettings::EncoderQuality).toDouble(); + m_symbolsResolved = true; +} + +void EncoderLame::generateSettings() +{ + // no settings for now. + // show lame version. + if(m_symbolsResolved) { + double quality = RbSettings::subValue("lame", + RbSettings::EncoderQuality).toDouble(); + // default quality is 0.999. + if(quality < 0) { + quality = 0.99; + } + insertSetting(LAMEVERSION, new EncTtsSetting(this, EncTtsSetting::eREADONLYSTRING, + tr("LAME"), QString(m_get_lame_short_version()))); + insertSetting(VOLUME, new EncTtsSetting(this, EncTtsSetting::eDOUBLE, + tr("Volume"), + RbSettings::subValue("lame", RbSettings::EncoderVolume).toDouble(), + 0.0, 2.0)); + insertSetting(QUALITY, new EncTtsSetting(this, EncTtsSetting::eDOUBLE, + tr("Quality"), quality, 0.0, 1.0)); + } + else { + insertSetting(LAMEVERSION, new EncTtsSetting(this, EncTtsSetting::eREADONLYSTRING, + tr("LAME"), tr("Could not find libmp3lame!"))); + } +} + +void EncoderLame::saveSettings() +{ + if(m_symbolsResolved) { + RbSettings::setSubValue("lame", RbSettings::EncoderVolume, + getSetting(VOLUME)->current().toDouble()); + RbSettings::setSubValue("lame", RbSettings::EncoderQuality, + getSetting(QUALITY)->current().toDouble()); + m_encoderVolume = + RbSettings::subValue("lame", RbSettings::EncoderVolume).toDouble(); + m_encoderQuality = + RbSettings::subValue("lame", RbSettings::EncoderQuality).toDouble(); + } +} + +bool EncoderLame::start() +{ + if(!m_symbolsResolved) { + return false; + } + // try to get config from settings + return true; +} + +bool EncoderLame::encode(QString input,QString output) +{ + LOG_INFO() << "Encoding" << QDir::cleanPath(input); + if(!m_symbolsResolved) { + LOG_ERROR() << "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 + // scale input volume + m_lame_set_scale(gfp, m_encoderVolume); + m_lame_set_mode(gfp, MONO); // mono output mode + m_lame_set_VBR(gfp, vbr_default); // enable default VBR mode + // VBR quality + m_lame_set_VBR_quality(gfp, m_encoderQuality); + m_lame_set_VBR_max_bitrate_kbps(gfp, 64); // maximum bitrate 64kbps + m_lame_set_bWriteVbrTag(gfp, 0); // disable LAME tag. + + if(!fin.open(QIODevice::ReadOnly)) { + LOG_ERROR() << "Could not open input file" << input; + return false; + } + + // read RIFF header + fin.read((char*)header, 12); + if(memcmp("RIFF", header, 4) != 0) { + LOG_ERROR() << "RIFF header not found!" + << header[0] << header[1] << header[2] << header[3]; + fin.close(); + return false; + } + if(memcmp("WAVE", &header[8], 4) != 0) { + LOG_ERROR() << "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) { + LOG_ERROR() << "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. + LOG_WARNING() << "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) { + LOG_ERROR() << "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) { + LOG_ERROR() << "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) { + LOG_ERROR() << "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 { + LOG_ERROR() << "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) { + LOG_ERROR() << "Error during encoding:" << ret; + } + if(fout.write((char*)mp3buf, ret) != (unsigned int)ret) { + LOG_ERROR() << "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) { + LOG_ERROR() << "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 m_symbolsResolved; +} + -- cgit v1.2.3