Audacity 3.2.0
ImportOpus.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 SPDX-License-Identifier: GPL-2.0-or-later
4
5 Audacity: A Digital Audio Editor
6
7 ImportOpus.cpp
8
9 Dmitry Vedenko
10
11**********************************************************************/
12
13
14#include "Import.h"
15#include "ImportPlugin.h"
16
17#include <string_view>
18
19#include<wx/string.h>
20#include<wx/log.h>
21
22#include<stdlib.h>
23
24#include "Tags.h"
25#include "WaveTrack.h"
26#include "CodeConversions.h"
27#include "ImportUtils.h"
29#include "CodeConversions.h"
30
31#include <opus/opusfile.h>
32
33#define DESC XO("Opus files")
34
35static const auto exts = { L"opus", L"ogg" };
36
37class OpusImportPlugin final : public ImportPlugin
38{
39public:
42
43 wxString GetPluginStringID() override;
45 std::unique_ptr<ImportFileHandle> Open(
46 const FilePath &Filename, AudacityProject*) override;
47};
48
50{
51public:
52 explicit OpusImportFileHandle(const FilePath& filename);
54
55 bool IsOpen() const;
56
59 void Import(
60 ImportProgressListener& progressListener, WaveTrackFactory* trackFactory,
61 TrackHolders& outTracks, Tags* tags,
62 std::optional<LibFileFormats::AcidizerTags>& outAcidTags) override;
63
64 wxInt32 GetStreamCount() override;
65 const TranslatableStrings &GetStreamInfo() override;
66 void SetStreamUsage(wxInt32 StreamID, bool Use) override;
67
68private:
69 static int OpusReadCallback(void* stream, unsigned char* ptr, int nbytes);
70 static int OpusSeekCallback(void* stream, opus_int64 offset, int whence);
71 static opus_int64 OpusTellCallback(void* stream);
72 static int OpusCloseCallback(void* stream);
73
74 static TranslatableString GetOpusErrorString(int error);
75 void LogOpusError(const char* method, int error);
76 void NotifyImportFailed(ImportProgressListener& progressListener, int error);
77 void NotifyImportFailed(ImportProgressListener& progressListener, const TranslatableString& error);
78
79 wxFile mFile;
80
81 OpusFileCallbacks mCallbacks;
82 OggOpusFile* mOpusFile {};
84 int64_t mNumSamples {};
85
86 // Opus internally uses 48kHz sample rate
87 // The file header contains the sample rate of the original audio.
88 // We ignore it and let Audacity resample the audio to the project sample rate.
89 const double mSampleRate { 48000.0 };
90
91 // Opus decodes to float samples internally, optionally converting them to int16.
92 // We let Audacity to convert the stream to the project sample format.
94};
95
96// ============================================================================
97// OpusImportPlugin
98// ============================================================================
99
102{
103}
104
106{
107}
108
110{
111 return wxT("libopus");
112}
113
115{
116 return DESC;
117}
118
119std::unique_ptr<ImportFileHandle> OpusImportPlugin::Open(const FilePath &filename, AudacityProject*)
120{
121 auto handle = std::make_unique<OpusImportFileHandle>(filename);
122
123 if (!handle->IsOpen())
124 return {};
125
126 return std::move(handle);
127}
128
130 std::make_unique< OpusImportPlugin >()
131};
132
133// ============================================================================
134// OpusImportFileHandle
135// ============================================================================
136
138 : ImportFileHandleEx { filename }
139{
140 // Try to open the file for reading
141 if (!mFile.Open(filename, wxFile::read))
142 return;
143
144 OpusFileCallbacks callbacks = {
149 };
150
151 int error = 0;
152 mOpusFile = op_open_callbacks(this, &callbacks, nullptr, 0, &error);
153
154 if (mOpusFile == nullptr)
155 {
156 LogOpusError("Error while opening Opus file", error);
157 return;
158 }
159
160 mNumChannels = op_channel_count(mOpusFile, -1);
161 mNumSamples = op_pcm_total(mOpusFile, -1);
162}
163
165{
166 return DESC;
167}
168
170{
171 return 0;
172}
173
175 ImportProgressListener& progressListener, WaveTrackFactory* trackFactory,
176 TrackHolders& outTracks, Tags* tags,
177 std::optional<LibFileFormats::AcidizerTags>&)
178{
179 BeginImport();
180
181 outTracks.clear();
182
183 auto track = ImportUtils::NewWaveTrack(
184 *trackFactory,
186 mFormat,
188
189 /* The number of samples to read in each loop */
190 const size_t SAMPLES_TO_READ = track->GetMaxBlockSize();
191 uint64_t totalSamplesRead = 0;
192
193 const auto bufferSize = mNumChannels * SAMPLES_TO_READ;
194
195 ArrayOf<float> floatBuffer { bufferSize };
196
197 uint64_t samplesRead = 0;
198
199 do
200 {
201 int linkIndex { -1 };
202 auto samplesPerChannelRead = op_read_float(mOpusFile, floatBuffer.get(), SAMPLES_TO_READ, &linkIndex);
203
204 if (samplesPerChannelRead < 0 && samplesPerChannelRead != OP_HOLE)
205 {
206 NotifyImportFailed(progressListener, samplesPerChannelRead);
207 return;
208 }
209
210 auto linkChannels = op_head(mOpusFile, linkIndex)->channel_count;
211
212 if (linkChannels != mNumChannels)
213 {
214 NotifyImportFailed(progressListener, XO("File has changed the number of channels in the middle."));
215 return;
216 }
217
218 unsigned chn = 0;
219 ImportUtils::ForEachChannel(*track, [&](auto& channel)
220 {
221 channel.AppendBuffer(
222 reinterpret_cast<constSamplePtr>(floatBuffer.get() +
223 chn), mFormat, samplesRead, mNumChannels, mFormat
224 );
225 ++chn;
226 });
227
228 samplesRead = samplesPerChannelRead;
229 totalSamplesRead += samplesRead;
230
231 progressListener.OnImportProgress(double(totalSamplesRead) / mNumSamples);
232 } while (!IsCancelled() && !IsStopped() && samplesRead != 0);
233
234 if (IsCancelled())
235 {
236 progressListener.OnImportResult(
238 return;
239 }
240
241 if (totalSamplesRead < mNumSamples && !IsStopped())
242 {
244 return;
245 }
246
247 ImportUtils::FinalizeImport(outTracks, *track);
248
249 auto opusTags = op_tags(mOpusFile, -1);
250
251 if (opusTags != nullptr)
252 {
253 for (int i = 0; i < opusTags->comments; ++i)
254 {
255 const auto comment = opusTags->user_comments[i];
256 const auto commentLength = opusTags->comment_lengths[i];
257
258 std::string_view tag { comment,
259 std::string_view::size_type(commentLength) };
260
261 const auto separator = tag.find('=');
262
263 if (separator != std::string_view::npos)
264 {
265 auto name = audacity::ToWXString(tag.substr(0, separator));
266 const auto value = audacity::ToWXString(tag.substr(separator + 1));
267
268 // See: ImportOGG.cpp tags parsing
269 if (name.Upper() == wxT("DATE") && !tags->HasTag(TAG_YEAR))
270 {
271 long val;
272
273 if (value.length() == 4 && value.ToLong(&val))
274 name = TAG_YEAR;
275 }
276
277 tags->SetTag(name, value);
278 }
279 }
280 }
281
282 progressListener.OnImportResult(IsStopped()
285}
286
288{
289 return 1;
290}
291
293{
294 static TranslatableStrings empty;
295 return empty;
296}
297
299{
300}
301
303 void* pstream, unsigned char* ptr, int nbytes)
304{
305 auto stream = static_cast<OpusImportFileHandle*>(pstream);
306
307 if (!stream->mFile.IsOpened())
308 return EOF;
309
310 // OpusFile never reads more than 2^31 bytes at a time,
311 // so we can safely cast ssize_t to int.
312 return int(stream->mFile.Read(ptr, nbytes));
313}
314
316 void* pstream, opus_int64 offset, int whence)
317{
318 auto stream = static_cast<OpusImportFileHandle*>(pstream);
319
320 if (!stream->mFile.IsOpened())
321 return -1;
322
323 wxSeekMode wxWhence = whence == SEEK_SET ? wxFromStart :
324 whence == SEEK_CUR ? wxFromCurrent :
325 whence == SEEK_END ? wxFromEnd : wxFromStart;
326
327 return stream->mFile.Seek(offset, wxWhence) != wxInvalidOffset ? 0 : -1;
328}
329
331{
332 auto stream = static_cast<OpusImportFileHandle*>(pstream);
333
334 return static_cast<opus_int64>(stream->mFile.Tell());
335}
336
338{
339 auto stream = static_cast<OpusImportFileHandle*>(pstream);
340
341 if (stream->mFile.IsOpened())
342 return stream->mFile.Close() ? 0 : EOF;
343
344 return 0;
345}
346
348{
349 switch (error)
350 {
351 case OP_EREAD:
352 return XO("IO error reading from file");
353 case OP_EFAULT:
354 return XO("internal error");
355 case OP_EIMPL:
356 return XO("not implemented");
357 case OP_EINVAL:
358 return XO("invalid argument");
359 case OP_ENOTFORMAT:
360 return XO("not an Opus file");
361 case OP_EBADHEADER:
362 return XO("invalid header");
363 case OP_EVERSION:
364 return XO("unsupported version");
365 case OP_EBADPACKET:
366 return XO("invalid packet");
367 case OP_EBADLINK:
368 return XO("invalid stream structure");
369 case OP_ENOSEEK:
370 return XO("stream is not seekable");
371 case OP_EBADTIMESTAMP:
372 return XO("invalid timestamp");
373 default:
374 return {};
375 }
376}
377
378void OpusImportFileHandle::LogOpusError(const char* method, int error)
379{
380 if (error == 0)
381 return;
382
383 if (error == OP_ENOTFORMAT)
384 wxLogDebug("%s: Not Opus format", GetOpusErrorString(error).Translation());
385 else
386 wxLogError("%s: %s", method, GetOpusErrorString(error).Translation());
387}
388
390 ImportProgressListener& progressListener, int error)
391{
392 NotifyImportFailed(progressListener, GetOpusErrorString(error));
393}
394
396 ImportProgressListener& progressListener, const TranslatableString& error)
397{
399 XO("Failed to decode Opus file: %s").Format(error));
400
401 if (IsCancelled())
402 progressListener.OnImportResult(
404 else if (!IsStopped())
405 progressListener.OnImportResult(
407 else
408 progressListener.OnImportResult(
410}
411
413{
414 return mOpusFile != nullptr;
415}
416
418{
419 if (mOpusFile != nullptr)
420 op_free(mOpusFile);
421}
wxT("CloseDown"))
Declare functions to perform UTF-8 to std::wstring conversions.
XO("Cut/Copy/Paste")
static const auto exts
Definition: ImportOpus.cpp:35
#define DESC
Definition: ImportOpus.cpp:33
static Importer::RegisteredImportPlugin registered
Definition: ImportOpus.cpp:129
The interface that all file import "plugins" (if you want to call them that) must implement....
std::vector< std::shared_ptr< Track > > TrackHolders
Definition: ImportRaw.h:24
wxString FilePath
Definition: Project.h:21
sampleFormat
The ordering of these values with operator < agrees with the order of increasing bit width.
Definition: SampleFormat.h:30
const char * constSamplePtr
Definition: SampleFormat.h:58
#define TAG_YEAR
Definition: Tags.h:62
wxString name
Definition: TagsEditor.cpp:166
std::vector< TranslatableString > TranslatableStrings
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:90
Abstract base class used in importing a file.
bool IsStopped() const noexcept
bool IsCancelled() const noexcept
unsigned long long ByteCount
Definition: ImportPlugin.h:114
Base class for FlacImportPlugin, LOFImportPlugin, MP3ImportPlugin, OggImportPlugin and PCMImportPlugi...
Definition: ImportPlugin.h:67
Interface used to report on import state and progress.
virtual void OnImportResult(ImportResult result)=0
Used to report on import result for file handle passed as argument to OnImportFileOpened.
virtual void OnImportProgress(double progress)=0
static void ShowMessageBox(const TranslatableString &message, const TranslatableString &caption=XO("Import Project"))
Definition: ImportUtils.cpp:43
static std::shared_ptr< WaveTrack > NewWaveTrack(WaveTrackFactory &trackFactory, unsigned nChannels, sampleFormat effectiveFormat, double rate)
Definition: ImportUtils.cpp:35
static void ForEachChannel(TrackList &trackList, const std::function< void(WaveChannel &)> &op)
Iterates over channels in each wave track from the list.
Definition: ImportUtils.cpp:73
static void FinalizeImport(TrackHolders &outTracks, const std::vector< std::shared_ptr< WaveTrack > > &importedStreams)
Flushes the given channels and moves them to outTracks.
Definition: ImportUtils.cpp:49
bool IsOpen() const
Definition: ImportOpus.cpp:412
static opus_int64 OpusTellCallback(void *stream)
Definition: ImportOpus.cpp:330
const sampleFormat mFormat
Definition: ImportOpus.cpp:93
OggOpusFile * mOpusFile
Definition: ImportOpus.cpp:82
static TranslatableString GetOpusErrorString(int error)
Definition: ImportOpus.cpp:347
wxInt32 GetStreamCount() override
Definition: ImportOpus.cpp:287
void LogOpusError(const char *method, int error)
Definition: ImportOpus.cpp:378
static int OpusCloseCallback(void *stream)
Definition: ImportOpus.cpp:337
void SetStreamUsage(wxInt32 StreamID, bool Use) override
Definition: ImportOpus.cpp:298
ByteCount GetFileUncompressedBytes() override
Definition: ImportOpus.cpp:169
OpusFileCallbacks mCallbacks
Definition: ImportOpus.cpp:81
static int OpusReadCallback(void *stream, unsigned char *ptr, int nbytes)
Definition: ImportOpus.cpp:302
const double mSampleRate
Definition: ImportOpus.cpp:89
const TranslatableStrings & GetStreamInfo() override
Definition: ImportOpus.cpp:292
static int OpusSeekCallback(void *stream, opus_int64 offset, int whence)
Definition: ImportOpus.cpp:315
void Import(ImportProgressListener &progressListener, WaveTrackFactory *trackFactory, TrackHolders &outTracks, Tags *tags, std::optional< LibFileFormats::AcidizerTags > &outAcidTags) override
Definition: ImportOpus.cpp:174
OpusImportFileHandle(const FilePath &filename)
Definition: ImportOpus.cpp:137
void NotifyImportFailed(ImportProgressListener &progressListener, int error)
Definition: ImportOpus.cpp:389
TranslatableString GetFileDescription() override
Definition: ImportOpus.cpp:164
std::unique_ptr< ImportFileHandle > Open(const FilePath &Filename, AudacityProject *) override
Definition: ImportOpus.cpp:119
wxString GetPluginStringID() override
Definition: ImportOpus.cpp:109
TranslatableString GetPluginFormatDescription() override
Definition: ImportOpus.cpp:114
ID3 Tags (for MP3)
Definition: Tags.h:73
bool HasTag(const wxString &name) const
Definition: Tags.cpp:397
void SetTag(const wxString &name, const wxString &value, const bool bSpecialTag=false)
Definition: Tags.cpp:431
Holds a msgid for the translation catalog; may also bind format arguments.
Used to create or clone a WaveTrack, with appropriate context from the project that will own the trac...
Definition: WaveTrack.h:871
Extend wxArrayString with move operations and construction and insertion fromstd::initializer_list.
constexpr size_t npos(-1)
wxString ToWXString(const std::string &str)
const char * end(const char *str) noexcept
Definition: StringUtils.h:106
const char * begin(const char *str) noexcept
Definition: StringUtils.h:101