diff --git a/src/import/Import.cpp b/src/import/Import.cpp index bb7b25518..ce0c0652a 100644 --- a/src/import/Import.cpp +++ b/src/import/Import.cpp @@ -578,24 +578,6 @@ bool Importer::Import( AudacityProject &project, { if (plugin->SupportsExtension(extension)) { - // If libmad is accidentally fed a wav file which has been incorrectly - // given an .mp3 extension then it can choke on the contents and crash. - // To avoid this, put libsndfile ahead of libmad in the lists created for - // mp3 files, or for any of the extensions supported by libmad. - // A genuine .mp3 file will first fail an attempted import with libsndfile - // but then get processed as desired by libmad. - // But a wav file which bears an incorrect .mp3 extension will be successfully - // processed by libsndfile and thus avoid being submitted to libmad. - if (plugin->GetPluginStringID() == wxT("libmad")) - { - // Make sure libsndfile is not already in the list - if (importPlugins.end() == - std::find(importPlugins.begin(), importPlugins.end(), libsndfilePlugin)) - { - wxLogDebug(wxT("Appending %s"),libsndfilePlugin->GetPluginStringID()); - importPlugins.push_back(libsndfilePlugin); - } - } wxLogDebug(wxT("Appending %s"),plugin->GetPluginStringID()); importPlugins.push_back(plugin); } @@ -608,15 +590,12 @@ bool Importer::Import( AudacityProject &project, // formats unsuitable for it, and produce distorted results. for (const auto &plugin : sImportPluginList()) { - if (!(plugin->GetPluginStringID() == wxT("libmad"))) + // Make sure its not already in the list + if (importPlugins.end() == + std::find(importPlugins.begin(), importPlugins.end(), plugin)) { - // Make sure its not already in the list - if (importPlugins.end() == - std::find(importPlugins.begin(), importPlugins.end(), plugin)) - { - wxLogDebug(wxT("Appending %s"),plugin->GetPluginStringID()); - importPlugins.push_back(plugin); - } + wxLogDebug(wxT("Appending %s"),plugin->GetPluginStringID()); + importPlugins.push_back(plugin); } } diff --git a/src/import/ImportMP3.cpp b/src/import/ImportMP3.cpp index 4d57d789b..72322a1ff 100644 --- a/src/import/ImportMP3.cpp +++ b/src/import/ImportMP3.cpp @@ -28,26 +28,16 @@ #include "../Audacity.h" // for USE_* macros -// For compilers that support precompilation, includes "wx/wx.h". -#include - -#ifndef WX_PRECOMP -#include -#endif - #include -#include -#include "../Prefs.h" #include "Import.h" #include "ImportPlugin.h" -#include "../Tags.h" -#include "../prefs/QualityPrefs.h" -#include "../widgets/ProgressDialog.h" +#include "../Project.h" #define DESC XO("MP3 files") -static const auto exts = { +static const auto exts = +{ wxT("mp3"), wxT("mp2"), wxT("mpa") @@ -55,27 +45,35 @@ static const auto exts = { #ifndef USE_LIBMAD -static Importer::RegisteredUnusableImportPlugin registered{ - std::make_unique - (DESC, FileExtensions( exts.begin(), exts.end() ) ) +static Importer::RegisteredUnusableImportPlugin registered +{ + std::make_unique(DESC, FileExtensions(exts.begin(), exts.end())) }; -#else /* USE_LIBMAD */ +#else + +#if defined(__WXMSW__) +#include +#else +#include +#endif -#include #include -#include #include -#include -#include +#include "../Prefs.h" +#include "../Tags.h" #include "../WaveTrack.h" +#include "../prefs/QualityPrefs.h" +#include "../widgets/AudacityMessageBox.h" +#include "../widgets/ProgressDialog.h" // PRL: include these last, // and correct some preprocessor namespace pollution from wxWidgets that // caused a warning about duplicate definition #undef SIZEOF_LONG -extern "C" { +extern "C" +{ #include "mad.h" #ifdef USE_LIBID3TAG @@ -83,92 +81,107 @@ extern "C" { #endif } +// Specifies the number of bytes in the input buffer. This also controls +// how many bytes will be scanned when searching for the first MP3 frame. #define INPUT_BUFFER_SIZE 65535 -#define PROGRESS_SCALING_FACTOR 100000 -/* this is a private structure we can use for whatever we like, and it will get - * passed to each of the callback routines, allowing us to keep track of - * things. */ -struct private_data { - wxFile *file; /* the file containing the mp3 data we're feeding the encoder */ - ArrayOf inputBuffer{ static_cast(INPUT_BUFFER_SIZE) }; - int inputBufferFill; /* amount of data in inputBuffer */ - TrackFactory *trackFactory; - NewChannelGroup channels; - ProgressDialog *progress; - unsigned numChannels; - ProgressResult updateResult; - bool id3checked; - bool eof; /* having supplied both underlying file and guard pad data */ -}; +// This is the number of decoded samples libmad adds at the beginning +// (This is an "observed" value.) +#define MAD_DELAY 529 class MP3ImportPlugin final : public ImportPlugin { public: - MP3ImportPlugin(): - ImportPlugin( FileExtensions( exts.begin(), exts.end() ) ) - { - } + MP3ImportPlugin(); + ~MP3ImportPlugin(); - ~MP3ImportPlugin() { } - - wxString GetPluginStringID() override { return wxT("libmad"); } + wxString GetPluginStringID() override; TranslatableString GetPluginFormatDescription() override; - std::unique_ptr Open( - const FilePath &Filename, AudacityProject*) override; + std::unique_ptr Open(const FilePath &Filename, AudacityProject*) override; }; class MP3ImportFileHandle final : public ImportFileHandle { public: - MP3ImportFileHandle(std::unique_ptr &&file, const FilePath &filename): - ImportFileHandle(filename), - mFile(std::move(file)) - { - } - + MP3ImportFileHandle(const FilePath &filename); ~MP3ImportFileHandle(); TranslatableString GetFileDescription() override; ByteCount GetFileUncompressedBytes() override; - ProgressResult Import(TrackFactory *trackFactory, TrackHolders &outTracks, - Tags *tags) override; - - wxInt32 GetStreamCount() override { return 1; } - - const TranslatableStrings &GetStreamInfo() override - { - static TranslatableStrings empty; - return empty; - } - - void SetStreamUsage(wxInt32 WXUNUSED(StreamID), bool WXUNUSED(Use)) override - {} + ProgressResult Import(TrackFactory *trackFactory, TrackHolders &outTracks, Tags *tags) override; + wxInt32 GetStreamCount() override; + const TranslatableStrings &GetStreamInfo() override; + void SetStreamUsage(wxInt32 StreamID, bool Use) override; private: - void ImportID3(Tags *tags); + bool Open(); + bool CheckID3(); + bool CheckMP3(); + bool FillBuffer(); + void LoadID3(Tags *tags); - std::unique_ptr mFile; - void *mUserData; + // The MAD callbacks + + static mad_flow input_cb(void *that, + struct mad_stream *stream); + mad_flow InputCB(struct mad_stream *stream); + + static mad_flow filter_cb(void *that, + struct mad_stream const *stream, + struct mad_frame *frame); + mad_flow FilterCB(struct mad_stream const *stream, struct mad_frame *frame); + + static mad_flow output_cb(void *that, + struct mad_header const *header, + struct mad_pcm *pcm); + mad_flow OutputCB(struct mad_header const *header, struct mad_pcm *pcm); + + static mad_flow error_cb(void *that, + struct mad_stream *stream, + struct mad_frame *frame); + mad_flow ErrorCB(struct mad_stream *stream, struct mad_frame *frame); + +private: mad_decoder mDecoder; + + wxFile mFile; + wxFileOffset mFilePos; + wxFileOffset mFileLen; + + unsigned char mInputBuffer[INPUT_BUFFER_SIZE + MAD_BUFFER_GUARD]; + int mInputBufferLen; + + TrackFactory *mTrackFactory; + NewChannelGroup mChannels; + unsigned mNumChannels; + + ProgressResult mUpdateResult; + + int mDelay; + int mPadding; + + bool mHaveID3; + + friend MP3ImportPlugin; }; -/* The MAD callbacks */ -enum mad_flow input_cb(void *_data, struct mad_stream *stream); -enum mad_flow output_cb(void *_data, - struct mad_header const *header, - struct mad_pcm *pcm); -enum mad_flow error_cb(void *_data, struct mad_stream *stream, - struct mad_frame *frame); +// ============================================================================ +// MP3ImportPlugin +// ============================================================================ -/* convert libmad's fixed point representation to 16 bit signed integers. This - * code is taken verbatim from minimad.c. */ - -inline float scale(mad_fixed_t sample) +MP3ImportPlugin::MP3ImportPlugin() +: ImportPlugin(FileExtensions(exts.begin(), exts.end())) { - return (float) (sample / (float) (1L << MAD_F_FRACBITS)); } +MP3ImportPlugin::~MP3ImportPlugin() +{ +} + +wxString MP3ImportPlugin::GetPluginStringID() +{ + return wxT("libmad"); +} TranslatableString MP3ImportPlugin::GetPluginFormatDescription() { @@ -176,17 +189,35 @@ TranslatableString MP3ImportPlugin::GetPluginFormatDescription() } std::unique_ptr MP3ImportPlugin::Open( - const FilePath &Filename, AudacityProject*) + const FilePath &Filename, AudacityProject *) { - auto file = std::make_unique(Filename); + auto handle = std::make_unique(Filename); - if (!file->IsOpened()) + if (!handle->Open()) + { return nullptr; + } - /* There's no way to tell if this is a valid mp3 file before actually - * decoding, so we return a valid FileHandle. */ + return handle; +} - return std::make_unique(std::move(file), Filename); +static Importer::RegisteredImportPlugin registered +{ + "MP3", + std::make_unique() +}; + +// ============================================================================ +// MP3ImportFileHandle +// ============================================================================ + +MP3ImportFileHandle::MP3ImportFileHandle(const FilePath &filename) +: ImportFileHandle(filename) +{ +} + +MP3ImportFileHandle::~MP3ImportFileHandle() +{ } TranslatableString MP3ImportFileHandle::GetFileDescription() @@ -200,353 +231,778 @@ auto MP3ImportFileHandle::GetFileUncompressedBytes() -> ByteCount return 0; } -ProgressResult MP3ImportFileHandle::Import( - TrackFactory *trackFactory, TrackHolders &outTracks, - Tags *tags) +wxInt32 MP3ImportFileHandle::GetStreamCount() +{ + return 1; +} + +const TranslatableStrings &MP3ImportFileHandle::GetStreamInfo() +{ + static TranslatableStrings empty; + return empty; +} + +void MP3ImportFileHandle::SetStreamUsage(wxInt32 WXUNUSED(StreamID), bool WXUNUSED(Use)) +{ +} + +ProgressResult MP3ImportFileHandle::Import(TrackFactory *trackFactory, + TrackHolders &outTracks, + Tags *tags) { outTracks.clear(); CreateProgress(); - /* Prepare decoder data, initialize decoder */ + mTrackFactory = trackFactory; + mUpdateResult = ProgressResult::Success; + mNumChannels = 0; - private_data privateData; - privateData.file = mFile.get(); - privateData.inputBufferFill = 0; - privateData.progress = mProgress.get(); - privateData.updateResult= ProgressResult::Success; - privateData.id3checked = false; - privateData.numChannels = 0; - privateData.trackFactory= trackFactory; - privateData.eof = false; + // Set delay and padding to best possible in case the LAME tag is not present + mDelay = MAD_DELAY; + mPadding = 0; - mad_decoder_init(&mDecoder, &privateData, input_cb, 0, 0, output_cb, error_cb, 0); + // Initialize decoder + mad_decoder_init(&mDecoder, this, input_cb, 0, filter_cb, output_cb, error_cb, 0); - /* and send the decoder on its way! */ - - bool res = (mad_decoder_run(&mDecoder, MAD_DECODER_MODE_SYNC) == 0) && - (privateData.numChannels > 0) && - !(privateData.updateResult == ProgressResult::Cancelled) && - !(privateData.updateResult == ProgressResult::Failed); + // Send the decoder on its way! + auto res = mad_decoder_run(&mDecoder, MAD_DECODER_MODE_SYNC); + // Terminate decoder mad_decoder_finish(&mDecoder); - if (!res) { - /* failure */ - /* wxPrintf("failure\n"); */ - return (privateData.updateResult); + // Decoding failed, so pass it on + if (res != 0) + { + return ProgressResult::Failed; } - /* success */ - /* wxPrintf("success\n"); */ + // The user canceled the decoding, so bail without saving tracks or tags + if (mUpdateResult == ProgressResult::Cancelled) + { + return mUpdateResult; + } - /* copy the WaveTrack pointers into the Track pointer list that - * we are expected to fill */ - for(const auto &channel : privateData.channels) { + // Flush and trim the channels + for (const auto &channel : mChannels) + { channel->Flush(); + + // Trim any padding + if (mPadding) + { + double et = channel->GetEndTime(); + double t1 = et - channel->LongSamplesToTime(mPadding); + channel->Clear(t1, et); + } + + // And delay + if (mDelay) + { + double st = channel->GetStartTime(); + double t0 = st + channel->LongSamplesToTime(mDelay); + channel->Clear(st, t0); + } } - if (!privateData.channels.empty()) - outTracks.push_back(std::move(privateData.channels)); - /* Read in any metadata */ - ImportID3(tags); + // Copy the WaveTrack pointers into the Track pointer list that + // we are expected to fill + outTracks.push_back(std::move(mChannels)); - return privateData.updateResult; + // Load ID3 tags from the file + LoadID3(tags); + + return mUpdateResult; } -static Importer::RegisteredImportPlugin registered{ "MP3", - std::make_unique< MP3ImportPlugin >() -}; - -MP3ImportFileHandle::~MP3ImportFileHandle() +bool MP3ImportFileHandle::Open() { + mInputBufferLen = 0; + mFilePos = 0; + mFileLen = 0; + mHaveID3 = false; + + // Open the file + if (!mFile.Open(mFilename)) + { + return false; + } + + // Check for ID3 tags + if (!CheckID3()) + { + return false; + } + + // Scan for the first MP3 frame + if (!CheckMP3()) + { + return false; + } + + return true; } -void MP3ImportFileHandle::ImportID3(Tags *tags) +bool MP3ImportFileHandle::CheckID3() +{ + wxFileOffset mp3Start = 0; + + // An ID3v2 tag header is 10 bytes + if (mFile.Read(mInputBuffer, 10) != 10 || mFile.Error()) + { + // An error if we can't read 10 bytes or a true error occurred + wxLogMessage(wxT("Couldn't read 10 bytes while searching for ID3v2 header")); + return false; + } + + // Does it look like an ID3v2 tag? + if (memcmp(mInputBuffer, "ID3", 3) == 0) + { + // Get and decode the length + wxFileOffset len = (mInputBuffer[6] & 0x7f); + len = (len << 7) | (mInputBuffer[7] & 0x7f); + len = (len << 7) | (mInputBuffer[8] & 0x7f); + len = (len << 7) | (mInputBuffer[9] & 0x7f); + + // Start of MP3 should 10 for the ID3 header + length of tags + mp3Start = 10 + len; + + // Remember that we have tags + mHaveID3 = true; + } + + // ID3v1 tags always start 128 bytes from the end of a file + mFileLen = mFile.Seek(-128, wxFromEnd); + if (mFileLen == wxInvalidOffset || mFile.Error()) + { + // An error if we can't seek to 128 before end (MP3 files will always be + // longer than this) or a true error occurred + wxLogMessage(wxT("Couldn't seek to ID3v1 header")); + return false; + } + + // Read in possible header + if (mFile.Read(mInputBuffer, 3) != 3 || mFile.Error()) + { + // An error if we can't read the ID3v1 header + wxLogMessage(wxT("Couldn't read 3 bytes while searching for the ID3v1 tag")); + return false; + } + + // Does it look like an ID3v1 tag? + if (memcmp(mInputBuffer, "TAG", 3) == 0) + { + // Remember that we have tags + mHaveID3 = true; + } + else + { + // No, so adjust file length + mFileLen += 128; + } + + // Look for an ID3v2 trailer tag (should probably try to load them but I can't find + // any sample files or apps to test with) + if (mFile.Seek(mFileLen - 10, wxFromStart) == wxInvalidOffset || mFile.Error()) + { + // An error if we can't seek to 10 before end or before the v1 tag (MP3 files will + // always be longer than this) or a true error occurred + wxLogMessage(wxT("Couldn't seek while searching for ID3v2 footer")); + return false; + } + + // An ID3v2 tag footer is 10 bytes + if (mFile.Read(mInputBuffer, 10) != 10 || mFile.Error()) + { + // An error if we can't read the footer or a true error occurred + wxLogMessage(wxT("Couldn't read 10 bytes while searching for ID3v2 footer")); + return false; + } + + // Does it look like a footer tag? + if (memcmp(mInputBuffer, "3DI", 3) == 0) + { + // Get and decode the length + wxFileOffset len = (mInputBuffer[6] & 0x7f); + len = (len << 7) | (mInputBuffer[7] & 0x7f); + len = (len << 7) | (mInputBuffer[8] & 0x7f); + len = (len << 7) | (mInputBuffer[9] & 0x7f); + + // Yes, so "remove" tags from the file's length + // (10 for the 3DI header, len for the tag data, and 10 more for the ID3 header) + mFileLen -= (10 + len + 10); + + // Remember that we have tags + mHaveID3 = true; + } + + // Return to the end of the ID3v2 tags + if (mFile.Seek(mp3Start, wxFromStart) == wxInvalidOffset || mFile.Error()) + { + // An error if we can't seek to the start of the file or a true error occurred + wxLogMessage(wxT("Couldn't seek to beginning of the MP3 frames")); + return false; + } + + // Finally, remove the length of any ID3v2 tags from the file's length + mFileLen -= mp3Start; + + // Reset input controls + mFilePos = 0; + mInputBufferLen = 0; + + return true; +} + +bool MP3ImportFileHandle::CheckMP3() +{ + // Load as much as will fit into the buffer + if (!FillBuffer()) + { + return false; + } + + // Scan the input buffer for an MP3 frame. We limit it to the input buffer len + // minus 4 since we need to examine the full header and a header is 4 bytes. + auto header = mInputBuffer; + for (int i = 0, limit = mInputBufferLen - 4; i < limit; ++i, ++header) + { + if ((header[0] & 0xff) != 0xff) + { + continue; // first 8 bits must be '1' + } + + if ((header[1] & 0xe0) != 0xe0) + { + continue; // first 3 bits must be '1' + } + + if ((header[1] & 0x18) == 0x08) + { + continue; // not MPEG-1, -2 or -2.5 + } + + switch (header[1] & 0x06) + { + case 0x02: // Layer 3 + case 0x04: // Layer 2 + case 0x06: // Layer 1 + break; + + default: + continue; // no/invalid Layer I, II and III + } + + if ((header[2] & 0xf0) == 0xf0) + { + continue; // bad bitrate + } + + if ((header[2] & 0x0c) == 0x0c) + { + continue; // no sample frequency with (32,44.1,48)/(1,2,4) + } + + static const char abl2[16] = { 0, 7, 7, 7, 0, 7, 0, 0, 0, 0, 0, 8, 8, 8, 8, 8 }; + if ((header[1] & 0x18) == 0x18 && (header[1] & 0x06) == 0x04 && abl2[header[2] >> 4] & (1 << (header[3] >> 6))) + { + continue; // bad Layer II sample frequency + } + + if ((header[3] & 0xc0) != 0x40 && (header[3] & 0x30) != 0) + { + continue; // mode extension when not joint stereo + } + + if ((header[3] & 0x03) == 0x02) + { + continue; // reserved enphasis mode + } + + // Resync the real file position to the located frame + if (mFile.Seek(i - mFilePos, wxFromCurrent) == wxInvalidOffset || mFile.Error()) + { + // An error if we can't seek to the start of the file or a true error occurred + wxLogMessage(wxT("Couldn't resync to the first MP3 frame")); + return false; + } + + // Reset file controls + mFilePos = 0; + mFileLen -= i; + mInputBufferLen = 0; + + // Log the number of bytes skipped + if (i) + { + wxLogMessage(wxT("Skipped %d bytes while searching for first MP3 frame."), i); + } + + return true; + } + + // Didn't find the first MP3 frame...give up. + return false; +} + +bool MP3ImportFileHandle::FillBuffer() +{ + // We either want enough to fill the input buffer or what's left in the file + auto want = wxMin(INPUT_BUFFER_SIZE - mInputBufferLen, mFileLen - mFilePos); + if (want > 0) + { + // We should always get what we ask for + auto got = mFile.Read(&mInputBuffer[mInputBufferLen], want); + if (got != want || mFile.Error()) + { + return false; + } + + // Adjust input control + mInputBufferLen += got; + mFilePos += got; + } + + // MAD requires that we add MAD_BUFFER_GUARD extra bytes when we've processed + // all of the MP3 frames. Otherwise, we will drop the last frame. + if (mFilePos == mFileLen) + { + memset(&mInputBuffer[mInputBufferLen], 0, MAD_BUFFER_GUARD); + mInputBufferLen += MAD_BUFFER_GUARD; + } + + return true; +} + +void MP3ImportFileHandle::LoadID3(Tags *tags) { #ifdef USE_LIBID3TAG - wxFile f; // will be closed when it goes out of scope - struct id3_file *fp = NULL; - auto cleanup = finally([&]{ - if (fp) - id3_file_close(fp); - }); - - if (f.Open(mFilename)) { - // Use id3_file_fdopen() instead of id3_file_open since wxWidgets can open a - // file with a Unicode name and id3_file_open() can't (under Windows). - fp = id3_file_fdopen(f.fd(), ID3_FILE_MODE_READONLY); - } - - if (!fp) { - return; - } - - // The file descriptor is now owned by "fp", so we must tell "f" to forget - // about it. - f.Detach(); - - struct id3_tag *tp = id3_file_tag(fp); - if (!tp) - return; - tags->Clear(); - // Loop through all frames + struct id3_file *id3file = NULL; + auto cleanup = finally([&] + { + if (id3file) + { + id3_file_close(id3file); + } + }); + + // Use id3_file_fdopen() instead of id3_file_open since wxWidgets can open a + // file with a Unicode name and id3_file_open() can't (under Windows). + id3file = id3_file_fdopen(mFile.fd(), ID3_FILE_MODE_READONLY); + if (!id3file) + { + return; + } + + // The file descriptor is now owned by "id3file", so we must tell "mFile" to forget + // about it. + mFile.Detach(); + + // Load the tags + struct id3_tag *id3tags = id3_file_tag(id3file); + if (!id3tags) + { + return; + } + + // Convert from libid3tag's ucs4 type to wxString. + // + // The ucs4 type is unsigned long which can be 8 bytes instead + // of the expected 4 bytes for a UTF-32 character, so we have + // to convert to unsigned int and then to wxString. + wxMBConvUTF32 converter; + auto toString = [=](const id3_ucs4_t *in) + { + // Count the number of characters + size_t len = 0; + for (const id3_ucs4_t *p = in; *p; p++) + { + len++; + } + + // Would like to use std::dynarray or runtime-sized array, + // but VS doesn't support either. + wxUint32 *buf = (wxUint32 *) alloca((len + 1) * sizeof(wxUint32)); + + // Copy and convert to unsigned int + wxUint32 *out; + for (out = buf; *in; in++, out++) + { + *out = (wxUint32) (*in); + } + *out = 0; + + // Finally convert to and return wxString + return wxString((char *) buf, converter); + }; + + // Extract tags from ID3 frames and add to our tags bool have_year = false; - for (int i = 0; i < (int) tp->nframes; i++) { - struct id3_frame *frame = tp->frames[i]; + for (unsigned int i = 0; i < id3tags->nframes; ++i) + { + struct id3_frame *frame = id3tags->frames[i]; - // wxPrintf("ID: %08x '%4s'\n", (int) *(int *)frame->id, frame->id); - // wxPrintf("Desc: %s\n", frame->description); - // wxPrintf("Num fields: %d\n", frame->nfields); +#if 0 + wxLogDebug("ID: %08x '%4s'", (int) *(int *)frame->id, frame->id); + wxLogDebug("Desc: %s", frame->description); + wxLogDebug("Num fields: %d", frame->nfields); - // for (int j = 0; j < (int) frame->nfields; j++) { - // wxPrintf("field %d type %d\n", j, frame->fields[j].type ); - // if (frame->fields[j].type == ID3_FIELD_TYPE_STRINGLIST) { - // wxPrintf("num strings %d\n", frame->fields[j].stringlist.nstrings); - // } - // } + for (unsigned int j = 0; j < frame->nfields; ++j) + { + wxLogDebug("field %d type %d", j, frame->fields[j].type); + if (frame->fields[j].type == ID3_FIELD_TYPE_STRINGLIST) + { + wxLogDebug("num strings %d", frame->fields[j].stringlist.nstrings); + } + } +#endif - wxString n, v; + wxString n; + wxString v; // Determine the tag name - if (strcmp(frame->id, ID3_FRAME_TITLE) == 0) { + if (strcmp(frame->id, ID3_FRAME_TITLE) == 0) + { n = TAG_TITLE; } - else if (strcmp(frame->id, ID3_FRAME_ARTIST) == 0) { + else if (strcmp(frame->id, ID3_FRAME_ARTIST) == 0) + { n = TAG_ARTIST; } - else if (strcmp(frame->id, ID3_FRAME_ALBUM) == 0) { + else if (strcmp(frame->id, ID3_FRAME_ALBUM) == 0) + { n = TAG_ALBUM; } - else if (strcmp(frame->id, ID3_FRAME_TRACK) == 0) { + else if (strcmp(frame->id, ID3_FRAME_TRACK) == 0) + { n = TAG_TRACK; } - else if (strcmp(frame->id, ID3_FRAME_YEAR) == 0) { + else if (strcmp(frame->id, ID3_FRAME_YEAR) == 0) + { // LLL: When libid3tag encounters the "TYER" tag, it converts it to a // "ZOBS" (obsolete) tag and adds a "TDRC" tag at the end of the // list of tags using the first 4 characters of the "TYER" tag. // Since we write both the "TDRC" and "TYER" tags, the "TDRC" tag - // will always be encountered first in the list. We want use it - // since the converted "TYER" tag may have been truncated. - if (have_year) { + // will always be encountered first in the list. We want to use + // it since the converted "TYER" tag may have been truncated. + if (have_year) + { continue; } n = TAG_YEAR; have_year = true; } - else if (strcmp(frame->id, ID3_FRAME_COMMENT) == 0) { + else if (strcmp(frame->id, ID3_FRAME_COMMENT) == 0) + { n = TAG_COMMENTS; } - else if (strcmp(frame->id, ID3_FRAME_GENRE) == 0) { + else if (strcmp(frame->id, ID3_FRAME_GENRE) == 0) + { n = TAG_GENRE; } - else { + else + { // Use frame description as default tag name. The descriptions // may include several "meanings" separated by "/" characters, so // we just use the first meaning n = UTF8CTOWX(frame->description).BeforeFirst(wxT('/')); } + // Now get the tag value const id3_ucs4_t *ustr = NULL; - if (n == TAG_COMMENTS) { + if (n == TAG_COMMENTS) + { ustr = id3_field_getfullstring(&frame->fields[3]); } - else if (frame->nfields == 3) { + else if (frame->nfields == 3) + { ustr = id3_field_getstring(&frame->fields[1]); - if (ustr) { - // Is this duplication really needed? - MallocString<> str{ (char *)id3_ucs4_utf8duplicate(ustr) }; - n = UTF8CTOWX(str.get()); + if (ustr) + { + n = toString(ustr); } ustr = id3_field_getstring(&frame->fields[2]); } - else if (frame->nfields >= 2) { + else if (frame->nfields >= 2) + { ustr = id3_field_getstrings(&frame->fields[1], 0); } - if (ustr) { - // Is this duplication really needed? - MallocString<> str{ (char *)id3_ucs4_utf8duplicate(ustr) }; - v = UTF8CTOWX(str.get()); + // Convert the value + if (ustr) + { + v = toString(ustr); } - if (!n.empty() && !v.empty()) { + // And add it to the list of tags + if (!n.empty() && !v.empty()) + { tags->SetTag(n, v); } } // Convert v1 genre to name - if (tags->HasTag(TAG_GENRE)) { + if (tags->HasTag(TAG_GENRE)) + { long g = -1; - if (tags->GetTag(TAG_GENRE).ToLong(&g)) { + if (tags->GetTag(TAG_GENRE).ToLong(&g)) + { tags->SetTag(TAG_GENRE, tags->GetGenre(g)); } } -#endif // ifdef USE_LIBID3TAG -} - -// -// MAD Callbacks -// - -/* The input callback is called when the decoder wants more data. */ - -enum mad_flow input_cb(void *_data, struct mad_stream *stream) -{ - struct private_data *data = (struct private_data *)_data; - - data->updateResult = data->progress->Update((wxULongLong_t)data->file->Tell(), - (wxULongLong_t)data->file->Length() != 0 ? - (wxULongLong_t)data->file->Length() : 1); - if(data->updateResult != ProgressResult::Success) - return MAD_FLOW_STOP; - - if (data->eof) { - /* different from data->File->Eof(), this means the underlying - file has reached eof *and* we have subsequently supplied the - final padding zeros */ - return MAD_FLOW_STOP; - } - -#ifdef USE_LIBID3TAG - if (!data->id3checked) { - data->file->Read(data->inputBuffer.get(), ID3_TAG_QUERYSIZE); - int len = id3_tag_query(data->inputBuffer.get(), ID3_TAG_QUERYSIZE); - if (len > 0) { - data->file->Seek(len, wxFromStart); - } - else { - data->file->Seek(0); - } - - data->id3checked = true; - } +#else + (void) tags; #endif +} - /* "Each time you refill your buffer, you need to preserve the data in - * your existing buffer from stream.next_frame to the end. - * - * This usually amounts to calling memmove() on this unconsumed portion - * of the buffer and appending NEW data after it, before calling - * mad_stream_buffer()" - * -- Rob Leslie, on the mad-dev mailing list */ +// +// MAD Callbacks +// - int unconsumedBytes; - if(stream->next_frame ) { - /* we must use inputBufferFill instead of INPUT_BUFFER_SIZE here - because the final buffer of the file may be only partially - filled, and we would otherwise be providing too much input - after eof */ - unconsumedBytes = data->inputBuffer.get() + data->inputBufferFill - - stream->next_frame; - if (unconsumedBytes > 0) - memmove(data->inputBuffer.get(), stream->next_frame, unconsumedBytes); - } - else - unconsumedBytes = 0; +// The input callback is called when the decoder wants more data +mad_flow MP3ImportFileHandle::input_cb(void *that, + struct mad_stream *stream) +{ + auto cb = [&]() + { + return ((MP3ImportFileHandle *) that)->InputCB(stream); + }; - if (data->file->Eof() && - (unconsumedBytes + MAD_BUFFER_GUARD < INPUT_BUFFER_SIZE)) { - - /* supply the requisite MAD_BUFFER_GUARD zero bytes to ensure - the final frame gets decoded properly, then finish */ - - memset(data->inputBuffer.get() + unconsumedBytes, 0, MAD_BUFFER_GUARD); - mad_stream_buffer - (stream, data->inputBuffer.get(), MAD_BUFFER_GUARD + unconsumedBytes); - - data->eof = true; /* so on next call, we will tell mad to stop */ - - return MAD_FLOW_CONTINUE; + return GuardedCall(cb, MakeSimpleGuard(MAD_FLOW_BREAK)); +} + +mad_flow MP3ImportFileHandle::InputCB(struct mad_stream *stream) +{ + // Update the progress + mUpdateResult = mProgress->Update((wxLongLong_t) mFilePos, (wxLongLong_t) mFileLen); + if (mUpdateResult != ProgressResult::Success) + { + return MAD_FLOW_STOP; } - off_t read = data->file->Read(data->inputBuffer.get() + unconsumedBytes, - INPUT_BUFFER_SIZE - unconsumedBytes); + // Stop if we've consumed all of the MP3 data + if (mFilePos == mFileLen) + { + return MAD_FLOW_STOP; + } - mad_stream_buffer(stream, data->inputBuffer.get(), read + unconsumedBytes); + // "Each time you refill your buffer, you need to preserve the data in + // your existing buffer from stream.next_frame to the end. + // + // This usually amounts to calling memmove() on this unconsumed portion + // of the buffer and appending NEW data after it, before calling + // mad_stream_buffer() + // -- Rob Leslie, on the mad-dev mailing list + if (stream->next_frame) + { + mInputBufferLen -= (stream->next_frame - mInputBuffer); + memmove(mInputBuffer, stream->next_frame, mInputBufferLen); + } - data->inputBufferFill = int(read + unconsumedBytes); + // Refill the buffer + if (!FillBuffer()) + { + return MAD_FLOW_BREAK; + } + + // And give it back to MAD + mad_stream_buffer(stream, mInputBuffer, mInputBufferLen); return MAD_FLOW_CONTINUE; } -/* The output callback is called every time the decoder has finished decoding - * a frame, allowing us to use the decoded data */ - -enum mad_flow output_cb(void *_data, - struct mad_header const * WXUNUSED(header), - struct mad_pcm *pcm) +// The filter callback let's us examine each frame and decide if it should be +// kept or tossed. We use this to detect the Xing or LAME tags. +mad_flow MP3ImportFileHandle::filter_cb(void *that, + struct mad_stream const *stream, + struct mad_frame *frame) { - // Don't C++ exceptions propagate through mad - return GuardedCall< mad_flow > ( [&] { - int samplerate; - struct private_data *data = (struct private_data *)_data; + auto cb = [&]() + { + return ((MP3ImportFileHandle *) that)->FilterCB(stream, frame); + }; - samplerate= pcm->samplerate; - auto channels = pcm->channels; - const auto samples = pcm->length; - - /* If this is the first run, we need to create the WaveTracks that - * will hold the data. We do this now because now is the first - * moment when we know how many channels there are. */ - - if(data->channels.empty()) { - data->channels.resize(channels); - - auto format = QualityPrefs::SampleFormatChoice(); - - for(auto &channel: data->channels) - channel = data->trackFactory->NewWaveTrack(format, samplerate); - - data->numChannels = channels; - } - else { - // This is not the first run, protect us from libmad glitching - // on the number of channels - channels = data->numChannels; - } - - /* TODO: get rid of this by adding fixed-point support to SampleFormat. - * For now, we allocate temporary float buffers to convert the fixed - * point samples into something we can feed to the WaveTrack. Allocating - * big blocks of data like this isn't a great idea, but it's temporary. - */ - FloatBuffers channelBuffers{ channels, samples }; - for(size_t smpl = 0; smpl < samples; smpl++) - for(int chn = 0; chn < channels; chn++) - channelBuffers[chn][smpl] = scale(pcm->samples[chn][smpl]); - - for(int chn = 0; chn < channels; chn++) - data->channels[chn]->Append((samplePtr)channelBuffers[chn].get(), - floatSample, - samples); - - return MAD_FLOW_CONTINUE; - }, MakeSimpleGuard(MAD_FLOW_BREAK) ); + return GuardedCall(cb, MakeSimpleGuard(MAD_FLOW_BREAK)); } -enum mad_flow error_cb(void * WXUNUSED(_data), struct mad_stream * WXUNUSED(stream), - struct mad_frame * WXUNUSED(frame)) +enum mad_flow MP3ImportFileHandle::FilterCB(struct mad_stream const *stream, + struct mad_frame *frame) { -/* enum mad_flow { - MAD_FLOW_CONTINUE = 0x0000, - MAD_FLOW_STOP = 0x0010, - MAD_FLOW_BREAK = 0x0011, - MAD_FLOW_IGNORE = 0x0020 - }; */ - /* - wxPrintf("decoding error 0x%04x (%s)\n", - stream->error, mad_stream_errorstr(stream)); - */ + // We only want to inspect the first frame, so disable future calls + mDecoder.filter_func = nullptr; + + // Get the ancillary data ptr and length + auto ptr = stream->anc_ptr.byte; + int len = stream->anc_bitlen / 8; + + // Ensure it's something we can understand + if (len < 4 || (memcmp(ptr, "Xing", 4) != 0 && memcmp(ptr, "Info", 4) != 0)) + { + return MAD_FLOW_CONTINUE; + } + + // Skip the tag + ptr += 4; + len -= 4; + + enum flagBits + { + hasFrames = 0x0001, + hasBytes = 0x0002, + hasToc = 0x0004, + hasScale = 0x0008 + }; + + // Extract the flags + unsigned int flags = (((((ptr[0] << 8) + ptr[1]) << 8) + ptr[2]) << 8) + ptr[3]; + ptr += 4; + len -= 4; + + // Skip the number of frames + if (len >= 4 && flags & hasFrames) + { + ptr += 4; + len -= 4; + } + + // Skip the number of bytes + if (len >= 4 && flags & hasBytes) + { + ptr += 4; + len -= 4; + } + + // Skip the TOC + if (len >= 100 && flags & hasToc) + { + ptr += 100; + len -= 100; + } + + // Skip the VBR Scale + if (len >= 4 && flags && hasScale) + { + ptr += 4; + len -= 4; + } + + // Bail if LAME wasn't the encoder or we don't have enough ancillary data left + if (len < 24 || memcmp(ptr, "LAME", 4) != 0) + { + return MAD_FLOW_IGNORE; + } + + // Skip down to the delay and padding + ptr += 21; + len -= 21; + + // Extract the delay and padding and adjust for decoder delay + mDelay = (ptr[0] << 4) + (ptr[1] >> 4) + MAD_DELAY; + mPadding = ((ptr[1] & 0x0f) << 8) + ptr[2] - MAD_DELAY; + if (mPadding < 0) + { + mPadding = 0; + } + + return MAD_FLOW_IGNORE; +} + +// The output callback is called every time the decoder has finished decoding + // a frame, allowing us to use the decoded data +mad_flow MP3ImportFileHandle::output_cb(void *that, + struct mad_header const *header, + struct mad_pcm *pcm) +{ + auto cb = [&]() + { + return ((MP3ImportFileHandle *) that)->OutputCB(header, pcm); + }; + + return GuardedCall(cb, MakeSimpleGuard(MAD_FLOW_BREAK)); +} + +enum mad_flow MP3ImportFileHandle::OutputCB(struct mad_header const * WXUNUSED(header), + struct mad_pcm *pcm) +{ + // If this is the first run, we need to create the WaveTracks that + // will hold the data. We do this now because now is the first + // moment when we know how many channels there are. + if (mChannels.empty()) + { + mNumChannels = pcm->channels; + + mChannels.resize(mNumChannels); + + auto format = QualityPrefs::SampleFormatChoice(); + + for (auto &channel: mChannels) + { + channel = mTrackFactory->NewWaveTrack(format, pcm->samplerate); + } + } + + // Get the number of samples in each channel + auto samples = pcm->length; + + // Convert libmad samples to float and append to WaveTracks + for (int chn = 0; chn < mNumChannels; ++chn) + { + // Number of samples will never be more than 1152 + float sampleBuf[1152]; + wxASSERT(samples <= 1152); + + // Copy over the samples + for (int sample = 0; sample < samples; ++sample) + { + // Convert libmad's fixed point representation to float + sampleBuf[sample] = ((float) pcm->samples[chn][sample] / (1L << MAD_F_FRACBITS)); + } + + // And append to the channel + mChannels[chn]->Append((samplePtr) sampleBuf, floatSample, samples); + } return MAD_FLOW_CONTINUE; - - /* return MAD_FLOW_BREAK; */ } +// The error callback is used when MAD encounters an error and needs to know +// how it should proceed +mad_flow MP3ImportFileHandle::error_cb(void *that, + struct mad_stream *stream, + struct mad_frame *frame) +{ + auto cb = [&]() + { + return ((MP3ImportFileHandle *) that)->ErrorCB(stream, frame); + }; -#endif /* defined(USE_LIBMAD) */ + return GuardedCall(cb, MakeSimpleGuard(MAD_FLOW_BREAK)); +} + +enum mad_flow MP3ImportFileHandle::ErrorCB(struct mad_stream *stream, + struct mad_frame *frame) +{ + // You always get a LOSTSYNC error at EOF, so just ignore it + if (stream->error == MAD_ERROR_LOSTSYNC && mFilePos == mFileLen) + { + return MAD_FLOW_CONTINUE; + } + + // This can happen when parsing the first frame. We can use the number of channels + // for it hasn't yet been determined. + if (stream->error == MAD_ERROR_BADDATAPTR && mNumChannels == 0) + { + return MAD_FLOW_CONTINUE; + } + + // Let the user know about the error + AudacityMessageBox(XO("MP3 Decoding Failed:\n\n%s").Format(mad_stream_errorstr(stream))); + + return MAD_FLOW_BREAK; +} + +#endif