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
358 wxLogNull logNo; /* temporarily disable wxWidgets error messages */
359
360 twolame_options *&encodeOptions = context.encodeOptions;
361 encodeOptions = twolame_init();
362
363 twolame_set_version(encodeOptions, version);
364 twolame_set_in_samplerate(encodeOptions, static_cast<int>(sampleRate));
365 twolame_set_out_samplerate(encodeOptions, static_cast<int>(sampleRate));
366 twolame_set_bitrate(encodeOptions, bitrate);
367 twolame_set_num_channels(encodeOptions, stereo ? 2 : 1);
368
369 if (twolame_init_params(encodeOptions) != 0)
370 {
371 throw ExportException(_("Cannot export MP2 with this sample rate and bit rate"));
372 }
373
374 // Put ID3 tags at beginning of file
375 if (metadata == NULL)
376 metadata = &Tags::Get( project );
377
378 context.outFile = std::make_unique<FileIO>(fName, FileIO::Output);
379 if (!context.outFile->IsOpened()) {
380 throw ExportException(_("Unable to open target file for writing"));
381 }
382
383 bool endOfFile;
384 context.id3len = AddTags(context.id3buffer, &endOfFile, metadata);
385 if (context.id3len && !endOfFile) {
386 if ( context.outFile->Write(context.id3buffer.get(), context.id3len).GetLastError() ) {
387 // TODO: more precise message
388 throw ExportErrorException("MP2:292");
389 }
390 context.id3len = 0;
391 context.id3buffer.reset();
392 }
393
394 context.status = selectionOnly
395 ? XO("Exporting selected audio at %ld kbps")
396 .Format( bitrate )
397 : XO("Exporting the audio at %ld kbps")
398 .Format( bitrate );
399
401 project, selectionOnly, t0, t1, stereo ? 2 : 1, pcmBufferSize, true,
402 sampleRate, int16Sample, mixerSpec);
403
404 return true;
405}
406
408{
409 delegate.SetStatusString(context.status);
410 // We allocate a buffer which is twice as big as the
411 // input buffer, which should always be enough.
412 // We have to multiply by 4 because one sample is 2 bytes wide!
414
415 auto exportResult = ExportResult::Success;
416
417 {
418 while (exportResult == ExportResult::Success) {
419 auto pcmNumSamples = context.mixer->Process();
420 if (pcmNumSamples == 0)
421 break;
422
423 short *pcmBuffer = (short *)context.mixer->GetBuffer();
424
425 int mp2BufferNumBytes = twolame_encode_buffer_interleaved(
426 context.encodeOptions,
427 pcmBuffer,
428 pcmNumSamples,
429 mp2Buffer.get(),
431
432 if (mp2BufferNumBytes < 0) {
433 // TODO: more precise message
434 throw ExportErrorException("MP2:339");
435 }
436
437 if ( context.outFile->Write(mp2Buffer.get(), mp2BufferNumBytes).GetLastError() ) {
438 // TODO: more precise message
439 throw ExportDiskFullError(context.fName);
440 }
442 delegate, *context.mixer, context.t0, context.t1);
443 }
444 }
445
446 int mp2BufferNumBytes = twolame_encode_flush(
447 context.encodeOptions,
448 mp2Buffer.get(),
450
451 if (mp2BufferNumBytes > 0)
452 if ( context.outFile->Write(mp2Buffer.get(), mp2BufferNumBytes).GetLastError() ) {
453 // TODO: more precise message
454 throw ExportErrorException("MP2:362");
455 }
456
457 /* Write ID3 tag if it was supposed to be at the end of the file */
458
459 if (context.id3len)
460 if ( context.outFile->Write(context.id3buffer.get(), context.id3len).GetLastError() ) {
461 // TODO: more precise message
462 throw ExportErrorException("MP2:371");
463 }
464
465 if ( !context.outFile->Close() ) {
466 // TODO: more precise message
467 throw ExportErrorException("MP2:377");
468 }
469 return exportResult;
470}
471
472
473#ifdef USE_LIBID3TAG
474struct id3_tag_deleter {
475 void operator () (id3_tag *p) const { if (p) id3_tag_delete(p); }
476};
477using id3_tag_holder = std::unique_ptr<id3_tag, id3_tag_deleter>;
478#endif
479
480// returns buffer len; caller frees
482 bool *endOfFile, const Tags *tags)
483{
484#ifdef USE_LIBID3TAG
485 id3_tag_holder tp { id3_tag_new() };
486
487 for (const auto &pair : tags->GetRange()) {
488 const auto &n = pair.first;
489 const auto &v = pair.second;
490 const char *name = "TXXX";
491
492 if (n.CmpNoCase(TAG_TITLE) == 0) {
493 name = ID3_FRAME_TITLE;
494 }
495 else if (n.CmpNoCase(TAG_ARTIST) == 0) {
496 name = ID3_FRAME_ARTIST;
497 }
498 else if (n.CmpNoCase(TAG_ALBUM) == 0) {
499 name = ID3_FRAME_ALBUM;
500 }
501 else if (n.CmpNoCase(TAG_YEAR) == 0) {
502 // LLL: Some apps do not like the newer frame ID (ID3_FRAME_YEAR),
503 // so we add old one as well.
504 AddFrame(tp.get(), n, v, "TYER");
505 name = ID3_FRAME_YEAR;
506 }
507 else if (n.CmpNoCase(TAG_GENRE) == 0) {
508 name = ID3_FRAME_GENRE;
509 }
510 else if (n.CmpNoCase(TAG_COMMENTS) == 0) {
511 name = ID3_FRAME_COMMENT;
512 }
513 else if (n.CmpNoCase(TAG_TRACK) == 0) {
514 name = ID3_FRAME_TRACK;
515 }
516
517 AddFrame(tp.get(), n, v, name);
518 }
519
520 tp->options &= (~ID3_TAG_OPTION_COMPRESSION); // No compression
521
522 // If this version of libid3tag supports it, use v2.3 ID3
523 // tags instead of the newer, but less well supported, v2.4
524 // that libid3tag uses by default.
525 #ifdef ID3_TAG_HAS_TAG_OPTION_ID3V2_3
526 tp->options |= ID3_TAG_OPTION_ID3V2_3;
527 #endif
528
529 *endOfFile = false;
530
531 id3_length_t len;
532
533 len = id3_tag_render(tp.get(), 0);
534 buffer.reinit(len);
535 len = id3_tag_render(tp.get(), (id3_byte_t *)buffer.get());
536
537
538 return len;
539#else //ifdef USE_LIBID3TAG
540 return 0;
541#endif
542}
543
544#ifdef USE_LIBID3TAG
545void MP2ExportProcessor::AddFrame(struct id3_tag *tp, const wxString & n, const wxString & v, const char *name)
546{
547 struct id3_frame *frame = id3_frame_new(name);
548
549 if (!n.IsAscii() || !v.IsAscii()) {
550 id3_field_settextencoding(id3_frame_field(frame, 0), ID3_FIELD_TEXTENCODING_UTF_16);
551 }
552 else {
553 id3_field_settextencoding(id3_frame_field(frame, 0), ID3_FIELD_TEXTENCODING_ISO_8859_1);
554 }
555
557 id3_utf8_ucs4duplicate((id3_utf8_t *) (const char *) v.mb_str(wxConvUTF8)) };
558
559 if (strcmp(name, ID3_FRAME_COMMENT) == 0) {
560 // A hack to get around iTunes not recognizing the comment. The
561 // language defaults to XXX and, since it's not a valid language,
562 // iTunes just ignores the tag. So, either set it to a valid language
563 // (which one???) or just clear it. Unfortunately, there's no supported
564 // way of clearing the field, so do it directly.
565 id3_field *f = id3_frame_field(frame, 1);
566 memset(f->immediate.value, 0, sizeof(f->immediate.value));
567 id3_field_setfullstring(id3_frame_field(frame, 3), ucs4.get());
568 }
569 else if (strcmp(name, "TXXX") == 0) {
570 id3_field_setstring(id3_frame_field(frame, 2), ucs4.get());
571
572 ucs4.reset(id3_utf8_ucs4duplicate((id3_utf8_t *) (const char *) n.mb_str(wxConvUTF8)));
573
574 id3_field_setstring(id3_frame_field(frame, 1), ucs4.get());
575 }
576 else {
577 auto addr = ucs4.get();
578 id3_field_setstrings(id3_frame_field(frame, 1), 1, &addr);
579 }
580
581 id3_tag_attachframe(tp, frame);
582}
583#endif
584
586 []{ return std::make_unique< ExportMP2 >(); }
587};
wxT("CloseDown"))
const TranslatableString name
Definition: Distortion.cpp:76
static ExportPluginRegistry::RegisteredPlugin sRegisteredPlugin
Definition: ExportMP2.cpp:585
TranslatableString n_kbps(int n)
Definition: ExportMP3.cpp:121
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:148
#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 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:59
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 AudacityProject &project, 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
ExportResult Process(ExportProcessorDelegate &delegate) override
Definition: ExportMP2.cpp:407
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:481
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
struct MP2ExportProcessor::@172 context
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
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