aboutsummaryrefslogtreecommitdiff
path: root/Src/external_dependencies/openmpt-trunk/mptrack/StreamEncoderMP3.cpp
diff options
context:
space:
mode:
authorJef <jef@targetspot.com>2024-09-24 08:54:57 -0400
committerJef <jef@targetspot.com>2024-09-24 08:54:57 -0400
commit20d28e80a5c861a9d5f449ea911ab75b4f37ad0d (patch)
tree12f17f78986871dd2cfb0a56e5e93b545c1ae0d0 /Src/external_dependencies/openmpt-trunk/mptrack/StreamEncoderMP3.cpp
parent537bcbc86291b32fc04ae4133ce4d7cac8ebe9a7 (diff)
downloadwinamp-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.cpp741
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