1
0

StreamEncoderMP3.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741
  1. /*
  2. * StreamEncoder.cpp
  3. * -----------------
  4. * Purpose: Exporting streamed music files.
  5. * Notes : none
  6. * Authors: Joern Heusipp
  7. * OpenMPT Devs
  8. * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
  9. */
  10. #include "stdafx.h"
  11. #include "StreamEncoder.h"
  12. #include "StreamEncoderMP3.h"
  13. #include "Mptrack.h"
  14. #include "../soundlib/Sndfile.h"
  15. #include "../common/misc_util.h"
  16. #include "../common/mptStringBuffer.h"
  17. #ifdef MPT_WITH_LAME
  18. #if defined(MPT_BUILD_MSVC)
  19. #include <lame.h>
  20. #else
  21. #include <lame/lame.h>
  22. #endif
  23. #endif // MPT_WITH_LAME
  24. OPENMPT_NAMESPACE_BEGIN
  25. ///////////////////////////////////////////////////////////////////////////////////////////////////
  26. // ID3v2.4 Tags
  27. struct ID3v2Header
  28. {
  29. uint8 signature[3];
  30. uint8 version[2];
  31. uint8be flags;
  32. uint32be size;
  33. };
  34. MPT_BINARY_STRUCT(ID3v2Header, 10)
  35. struct ID3v2Frame
  36. {
  37. char frameid[4];
  38. uint32be size;
  39. uint16be flags;
  40. };
  41. MPT_BINARY_STRUCT(ID3v2Frame, 10)
  42. // charset... choose text ending accordingly.
  43. // $00 = ISO-8859-1. Terminated with $00.
  44. // $01 = UTF-16 with BOM. Terminated with $00 00.
  45. // $02 = UTF-16BE without BOM. Terminated with $00 00.
  46. // $03 = UTF-8. Terminated with $00.
  47. #define ID3v2_CHARSET '\3'
  48. #define ID3v2_TEXTENDING '\0'
  49. struct ReplayGain
  50. {
  51. enum GainTag
  52. {
  53. TagSkip,
  54. TagReserve,
  55. TagWrite
  56. };
  57. GainTag Tag;
  58. float TrackPeak;
  59. bool TrackPeakValid;
  60. float TrackGaindB;
  61. bool TrackGaindBValid;
  62. ReplayGain()
  63. : Tag(TagSkip)
  64. , TrackPeak(0.0f)
  65. , TrackPeakValid(false)
  66. , TrackGaindB(0.0f)
  67. , TrackGaindBValid(false)
  68. {
  69. return;
  70. }
  71. };
  72. class ID3V2Tagger
  73. {
  74. private:
  75. Encoder::StreamSettings settings;
  76. public:
  77. // Write Tags
  78. void WriteID3v2Tags(std::ostream &s, const FileTags &tags, ReplayGain replayGain = ReplayGain());
  79. ID3V2Tagger(const Encoder::StreamSettings &settings_);
  80. private:
  81. // Convert Integer to Synchsafe Integer (see ID3v2.4 specs)
  82. uint32 intToSynchsafe(uint32 in);
  83. // Return maximum value that fits into a syncsafe int
  84. uint32 GetMaxSynchsafeInt() const;
  85. // Write a frame
  86. void WriteID3v2Frame(const char cFrameID[4], std::string sFramecontent, std::ostream &s);
  87. // Return an upper bound for the size of all replay gain frames
  88. uint32 GetMaxReplayGainFramesSizes();
  89. uint32 GetMaxReplayGainTxxxTrackGainFrameSize();
  90. uint32 GetMaxReplayGainTxxxTrackPeakFrameSize();
  91. // Write out all ReplayGain frames
  92. void WriteID3v2ReplayGainFrames(ReplayGain replaygain, std::ostream &s);
  93. // Size of our tag
  94. uint32 totalID3v2Size;
  95. };
  96. ///////////////////////////////////////////////////
  97. // CFileTagging - helper class for writing tags
  98. ID3V2Tagger::ID3V2Tagger(const Encoder::StreamSettings &settings_)
  99. : settings(settings_)
  100. , totalID3v2Size(0)
  101. {
  102. return;
  103. }
  104. ///////////////////////////////////////////////////////////////////////////////////////////////////
  105. // ID3v2.4 Tags
  106. // Convert Integer to Synchsafe Integer (see ID3v2.4 specs)
  107. // Basically, it's a BigEndian integer, but the MSB of all bytes is 0.
  108. // Thus, a 32-bit integer turns into a 28-bit integer.
  109. uint32 ID3V2Tagger::intToSynchsafe(uint32 in)
  110. {
  111. uint32 out = 0, steps = 0;
  112. do
  113. {
  114. out |= (in & 0x7F) << steps;
  115. steps += 8;
  116. } while(in >>= 7);
  117. return out;
  118. }
  119. // Return maximum value that fits into a syncsafe int
  120. uint32 ID3V2Tagger::GetMaxSynchsafeInt() const
  121. {
  122. return 0x0fffffffu;
  123. }
  124. // Write Tags
  125. void ID3V2Tagger::WriteID3v2Tags(std::ostream &s, const FileTags &tags, ReplayGain replayGain)
  126. {
  127. if(!s) return;
  128. ID3v2Header tHeader;
  129. std::streampos fOffset = s.tellp();
  130. uint32 paddingSize = 0;
  131. totalID3v2Size = 0;
  132. // Correct header will be written later (tag size missing)
  133. memcpy(tHeader.signature, "ID3", 3);
  134. tHeader.version[0] = 0x04; // Version 2.4.0
  135. tHeader.version[1] = 0x00; // Ditto
  136. tHeader.flags = 0; // No flags
  137. tHeader.size = 0; // will be filled later
  138. s.write(reinterpret_cast<const char*>(&tHeader), sizeof(tHeader));
  139. totalID3v2Size += sizeof(tHeader);
  140. WriteID3v2Frame("TIT2", mpt::ToCharset(mpt::Charset::UTF8, tags.title), s);
  141. WriteID3v2Frame("TPE1", mpt::ToCharset(mpt::Charset::UTF8, tags.artist), s);
  142. WriteID3v2Frame("TCOM", mpt::ToCharset(mpt::Charset::UTF8, tags.artist), s);
  143. WriteID3v2Frame("TALB", mpt::ToCharset(mpt::Charset::UTF8, tags.album), s);
  144. WriteID3v2Frame("TCON", mpt::ToCharset(mpt::Charset::UTF8, tags.genre), s);
  145. //WriteID3v2Frame("TYER", mpt::ToCharset(mpt::Charset::UTF8, tags.year), s); // Deprecated
  146. WriteID3v2Frame("TDRC", mpt::ToCharset(mpt::Charset::UTF8, tags.year), s);
  147. WriteID3v2Frame("TBPM", mpt::ToCharset(mpt::Charset::UTF8, tags.bpm), s);
  148. WriteID3v2Frame("WXXX", mpt::ToCharset(mpt::Charset::UTF8, tags.url), s);
  149. WriteID3v2Frame("TENC", mpt::ToCharset(mpt::Charset::UTF8, tags.encoder), s);
  150. WriteID3v2Frame("COMM", mpt::ToCharset(mpt::Charset::UTF8, tags.comments), s);
  151. if(replayGain.Tag == ReplayGain::TagReserve)
  152. {
  153. paddingSize += GetMaxReplayGainFramesSizes();
  154. } else if(replayGain.Tag == ReplayGain::TagWrite)
  155. {
  156. std::streampos replayGainBeg = s.tellp();
  157. WriteID3v2ReplayGainFrames(replayGain, s);
  158. std::streampos replayGainEnd = s.tellp();
  159. paddingSize += GetMaxReplayGainFramesSizes() - static_cast<uint32>(replayGainEnd - replayGainBeg);
  160. }
  161. // Write Padding
  162. uint32 totalID3v2SizeWithoutPadding = totalID3v2Size;
  163. paddingSize += settings.MP3ID3v2MinPadding;
  164. totalID3v2Size += paddingSize;
  165. if(settings.MP3ID3v2PaddingAlignHint > 0)
  166. {
  167. totalID3v2Size = mpt::align_up<uint32>(totalID3v2Size, settings.MP3ID3v2PaddingAlignHint);
  168. paddingSize = totalID3v2Size - totalID3v2SizeWithoutPadding;
  169. }
  170. for(size_t i = 0; i < paddingSize; i++)
  171. {
  172. char c = 0;
  173. s.write(&c, 1);
  174. }
  175. // Write correct header (update tag size)
  176. tHeader.size = intToSynchsafe(totalID3v2Size - sizeof(tHeader));
  177. s.seekp(fOffset);
  178. s.write(reinterpret_cast<const char*>(&tHeader), sizeof(tHeader));
  179. s.seekp(totalID3v2Size - sizeof(tHeader), std::ios::cur);
  180. }
  181. uint32 ID3V2Tagger::GetMaxReplayGainTxxxTrackGainFrameSize()
  182. {
  183. return mpt::saturate_cast<uint32>(sizeof(ID3v2Frame) + 1 + std::strlen("REPLAYGAIN_TRACK_GAIN") + 1 + std::strlen("-123.45 dB") + 1); // should be enough
  184. }
  185. uint32 ID3V2Tagger::GetMaxReplayGainTxxxTrackPeakFrameSize()
  186. {
  187. return mpt::saturate_cast<uint32>(sizeof(ID3v2Frame) + 1 + std::strlen("REPLAYGAIN_TRACK_PEAK") + 1 + std::strlen("2147483648.123456") + 1); // unrealistic worst case
  188. }
  189. uint32 ID3V2Tagger::GetMaxReplayGainFramesSizes()
  190. {
  191. uint32 size = 0;
  192. if(settings.MP3ID3v2WriteReplayGainTXXX)
  193. {
  194. size += GetMaxReplayGainTxxxTrackGainFrameSize();
  195. size += GetMaxReplayGainTxxxTrackPeakFrameSize();
  196. }
  197. return size;
  198. }
  199. void ID3V2Tagger::WriteID3v2ReplayGainFrames(ReplayGain replayGain, std::ostream &s)
  200. {
  201. if(settings.MP3ID3v2WriteReplayGainTXXX && replayGain.TrackGaindBValid)
  202. {
  203. std::string content;
  204. content += std::string(1, 0x00); // ISO-8859-1
  205. content += std::string("REPLAYGAIN_TRACK_GAIN");
  206. content += std::string(1, '\0');
  207. int32 gainTimes100 = mpt::saturate_round<int32>(replayGain.TrackGaindB * 100.0f);
  208. if(gainTimes100 < 0)
  209. {
  210. content += "-";
  211. gainTimes100 = std::abs(gainTimes100);
  212. }
  213. content += mpt::afmt::dec(gainTimes100 / 100);
  214. content += ".";
  215. content += mpt::afmt::dec0<2>(gainTimes100 % 100);
  216. content += " ";
  217. content += "dB";
  218. content += std::string(1, '\0');
  219. if(sizeof(ID3v2Frame) + content.size() <= GetMaxReplayGainTxxxTrackGainFrameSize())
  220. {
  221. ID3v2Frame frame;
  222. std::memset(&frame, 0, sizeof(ID3v2Frame));
  223. std::memcpy(&frame.frameid, "TXXX", 4);
  224. frame.size = intToSynchsafe(static_cast<uint32>(content.size()));
  225. frame.flags = 0x4000; // discard if audio data changed
  226. s.write(reinterpret_cast<const char*>(&frame), sizeof(ID3v2Frame));
  227. s.write(content.data(), content.size());
  228. }
  229. }
  230. if(settings.MP3ID3v2WriteReplayGainTXXX && replayGain.TrackPeakValid)
  231. {
  232. std::string content;
  233. content += std::string(1, 0x00); // ISO-8859-1
  234. content += std::string("REPLAYGAIN_TRACK_PEAK");
  235. content += std::string(1, '\0');
  236. int32 peakTimes1000000 = mpt::saturate_round<int32>(std::fabs(replayGain.TrackPeak) * 1000000.0f);
  237. std::string number;
  238. number += mpt::afmt::dec(peakTimes1000000 / 1000000);
  239. number += ".";
  240. number += mpt::afmt::dec0<6>(peakTimes1000000 % 1000000);
  241. content += number;
  242. content += std::string(1, '\0');
  243. if(sizeof(ID3v2Frame) + content.size() <= GetMaxReplayGainTxxxTrackPeakFrameSize())
  244. {
  245. ID3v2Frame frame;
  246. std::memset(&frame, 0, sizeof(ID3v2Frame));
  247. std::memcpy(&frame.frameid, "TXXX", 4);
  248. frame.size = intToSynchsafe(static_cast<uint32>(content.size()));
  249. frame.flags = 0x4000; // discard if audio data changed
  250. s.write(reinterpret_cast<const char*>(&frame), sizeof(ID3v2Frame));
  251. s.write(content.data(), content.size());
  252. }
  253. }
  254. }
  255. // Write a ID3v2 frame
  256. void ID3V2Tagger::WriteID3v2Frame(const char cFrameID[4], std::string sFramecontent, std::ostream &s)
  257. {
  258. if(!cFrameID[0] || sFramecontent.empty() || !s) return;
  259. if(!memcmp(cFrameID, "COMM", 4))
  260. {
  261. // English language for comments - no description following (hence the text ending nullchar(s))
  262. // For language IDs, see https://en.wikipedia.org/wiki/ISO-639-2
  263. sFramecontent = "eng" + (ID3v2_TEXTENDING + sFramecontent);
  264. }
  265. if(!memcmp(cFrameID, "WXXX", 4))
  266. {
  267. // User-defined URL field (we have no description for the URL, so we leave it out)
  268. sFramecontent = ID3v2_TEXTENDING + sFramecontent;
  269. }
  270. sFramecontent = ID3v2_CHARSET + sFramecontent;
  271. sFramecontent += ID3v2_TEXTENDING;
  272. if(sFramecontent.size() <= GetMaxSynchsafeInt())
  273. {
  274. ID3v2Frame tFrame;
  275. std::memset(&tFrame, 0, sizeof(ID3v2Frame));
  276. std::memcpy(&tFrame.frameid, cFrameID, 4); // ID
  277. tFrame.size = intToSynchsafe(static_cast<uint32>(sFramecontent.size())); // Text size
  278. tFrame.flags = 0x0000; // No flags
  279. s.write(reinterpret_cast<const char*>(&tFrame), sizeof(tFrame));
  280. s.write(sFramecontent.c_str(), sFramecontent.size());
  281. totalID3v2Size += static_cast<uint32>((sizeof(tFrame) + sFramecontent.size()));
  282. }
  283. }
  284. #ifdef MPT_WITH_LAME
  285. using lame_t = lame_global_flags *;
  286. static void GenreEnumCallback(int num, const char *name, void *cookie)
  287. {
  288. MPT_UNREFERENCED_PARAMETER(num);
  289. Encoder::Traits &traits = *reinterpret_cast<Encoder::Traits*>(cookie);
  290. if(name)
  291. {
  292. traits.genres.push_back(mpt::ToUnicode(mpt::Charset::ISO8859_1, name));
  293. }
  294. }
  295. static Encoder::Traits BuildTraits(bool compatible)
  296. {
  297. Encoder::Traits traits;
  298. traits.fileExtension = P_("mp3");
  299. traits.fileShortDescription = (compatible ? U_("Compatible MP3") : U_("MP3"));
  300. traits.encoderSettingsName = (compatible ? U_("MP3LameCompatible") : U_("MP3Lame"));
  301. traits.fileDescription = (compatible ? U_("MPEG-1 Layer 3") : U_("MPEG-1/2 Layer 3"));
  302. traits.canTags = true;
  303. traits.genres.clear();
  304. id3tag_genre_list(&GenreEnumCallback, &traits);
  305. traits.modesWithFixedGenres = (compatible ? Encoder::ModeCBR : Encoder::ModeInvalid);
  306. traits.maxChannels = 2;
  307. traits.samplerates = (compatible
  308. ? mpt::make_vector(mpeg1layer3_samplerates)
  309. : mpt::make_vector(layer3_samplerates)
  310. );
  311. traits.modes = (compatible ? Encoder::ModeCBR : (Encoder::ModeABR | Encoder::ModeQuality));
  312. traits.bitrates = (compatible
  313. ? mpt::make_vector(mpeg1layer3_bitrates)
  314. : mpt::make_vector(layer3_bitrates)
  315. );
  316. traits.defaultSamplerate = 44100;
  317. traits.defaultChannels = 2;
  318. traits.defaultMode = (compatible ? Encoder::ModeCBR : Encoder::ModeQuality);
  319. traits.defaultBitrate = 256;
  320. traits.defaultQuality = 0.8f;
  321. return traits;
  322. }
  323. class MP3LameStreamWriter : public StreamWriterBase
  324. {
  325. private:
  326. bool compatible;
  327. Encoder::Settings settings;
  328. Encoder::Mode Mode;
  329. bool gfp_inited;
  330. lame_t gfp;
  331. enum ID3Type
  332. {
  333. ID3None,
  334. ID3v1,
  335. ID3v2Lame,
  336. ID3v2OpenMPT,
  337. };
  338. ID3Type id3type;
  339. std::streamoff id3v2Size;
  340. FileTags Tags;
  341. public:
  342. MP3LameStreamWriter(std::ostream &stream, bool compatible, const Encoder::Settings &settings_, const FileTags &tags)
  343. : StreamWriterBase(stream)
  344. , compatible(compatible)
  345. , settings(settings_)
  346. {
  347. Mode = Encoder::ModeInvalid;
  348. gfp_inited = false;
  349. gfp = lame_t();
  350. id3type = ID3v2Lame;
  351. id3v2Size = 0;
  352. if(!gfp)
  353. {
  354. gfp = lame_init();
  355. }
  356. uint32 samplerate = settings.Samplerate;
  357. uint16 channels = settings.Channels;
  358. if(settings.Tags)
  359. {
  360. if(compatible)
  361. {
  362. id3type = ID3v1;
  363. } else if(settings.Details.MP3LameID3v2UseLame)
  364. {
  365. id3type = ID3v2Lame;
  366. } else
  367. {
  368. id3type = ID3v2OpenMPT;
  369. }
  370. } else
  371. {
  372. id3type = ID3None;
  373. }
  374. id3v2Size = 0;
  375. lame_set_in_samplerate(gfp, samplerate);
  376. lame_set_num_channels(gfp, channels);
  377. int lameQuality = settings.Details.MP3LameQuality;
  378. lame_set_quality(gfp, lameQuality);
  379. if(settings.Mode == Encoder::ModeCBR)
  380. {
  381. if(compatible)
  382. {
  383. if(settings.Bitrate >= 32)
  384. {
  385. // For maximum compatibility,
  386. // force samplerate to a samplerate supported by MPEG1 streams.
  387. if(samplerate <= 32000)
  388. {
  389. samplerate = 32000;
  390. } else if(samplerate >= 48000)
  391. {
  392. samplerate = 48000;
  393. } else
  394. {
  395. samplerate = 44100;
  396. }
  397. lame_set_out_samplerate(gfp, samplerate);
  398. } else
  399. {
  400. // A very low bitrate was chosen,
  401. // force samplerate to lowest possible for MPEG2.
  402. // Disable unofficial MPEG2.5 however.
  403. lame_set_out_samplerate(gfp, 16000);
  404. }
  405. }
  406. lame_set_brate(gfp, settings.Bitrate);
  407. lame_set_VBR(gfp, vbr_off);
  408. if(compatible)
  409. {
  410. lame_set_bWriteVbrTag(gfp, 0);
  411. lame_set_strict_ISO(gfp, 1);
  412. lame_set_disable_reservoir(gfp, 1);
  413. } else
  414. {
  415. lame_set_bWriteVbrTag(gfp, 1);
  416. }
  417. } else if(settings.Mode == Encoder::ModeABR)
  418. {
  419. lame_set_brate(gfp, settings.Bitrate);
  420. lame_set_VBR(gfp, vbr_abr);
  421. lame_set_bWriteVbrTag(gfp, 1);
  422. } else
  423. {
  424. float lame_quality = 10.0f - (settings.Quality * 10.0f);
  425. Limit(lame_quality, 0.0f, 9.999f);
  426. lame_set_VBR_quality(gfp, lame_quality);
  427. lame_set_VBR(gfp, vbr_default);
  428. lame_set_bWriteVbrTag(gfp, 1);
  429. }
  430. lame_set_decode_on_the_fly(gfp, settings.Details.MP3LameCalculatePeakSample ? 1 : 0); // see LAME docs for why
  431. lame_set_findReplayGain(gfp, settings.Details.MP3LameCalculateReplayGain ? 1 : 0);
  432. switch(id3type)
  433. {
  434. case ID3None:
  435. lame_set_write_id3tag_automatic(gfp, 0);
  436. break;
  437. case ID3v1:
  438. id3tag_init(gfp);
  439. id3tag_v1_only(gfp);
  440. break;
  441. case ID3v2Lame:
  442. id3tag_init(gfp);
  443. id3tag_add_v2(gfp);
  444. id3tag_v2_only(gfp);
  445. id3tag_set_pad(gfp, settings.Details.MP3ID3v2MinPadding);
  446. break;
  447. case ID3v2OpenMPT:
  448. lame_set_write_id3tag_automatic(gfp, 0);
  449. break;
  450. }
  451. Mode = settings.Mode;
  452. if(settings.Tags)
  453. {
  454. if(id3type == ID3v2Lame || id3type == ID3v1)
  455. {
  456. // Lame API expects Latin1, which is sad, but we cannot change that.
  457. if(!tags.title.empty()) id3tag_set_title( gfp, mpt::ToCharset(mpt::Charset::ISO8859_1, tags.title ).c_str());
  458. if(!tags.artist.empty()) id3tag_set_artist( gfp, mpt::ToCharset(mpt::Charset::ISO8859_1, tags.artist ).c_str());
  459. if(!tags.album.empty()) id3tag_set_album( gfp, mpt::ToCharset(mpt::Charset::ISO8859_1, tags.album ).c_str());
  460. if(!tags.year.empty()) id3tag_set_year( gfp, mpt::ToCharset(mpt::Charset::ISO8859_1, tags.year ).c_str());
  461. if(!tags.comments.empty()) id3tag_set_comment(gfp, mpt::ToCharset(mpt::Charset::ISO8859_1, tags.comments).c_str());
  462. if(!tags.trackno.empty()) id3tag_set_track( gfp, mpt::ToCharset(mpt::Charset::ISO8859_1, tags.trackno ).c_str());
  463. if(!tags.genre.empty()) id3tag_set_genre( gfp, mpt::ToCharset(mpt::Charset::ISO8859_1, tags.genre ).c_str());
  464. } else if(id3type == ID3v2OpenMPT)
  465. {
  466. Tags = tags;
  467. std::streampos id3beg = f.tellp();
  468. ID3V2Tagger tagger(settings.Details);
  469. ReplayGain replayGain;
  470. if(settings.Details.MP3LameCalculatePeakSample || settings.Details.MP3LameCalculateReplayGain)
  471. {
  472. replayGain.Tag = ReplayGain::TagReserve;
  473. }
  474. tagger.WriteID3v2Tags(f, tags, replayGain);
  475. std::streampos id3end = f.tellp();
  476. id3v2Size = id3end - id3beg;
  477. }
  478. }
  479. }
  480. void WriteInterleaved(size_t count, const float *interleaved) override
  481. {
  482. if(!gfp_inited)
  483. {
  484. lame_init_params(gfp);
  485. gfp_inited = true;
  486. }
  487. const int count_max = 0xffff;
  488. while(count > 0)
  489. {
  490. int count_chunk = std::clamp(mpt::saturate_cast<int>(count), int(0), count_max);
  491. buf.resize(count_chunk + (count_chunk+3)/4 + 7200);
  492. int result = 0;
  493. if(lame_get_num_channels(gfp) == 1)
  494. {
  495. // lame always assumes stereo input with interleaved interface, so use non-interleaved for mono
  496. result = lame_encode_buffer_ieee_float(gfp, interleaved, nullptr, count_chunk, mpt::byte_cast<unsigned char*>(buf.data()), mpt::saturate_cast<int>(buf.size()));
  497. } else
  498. {
  499. result = lame_encode_buffer_interleaved_ieee_float(gfp, interleaved, count_chunk, mpt::byte_cast<unsigned char*>(buf.data()), mpt::saturate_cast<int>(buf.size()));
  500. }
  501. buf.resize((result >= 0) ? result : 0);
  502. if(result == -2)
  503. {
  504. throw std::bad_alloc();
  505. }
  506. WriteBuffer();
  507. count -= static_cast<size_t>(count_chunk);
  508. }
  509. }
  510. void WriteFinalize() override
  511. {
  512. if(!gfp_inited)
  513. {
  514. lame_init_params(gfp);
  515. gfp_inited = true;
  516. }
  517. buf.resize(7200);
  518. buf.resize(lame_encode_flush(gfp, mpt::byte_cast<unsigned char*>(buf.data()), mpt::saturate_cast<int>(buf.size())));
  519. WriteBuffer();
  520. ReplayGain replayGain;
  521. if(settings.Details.MP3LameCalculatePeakSample)
  522. {
  523. replayGain.TrackPeak = std::fabs(lame_get_PeakSample(gfp)) / 32768.0f;
  524. replayGain.TrackPeakValid = true;
  525. }
  526. if(settings.Details.MP3LameCalculateReplayGain)
  527. {
  528. replayGain.TrackGaindB = lame_get_RadioGain(gfp) / 10.0f;
  529. replayGain.TrackGaindBValid = true;
  530. }
  531. if(id3type == ID3v2OpenMPT && (settings.Details.MP3LameCalculatePeakSample || settings.Details.MP3LameCalculateReplayGain))
  532. { // update ID3v2 tag with replay gain information
  533. replayGain.Tag = ReplayGain::TagWrite;
  534. std::streampos endPos = f.tellp();
  535. f.seekp(fStart);
  536. std::string tagdata(static_cast<std::size_t>(id3v2Size), '\0');
  537. f.write(tagdata.data(), id3v2Size); // clear out the old tag
  538. f.seekp(fStart);
  539. ID3V2Tagger tagger(settings.Details);
  540. tagger.WriteID3v2Tags(f, Tags, replayGain);
  541. f.seekp(endPos);
  542. }
  543. if(id3type == ID3v2Lame)
  544. {
  545. id3v2Size = lame_get_id3v2_tag(gfp, nullptr, 0);
  546. } else if(id3type == ID3v2OpenMPT)
  547. {
  548. // id3v2Size already set
  549. }
  550. if(!compatible)
  551. {
  552. std::streampos endPos = f.tellp();
  553. f.seekp(fStart + id3v2Size);
  554. buf.resize(lame_get_lametag_frame(gfp, nullptr, 0));
  555. buf.resize(lame_get_lametag_frame(gfp, (unsigned char*)buf.data(), buf.size()));
  556. WriteBuffer();
  557. f.seekp(endPos);
  558. }
  559. }
  560. virtual ~MP3LameStreamWriter()
  561. {
  562. if(!gfp)
  563. {
  564. return;
  565. }
  566. lame_close(gfp);
  567. gfp = lame_t();
  568. gfp_inited = false;
  569. }
  570. };
  571. #endif // MPT_WITH_LAME
  572. MP3Encoder::MP3Encoder(MP3EncoderType type)
  573. : m_Type(type)
  574. {
  575. #ifdef MPT_WITH_LAME
  576. if(type == MP3EncoderLame)
  577. {
  578. m_Type = MP3EncoderLame;
  579. SetTraits(BuildTraits(false));
  580. return;
  581. }
  582. if(type == MP3EncoderLameCompatible)
  583. {
  584. m_Type = MP3EncoderLameCompatible;
  585. SetTraits(BuildTraits(true));
  586. return;
  587. }
  588. #endif // MPT_WITH_LAME
  589. }
  590. bool MP3Encoder::IsAvailable() const
  591. {
  592. return false
  593. #ifdef MPT_WITH_LAME
  594. || (m_Type == MP3EncoderLame)
  595. || (m_Type == MP3EncoderLameCompatible)
  596. #endif // MPT_WITH_LAME
  597. ;
  598. }
  599. std::unique_ptr<IAudioStreamEncoder> MP3Encoder::ConstructStreamEncoder(std::ostream &file, const Encoder::Settings &settings, const FileTags &tags) const
  600. {
  601. std::unique_ptr<IAudioStreamEncoder> result = nullptr;
  602. if(false)
  603. {
  604. // nothing
  605. #ifdef MPT_WITH_LAME
  606. } else if(m_Type == MP3EncoderLame || m_Type == MP3EncoderLameCompatible)
  607. {
  608. result = std::make_unique<MP3LameStreamWriter>(file, (m_Type == MP3EncoderLameCompatible), settings, tags);
  609. #endif // MPT_WITH_LAME
  610. }
  611. return result;
  612. }
  613. mpt::ustring MP3Encoder::DescribeQuality(float quality) const
  614. {
  615. #ifdef MPT_WITH_LAME
  616. if(m_Type == MP3EncoderLame)
  617. {
  618. 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
  619. int q = mpt::saturate_round<int>((1.0f - quality) * 10.0f);
  620. if(q < 0) q = 0;
  621. if(q >= 10)
  622. {
  623. return MPT_UFORMAT("VBR -V{} (~{} kbit)")(U_("9.999"), q_table[q]);
  624. } else
  625. {
  626. return MPT_UFORMAT("VBR -V{} (~{} kbit)")(q, q_table[q]);
  627. }
  628. }
  629. #endif // MPT_WITH_LAME
  630. return EncoderFactoryBase::DescribeQuality(quality);
  631. }
  632. mpt::ustring MP3Encoder::DescribeBitrateABR(int bitrate) const
  633. {
  634. return EncoderFactoryBase::DescribeBitrateABR(bitrate);
  635. }
  636. OPENMPT_NAMESPACE_END