Audacity  2.2.2
ExportFFmpeg.cpp
Go to the documentation of this file.
1 /**********************************************************************
2 
3  Audacity: A Digital Audio Editor
4 
5  ExportFFmpeg.cpp
6 
7  Audacity(R) is copyright (c) 1999-2009 Audacity Team.
8  License: GPL v2. See License.txt.
9 
10  LRN
11 
12 ******************************************************************//*******************************************************************/
20 
21 
22 #include "../Audacity.h" // keep ffmpeg before wx because they interact
23 #include "../FFmpeg.h" // and Audacity.h before FFmpeg for config*.h
24 
25 #include <wx/choice.h>
26 #include <wx/intl.h>
27 #include <wx/timer.h>
28 #include <wx/progdlg.h>
29 #include <wx/string.h>
30 #include <wx/textctrl.h>
31 #include <wx/listbox.h>
32 #include <wx/window.h>
33 #include <wx/spinctrl.h>
34 #include <wx/combobox.h>
35 
36 #include "../FileFormats.h"
37 #include "../Internat.h"
38 #include "../Mix.h"
39 #include "../Prefs.h"
40 #include "../Project.h"
41 #include "../Tags.h"
42 #include "../Track.h"
43 #include "../widgets/ErrorDialog.h"
44 
45 #include "Export.h"
46 #include "ExportFFmpeg.h"
47 
48 #include "ExportFFmpegDialogs.h"
49 
50 #if defined(WIN32) && _MSC_VER < 1900
51 #define snprintf _snprintf
52 #endif
53 
54 #if defined(USE_FFMPEG)
55 
56 extern FFmpegLibs *FFmpegLibsInst();
57 
58 static bool CheckFFmpegPresence(bool quiet = false)
59 {
60  bool result = true;
61  PickFFmpegLibs();
62  if (!FFmpegLibsInst()->ValidLibsLoaded())
63  {
64  if (!quiet)
65  {
66  AudacityMessageBox(_("Properly configured FFmpeg is required to proceed.\nYou can configure it at Preferences > Libraries."));
67  }
68  result = false;
69  }
70  DropFFmpegLibs();
71  return result;
72 }
73 
74 static int AdjustFormatIndex(int format)
75 {
76  int subFormat = -1;
77  for (int i = 0; i <= FMT_OTHER; i++)
78  {
79  if (ExportFFmpegOptions::fmts[i].compiledIn) subFormat++;
80  if (subFormat == format || i == FMT_OTHER)
81  {
82  subFormat = i;
83  break;
84  }
85  }
86  return subFormat;
87 }
88 
89 //----------------------------------------------------------------------------
90 // ExportFFmpeg
91 //----------------------------------------------------------------------------
92 
93 class ExportFFmpeg final : public ExportPlugin
94 {
95 public:
96 
97  ExportFFmpeg();
98  ~ExportFFmpeg() override;
99 
101  bool CheckFileName(wxFileName &filename, int format = 0) override;
102 
104  bool Init(const char *shortname, AudacityProject *project, const Tags *metadata, int subformat);
105 
107  bool InitCodecs(AudacityProject *project);
108 
110  bool AddTags(const Tags *metadata);
111 
113  void SetMetadata(const Tags *tags, const char *name, const wxChar *tag);
114 
116  bool EncodeAudioFrame(int16_t *pFrame, size_t frameSize);
117 
119  bool Finalize();
120 
121  void FreeResources();
122 
125  wxWindow *OptionsCreate(wxWindow *parent, int format) override;
126 
128  bool CheckSampleRate(int rate, int lowrate, int highrate, const int *sampRates);
129 
131  int AskResample(int bitrate, int rate, int lowrate, int highrate, const int *sampRates);
132 
144  std::unique_ptr<ProgressDialog> &pDialog,
145  unsigned channels,
146  const wxString &fName,
147  bool selectedOnly,
148  double t0,
149  double t1,
150  MixerSpec *mixerSpec = NULL,
151  const Tags *metadata = NULL,
152  int subformat = 0) override;
153 
154 private:
155 
156  AVOutputFormat * mEncFormatDesc{}; // describes our output file to libavformat
157  int default_frame_size{};
158  AVStream * mEncAudioStream{}; // the output audio stream (may remain NULL)
159  int mEncAudioFifoOutBufSiz{};
160 
161  wxString mName;
162 
163  int mSubFormat{};
164  int mBitRate{};
165  int mSampleRate{};
166  unsigned mChannels{};
167  bool mSupportsUTF8{};
168 
169  // Smart pointer fields, their order is the reverse in which they are reset in FreeResources():
170  AVFifoBufferHolder mEncAudioFifo; // FIFO to write incoming audio samples into
171  AVMallocHolder<int16_t> mEncAudioFifoOutBuf; // buffer to read _out_ of the FIFO into
172  AVFormatContextHolder mEncFormatCtx; // libavformat's context for our output file
173  UFileHolder mUfileCloser;
174  AVCodecContextHolder mEncAudioCodecCtx; // the encoder for the output audio stream
175 };
176 
177 ExportFFmpeg::ExportFFmpeg()
178 : ExportPlugin()
179 {
180  mEncFormatDesc = NULL; // describes our output file to libavformat
181  mEncAudioStream = NULL; // the output audio stream (may remain NULL)
182  #define MAX_AUDIO_PACKET_SIZE (128 * 1024)
183  mEncAudioFifoOutBufSiz = 0;
184 
185  mSampleRate = 0;
186  mSupportsUTF8 = true;
187 
188  PickFFmpegLibs(); // DropFFmpegLibs() call is in ExportFFmpeg destructor
189  int avfver = FFmpegLibsInst()->ValidLibsLoaded() ? avformat_version() : 0;
190  int newfmt;
191  // Adds export types from the export type list
192  for (newfmt = 0; newfmt < FMT_LAST; newfmt++)
193  {
194  wxString shortname(ExportFFmpegOptions::fmts[newfmt].shortname);
195  //Don't hide export types when there's no av-libs, and don't hide FMT_OTHER
196  if (newfmt < FMT_OTHER && FFmpegLibsInst()->ValidLibsLoaded())
197  {
198  // Format/Codec support is compiled in?
199  AVOutputFormat *avoformat = av_guess_format(shortname.mb_str(), NULL, NULL);
200  AVCodec *avcodec = avcodec_find_encoder(ExportFFmpegOptions::fmts[newfmt].codecid);
201  if (avoformat == NULL || avcodec == NULL)
202  {
203  ExportFFmpegOptions::fmts[newfmt].compiledIn = false;
204  continue;
205  }
206  }
207  int fmtindex = AddFormat() - 1;
208  SetFormat(ExportFFmpegOptions::fmts[newfmt].name,fmtindex);
209  AddExtension(ExportFFmpegOptions::fmts[newfmt].extension,fmtindex);
210  // For some types add other extensions
211  switch(newfmt)
212  {
213  case FMT_M4A:
214  AddExtension(wxString(wxT("3gp")),fmtindex);
215  AddExtension(wxString(wxT("m4r")),fmtindex);
216  AddExtension(wxString(wxT("mp4")),fmtindex);
217  break;
218  case FMT_WMA2:
219  AddExtension(wxString(wxT("asf")),fmtindex);
220  AddExtension(wxString(wxT("wmv")),fmtindex);
221  break;
222  default:
223  break;
224  }
225 
226  SetMaxChannels(ExportFFmpegOptions::fmts[newfmt].maxchannels,fmtindex);
227  SetDescription(ExportFFmpegOptions::fmts[newfmt].Description(), fmtindex);
228 
229  int canmeta = ExportFFmpegOptions::fmts[newfmt].canmetadata;
230  if (canmeta && (canmeta == AV_CANMETA || canmeta <= avfver))
231  {
232  SetCanMetaData(true,fmtindex);
233  }
234  else
235  {
236  SetCanMetaData(false,fmtindex);
237  }
238  }
239 }
240 
241 ExportFFmpeg::~ExportFFmpeg()
242 {
243  DropFFmpegLibs();
244 }
245 
246 bool ExportFFmpeg::CheckFileName(wxFileName & WXUNUSED(filename), int WXUNUSED(format))
247 {
248  bool result = true;
249 
250  // Show "Locate FFmpeg" dialog
251  if (!CheckFFmpegPresence(true))
252  {
253  FFmpegLibsInst()->FindLibs(NULL);
254  FFmpegLibsInst()->FreeLibs();
255  return LoadFFmpeg(true);
256  }
257 
258  return result;
259 }
260 
261 bool ExportFFmpeg::Init(const char *shortname, AudacityProject *project, const Tags *metadata, int subformat)
262 {
263  // This will undo the acquisition of resources along any early exit path:
264  auto deleter = [](ExportFFmpeg *This) {
265  if (This)
266  This->FreeResources();
267  };
268  std::unique_ptr<ExportFFmpeg, decltype(deleter)> cleanup{ this, deleter };
269 
270  int err;
271  //FFmpegLibsInst()->LoadLibs(NULL,true); //Loaded at startup or from Prefs now
272 
273  if (!FFmpegLibsInst()->ValidLibsLoaded())
274  return false;
275 
276  av_log_set_callback(av_log_wx_callback);
277 
278  // See if libavformat has modules that can write our output format. If so, mEncFormatDesc
279  // will describe the functions used to write the format (used internally by libavformat)
280  // and the default video/audio codecs that the format uses.
281  if ((mEncFormatDesc = av_guess_format(shortname, OSINPUT(mName), NULL)) == NULL)
282  {
283  AudacityMessageBox(wxString::Format(_("FFmpeg : ERROR - Can't determine format description for file \"%s\"."), mName),
284  _("FFmpeg Error"), wxOK|wxCENTER|wxICON_EXCLAMATION);
285  return false;
286  }
287 
288  // mEncFormatCtx is used by libavformat to carry around context data re our output file.
289  mEncFormatCtx.reset(avformat_alloc_context());
290  if (!mEncFormatCtx)
291  {
292  AudacityMessageBox(_("FFmpeg : ERROR - Can't allocate output format context."),
293  _("FFmpeg Error"), wxOK|wxCENTER|wxICON_EXCLAMATION);
294  return false;
295  }
296 
297  // Initialise the output format context.
298  mEncFormatCtx->oformat = mEncFormatDesc;
299 
300  memcpy(mEncFormatCtx->filename, OSINPUT(mName), strlen(OSINPUT(mName))+1);
301 
302  // At the moment Audacity can export only one audio stream
303  if ((mEncAudioStream = avformat_new_stream(mEncFormatCtx.get(), NULL)) == NULL)
304  {
305  AudacityMessageBox(wxString::Format(_("FFmpeg : ERROR - Can't add audio stream to output file \"%s\"."), mName),
306  _("FFmpeg Error"), wxOK|wxCENTER|wxICON_EXCLAMATION);
307  return false;
308  }
309 
310  // Documentation for avformat_new_stream says
311  // "User is required to call avcodec_close() and avformat_free_context() to clean
312  // up the allocation by avformat_new_stream()."
313 
314  // We use smart pointers that ensure these cleanups either in their destructors or
315  // sooner if they are reset. These are std::unique_ptr with nondefault deleter
316  // template parameters.
317 
318  // mEncFormatCtx takes care of avformat_free_context(), so
319  // mEncAudioStream can be a plain pointer.
320 
321  // mEncAudioCodecCtx now becomes responsible for closing the codec:
322  mEncAudioCodecCtx.reset(mEncAudioStream->codec);
323  mEncAudioStream->id = 0;
324 
325  // Open the output file.
326  if (!(mEncFormatDesc->flags & AVFMT_NOFILE))
327  {
328  if ((err = ufile_fopen(&mEncFormatCtx->pb, mName, AVIO_FLAG_WRITE)) < 0)
329  {
330  AudacityMessageBox(wxString::Format(_("FFmpeg : ERROR - Can't open output file \"%s\" to write. Error code is %d."), mName, err),
331  _("FFmpeg Error"), wxOK|wxCENTER|wxICON_EXCLAMATION);
332  return false;
333  }
334  // Give mUfileCloser responsibility
335  mUfileCloser.reset(mEncFormatCtx->pb);
336  }
337 
338  // Open the audio stream's codec and initialise any stream related data.
339  if (!InitCodecs(project))
340  return false;
341 
342  if (metadata == NULL)
343  metadata = project->GetTags();
344 
345  // Add metadata BEFORE writing the header.
346  // At the moment that works with ffmpeg-git and ffmpeg-0.5 for MP4.
347  if (GetCanMetaData(subformat))
348  {
349  mSupportsUTF8 = ExportFFmpegOptions::fmts[mSubFormat].canutf8;
350  AddTags(metadata);
351  }
352 
353  // Write headers to the output file.
354  if ((err = avformat_write_header(mEncFormatCtx.get(), NULL)) < 0)
355  {
356  AudacityMessageBox(wxString::Format(_("FFmpeg : ERROR - Can't write headers to output file \"%s\". Error code is %d."), mName,err),
357  _("FFmpeg Error"), wxOK|wxCENTER|wxICON_EXCLAMATION);
358  return false;
359  }
360 
361  // Only now, we can keep all the resources until after Finalize().
362  // Cancel the local cleanup.
363  cleanup.release();
364 
365  return true;
366 }
367 
368 bool ExportFFmpeg::CheckSampleRate(int rate, int lowrate, int highrate, const int *sampRates)
369 {
370  if (rate < lowrate || rate > highrate) return false;
371  for (int i = 0; sampRates[i] > 0; i++)
372  if (rate == sampRates[i]) return true;
373  return false;
374 }
375 
376 static int set_dict_int(AVDictionary **dict, const char *key, int val)
377 {
378  char val_str[256];
379  snprintf(val_str, sizeof(val_str), "%d", val);
380  return av_dict_set(dict, key, val_str, 0);
381 }
382 
383 bool ExportFFmpeg::InitCodecs(AudacityProject *project)
384 {
385  AVCodec *codec = NULL;
386  AVDictionary *options = NULL;
387  AVDictionaryCleanup cleanup{ &options };
388 
389  // Configure the audio stream's codec context.
390 
391  mEncAudioCodecCtx->codec_id = ExportFFmpegOptions::fmts[mSubFormat].codecid;
392  mEncAudioCodecCtx->codec_type = AVMEDIA_TYPE_AUDIO;
393  mEncAudioCodecCtx->codec_tag = av_codec_get_tag(mEncFormatCtx->oformat->codec_tag,mEncAudioCodecCtx->codec_id);
394  mSampleRate = (int)project->GetRate();
395  mEncAudioCodecCtx->global_quality = -99999; //quality mode is off by default;
396 
397  // Each export type has its own settings
398  switch (mSubFormat)
399  {
400  case FMT_M4A:
401  mEncAudioCodecCtx->bit_rate = 98000;
402  mEncAudioCodecCtx->bit_rate *= mChannels;
403  mEncAudioCodecCtx->profile = FF_PROFILE_AAC_LOW;
404  mEncAudioCodecCtx->cutoff = 0;
405  mEncAudioCodecCtx->global_quality = gPrefs->Read(wxT("/FileFormats/AACQuality"),-99999);
406  if (!CheckSampleRate(mSampleRate,
407  ExportFFmpegOptions::iAACSampleRates[0],
408  ExportFFmpegOptions::iAACSampleRates[11],
409  &ExportFFmpegOptions::iAACSampleRates[0]))
410  {
411  mSampleRate = AskResample(mEncAudioCodecCtx->bit_rate,mSampleRate,
412  ExportFFmpegOptions::iAACSampleRates[0],
413  ExportFFmpegOptions::iAACSampleRates[11],
414  &ExportFFmpegOptions::iAACSampleRates[0]);
415  }
416  break;
417  case FMT_AC3:
418  mEncAudioCodecCtx->bit_rate = gPrefs->Read(wxT("/FileFormats/AC3BitRate"), 192000);
419  if (!CheckSampleRate(mSampleRate,ExportFFmpegAC3Options::iAC3SampleRates[0], ExportFFmpegAC3Options::iAC3SampleRates[2], &ExportFFmpegAC3Options::iAC3SampleRates[0]))
420  mSampleRate = AskResample(mEncAudioCodecCtx->bit_rate,mSampleRate, ExportFFmpegAC3Options::iAC3SampleRates[0], ExportFFmpegAC3Options::iAC3SampleRates[2], &ExportFFmpegAC3Options::iAC3SampleRates[0]);
421  break;
422  case FMT_AMRNB:
423  mSampleRate = 8000;
424  mEncAudioCodecCtx->bit_rate = gPrefs->Read(wxT("/FileFormats/AMRNBBitRate"), 12200);
425  break;
426  case FMT_WMA2:
427  mEncAudioCodecCtx->bit_rate = gPrefs->Read(wxT("/FileFormats/WMABitRate"), 198000);
428  if (!CheckSampleRate(mSampleRate,ExportFFmpegWMAOptions::iWMASampleRates[0], ExportFFmpegWMAOptions::iWMASampleRates[4], &ExportFFmpegWMAOptions::iWMASampleRates[0]))
429  mSampleRate = AskResample(mEncAudioCodecCtx->bit_rate,mSampleRate, ExportFFmpegWMAOptions::iWMASampleRates[0], ExportFFmpegWMAOptions::iWMASampleRates[4], &ExportFFmpegWMAOptions::iWMASampleRates[0]);
430  break;
431  case FMT_OTHER:
432  av_dict_set(&mEncAudioStream->metadata, "language", gPrefs->Read(wxT("/FileFormats/FFmpegLanguage"),wxT("")).ToUTF8(), 0);
433  mEncAudioCodecCtx->sample_rate = gPrefs->Read(wxT("/FileFormats/FFmpegSampleRate"),(long)0);
434  if (mEncAudioCodecCtx->sample_rate != 0) mSampleRate = mEncAudioCodecCtx->sample_rate;
435  mEncAudioCodecCtx->bit_rate = gPrefs->Read(wxT("/FileFormats/FFmpegBitRate"), (long)0);
436  strncpy((char *)&mEncAudioCodecCtx->codec_tag,gPrefs->Read(wxT("/FileFormats/FFmpegTag"),wxT("")).mb_str(wxConvUTF8),4);
437  mEncAudioCodecCtx->global_quality = gPrefs->Read(wxT("/FileFormats/FFmpegQuality"),(long)-99999);
438  mEncAudioCodecCtx->cutoff = gPrefs->Read(wxT("/FileFormats/FFmpegCutOff"),(long)0);
439  mEncAudioCodecCtx->flags2 = 0;
440  if (gPrefs->Read(wxT("/FileFormats/FFmpegBitReservoir"),true))
441  av_dict_set(&options, "reservoir", "1", 0);
442  if (gPrefs->Read(wxT("/FileFormats/FFmpegVariableBlockLen"),true)) mEncAudioCodecCtx->flags2 |= 0x0004; //WMA only?
443  mEncAudioCodecCtx->compression_level = gPrefs->Read(wxT("/FileFormats/FFmpegCompLevel"),-1);
444  mEncAudioCodecCtx->frame_size = gPrefs->Read(wxT("/FileFormats/FFmpegFrameSize"),(long)0);
445 
446 //FIXME The list of supported options for the seleced encoder should be extracted instead of a few hardcoded
447  set_dict_int(&options, "lpc_coeff_precision", gPrefs->Read(wxT("/FileFormats/FFmpegLPCCoefPrec"),(long)0));
448  set_dict_int(&options, "min_prediction_order", gPrefs->Read(wxT("/FileFormats/FFmpegMinPredOrder"),(long)-1));
449  set_dict_int(&options, "max_prediction_order", gPrefs->Read(wxT("/FileFormats/FFmpegMaxPredOrder"),(long)-1));
450  set_dict_int(&options, "min_partition_order", gPrefs->Read(wxT("/FileFormats/FFmpegMinPartOrder"),(long)-1));
451  set_dict_int(&options, "max_partition_order", gPrefs->Read(wxT("/FileFormats/FFmpegMaxPartOrder"),(long)-1));
452  set_dict_int(&options, "prediction_order_method", gPrefs->Read(wxT("/FileFormats/FFmpegPredOrderMethod"),(long)0));
453  set_dict_int(&options, "muxrate", gPrefs->Read(wxT("/FileFormats/FFmpegMuxRate"),(long)0));
454  mEncFormatCtx->packet_size = gPrefs->Read(wxT("/FileFormats/FFmpegPacketSize"),(long)0);
455  codec = avcodec_find_encoder_by_name(gPrefs->Read(wxT("/FileFormats/FFmpegCodec")).ToUTF8());
456  if (!codec)
457  mEncAudioCodecCtx->codec_id = mEncFormatDesc->audio_codec;
458  break;
459  default:
460  return false;
461  }
462 
463  // This happens if user refused to resample the project
464  if (mSampleRate == 0) return false;
465 
466  if (mEncAudioCodecCtx->global_quality >= 0)
467  {
468  mEncAudioCodecCtx->flags |= AV_CODEC_FLAG_QSCALE;
469  }
470  else mEncAudioCodecCtx->global_quality = 0;
471  mEncAudioCodecCtx->global_quality = mEncAudioCodecCtx->global_quality * FF_QP2LAMBDA;
472  mEncAudioCodecCtx->sample_rate = mSampleRate;
473  mEncAudioCodecCtx->channels = mChannels;
474  mEncAudioCodecCtx->channel_layout = av_get_default_channel_layout(mChannels);
475  mEncAudioCodecCtx->time_base.num = 1;
476  mEncAudioCodecCtx->time_base.den = mEncAudioCodecCtx->sample_rate;
477  mEncAudioCodecCtx->sample_fmt = AV_SAMPLE_FMT_S16;
478  mEncAudioCodecCtx->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
479 
480  if (mEncAudioCodecCtx->codec_id == AV_CODEC_ID_AC3)
481  {
482  // As of Jan 4, 2011, the default AC3 encoder only accept SAMPLE_FMT_FLT samples.
483  // But, currently, Audacity only supports SAMPLE_FMT_S16. So, for now, look for the
484  // "older" AC3 codec. this is not a proper solution, but will suffice until other
485  // encoders no longer support SAMPLE_FMT_S16.
486  codec = avcodec_find_encoder_by_name("ac3_fixed");
487  }
488 
489  if (!codec)
490  {
491  codec = avcodec_find_encoder(mEncAudioCodecCtx->codec_id);
492  }
493 
494  // Is the required audio codec compiled into libavcodec?
495  if (codec == NULL)
496  {
497  AudacityMessageBox(wxString::Format(_("FFmpeg cannot find audio codec 0x%x.\nSupport for this codec is probably not compiled in."), (unsigned int) mEncAudioCodecCtx->codec_id),
498  _("FFmpeg Error"), wxOK|wxCENTER|wxICON_EXCLAMATION);
499  return false;
500  }
501 
502  if (codec->sample_fmts) {
503  for (int i=0; codec->sample_fmts[i] != AV_SAMPLE_FMT_NONE; i++) {
504  enum AVSampleFormat fmt = codec->sample_fmts[i];
505  if ( fmt == AV_SAMPLE_FMT_U8
506  || fmt == AV_SAMPLE_FMT_U8P
507  || fmt == AV_SAMPLE_FMT_S16
508  || fmt == AV_SAMPLE_FMT_S16P
509  || fmt == AV_SAMPLE_FMT_S32
510  || fmt == AV_SAMPLE_FMT_S32P
511  || fmt == AV_SAMPLE_FMT_FLT
512  || fmt == AV_SAMPLE_FMT_FLTP) {
513  mEncAudioCodecCtx->sample_fmt = fmt;
514  }
515  if ( fmt == AV_SAMPLE_FMT_S16
516  || fmt == AV_SAMPLE_FMT_S16P)
517  break;
518  }
519  }
520 
521  if (mEncFormatCtx->oformat->flags & AVFMT_GLOBALHEADER)
522  {
523  mEncAudioCodecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
524  mEncFormatCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
525  }
526 
527  // Open the codec.
528  if (avcodec_open2(mEncAudioCodecCtx.get(), codec, &options) < 0)
529  {
530  AudacityMessageBox(wxString::Format(_("FFmpeg : ERROR - Can't open audio codec 0x%x."),mEncAudioCodecCtx->codec_id),
531  _("FFmpeg Error"), wxOK|wxCENTER|wxICON_EXCLAMATION);
532  return false;
533  }
534 
535  default_frame_size = mEncAudioCodecCtx->frame_size;
536  if (default_frame_size == 0)
537  default_frame_size = 1024; // arbitrary non zero value;
538 
539  wxLogDebug(wxT("FFmpeg : Audio Output Codec Frame Size: %d samples."), mEncAudioCodecCtx->frame_size);
540 
541  // The encoder may require a minimum number of raw audio samples for each encoding but we can't
542  // guarantee we'll get this minimum each time an audio frame is decoded from the input file so
543  // we use a FIFO to store up incoming raw samples until we have enough for one call to the codec.
544  mEncAudioFifo.reset(av_fifo_alloc(1024));
545 
546  mEncAudioFifoOutBufSiz = 2*MAX_AUDIO_PACKET_SIZE;
547  // Allocate a buffer to read OUT of the FIFO into. The FIFO maintains its own buffer internally.
548  mEncAudioFifoOutBuf.reset(static_cast<int16_t*>(av_malloc(mEncAudioFifoOutBufSiz)));
549  if (!mEncAudioFifoOutBuf)
550  {
552  _("FFmpeg : ERROR - Can't allocate buffer to read into from audio FIFO."),
553  _("FFmpeg Error"), wxOK|wxCENTER|wxICON_EXCLAMATION
554  );
555  return false;
556  }
557 
558  return true;
559 }
560 
561 // Returns 0 if no more output, 1 if more output, negative if error
562 static int encode_audio(AVCodecContext *avctx, AVPacket *pkt, int16_t *audio_samples, int nb_samples)
563 {
564  // Assume *pkt is already initialized.
565 
566  int i, ch, buffer_size, ret, got_output = 0;
567  AVMallocHolder<uint8_t> samples;
568  AVFrameHolder frame;
569 
570  if (audio_samples) {
571  frame.reset(av_frame_alloc());
572  if (!frame)
573  return AVERROR(ENOMEM);
574 
575  frame->nb_samples = nb_samples;
576  frame->format = avctx->sample_fmt;
577 #if !defined(DISABLE_DYNAMIC_LOADING_FFMPEG) || (LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 13, 0))
578  frame->channel_layout = avctx->channel_layout;
579 #endif
580 
581  buffer_size = av_samples_get_buffer_size(NULL, avctx->channels, frame->nb_samples,
582  avctx->sample_fmt, 0);
583  if (buffer_size < 0) {
585  _("FFmpeg : ERROR - Could not get sample buffer size"),
586  _("FFmpeg Error"), wxOK|wxCENTER|wxICON_EXCLAMATION
587  );
588  return buffer_size;
589  }
590  samples.reset(static_cast<uint8_t*>(av_malloc(buffer_size)));
591  if (!samples) {
593  _("FFmpeg : ERROR - Could not allocate bytes for samples buffer"),
594  _("FFmpeg Error"), wxOK|wxCENTER|wxICON_EXCLAMATION
595  );
596  return AVERROR(ENOMEM);
597  }
598  /* setup the data pointers in the AVFrame */
599  ret = avcodec_fill_audio_frame(frame.get(), avctx->channels, avctx->sample_fmt,
600  samples.get(), buffer_size, 0);
601  if (ret < 0) {
603  _("FFmpeg : ERROR - Could not setup audio frame"),
604  _("FFmpeg Error"), wxOK|wxCENTER|wxICON_EXCLAMATION
605  );
606  return ret;
607  }
608 
609  for (ch = 0; ch < avctx->channels; ch++) {
610  for (i = 0; i < frame->nb_samples; i++) {
611  switch(avctx->sample_fmt) {
612  case AV_SAMPLE_FMT_U8:
613  ((uint8_t*)(frame->data[0]))[ch + i*avctx->channels] = audio_samples[ch + i*avctx->channels]/258 + 128;
614  break;
615  case AV_SAMPLE_FMT_U8P:
616  ((uint8_t*)(frame->data[ch]))[i] = audio_samples[ch + i*avctx->channels]/258 + 128;
617  break;
618  case AV_SAMPLE_FMT_S16:
619  ((int16_t*)(frame->data[0]))[ch + i*avctx->channels] = audio_samples[ch + i*avctx->channels];
620  break;
621  case AV_SAMPLE_FMT_S16P:
622  ((int16_t*)(frame->data[ch]))[i] = audio_samples[ch + i*avctx->channels];
623  break;
624  case AV_SAMPLE_FMT_S32:
625  ((int32_t*)(frame->data[0]))[ch + i*avctx->channels] = audio_samples[ch + i*avctx->channels]<<16;
626  break;
627  case AV_SAMPLE_FMT_S32P:
628  ((int32_t*)(frame->data[ch]))[i] = audio_samples[ch + i*avctx->channels]<<16;
629  break;
630  case AV_SAMPLE_FMT_FLT:
631  ((float*)(frame->data[0]))[ch + i*avctx->channels] = audio_samples[ch + i*avctx->channels] / 32767.0;
632  break;
633  case AV_SAMPLE_FMT_FLTP:
634  ((float*)(frame->data[ch]))[i] = audio_samples[ch + i*avctx->channels] / 32767.;
635  break;
636  case AV_SAMPLE_FMT_NONE:
637  case AV_SAMPLE_FMT_DBL:
638  case AV_SAMPLE_FMT_DBLP:
639  case AV_SAMPLE_FMT_NB:
640  wxASSERT(false);
641  break;
642  }
643  }
644  }
645  }
646 
647  pkt->data = NULL; // packet data will be allocated by the encoder
648  pkt->size = 0;
649 
650  ret = avcodec_encode_audio2(avctx, pkt, frame.get(), &got_output);
651  if (ret < 0) {
653  _("FFmpeg : ERROR - encoding frame failed"),
654  _("FFmpeg Error"), wxOK|wxCENTER|wxICON_EXCLAMATION
655  );
656  return ret;
657  }
658 
659  pkt->dts = pkt->pts = AV_NOPTS_VALUE; // we dont set frame.pts thus dont trust the AVPacket ts
660 
661  return got_output;
662 }
663 
664 
665 bool ExportFFmpeg::Finalize()
666 {
667  // Flush the audio FIFO and encoder.
668  for (;;)
669  {
670  AVPacketEx pkt;
671  const int nFifoBytes = av_fifo_size(mEncAudioFifo.get()); // any bytes left in audio FIFO?
672  int encodeResult = 0;
673 
674  // Flush the audio FIFO first if necessary. It won't contain a _full_ audio frame because
675  // if it did we'd have pulled it from the FIFO during the last encodeAudioFrame() call
676  if (nFifoBytes > 0)
677  {
678  const int nAudioFrameSizeOut = default_frame_size * mEncAudioCodecCtx->channels * sizeof(int16_t);
679 
680  if (nAudioFrameSizeOut > mEncAudioFifoOutBufSiz || nFifoBytes > mEncAudioFifoOutBufSiz) {
682  _("FFmpeg : ERROR - Too much remaining data."),
683  _("FFmpeg Error"), wxOK | wxCENTER | wxICON_EXCLAMATION
684  );
685  return false;
686  }
687 
688  // We have an incomplete buffer of samples left, encode it.
689  // If codec supports CODEC_CAP_SMALL_LAST_FRAME, we can feed it with smaller frame
690  // Or if frame_size is 1, then it's some kind of PCM codec, they don't have frames and will be fine with the samples
691  // Otherwise we'll send a full frame of audio + silence padding to ensure all audio is encoded
692  int frame_size = default_frame_size;
693  if (mEncAudioCodecCtx->codec->capabilities & AV_CODEC_CAP_SMALL_LAST_FRAME ||
694  frame_size == 1)
695  frame_size = nFifoBytes / (mEncAudioCodecCtx->channels * sizeof(int16_t));
696 
697  wxLogDebug(wxT("FFmpeg : Audio FIFO still contains %d bytes, writing %d sample frame ..."),
698  nFifoBytes, frame_size);
699 
700  // Fill audio buffer with zeroes. If codec tries to read the whole buffer,
701  // it will just read silence. If not - who cares?
702  memset(mEncAudioFifoOutBuf.get(), 0, mEncAudioFifoOutBufSiz);
703  //const AVCodec *codec = mEncAudioCodecCtx->codec;
704 
705  // Pull the bytes out from the FIFO and feed them to the encoder.
706  if (av_fifo_generic_read(mEncAudioFifo.get(), mEncAudioFifoOutBuf.get(), nFifoBytes, NULL) == 0)
707  {
708  encodeResult = encode_audio(mEncAudioCodecCtx.get(), &pkt, mEncAudioFifoOutBuf.get(), frame_size);
709  }
710  else
711  {
712  wxLogDebug(wxT("FFmpeg : Reading from Audio FIFO failed, aborting"));
713  // TODO: more precise message
714  AudacityMessageBox(_("Unable to export"));
715  return false;
716  }
717  }
718  else
719  {
720  // Fifo is empty, flush encoder. May be called multiple times.
721  encodeResult = encode_audio(mEncAudioCodecCtx.get(), &pkt, NULL, 0);
722  }
723 
724  if (encodeResult < 0) {
725  // TODO: more precise message
726  AudacityMessageBox(_("Unable to export"));
727  return false;
728  }
729  else if (encodeResult == 0)
730  break;
731 
732  // We have a packet, send to the muxer
733  pkt.stream_index = mEncAudioStream->index;
734 
735  // Set presentation time of frame (currently in the codec's timebase) in the stream timebase.
736  if (pkt.pts != int64_t(AV_NOPTS_VALUE))
737  pkt.pts = av_rescale_q(pkt.pts, mEncAudioCodecCtx->time_base, mEncAudioStream->time_base);
738  if (pkt.dts != int64_t(AV_NOPTS_VALUE))
739  pkt.dts = av_rescale_q(pkt.dts, mEncAudioCodecCtx->time_base, mEncAudioStream->time_base);
740  if (pkt.duration)
741  pkt.duration = av_rescale_q(pkt.duration, mEncAudioCodecCtx->time_base, mEncAudioStream->time_base);
742 
743  if (av_interleaved_write_frame(mEncFormatCtx.get(), &pkt) != 0)
744  {
746  _("FFmpeg : ERROR - Couldn't write last audio frame to output file."),
747  _("FFmpeg Error"), wxOK | wxCENTER | wxICON_EXCLAMATION
748  );
749  return false;
750  }
751  }
752 
753  // Write any file trailers.
754  if (av_write_trailer(mEncFormatCtx.get()) != 0) {
755  // TODO: more precise message
756  AudacityMessageBox(_("Unable to export"));
757  return false;
758  }
759 
760  return true;
761 }
762 
763 void ExportFFmpeg::FreeResources()
764 {
765  // Close the codecs.
766  mEncAudioCodecCtx.reset();
767 
768  // Close the output file if we created it.
769  mUfileCloser.reset();
770 
771  // Free any buffers or structures we allocated.
772  mEncFormatCtx.reset();
773 
774  mEncAudioFifoOutBuf.reset();
775  mEncAudioFifoOutBufSiz = 0;
776 
777  mEncAudioFifo.reset();
778 
779  av_log_set_callback(av_log_default_callback);
780 }
781 
782 bool ExportFFmpeg::EncodeAudioFrame(int16_t *pFrame, size_t frameSize)
783 {
784  int nBytesToWrite = 0;
785  uint8_t *pRawSamples = NULL;
786  int nAudioFrameSizeOut = default_frame_size * mEncAudioCodecCtx->channels * sizeof(int16_t);
787  int ret;
788 
789  nBytesToWrite = frameSize;
790  pRawSamples = (uint8_t*)pFrame;
791  if (av_fifo_realloc2(mEncAudioFifo.get(), av_fifo_size(mEncAudioFifo.get()) + frameSize) < 0)
792  return false;
793 
794  // Put the raw audio samples into the FIFO.
795  ret = av_fifo_generic_write(mEncAudioFifo.get(), pRawSamples, nBytesToWrite,NULL);
796 
797  if(ret != nBytesToWrite)
798  return false;
799 
800  if (nAudioFrameSizeOut > mEncAudioFifoOutBufSiz) {
802  _("FFmpeg : ERROR - nAudioFrameSizeOut too large."),
803  _("FFmpeg Error"), wxOK|wxCENTER|wxICON_EXCLAMATION
804  );
805  return false;
806  }
807 
808  // Read raw audio samples out of the FIFO in nAudioFrameSizeOut byte-sized groups to encode.
809  while ((ret = av_fifo_size(mEncAudioFifo.get())) >= nAudioFrameSizeOut)
810  {
811  ret = av_fifo_generic_read(mEncAudioFifo.get(), mEncAudioFifoOutBuf.get(), nAudioFrameSizeOut, NULL);
812 
813  AVPacketEx pkt;
814 
815  int ret= encode_audio(mEncAudioCodecCtx.get(),
816  &pkt, // out
817  mEncAudioFifoOutBuf.get(), // in
818  default_frame_size);
819  if (ret < 0)
820  {
822  _("FFmpeg : ERROR - Can't encode audio frame."),
823  _("FFmpeg Error"), wxOK|wxCENTER|wxICON_EXCLAMATION
824  );
825  return false;
826  }
827  if (ret == 0)
828  continue;
829 
830  // Rescale from the codec time_base to the AVStream time_base.
831  if (pkt.pts != int64_t(AV_NOPTS_VALUE))
832  pkt.pts = av_rescale_q(pkt.pts, mEncAudioCodecCtx->time_base, mEncAudioStream->time_base);
833  if (pkt.dts != int64_t(AV_NOPTS_VALUE))
834  pkt.dts = av_rescale_q(pkt.dts, mEncAudioCodecCtx->time_base, mEncAudioStream->time_base);
835  //wxLogDebug(wxT("FFmpeg : (%d) Writing audio frame with PTS: %lld."), mEncAudioCodecCtx->frame_number, (long long) pkt.pts);
836 
837  pkt.stream_index = mEncAudioStream->index;
838 
839  // Write the encoded audio frame to the output file.
840  if ((ret = av_interleaved_write_frame(mEncFormatCtx.get(), &pkt)) < 0)
841  {
843  _("FFmpeg : ERROR - Failed to write audio frame to file."),
844  _("FFmpeg Error"), wxOK|wxCENTER|wxICON_EXCLAMATION
845  );
846  return false;
847  }
848  }
849  return true;
850 }
851 
852 
853 ProgressResult ExportFFmpeg::Export(AudacityProject *project,
854  std::unique_ptr<ProgressDialog> &pDialog,
855  unsigned channels, const wxString &fName,
856  bool selectionOnly, double t0, double t1,
857  MixerSpec *mixerSpec, const Tags *metadata, int subformat)
858 {
859  if (!CheckFFmpegPresence())
861  mChannels = channels;
862  // subformat index may not correspond directly to fmts[] index, convert it
863  mSubFormat = AdjustFormatIndex(subformat);
864  if (channels > ExportFFmpegOptions::fmts[mSubFormat].maxchannels)
865  {
867  wxString::Format(
868  _("Attempted to export %d channels, but maximum number of channels for selected output format is %d"),
869  channels,
870  ExportFFmpegOptions::fmts[mSubFormat].maxchannels),
871  _("Error"));
873  }
874  mName = fName;
875  const TrackList *tracks = project->GetTracks();
876  bool ret = true;
877 
878  if (mSubFormat >= FMT_LAST) {
879  // TODO: more precise message
880  AudacityMessageBox(_("Unable to export"));
882  }
883 
884  wxString shortname(ExportFFmpegOptions::fmts[mSubFormat].shortname);
885  if (mSubFormat == FMT_OTHER)
886  shortname = gPrefs->Read(wxT("/FileFormats/FFmpegFormat"),wxT("matroska"));
887  ret = Init(shortname.mb_str(),project, metadata, subformat);
888  auto cleanup = finally ( [&] { FreeResources(); } );
889 
890  if (!ret) {
891  // TODO: more precise message
892  AudacityMessageBox(_("Unable to export"));
894  }
895 
896  size_t pcmBufferSize = 1024;
897 
898  const WaveTrackConstArray waveTracks =
899  tracks->GetWaveTrackConstArray(selectionOnly, false);
900  auto mixer = CreateMixer(waveTracks,
901  tracks->GetTimeTrack(),
902  t0, t1,
903  channels, pcmBufferSize, true,
904  mSampleRate, int16Sample, true, mixerSpec);
905 
906  auto updateResult = ProgressResult::Success;
907  {
908  InitProgress( pDialog, wxFileName(fName).GetName(),
909  selectionOnly
910  ? wxString::Format(_("Exporting selected audio as %s"),
911  ExportFFmpegOptions::fmts[mSubFormat].Description())
912  : wxString::Format(_("Exporting the audio as %s"),
913  ExportFFmpegOptions::fmts[mSubFormat].Description()) );
914  auto &progress = *pDialog;
915 
916  while (updateResult == ProgressResult::Success) {
917  auto pcmNumSamples = mixer->Process(pcmBufferSize);
918 
919  if (pcmNumSamples == 0)
920  break;
921 
922  short *pcmBuffer = (short *)mixer->GetBuffer();
923 
924  if (!EncodeAudioFrame(
925  pcmBuffer, (pcmNumSamples)*sizeof(int16_t)*mChannels)) {
926  // TODO: more precise message, and fix redundancy with messages
927  // already given on some of the failure paths of the above call
928  AudacityMessageBox(_("Unable to export"));
929  updateResult = ProgressResult::Cancelled;
930  break;
931  }
932 
933  updateResult = progress.Update(mixer->MixGetCurrentTime() - t0, t1 - t0);
934  }
935  }
936 
937  if ( updateResult != ProgressResult::Cancelled )
938  if ( !Finalize() ) // Finalize makes its own messages
940 
941  if ( mUfileCloser.close() != 0 ) {
942  // TODO: more precise message
943  AudacityMessageBox(_("Unable to export"));
945  }
946 
947  return updateResult;
948 }
949 
950 void AddStringTagUTF8(char field[], int size, wxString value)
951 {
952  memset(field,0,size);
953  memcpy(field,value.ToUTF8(),(int)strlen(value.ToUTF8()) > size -1 ? size -1 : strlen(value.ToUTF8()));
954 }
955 
956 void AddStringTagANSI(char field[], int size, wxString value)
957 {
958  memset(field,0,size);
959  memcpy(field,value.mb_str(),(int)strlen(value.mb_str()) > size -1 ? size -1 : strlen(value.mb_str()));
960 }
961 
962 bool ExportFFmpeg::AddTags(const Tags *tags)
963 {
964  if (tags == NULL)
965  {
966  return false;
967  }
968 
969  SetMetadata(tags, "author", TAG_ARTIST);
970  SetMetadata(tags, "album", TAG_ALBUM);
971  SetMetadata(tags, "comment", TAG_COMMENTS);
972  SetMetadata(tags, "genre", TAG_GENRE);
973  SetMetadata(tags, "title", TAG_TITLE);
974  SetMetadata(tags, "year", TAG_YEAR);
975  SetMetadata(tags, "track", TAG_TRACK);
976 
977  return true;
978 }
979 
980 void ExportFFmpeg::SetMetadata(const Tags *tags, const char *name, const wxChar *tag)
981 {
982  if (tags->HasTag(tag))
983  {
984  wxString value = tags->GetTag(tag);
985 
986  av_dict_set(&mEncFormatCtx->metadata, name, mSupportsUTF8 ? value.ToUTF8() : value.mb_str(), 0);
987  }
988 }
989 
990 
991 //----------------------------------------------------------------------------
992 // AskResample dialog
993 //----------------------------------------------------------------------------
994 
995 int ExportFFmpeg::AskResample(int bitrate, int rate, int lowrate, int highrate, const int *sampRates)
996 {
997  wxDialogWrapper d(nullptr, wxID_ANY, wxString(_("Invalid sample rate")));
998  d.SetName(d.GetTitle());
999  wxChoice *choice;
1000  ShuttleGui S(&d, eIsCreating);
1001  wxString text;
1002 
1003  S.StartVerticalLay();
1004  {
1005  S.SetBorder(10);
1006  S.StartStatic(_("Resample"));
1007  {
1008  S.StartHorizontalLay(wxALIGN_CENTER, false);
1009  {
1010  if (bitrate == 0) {
1011  text.Printf(_("The project sample rate (%d) is not supported by the current output\nfile format. "), rate);
1012  }
1013  else {
1014  text.Printf(_("The project sample rate (%d) and bit rate (%d kbps) combination is not\nsupported by the current output file format. "), rate, bitrate/1024);
1015  }
1016 
1017  text += _("You may resample to one of the rates below.");
1018  S.AddTitle(text);
1019  }
1020  S.EndHorizontalLay();
1021 
1022  wxArrayString choices;
1023  wxString selected = wxT("");
1024  for (int i = 0; sampRates[i] > 0; i++)
1025  {
1026  int label = sampRates[i];
1027  if (label >= lowrate && label <= highrate)
1028  {
1029  wxString name = wxString::Format(wxT("%d"),label);
1030  choices.Add(name);
1031  if (label <= rate)
1032  {
1033  selected = name;
1034  }
1035  }
1036  }
1037 
1038  if (selected.IsEmpty())
1039  {
1040  selected = choices[0];
1041  }
1042 
1043  S.StartHorizontalLay(wxALIGN_CENTER, false);
1044  {
1045  choice = S.AddChoice(_("Sample Rates"),
1046  selected,
1047  &choices);
1048  }
1049  S.EndHorizontalLay();
1050  }
1051  S.EndStatic();
1052 
1053  S.AddStandardButtons();
1054  }
1055  S.EndVerticalLay();
1056 
1057  d.Layout();
1058  d.Fit();
1059  d.SetMinSize(d.GetSize());
1060  d.Center();
1061 
1062  if (d.ShowModal() == wxID_CANCEL) {
1063  return 0;
1064  }
1065 
1066  return wxAtoi(choice->GetStringSelection());
1067 }
1068 
1069 wxWindow *ExportFFmpeg::OptionsCreate(wxWindow *parent, int format)
1070 {
1071  wxASSERT(parent); // to justify safenew
1072  // subformat index may not correspond directly to fmts[] index, convert it
1073  mSubFormat = AdjustFormatIndex(format);
1074  if (mSubFormat == FMT_M4A)
1075  {
1076  return safenew ExportFFmpegAACOptions(parent, format);
1077  }
1078  else if (mSubFormat == FMT_AC3)
1079  {
1080  return safenew ExportFFmpegAC3Options(parent, format);
1081  }
1082  else if (mSubFormat == FMT_AMRNB)
1083  {
1084  return safenew ExportFFmpegAMRNBOptions(parent, format);
1085  }
1086  else if (mSubFormat == FMT_WMA2)
1087  {
1088  return safenew ExportFFmpegWMAOptions(parent, format);
1089  }
1090  else if (mSubFormat == FMT_OTHER)
1091  {
1092  return safenew ExportFFmpegCustomOptions(parent, format);
1093  }
1094 
1095  return ExportPlugin::OptionsCreate(parent, format);
1096 }
1097 
1098 std::unique_ptr<ExportPlugin> New_ExportFFmpeg()
1099 {
1100  return std::make_unique<ExportFFmpeg>();
1101 }
1102 
1103 #endif
1104 
void av_log_wx_callback(void *ptr, int level, const char *fmt, va_list vl)
Callback function to catch FFmpeg log messages.
AudacityPrefs * gPrefs
Definition: Prefs.cpp:73
A list of TrackListNode items.
Definition: Track.h:618
ProgressResult
#define TAG_TRACK
Definition: Tags.h:63
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI...
Definition: ShuttleGui.h:409
Controlling class for FFmpeg exporting. Creates the options dialog of the appropriate type...
wxString label
Definition: Tags.cpp:727
#define TAG_TITLE
Definition: Tags.h:60
virtual wxWindow * OptionsCreate(wxWindow *parent, int format)=0
Definition: Export.cpp:215
int AudacityMessageBox(const wxString &message, const wxString &caption=AudacityMessageBoxCaptionStr(), long style=wxOK|wxCENTRE, wxWindow *parent=NULL, int x=wxDefaultCoord, int y=wxDefaultCoord)
Definition: ErrorDialog.h:92
Main class to control the export function.
#define TAG_ARTIST
Definition: Tags.h:61
Options dialog for FFmpeg exporting of AC3 format.
TimeTrack * GetTimeTrack()
Definition: Track.cpp:1244
#define safenew
Definition: Audacity.h:230
static CHOICES sampRates[]
Definition: ExportMP3.cpp:214
AudacityProject provides the main window, with tools and tracks contained within it.
Definition: Project.h:176
int format
Definition: ExportPCM.cpp:56
WaveTrackConstArray GetWaveTrackConstArray(bool selectionOnly, bool includeMuted=true) const
Definition: Track.cpp:1349
#define OSINPUT(X)
Definition: Internat.h:174
std::vector< std::shared_ptr< const WaveTrack > > WaveTrackConstArray
Definition: AudioIO.h:66
ID3 Tags (for MP3)
Definition: Tags.h:70
wxString GetTag(const wxString &name) const
Definition: Tags.cpp:424
_("Move Track &Down")+wxT("\t")+(GetActiveProject() -> GetCommandManager() ->GetKeyFromName(wxT("TrackMoveDown")).Raw()), OnMoveTrack) POPUP_MENU_ITEM(OnMoveTopID, _("Move Track to &Top")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveTop")).Raw()), OnMoveTrack) POPUP_MENU_ITEM(OnMoveBottomID, _("Move Track to &Bottom")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveBottom")).Raw()), OnMoveTrack)#define SET_TRACK_NAME_PLUGIN_SYMBOLclass SetTrackNameCommand:public AudacityCommand
const wxChar * name
Definition: Distortion.cpp:94
Options dialog for FFmpeg exporting of AAC format.
#define TAG_COMMENTS
Definition: Tags.h:66
const Tags * GetTags()
Definition: Project.cpp:1453
#define TAG_GENRE
Definition: Tags.h:65
std::unique_ptr< ExportPlugin > New_ExportFFmpeg()
Class used to dynamically load FFmpeg libraries.
double GetRate() const
Definition: Project.h:199
Options dialog for FFmpeg exporting of WMA format.
TrackList * GetTracks()
Definition: Project.h:192
Options dialog for FFmpeg exporting of AMRNB format.
#define TAG_ALBUM
Definition: Tags.h:62
#define TAG_YEAR
Definition: Tags.h:64
Class used with Mixer.
Definition: Mix.h:58
bool HasTag(const wxString &name) const
Definition: Tags.cpp:415