diff --git a/modules/juce_audio_formats/codecs/juce_LAMEEncoderAudioFormat.cpp b/modules/juce_audio_formats/codecs/juce_LAMEEncoderAudioFormat.cpp new file mode 100644 index 0000000000..0689c5b59b --- /dev/null +++ b/modules/juce_audio_formats/codecs/juce_LAMEEncoderAudioFormat.cpp @@ -0,0 +1,220 @@ +/* + ============================================================================== + + This file is part of the JUCE library - "Jules' Utility Class Extensions" + Copyright 2004-11 by Raw Material Software Ltd. + + ------------------------------------------------------------------------------ + + JUCE can be redistributed and/or modified under the terms of the GNU General + Public License (Version 2), as published by the Free Software Foundation. + A copy of the license is included in the JUCE distribution, or can be found + online at www.gnu.org/licenses. + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.rawmaterialsoftware.com/juce for more information. + + ============================================================================== +*/ + +#if JUCE_USE_LAME_AUDIO_FORMAT + +class LAMEEncoderAudioFormat::Writer : public AudioFormatWriter +{ +public: + Writer (OutputStream* destStream, const String& formatName, + const File& lameApp, int vbr, int cbr, + double sampleRate, unsigned int numberOfChannels, + unsigned int bitsPerSample, const StringPairArray& metadata) + : AudioFormatWriter (destStream, formatName, sampleRate, + numberOfChannels, bitsPerSample), + vbrLevel (vbr), cbrBitrate (cbr), + tempWav (".wav") + { + WavAudioFormat wavFormat; + + if (FileOutputStream* out = tempWav.getFile().createOutputStream()) + { + writer = wavFormat.createWriterFor (out, sampleRate, numChannels, + bitsPerSample, metadata, 0); + + args.add (lameApp.getFullPathName()); + + args.add ("--quiet"); + + if (cbrBitrate == 0) + { + args.add ("-vbr-new"); + args.add ("-V"); + args.add (String (vbrLevel)); + } + else + { + args.add ("--cbr"); + args.add ("-b"); + args.add (String (cbrBitrate)); + } + + addMetadataArg (metadata, "id3title", "--tt"); + addMetadataArg (metadata, "id3artist", "--ta"); + addMetadataArg (metadata, "id3album", "--tl"); + addMetadataArg (metadata, "id3comment", "--tc"); + addMetadataArg (metadata, "id3date", "--ty"); + addMetadataArg (metadata, "id3genre", "--tg"); + addMetadataArg (metadata, "id3trackNumber", "--tn"); + } + } + + void addMetadataArg (const StringPairArray& metadata, const char* key, const char* lameFlag) + { + const String value (metadata.getValue (key, String::empty)); + + if (value.isNotEmpty()) + { + args.add (lameFlag); + args.add (value); + } + } + + ~Writer() + { + if (writer != nullptr) + { + writer = nullptr; + + if (! convertToMP3()) + convertToMP3(); // try again + } + } + + bool write (const int** samplesToWrite, int numSamples) + { + return writer != nullptr && writer->write (samplesToWrite, numSamples); + } + +private: + int vbrLevel, cbrBitrate; + TemporaryFile tempWav; + ScopedPointer writer; + StringArray args; + + bool convertToMP3() const + { + TemporaryFile tempMP3 (".mp3"); + + StringArray args2 (args); + args2.add (tempWav.getFile().getFullPathName()); + args2.add (tempMP3.getFile().getFullPathName()); + + ChildProcess cp; + + DBG (args2.joinIntoString(" ")); + + if (cp.start (args2)) + { + String childOutput (cp.readAllProcessOutput()); + DBG (childOutput); + + if (tempMP3.getFile().getSize() > 0) + { + FileInputStream fis (tempMP3.getFile()); + + if (fis.openedOk() && output->writeFromInputStream (fis, -1) > 0) + { + output->flush(); + return true; + } + } + } + + return false; + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Writer) +}; + +//============================================================================== +static const char* const lameFormatName = "MP3 file"; +static const char* const lameExtensions[] = { ".mp3", nullptr }; + +LAMEEncoderAudioFormat::LAMEEncoderAudioFormat (const File& lameApplication) + : AudioFormat (TRANS (lameFormatName), StringArray (lameExtensions)), + lameApp (lameApplication) +{ +} + +LAMEEncoderAudioFormat::~LAMEEncoderAudioFormat() +{ +} + +bool LAMEEncoderAudioFormat::canHandleFile (const File&) +{ + return false; +} + +Array LAMEEncoderAudioFormat::getPossibleSampleRates() +{ + const int rates[] = { 32000, 44100, 48000, 0 }; + return Array (rates); +} + +Array LAMEEncoderAudioFormat::getPossibleBitDepths() +{ + const int depths[] = { 16, 0 }; + return Array (depths); +} + +bool LAMEEncoderAudioFormat::canDoStereo() { return true; } +bool LAMEEncoderAudioFormat::canDoMono() { return true; } +bool LAMEEncoderAudioFormat::isCompressed() { return true; } + +StringArray LAMEEncoderAudioFormat::getQualityOptions() +{ + const char* vbrOptions[] = { "VBR quality 0 (best)", "VBR quality 1", "VBR quality 2", "VBR quality 3", + "VBR quality 4 (normal)", "VBR quality 5", "VBR quality 6", "VBR quality 7", "VBR quality 8", + "VBR quality 9 (smallest)", + nullptr }; + + StringArray opts (vbrOptions); + + const int cbrRates[] = { 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320 }; + + for (int i = 0; i < numElementsInArray (cbrRates); ++i) + opts.add (String (cbrRates[i]) + " Kb/s CBR"); + + return opts; +} + +AudioFormatReader* LAMEEncoderAudioFormat::createReaderFor (InputStream*, const bool) +{ + return nullptr; +} + +AudioFormatWriter* LAMEEncoderAudioFormat::createWriterFor (OutputStream* streamToWriteTo, + double sampleRateToUse, + unsigned int numberOfChannels, + int bitsPerSample, + const StringPairArray& metadataValues, + int qualityOptionIndex) +{ + int vbr = 4; + int cbr = 0; + + const String qual (getQualityOptions() [qualityOptionIndex]); + + if (qual.contains ("VBR")) + vbr = qual.retainCharacters ("0123456789").getIntValue(); + else + cbr = qual.getIntValue(); + + return new Writer (streamToWriteTo, getFormatName(), lameApp, vbr, cbr, + sampleRateToUse, numberOfChannels, bitsPerSample, metadataValues); +} + +#endif diff --git a/modules/juce_audio_formats/codecs/juce_LAMEEncoderAudioFormat.h b/modules/juce_audio_formats/codecs/juce_LAMEEncoderAudioFormat.h new file mode 100644 index 0000000000..3382ffaae8 --- /dev/null +++ b/modules/juce_audio_formats/codecs/juce_LAMEEncoderAudioFormat.h @@ -0,0 +1,72 @@ +/* + ============================================================================== + + This file is part of the JUCE library - "Jules' Utility Class Extensions" + Copyright 2004-11 by Raw Material Software Ltd. + + ------------------------------------------------------------------------------ + + JUCE can be redistributed and/or modified under the terms of the GNU General + Public License (Version 2), as published by the Free Software Foundation. + A copy of the license is included in the JUCE distribution, or can be found + online at www.gnu.org/licenses. + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.rawmaterialsoftware.com/juce for more information. + + ============================================================================== +*/ + +#if JUCE_USE_LAME_AUDIO_FORMAT || defined (DOXYGEN) + +//============================================================================== +/** + An AudioFormat class which can use an installed version of the LAME mp3 + encoder to encode a file. + + This format can't read mp3s, it just writes them. Internally, the + AudioFormatWriter object that is returned writes the incoming audio data + to a temporary WAV file, and then when the writer is deleted, it invokes + the LAME executable to convert the data to an MP3, whose data is then + piped into the original OutputStream that was used when first creating + the writer. + + @see AudioFormat +*/ +class JUCE_API LAMEEncoderAudioFormat : public AudioFormat +{ +public: + /** Creates a LAMEEncoderAudioFormat that expects to find a working LAME + executable at the location given. + */ + LAMEEncoderAudioFormat (const File& lameApplicationToUse); + ~LAMEEncoderAudioFormat(); + + bool canHandleFile (const File&); + Array getPossibleSampleRates(); + Array getPossibleBitDepths(); + bool canDoStereo(); + bool canDoMono(); + bool isCompressed(); + StringArray getQualityOptions(); + + AudioFormatReader* createReaderFor (InputStream*, bool deleteStreamIfOpeningFails); + + AudioFormatWriter* createWriterFor (OutputStream*, double sampleRateToUse, + unsigned int numberOfChannels, int bitsPerSample, + const StringPairArray& metadataValues, int qualityOptionIndex); + +private: + File lameApp; + class Writer; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LAMEEncoderAudioFormat) +}; + +#endif diff --git a/modules/juce_audio_formats/codecs/juce_MP3AudioFormat.cpp b/modules/juce_audio_formats/codecs/juce_MP3AudioFormat.cpp index 479c3330c7..5a5b3c411b 100644 --- a/modules/juce_audio_formats/codecs/juce_MP3AudioFormat.cpp +++ b/modules/juce_audio_formats/codecs/juce_MP3AudioFormat.cpp @@ -3003,7 +3003,7 @@ public: { if (decodedEnd <= decodedStart && ! readNextBlock()) { - for (int i = 2; --i >= 0;) + for (int i = numDestChannels; --i >= 0;) if (destSamples[i] != nullptr) zeromem (destSamples[i] + startOffsetInDestBuffer, sizeof (float) * numSamples); @@ -3014,7 +3014,7 @@ public: float* const* const dst = reinterpret_cast (destSamples); memcpy (dst[0] + startOffsetInDestBuffer, decoded0 + decodedStart, sizeof (float) * numToCopy); - if (dst[1] != nullptr) + if (numDestChannels > 1 && dst[1] != nullptr) memcpy (dst[1] + startOffsetInDestBuffer, (numChannels < 2 ? decoded0 : decoded1) + decodedStart, sizeof (float) * numToCopy); startOffsetInDestBuffer += numToCopy; diff --git a/modules/juce_audio_formats/juce_audio_formats.cpp b/modules/juce_audio_formats/juce_audio_formats.cpp index ed543703e5..5206be96e3 100644 --- a/modules/juce_audio_formats/juce_audio_formats.cpp +++ b/modules/juce_audio_formats/juce_audio_formats.cpp @@ -112,6 +112,7 @@ namespace juce #include "codecs/juce_OggVorbisAudioFormat.cpp" #include "codecs/juce_QuickTimeAudioFormat.cpp" #include "codecs/juce_WavAudioFormat.cpp" +#include "codecs/juce_LAMEEncoderAudioFormat.cpp" #if JUCE_WINDOWS && JUCE_USE_WINDOWS_MEDIA_FORMAT #include "codecs/juce_WindowsMediaAudioFormat.cpp" diff --git a/modules/juce_audio_formats/juce_audio_formats.h b/modules/juce_audio_formats/juce_audio_formats.h index e591be2a62..24ceddc540 100644 --- a/modules/juce_audio_formats/juce_audio_formats.h +++ b/modules/juce_audio_formats/juce_audio_formats.h @@ -63,6 +63,13 @@ #define JUCE_USE_MP3AUDIOFORMAT 0 #endif +/** Config: JUCE_USE_LAME_AUDIO_FORMAT + Enables the LameEncoderAudioFormat class. +*/ +#ifndef JUCE_USE_LAME_AUDIO_FORMAT + #define JUCE_USE_LAME_AUDIO_FORMAT 0 +#endif + /** Config: JUCE_USE_WINDOWS_MEDIA_FORMAT Enables the Windows Media SDK codecs. */ @@ -101,6 +108,7 @@ namespace juce #include "codecs/juce_AiffAudioFormat.h" #include "codecs/juce_CoreAudioFormat.h" #include "codecs/juce_FlacAudioFormat.h" +#include "codecs/juce_LAMEEncoderAudioFormat.h" #include "codecs/juce_MP3AudioFormat.h" #include "codecs/juce_OggVorbisAudioFormat.h" #include "codecs/juce_QuickTimeAudioFormat.h"