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 *******************************************************************//**
29
30 \class MP2Exporter
31 \brief Class used to export MP2 files
32
33 */
34
35
36
37 #ifdef USE_LIBTWOLAME
38
39 #include <wx/defs.h>
40 #include <wx/textctrl.h>
41 #include <wx/dynlib.h>
42 #include <wx/utils.h>
43 #include <wx/timer.h>
44 #include <wx/window.h>
45 #include <wx/log.h>
46 #include <wx/intl.h>
47 #include <wx/stream.h>
48
49 #include "Export.h"
50 #include "FileIO.h"
51 #include "../Mix.h"
52 #include "Prefs.h"
53 #include "ProjectRate.h"
54 #include "../ShuttleGui.h"
55 #include "../Tags.h"
56 #include "../Track.h"
57 #include "../widgets/AudacityMessageBox.h"
58 #include "../widgets/ProgressDialog.h"
59
60 #define LIBTWOLAME_STATIC
61 #include "twolame.h"
62
63 #ifdef USE_LIBID3TAG
64 #include <id3tag.h>
65 // DM: the following functions were supposed to have been
66 // included in id3tag.h - should be fixed in the next release
67 // of mad.
68 extern "C" {
69 struct id3_frame *id3_frame_new(char const *);
70 id3_length_t id3_latin1_length(id3_latin1_t const *);
71 void id3_latin1_decode(id3_latin1_t const *, id3_ucs4_t *);
72 }
73 #endif
74
75 //----------------------------------------------------------------------------
76 // ExportMP2Options
77 //----------------------------------------------------------------------------
78
79 namespace {
80
81 // i18n-hint kbps abbreviates "thousands of bits per second"
n_kbps(int n)82 inline TranslatableString n_kbps( int n ) { return XO("%d kbps").Format( n ); }
83
84 const TranslatableStrings BitRateNames {
85 n_kbps(16),
86 n_kbps(24),
87 n_kbps(32),
88 n_kbps(40),
89 n_kbps(48),
90 n_kbps(56),
91 n_kbps(64),
92 n_kbps(80),
93 n_kbps(96),
94 n_kbps(112),
95 n_kbps(128),
96 n_kbps(160),
97 n_kbps(192),
98 n_kbps(224),
99 n_kbps(256),
100 n_kbps(320),
101 n_kbps(384),
102 };
103
104 const std::vector< int > BitRateValues {
105 16,
106 24,
107 32,
108 40,
109 48,
110 56,
111 64,
112 80,
113 96,
114 112,
115 128,
116 160,
117 192,
118 224,
119 256,
120 320,
121 384,
122 };
123
124 }
125
126 class ExportMP2Options final : public wxPanelWrapper
127 {
128 public:
129 ExportMP2Options(wxWindow *parent, int format);
130 virtual ~ExportMP2Options();
131
132 void PopulateOrExchange(ShuttleGui & S);
133 bool TransferDataToWindow() override;
134 bool TransferDataFromWindow() override;
135 };
136
137 ///
138 ///
ExportMP2Options(wxWindow * parent,int WXUNUSED (format))139 ExportMP2Options::ExportMP2Options(wxWindow *parent, int WXUNUSED(format))
140 : wxPanelWrapper(parent, wxID_ANY)
141 {
142 ShuttleGui S(this, eIsCreatingFromPrefs);
143 PopulateOrExchange(S);
144
145 TransferDataToWindow();
146 }
147
148 ///
149 ///
~ExportMP2Options()150 ExportMP2Options::~ExportMP2Options()
151 {
152 TransferDataFromWindow();
153 }
154
155 ///
156 ///
PopulateOrExchange(ShuttleGui & S)157 void ExportMP2Options::PopulateOrExchange(ShuttleGui & S)
158 {
159 S.StartVerticalLay();
160 {
161 S.StartHorizontalLay(wxCENTER);
162 {
163 S.StartMultiColumn(2, wxCENTER);
164 {
165 S.TieNumberAsChoice(
166 XXO("Bit Rate:"),
167 {wxT("/FileFormats/MP2Bitrate"),
168 160},
169 BitRateNames,
170 &BitRateValues
171 );
172 }
173 S.EndMultiColumn();
174 }
175 S.EndHorizontalLay();
176 }
177 S.EndVerticalLay();
178 }
179
180 ///
181 ///
TransferDataToWindow()182 bool ExportMP2Options::TransferDataToWindow()
183 {
184 return true;
185 }
186
187 ///
188 ///
TransferDataFromWindow()189 bool ExportMP2Options::TransferDataFromWindow()
190 {
191 ShuttleGui S(this, eIsSavingToPrefs);
192 PopulateOrExchange(S);
193
194 gPrefs->Flush();
195
196 return true;
197 }
198
199 //----------------------------------------------------------------------------
200 // ExportMP2
201 //----------------------------------------------------------------------------
202
203 class ExportMP2 final : public ExportPlugin
204 {
205 public:
206
207 ExportMP2();
208
209 // Required
210
211 void OptionsCreate(ShuttleGui &S, int format) override;
212 ProgressResult Export(AudacityProject *project,
213 std::unique_ptr<ProgressDialog> &pDialog,
214 unsigned channels,
215 const wxFileNameWrapper &fName,
216 bool selectedOnly,
217 double t0,
218 double t1,
219 MixerSpec *mixerSpec = NULL,
220 const Tags *metadata = NULL,
221 int subformat = 0) override;
222
223 private:
224
225 int AddTags(AudacityProject *project, ArrayOf<char> &buffer, bool *endOfFile, const Tags *tags);
226 #ifdef USE_LIBID3TAG
227 void AddFrame(struct id3_tag *tp, const wxString & n, const wxString & v, const char *name);
228 #endif
229
230 };
231
ExportMP2()232 ExportMP2::ExportMP2()
233 : ExportPlugin()
234 {
235 AddFormat();
236 SetFormat(wxT("MP2"),0);
237 AddExtension(wxT("mp2"),0);
238 SetMaxChannels(2,0);
239 SetCanMetaData(true,0);
240 SetDescription(XO("MP2 Files"),0);
241 }
242
Export(AudacityProject * project,std::unique_ptr<ProgressDialog> & pDialog,unsigned channels,const wxFileNameWrapper & fName,bool selectionOnly,double t0,double t1,MixerSpec * mixerSpec,const Tags * metadata,int WXUNUSED (subformat))243 ProgressResult ExportMP2::Export(AudacityProject *project,
244 std::unique_ptr<ProgressDialog> &pDialog,
245 unsigned channels, const wxFileNameWrapper &fName,
246 bool selectionOnly, double t0, double t1, MixerSpec *mixerSpec, const Tags *metadata,
247 int WXUNUSED(subformat))
248 {
249 bool stereo = (channels == 2);
250 long bitrate = gPrefs->Read(wxT("/FileFormats/MP2Bitrate"), 160);
251 double rate = ProjectRate::Get(*project).GetRate();
252 const auto &tracks = TrackList::Get( *project );
253
254 wxLogNull logNo; /* temporarily disable wxWidgets error messages */
255
256 twolame_options *encodeOptions;
257 encodeOptions = twolame_init();
258 auto cleanup = finally( [&] { twolame_close(&encodeOptions); } );
259
260 twolame_set_in_samplerate(encodeOptions, (int)(rate + 0.5));
261 twolame_set_out_samplerate(encodeOptions, (int)(rate + 0.5));
262 twolame_set_bitrate(encodeOptions, bitrate);
263 twolame_set_num_channels(encodeOptions, stereo ? 2 : 1);
264
265 if (twolame_init_params(encodeOptions) != 0)
266 {
267 AudacityMessageBox(
268 XO("Cannot export MP2 with this sample rate and bit rate"),
269 XO("Error"),
270 wxICON_STOP);
271 return ProgressResult::Cancelled;
272 }
273
274 // Put ID3 tags at beginning of file
275 if (metadata == NULL)
276 metadata = &Tags::Get( *project );
277
278 FileIO outFile(fName, FileIO::Output);
279 if (!outFile.IsOpened()) {
280 AudacityMessageBox( XO("Unable to open target file for writing") );
281 return ProgressResult::Cancelled;
282 }
283
284 ArrayOf<char> id3buffer;
285 int id3len;
286 bool endOfFile;
287 id3len = AddTags(project, id3buffer, &endOfFile, metadata);
288 if (id3len && !endOfFile) {
289 if ( outFile.Write(id3buffer.get(), id3len).GetLastError() ) {
290 // TODO: more precise message
291 ShowExportErrorDialog("MP2:292");
292 return ProgressResult::Cancelled;
293 }
294 }
295
296 // Values taken from the twolame simple encoder sample
297 const size_t pcmBufferSize = 9216 / 2; // number of samples
298 const size_t mp2BufferSize = 16384u; // bytes
299
300 // We allocate a buffer which is twice as big as the
301 // input buffer, which should always be enough.
302 // We have to multiply by 4 because one sample is 2 bytes wide!
303 ArrayOf<unsigned char> mp2Buffer{ mp2BufferSize };
304
305 auto updateResult = ProgressResult::Success;
306 {
307 auto mixer = CreateMixer(tracks, selectionOnly,
308 t0, t1,
309 stereo ? 2 : 1, pcmBufferSize, true,
310 rate, int16Sample, mixerSpec);
311
312 InitProgress( pDialog, fName,
313 selectionOnly
314 ? XO("Exporting selected audio at %ld kbps")
315 .Format( bitrate )
316 : XO("Exporting the audio at %ld kbps")
317 .Format( bitrate ) );
318 auto &progress = *pDialog;
319
320 while (updateResult == ProgressResult::Success) {
321 auto pcmNumSamples = mixer->Process(pcmBufferSize);
322
323 if (pcmNumSamples == 0)
324 break;
325
326 short *pcmBuffer = (short *)mixer->GetBuffer();
327
328 int mp2BufferNumBytes = twolame_encode_buffer_interleaved(
329 encodeOptions,
330 pcmBuffer,
331 pcmNumSamples,
332 mp2Buffer.get(),
333 mp2BufferSize);
334
335 if (mp2BufferNumBytes < 0) {
336 // TODO: more precise message
337 ShowExportErrorDialog("MP2:339");
338 updateResult = ProgressResult::Cancelled;
339 break;
340 }
341
342 if ( outFile.Write(mp2Buffer.get(), mp2BufferNumBytes).GetLastError() ) {
343 // TODO: more precise message
344 ShowDiskFullExportErrorDialog(fName);
345 return ProgressResult::Cancelled;
346 }
347
348 updateResult = progress.Update(mixer->MixGetCurrentTime() - t0, t1 - t0);
349 }
350 }
351
352 int mp2BufferNumBytes = twolame_encode_flush(
353 encodeOptions,
354 mp2Buffer.get(),
355 mp2BufferSize);
356
357 if (mp2BufferNumBytes > 0)
358 if ( outFile.Write(mp2Buffer.get(), mp2BufferNumBytes).GetLastError() ) {
359 // TODO: more precise message
360 ShowExportErrorDialog("MP2:362");
361 return ProgressResult::Cancelled;
362 }
363
364 /* Write ID3 tag if it was supposed to be at the end of the file */
365
366 if (id3len && endOfFile)
367 if ( outFile.Write(id3buffer.get(), id3len).GetLastError() ) {
368 // TODO: more precise message
369 ShowExportErrorDialog("MP2:371");
370 return ProgressResult::Cancelled;
371 }
372
373 if ( !outFile.Close() ) {
374 // TODO: more precise message
375 ShowExportErrorDialog("MP2:377");
376 return ProgressResult::Cancelled;
377 }
378
379 return updateResult;
380 }
381
OptionsCreate(ShuttleGui & S,int format)382 void ExportMP2::OptionsCreate(ShuttleGui &S, int format)
383 {
384 S.AddWindow( safenew ExportMP2Options{ S.GetParent(), format } );
385 }
386
387
388 #ifdef USE_LIBID3TAG
389 struct id3_tag_deleter {
operator ()id3_tag_deleter390 void operator () (id3_tag *p) const { if (p) id3_tag_delete(p); }
391 };
392 using id3_tag_holder = std::unique_ptr<id3_tag, id3_tag_deleter>;
393 #endif
394
395 // returns buffer len; caller frees
AddTags(AudacityProject * WXUNUSED (project),ArrayOf<char> & buffer,bool * endOfFile,const Tags * tags)396 int ExportMP2::AddTags(
397 AudacityProject * WXUNUSED(project), ArrayOf< char > &buffer,
398 bool *endOfFile, const Tags *tags)
399 {
400 #ifdef USE_LIBID3TAG
401 id3_tag_holder tp { id3_tag_new() };
402
403 for (const auto &pair : tags->GetRange()) {
404 const auto &n = pair.first;
405 const auto &v = pair.second;
406 const char *name = "TXXX";
407
408 if (n.CmpNoCase(TAG_TITLE) == 0) {
409 name = ID3_FRAME_TITLE;
410 }
411 else if (n.CmpNoCase(TAG_ARTIST) == 0) {
412 name = ID3_FRAME_ARTIST;
413 }
414 else if (n.CmpNoCase(TAG_ALBUM) == 0) {
415 name = ID3_FRAME_ALBUM;
416 }
417 else if (n.CmpNoCase(TAG_YEAR) == 0) {
418 // LLL: Some apps do not like the newer frame ID (ID3_FRAME_YEAR),
419 // so we add old one as well.
420 AddFrame(tp.get(), n, v, "TYER");
421 name = ID3_FRAME_YEAR;
422 }
423 else if (n.CmpNoCase(TAG_GENRE) == 0) {
424 name = ID3_FRAME_GENRE;
425 }
426 else if (n.CmpNoCase(TAG_COMMENTS) == 0) {
427 name = ID3_FRAME_COMMENT;
428 }
429 else if (n.CmpNoCase(TAG_TRACK) == 0) {
430 name = ID3_FRAME_TRACK;
431 }
432
433 AddFrame(tp.get(), n, v, name);
434 }
435
436 tp->options &= (~ID3_TAG_OPTION_COMPRESSION); // No compression
437
438 // If this version of libid3tag supports it, use v2.3 ID3
439 // tags instead of the newer, but less well supported, v2.4
440 // that libid3tag uses by default.
441 #ifdef ID3_TAG_HAS_TAG_OPTION_ID3V2_3
442 tp->options |= ID3_TAG_OPTION_ID3V2_3;
443 #endif
444
445 *endOfFile = false;
446
447 id3_length_t len;
448
449 len = id3_tag_render(tp.get(), 0);
450 buffer.reinit(len);
451 len = id3_tag_render(tp.get(), (id3_byte_t *)buffer.get());
452
453
454 return len;
455 #else //ifdef USE_LIBID3TAG
456 return 0;
457 #endif
458 }
459
460 #ifdef USE_LIBID3TAG
AddFrame(struct id3_tag * tp,const wxString & n,const wxString & v,const char * name)461 void ExportMP2::AddFrame(struct id3_tag *tp, const wxString & n, const wxString & v, const char *name)
462 {
463 struct id3_frame *frame = id3_frame_new(name);
464
465 if (!n.IsAscii() || !v.IsAscii()) {
466 id3_field_settextencoding(id3_frame_field(frame, 0), ID3_FIELD_TEXTENCODING_UTF_16);
467 }
468 else {
469 id3_field_settextencoding(id3_frame_field(frame, 0), ID3_FIELD_TEXTENCODING_ISO_8859_1);
470 }
471
472 MallocString<id3_ucs4_t> ucs4 {
473 id3_utf8_ucs4duplicate((id3_utf8_t *) (const char *) v.mb_str(wxConvUTF8)) };
474
475 if (strcmp(name, ID3_FRAME_COMMENT) == 0) {
476 // A hack to get around iTunes not recognizing the comment. The
477 // language defaults to XXX and, since it's not a valid language,
478 // iTunes just ignores the tag. So, either set it to a valid language
479 // (which one???) or just clear it. Unfortunately, there's no supported
480 // way of clearing the field, so do it directly.
481 id3_field *f = id3_frame_field(frame, 1);
482 memset(f->immediate.value, 0, sizeof(f->immediate.value));
483 id3_field_setfullstring(id3_frame_field(frame, 3), ucs4.get());
484 }
485 else if (strcmp(name, "TXXX") == 0) {
486 id3_field_setstring(id3_frame_field(frame, 2), ucs4.get());
487
488 ucs4.reset(id3_utf8_ucs4duplicate((id3_utf8_t *) (const char *) n.mb_str(wxConvUTF8)));
489
490 id3_field_setstring(id3_frame_field(frame, 1), ucs4.get());
491 }
492 else {
493 auto addr = ucs4.get();
494 id3_field_setstrings(id3_frame_field(frame, 1), 1, &addr);
495 }
496
497 id3_tag_attachframe(tp, frame);
498 }
499 #endif
500
501 static Exporter::RegisteredExportPlugin sRegisteredPlugin{ "MP2",
__anon18789a1c0302null502 []{ return std::make_unique< ExportMP2 >(); }
503 };
504
505 #endif // #ifdef USE_LIBTWOLAME
506
507