Audacity  2.2.2
ExportOGG.cpp
Go to the documentation of this file.
1 /**********************************************************************
2 
3  Audacity: A Digital Audio Editor
4 
5  ExportOGG.cpp
6 
7  Joshua Haberman
8 
9  This program is distributed under the GNU General Public License, version 2.
10  A copy of this license is included with this source.
11 
12  Portions from vorbis-tools, copyright 2000-2002 Michael Smith
13  <[email protected]>; Vorbize, Kenneth Arnold <[email protected]>;
14  and libvorbis examples, Monty <[email protected]>
15 
16 **********************************************************************/
17 
18 #include "../Audacity.h"
19 
20 #ifdef USE_LIBVORBIS
21 
22 #include "ExportOGG.h"
23 #include "Export.h"
24 
25 #include <wx/log.h>
26 #include <wx/slider.h>
27 
28 #include <vorbis/vorbisenc.h>
29 
30 #include "../FileIO.h"
31 #include "../Project.h"
32 #include "../Mix.h"
33 #include "../Prefs.h"
34 #include "../ShuttleGui.h"
35 
36 #include "../Internat.h"
37 #include "../Tags.h"
38 #include "../Track.h"
39 #include "../widgets/ErrorDialog.h"
40 
41 //----------------------------------------------------------------------------
42 // ExportOGGOptions
43 //----------------------------------------------------------------------------
44 
45 class ExportOGGOptions final : public wxPanelWrapper
46 {
47 public:
48 
49  ExportOGGOptions(wxWindow *parent, int format);
50  virtual ~ExportOGGOptions();
51 
52  void PopulateOrExchange(ShuttleGui & S);
53  bool TransferDataToWindow() override;
54  bool TransferDataFromWindow() override;
55 
56 private:
57 
58  int mOggQualityUnscaled;
59 };
60 
63 ExportOGGOptions::ExportOGGOptions(wxWindow *parent, int WXUNUSED(format))
64 : wxPanelWrapper(parent, wxID_ANY)
65 {
66  mOggQualityUnscaled = gPrefs->Read(wxT("/FileFormats/OggExportQuality"),50)/10;
67 
69  PopulateOrExchange(S);
70 
71  TransferDataToWindow();
72 }
73 
74 ExportOGGOptions::~ExportOGGOptions()
75 {
76  TransferDataFromWindow();
77 }
78 
81 void ExportOGGOptions::PopulateOrExchange(ShuttleGui & S)
82 {
83  S.StartVerticalLay();
84  {
85  S.StartHorizontalLay(wxEXPAND);
86  {
87  S.SetSizerProportion(1);
88  S.StartMultiColumn(2, wxCENTER);
89  {
90  S.SetStretchyCol(1);
91  S.Prop(1).TieSlider(_("Quality:"), mOggQualityUnscaled, 10);
92  }
93  S.EndMultiColumn();
94  }
95  S.EndHorizontalLay();
96  }
97  S.EndVerticalLay();
98 }
99 
102 bool ExportOGGOptions::TransferDataToWindow()
103 {
104  return true;
105 }
106 
109 bool ExportOGGOptions::TransferDataFromWindow()
110 {
111  ShuttleGui S(this, eIsSavingToPrefs);
112  PopulateOrExchange(S);
113 
114  gPrefs->Write(wxT("/FileFormats/OggExportQuality"),mOggQualityUnscaled * 10);
115  gPrefs->Flush();
116 
117  return true;
118 }
119 
120 //----------------------------------------------------------------------------
121 // ExportOGG
122 //----------------------------------------------------------------------------
123 
124 #define SAMPLES_PER_RUN 8192u
125 
126 class ExportOGG final : public ExportPlugin
127 {
128 public:
129 
130  ExportOGG();
131 
132  // Required
133  wxWindow *OptionsCreate(wxWindow *parent, int format) override;
134 
136  std::unique_ptr<ProgressDialog> &pDialog,
137  unsigned channels,
138  const wxString &fName,
139  bool selectedOnly,
140  double t0,
141  double t1,
142  MixerSpec *mixerSpec = NULL,
143  const Tags *metadata = NULL,
144  int subformat = 0) override;
145 
146 private:
147 
148  bool FillComment(AudacityProject *project, vorbis_comment *comment, const Tags *metadata);
149 };
150 
151 ExportOGG::ExportOGG()
152 : ExportPlugin()
153 {
154  AddFormat();
155  SetFormat(wxT("OGG"),0);
156  AddExtension(wxT("ogg"),0);
157  SetMaxChannels(255,0);
158  SetCanMetaData(true,0);
159  SetDescription(_("Ogg Vorbis Files"),0);
160 }
161 
162 ProgressResult ExportOGG::Export(AudacityProject *project,
163  std::unique_ptr<ProgressDialog> &pDialog,
164  unsigned numChannels,
165  const wxString &fName,
166  bool selectionOnly,
167  double t0,
168  double t1,
169  MixerSpec *mixerSpec,
170  const Tags *metadata,
171  int WXUNUSED(subformat))
172 {
173  double rate = project->GetRate();
174  const TrackList *tracks = project->GetTracks();
175  double quality = (gPrefs->Read(wxT("/FileFormats/OggExportQuality"), 50)/(float)100.0);
176 
177  wxLogNull logNo; // temporarily disable wxWidgets error messages
178  auto updateResult = ProgressResult::Success;
179  int eos = 0;
180 
181  FileIO outFile(fName, FileIO::Output);
182 
183  if (!outFile.IsOpened()) {
184  AudacityMessageBox(_("Unable to open target file for writing"));
186  }
187 
188  // All the Ogg and Vorbis encoding data
189  ogg_stream_state stream;
190  ogg_page page;
191  ogg_packet packet;
192 
193  vorbis_info info;
194  vorbis_comment comment;
195  vorbis_dsp_state dsp;
196  vorbis_block block;
197 
198  auto cleanup = finally( [&] {
199  ogg_stream_clear(&stream);
200 
201  vorbis_block_clear(&block);
202  vorbis_dsp_clear(&dsp);
203  vorbis_info_clear(&info);
204  vorbis_comment_clear(&comment);
205  } );
206 
207  // Many of the library functions called below return 0 for success and
208  // various nonzero codes for failure.
209 
210  // Encoding setup
211  vorbis_info_init(&info);
212  if (vorbis_encode_init_vbr(&info, numChannels, (int)(rate + 0.5), quality)) {
213  // TODO: more precise message
214  AudacityMessageBox(_("Unable to export"));
216  }
217 
218  // Retrieve tags
219  if (!FillComment(project, &comment, metadata)) {
220  // TODO: more precise message
221  AudacityMessageBox(_("Unable to export"));
223  }
224 
225  // Set up analysis state and auxiliary encoding storage
226  if (vorbis_analysis_init(&dsp, &info) ||
227  vorbis_block_init(&dsp, &block)) {
228  // TODO: more precise message
229  AudacityMessageBox(_("Unable to export"));
231  }
232 
233  // Set up packet->stream encoder. According to encoder example,
234  // a random serial number makes it more likely that you can make
235  // chained streams with concatenation.
236  srand(time(NULL));
237  if (ogg_stream_init(&stream, rand())) {
238  // TODO: more precise message
239  AudacityMessageBox(_("Unable to export"));
241  }
242 
243  // First we need to write the required headers:
244  // 1. The Ogg bitstream header, which contains codec setup params
245  // 2. The Vorbis comment header
246  // 3. The bitstream codebook.
247  //
248  // After we create those our responsibility is complete, libvorbis will
249  // take care of any other ogg bistream constraints (again, according
250  // to the example encoder source)
251  ogg_packet bitstream_header;
252  ogg_packet comment_header;
253  ogg_packet codebook_header;
254 
255  if(vorbis_analysis_headerout(&dsp, &comment, &bitstream_header, &comment_header,
256  &codebook_header) ||
257  // Place these headers into the stream
258  ogg_stream_packetin(&stream, &bitstream_header) ||
259  ogg_stream_packetin(&stream, &comment_header) ||
260  ogg_stream_packetin(&stream, &codebook_header)) {
261  // TODO: more precise message
262  AudacityMessageBox(_("Unable to export"));
264  }
265 
266  // Flushing these headers now guarentees that audio data will
267  // start on a NEW page, which apparently makes streaming easier
268  while (ogg_stream_flush(&stream, &page)) {
269  if ( outFile.Write(page.header, page.header_len).GetLastError() ||
270  outFile.Write(page.body, page.body_len).GetLastError()) {
271  // TODO: more precise message
272  AudacityMessageBox(_("Unable to export"));
274  }
275  }
276 
277  const WaveTrackConstArray waveTracks =
278  tracks->GetWaveTrackConstArray(selectionOnly, false);
279  {
280  auto mixer = CreateMixer(waveTracks,
281  tracks->GetTimeTrack(),
282  t0, t1,
283  numChannels, SAMPLES_PER_RUN, false,
284  rate, floatSample, true, mixerSpec);
285 
286  InitProgress( pDialog, wxFileName(fName).GetName(),
287  selectionOnly
288  ? _("Exporting the selected audio as Ogg Vorbis")
289  : _("Exporting the audio as Ogg Vorbis") );
290  auto &progress = *pDialog;
291 
292  while (updateResult == ProgressResult::Success && !eos) {
293  float **vorbis_buffer = vorbis_analysis_buffer(&dsp, SAMPLES_PER_RUN);
294  auto samplesThisRun = mixer->Process(SAMPLES_PER_RUN);
295 
296  int err;
297  if (samplesThisRun == 0) {
298  // Tell the library that we wrote 0 bytes - signalling the end.
299  err = vorbis_analysis_wrote(&dsp, 0);
300  }
301  else {
302 
303  for (size_t i = 0; i < numChannels; i++) {
304  float *temp = (float *)mixer->GetBuffer(i);
305  memcpy(vorbis_buffer[i], temp, sizeof(float)*SAMPLES_PER_RUN);
306  }
307 
308  // tell the encoder how many samples we have
309  err = vorbis_analysis_wrote(&dsp, samplesThisRun);
310  }
311 
312  // I don't understand what this call does, so here is the comment
313  // from the example, verbatim:
314  //
315  // vorbis does some data preanalysis, then divvies up blocks
316  // for more involved (potentially parallel) processing. Get
317  // a single block for encoding now
318  while (!err && vorbis_analysis_blockout(&dsp, &block) == 1) {
319 
320  // analysis, assume we want to use bitrate management
321  err = vorbis_analysis(&block, NULL);
322  if (!err)
323  err = vorbis_bitrate_addblock(&block);
324 
325  while (!err && vorbis_bitrate_flushpacket(&dsp, &packet)) {
326 
327  // add the packet to the bitstream
328  err = ogg_stream_packetin(&stream, &packet);
329 
330  // From vorbis-tools-1.0/oggenc/encode.c:
331  // If we've gone over a page boundary, we can do actual output,
332  // so do so (for however many pages are available).
333 
334  while (!err && !eos) {
335  int result = ogg_stream_pageout(&stream, &page);
336  if (!result) {
337  break;
338  }
339 
340  if ( outFile.Write(page.header, page.header_len).GetLastError() ||
341  outFile.Write(page.body, page.body_len).GetLastError()) {
342  // TODO: more precise message
343  AudacityMessageBox(_("Unable to export"));
345  }
346 
347  if (ogg_page_eos(&page)) {
348  eos = 1;
349  }
350  }
351  }
352  }
353 
354  if (err) {
355  updateResult = ProgressResult::Cancelled;
356  // TODO: more precise message
357  AudacityMessageBox(_("Unable to export"));
358  break;
359  }
360 
361  updateResult = progress.Update(mixer->MixGetCurrentTime() - t0, t1 - t0);
362  }
363  }
364 
365  if ( !outFile.Close() ) {
366  updateResult = ProgressResult::Cancelled;
367  // TODO: more precise message
368  AudacityMessageBox(_("Unable to export"));
369  }
370 
371  return updateResult;
372 }
373 
374 wxWindow *ExportOGG::OptionsCreate(wxWindow *parent, int format)
375 {
376  wxASSERT(parent); // to justify safenew
377  return safenew ExportOGGOptions(parent, format);
378 }
379 
380 bool ExportOGG::FillComment(AudacityProject *project, vorbis_comment *comment, const Tags *metadata)
381 {
382  // Retrieve tags from project if not over-ridden
383  if (metadata == NULL)
384  metadata = project->GetTags();
385 
386  vorbis_comment_init(comment);
387 
388  wxString n;
389  for (const auto &pair : metadata->GetRange()) {
390  n = pair.first;
391  const auto &v = pair.second;
392  if (n == TAG_YEAR) {
393  n = wxT("DATE");
394  }
395  vorbis_comment_add_tag(comment,
396  (char *) (const char *) n.mb_str(wxConvUTF8),
397  (char *) (const char *) v.mb_str(wxConvUTF8));
398  }
399 
400  return true;
401 }
402 
404 {
405  return make_movable<ExportOGG>();
406 }
407 
408 #endif // USE_LIBVORBIS
409 
A list of TrackListNode items.
Definition: Track.h:611
ProgressResult
std::unique_ptr< T > movable_ptr
Definition: MemoryX.h:713
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI...
Definition: ShuttleGui.h:366
wxSlider * TieSlider(const wxString &Prompt, WrappedType &WrappedRef, const int max, const int min=0)
void EndMultiColumn()
movable_ptr< ExportPlugin > New_ExportOGG()
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
TimeTrack * GetTimeTrack()
Definition: Track.cpp:1235
void SetSizerProportion(int iProp)
Definition: ShuttleGui.h:254
#define safenew
Definition: Audacity.h:223
void EndHorizontalLay()
Definition: ShuttleGui.cpp:975
AudacityProject provides the main window, with tools and tracks contained within it.
Definition: Project.h:158
void EndVerticalLay()
Definition: ShuttleGui.cpp:991
wxFileConfig * gPrefs
Definition: Prefs.cpp:72
int format
Definition: ExportPCM.cpp:56
WaveTrackConstArray GetWaveTrackConstArray(bool selectionOnly, bool includeMuted=true) const
Definition: Track.cpp:1340
void StartHorizontalLay(int PositionFlags=wxALIGN_CENTRE, int iProp=1)
Definition: ShuttleGui.cpp:966
void StartMultiColumn(int nCols, int PositionFlags=wxALIGN_LEFT)
Definition: ShuttleGui.cpp:998
std::vector< std::shared_ptr< const WaveTrack > > WaveTrackConstArray
Definition: AudioIO.h:66
ID3 Tags (for MP3)
Definition: Tags.h:72
_("Move Track &Down")+wxT("\t")+(GetActiveProject() -> GetCommandManager() ->GetKeyFromName(wxT("TrackMoveDown"))), OnMoveTrack) POPUP_MENU_ITEM(OnMoveTopID, _("Move Track to &Top")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveTop"))), OnMoveTrack) POPUP_MENU_ITEM(OnMoveBottomID, _("Move Track to &Bottom")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveBottom"))), OnMoveTrack) void TrackMenuTable::OnSetName(wxCommandEvent &)
const Tags * GetTags()
Definition: Project.cpp:1429
ShuttleGui & Prop(int iProp)
Definition: ShuttleGui.h:374
virtual ProgressResult Export(AudacityProject *project, std::unique_ptr< ProgressDialog > &pDialog, unsigned channels, const wxString &fName, bool selectedOnly, double t0, double t1, MixerSpec *mixerSpec=NULL, const Tags *metadata=NULL, int subformat=0)=0
called to export audio into a file.
double GetRate() const
Definition: Project.h:181
TrackList * GetTracks()
Definition: Project.h:174
Iterators GetRange() const
Definition: Tags.cpp:444
void SetStretchyCol(int i)
Used to modify an already placed FlexGridSizer to make a column stretchy.
Definition: ShuttleGui.cpp:192
#define TAG_YEAR
Definition: Tags.h:66
Definition: FileIO.h:18
Class used with Mixer.
Definition: Mix.h:58
void StartVerticalLay(int iProp=1)
Definition: ShuttleGui.cpp:982