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 "../widgets/AudacityMessageBox.h"
35#include "../widgets/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<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<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(SAMPLES_PER_RUN);
289
290 int err;
291 if (samplesThisRun == 0) {
292 // Tell the library that we wrote 0 bytes - signalling the end.
293 err = vorbis_analysis_wrote(&dsp, 0);
294 }
295 else {
296
297 for (size_t i = 0; i < numChannels; i++) {
298 float *temp = (float *)mixer->GetBuffer(i);
299 memcpy(vorbis_buffer[i], temp, sizeof(float)*SAMPLES_PER_RUN);
300 }
301
302 // tell the encoder how many samples we have
303 err = vorbis_analysis_wrote(&dsp, samplesThisRun);
304 }
305
306 // I don't understand what this call does, so here is the comment
307 // from the example, verbatim:
308 //
309 // vorbis does some data preanalysis, then divvies up blocks
310 // for more involved (potentially parallel) processing. Get
311 // a single block for encoding now
312 while (!err && vorbis_analysis_blockout(&dsp, &block) == 1) {
313
314 // analysis, assume we want to use bitrate management
315 err = vorbis_analysis(&block, NULL);
316 if (!err)
317 err = vorbis_bitrate_addblock(&block);
318
319 while (!err && vorbis_bitrate_flushpacket(&dsp, &packet)) {
320
321 // add the packet to the bitstream
322 err = ogg_stream_packetin(&stream, &packet);
323
324 // From vorbis-tools-1.0/oggenc/encode.c:
325 // If we've gone over a page boundary, we can do actual output,
326 // so do so (for however many pages are available).
327
328 while (!err && !eos) {
329 int result = ogg_stream_pageout(&stream, &page);
330 if (!result) {
331 break;
332 }
333
334 if ( outFile.Write(page.header, page.header_len).GetLastError() ||
335 outFile.Write(page.body, page.body_len).GetLastError()) {
336 // TODO: more precise message
339 }
340
341 if (ogg_page_eos(&page)) {
342 eos = 1;
343 }
344 }
345 }
346 }
347
348 if (err) {
349 updateResult = ProgressResult::Cancelled;
350 // TODO: more precise message
351 ShowExportErrorDialog("OGG:355");
352 break;
353 }
354
355 updateResult = progress.Update(mixer->MixGetCurrentTime() - t0, t1 - t0);
356 }
357 }
358
359 if ( !outFile.Close() ) {
360 updateResult = ProgressResult::Cancelled;
361 // TODO: more precise message
362 ShowExportErrorDialog("OGG:366");
363 }
364
365 return updateResult;
366}
367
369{
370 S.AddWindow( safenew ExportOGGOptions{ S.GetParent(), format } );
371}
372
373bool ExportOGG::FillComment(AudacityProject *project, vorbis_comment *comment, const Tags *metadata)
374{
375 // Retrieve tags from project if not over-ridden
376 if (metadata == NULL)
377 metadata = &Tags::Get( *project );
378
379 vorbis_comment_init(comment);
380
381 wxString n;
382 for (const auto &pair : metadata->GetRange()) {
383 n = pair.first;
384 const auto &v = pair.second;
385 if (n == TAG_YEAR) {
386 n = wxT("DATE");
387 }
388 vorbis_comment_add_tag(comment,
389 (char *) (const char *) n.mb_str(wxConvUTF8),
390 (char *) (const char *) v.mb_str(wxConvUTF8));
391 }
392
393 return true;
394}
395
397 []{ return std::make_unique< ExportOGG >(); }
398};
399
400#endif // USE_LIBVORBIS
401
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:1520
void ShowDiskFullExportErrorDialog(const wxFileNameWrapper &fileName)
Definition: Export.cpp:1533
#define SAMPLES_PER_RUN
Definition: ExportOGG.cpp:121
static Exporter::RegisteredExportPlugin sRegisteredPlugin
Definition: ExportOGG.cpp:396
int format
Definition: ExportPCM.cpp:56
#define XXO(s)
Definition: Internat.h:44
#define XO(s)
Definition: Internat.h:31
#define safenew
Definition: MemoryX.h:10
FileConfig * gPrefs
Definition: Prefs.cpp:71
an object holding per-project preferred sample rate
@ floatSample
Definition: SampleFormat.h:34
@ eIsCreatingFromPrefs
Definition: ShuttleGui.h:48
@ eIsSavingToPrefs
Definition: ShuttleGui.h:49
#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:89
void OptionsCreate(ShuttleGui &S, int format) override
Definition: ExportOGG.cpp:368
bool FillComment(AudacityProject *project, vorbis_comment *comment, const Tags *metadata)
Definition: ExportOGG.cpp:373
ProgressResult Export(AudacityProject *project, std::unique_ptr< 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
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:127
int AddFormat()
Add a NEW entry to the list of formats this plug-in can export.
Definition: Export.cpp:101
void SetFormat(const wxString &format, int index)
Definition: Export.cpp:117
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:223
void SetDescription(const TranslatableString &description, int index)
Definition: Export.cpp:122
void SetCanMetaData(bool canmetadata, int index)
Definition: Export.cpp:147
static void InitProgress(std::unique_ptr< ProgressDialog > &pDialog, const TranslatableString &title, const TranslatableString &message)
Definition: Export.cpp:251
void SetMaxChannels(unsigned maxchannels, unsigned index)
Definition: Export.cpp:142
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
Class used with Mixer.
Definition: Mix.h:37
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:631
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:467
ProgressResult
Definition: BasicUI.h:145