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 double quality = ExportPluginHelpers::GetParameterValue(parameters, 0, 5) / 10.0;
201
202 wxLogNull logNo; // temporarily disable wxWidgets error messages
203
204 // Many of the library functions called below return 0 for success and
205 // various nonzero codes for failure.
206
207 // Encoding setup
208
209 vorbis_info_init(&context.info);
210
211 if (vorbis_encode_init_vbr(&context.info, numChannels, (int)(sampleRate + 0.5), quality)) {
212 // TODO: more precise message
213 throw ExportException(_("Unable to export - rate or quality problem"));
214 }
215
216 context.outFile = std::make_unique<FileIO>(fName, FileIO::Output);
217
218 if (!context.outFile->IsOpened()) {
219 throw ExportException(_("Unable to open target file for writing"));
220 }
221
222 context.analysis_state_ok = vorbis_analysis_init(&context.dsp, &context.info) == 0 &&
223 vorbis_block_init(&context.dsp, &context.block) == 0;
224 // Set up analysis state and auxiliary encoding storage
225 if (!context.analysis_state_ok) {
226 throw ExportException(_("Unable to export - problem initialising"));
227 }
228
229 // Retrieve tags
230 FillComment(&project, &context.comment, metadata);
231
232 // Set up packet->stream encoder. According to encoder example,
233 // a random serial number makes it more likely that you can make
234 // chained streams with concatenation.
235 srand(time(NULL));
236 context.stream_ok = ogg_stream_init(&context.stream, rand()) == 0;
237 if (!context.stream_ok) {
238 throw ExportException(_("Unable to export - problem creating stream"));
239 }
240
241 // First we need to write the required headers:
242 // 1. The Ogg bitstream header, which contains codec setup params
243 // 2. The Vorbis comment header
244 // 3. The bitstream codebook.
245 //
246 // After we create those our responsibility is complete, libvorbis will
247 // take care of any other ogg bitstream constraints (again, according
248 // to the example encoder source)
249 ogg_packet bitstream_header;
250 ogg_packet comment_header;
251 ogg_packet codebook_header;
252
253 if(vorbis_analysis_headerout(&context.dsp, &context.comment, &bitstream_header, &comment_header,
254 &codebook_header) ||
255 // Place these headers into the stream
256 ogg_stream_packetin(&context.stream, &bitstream_header) ||
257 ogg_stream_packetin(&context.stream, &comment_header) ||
258 ogg_stream_packetin(&context.stream, &codebook_header)) {
259 throw ExportException(_("Unable to export - problem with packets"));
260 }
261
262 // Flushing these headers now guarantees that audio data will
263 // start on a NEW page, which apparently makes streaming easier
264 while (ogg_stream_flush(&context.stream, &context.page)) {
265 if ( context.outFile->Write(context.page.header, context.page.header_len).GetLastError() ||
266 context.outFile->Write(context.page.body, context.page.body_len).GetLastError()) {
267 throw ExportException(_("Unable to export - problem with file"));
268 }
269 }
270
272 project, selectionOnly, t0, t1, numChannels, SAMPLES_PER_RUN, false,
273 sampleRate, floatSample, mixerSpec);
274
275 context.status = selectionOnly
276 ? XO("Exporting the selected audio as Ogg Vorbis")
277 : XO("Exporting the audio as Ogg Vorbis");
278
279 return true;
280}
281
283{
284 delegate.SetStatusString(context.status);
285 auto exportResult = ExportResult::Success;
286 {
287 int err;
288 int eos = 0;
289 while (exportResult == ExportResult::Success && !eos) {
290 float **vorbis_buffer = vorbis_analysis_buffer(&context.dsp, SAMPLES_PER_RUN);
291 auto samplesThisRun = context.mixer->Process();
292
293 if (samplesThisRun == 0) {
294 // Tell the library that we wrote 0 bytes - signalling the end.
295 err = vorbis_analysis_wrote(&context.dsp, 0);
296 }
297 else {
298
299 for (size_t i = 0; i < context.numChannels; i++) {
300 float *temp = (float *)context.mixer->GetBuffer(i);
301 memcpy(vorbis_buffer[i], temp, sizeof(float)*SAMPLES_PER_RUN);
302 }
303
304 // tell the encoder how many samples we have
305 err = vorbis_analysis_wrote(&context.dsp, samplesThisRun);
306 }
307
308 // I don't understand what this call does, so here is the comment
309 // from the example, verbatim:
310 //
311 // vorbis does some data preanalysis, then divvies up blocks
312 // for more involved (potentially parallel) processing. Get
313 // a single block for encoding now
314 while (!err && vorbis_analysis_blockout(&context.dsp, &context.block) == 1) {
315
316 // analysis, assume we want to use bitrate management
317 err = vorbis_analysis(&context.block, NULL);
318 if (!err)
319 err = vorbis_bitrate_addblock(&context.block);
320
321 while (!err && vorbis_bitrate_flushpacket(&context.dsp, &context.packet)) {
322
323 // add the packet to the bitstream
324 err = ogg_stream_packetin(&context.stream, &context.packet);
325
326 // From vorbis-tools-1.0/oggenc/encode.c:
327 // If we've gone over a page boundary, we can do actual output,
328 // so do so (for however many pages are available).
329
330 while (!err && !eos) {
331 int result = ogg_stream_pageout(&context.stream, &context.page);
332 if (!result) {
333 break;
334 }
335
336 if ( context.outFile->Write(context.page.header, context.page.header_len).GetLastError() ||
337 context.outFile->Write(context.page.body, context.page.body_len).GetLastError()) {
338 // TODO: more precise message
339 throw ExportDiskFullError(context.fName);
340 }
341
342 if (ogg_page_eos(&context.page)) {
343 eos = 1;
344 }
345 }
346 }
347 }
348
349 if (err) {
350 // TODO: more precise message
351 throw ExportErrorException("OGG:355");
352 }
354 delegate, *context.mixer, context.t0, context.t1);
355 }
356 }
357
358 if ( !context.outFile->Close() ) {
359 // TODO: more precise message
360 throw ExportErrorException("OGG:366");
361 }
362
363 return exportResult;
364}
365
366
367std::unique_ptr<ExportOptionsEditor>
369{
370 return std::make_unique<ExportOptionOGGEditor>();
371}
372
373std::unique_ptr<ExportProcessor> ExportOGG::CreateProcessor(int format) const
374{
375 return std::make_unique<OGGExportProcessor>();
376}
377
378
379void OGGExportProcessor::FillComment(AudacityProject *project, vorbis_comment *comment, const Tags *metadata)
380{
381 // Retrieve tags from project if not over-ridden
382 if (metadata == NULL)
383 metadata = &Tags::Get( *project );
384
385 vorbis_comment_init(comment);
386
387 wxString n;
388 for (const auto &pair : metadata->GetRange()) {
389 n = pair.first;
390 const auto &v = pair.second;
391 if (n == TAG_YEAR) {
392 n = wxT("DATE");
393 }
394 vorbis_comment_add_tag(comment,
395 (char *) (const char *) n.mb_str(wxConvUTF8),
396 (char *) (const char *) v.mb_str(wxConvUTF8));
397 }
398}
399
401 []{ return std::make_unique< ExportOGG >(); }
402};
wxT("CloseDown"))
#define SAMPLES_PER_RUN
Definition: ExportOGG.cpp:100
static ExportPluginRegistry::RegisteredPlugin sRegisteredPlugin
Definition: ExportOGG.cpp:400
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 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:368
int GetFormatCount() const override
Definition: ExportOGG.cpp:160
std::unique_ptr< ExportProcessor > CreateProcessor(int format) const override
Definition: ExportOGG.cpp:373
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 AudacityProject &project, 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
struct OGGExportProcessor::@177 context
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:379
ExportResult Process(ExportProcessorDelegate &delegate) override
Definition: ExportOGG.cpp:282
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
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
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