OpenShot Audio Library | OpenShotAudio 0.4.0
 
Loading...
Searching...
No Matches
juce_OggVorbisAudioFormat.cpp
1/*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2022 - Raw Material Software Limited
6
7 JUCE is an open source library subject to commercial or open-source
8 licensing.
9
10 By using JUCE, you agree to the terms of both the JUCE 7 End-User License
11 Agreement and JUCE Privacy Policy.
12
13 End User License Agreement: www.juce.com/juce-7-licence
14 Privacy Policy: www.juce.com/juce-privacy-policy
15
16 Or: You may also use this code under the terms of the GPL v3 (see
17 www.gnu.org/licenses).
18
19 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21 DISCLAIMED.
22
23 ==============================================================================
24*/
25
26namespace juce
27{
28
29#if JUCE_USE_OGGVORBIS
30
31#if JUCE_MAC && ! defined (__MACOSX__)
32 #define __MACOSX__ 1
33#endif
34
35namespace OggVorbisNamespace
36{
37#if JUCE_INCLUDE_OGGVORBIS_CODE || ! defined (JUCE_INCLUDE_OGGVORBIS_CODE)
38 JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4267 4127 4244 4996 4100 4701 4702 4013 4133 4206 4305 4189 4706 4995 4365 4456 4457 4459 6297 6011 6001 6308 6255 6386 6385 6246 6387 6263 6262 28182)
39
40 JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wcast-align",
41 "-Wconversion",
42 "-Wdeprecated-declarations",
43 "-Wdeprecated-register",
44 "-Wfloat-conversion",
45 "-Wfloat-equal",
46 "-Wmaybe-uninitialized",
47 "-Wmisleading-indentation",
48 "-Wmissing-prototypes",
49 "-Wredundant-decls",
50 "-Wshadow",
51 "-Wsign-conversion",
52 "-Wswitch-default",
53 "-Wswitch-enum",
54 "-Wzero-as-null-pointer-constant")
55 JUCE_BEGIN_NO_SANITIZE ("undefined")
56
57 #include "oggvorbis/vorbisenc.h"
58 #include "oggvorbis/codec.h"
59 #include "oggvorbis/vorbisfile.h"
60
61 #include "oggvorbis/bitwise.c"
62 #include "oggvorbis/framing.c"
63 #include "oggvorbis/libvorbis-1.3.7/lib/analysis.c"
64 #include "oggvorbis/libvorbis-1.3.7/lib/bitrate.c"
65 #include "oggvorbis/libvorbis-1.3.7/lib/block.c"
66 #include "oggvorbis/libvorbis-1.3.7/lib/codebook.c"
67 #include "oggvorbis/libvorbis-1.3.7/lib/envelope.c"
68 #include "oggvorbis/libvorbis-1.3.7/lib/floor0.c"
69 #include "oggvorbis/libvorbis-1.3.7/lib/floor1.c"
70 #include "oggvorbis/libvorbis-1.3.7/lib/info.c"
71 #include "oggvorbis/libvorbis-1.3.7/lib/lpc.c"
72 #include "oggvorbis/libvorbis-1.3.7/lib/lsp.c"
73 #include "oggvorbis/libvorbis-1.3.7/lib/mapping0.c"
74 #include "oggvorbis/libvorbis-1.3.7/lib/mdct.c"
75 #include "oggvorbis/libvorbis-1.3.7/lib/psy.c"
76 #include "oggvorbis/libvorbis-1.3.7/lib/registry.c"
77 #include "oggvorbis/libvorbis-1.3.7/lib/res0.c"
78 #include "oggvorbis/libvorbis-1.3.7/lib/sharedbook.c"
79 #include "oggvorbis/libvorbis-1.3.7/lib/smallft.c"
80 #include "oggvorbis/libvorbis-1.3.7/lib/synthesis.c"
81 #include "oggvorbis/libvorbis-1.3.7/lib/vorbisenc.c"
82 #include "oggvorbis/libvorbis-1.3.7/lib/vorbisfile.c"
83 #include "oggvorbis/libvorbis-1.3.7/lib/window.c"
84
85 JUCE_END_NO_SANITIZE
86 JUCE_END_IGNORE_WARNINGS_MSVC
87 JUCE_END_IGNORE_WARNINGS_GCC_LIKE
88#else
89 #include <vorbis/vorbisenc.h>
90 #include <vorbis/codec.h>
91 #include <vorbis/vorbisfile.h>
92#endif
93}
94
95#undef max
96#undef min
97
98//==============================================================================
99static const char* const oggFormatName = "Ogg-Vorbis file";
100
101const char* const OggVorbisAudioFormat::encoderName = "encoder";
102const char* const OggVorbisAudioFormat::id3title = "id3title";
103const char* const OggVorbisAudioFormat::id3artist = "id3artist";
104const char* const OggVorbisAudioFormat::id3album = "id3album";
105const char* const OggVorbisAudioFormat::id3comment = "id3comment";
106const char* const OggVorbisAudioFormat::id3date = "id3date";
107const char* const OggVorbisAudioFormat::id3genre = "id3genre";
108const char* const OggVorbisAudioFormat::id3trackNumber = "id3trackNumber";
109
110
111//==============================================================================
112class OggReader final : public AudioFormatReader
113{
114public:
115 OggReader (InputStream* inp) : AudioFormatReader (inp, oggFormatName)
116 {
117 sampleRate = 0;
118 usesFloatingPointData = true;
119
120 callbacks.read_func = &oggReadCallback;
121 callbacks.seek_func = &oggSeekCallback;
122 callbacks.close_func = &oggCloseCallback;
123 callbacks.tell_func = &oggTellCallback;
124
125 auto err = ov_open_callbacks (input, &ovFile, nullptr, 0, callbacks);
126
127 if (err == 0)
128 {
129 auto* info = ov_info (&ovFile, -1);
130
131 auto* comment = ov_comment (&ovFile, -1);
132 addMetadataItem (comment, "ENCODER", OggVorbisAudioFormat::encoderName);
133 addMetadataItem (comment, "TITLE", OggVorbisAudioFormat::id3title);
134 addMetadataItem (comment, "ARTIST", OggVorbisAudioFormat::id3artist);
135 addMetadataItem (comment, "ALBUM", OggVorbisAudioFormat::id3album);
136 addMetadataItem (comment, "COMMENT", OggVorbisAudioFormat::id3comment);
137 addMetadataItem (comment, "DATE", OggVorbisAudioFormat::id3date);
138 addMetadataItem (comment, "GENRE", OggVorbisAudioFormat::id3genre);
139 addMetadataItem (comment, "TRACKNUMBER", OggVorbisAudioFormat::id3trackNumber);
140
141 lengthInSamples = (uint32) ov_pcm_total (&ovFile, -1);
142 numChannels = (unsigned int) info->channels;
143 bitsPerSample = 16;
144 sampleRate = (double) info->rate;
145
146 reservoir.setSize ((int) numChannels, (int) jmin (lengthInSamples, (int64) 4096));
147 }
148 }
149
150 ~OggReader() override
151 {
152 ov_clear (&ovFile);
153 }
154
155 void addMetadataItem (OggVorbisNamespace::vorbis_comment* comment, const char* name, const char* metadataName)
156 {
157 if (auto* value = vorbis_comment_query (comment, name, 0))
158 metadataValues.set (metadataName, value);
159 }
160
161 //==============================================================================
162 bool readSamples (int* const* destSamples, int numDestChannels, int startOffsetInDestBuffer,
163 int64 startSampleInFile, int numSamples) override
164 {
165 const auto getBufferedRange = [this] { return bufferedRange; };
166
167 const auto readFromReservoir = [this, &destSamples, &numDestChannels, &startOffsetInDestBuffer, &startSampleInFile] (const Range<int64> rangeToRead)
168 {
169 const auto bufferIndices = rangeToRead - bufferedRange.getStart();
170 const auto writePos = (int64) startOffsetInDestBuffer + (rangeToRead.getStart() - startSampleInFile);
171
172 for (int i = jmin (numDestChannels, reservoir.getNumChannels()); --i >= 0;)
173 if (destSamples[i] != nullptr)
174 memcpy (destSamples[i] + writePos,
175 reservoir.getReadPointer (i) + bufferIndices.getStart(),
176 (size_t) bufferIndices.getLength() * sizeof (float));
177 };
178
179 const auto fillReservoir = [this] (int64 requestedStart)
180 {
181 const auto newStart = jmax ((int64) 0, requestedStart);
182 bufferedRange = Range<int64> { newStart, newStart + reservoir.getNumSamples() };
183
184 if (bufferedRange.getStart() != ov_pcm_tell (&ovFile))
185 ov_pcm_seek (&ovFile, bufferedRange.getStart());
186
187 int bitStream = 0;
188 int offset = 0;
189 int numToRead = (int) bufferedRange.getLength();
190
191 while (numToRead > 0)
192 {
193 float** dataIn = nullptr;
194 auto samps = static_cast<int> (ov_read_float (&ovFile, &dataIn, numToRead, &bitStream));
195
196 if (samps <= 0)
197 break;
198
199 jassert (samps <= numToRead);
200
201 for (int i = jmin ((int) numChannels, reservoir.getNumChannels()); --i >= 0;)
202 memcpy (reservoir.getWritePointer (i, offset), dataIn[i], (size_t) samps * sizeof (float));
203
204 numToRead -= samps;
205 offset += samps;
206 }
207
208 if (numToRead > 0)
209 reservoir.clear (offset, numToRead);
210 };
211
212 const auto remainingSamples = Reservoir::doBufferedRead (Range<int64> { startSampleInFile, startSampleInFile + numSamples },
213 getBufferedRange,
214 readFromReservoir,
215 fillReservoir);
216
217 if (! remainingSamples.isEmpty())
218 for (int i = numDestChannels; --i >= 0;)
219 if (destSamples[i] != nullptr)
220 zeromem (destSamples[i] + startOffsetInDestBuffer + (remainingSamples.getStart() - startSampleInFile),
221 (size_t) remainingSamples.getLength() * sizeof (int));
222
223 return true;
224 }
225
226 //==============================================================================
227 static size_t oggReadCallback (void* ptr, size_t size, size_t nmemb, void* datasource)
228 {
229 return (size_t) (static_cast<InputStream*> (datasource)->read (ptr, (int) (size * nmemb))) / size;
230 }
231
232 static int oggSeekCallback (void* datasource, OggVorbisNamespace::ogg_int64_t offset, int whence)
233 {
234 auto* in = static_cast<InputStream*> (datasource);
235
236 if (whence == SEEK_CUR)
237 offset += in->getPosition();
238 else if (whence == SEEK_END)
239 offset += in->getTotalLength();
240
241 in->setPosition (offset);
242 return 0;
243 }
244
245 static int oggCloseCallback (void*)
246 {
247 return 0;
248 }
249
250 static long oggTellCallback (void* datasource)
251 {
252 return (long) static_cast<InputStream*> (datasource)->getPosition();
253 }
254
255private:
256 OggVorbisNamespace::OggVorbis_File ovFile;
257 OggVorbisNamespace::ov_callbacks callbacks;
258 AudioBuffer<float> reservoir;
259 Range<int64> bufferedRange;
260
261 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OggReader)
262};
263
264//==============================================================================
265class OggWriter final : public AudioFormatWriter
266{
267public:
268 OggWriter (OutputStream* out, double rate,
269 unsigned int numChans, unsigned int bitsPerSamp,
270 int qualityIndex, const StringPairArray& metadata)
271 : AudioFormatWriter (out, oggFormatName, rate, numChans, bitsPerSamp)
272 {
273 vorbis_info_init (&vi);
274
275 if (vorbis_encode_init_vbr (&vi, (int) numChans, (int) rate,
276 jlimit (0.0f, 1.0f, (float) qualityIndex * 0.1f)) == 0)
277 {
278 vorbis_comment_init (&vc);
279
280 addMetadata (metadata, OggVorbisAudioFormat::encoderName, "ENCODER");
281 addMetadata (metadata, OggVorbisAudioFormat::id3title, "TITLE");
282 addMetadata (metadata, OggVorbisAudioFormat::id3artist, "ARTIST");
283 addMetadata (metadata, OggVorbisAudioFormat::id3album, "ALBUM");
284 addMetadata (metadata, OggVorbisAudioFormat::id3comment, "COMMENT");
285 addMetadata (metadata, OggVorbisAudioFormat::id3date, "DATE");
286 addMetadata (metadata, OggVorbisAudioFormat::id3genre, "GENRE");
287 addMetadata (metadata, OggVorbisAudioFormat::id3trackNumber, "TRACKNUMBER");
288
289 vorbis_analysis_init (&vd, &vi);
290 vorbis_block_init (&vd, &vb);
291
292 ogg_stream_init (&os, Random::getSystemRandom().nextInt());
293
294 OggVorbisNamespace::ogg_packet header, header_comm, header_code;
295 vorbis_analysis_headerout (&vd, &vc, &header, &header_comm, &header_code);
296
297 ogg_stream_packetin (&os, &header);
298 ogg_stream_packetin (&os, &header_comm);
299 ogg_stream_packetin (&os, &header_code);
300
301 for (;;)
302 {
303 if (ogg_stream_flush (&os, &og) == 0)
304 break;
305
306 output->write (og.header, (size_t) og.header_len);
307 output->write (og.body, (size_t) og.body_len);
308 }
309
310 ok = true;
311 }
312 }
313
314 ~OggWriter() override
315 {
316 if (ok)
317 {
318 // write a zero-length packet to show ogg that we're finished..
319 writeSamples (0);
320
321 ogg_stream_clear (&os);
322 vorbis_block_clear (&vb);
323 vorbis_dsp_clear (&vd);
324 vorbis_comment_clear (&vc);
325
326 vorbis_info_clear (&vi);
327 output->flush();
328 }
329 else
330 {
331 vorbis_info_clear (&vi);
332 output = nullptr; // to stop the base class deleting this, as it needs to be returned
333 // to the caller of createWriter()
334 }
335 }
336
337 //==============================================================================
338 bool write (const int** samplesToWrite, int numSamples) override
339 {
340 if (ok)
341 {
342 if (numSamples > 0)
343 {
344 const double gain = 1.0 / 0x80000000u;
345 float** const vorbisBuffer = vorbis_analysis_buffer (&vd, numSamples);
346
347 for (int i = (int) numChannels; --i >= 0;)
348 {
349 if (auto* dst = vorbisBuffer[i])
350 {
351 if (const int* src = samplesToWrite [i])
352 {
353 for (int j = 0; j < numSamples; ++j)
354 dst[j] = (float) (src[j] * gain);
355 }
356 }
357 }
358 }
359
360 writeSamples (numSamples);
361 }
362
363 return ok;
364 }
365
366 void writeSamples (int numSamples)
367 {
368 vorbis_analysis_wrote (&vd, numSamples);
369
370 while (vorbis_analysis_blockout (&vd, &vb) == 1)
371 {
372 vorbis_analysis (&vb, nullptr);
373 vorbis_bitrate_addblock (&vb);
374
375 while (vorbis_bitrate_flushpacket (&vd, &op))
376 {
377 ogg_stream_packetin (&os, &op);
378
379 for (;;)
380 {
381 if (ogg_stream_pageout (&os, &og) == 0)
382 break;
383
384 output->write (og.header, (size_t) og.header_len);
385 output->write (og.body, (size_t) og.body_len);
386
387 if (ogg_page_eos (&og))
388 break;
389 }
390 }
391 }
392 }
393
394 bool ok = false;
395
396private:
397 OggVorbisNamespace::ogg_stream_state os;
398 OggVorbisNamespace::ogg_page og;
399 OggVorbisNamespace::ogg_packet op;
400 OggVorbisNamespace::vorbis_info vi;
401 OggVorbisNamespace::vorbis_comment vc;
402 OggVorbisNamespace::vorbis_dsp_state vd;
403 OggVorbisNamespace::vorbis_block vb;
404
405 void addMetadata (const StringPairArray& metadata, const char* name, const char* vorbisName)
406 {
407 auto s = metadata [name];
408
409 if (s.isNotEmpty())
410 vorbis_comment_add_tag (&vc, vorbisName, const_cast<char*> (s.toRawUTF8()));
411 }
412
413 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OggWriter)
414};
415
416
417//==============================================================================
418OggVorbisAudioFormat::OggVorbisAudioFormat() : AudioFormat (oggFormatName, ".ogg")
419{
420}
421
422OggVorbisAudioFormat::~OggVorbisAudioFormat()
423{
424}
425
426Array<int> OggVorbisAudioFormat::getPossibleSampleRates()
427{
428 return { 8000, 11025, 12000, 16000, 22050, 32000,
429 44100, 48000, 88200, 96000, 176400, 192000 };
430}
431
432Array<int> OggVorbisAudioFormat::getPossibleBitDepths()
433{
434 return { 32 };
435}
436
437bool OggVorbisAudioFormat::canDoStereo() { return true; }
438bool OggVorbisAudioFormat::canDoMono() { return true; }
439bool OggVorbisAudioFormat::isCompressed() { return true; }
440
441AudioFormatReader* OggVorbisAudioFormat::createReaderFor (InputStream* in, bool deleteStreamIfOpeningFails)
442{
443 std::unique_ptr<OggReader> r (new OggReader (in));
444
445 if (r->sampleRate > 0)
446 return r.release();
447
448 if (! deleteStreamIfOpeningFails)
449 r->input = nullptr;
450
451 return nullptr;
452}
453
454AudioFormatWriter* OggVorbisAudioFormat::createWriterFor (OutputStream* out,
455 double sampleRate,
456 unsigned int numChannels,
457 int bitsPerSample,
458 const StringPairArray& metadataValues,
459 int qualityOptionIndex)
460{
461 if (out == nullptr)
462 return nullptr;
463
464 std::unique_ptr<OggWriter> w (new OggWriter (out, sampleRate, numChannels,
465 (unsigned int) bitsPerSample,
466 qualityOptionIndex, metadataValues));
467
468 return w->ok ? w.release() : nullptr;
469}
470
471StringArray OggVorbisAudioFormat::getQualityOptions()
472{
473 return { "64 kbps", "80 kbps", "96 kbps", "112 kbps", "128 kbps", "160 kbps",
474 "192 kbps", "224 kbps", "256 kbps", "320 kbps", "500 kbps" };
475}
476
477int OggVorbisAudioFormat::estimateOggFileQuality (const File& source)
478{
479 if (auto in = source.createInputStream())
480 {
481 if (auto r = std::unique_ptr<AudioFormatReader> (createReaderFor (in.release(), true)))
482 {
483 auto lengthSecs = (double) r->lengthInSamples / r->sampleRate;
484 auto approxBitsPerSecond = (int) ((double) source.getSize() * 8 / lengthSecs);
485
486 auto qualities = getQualityOptions();
487 int bestIndex = 0;
488 int bestDiff = 10000;
489
490 for (int i = qualities.size(); --i >= 0;)
491 {
492 auto diff = std::abs (qualities[i].getIntValue() - approxBitsPerSecond);
493
494 if (diff < bestDiff)
495 {
496 bestDiff = diff;
497 bestIndex = i;
498 }
499 }
500
501 return bestIndex;
502 }
503 }
504
505 return 0;
506}
507
508#endif
509
510} // namespace juce