diff options
author | Jef <jef@targetspot.com> | 2024-09-24 08:54:57 -0400 |
---|---|---|
committer | Jef <jef@targetspot.com> | 2024-09-24 08:54:57 -0400 |
commit | 20d28e80a5c861a9d5f449ea911ab75b4f37ad0d (patch) | |
tree | 12f17f78986871dd2cfb0a56e5e93b545c1ae0d0 /Src/external_dependencies/openmpt-trunk/mptrack/StreamEncoderMP3.cpp | |
parent | 537bcbc86291b32fc04ae4133ce4d7cac8ebe9a7 (diff) | |
download | winamp-20d28e80a5c861a9d5f449ea911ab75b4f37ad0d.tar.gz |
Initial community commit
Diffstat (limited to 'Src/external_dependencies/openmpt-trunk/mptrack/StreamEncoderMP3.cpp')
-rw-r--r-- | Src/external_dependencies/openmpt-trunk/mptrack/StreamEncoderMP3.cpp | 741 |
1 files changed, 741 insertions, 0 deletions
diff --git a/Src/external_dependencies/openmpt-trunk/mptrack/StreamEncoderMP3.cpp b/Src/external_dependencies/openmpt-trunk/mptrack/StreamEncoderMP3.cpp new file mode 100644 index 00000000..2b4788b5 --- /dev/null +++ b/Src/external_dependencies/openmpt-trunk/mptrack/StreamEncoderMP3.cpp @@ -0,0 +1,741 @@ +/* + * StreamEncoder.cpp + * ----------------- + * Purpose: Exporting streamed music files. + * Notes : none + * Authors: Joern Heusipp + * OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + +#include "stdafx.h" + +#include "StreamEncoder.h" +#include "StreamEncoderMP3.h" + +#include "Mptrack.h" + +#include "../soundlib/Sndfile.h" + +#include "../common/misc_util.h" +#include "../common/mptStringBuffer.h" + +#ifdef MPT_WITH_LAME +#if defined(MPT_BUILD_MSVC) +#include <lame.h> +#else +#include <lame/lame.h> +#endif +#endif // MPT_WITH_LAME + + + +OPENMPT_NAMESPACE_BEGIN + + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// ID3v2.4 Tags + +struct ID3v2Header +{ + uint8 signature[3]; + uint8 version[2]; + uint8be flags; + uint32be size; +}; + +MPT_BINARY_STRUCT(ID3v2Header, 10) + +struct ID3v2Frame +{ + char frameid[4]; + uint32be size; + uint16be flags; +}; + +MPT_BINARY_STRUCT(ID3v2Frame, 10) + + +// charset... choose text ending accordingly. +// $00 = ISO-8859-1. Terminated with $00. +// $01 = UTF-16 with BOM. Terminated with $00 00. +// $02 = UTF-16BE without BOM. Terminated with $00 00. +// $03 = UTF-8. Terminated with $00. +#define ID3v2_CHARSET '\3' +#define ID3v2_TEXTENDING '\0' + +struct ReplayGain +{ + enum GainTag + { + TagSkip, + TagReserve, + TagWrite + }; + GainTag Tag; + float TrackPeak; + bool TrackPeakValid; + float TrackGaindB; + bool TrackGaindBValid; + ReplayGain() + : Tag(TagSkip) + , TrackPeak(0.0f) + , TrackPeakValid(false) + , TrackGaindB(0.0f) + , TrackGaindBValid(false) + { + return; + } +}; + +class ID3V2Tagger +{ +private: + Encoder::StreamSettings settings; +public: + // Write Tags + void WriteID3v2Tags(std::ostream &s, const FileTags &tags, ReplayGain replayGain = ReplayGain()); + + ID3V2Tagger(const Encoder::StreamSettings &settings_); + +private: + // Convert Integer to Synchsafe Integer (see ID3v2.4 specs) + uint32 intToSynchsafe(uint32 in); + // Return maximum value that fits into a syncsafe int + uint32 GetMaxSynchsafeInt() const; + // Write a frame + void WriteID3v2Frame(const char cFrameID[4], std::string sFramecontent, std::ostream &s); + // Return an upper bound for the size of all replay gain frames + uint32 GetMaxReplayGainFramesSizes(); + uint32 GetMaxReplayGainTxxxTrackGainFrameSize(); + uint32 GetMaxReplayGainTxxxTrackPeakFrameSize(); + // Write out all ReplayGain frames + void WriteID3v2ReplayGainFrames(ReplayGain replaygain, std::ostream &s); + // Size of our tag + uint32 totalID3v2Size; +}; + +/////////////////////////////////////////////////// +// CFileTagging - helper class for writing tags + +ID3V2Tagger::ID3V2Tagger(const Encoder::StreamSettings &settings_) + : settings(settings_) + , totalID3v2Size(0) +{ + return; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// ID3v2.4 Tags + +// Convert Integer to Synchsafe Integer (see ID3v2.4 specs) +// Basically, it's a BigEndian integer, but the MSB of all bytes is 0. +// Thus, a 32-bit integer turns into a 28-bit integer. +uint32 ID3V2Tagger::intToSynchsafe(uint32 in) +{ + uint32 out = 0, steps = 0; + do + { + out |= (in & 0x7F) << steps; + steps += 8; + } while(in >>= 7); + return out; +} + +// Return maximum value that fits into a syncsafe int +uint32 ID3V2Tagger::GetMaxSynchsafeInt() const +{ + return 0x0fffffffu; +} + +// Write Tags +void ID3V2Tagger::WriteID3v2Tags(std::ostream &s, const FileTags &tags, ReplayGain replayGain) +{ + if(!s) return; + + ID3v2Header tHeader; + std::streampos fOffset = s.tellp(); + uint32 paddingSize = 0; + + totalID3v2Size = 0; + + // Correct header will be written later (tag size missing) + memcpy(tHeader.signature, "ID3", 3); + tHeader.version[0] = 0x04; // Version 2.4.0 + tHeader.version[1] = 0x00; // Ditto + tHeader.flags = 0; // No flags + tHeader.size = 0; // will be filled later + s.write(reinterpret_cast<const char*>(&tHeader), sizeof(tHeader)); + totalID3v2Size += sizeof(tHeader); + + WriteID3v2Frame("TIT2", mpt::ToCharset(mpt::Charset::UTF8, tags.title), s); + WriteID3v2Frame("TPE1", mpt::ToCharset(mpt::Charset::UTF8, tags.artist), s); + WriteID3v2Frame("TCOM", mpt::ToCharset(mpt::Charset::UTF8, tags.artist), s); + WriteID3v2Frame("TALB", mpt::ToCharset(mpt::Charset::UTF8, tags.album), s); + WriteID3v2Frame("TCON", mpt::ToCharset(mpt::Charset::UTF8, tags.genre), s); + //WriteID3v2Frame("TYER", mpt::ToCharset(mpt::Charset::UTF8, tags.year), s); // Deprecated + WriteID3v2Frame("TDRC", mpt::ToCharset(mpt::Charset::UTF8, tags.year), s); + WriteID3v2Frame("TBPM", mpt::ToCharset(mpt::Charset::UTF8, tags.bpm), s); + WriteID3v2Frame("WXXX", mpt::ToCharset(mpt::Charset::UTF8, tags.url), s); + WriteID3v2Frame("TENC", mpt::ToCharset(mpt::Charset::UTF8, tags.encoder), s); + WriteID3v2Frame("COMM", mpt::ToCharset(mpt::Charset::UTF8, tags.comments), s); + if(replayGain.Tag == ReplayGain::TagReserve) + { + paddingSize += GetMaxReplayGainFramesSizes(); + } else if(replayGain.Tag == ReplayGain::TagWrite) + { + std::streampos replayGainBeg = s.tellp(); + WriteID3v2ReplayGainFrames(replayGain, s); + std::streampos replayGainEnd = s.tellp(); + paddingSize += GetMaxReplayGainFramesSizes() - static_cast<uint32>(replayGainEnd - replayGainBeg); + } + + // Write Padding + uint32 totalID3v2SizeWithoutPadding = totalID3v2Size; + paddingSize += settings.MP3ID3v2MinPadding; + totalID3v2Size += paddingSize; + if(settings.MP3ID3v2PaddingAlignHint > 0) + { + totalID3v2Size = mpt::align_up<uint32>(totalID3v2Size, settings.MP3ID3v2PaddingAlignHint); + paddingSize = totalID3v2Size - totalID3v2SizeWithoutPadding; + } + for(size_t i = 0; i < paddingSize; i++) + { + char c = 0; + s.write(&c, 1); + } + + // Write correct header (update tag size) + tHeader.size = intToSynchsafe(totalID3v2Size - sizeof(tHeader)); + s.seekp(fOffset); + s.write(reinterpret_cast<const char*>(&tHeader), sizeof(tHeader)); + s.seekp(totalID3v2Size - sizeof(tHeader), std::ios::cur); + +} + +uint32 ID3V2Tagger::GetMaxReplayGainTxxxTrackGainFrameSize() +{ + return mpt::saturate_cast<uint32>(sizeof(ID3v2Frame) + 1 + std::strlen("REPLAYGAIN_TRACK_GAIN") + 1 + std::strlen("-123.45 dB") + 1); // should be enough +} + +uint32 ID3V2Tagger::GetMaxReplayGainTxxxTrackPeakFrameSize() +{ + return mpt::saturate_cast<uint32>(sizeof(ID3v2Frame) + 1 + std::strlen("REPLAYGAIN_TRACK_PEAK") + 1 + std::strlen("2147483648.123456") + 1); // unrealistic worst case +} + +uint32 ID3V2Tagger::GetMaxReplayGainFramesSizes() +{ + uint32 size = 0; + if(settings.MP3ID3v2WriteReplayGainTXXX) + { + size += GetMaxReplayGainTxxxTrackGainFrameSize(); + size += GetMaxReplayGainTxxxTrackPeakFrameSize(); + } + return size; +} + +void ID3V2Tagger::WriteID3v2ReplayGainFrames(ReplayGain replayGain, std::ostream &s) +{ + + if(settings.MP3ID3v2WriteReplayGainTXXX && replayGain.TrackGaindBValid) + { + + std::string content; + + content += std::string(1, 0x00); // ISO-8859-1 + content += std::string("REPLAYGAIN_TRACK_GAIN"); + content += std::string(1, '\0'); + + int32 gainTimes100 = mpt::saturate_round<int32>(replayGain.TrackGaindB * 100.0f); + if(gainTimes100 < 0) + { + content += "-"; + gainTimes100 = std::abs(gainTimes100); + } + content += mpt::afmt::dec(gainTimes100 / 100); + content += "."; + content += mpt::afmt::dec0<2>(gainTimes100 % 100); + content += " "; + content += "dB"; + + content += std::string(1, '\0'); + + if(sizeof(ID3v2Frame) + content.size() <= GetMaxReplayGainTxxxTrackGainFrameSize()) + { + ID3v2Frame frame; + std::memset(&frame, 0, sizeof(ID3v2Frame)); + std::memcpy(&frame.frameid, "TXXX", 4); + frame.size = intToSynchsafe(static_cast<uint32>(content.size())); + frame.flags = 0x4000; // discard if audio data changed + s.write(reinterpret_cast<const char*>(&frame), sizeof(ID3v2Frame)); + s.write(content.data(), content.size()); + } + + } + + + if(settings.MP3ID3v2WriteReplayGainTXXX && replayGain.TrackPeakValid) + { + + std::string content; + + content += std::string(1, 0x00); // ISO-8859-1 + content += std::string("REPLAYGAIN_TRACK_PEAK"); + content += std::string(1, '\0'); + + int32 peakTimes1000000 = mpt::saturate_round<int32>(std::fabs(replayGain.TrackPeak) * 1000000.0f); + std::string number; + number += mpt::afmt::dec(peakTimes1000000 / 1000000); + number += "."; + number += mpt::afmt::dec0<6>(peakTimes1000000 % 1000000); + content += number; + + content += std::string(1, '\0'); + + if(sizeof(ID3v2Frame) + content.size() <= GetMaxReplayGainTxxxTrackPeakFrameSize()) + { + ID3v2Frame frame; + std::memset(&frame, 0, sizeof(ID3v2Frame)); + std::memcpy(&frame.frameid, "TXXX", 4); + frame.size = intToSynchsafe(static_cast<uint32>(content.size())); + frame.flags = 0x4000; // discard if audio data changed + s.write(reinterpret_cast<const char*>(&frame), sizeof(ID3v2Frame)); + s.write(content.data(), content.size()); + } + + } + +} + +// Write a ID3v2 frame +void ID3V2Tagger::WriteID3v2Frame(const char cFrameID[4], std::string sFramecontent, std::ostream &s) +{ + if(!cFrameID[0] || sFramecontent.empty() || !s) return; + + if(!memcmp(cFrameID, "COMM", 4)) + { + // English language for comments - no description following (hence the text ending nullchar(s)) + // For language IDs, see https://en.wikipedia.org/wiki/ISO-639-2 + sFramecontent = "eng" + (ID3v2_TEXTENDING + sFramecontent); + } + if(!memcmp(cFrameID, "WXXX", 4)) + { + // User-defined URL field (we have no description for the URL, so we leave it out) + sFramecontent = ID3v2_TEXTENDING + sFramecontent; + } + sFramecontent = ID3v2_CHARSET + sFramecontent; + sFramecontent += ID3v2_TEXTENDING; + + if(sFramecontent.size() <= GetMaxSynchsafeInt()) + { + ID3v2Frame tFrame; + std::memset(&tFrame, 0, sizeof(ID3v2Frame)); + std::memcpy(&tFrame.frameid, cFrameID, 4); // ID + tFrame.size = intToSynchsafe(static_cast<uint32>(sFramecontent.size())); // Text size + tFrame.flags = 0x0000; // No flags + s.write(reinterpret_cast<const char*>(&tFrame), sizeof(tFrame)); + s.write(sFramecontent.c_str(), sFramecontent.size()); + + totalID3v2Size += static_cast<uint32>((sizeof(tFrame) + sFramecontent.size())); + } +} + + + + +#ifdef MPT_WITH_LAME + + +using lame_t = lame_global_flags *; + + +static void GenreEnumCallback(int num, const char *name, void *cookie) +{ + MPT_UNREFERENCED_PARAMETER(num); + Encoder::Traits &traits = *reinterpret_cast<Encoder::Traits*>(cookie); + if(name) + { + traits.genres.push_back(mpt::ToUnicode(mpt::Charset::ISO8859_1, name)); + } +} + + +static Encoder::Traits BuildTraits(bool compatible) +{ + Encoder::Traits traits; + traits.fileExtension = P_("mp3"); + traits.fileShortDescription = (compatible ? U_("Compatible MP3") : U_("MP3")); + traits.encoderSettingsName = (compatible ? U_("MP3LameCompatible") : U_("MP3Lame")); + traits.fileDescription = (compatible ? U_("MPEG-1 Layer 3") : U_("MPEG-1/2 Layer 3")); + traits.canTags = true; + traits.genres.clear(); + id3tag_genre_list(&GenreEnumCallback, &traits); + traits.modesWithFixedGenres = (compatible ? Encoder::ModeCBR : Encoder::ModeInvalid); + traits.maxChannels = 2; + traits.samplerates = (compatible + ? mpt::make_vector(mpeg1layer3_samplerates) + : mpt::make_vector(layer3_samplerates) + ); + traits.modes = (compatible ? Encoder::ModeCBR : (Encoder::ModeABR | Encoder::ModeQuality)); + traits.bitrates = (compatible + ? mpt::make_vector(mpeg1layer3_bitrates) + : mpt::make_vector(layer3_bitrates) + ); + traits.defaultSamplerate = 44100; + traits.defaultChannels = 2; + traits.defaultMode = (compatible ? Encoder::ModeCBR : Encoder::ModeQuality); + traits.defaultBitrate = 256; + traits.defaultQuality = 0.8f; + return traits; +} + + +class MP3LameStreamWriter : public StreamWriterBase +{ +private: + bool compatible; + Encoder::Settings settings; + Encoder::Mode Mode; + bool gfp_inited; + lame_t gfp; + enum ID3Type + { + ID3None, + ID3v1, + ID3v2Lame, + ID3v2OpenMPT, + }; + ID3Type id3type; + std::streamoff id3v2Size; + FileTags Tags; +public: + MP3LameStreamWriter(std::ostream &stream, bool compatible, const Encoder::Settings &settings_, const FileTags &tags) + : StreamWriterBase(stream) + , compatible(compatible) + , settings(settings_) + { + Mode = Encoder::ModeInvalid; + gfp_inited = false; + gfp = lame_t(); + id3type = ID3v2Lame; + id3v2Size = 0; + + if(!gfp) + { + gfp = lame_init(); + } + + uint32 samplerate = settings.Samplerate; + uint16 channels = settings.Channels; + if(settings.Tags) + { + if(compatible) + { + id3type = ID3v1; + } else if(settings.Details.MP3LameID3v2UseLame) + { + id3type = ID3v2Lame; + } else + { + id3type = ID3v2OpenMPT; + } + } else + { + id3type = ID3None; + } + id3v2Size = 0; + + lame_set_in_samplerate(gfp, samplerate); + lame_set_num_channels(gfp, channels); + + int lameQuality = settings.Details.MP3LameQuality; + lame_set_quality(gfp, lameQuality); + + if(settings.Mode == Encoder::ModeCBR) + { + + if(compatible) + { + if(settings.Bitrate >= 32) + { + // For maximum compatibility, + // force samplerate to a samplerate supported by MPEG1 streams. + if(samplerate <= 32000) + { + samplerate = 32000; + } else if(samplerate >= 48000) + { + samplerate = 48000; + } else + { + samplerate = 44100; + } + lame_set_out_samplerate(gfp, samplerate); + } else + { + // A very low bitrate was chosen, + // force samplerate to lowest possible for MPEG2. + // Disable unofficial MPEG2.5 however. + lame_set_out_samplerate(gfp, 16000); + } + } + + lame_set_brate(gfp, settings.Bitrate); + lame_set_VBR(gfp, vbr_off); + + if(compatible) + { + lame_set_bWriteVbrTag(gfp, 0); + lame_set_strict_ISO(gfp, 1); + lame_set_disable_reservoir(gfp, 1); + } else + { + lame_set_bWriteVbrTag(gfp, 1); + } + + } else if(settings.Mode == Encoder::ModeABR) + { + + lame_set_brate(gfp, settings.Bitrate); + lame_set_VBR(gfp, vbr_abr); + + lame_set_bWriteVbrTag(gfp, 1); + + } else + { + + float lame_quality = 10.0f - (settings.Quality * 10.0f); + Limit(lame_quality, 0.0f, 9.999f); + lame_set_VBR_quality(gfp, lame_quality); + lame_set_VBR(gfp, vbr_default); + + lame_set_bWriteVbrTag(gfp, 1); + + } + + lame_set_decode_on_the_fly(gfp, settings.Details.MP3LameCalculatePeakSample ? 1 : 0); // see LAME docs for why + lame_set_findReplayGain(gfp, settings.Details.MP3LameCalculateReplayGain ? 1 : 0); + + switch(id3type) + { + case ID3None: + lame_set_write_id3tag_automatic(gfp, 0); + break; + case ID3v1: + id3tag_init(gfp); + id3tag_v1_only(gfp); + break; + case ID3v2Lame: + id3tag_init(gfp); + id3tag_add_v2(gfp); + id3tag_v2_only(gfp); + id3tag_set_pad(gfp, settings.Details.MP3ID3v2MinPadding); + break; + case ID3v2OpenMPT: + lame_set_write_id3tag_automatic(gfp, 0); + break; + } + + Mode = settings.Mode; + + if(settings.Tags) + { + if(id3type == ID3v2Lame || id3type == ID3v1) + { + // Lame API expects Latin1, which is sad, but we cannot change that. + if(!tags.title.empty()) id3tag_set_title( gfp, mpt::ToCharset(mpt::Charset::ISO8859_1, tags.title ).c_str()); + if(!tags.artist.empty()) id3tag_set_artist( gfp, mpt::ToCharset(mpt::Charset::ISO8859_1, tags.artist ).c_str()); + if(!tags.album.empty()) id3tag_set_album( gfp, mpt::ToCharset(mpt::Charset::ISO8859_1, tags.album ).c_str()); + if(!tags.year.empty()) id3tag_set_year( gfp, mpt::ToCharset(mpt::Charset::ISO8859_1, tags.year ).c_str()); + if(!tags.comments.empty()) id3tag_set_comment(gfp, mpt::ToCharset(mpt::Charset::ISO8859_1, tags.comments).c_str()); + if(!tags.trackno.empty()) id3tag_set_track( gfp, mpt::ToCharset(mpt::Charset::ISO8859_1, tags.trackno ).c_str()); + if(!tags.genre.empty()) id3tag_set_genre( gfp, mpt::ToCharset(mpt::Charset::ISO8859_1, tags.genre ).c_str()); + } else if(id3type == ID3v2OpenMPT) + { + Tags = tags; + std::streampos id3beg = f.tellp(); + ID3V2Tagger tagger(settings.Details); + ReplayGain replayGain; + if(settings.Details.MP3LameCalculatePeakSample || settings.Details.MP3LameCalculateReplayGain) + { + replayGain.Tag = ReplayGain::TagReserve; + } + tagger.WriteID3v2Tags(f, tags, replayGain); + std::streampos id3end = f.tellp(); + id3v2Size = id3end - id3beg; + } + } + + } + void WriteInterleaved(size_t count, const float *interleaved) override + { + if(!gfp_inited) + { + lame_init_params(gfp); + gfp_inited = true; + } + const int count_max = 0xffff; + while(count > 0) + { + int count_chunk = std::clamp(mpt::saturate_cast<int>(count), int(0), count_max); + buf.resize(count_chunk + (count_chunk+3)/4 + 7200); + int result = 0; + if(lame_get_num_channels(gfp) == 1) + { + // lame always assumes stereo input with interleaved interface, so use non-interleaved for mono + result = lame_encode_buffer_ieee_float(gfp, interleaved, nullptr, count_chunk, mpt::byte_cast<unsigned char*>(buf.data()), mpt::saturate_cast<int>(buf.size())); + } else + { + result = lame_encode_buffer_interleaved_ieee_float(gfp, interleaved, count_chunk, mpt::byte_cast<unsigned char*>(buf.data()), mpt::saturate_cast<int>(buf.size())); + } + buf.resize((result >= 0) ? result : 0); + if(result == -2) + { + throw std::bad_alloc(); + } + WriteBuffer(); + count -= static_cast<size_t>(count_chunk); + } + } + void WriteFinalize() override + { + if(!gfp_inited) + { + lame_init_params(gfp); + gfp_inited = true; + } + buf.resize(7200); + buf.resize(lame_encode_flush(gfp, mpt::byte_cast<unsigned char*>(buf.data()), mpt::saturate_cast<int>(buf.size()))); + WriteBuffer(); + ReplayGain replayGain; + if(settings.Details.MP3LameCalculatePeakSample) + { + replayGain.TrackPeak = std::fabs(lame_get_PeakSample(gfp)) / 32768.0f; + replayGain.TrackPeakValid = true; + } + if(settings.Details.MP3LameCalculateReplayGain) + { + replayGain.TrackGaindB = lame_get_RadioGain(gfp) / 10.0f; + replayGain.TrackGaindBValid = true; + } + if(id3type == ID3v2OpenMPT && (settings.Details.MP3LameCalculatePeakSample || settings.Details.MP3LameCalculateReplayGain)) + { // update ID3v2 tag with replay gain information + replayGain.Tag = ReplayGain::TagWrite; + std::streampos endPos = f.tellp(); + f.seekp(fStart); + std::string tagdata(static_cast<std::size_t>(id3v2Size), '\0'); + f.write(tagdata.data(), id3v2Size); // clear out the old tag + f.seekp(fStart); + ID3V2Tagger tagger(settings.Details); + tagger.WriteID3v2Tags(f, Tags, replayGain); + f.seekp(endPos); + } + if(id3type == ID3v2Lame) + { + id3v2Size = lame_get_id3v2_tag(gfp, nullptr, 0); + } else if(id3type == ID3v2OpenMPT) + { + // id3v2Size already set + } + if(!compatible) + { + std::streampos endPos = f.tellp(); + f.seekp(fStart + id3v2Size); + buf.resize(lame_get_lametag_frame(gfp, nullptr, 0)); + buf.resize(lame_get_lametag_frame(gfp, (unsigned char*)buf.data(), buf.size())); + WriteBuffer(); + f.seekp(endPos); + } + } + virtual ~MP3LameStreamWriter() + { + if(!gfp) + { + return; + } + lame_close(gfp); + gfp = lame_t(); + gfp_inited = false; + } +}; + +#endif // MPT_WITH_LAME + + + +MP3Encoder::MP3Encoder(MP3EncoderType type) + : m_Type(type) +{ +#ifdef MPT_WITH_LAME + if(type == MP3EncoderLame) + { + m_Type = MP3EncoderLame; + SetTraits(BuildTraits(false)); + return; + } + if(type == MP3EncoderLameCompatible) + { + m_Type = MP3EncoderLameCompatible; + SetTraits(BuildTraits(true)); + return; + } +#endif // MPT_WITH_LAME +} + + +bool MP3Encoder::IsAvailable() const +{ + return false +#ifdef MPT_WITH_LAME + || (m_Type == MP3EncoderLame) + || (m_Type == MP3EncoderLameCompatible) +#endif // MPT_WITH_LAME + ; +} + + +std::unique_ptr<IAudioStreamEncoder> MP3Encoder::ConstructStreamEncoder(std::ostream &file, const Encoder::Settings &settings, const FileTags &tags) const +{ + std::unique_ptr<IAudioStreamEncoder> result = nullptr; + if(false) + { + // nothing +#ifdef MPT_WITH_LAME + } else if(m_Type == MP3EncoderLame || m_Type == MP3EncoderLameCompatible) + { + result = std::make_unique<MP3LameStreamWriter>(file, (m_Type == MP3EncoderLameCompatible), settings, tags); +#endif // MPT_WITH_LAME + } + return result; +} + + +mpt::ustring MP3Encoder::DescribeQuality(float quality) const +{ +#ifdef MPT_WITH_LAME + if(m_Type == MP3EncoderLame) + { + static constexpr int q_table[11] = { 240, 220, 190, 170, 160, 130, 120, 100, 80, 70, 50 }; // http://wiki.hydrogenaud.io/index.php?title=LAME + int q = mpt::saturate_round<int>((1.0f - quality) * 10.0f); + if(q < 0) q = 0; + if(q >= 10) + { + return MPT_UFORMAT("VBR -V{} (~{} kbit)")(U_("9.999"), q_table[q]); + } else + { + return MPT_UFORMAT("VBR -V{} (~{} kbit)")(q, q_table[q]); + } + } +#endif // MPT_WITH_LAME + return EncoderFactoryBase::DescribeQuality(quality); +} + +mpt::ustring MP3Encoder::DescribeBitrateABR(int bitrate) const +{ + return EncoderFactoryBase::DescribeBitrateABR(bitrate); +} + + + +OPENMPT_NAMESPACE_END |