Audacity 3.2.0
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**********************************************************************/
13
14
15
16#ifdef USE_LIBVORBIS
17
18#include "Export.h"
19
20#include <wx/log.h>
21#include <wx/slider.h>
22#include <wx/stream.h>
23
24#include <vorbis/vorbisenc.h>
25
26#include "FileIO.h"
27#include "ProjectRate.h"
28#include "Mix.h"
29#include "Prefs.h"
30#include "ShuttleGui.h"
31
32#include "Tags.h"
33#include "Track.h"
34#include "AudacityMessageBox.h"
35#include "ProgressDialog.h"
36
37//----------------------------------------------------------------------------
38// ExportOGGOptions
39//----------------------------------------------------------------------------
40
41class ExportOGGOptions final : public wxPanelWrapper
42{
43public:
44
45 ExportOGGOptions(wxWindow *parent, int format);
46 virtual ~ExportOGGOptions();
47
49 bool TransferDataToWindow() override;
50 bool TransferDataFromWindow() override;
51
52private:
53
55};
56
59ExportOGGOptions::ExportOGGOptions(wxWindow *parent, int WXUNUSED(format))
60: wxPanelWrapper(parent, wxID_ANY)
61{
62 mOggQualityUnscaled = gPrefs->Read(wxT("/FileFormats/OggExportQuality"),50)/10;
63
66
68}
69
71{
73}
74
78{
79 S.StartVerticalLay();
80 {
81 S.StartHorizontalLay(wxEXPAND);
82 {
83 S.SetSizerProportion(1);
84 S.StartMultiColumn(2, wxCENTER);
85 {
86 S.SetStretchyCol(1);
87 S.Prop(1).TieSlider(
88 XXO("Quality:"), mOggQualityUnscaled, 10);
89 }
90 S.EndMultiColumn();
91 }
92 S.EndHorizontalLay();
93 }
94 S.EndVerticalLay();
95}
96
100{
101 return true;
102}
103
107{
110
111 gPrefs->Write(wxT("/FileFormats/OggExportQuality"),mOggQualityUnscaled * 10);
112 gPrefs->Flush();
113
114 return true;
115}
116
117//----------------------------------------------------------------------------
118// ExportOGG
119//----------------------------------------------------------------------------
120
121#define SAMPLES_PER_RUN 8192u
122
123class ExportOGG final : public ExportPlugin
124{
125public:
126
127 ExportOGG();
128
129 // Required
130 void OptionsCreate(ShuttleGui &S, int format) override;
131
133 std::unique_ptr<BasicUI::ProgressDialog> &pDialog,
134 unsigned channels,
135 const wxFileNameWrapper &fName,
136 bool selectedOnly,
137 double t0,
138 double t1,
139 MixerSpec *mixerSpec = NULL,
140 const Tags *metadata = NULL,
141 int subformat = 0) override;
142
143private:
144
145 bool FillComment(AudacityProject *project, vorbis_comment *comment, const Tags *metadata);
146};
147
149: ExportPlugin()
150{
151 AddFormat();
152 SetFormat(wxT("OGG"),0);
153 AddExtension(wxT("ogg"),0);
154 SetMaxChannels(255,0);
155 SetCanMetaData(true,0);
156 SetDescription(XO("Ogg Vorbis Files"),0);
157}
158
160 std::unique_ptr<BasicUI::ProgressDialog> &pDialog,
161 unsigned numChannels,
162 const wxFileNameWrapper &fName,
163 bool selectionOnly,
164 double t0,
165 double t1,
166 MixerSpec *mixerSpec,
167 const Tags *metadata,
168 int WXUNUSED(subformat))
169{
170 double rate = ProjectRate::Get( *project ).GetRate();
171 const auto &tracks = TrackList::Get( *project );
172 double quality = (gPrefs->Read(wxT("/FileFormats/OggExportQuality"), 50)/(float)100.0);
173
174 wxLogNull logNo; // temporarily disable wxWidgets error messages
175 auto updateResult = ProgressResult::Success;
176 int eos = 0;
177
178 FileIO outFile(fName, FileIO::Output);
179
180 if (!outFile.IsOpened()) {
181 AudacityMessageBox( XO("Unable to open target file for writing") );
183 }
184
185 // All the Ogg and Vorbis encoding data
186 ogg_stream_state stream;
187 ogg_page page;
188 ogg_packet packet;
189
190 vorbis_info info;
191 vorbis_comment comment;
192 vorbis_dsp_state dsp;
193 vorbis_block block;
194
195
196 auto cleanup1 = finally( [&] {
197 vorbis_info_clear(&info);
198 } );
199
200
201 // Many of the library functions called below return 0 for success and
202 // various nonzero codes for failure.
203
204 // Encoding setup
205 vorbis_info_init(&info);
206 if (vorbis_encode_init_vbr(&info, numChannels, (int)(rate + 0.5), quality)) {
207 // TODO: more precise message
208 AudacityMessageBox( XO("Unable to export - rate or quality problem") );
210 }
211
212 auto cleanup2 = finally( [&] {
213 ogg_stream_clear(&stream);
214
215 vorbis_block_clear(&block);
216 vorbis_dsp_clear(&dsp);
217 vorbis_comment_clear(&comment);
218 } );
219
220 // Retrieve tags
221 if (!FillComment(project, &comment, metadata)) {
222 AudacityMessageBox( XO("Unable to export - problem with metadata") );
224 }
225
226 // Set up analysis state and auxiliary encoding storage
227 if (vorbis_analysis_init(&dsp, &info) ||
228 vorbis_block_init(&dsp, &block)) {
229 AudacityMessageBox( XO("Unable to export - problem initialising") );
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 AudacityMessageBox( XO("Unable to export - problem creating stream") );
240 }
241
242 // First we need to write the required headers:
243 // 1. The Ogg bitstream header, which contains codec setup params
244 // 2. The Vorbis comment header
245 // 3. The bitstream codebook.
246 //
247 // After we create those our responsibility is complete, libvorbis will
248 // take care of any other ogg bitstream constraints (again, according
249 // to the example encoder source)
250 ogg_packet bitstream_header;
251 ogg_packet comment_header;
252 ogg_packet codebook_header;
253
254 if(vorbis_analysis_headerout(&dsp, &comment, &bitstream_header, &comment_header,
255 &codebook_header) ||
256 // Place these headers into the stream
257 ogg_stream_packetin(&stream, &bitstream_header) ||
258 ogg_stream_packetin(&stream, &comment_header) ||
259 ogg_stream_packetin(&stream, &codebook_header)) {
260 AudacityMessageBox( XO("Unable to export - problem with packets") );
262 }
263
264 // Flushing these headers now guarantees that audio data will
265 // start on a NEW page, which apparently makes streaming easier
266 while (ogg_stream_flush(&stream, &page)) {
267 if ( outFile.Write(page.header, page.header_len).GetLastError() ||
268 outFile.Write(page.body, page.body_len).GetLastError()) {
269 AudacityMessageBox( XO("Unable to export - problem with file") );
271 }
272 }
273
274 {
275 auto mixer = CreateMixer(tracks, selectionOnly,
276 t0, t1,
277 numChannels, SAMPLES_PER_RUN, false,
278 rate, floatSample, mixerSpec);
279
280 InitProgress( pDialog, fName,
281 selectionOnly
282 ? XO("Exporting the selected audio as Ogg Vorbis")
283 : XO("Exporting the audio as Ogg Vorbis") );
284 auto &progress = *pDialog;
285
286 while (updateResult == ProgressResult::Success && !eos) {
287 float **vorbis_buffer = vorbis_analysis_buffer(&dsp, SAMPLES_PER_RUN);
288 auto samplesThisRun = mixer->Process();
289 int err;
290 if (samplesThisRun == 0) {
291 // Tell the library that we wrote 0 bytes - signalling the end.
292 err = vorbis_analysis_wrote(&dsp, 0);
293 }
294 else {
295
296 for (size_t i = 0; i < numChannels; i++) {
297 float *temp = (float *)mixer->GetBuffer(i);
298 memcpy(vorbis_buffer[i], temp, sizeof(float)*SAMPLES_PER_RUN);
299 }
300
301 // tell the encoder how many samples we have
302 err = vorbis_analysis_wrote(&dsp, samplesThisRun);
303 }
304
305 // I don't understand what this call does, so here is the comment
306 // from the example, verbatim:
307 //
308 // vorbis does some data preanalysis, then divvies up blocks
309 // for more involved (potentially parallel) processing. Get
310 // a single block for encoding now
311 while (!err && vorbis_analysis_blockout(&dsp, &block) == 1) {
312
313 // analysis, assume we want to use bitrate management
314 err = vorbis_analysis(&block, NULL);
315 if (!err)
316 err = vorbis_bitrate_addblock(&block);
317
318 while (!err && vorbis_bitrate_flushpacket(&dsp, &packet)) {
319
320 // add the packet to the bitstream
321 err = ogg_stream_packetin(&stream, &packet);
322
323 // From vorbis-tools-1.0/oggenc/encode.c:
324 // If we've gone over a page boundary, we can do actual output,
325 // so do so (for however many pages are available).
326
327 while (!err && !eos) {
328 int result = ogg_stream_pageout(&stream, &page);
329 if (!result) {
330 break;
331 }
332
333 if ( outFile.Write(page.header, page.header_len).GetLastError() ||
334 outFile.Write(page.body, page.body_len).GetLastError()) {
335 // TODO: more precise message
338 }
339
340 if (ogg_page_eos(&page)) {
341 eos = 1;
342 }
343 }
344 }
345 }
346
347 if (err) {
348 updateResult = ProgressResult::Cancelled;
349 // TODO: more precise message
350 ShowExportErrorDialog("OGG:355");
351 break;
352 }
353
354 updateResult = progress.Poll(mixer->MixGetCurrentTime() - t0, t1 - t0);
355 }
356 }
357
358 if ( !outFile.Close() ) {
359 updateResult = ProgressResult::Cancelled;
360 // TODO: more precise message
361 ShowExportErrorDialog("OGG:366");
362 }
363
364 return updateResult;
365}
366
368{
369 S.AddWindow( safenew ExportOGGOptions{ S.GetParent(), format } );
370}
371
372bool ExportOGG::FillComment(AudacityProject *project, vorbis_comment *comment, const Tags *metadata)
373{
374 // Retrieve tags from project if not over-ridden
375 if (metadata == NULL)
376 metadata = &Tags::Get( *project );
377
378 vorbis_comment_init(comment);
379
380 wxString n;
381 for (const auto &pair : metadata->GetRange()) {
382 n = pair.first;
383 const auto &v = pair.second;
384 if (n == TAG_YEAR) {
385 n = wxT("DATE");
386 }
387 vorbis_comment_add_tag(comment,
388 (char *) (const char *) n.mb_str(wxConvUTF8),
389 (char *) (const char *) v.mb_str(wxConvUTF8));
390 }
391
392 return true;
393}
394
396 []{ return std::make_unique< ExportOGG >(); }
397};
398
399#endif // USE_LIBVORBIS
400
wxT("CloseDown"))
int AudacityMessageBox(const TranslatableString &message, const TranslatableString &caption, long style, wxWindow *parent, int x, int y)
void ShowExportErrorDialog(wxString ErrorCode, TranslatableString message, const TranslatableString &caption, bool allowReporting)
Definition: Export.cpp:1503
void ShowDiskFullExportErrorDialog(const wxFileNameWrapper &fileName)
Definition: Export.cpp:1516
#define SAMPLES_PER_RUN
Definition: ExportOGG.cpp:121
static Exporter::RegisteredExportPlugin sRegisteredPlugin
Definition: ExportOGG.cpp:395
int format
Definition: ExportPCM.cpp:53
XO("Cut/Copy/Paste")
XXO("&Cut/Copy/Paste Toolbar")
#define safenew
Definition: MemoryX.h:10
FileConfig * gPrefs
Definition: Prefs.cpp:70
an object holding per-project preferred sample rate
@ eIsCreatingFromPrefs
Definition: ShuttleGui.h:46
@ eIsSavingToPrefs
Definition: ShuttleGui.h:47
#define TAG_YEAR
Definition: Tags.h:62
#define S(N)
Definition: ToChars.cpp:64
declares abstract base class Track, TrackList, and iterators over TrackList
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:90
void OptionsCreate(ShuttleGui &S, int format) override
Definition: ExportOGG.cpp:367
ProgressResult Export(AudacityProject *project, std::unique_ptr< BasicUI::ProgressDialog > &pDialog, unsigned channels, const wxFileNameWrapper &fName, bool selectedOnly, double t0, double t1, MixerSpec *mixerSpec=NULL, const Tags *metadata=NULL, int subformat=0) override
called to export audio into a file.
Definition: ExportOGG.cpp:159
bool FillComment(AudacityProject *project, vorbis_comment *comment, const Tags *metadata)
Definition: ExportOGG.cpp:372
ExportOGGOptions(wxWindow *parent, int format)
Definition: ExportOGG.cpp:59
virtual ~ExportOGGOptions()
Definition: ExportOGG.cpp:70
bool TransferDataToWindow() override
Definition: ExportOGG.cpp:99
void PopulateOrExchange(ShuttleGui &S)
Definition: ExportOGG.cpp:77
bool TransferDataFromWindow() override
Definition: ExportOGG.cpp:106
void AddExtension(const FileExtension &extension, int index)
Definition: Export.cpp:126
int AddFormat()
Add a NEW entry to the list of formats this plug-in can export.
Definition: Export.cpp:100
static void InitProgress(std::unique_ptr< BasicUI::ProgressDialog > &pDialog, const TranslatableString &title, const TranslatableString &message)
Definition: Export.cpp:250
void SetFormat(const wxString &format, int index)
Definition: Export.cpp:116
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, MixerSpec *mixerSpec)
Definition: Export.cpp:222
void SetDescription(const TranslatableString &description, int index)
Definition: Export.cpp:121
void SetCanMetaData(bool canmetadata, int index)
Definition: Export.cpp:146
void SetMaxChannels(unsigned maxchannels, unsigned index)
Definition: Export.cpp:141
virtual bool Flush(bool bCurrentOnly=false) wxOVERRIDE
Definition: FileConfig.cpp:143
Definition: FileIO.h:22
@ Output
Definition: FileIO.h:27
bool Close()
Definition: FileIO.cpp:54
wxOutputStream & Write(const void *buffer, size_t size)
Definition: FileIO.cpp:77
bool IsOpened()
Definition: FileIO.cpp:49
A matrix of booleans, one row per input channel, column per output.
Definition: MixerOptions.h:32
static ProjectRate & Get(AudacityProject &project)
Definition: ProjectRate.cpp:28
double GetRate() const
Definition: ProjectRate.cpp:53
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI.
Definition: ShuttleGui.h:625
ID3 Tags (for MP3)
Definition: Tags.h:73
Iterators GetRange() const
Definition: Tags.cpp:436
static Tags & Get(AudacityProject &project)
Definition: Tags.cpp:214
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:487
ProgressResult
Definition: BasicUI.h:147