Audacity 3.2.0
ExportMP2.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 ExportMP2.cpp
6
7 Joshua Haberman
8 Markus Meyer
9
10 Copyright 2002, 2003 Joshua Haberman.
11 Copyright 2006 Markus Meyer
12 Some portions may be Copyright 2003 Paolo Patruno.
13
14 This program is free software; you can redistribute it and/or modify
15 it under the terms of the GNU General Public License as published by
16 the Free Software Foundation; either version 2 of the License, or
17 (at your option) any later version.
18
19 This program is distributed in the hope that it will be useful,
20 but WITHOUT ANY WARRANTY; without even the implied warranty of
21 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 GNU General Public License for more details.
23
24 You should have received a copy of the GNU General Public License
25 along with this program; if not, write to the Free Software
26 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
27
28*******************************************************************/
36#include <wx/defs.h>
37#include <wx/dynlib.h>
38#include <wx/log.h>
39#include <wx/stream.h>
40
41#include "Export.h"
42#include "FileIO.h"
43#include "Mix.h"
44#include "Tags.h"
45#include "Track.h"
46
47#include "ExportPluginHelpers.h"
49
50#define LIBTWOLAME_STATIC
52#include "twolame.h"
53
54#ifdef USE_LIBID3TAG
55 #include <id3tag.h>
56 // DM: the following functions were supposed to have been
57 // included in id3tag.h - should be fixed in the next release
58 // of mad.
59 extern "C" {
60 struct id3_frame *id3_frame_new(char const *);
61 id3_length_t id3_latin1_length(id3_latin1_t const *);
62 void id3_latin1_decode(id3_latin1_t const *, id3_ucs4_t *);
63 }
64#endif
65
66//----------------------------------------------------------------------------
67// ExportMP2Options
68//----------------------------------------------------------------------------
69
70namespace {
71
72// i18n-hint kbps abbreviates "thousands of bits per second"
73inline TranslatableString n_kbps( int n ) { return XO("%d kbps").Format( n ); }
74
75
77 n_kbps(32),
78 n_kbps(48),
79 n_kbps(56),
80 n_kbps(64),
81 n_kbps(80),
82 n_kbps(96),
83 n_kbps(112),
84 n_kbps(128),
85 n_kbps(160),
86 n_kbps(192),//default
87 n_kbps(224),
88 n_kbps(256),
89 n_kbps(320),
90 n_kbps(384),
91};
92
94 n_kbps(8),
95 n_kbps(16),
96 n_kbps(24),
97 n_kbps(32),
98 n_kbps(40),
99 n_kbps(48),
100 n_kbps(56),
101 n_kbps(64),
102 n_kbps(80),
103 n_kbps(96),//default
104 n_kbps(112),
105 n_kbps(128),
106 n_kbps(144),
107 n_kbps(160)
108};
109
110enum : int {
114};
115
116const std::initializer_list<ExportOption> MP2Options {
117 {
118 MP2OptionIDVersion, XO("Version"),
119 1,
121 { 0, 1 },
122 { XO("MPEG2"), XO("MPEG1") },
123
124 },
125 {
126 MP2OptionIDBitRateMPEG1, XO("Bit Rate"),
127 192,
129 { 32, 48, 56, 64, 80, 96,112,128,160, 192, 224, 256, 320, 384 },
131 },
132 {
133 MP2OptionIDBitRateMPEG2, XO("Bit Rate"),
134 96,
136 { 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160 },
138 }
139};
140
141}
142
144{
145 std::vector<ExportOption> mOptions {MP2Options };
146 std::unordered_map<ExportOptionID, ExportValue> mValues;
148public:
150 : mListener(listener)
151 {
152 for(auto& option : mOptions)
153 mValues[option.id] = option.defaultValue;
154 }
155
156 int GetOptionsCount() const override
157 {
158 return static_cast<int>(mOptions.size());
159 }
160 bool GetOption(int index, ExportOption& option) const override
161 {
162 if(index >= 0 && index < mOptions.size())
163 {
164 option = mOptions[index];
165 return true;
166 }
167 return false;
168 }
169 bool GetValue(ExportOptionID id, ExportValue& value) const override
170 {
171 const auto it = mValues.find(id);
172 if(it != mValues.end())
173 {
174 value = it->second;
175 return true;
176 }
177 return false;
178 }
179 bool SetValue(ExportOptionID id, const ExportValue& value) override
180 {
181 auto it = mValues.find(id);
182 if(it == mValues.end() || it->second.index() != value.index())
183 return false;
184
185 it->second = value;
186
187 if(id == MP2OptionIDVersion)
188 {
190
191 if(mListener != nullptr)
192 {
197
199 }
200 }
201 return true;
202 }
204 {
205 auto it = mValues.find(MP2OptionIDVersion);
206 if(*std::get_if<int>(&it->second) == TWOLAME_MPEG1)
207 return { 32000, 44100, 48000 };
208 return {16000, 22050, 24000 };
209 }
210 void Store(audacity::BasicSettings& config) const override
211 {
212 auto it = mValues.find(MP2OptionIDVersion);
213 config.Write(wxT("/FileFormats/MP2Version"), *std::get_if<int>(&it->second));
215 config.Write(wxT("/FileFormats/MP2BitrateMPEG1"), *std::get_if<int>(&it->second));
217 config.Write(wxT("/FileFormats/MP2BitrateMPEG2"), *std::get_if<int>(&it->second));
218 }
219
220 void Load(const audacity::BasicSettings& config) override
221 {
222 config.Read(wxT("/FileFormats/MP2Version"), std::get_if<int>(&mValues[MP2OptionIDVersion]));
223 config.Read(wxT("/FileFormats/MP2BitrateMPEG1"), std::get_if<int>(&mValues[MP2OptionIDBitRateMPEG1]));
224 config.Read(wxT("/FileFormats/MP2BitrateMPEG2"), std::get_if<int>(&mValues[MP2OptionIDBitRateMPEG2]));
226 }
227
229 {
230 if(*std::get_if<int>(&mValues[MP2OptionIDVersion]) == TWOLAME_MPEG1)
231 {
234 }
235 else
236 {
239 }
240 }
241};
242
244{
245 // Values taken from the twolame simple encoder sample
246 constexpr static size_t pcmBufferSize = 9216 / 2; // number of samples
247 constexpr static size_t mp2BufferSize = 16384u; // bytes
248
249 struct
250 {
252 double t0;
253 double t1;
255 std::unique_ptr<Mixer> mixer;
258 twolame_options* encodeOptions{};
259 std::unique_ptr<FileIO> outFile;
261
262public:
263
264 ~MP2ExportProcessor() override;
265
267 const Parameters& parameters,
268 const wxFileNameWrapper& filename,
269 double t0, double t1, bool selectedOnly,
270 double sampleRate, unsigned channels,
271 MixerOptions::Downmix* mixerSpec,
272 const Tags* tags) override;
273
274 ExportResult Process(ExportProcessorDelegate& delegate) override;
275
276private:
277 static int AddTags(ArrayOf<char> &buffer, bool *endOfFile, const Tags *tags);
278#ifdef USE_LIBID3TAG
279 static void AddFrame(struct id3_tag *tp, const wxString & n, const wxString & v, const char *name);
280#endif
281
282};
283
284class ExportMP2 final : public ExportPlugin
285{
286public:
287
289
290 int GetFormatCount() const override;
291 FormatInfo GetFormatInfo(int) const override;
292
293 // Required
294
295 std::unique_ptr<ExportOptionsEditor>
297
298 std::unique_ptr<ExportProcessor> CreateProcessor(int) const override;
299};
300
301ExportMP2::ExportMP2() = default;
302
304{
305 return 1;
306}
307
309{
310 return {
311 wxT("MP2"), XO("MP2 Files"), { wxT("mp2") }, 2, true
312 };
313}
314
315std::unique_ptr<ExportOptionsEditor>
317{
318 return std::make_unique<MP2ExportOptionsEditor>(listener);
319}
320
321std::unique_ptr<ExportProcessor> ExportMP2::CreateProcessor(int) const
322{
323 return std::make_unique<MP2ExportProcessor>();
324}
325
327{
328 if(context.encodeOptions)
329 twolame_close(&context.encodeOptions);
330}
331
332
334 const Parameters& parameters,
335 const wxFileNameWrapper& fName,
336 double t0, double t1, bool selectionOnly,
337 double sampleRate, unsigned channels,
338 MixerOptions::Downmix* mixerSpec,
339 const Tags* metadata)
340{
341 context.t0 = t0;
342 context.t1 = t1;
343 context.fName = fName;
344
345 bool stereo = (channels == 2);
346 const auto version = static_cast<TWOLAME_MPEG_version>(
349
350 const auto bitrate = version == TWOLAME_MPEG1
352 parameters,
355 parameters,
357 const auto &tracks = TrackList::Get( project );
358
359 wxLogNull logNo; /* temporarily disable wxWidgets error messages */
360
361 twolame_options *&encodeOptions = context.encodeOptions;
362 encodeOptions = twolame_init();
363
364 twolame_set_version(encodeOptions, version);
365 twolame_set_in_samplerate(encodeOptions, static_cast<int>(sampleRate));
366 twolame_set_out_samplerate(encodeOptions, static_cast<int>(sampleRate));
367 twolame_set_bitrate(encodeOptions, bitrate);
368 twolame_set_num_channels(encodeOptions, stereo ? 2 : 1);
369
370 if (twolame_init_params(encodeOptions) != 0)
371 {
372 throw ExportException(_("Cannot export MP2 with this sample rate and bit rate"));
373 }
374
375 // Put ID3 tags at beginning of file
376 if (metadata == NULL)
377 metadata = &Tags::Get( project );
378
379 context.outFile = std::make_unique<FileIO>(fName, FileIO::Output);
380 if (!context.outFile->IsOpened()) {
381 throw ExportException(_("Unable to open target file for writing"));
382 }
383
384 bool endOfFile;
385 context.id3len = AddTags(context.id3buffer, &endOfFile, metadata);
386 if (context.id3len && !endOfFile) {
387 if ( context.outFile->Write(context.id3buffer.get(), context.id3len).GetLastError() ) {
388 // TODO: more precise message
389 throw ExportErrorException("MP2:292");
390 }
391 context.id3len = 0;
392 context.id3buffer.reset();
393 }
394
395 context.status = selectionOnly
396 ? XO("Exporting selected audio at %ld kbps")
397 .Format( bitrate )
398 : XO("Exporting the audio at %ld kbps")
399 .Format( bitrate );
400
401 context.mixer = ExportPluginHelpers::CreateMixer(tracks, selectionOnly,
402 t0, t1,
403 stereo ? 2 : 1, pcmBufferSize, true,
404 sampleRate, int16Sample, mixerSpec);
405
406 return true;
407}
408
410{
411 delegate.SetStatusString(context.status);
412 // We allocate a buffer which is twice as big as the
413 // input buffer, which should always be enough.
414 // We have to multiply by 4 because one sample is 2 bytes wide!
416
417 auto exportResult = ExportResult::Success;
418
419 {
420 while (exportResult == ExportResult::Success) {
421 auto pcmNumSamples = context.mixer->Process();
422 if (pcmNumSamples == 0)
423 break;
424
425 short *pcmBuffer = (short *)context.mixer->GetBuffer();
426
427 int mp2BufferNumBytes = twolame_encode_buffer_interleaved(
428 context.encodeOptions,
429 pcmBuffer,
430 pcmNumSamples,
431 mp2Buffer.get(),
433
434 if (mp2BufferNumBytes < 0) {
435 // TODO: more precise message
436 throw ExportErrorException("MP2:339");
437 }
438
439 if ( context.outFile->Write(mp2Buffer.get(), mp2BufferNumBytes).GetLastError() ) {
440 // TODO: more precise message
441 throw ExportDiskFullError(context.fName);
442 }
444 delegate, *context.mixer, context.t0, context.t1);
445 }
446 }
447
448 int mp2BufferNumBytes = twolame_encode_flush(
449 context.encodeOptions,
450 mp2Buffer.get(),
452
453 if (mp2BufferNumBytes > 0)
454 if ( context.outFile->Write(mp2Buffer.get(), mp2BufferNumBytes).GetLastError() ) {
455 // TODO: more precise message
456 throw ExportErrorException("MP2:362");
457 }
458
459 /* Write ID3 tag if it was supposed to be at the end of the file */
460
461 if (context.id3len)
462 if ( context.outFile->Write(context.id3buffer.get(), context.id3len).GetLastError() ) {
463 // TODO: more precise message
464 throw ExportErrorException("MP2:371");
465 }
466
467 if ( !context.outFile->Close() ) {
468 // TODO: more precise message
469 throw ExportErrorException("MP2:377");
470 }
471 return exportResult;
472}
473
474
475#ifdef USE_LIBID3TAG
476struct id3_tag_deleter {
477 void operator () (id3_tag *p) const { if (p) id3_tag_delete(p); }
478};
479using id3_tag_holder = std::unique_ptr<id3_tag, id3_tag_deleter>;
480#endif
481
482// returns buffer len; caller frees
484 bool *endOfFile, const Tags *tags)
485{
486#ifdef USE_LIBID3TAG
487 id3_tag_holder tp { id3_tag_new() };
488
489 for (const auto &pair : tags->GetRange()) {
490 const auto &n = pair.first;
491 const auto &v = pair.second;
492 const char *name = "TXXX";
493
494 if (n.CmpNoCase(TAG_TITLE) == 0) {
495 name = ID3_FRAME_TITLE;
496 }
497 else if (n.CmpNoCase(TAG_ARTIST) == 0) {
498 name = ID3_FRAME_ARTIST;
499 }
500 else if (n.CmpNoCase(TAG_ALBUM) == 0) {
501 name = ID3_FRAME_ALBUM;
502 }
503 else if (n.CmpNoCase(TAG_YEAR) == 0) {
504 // LLL: Some apps do not like the newer frame ID (ID3_FRAME_YEAR),
505 // so we add old one as well.
506 AddFrame(tp.get(), n, v, "TYER");
507 name = ID3_FRAME_YEAR;
508 }
509 else if (n.CmpNoCase(TAG_GENRE) == 0) {
510 name = ID3_FRAME_GENRE;
511 }
512 else if (n.CmpNoCase(TAG_COMMENTS) == 0) {
513 name = ID3_FRAME_COMMENT;
514 }
515 else if (n.CmpNoCase(TAG_TRACK) == 0) {
516 name = ID3_FRAME_TRACK;
517 }
518
519 AddFrame(tp.get(), n, v, name);
520 }
521
522 tp->options &= (~ID3_TAG_OPTION_COMPRESSION); // No compression
523
524 // If this version of libid3tag supports it, use v2.3 ID3
525 // tags instead of the newer, but less well supported, v2.4
526 // that libid3tag uses by default.
527 #ifdef ID3_TAG_HAS_TAG_OPTION_ID3V2_3
528 tp->options |= ID3_TAG_OPTION_ID3V2_3;
529 #endif
530
531 *endOfFile = false;
532
533 id3_length_t len;
534
535 len = id3_tag_render(tp.get(), 0);
536 buffer.reinit(len);
537 len = id3_tag_render(tp.get(), (id3_byte_t *)buffer.get());
538
539
540 return len;
541#else //ifdef USE_LIBID3TAG
542 return 0;
543#endif
544}
545
546#ifdef USE_LIBID3TAG
547void MP2ExportProcessor::AddFrame(struct id3_tag *tp, const wxString & n, const wxString & v, const char *name)
548{
549 struct id3_frame *frame = id3_frame_new(name);
550
551 if (!n.IsAscii() || !v.IsAscii()) {
552 id3_field_settextencoding(id3_frame_field(frame, 0), ID3_FIELD_TEXTENCODING_UTF_16);
553 }
554 else {
555 id3_field_settextencoding(id3_frame_field(frame, 0), ID3_FIELD_TEXTENCODING_ISO_8859_1);
556 }
557
559 id3_utf8_ucs4duplicate((id3_utf8_t *) (const char *) v.mb_str(wxConvUTF8)) };
560
561 if (strcmp(name, ID3_FRAME_COMMENT) == 0) {
562 // A hack to get around iTunes not recognizing the comment. The
563 // language defaults to XXX and, since it's not a valid language,
564 // iTunes just ignores the tag. So, either set it to a valid language
565 // (which one???) or just clear it. Unfortunately, there's no supported
566 // way of clearing the field, so do it directly.
567 id3_field *f = id3_frame_field(frame, 1);
568 memset(f->immediate.value, 0, sizeof(f->immediate.value));
569 id3_field_setfullstring(id3_frame_field(frame, 3), ucs4.get());
570 }
571 else if (strcmp(name, "TXXX") == 0) {
572 id3_field_setstring(id3_frame_field(frame, 2), ucs4.get());
573
574 ucs4.reset(id3_utf8_ucs4duplicate((id3_utf8_t *) (const char *) n.mb_str(wxConvUTF8)));
575
576 id3_field_setstring(id3_frame_field(frame, 1), ucs4.get());
577 }
578 else {
579 auto addr = ucs4.get();
580 id3_field_setstrings(id3_frame_field(frame, 1), 1, &addr);
581 }
582
583 id3_tag_attachframe(tp, frame);
584}
585#endif
586
588 []{ return std::make_unique< ExportMP2 >(); }
589};
wxT("CloseDown"))
const TranslatableString name
Definition: Distortion.cpp:76
static ExportPluginRegistry::RegisteredPlugin sRegisteredPlugin
Definition: ExportMP2.cpp:587
TranslatableString n_kbps(int n)
Definition: ExportMP3.cpp:123
int ExportOptionID
Definition: ExportTypes.h:21
std::variant< bool, int, double, std::string > ExportValue
A type of option values (parameters) used by exporting plugins.
Definition: ExportTypes.h:38
ExportResult
Definition: ExportTypes.h:24
XO("Cut/Copy/Paste")
#define _(s)
Definition: Internat.h:73
std::unique_ptr< Character[], freer > MallocString
Definition: MemoryX.h:147
#define TAG_TRACK
Definition: Tags.h:61
#define TAG_COMMENTS
Definition: Tags.h:64
#define TAG_GENRE
Definition: Tags.h:63
#define TAG_ALBUM
Definition: Tags.h:60
#define TAG_YEAR
Definition: Tags.h:62
#define TAG_TITLE
Definition: Tags.h:58
#define TAG_ARTIST
Definition: Tags.h:59
const auto tracks
const auto project
declares abstract base class Track, TrackList, and iterators over TrackList
std::vector< TranslatableString > TranslatableStrings
void reinit(Integral count, bool initialize=false)
Definition: MemoryX.h:58
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:90
int GetFormatCount() const override
Definition: ExportMP2.cpp:303
std::unique_ptr< ExportProcessor > CreateProcessor(int) const override
Definition: ExportMP2.cpp:321
std::unique_ptr< ExportOptionsEditor > CreateOptionsEditor(int, ExportOptionsEditor::Listener *) const override
Creates format-dependent options editor, that is used to create a valid set of parameters to be used ...
Definition: ExportMP2.cpp:316
FormatInfo GetFormatInfo(int) const override
Returns FormatInfo structure for given index if it's valid, or a default one. FormatInfo::format isn'...
Definition: ExportMP2.cpp:308
Listener object that is used to report on option changes.
virtual void OnExportOptionChangeEnd()=0
Called after OnExportOptionChange
virtual void OnExportOptionChangeBegin()=0
Called before OnExportOptionChange
virtual void OnExportOptionChange(const ExportOption &option)=0
Called when option change.
virtual void OnSampleRateListChange()=0
Editor objects are used to retrieve a set of export options, and configure exporting parameters accor...
std::vector< int > SampleRateList
static T GetParameterValue(const ExportProcessor::Parameters &parameters, int id, T defaultValue=T())
static ExportResult UpdateProgress(ExportProcessorDelegate &delegate, Mixer &mixer, double t0, double t1)
Sends progress update to delegate and retrieves state update from it. Typically used inside each expo...
static std::unique_ptr< Mixer > CreateMixer(const TrackList &tracks, bool selectionOnly, double startTime, double stopTime, unsigned numOutChannels, size_t outBufferSize, bool outInterleaved, double outRate, sampleFormat outFormat, MixerOptions::Downmix *mixerSpec)
virtual void SetStatusString(const TranslatableString &str)=0
std::vector< std::tuple< ExportOptionID, ExportValue > > Parameters
Definition: ExportPlugin.h:93
@ Output
Definition: FileIO.h:27
std::vector< ExportOption > mOptions
Definition: ExportMP2.cpp:145
bool GetOption(int index, ExportOption &option) const override
Definition: ExportMP2.cpp:160
void Load(const audacity::BasicSettings &config) override
Definition: ExportMP2.cpp:220
std::unordered_map< ExportOptionID, ExportValue > mValues
Definition: ExportMP2.cpp:146
bool GetValue(ExportOptionID id, ExportValue &value) const override
Definition: ExportMP2.cpp:169
int GetOptionsCount() const override
Definition: ExportMP2.cpp:156
MP2ExportOptionsEditor(Listener *listener)
Definition: ExportMP2.cpp:149
SampleRateList GetSampleRateList() const override
Definition: ExportMP2.cpp:203
void Store(audacity::BasicSettings &config) const override
Definition: ExportMP2.cpp:210
bool SetValue(ExportOptionID id, const ExportValue &value) override
Definition: ExportMP2.cpp:179
ArrayOf< char > id3buffer
Definition: ExportMP2.cpp:256
static constexpr size_t mp2BufferSize
Definition: ExportMP2.cpp:247
struct MP2ExportProcessor::@163 context
ExportResult Process(ExportProcessorDelegate &delegate) override
Definition: ExportMP2.cpp:409
std::unique_ptr< FileIO > outFile
Definition: ExportMP2.cpp:259
twolame_options * encodeOptions
Definition: ExportMP2.cpp:258
static int AddTags(ArrayOf< char > &buffer, bool *endOfFile, const Tags *tags)
Definition: ExportMP2.cpp:483
bool Initialize(AudacityProject &project, const Parameters &parameters, const wxFileNameWrapper &filename, double t0, double t1, bool selectedOnly, double sampleRate, unsigned channels, MixerOptions::Downmix *mixerSpec, const Tags *tags) override
Called before start processing.
Definition: ExportMP2.cpp:333
TranslatableString status
Definition: ExportMP2.cpp:251
std::unique_ptr< Mixer > mixer
Definition: ExportMP2.cpp:255
~MP2ExportProcessor() override
Definition: ExportMP2.cpp:326
wxFileNameWrapper fName
Definition: ExportMP2.cpp:254
static constexpr size_t pcmBufferSize
Definition: ExportMP2.cpp:246
A matrix of booleans, one row per input channel, column per output.
Definition: MixerOptions.h:32
ID3 Tags (for MP3)
Definition: Tags.h:73
Iterators GetRange() const
Definition: Tags.cpp:426
static Tags & Get(AudacityProject &project)
Definition: Tags.cpp:214
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:314
Holds a msgid for the translation catalog; may also bind format arguments.
Base class for objects that provide facility to store data persistently, and access it with string ke...
Definition: BasicSettings.h:31
virtual bool Write(const wxString &key, bool value)=0
virtual bool Read(const wxString &key, bool *value) const =0
const std::initializer_list< ExportOption > MP2Options
Definition: ExportMP2.cpp:116
const TranslatableStrings BitRateMPEG2Names
Definition: ExportMP2.cpp:93
const TranslatableStrings BitRateMPEG1Names
Definition: ExportMP2.cpp:76
A type that provides a description of an exporting option. Isn't allowed to change except non-type re...
Definition: ExportTypes.h:43
@ TypeEnum
List/enum option. values holds items, and names text to be displayed.
Definition: ExportTypes.h:48
@ Hidden
Option is not used and may be hidden from the user.
Definition: ExportTypes.h:51