Audacity 3.2.0
ImportMP3_MPG123.cpp
Go to the documentation of this file.
1/* SPDX-License-Identifier: GPL-2.0-or-later */
2/*!********************************************************************
3
4 Audacity: A Digital Audio Editor
5
6 ImportMP3_MPG123.cpp
7
8 Dmitry Vedenko
9
10*/
11
12#include <wx/defs.h>
13#include <cstddef>
14#include <cstring>
15
16#include "Import.h"
17#include "BasicUI.h"
18#include "ImportPlugin.h"
19#include "ImportUtils.h"
21#include "Project.h"
22
23#define DESC XO("MP3 files")
24
25#include <wx/file.h>
26#include <wx/string.h>
27#include <wx/log.h>
28
29#include <mpg123.h>
30
31#include "Tags.h"
32#include "WaveTrack.h"
33
34#include "CodeConversions.h"
35#include "FromChars.h"
36
37namespace
38{
39
40const auto exts = { wxT("mp3"), wxT("mp2"), wxT("mpa") };
41
42// ID2V2 genre can be quite complex:
43// (from https://id3.org/id3v2.3.0)
44// References to the ID3v1 genres can be made by, as first byte, enter
45// "(" followed by a number from the genres list (appendix A) and ended
46// with a ")" character. This is optionally followed by a refinement,
47// e.g. "(21)" or "(4)Eurodisco". Several references can be made in the
48// same frame, e.g. "(51)(39)". However, Audacity only supports one
49// genre, so we just skip ( a parse the number afterwards.
50wxString GetId3v2Genre(Tags& tags, const char* genre)
51{
52 if (genre == nullptr)
53 return {};
54
55 // It was observed, however, that Genre can use a different format
56 if (genre[0] != '(')
57 // We consider the string to be a genre name
58 return audacity::ToWXString(genre);
59
60 auto it = genre;
61 auto end = it + std::strlen(it);
62
63 while (*it == '(')
64 {
65 int tagValue;
66 auto result = FromChars(++it, end, tagValue);
67
68 // Parsing failed, consider it to be the genre
69 if (result.ec != std::errc {})
70 break;
71
72 const auto parsedGenre = tags.GetGenre(tagValue);
73
74 if (!parsedGenre.empty())
75 return parsedGenre;
76
77 it = result.ptr;
78
79 // Nothing left to parse
80 if (it == end)
81 break;
82
83 // Unexpected symbol in the tag
84 if (*it != ')')
85 break;
86
87 ++it;
88 }
89
90 if (it != end)
91 return audacity::ToWXString(it);
92
93 return audacity::ToWXString(genre);
94}
95
96class MP3ImportPlugin final : public ImportPlugin
97{
98public:
100 : ImportPlugin(
102 {
103#if MPG123_API_VERSION < 46
104 // Newer versions of the library don't need that anymore, but it is safe
105 // to have the no-op call present for compatibility with old versions.
106 mpg123_init();
107#endif
108 }
109
110 wxString GetPluginStringID() override
111 {
112 return wxT("libmpg123");
113 }
114
116 {
117 return DESC;
118 }
119
120 std::unique_ptr<ImportFileHandle> Open(const FilePath &Filename, AudacityProject*) override;
121}; // class MP3ImportPlugin
122
124{
125public:
126 MP3ImportFileHandle(const FilePath &filename);
128
129 TranslatableString GetFileDescription() override;
130 ByteCount GetFileUncompressedBytes() override;
131 void Import(
132 ImportProgressListener& progressListener, WaveTrackFactory* trackFactory,
133 TrackHolders& outTracks, Tags* tags,
134 std::optional<LibFileFormats::AcidizerTags>& outAcidTags) override;
135
136 bool SetupOutputFormat();
137
138 void ReadTags(Tags* tags);
139
140 wxInt32 GetStreamCount() override;
141 const TranslatableStrings &GetStreamInfo() override;
142 void SetStreamUsage(wxInt32 StreamID, bool Use) override;
143
144private:
145 bool Open();
146
147private:
148 static ptrdiff_t ReadCallback(void* handle, void* buffer, size_t size);
149 static off_t SeekCallback(void* handle, off_t offset, int whence);
150
151 wxFile mFile;
152 wxFileOffset mFileLen { 0 };
153
154 WaveTrackFactory* mTrackFactory { nullptr };
156 unsigned mNumChannels { 0 };
157
158 mpg123_handle* mHandle { nullptr };
159
160 bool mFloat64Output {};
161
163}; // class MP3ImportFileHandle
164
165std::unique_ptr<ImportFileHandle> MP3ImportPlugin::Open(
166 const FilePath &Filename, AudacityProject *)
167{
168 auto handle = std::make_unique<MP3ImportFileHandle>(Filename);
169
170 if (!handle->Open())
171 return nullptr;
172
173 return handle;
174}
175
177{
178 "MP3",
179 std::make_unique<MP3ImportPlugin>()
180};
181
182// ============================================================================
183// MP3ImportFileHandle
184// ============================================================================
185
186MP3ImportFileHandle::MP3ImportFileHandle(const FilePath& filename)
187 : ImportFileHandleEx(filename)
188{
189 int errorCode = MPG123_OK;
190 mHandle = mpg123_new(nullptr, &errorCode);
191
192 if (errorCode != MPG123_OK)
193 {
194 wxLogError(
195 "Failed to create MPG123 handle: %s",
196 mpg123_plain_strerror(errorCode));
197
198 mHandle = nullptr;
199
200 return;
201 }
202
203 errorCode = mpg123_replace_reader_handle(
205
206 if (errorCode != MPG123_OK)
207 {
208 wxLogError(
209 "Failed to set reader on the MPG123 handle: %s",
210 mpg123_plain_strerror(errorCode));
211
212 mpg123_delete(mHandle);
213 mHandle = nullptr;
214 }
215
216 // We force mpg123 to decode into floats
217 mpg123_param(mHandle, MPG123_FLAGS, MPG123_GAPLESS | MPG123_FORCE_FLOAT, 0.0);
218
219 if (errorCode != MPG123_OK)
220 {
221 wxLogError(
222 "Failed to set options on the MPG123 handle",
223 mpg123_plain_strerror(errorCode));
224
225 mpg123_delete(mHandle);
226 mHandle = nullptr;
227 }
228}
229
231{
232 // nullptr is a valid input for the mpg123_delete
233 mpg123_delete(mHandle);
234}
235
237{
238 return DESC;
239}
240
242{
243 // We have to parse the file first using mpg123_scan,
244 // we do not want to do that before the import starts.
245 return 0;
246}
247
249{
250 return 1;
251}
252
254{
255 static TranslatableStrings empty;
256 return empty;
257}
258
259void MP3ImportFileHandle::SetStreamUsage(wxInt32 WXUNUSED(StreamID), bool WXUNUSED(Use))
260{
261}
262
264 ImportProgressListener& progressListener, WaveTrackFactory* trackFactory,
265 TrackHolders& outTracks, Tags* tags,
266 std::optional<LibFileFormats::AcidizerTags>&)
267{
268 BeginImport();
269
270 auto finalAction = finally([handle = mHandle]() { mpg123_close(handle); });
271
272 outTracks.clear();
273 mTrackFactory = trackFactory;
274
275 long long framesCount = mpg123_framelength(mHandle);
276
277 if (!SetupOutputFormat())
278 {
280 return;
281 }
282
283 off_t frameIndex { 0 };
284 unsigned char* data { nullptr };
285 size_t dataSize { 0 };
286
287 std::vector<float> conversionBuffer;
288
289 int ret = MPG123_OK;
290
291 while ((ret = mpg123_decode_frame(mHandle, &frameIndex, &data, &dataSize)) ==
292 MPG123_OK)
293 {
294 if(framesCount > 0)
295 progressListener.OnImportProgress(static_cast<double>(frameIndex) / static_cast<double>(framesCount));
296
297 if(IsCancelled())
298 {
300 return;
301 }
302 //VS: doesn't implement Stop behavior...
303
304 constSamplePtr samples = reinterpret_cast<constSamplePtr>(data);
305 const size_t samplesCount = dataSize / sizeof(float) / mNumChannels;
306
307 // libmpg123 picks up the format based on some "internal" precision.
308 // This case is not expected to happen
309 if (mFloat64Output)
310 {
311 conversionBuffer.resize(samplesCount * mNumChannels);
312
313 for (size_t sampleIndex = 0; sampleIndex < conversionBuffer.size();
314 ++sampleIndex)
315 {
316 conversionBuffer[sampleIndex] = static_cast<float>(
317 reinterpret_cast<const double*>(data)[sampleIndex]);
318 }
319
320 samples = reinterpret_cast<constSamplePtr>(conversionBuffer.data());
321 }
322 // Just copy the interleaved data to the channels
323 unsigned chn = 0;
324 ImportUtils::ForEachChannel(*mTrackList, [&](auto& channel)
325 {
326 channel.AppendBuffer(
327 samples + sizeof(float) * chn,
328 floatSample, samplesCount,
331 ++chn;
332 });
333 }
334
335 if (ret != MPG123_DONE)
336 {
337 wxLogError(
338 "Failed to decode MP3 file: %s", mpg123_plain_strerror(ret));
339
341 return;
342 }
343
345
346 ReadTags(tags);
347
349}
350
352{
353 long rate;
354 int channels;
355 int encoding = MPG123_ENC_FLOAT_32;
356 mpg123_getformat(mHandle, &rate, &channels, &encoding);
357
358 mNumChannels = channels == MPG123_MONO ? 1 : 2;
359
360 if (encoding != MPG123_ENC_FLOAT_32 && encoding != MPG123_ENC_FLOAT_64)
361 {
362 wxLogError("MPG123 returned unexpected encoding");
363
364 return false;
365 }
366
367 mFloat64Output = encoding == MPG123_ENC_FLOAT_64;
368
373 rate);
374
375 return true;
376}
377
379{
380 mpg123_id3v1* v1;
381 mpg123_id3v2* v2;
382 int meta;
383
384 meta = mpg123_meta_check(mHandle);
385
386 if (meta & MPG123_ID3 && mpg123_id3(mHandle, &v1, &v2) == MPG123_OK)
387 {
388 if (v2 != nullptr && v2->title != nullptr && v2->title->fill > 0)
389 tags->SetTag(TAG_TITLE, audacity::ToWXString(v2->title->p));
390 else if (v1 != nullptr && v1->title[0] != '\0')
391 tags->SetTag(TAG_TITLE, audacity::ToWXString(v1->title));
392
393 if (v2 != nullptr && v2->artist != nullptr && v2->artist->fill > 0)
394 tags->SetTag(TAG_ARTIST, audacity::ToWXString(v2->artist->p));
395 else if (v1 != nullptr && v1->artist[0] != '\0')
396 tags->SetTag(TAG_ARTIST, audacity::ToWXString(v1->artist));
397
398 if (v2 != nullptr && v2->album != nullptr && v2->album->fill > 0)
399 tags->SetTag(TAG_ALBUM, audacity::ToWXString(v2->album->p));
400 else if (v1 != nullptr && v1->album[0] != '\0')
401 tags->SetTag(TAG_ALBUM, audacity::ToWXString(v1->album));
402
403 if (v2 != nullptr && v2->year != nullptr && v2->year->fill > 0)
404 tags->SetTag(TAG_YEAR, audacity::ToWXString(v2->year->p));
405 else if (v1 != nullptr && v1->year[0] != '\0')
406 tags->SetTag(TAG_YEAR, audacity::ToWXString(std::string(v1->year, 4)));
407
408 if (v2 != nullptr && v2->genre != nullptr && v2->genre->fill > 0)
409 tags->SetTag(TAG_GENRE, GetId3v2Genre(*tags, v2->genre->p));
410 else if (v1 != nullptr)
411 tags->SetTag(TAG_GENRE, tags->GetGenre(v1->genre));
412
413 if (v2 != nullptr && v2->comment != nullptr && v2->comment->fill > 0)
414 tags->SetTag(TAG_COMMENTS, audacity::ToWXString(v2->comment->p));
415 else if (v1 != nullptr && v1->comment[0] != '\0')
416 tags->SetTag(TAG_COMMENTS, audacity::ToWXString(v1->comment));
417
418 if (v2 != nullptr)
419 {
420 for (size_t i = 0; i < v2->comments; ++i)
421 {
422 if (v2->comment_list[i].text.fill == 0)
423 continue;
424
425 tags->SetTag(
426 audacity::ToWXString(std::string(v2->comment_list[i].id, 4)),
427 audacity::ToWXString(v2->comment_list[i].text.p));
428 }
429
430 for (size_t i = 0; i < v2->extras; ++i)
431 {
432 if (v2->extra[i].text.fill == 0)
433 continue;
434
435 tags->SetTag(
436 audacity::ToWXString(std::string(v2->extra[i].id, 4)),
437 audacity::ToWXString(v2->extra[i].text.p));
438 }
439
440 // libmpg123 does not parse TRCK tag, we have to do it ourselves
441 for (size_t i = 0; i < v2->texts; ++i)
442 {
443 if (memcmp(v2->text[i].id, "TRCK", 4) == 0)
444 {
445 tags->SetTag(
446 TAG_TRACK, audacity::ToWXString(v2->text[i].text.p));
447 }
448 }
449 }
450 }
451}
452
454{
455 if (mHandle == nullptr)
456 return false;
457
458 // Open the file
459 if (!mFile.Open(GetFilename()))
460 {
461 return false;
462 }
463
464 // Get the length of the file
465 mFileLen = mFile.Seek(0, wxFromEnd);
466
467 if (mFileLen == wxInvalidOffset || mFile.Error())
468 {
469 mFile.Close();
470 return false;
471 }
472
473 if (mFile.Seek(0, wxFromStart) == wxInvalidOffset || mFile.Error())
474 {
475 mFile.Close();
476 return false;
477 }
478
479 // Check if file is an MP3
480 auto errorCode = mpg123_open_handle(mHandle, this);
481
482 if (errorCode != MPG123_OK)
483 return false;
484
485 // Scan the file
486 errorCode = mpg123_scan(mHandle);
487
488 if (errorCode != MPG123_OK)
489 return false;
490
491 // Read the output format
492 errorCode = mpg123_decode_frame(mHandle, nullptr, nullptr, nullptr);
493
494 // First decode should read the format
495 if (errorCode != MPG123_NEW_FORMAT)
496 return false;
497
498 return true;
499}
500
502 void* handle, void* buffer, size_t size)
503{
504 return static_cast<MP3ImportFileHandle*>(handle)->mFile.Read(buffer, size);
505}
506
507wxSeekMode GetWXSeekMode(int whence)
508{
509 switch (whence)
510 {
511 case SEEK_SET:
512 return wxFromStart;
513 case SEEK_CUR:
514 return wxFromCurrent;
515 case SEEK_END:
516 return wxFromEnd;
517 default:
518 // We have covered all the lseek whence modes defined by POSIX
519 // so this branch should not be reachable
520 assert(false);
521 return wxFromStart;
522 }
523}
524
526 void* handle, off_t offset, int whence)
527{
528 return static_cast<MP3ImportFileHandle*>(handle)->mFile.Seek(
529 offset, GetWXSeekMode(whence));
530}
531
532} // namespace
wxT("CloseDown"))
Toolkit-neutral facade for basic user interface services.
Declare functions to perform UTF-8 to std::wstring conversions.
FromCharsResult FromChars(const char *buffer, const char *last, float &value) noexcept
Parse a string into a single precision floating point value, always uses the dot as decimal.
Definition: FromChars.cpp:153
Declare functions to convert numeric types to string representation.
#define DESC
The interface that all file import "plugins" (if you want to call them that) must implement....
std::vector< std::shared_ptr< TrackList > > TrackHolders
Definition: ImportRaw.h:24
wxString FilePath
Definition: Project.h:21
constexpr sampleFormat floatSample
Definition: SampleFormat.h:45
const char * constSamplePtr
Definition: SampleFormat.h:58
#define TAG_TRACK
Definition: Tags.h:61
#define TAG_COMMENTS
Definition: Tags.h:64
#define TAG_GENRE
Definition: Tags.h:63
#define TAG_ALBUM
Definition: Tags.h:60
#define TAG_YEAR
Definition: Tags.h:62
#define TAG_TITLE
Definition: Tags.h:58
#define TAG_ARTIST
Definition: Tags.h:59
std::shared_ptr< TrackList > TrackListHolder
Definition: Track.h:42
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
FilePath GetFilename() const override
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 TrackListHolder 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:66
static void FinalizeImport(TrackHolders &outTracks, const std::vector< TrackListHolder > &importedStreams)
Flushes the given channels and moves them to outTracks.
Definition: ImportUtils.cpp:49
ID3 Tags (for MP3)
Definition: Tags.h:73
wxString GetGenre(int value)
Definition: Tags.cpp:373
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:1279
void Import(ImportProgressListener &progressListener, WaveTrackFactory *trackFactory, TrackHolders &outTracks, Tags *tags, std::optional< LibFileFormats::AcidizerTags > &outAcidTags) override
static off_t SeekCallback(void *handle, off_t offset, int whence)
static ptrdiff_t ReadCallback(void *handle, void *buffer, size_t size)
void SetStreamUsage(wxInt32 StreamID, bool Use) override
Extend wxArrayString with move operations and construction and insertion fromstd::initializer_list.
auto end(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:159
auto begin(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:150
wxString GetId3v2Genre(Tags &tags, const char *genre)
static Importer::RegisteredImportPlugin registered
wxString ToWXString(const std::string &str)