123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741 |
- /*
- * 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
|