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#include "Export.h"
15
16#include <wx/log.h>
17#include <wx/stream.h>
18
19#include <vorbis/vorbisenc.h>
20
21#include "wxFileNameWrapper.h"
22#include "ExportPluginHelpers.h"
24#include "FileIO.h"
25#include "Mix.h"
26
27#include "Tags.h"
28#include "Track.h"
29
31
32namespace
33{
34 enum : int {
36 };
37
39 OptionIDOGGQuality, XO("Quality"),
40 5,
42 { 0, 10 }
43 };
44
46 {
48 public:
49
51 {
52 mQualityUnscaled = *std::get_if<int>(&OGGQualityOption.defaultValue);
53 }
54
55 int GetOptionsCount() const override
56 {
57 return 1;
58 }
59
60 bool GetOption(int, ExportOption& option) const override
61 {
62 option = OGGQualityOption;
63 return true;
64 }
65
66 bool GetValue(ExportOptionID, ExportValue& value) const override
67 {
68 value = mQualityUnscaled;
69 return true;
70 }
71
72 bool SetValue(ExportOptionID, const ExportValue& value) override
73 {
74 if(auto num = std::get_if<int>(&value))
75 {
76 mQualityUnscaled = *num;
77 return true;
78 }
79 return false;
80 }
81
83 {
84 return {};
85 }
86
87 void Load(const audacity::BasicSettings& config) override
88 {
89 mQualityUnscaled = config.Read(wxT("/FileFormats/OggExportQuality"),50)/10;
90 }
91
92 void Store(audacity::BasicSettings& config) const override
93 {
94 config.Write(wxT("/FileFormats/OggExportQuality"), mQualityUnscaled * 10);
95 }
96
97 };
98}
99
100#define SAMPLES_PER_RUN 8192u
101
103{
104 struct
105 {
107 double t0;
108 double t1;
109 unsigned numChannels;
110 std::unique_ptr<Mixer> mixer;
111 std::unique_ptr<FileIO> outFile;
113
114 // All the Ogg and Vorbis encoding data
115 ogg_stream_state stream;
116 ogg_page page;
117 ogg_packet packet;
118
119 vorbis_info info;
120 vorbis_comment comment;
121 vorbis_dsp_state dsp;
122 vorbis_block block;
123 bool stream_ok{false};
124 bool analysis_state_ok{false};
126public:
127 ~OGGExportProcessor() override;
128
130 const Parameters& parameters,
131 const wxFileNameWrapper& filename,
132 double t0, double t1, bool selectedOnly,
133 double sampleRate, unsigned channels,
134 MixerOptions::Downmix* mixerSpec,
135 const Tags* tags) override;
136
137 ExportResult Process(ExportProcessorDelegate& delegate) override;
138
139private:
140 static void FillComment(AudacityProject *project, vorbis_comment *comment, const Tags *metadata);
141};
142
143class ExportOGG final : public ExportPlugin
144{
145public:
146
148
149 int GetFormatCount() const override;
150 FormatInfo GetFormatInfo(int) const override;
151
152 std::unique_ptr<ExportOptionsEditor>
154
155 std::unique_ptr<ExportProcessor> CreateProcessor(int format) const override;
156};
157
158ExportOGG::ExportOGG() = default;
159
161{
162 return 1;
163}
164
166{
167 return {
168 wxT("OGG"), XO("Ogg Vorbis Files"), { wxT("ogg") }, 255, true
169 };
170}
171
173{
174 if(context.stream_ok)
175 ogg_stream_clear(&context.stream);
176
177 if(context.analysis_state_ok)
178 {
179 vorbis_comment_clear(&context.comment);
180 vorbis_block_clear(&context.block);
181 vorbis_dsp_clear(&context.dsp);
182 }
183
184 vorbis_info_clear(&context.info);
185}
186
187
189 const Parameters& parameters,
190 const wxFileNameWrapper& fName,
191 double t0, double t1, bool selectionOnly,
192 double sampleRate, unsigned numChannels,
193 MixerOptions::Downmix* mixerSpec,
194 const Tags* metadata)
195{
196 context.t0 = t0;
197 context.t1 = t1;
198 context.numChannels = numChannels;
199
200 const auto &tracks = TrackList::Get( project );
201 double quality = ExportPluginHelpers::GetParameterValue(parameters, 0, 5) / 10.0;
202
203 wxLogNull logNo; // temporarily disable wxWidgets error messages
204
205 // Many of the library functions called below return 0 for success and
206 // various nonzero codes for failure.
207
208 // Encoding setup
209
210 vorbis_info_init(&context.info);
211
212 if (vorbis_encode_init_vbr(&context.info, numChannels, (int)(sampleRate + 0.5), quality)) {
213 // TODO: more precise message
214 throw ExportException(_("Unable to export - rate or quality problem"));
215 }
216
217 context.outFile = std::make_unique<FileIO>(fName, FileIO::Output);
218
219 if (!context.outFile->IsOpened()) {
220 throw ExportException(_("Unable to open target file for writing"));
221 }
222
223 context.analysis_state_ok = vorbis_analysis_init(&context.dsp, &context.info) == 0 &&
224 vorbis_block_init(&context.dsp, &context.block) == 0;
225 // Set up analysis state and auxiliary encoding storage
226 if (!context.analysis_state_ok) {
227 throw ExportException(_("Unable to export - problem initialising"));
228 }
229
230 // Retrieve tags
231 FillComment(&project, &context.comment, metadata);
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 context.stream_ok = ogg_stream_init(&context.stream, rand()) == 0;
238 if (!context.stream_ok) {
239 throw ExportException(_("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(&context.dsp, &context.comment, &bitstream_header, &comment_header,
255 &codebook_header) ||
256 // Place these headers into the stream
257 ogg_stream_packetin(&context.stream, &bitstream_header) ||
258 ogg_stream_packetin(&context.stream, &comment_header) ||
259 ogg_stream_packetin(&context.stream, &codebook_header)) {
260 throw ExportException(_("Unable to export - problem with packets"));
261 }
262
263 // Flushing these headers now guarantees that audio data will
264 // start on a NEW page, which apparently makes streaming easier
265 while (ogg_stream_flush(&context.stream, &context.page)) {
266 if ( context.outFile->Write(context.page.header, context.page.header_len).GetLastError() ||
267 context.outFile->Write(context.page.body, context.page.body_len).GetLastError()) {
268 throw ExportException(_("Unable to export - problem with file"));
269 }
270 }
271
272 context.mixer = ExportPluginHelpers::CreateMixer(tracks, selectionOnly,
273 t0, t1,
275 sampleRate, floatSample, mixerSpec);
276
277 context.status = selectionOnly
278 ? XO("Exporting the selected audio as Ogg Vorbis")
279 : XO("Exporting the audio as Ogg Vorbis");
280
281 return true;
282}
283
285{
286 delegate.SetStatusString(context.status);
287 auto exportResult = ExportResult::Success;
288 {
289 int err;
290 int eos = 0;
291 while (exportResult == ExportResult::Success && !eos) {
292 float **vorbis_buffer = vorbis_analysis_buffer(&context.dsp, SAMPLES_PER_RUN);
293 auto samplesThisRun = context.mixer->Process();
294
295 if (samplesThisRun == 0) {
296 // Tell the library that we wrote 0 bytes - signalling the end.
297 err = vorbis_analysis_wrote(&context.dsp, 0);
298 }
299 else {
300
301 for (size_t i = 0; i < context.numChannels; i++) {
302 float *temp = (float *)context.mixer->GetBuffer(i);
303 memcpy(vorbis_buffer[i], temp, sizeof(float)*SAMPLES_PER_RUN);
304 }
305
306 // tell the encoder how many samples we have
307 err = vorbis_analysis_wrote(&context.dsp, samplesThisRun);
308 }
309
310 // I don't understand what this call does, so here is the comment
311 // from the example, verbatim:
312 //
313 // vorbis does some data preanalysis, then divvies up blocks
314 // for more involved (potentially parallel) processing. Get
315 // a single block for encoding now
316 while (!err && vorbis_analysis_blockout(&context.dsp, &context.block) == 1) {
317
318 // analysis, assume we want to use bitrate management
319 err = vorbis_analysis(&context.block, NULL);
320 if (!err)
321 err = vorbis_bitrate_addblock(&context.block);
322
323 while (!err && vorbis_bitrate_flushpacket(&context.dsp, &context.packet)) {
324
325 // add the packet to the bitstream
326 err = ogg_stream_packetin(&context.stream, &context.packet);
327
328 // From vorbis-tools-1.0/oggenc/encode.c:
329 // If we've gone over a page boundary, we can do actual output,
330 // so do so (for however many pages are available).
331
332 while (!err && !eos) {
333 int result = ogg_stream_pageout(&context.stream, &context.page);
334 if (!result) {
335 break;
336 }
337
338 if ( context.outFile->Write(context.page.header, context.page.header_len).GetLastError() ||
339 context.outFile->Write(context.page.body, context.page.body_len).GetLastError()) {
340 // TODO: more precise message
341 throw ExportDiskFullError(context.fName);
342 }
343
344 if (ogg_page_eos(&context.page)) {
345 eos = 1;
346 }
347 }
348 }
349 }
350
351 if (err) {
352 // TODO: more precise message
353 throw ExportErrorException("OGG:355");
354 }
356 delegate, *context.mixer, context.t0, context.t1);
357 }
358 }
359
360 if ( !context.outFile->Close() ) {
361 // TODO: more precise message
362 throw ExportErrorException("OGG:366");
363 }
364
365 return exportResult;
366}
367
368
369std::unique_ptr<ExportOptionsEditor>
371{
372 return std::make_unique<ExportOptionOGGEditor>();
373}
374
375std::unique_ptr<ExportProcessor> ExportOGG::CreateProcessor(int format) const
376{
377 return std::make_unique<OGGExportProcessor>();
378}
379
380
381void OGGExportProcessor::FillComment(AudacityProject *project, vorbis_comment *comment, const Tags *metadata)
382{
383 // Retrieve tags from project if not over-ridden
384 if (metadata == NULL)
385 metadata = &Tags::Get( *project );
386
387 vorbis_comment_init(comment);
388
389 wxString n;
390 for (const auto &pair : metadata->GetRange()) {
391 n = pair.first;
392 const auto &v = pair.second;
393 if (n == TAG_YEAR) {
394 n = wxT("DATE");
395 }
396 vorbis_comment_add_tag(comment,
397 (char *) (const char *) n.mb_str(wxConvUTF8),
398 (char *) (const char *) v.mb_str(wxConvUTF8));
399 }
400}
401
403 []{ return std::make_unique< ExportOGG >(); }
404};
wxT("CloseDown"))
#define SAMPLES_PER_RUN
Definition: ExportOGG.cpp:100
static ExportPluginRegistry::RegisteredPlugin sRegisteredPlugin
Definition: ExportOGG.cpp:402
int ExportOptionID
Definition: ExportTypes.h:21
std::variant< bool, int, double, std::string > ExportValue
A type of option values (parameters) used by exporting plugins.
Definition: ExportTypes.h:38
ExportResult
Definition: ExportTypes.h:24
XO("Cut/Copy/Paste")
#define _(s)
Definition: Internat.h:73
#define TAG_YEAR
Definition: Tags.h:62
const auto tracks
const auto project
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
FormatInfo GetFormatInfo(int) const override
Returns FormatInfo structure for given index if it's valid, or a default one. FormatInfo::format isn'...
Definition: ExportOGG.cpp:165
std::unique_ptr< ExportOptionsEditor > CreateOptionsEditor(int, ExportOptionsEditor::Listener *) const override
Creates format-dependent options editor, that is used to create a valid set of parameters to be used ...
Definition: ExportOGG.cpp:370
int GetFormatCount() const override
Definition: ExportOGG.cpp:160
std::unique_ptr< ExportProcessor > CreateProcessor(int format) const override
Definition: ExportOGG.cpp:375
Listener object that is used to report on option changes.
Editor objects are used to retrieve a set of export options, and configure exporting parameters accor...
std::vector< int > SampleRateList
static T GetParameterValue(const ExportProcessor::Parameters &parameters, int id, T defaultValue=T())
static ExportResult UpdateProgress(ExportProcessorDelegate &delegate, Mixer &mixer, double t0, double t1)
Sends progress update to delegate and retrieves state update from it. Typically used inside each expo...
static 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, MixerOptions::Downmix *mixerSpec)
virtual void SetStatusString(const TranslatableString &str)=0
std::vector< std::tuple< ExportOptionID, ExportValue > > Parameters
Definition: ExportPlugin.h:93
@ Output
Definition: FileIO.h:27
A matrix of booleans, one row per input channel, column per output.
Definition: MixerOptions.h:32
~OGGExportProcessor() override
Definition: ExportOGG.cpp:172
std::unique_ptr< Mixer > mixer
Definition: ExportOGG.cpp:110
vorbis_info info
Definition: ExportOGG.cpp:119
vorbis_block block
Definition: ExportOGG.cpp:122
vorbis_dsp_state dsp
Definition: ExportOGG.cpp:121
static void FillComment(AudacityProject *project, vorbis_comment *comment, const Tags *metadata)
Definition: ExportOGG.cpp:381
ExportResult Process(ExportProcessorDelegate &delegate) override
Definition: ExportOGG.cpp:284
ogg_packet packet
Definition: ExportOGG.cpp:117
unsigned numChannels
Definition: ExportOGG.cpp:109
bool Initialize(AudacityProject &project, const Parameters &parameters, const wxFileNameWrapper &filename, double t0, double t1, bool selectedOnly, double sampleRate, unsigned channels, MixerOptions::Downmix *mixerSpec, const Tags *tags) override
Called before start processing.
Definition: ExportOGG.cpp:188
wxFileNameWrapper fName
Definition: ExportOGG.cpp:112
struct OGGExportProcessor::@166 context
vorbis_comment comment
Definition: ExportOGG.cpp:120
ogg_stream_state stream
Definition: ExportOGG.cpp:115
TranslatableString status
Definition: ExportOGG.cpp:106
std::unique_ptr< FileIO > outFile
Definition: ExportOGG.cpp:111
ID3 Tags (for MP3)
Definition: Tags.h:73
Iterators GetRange() const
Definition: Tags.cpp:426
static Tags & Get(AudacityProject &project)
Definition: Tags.cpp:214
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:347
Holds a msgid for the translation catalog; may also bind format arguments.
bool GetOption(int, ExportOption &option) const override
Definition: ExportOGG.cpp:60
bool GetValue(ExportOptionID, ExportValue &value) const override
Definition: ExportOGG.cpp:66
void Store(audacity::BasicSettings &config) const override
Definition: ExportOGG.cpp:92
bool SetValue(ExportOptionID, const ExportValue &value) override
Definition: ExportOGG.cpp:72
void Load(const audacity::BasicSettings &config) override
Definition: ExportOGG.cpp:87
Base class for objects that provide facility to store data persistently, and access it with string ke...
Definition: BasicSettings.h:31
virtual bool Write(const wxString &key, bool value)=0
virtual bool Read(const wxString &key, bool *value) const =0
const ExportOption OGGQualityOption
Definition: ExportOGG.cpp:38
A type that provides a description of an exporting option. Isn't allowed to change except non-type re...
Definition: ExportTypes.h:43
@ TypeRange
Range option. values holds [min, max].
Definition: ExportTypes.h:47
ExportValue defaultValue
Default valid value for the parameter.
Definition: ExportTypes.h:58