diff --git a/CMakeLists.txt b/CMakeLists.txt index f32cdb26d1a..936e8a85de5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1936,7 +1936,14 @@ endif() # FAAD AAC audio file decoder plugin find_package(MP4) find_package(MP4v2) -option(FAAD "FAAD AAC audio file decoder support" OFF) +# It is enabled by default on Linux only, because other targets have other solutions. +# Used FAAD_DEFAULT because the complex expression is not supported by cmake_dependent_option() +if(UNIX AND NOT APPLE AND (MP4_FOUND OR MP4v2_FOUND)) + set(FAAD_DEFAULT TRUE) +else() + set(FAAD_DEFAULT FALSE) +endif() +cmake_dependent_option(FAAD "FAAD AAC audio file decoder support" ON "FAAD_DEFAULT" OFF) if(FAAD) if(NOT MP4_FOUND AND NOT MP4v2_FOUND) message(FATAL_ERROR "FAAD AAC audio support requires libmp4 or libmp4v2 with development headers.") diff --git a/src/sources/audiosource.cpp b/src/sources/audiosource.cpp index d603ff7b0c7..5a4ec747bef 100644 --- a/src/sources/audiosource.cpp +++ b/src/sources/audiosource.cpp @@ -60,7 +60,8 @@ bool AudioSource::initFrameIndexRangeOnce( << frameIndexRange; return false; // abort } - VERIFY_OR_DEBUG_ASSERT(m_frameIndexRange.empty() || (m_frameIndexRange == frameIndexRange)) { + if (!m_frameIndexRange.empty() && + m_frameIndexRange != frameIndexRange) { kLogger.warning() << "Frame index range has already been initialized to" << m_frameIndexRange @@ -80,9 +81,8 @@ bool AudioSource::initChannelCountOnce( << channelCount; return false; // abort } - VERIFY_OR_DEBUG_ASSERT( - !m_signalInfo.getChannelCount().isValid() || - m_signalInfo.getChannelCount() == channelCount) { + if (m_signalInfo.getChannelCount().isValid() && + m_signalInfo.getChannelCount() != channelCount) { kLogger.warning() << "Channel count has already been initialized to" << m_signalInfo.getChannelCount() @@ -102,9 +102,8 @@ bool AudioSource::initSampleRateOnce( << sampleRate; return false; // abort } - VERIFY_OR_DEBUG_ASSERT( - !m_signalInfo.getSampleRate().isValid() || - m_signalInfo.getSampleRate() == sampleRate) { + if (m_signalInfo.getSampleRate().isValid() && + m_signalInfo.getSampleRate() != sampleRate) { kLogger.warning() << "Sample rate has already been initialized to" << m_signalInfo.getSampleRate() diff --git a/src/sources/libfaadloader.cpp b/src/sources/libfaadloader.cpp index 98d42927603..ea1aa73716e 100644 --- a/src/sources/libfaadloader.cpp +++ b/src/sources/libfaadloader.cpp @@ -1,27 +1,32 @@ #include "sources/libfaadloader.h" + #include "util/logger.h" namespace { -mixxx::Logger kLogger("LibFaadLoader"); +mixxx::Logger kLogger("faad2::LibLoader"); } // anonymous namespace +namespace faad2 { + // static -LibFaadLoader* LibFaadLoader::Instance() { - static LibFaadLoader libFaadLoader; +LibLoader* LibLoader::Instance() { + static LibLoader libFaadLoader; return &libFaadLoader; } -LibFaadLoader::LibFaadLoader() +LibLoader::LibLoader() : m_neAACDecOpen(nullptr), m_neAACDecGetCurrentConfiguration(nullptr), m_neAACDecSetConfiguration(nullptr), + m_neAACDecInit(nullptr), m_neAACDecInit2(nullptr), m_neAACDecClose(nullptr), m_neAACDecPostSeekReset(nullptr), m_neAACDecDecode2(nullptr), - m_neAACDecGetErrorMessage(nullptr) { + m_neAACDecGetErrorMessage(nullptr), + m_neAACDecGetVersion(nullptr) { // Load shared library QStringList libnames; #ifdef __WINDOWS__ @@ -58,6 +63,8 @@ LibFaadLoader::LibFaadLoader() m_pLibrary->resolve("NeAACDecGetCurrentConfiguration")); m_neAACDecSetConfiguration = reinterpret_cast( m_pLibrary->resolve("NeAACDecSetConfiguration")); + m_neAACDecInit = reinterpret_cast( + m_pLibrary->resolve("NeAACDecInit")); m_neAACDecInit2 = reinterpret_cast( m_pLibrary->resolve("NeAACDecInit2")); m_neAACDecClose = reinterpret_cast( @@ -68,62 +75,75 @@ LibFaadLoader::LibFaadLoader() m_pLibrary->resolve("NeAACDecDecode2")); m_neAACDecGetErrorMessage = reinterpret_cast( m_pLibrary->resolve("NeAACDecGetErrorMessage")); + m_neAACDecGetVersion = reinterpret_cast( + m_pLibrary->resolve("NeAACDecGetVersion")); if (!m_neAACDecOpen || !m_neAACDecGetCurrentConfiguration || !m_neAACDecSetConfiguration || + !m_neAACDecInit || !m_neAACDecInit2 || !m_neAACDecClose || !m_neAACDecPostSeekReset || !m_neAACDecDecode2 || !m_neAACDecGetErrorMessage) { - kLogger.debug() << "NeAACDecOpen:" << m_neAACDecOpen; - kLogger.debug() << "NeAACDecGetCurrentConfiguration:" << m_neAACDecGetCurrentConfiguration; - kLogger.debug() << "NeAACDecSetConfiguration:" << m_neAACDecSetConfiguration; - kLogger.debug() << "NeAACDecInit2:" << m_neAACDecInit2; - kLogger.debug() << "NeAACDecClose:" << m_neAACDecClose; - kLogger.debug() << "NeAACDecPostSeekReset:" << m_neAACDecPostSeekReset; - kLogger.debug() << "NeAACDecDecode2:" << m_neAACDecDecode2; - kLogger.debug() << "NeAACDecGetErrorMessage:" << m_neAACDecGetErrorMessage; + kLogger.warning() << "NeAACDecOpen:" << m_neAACDecOpen; + kLogger.warning() << "NeAACDecGetCurrentConfiguration:" << m_neAACDecGetCurrentConfiguration; + kLogger.warning() << "NeAACDecSetConfiguration:" << m_neAACDecSetConfiguration; + kLogger.warning() << "NeAACDecInit:" << m_neAACDecInit; + kLogger.warning() << "NeAACDecInit2:" << m_neAACDecInit2; + kLogger.warning() << "NeAACDecClose:" << m_neAACDecClose; + kLogger.warning() << "NeAACDecPostSeekReset:" << m_neAACDecPostSeekReset; + kLogger.warning() << "NeAACDecDecode2:" << m_neAACDecDecode2; + kLogger.warning() << "NeAACDecGetErrorMessage:" << m_neAACDecGetErrorMessage; + kLogger.warning() << "NeAACDecGetVersion:" << m_neAACDecGetVersion; // From FAAD 2.8.2 m_neAACDecOpen = nullptr; m_neAACDecGetCurrentConfiguration = nullptr; m_neAACDecSetConfiguration = nullptr; + m_neAACDecInit = nullptr; m_neAACDecInit2 = nullptr; m_neAACDecClose = nullptr; m_neAACDecPostSeekReset = nullptr; m_neAACDecDecode2 = nullptr; m_neAACDecGetErrorMessage = nullptr; + m_neAACDecGetVersion = nullptr; m_pLibrary->unload(); m_pLibrary.reset(); return; } - kLogger.info() << "Successfully loaded library" << m_pLibrary->fileName(); + char* faad_id_string = nullptr; + char const* version = + GetVersion(&faad_id_string, nullptr) == 0 ? faad_id_string : ""; + kLogger.info() + << "Successfully loaded FAAD2 library" + << m_pLibrary->fileName() + << "version" + << version; }; -bool LibFaadLoader::isLoaded() { - if (m_pLibrary) { - return m_pLibrary->isLoaded(); - } - return false; +bool LibLoader::isLoaded() const { + return m_pLibrary && m_pLibrary->isLoaded(); } -LibFaadLoader::Handle LibFaadLoader::Open() { +DecoderHandle LibLoader::Open() const { if (m_neAACDecOpen) { return m_neAACDecOpen(); } return nullptr; } -LibFaadLoader::Configuration* -LibFaadLoader::GetCurrentConfiguration(Handle hDecoder) { +Configuration* +LibLoader::GetCurrentConfiguration( + DecoderHandle hDecoder) const { if (m_neAACDecGetCurrentConfiguration) { return m_neAACDecGetCurrentConfiguration(hDecoder); } return nullptr; } -unsigned char LibFaadLoader::SetConfiguration( - Handle hDecoder, Configuration* pConfig) { +unsigned char LibLoader::SetConfiguration( + DecoderHandle hDecoder, + Configuration* pConfig) const { if (m_neAACDecSetConfiguration) { return m_neAACDecSetConfiguration(hDecoder, pConfig); } @@ -133,18 +153,36 @@ unsigned char LibFaadLoader::SetConfiguration( return 0; } -// Init the library using a DecoderSpecificInfo -char LibFaadLoader::Init2( - Handle hDecoder, +long LibLoader::Init( + DecoderHandle hDecoder, unsigned char* pBuffer, - unsigned long SizeOfDecoderSpecificInfo, - unsigned long* pSamplerate, - unsigned char* pChannels) { + unsigned long sizeofBuffer, + unsigned long* pSampleRate, + unsigned char* pChannels) const { + if (m_neAACDecInit) { + return m_neAACDecInit(hDecoder, + pBuffer, + sizeofBuffer, + pSampleRate, + pChannels); + } + // Return values: + // < 0 – Error + // 0 - OK + return -1; +} + +char LibLoader::Init2( + DecoderHandle hDecoder, + unsigned char* pBuffer, + unsigned long sizeofBuffer, + unsigned long* pSampleRate, + unsigned char* pChannels) const { if (m_neAACDecInit2) { return m_neAACDecInit2(hDecoder, pBuffer, - SizeOfDecoderSpecificInfo, - pSamplerate, + sizeofBuffer, + pSampleRate, pChannels); } // Return values: @@ -153,25 +191,28 @@ char LibFaadLoader::Init2( return -1; } -void LibFaadLoader::Close(Handle hDecoder) { +void LibLoader::Close( + DecoderHandle hDecoder) const { if (m_neAACDecClose) { m_neAACDecClose(hDecoder); } } -void LibFaadLoader::PostSeekReset(Handle hDecoder, long frame) { +void LibLoader::PostSeekReset( + DecoderHandle hDecoder, + long frame) const { if (m_neAACDecPostSeekReset) { m_neAACDecPostSeekReset(hDecoder, frame); } } -void* LibFaadLoader::Decode2( - Handle hDecoder, +void* LibLoader::Decode2( + DecoderHandle hDecoder, FrameInfo* pInfo, unsigned char* pBuffer, unsigned long bufferSize, void** ppSampleBuffer, - unsigned long sampleBufferSize) { + unsigned long sampleBufferSize) const { if (m_neAACDecDecode2) { return m_neAACDecDecode2( hDecoder, @@ -184,9 +225,38 @@ void* LibFaadLoader::Decode2( return nullptr; } -char* LibFaadLoader::GetErrorMessage(unsigned char errcode) { +char* LibLoader::GetErrorMessage( + unsigned char errcode) const { if (m_neAACDecGetErrorMessage) { return m_neAACDecGetErrorMessage(errcode); } return nullptr; } + +int LibLoader::GetVersion( + char** faad_id_string, + char** faad_copyright_string) const { + if (m_neAACDecGetVersion) { + return m_neAACDecGetVersion(faad_id_string, faad_copyright_string); + } + // Return values: + // < 0 – Error + // 0 - OK + return -1; +} + +} // namespace faad2 + +QDebug operator<<( + QDebug dbg, + const faad2::Configuration& cfg) { + return dbg + << "FAAD2 Configuration{" + << "defObjectType:" << static_cast(cfg.defObjectType) + << "defSampleRate:" << cfg.defSampleRate + << "outputFormat:" << static_cast(cfg.outputFormat) + << "downMatrix:" << static_cast(cfg.downMatrix) + << "useOldADTSFormat:" << static_cast(cfg.useOldADTSFormat) + << "dontUpSampleImplicitSBR:" << static_cast(cfg.dontUpSampleImplicitSBR) + << '}'; +} diff --git a/src/sources/libfaadloader.h b/src/sources/libfaadloader.h index 77e917f37d8..a0822559e46 100644 --- a/src/sources/libfaadloader.h +++ b/src/sources/libfaadloader.h @@ -1,9 +1,12 @@ #pragma once #include +#include #include "util/memory.h" +namespace faad2 { + // object types for AAC constexpr unsigned char MAIN = 1; constexpr unsigned char LC = 2; @@ -16,122 +19,158 @@ constexpr unsigned char LD = 23; constexpr unsigned char DRM_ER_LC = 27; // special object type for DRM // library output formats -constexpr unsigned char FAAD_FMT_16BIT = 1; -constexpr unsigned char FAAD_FMT_24BIT = 2; -constexpr unsigned char FAAD_FMT_32BIT = 3; -constexpr unsigned char FAAD_FMT_FLOAT = 4; -constexpr unsigned char FAAD_FMT_FIXED = FAAD_FMT_FLOAT; -constexpr unsigned char FAAD_FMT_DOUBLE = 5; - -class LibFaadLoader { - public: - typedef void* Handle; +constexpr unsigned char FMT_16BIT = 1; +constexpr unsigned char FMT_24BIT = 2; +constexpr unsigned char FMT_32BIT = 3; +constexpr unsigned char FMT_FLOAT = 4; +constexpr unsigned char FMT_FIXED = FMT_FLOAT; +constexpr unsigned char FMT_DOUBLE = 5; + +enum class FrameError : unsigned char { + InvalidNumberOfChannels = 12, + InvalidChannelConfiguration = 21, +}; + +typedef void* DecoderHandle; - typedef struct - { - unsigned char defObjectType; - unsigned long defSampleRate; - unsigned char outputFormat; - unsigned char downMatrix; - unsigned char useOldADTSFormat; - unsigned char dontUpSampleImplicitSBR; - } Configuration; +extern "C" { - typedef struct - { - unsigned long bytesconsumed; - unsigned long samples; - unsigned char channels; - unsigned char error; - unsigned long samplerate; +typedef struct +{ + unsigned char defObjectType; + unsigned long defSampleRate; + unsigned char outputFormat; + unsigned char downMatrix; + unsigned char useOldADTSFormat; + unsigned char dontUpSampleImplicitSBR; +} Configuration; - // SBR: 0: off, 1: on; upsample, 2: on; downsampled, 3: off; upsampled - unsigned char sbr; +typedef struct +{ + unsigned long bytesconsumed; + unsigned long samples; + unsigned char channels; + unsigned char error; + unsigned long samplerate; - // MPEG-4 ObjectType - unsigned char object_type; + // SBR: 0: off, 1: on; upsample, 2: on; downsampled, 3: off; upsampled + unsigned char sbr; - // AAC header type; MP4 will be signalled as RAW also - unsigned char header_type; + // MPEG-4 ObjectType + unsigned char object_type; - // multichannel configuration - unsigned char num_front_channels; - unsigned char num_side_channels; - unsigned char num_back_channels; - unsigned char num_lfe_channels; - unsigned char channel_position[64]; + // AAC header type; MP4 will be signalled as RAW also + unsigned char header_type; - // PS: 0: off, 1: on - unsigned char ps; - } FrameInfo; + // multichannel configuration + unsigned char num_front_channels; + unsigned char num_side_channels; + unsigned char num_back_channels; + unsigned char num_lfe_channels; + unsigned char channel_position[64]; - static LibFaadLoader* Instance(); + // PS: 0: off, 1: on + unsigned char ps; +} FrameInfo; + +} // extern "C" + +class LibLoader { + public: + static LibLoader* Instance(); - bool isLoaded(); + bool isLoaded() const; - Handle Open(); + DecoderHandle Open() const; Configuration* GetCurrentConfiguration( - Handle hDecoder); + DecoderHandle hDecoder) const; unsigned char SetConfiguration( - Handle hDecoder, Configuration* config); + DecoderHandle hDecoder, Configuration* config) const; - // Init the library using a DecoderSpecificInfo + // Init the decoder using the AAC stream (ADTS/ADIF) + long Init( + DecoderHandle hDecoder, + unsigned char* pBuffer, + unsigned long sizeofBuffer, + unsigned long* pSampleRate, + unsigned char* pChannels) const; + + // Init the decoder using a DecoderSpecificInfo char Init2( - Handle hDecoder, + DecoderHandle hDecoder, unsigned char* pBuffer, - unsigned long SizeOfDecoderSpecificInfo, - unsigned long* pSamplerate, - unsigned char* pChannels); + unsigned long sizeofBuffer, + unsigned long* pSampleRate, + unsigned char* pChannels) const; - void Close(Handle hDecoder); + void Close(DecoderHandle hDecoder) const; - void PostSeekReset(Handle hDecoder, long frame); + void PostSeekReset(DecoderHandle hDecoder, long frame) const; void* Decode2( - Handle hDecoder, + DecoderHandle hDecoder, FrameInfo* pInfo, unsigned char* pBuffer, unsigned long bufferSize, void** ppSampleBuffer, - unsigned long sampleBufferSize); + unsigned long sampleBufferSize) const; - char* GetErrorMessage(unsigned char errcode); + char* GetErrorMessage(unsigned char errcode) const; + + int GetVersion( + char** faad2_id_string, + char** faad2_copyright_string) const; private: - LibFaadLoader(); + LibLoader(); - LibFaadLoader(const LibFaadLoader&) = delete; - LibFaadLoader& operator=(const LibFaadLoader&) = delete; + LibLoader(const LibLoader&) = delete; + LibLoader& operator=(const LibLoader&) = delete; // QLibrary is not copy-able std::unique_ptr m_pLibrary; - typedef Handle (*NeAACDecOpen_t)(); + typedef DecoderHandle (*NeAACDecOpen_t)(); NeAACDecOpen_t m_neAACDecOpen; - typedef Configuration* (*NeAACDecGetCurrentConfiguration_t)(Handle); + typedef Configuration* (*NeAACDecGetCurrentConfiguration_t)(DecoderHandle); NeAACDecGetCurrentConfiguration_t m_neAACDecGetCurrentConfiguration; typedef unsigned char (*NeAACDecSetConfiguration_t)( - Handle, Configuration*); + DecoderHandle, Configuration*); NeAACDecSetConfiguration_t m_neAACDecSetConfiguration; + typedef long (*NeAACDecInit_t)( + DecoderHandle, unsigned char*, unsigned long, unsigned long*, unsigned char*); + NeAACDecInit_t m_neAACDecInit; + typedef char (*NeAACDecInit2_t)( - Handle, unsigned char*, unsigned long, unsigned long*, unsigned char*); + DecoderHandle, unsigned char*, unsigned long, unsigned long*, unsigned char*); NeAACDecInit2_t m_neAACDecInit2; - typedef void (*NeAACDecClose_t)(Handle); + typedef void (*NeAACDecClose_t)(DecoderHandle); NeAACDecClose_t m_neAACDecClose; - typedef void (*NeAACDecPostSeekReset_t)(Handle, long); + typedef void (*NeAACDecPostSeekReset_t)(DecoderHandle, long); NeAACDecPostSeekReset_t m_neAACDecPostSeekReset; typedef void* (*NeAACDecDecode2_t)( - Handle, FrameInfo*, unsigned char*, unsigned long, void**, unsigned long); + DecoderHandle, FrameInfo*, unsigned char*, unsigned long, void**, unsigned long); NeAACDecDecode2_t m_neAACDecDecode2; typedef char* (*NeAACDecGetErrorMessage_t)(unsigned char); NeAACDecGetErrorMessage_t m_neAACDecGetErrorMessage; + + typedef int (*NeAACDecGetVersion_t)( + char** faad2_id_string, + char** faad2_copyright_string); + NeAACDecGetVersion_t m_neAACDecGetVersion; }; + +} // namespace faad2 + +QDebug operator<<( + QDebug dbg, + const faad2::Configuration& cfg); diff --git a/src/sources/soundsourcem4a.cpp b/src/sources/soundsourcem4a.cpp index 81033351618..b62b304ddc8 100644 --- a/src/sources/soundsourcem4a.cpp +++ b/src/sources/soundsourcem4a.cpp @@ -22,7 +22,7 @@ namespace { const Logger kLogger("SoundSourceM4A"); // MP4SampleId is 1-based -const MP4SampleId kSampleBlockIdMin = 1; +constexpr MP4SampleId kSampleBlockIdMin = 1; // Decoding will be restarted one or more blocks of samples // before the actual position after seeking randomly in the @@ -33,16 +33,16 @@ const MP4SampleId kSampleBlockIdMin = 1; // "It must also be assumed that without an explicit value, the playback // system will trim 2112 samples from the AAC decoder output when starting // playback from any point in the bitstream." -const SINT kNumberOfPrefetchFrames = 2112; +constexpr SINT kNumberOfPrefetchFrames = 2112; // The TrackId is a 1-based index of the tracks in an MP4 file -const u_int32_t kMinTrackId = 1; +constexpr u_int32_t kMinTrackId = 1; // http://www.iis.fraunhofer.de/content/dam/iis/de/doc/ame/wp/FraunhoferIIS_Application-Bulletin_AAC-Transport-Formats.pdf // Footnote 13: "The usual frame length for AAC-LC is 1024 samples, but a 960 sample version // is used for radio broadcasting, and 480 or 512 sample versions are used for the low-delay // codecs AAC-LD and AAC-ELD." -const MP4Duration kDefaultFramesPerSampleBlock = 1024; +constexpr MP4Duration kDefaultFramesPerSampleBlock = 1024; // According to various references DecoderConfigDescriptor.bufferSizeDB // is a 24-bit unsigned integer value. @@ -52,7 +52,7 @@ const MP4Duration kDefaultFramesPerSampleBlock = 1024; // https://github.com/sannies/mp4parser/blob/master/isoparser/src/main/java/org/mp4parser/boxes/iso14496/part1/objectdescriptors/DecoderConfigDescriptor.java // http://mutagen-specs.readthedocs.io/en/latest/mp4/ // http://perso.telecom-paristech.fr/~dufourd/mpeg-4/tools.html -const u_int32_t kMaxSampleBlockInputSizeLimit = (u_int32_t(1) << 24) - 1; +constexpr u_int32_t kMaxSampleBlockInputSizeLimit = (u_int32_t(1) << 24) - 1; inline u_int32_t getMaxTrackId(MP4FileHandle hFile) { // The maximum TrackId equals the number of all tracks @@ -146,10 +146,48 @@ MP4TrackId findFirstAudioTrackId(MP4FileHandle hFile, const QString& fileName) { return MP4_INVALID_TRACK_ID; } +// Either 7 (without CRC) or 9 (with CRC) bytes +// https://wiki.multimedia.cx/index.php/ADTS +constexpr size_t kMinADTSHeaderLength = 7; + +size_t getADTSHeaderLength( + const u_int8_t* pInputBuffer, + size_t sizeofInputBuffer) { + // The size of the raw data block must be strictly greater than + // the size of only ADTS header alone, i.e. additional sample + // data must be present. + if (sizeofInputBuffer > kMinADTSHeaderLength && + // ADTS header starts with syncword 0xFFF + 0-bit (Layer) + 0-bit (MPEG4) + pInputBuffer[0] == 0xff && + (pInputBuffer[1] & 0xf6) == 0xf0) { + const auto numberOfAacFramesMinusOne = pInputBuffer[6] & 0x03; + VERIFY_OR_DEBUG_ASSERT(numberOfAacFramesMinusOne == 0) { + // See also: https://wiki.multimedia.cx/index.php/ADTS + kLogger.warning() + << "Multiple AAC frames (RDBs) per ADTS " + "frame are not supported"; + } + // 2 bytes for CRC are optional + size_t actualADTSHeaderLength = kMinADTSHeaderLength + (pInputBuffer[1] & 0x01) ? 0 : 2; + if (sizeofInputBuffer > actualADTSHeaderLength) { + return actualADTSHeaderLength; + } + } + // No ADTS header found + return 0; +} + +inline bool startsWithADTSHeader( + const u_int8_t* pInputBuffer, + size_t sizeofInputBuffer) { + return 0 < getADTSHeaderLength(pInputBuffer, sizeofInputBuffer); +} + } // anonymous namespace SoundSourceM4A::SoundSourceM4A(const QUrl& url) : SoundSource(url, "m4a"), + m_pFaad(faad2::LibLoader::Instance()), m_hFile(MP4_INVALID_FILE_HANDLE), m_trackId(MP4_INVALID_TRACK_ID), m_framesPerSampleBlock(MP4_INVALID_DURATION), @@ -159,8 +197,7 @@ SoundSourceM4A::SoundSourceM4A(const QUrl& url) m_hDecoder(nullptr), m_numberOfPrefetchSampleBlocks(0), m_curSampleBlockId(MP4_INVALID_SAMPLE_ID), - m_curFrameIndex(0), - m_pFaad(LibFaadLoader::Instance()) { + m_curFrameIndex(0) { } SoundSourceM4A::~SoundSourceM4A() { @@ -257,45 +294,24 @@ SoundSource::OpenResult SoundSourceM4A::tryOpen( bool SoundSourceM4A::openDecoder() { DEBUG_ASSERT(m_hDecoder == nullptr); // not already opened - m_hDecoder = m_pFaad->Open(); - if (m_hDecoder == nullptr) { - kLogger.warning() << "Failed to open the AAC decoder!"; - return false; - } - LibFaadLoader::Configuration* pDecoderConfig = m_pFaad->GetCurrentConfiguration( - m_hDecoder); - pDecoderConfig->outputFormat = FAAD_FMT_FLOAT; - if ((m_openParams.getSignalInfo().getChannelCount() == 1) || - (m_openParams.getSignalInfo().getChannelCount() == 2)) { - pDecoderConfig->downMatrix = 1; - } else { - pDecoderConfig->downMatrix = 0; - } - - pDecoderConfig->defObjectType = LC; - if (!m_pFaad->SetConfiguration(m_hDecoder, pDecoderConfig)) { - kLogger.warning() << "Failed to configure AAC decoder!"; - return false; - } - - u_int8_t* configBuffer = nullptr; - u_int32_t configBufferSize = 0; - if (!MP4GetTrackESConfiguration(m_hFile, m_trackId, &configBuffer, &configBufferSize)) { + DEBUG_ASSERT(!m_pMP4ESConfigBuffer); + m_sizeofMP4ESConfigBuffer = 0; + if (!MP4GetTrackESConfiguration(m_hFile, + m_trackId, + &m_pMP4ESConfigBuffer, + &m_sizeofMP4ESConfigBuffer)) { // Failed to get mpeg-4 audio config... this is ok. - // Init2() will simply use default values instead. - kLogger.warning() << "Failed to read the MP4 audio configuration." - << "Continuing with default values."; + // Init2() will then simply use default values instead. + kLogger.info() << "Failed to read the MP4 elementary stream " + "(ES) configuration"; + DEBUG_ASSERT(!m_pMP4ESConfigBuffer); + DEBUG_ASSERT(m_sizeofMP4ESConfigBuffer == 0); } - SAMPLERATE_TYPE sampleRate; - unsigned char channelCount; - if (0 > m_pFaad->Init2(m_hDecoder, configBuffer, configBufferSize, &sampleRate, &channelCount)) { - free(configBuffer); - kLogger.warning() << "Failed to initialize the AAC decoder!"; + if (!reopenDecoder()) { return false; - } else { - free(configBuffer); } + DEBUG_ASSERT(m_hDecoder); // Calculate how many sample blocks we need to decode in advance // of a random seek in order to get the recommended number of @@ -304,45 +320,165 @@ bool SoundSourceM4A::openDecoder() { (kNumberOfPrefetchFrames + (m_framesPerSampleBlock - 1)) / m_framesPerSampleBlock; - initChannelCountOnce(channelCount); - initSampleRateOnce(sampleRate); - initFrameIndexRangeOnce( - mixxx::IndexRange::forward( - 0, - ((m_maxSampleBlockId - kSampleBlockIdMin) + 1) * m_framesPerSampleBlock)); - const SINT sampleBufferCapacity = getSignalInfo().frames2samples(m_framesPerSampleBlock); if (m_sampleBuffer.capacity() < sampleBufferCapacity) { m_sampleBuffer.adjustCapacity(sampleBufferCapacity); } - // Discard all buffered samples + // Discard all buffered input data + m_curSampleBlockId = MP4_INVALID_SAMPLE_ID; m_inputBufferLength = 0; + m_inputBufferOffset = 0; - // Invalidate current position(s) - m_curSampleBlockId = MP4_INVALID_SAMPLE_ID; + // Invalidate current stream position m_curFrameIndex = frameIndexMax(); return true; } -void SoundSourceM4A::closeDecoder() { - if (m_hDecoder != nullptr) { +bool SoundSourceM4A::reopenDecoder() { + auto hNewDecoder = m_pFaad->Open(); + if (!hNewDecoder) { + kLogger.warning() << "Failed to open the AAC decoder"; + return false; + } + + if (!replaceDecoder(hNewDecoder)) { + return false; + } + DEBUG_ASSERT(m_hDecoder == hNewDecoder); + + return true; +} + +bool SoundSourceM4A::replaceDecoder( + faad2::DecoderHandle hNewDecoder) { + const faad2::Configuration* pOldConfig = nullptr; + if (m_hDecoder) { + pOldConfig = m_pFaad->GetCurrentConfiguration(m_hDecoder); + if (!pOldConfig) { + kLogger.warning() + << "Failed to get the current (old) AAC decoder configuration"; + return false; + } + if (kLogger.traceEnabled()) { + kLogger.trace() + << "Old AAC decoder configuration" + << *pOldConfig; + } + } + + faad2::Configuration* pNewConfig = + m_pFaad->GetCurrentConfiguration(hNewDecoder); + if (!pNewConfig) { + kLogger.warning() + << "Failed to get the current (new) AAC decoder configuration"; + return false; + } + if (kLogger.traceEnabled()) { + kLogger.trace() + << "Current AAC decoder configuration" + << *pNewConfig; + } + + if (pOldConfig) { + if (pOldConfig->defSampleRate > 0) { + pNewConfig->defSampleRate = pOldConfig->defSampleRate; + } + pNewConfig->defObjectType = pOldConfig->defObjectType; + pNewConfig->outputFormat = pOldConfig->outputFormat; + pNewConfig->downMatrix = pOldConfig->downMatrix; + } else { + pNewConfig->outputFormat = faad2::FMT_FLOAT; + const auto desiredChannelCount = + getSignalInfo().getChannelCount().isValid() + ? getSignalInfo().getChannelCount() + : m_openParams.getSignalInfo().getChannelCount(); + if (desiredChannelCount == 1 || desiredChannelCount == 2) { + pNewConfig->downMatrix = 1; + } else { + pNewConfig->downMatrix = 0; + } + } + + if (kLogger.traceEnabled()) { + kLogger.trace() + << "Desired AAC decoder configuration" + << *pNewConfig; + } + if (!m_pFaad->SetConfiguration(hNewDecoder, pNewConfig)) { + kLogger.warning() + << "Failed to configure AAC decoder" + << *pNewConfig; + return false; + } + + SAMPLERATE_TYPE sampleRate; + unsigned char channelCount; + if (startsWithADTSHeader(m_inputBuffer.data(), m_inputBufferLength)) { + DEBUG_ASSERT(m_hDecoder); + kLogger.debug() + << "Reinitializing decoder from AAC stream"; + if (m_pFaad->Init(hNewDecoder, + m_inputBuffer.data(), + m_inputBufferLength, + &sampleRate, + &channelCount) < 0) { + kLogger.warning() << "Failed to initialize the AAC decoder from " + "AAC stream (ADTS/ADIF)"; + return false; + } + } else { + if (m_pFaad->Init2( + hNewDecoder, + m_pMP4ESConfigBuffer, + m_sizeofMP4ESConfigBuffer, + &sampleRate, + &channelCount) < 0) { + free(m_pMP4ESConfigBuffer); + m_pMP4ESConfigBuffer = nullptr; + kLogger.warning() << "Failed to initialize the AAC decoder from " + "MP4 elementary stream (ES) configuration"; + return false; + } + } + if (!initChannelCountOnce(channelCount)) { + return false; + } + if (!initSampleRateOnce(sampleRate)) { + return false; + } + if (!initFrameIndexRangeOnce(mixxx::IndexRange::forward(0, + ((m_maxSampleBlockId - kSampleBlockIdMin) + 1) * + m_framesPerSampleBlock))) { + return false; + } + + if (m_hDecoder) { m_pFaad->Close(m_hDecoder); - m_hDecoder = nullptr; } + m_hDecoder = hNewDecoder; + + return true; } -bool SoundSourceM4A::reopenDecoder() { - closeDecoder(); - return openDecoder(); +void SoundSourceM4A::closeDecoder() { + if (!m_hDecoder) { + return; + } + m_pFaad->Close(m_hDecoder); + m_hDecoder = nullptr; } void SoundSourceM4A::close() { closeDecoder(); m_sampleBuffer.clear(); m_inputBuffer.clear(); + if (m_pMP4ESConfigBuffer) { + free(m_pMP4ESConfigBuffer); + m_pMP4ESConfigBuffer = nullptr; + } if (MP4_INVALID_FILE_HANDLE != m_hFile) { MP4Close(m_hFile); m_hFile = MP4_INVALID_FILE_HANDLE; @@ -350,7 +486,8 @@ void SoundSourceM4A::close() { } bool SoundSourceM4A::isValidSampleBlockId(MP4SampleId sampleBlockId) const { - return (sampleBlockId >= kSampleBlockIdMin) && (sampleBlockId <= m_maxSampleBlockId); + return (sampleBlockId >= kSampleBlockIdMin) && + (sampleBlockId <= m_maxSampleBlockId); } void SoundSourceM4A::restartDecoding(MP4SampleId sampleBlockId) { @@ -363,6 +500,7 @@ void SoundSourceM4A::restartDecoding(MP4SampleId sampleBlockId) { // Discard input buffer m_inputBufferLength = 0; + m_inputBufferOffset = 0; // Discard previously decoded sample data m_sampleBuffer.clear(); @@ -372,213 +510,312 @@ ReadableSampleFrames SoundSourceM4A::readSampleFramesClamped( WritableSampleFrames writableSampleFrames) { const SINT firstFrameIndex = writableSampleFrames.frameIndexRange().start(); - if (m_curFrameIndex != firstFrameIndex) { - // NOTE(uklotzde): Resetting the decoder near to the beginning - // of the stream when seeking backwards produces invalid sample - // values! As a consequence the seeking test fails. - if ((m_curSampleBlockId != MP4_INVALID_SAMPLE_ID) && - (firstFrameIndex < m_curFrameIndex) && - (firstFrameIndex <= (frameIndexMin() + kNumberOfPrefetchFrames))) { - // Workaround: Reset the decoder when seeking near to the beginning - // of the stream while decoding. - reopenDecoder(); - } + bool retryAfterReopeningDecoder = false; + do { + while (m_curFrameIndex != firstFrameIndex) { + // NOTE(uklotzde): Resetting the decoder near to the beginning + // of the stream when seeking backwards produces invalid sample + // values! As a consequence the seeking test fails. + if ((m_curSampleBlockId != MP4_INVALID_SAMPLE_ID) && + (firstFrameIndex < m_curFrameIndex) && + (firstFrameIndex <= + (frameIndexMin() + kNumberOfPrefetchFrames))) { + // Workaround: Discard remaining input data and reopen the decoder when + // seeking near to the beginning of the stream while decoding. + m_curSampleBlockId = MP4_INVALID_SAMPLE_ID; + m_inputBufferLength = 0; + if (!reopenDecoder()) { + return {}; + } + } - MP4SampleId sampleBlockId = kSampleBlockIdMin + (firstFrameIndex / m_framesPerSampleBlock); - DEBUG_ASSERT(isValidSampleBlockId(sampleBlockId)); - if ((firstFrameIndex < m_curFrameIndex) || // seeking backwards? - !isValidSampleBlockId(m_curSampleBlockId) || // invalid seek position? - (sampleBlockId > (m_curSampleBlockId + m_numberOfPrefetchSampleBlocks))) { // jumping forward? - // Restart decoding one or more blocks of samples backwards - // from the calculated starting block to avoid audible glitches. - // Implementation note: The type MP4SampleId is unsigned so we - // need to be careful when subtracting! - if ((kSampleBlockIdMin + m_numberOfPrefetchSampleBlocks) < sampleBlockId) { - sampleBlockId -= m_numberOfPrefetchSampleBlocks; - } else { - sampleBlockId = kSampleBlockIdMin; + MP4SampleId sampleBlockId = kSampleBlockIdMin + + (firstFrameIndex / m_framesPerSampleBlock); + DEBUG_ASSERT(isValidSampleBlockId(sampleBlockId)); + if ((firstFrameIndex < m_curFrameIndex) || // seeking backwards? + !isValidSampleBlockId( + m_curSampleBlockId) || // invalid seek position? + (sampleBlockId > + (m_curSampleBlockId + + m_numberOfPrefetchSampleBlocks))) { // jumping forward? + // Restart decoding one or more blocks of samples backwards + // from the calculated starting block to avoid audible glitches. + // Implementation note: The type MP4SampleId is unsigned so we + // need to be careful when subtracting! + if ((kSampleBlockIdMin + m_numberOfPrefetchSampleBlocks) < + sampleBlockId) { + sampleBlockId -= m_numberOfPrefetchSampleBlocks; + } else { + sampleBlockId = kSampleBlockIdMin; + } + restartDecoding(sampleBlockId); + DEBUG_ASSERT(m_curSampleBlockId == sampleBlockId); } - restartDecoding(sampleBlockId); - DEBUG_ASSERT(m_curSampleBlockId == sampleBlockId); - } - // Decoding starts before the actual target position - DEBUG_ASSERT(m_curFrameIndex <= firstFrameIndex); - const auto precedingFrames = - IndexRange::between(m_curFrameIndex, firstFrameIndex); - if (!precedingFrames.empty() && (precedingFrames != readSampleFramesClamped(WritableSampleFrames(precedingFrames)).frameIndexRange())) { - kLogger.warning() - << "Failed to skip preceding frames" - << precedingFrames; - // Abort - return ReadableSampleFrames( - IndexRange::between( - m_curFrameIndex, - m_curFrameIndex)); + // Decoding starts before the actual target position + DEBUG_ASSERT(m_curFrameIndex <= firstFrameIndex); + const auto precedingFrames = + IndexRange::between(m_curFrameIndex, firstFrameIndex); + if (!precedingFrames.empty() && + (precedingFrames != + readSampleFramesClamped( + WritableSampleFrames(precedingFrames)) + .frameIndexRange())) { + kLogger.warning() + << "Failed to skip preceding frames" << precedingFrames; + // Abort + return ReadableSampleFrames( + IndexRange::between(m_curFrameIndex, m_curFrameIndex)); + } } - } - DEBUG_ASSERT(m_curFrameIndex == firstFrameIndex); - - const SINT numberOfSamplesTotal = getSignalInfo().frames2samples(writableSampleFrames.frameLength()); - - SINT numberOfSamplesRemaining = numberOfSamplesTotal; - SINT outputSampleOffset = 0; - while (0 < numberOfSamplesRemaining) { - if (!m_sampleBuffer.empty()) { - // Consume previously decoded sample data - const SampleBuffer::ReadableSlice readableSlice( - m_sampleBuffer.shrinkForReading(numberOfSamplesRemaining)); - if (writableSampleFrames.writableData()) { - SampleUtil::copy( - writableSampleFrames.writableData(outputSampleOffset), - readableSlice.data(), - readableSlice.length()); - outputSampleOffset += readableSlice.length(); + DEBUG_ASSERT(m_curFrameIndex == firstFrameIndex); + + const SINT numberOfSamplesTotal = getSignalInfo().frames2samples( + writableSampleFrames.frameLength()); + + SINT numberOfSamplesRemaining = numberOfSamplesTotal; + SINT outputSampleOffset = 0; + while (0 < numberOfSamplesRemaining) { + if (!m_sampleBuffer.empty()) { + // Consume previously decoded sample data + const SampleBuffer::ReadableSlice readableSlice( + m_sampleBuffer.shrinkForReading( + numberOfSamplesRemaining)); + if (writableSampleFrames.writableData()) { + SampleUtil::copy(writableSampleFrames.writableData( + outputSampleOffset), + readableSlice.data(), + readableSlice.length()); + outputSampleOffset += readableSlice.length(); + } + m_curFrameIndex += + getSignalInfo().samples2frames(readableSlice.length()); + DEBUG_ASSERT(isValidFrameIndex(m_curFrameIndex)); + DEBUG_ASSERT( + numberOfSamplesRemaining >= readableSlice.length()); + numberOfSamplesRemaining -= readableSlice.length(); + if (0 == numberOfSamplesRemaining) { + break; // exit loop + } } - m_curFrameIndex += getSignalInfo().samples2frames(readableSlice.length()); - DEBUG_ASSERT(isValidFrameIndex(m_curFrameIndex)); - DEBUG_ASSERT(numberOfSamplesRemaining >= readableSlice.length()); - numberOfSamplesRemaining -= readableSlice.length(); - if (0 == numberOfSamplesRemaining) { - break; // exit loop + // All previously decoded sample data has been consumed now + DEBUG_ASSERT(m_sampleBuffer.empty()); + + DEBUG_ASSERT(m_inputBufferLength >= m_inputBufferOffset); + if (m_inputBufferLength <= m_inputBufferOffset) { + // Fill input buffer from file + if (isValidSampleBlockId(m_curSampleBlockId)) { + // Read data for next sample block into input buffer + u_int8_t* pInputBuffer = m_inputBuffer.data(); + u_int32_t inputBufferLength = m_inputBuffer.size(); // in/out parameter + if (!MP4ReadSample(m_hFile, + m_trackId, + m_curSampleBlockId, + &pInputBuffer, + &inputBufferLength, + nullptr, + nullptr, + nullptr, + nullptr)) { + kLogger.warning() + << "Failed to read MP4 input data for sample " + "block" + << m_curSampleBlockId << "(" + << "min =" << kSampleBlockIdMin << "," + << "max =" << m_maxSampleBlockId << ")"; + break; // abort + } + DEBUG_ASSERT(pInputBuffer == m_inputBuffer.data()); + DEBUG_ASSERT(inputBufferLength <= m_inputBuffer.size()); + ++m_curSampleBlockId; + m_inputBufferLength = inputBufferLength; + m_inputBufferOffset = 0; + // Skip ADTS header if we have a decoder specific ES config + if (m_pMP4ESConfigBuffer && + startsWithADTSHeader(m_inputBuffer.data(), m_inputBufferLength)) { + m_inputBufferOffset += + getADTSHeaderLength(m_inputBuffer.data(), m_inputBufferLength); + } + } } - } - // All previously decoded sample data has been consumed now - DEBUG_ASSERT(m_sampleBuffer.empty()); - - if (0 == m_inputBufferLength) { - // Fill input buffer from file - if (isValidSampleBlockId(m_curSampleBlockId)) { - // Read data for next sample block into input buffer - u_int8_t* pInputBuffer = &m_inputBuffer[0]; - u_int32_t inputBufferLength = m_inputBuffer.size(); // in/out parameter - if (!MP4ReadSample(m_hFile, m_trackId, m_curSampleBlockId, &pInputBuffer, &inputBufferLength, nullptr, nullptr, nullptr, nullptr)) { + DEBUG_ASSERT(m_inputBufferLength >= m_inputBufferOffset); + if (m_inputBufferLength <= m_inputBufferOffset) { + break; // EOF + } + + // NOTE(uklotzde): The sample buffer for Decode2 has to + // be big enough for a whole block of decoded samples, which + // contains up to m_framesPerSampleBlock frames. Otherwise + // we need to use a temporary buffer. + CSAMPLE* pDecodeBuffer; // in/out parameter + SINT decodeBufferCapacity; + const SINT decodeBufferCapacityMin = + getSignalInfo().frames2samples(m_framesPerSampleBlock); + if (writableSampleFrames.writableData() && + (decodeBufferCapacityMin <= numberOfSamplesRemaining)) { + // Decode samples directly into the output buffer + pDecodeBuffer = + writableSampleFrames.writableData(outputSampleOffset); + decodeBufferCapacity = numberOfSamplesRemaining; + } else { + // Decode next sample block into temporary buffer + const SINT maxWriteLength = math_max( + numberOfSamplesRemaining, decodeBufferCapacityMin); + const SampleBuffer::WritableSlice writableSlice( + m_sampleBuffer.growForWriting(maxWriteLength)); + pDecodeBuffer = writableSlice.data(); + decodeBufferCapacity = writableSlice.length(); + } + DEBUG_ASSERT(decodeBufferCapacityMin <= decodeBufferCapacity); + + faad2::FrameInfo decFrameInfo; + DEBUG_ASSERT(m_inputBufferLength >= m_inputBufferOffset); + void* pDecodeResult = m_pFaad->Decode2(m_hDecoder, + &decFrameInfo, + &m_inputBuffer[m_inputBufferOffset], + m_inputBufferLength - m_inputBufferOffset, + reinterpret_cast(&pDecodeBuffer), + decodeBufferCapacity * sizeof(*pDecodeBuffer)); + if (decFrameInfo.error != 0) { + // A decoding error has occurred + if (retryAfterReopeningDecoder) { + // At this point we have failed to decode the current sample + // block twice and need to discard it. The content of the + // sample block is unknown and we simply continue with the + // next block. This is just a workaround! The reason why FAAD2 + // v2.9.2 rejects these blocks is unknown. + kLogger.warning() + << "Skipping block" + << m_curSampleBlockId + << "of length" + << m_inputBufferLength + << "after an AAC decoding error occurred"; + // Reset the retry flag before continuing with the next block + retryAfterReopeningDecoder = false; + m_inputBufferLength = 0; + m_inputBufferOffset = 0; + continue; + } else { + const auto frameError = faad2::FrameError(decFrameInfo.error); + if (frameError == faad2::FrameError::InvalidNumberOfChannels || + frameError == faad2::FrameError::InvalidChannelConfiguration) { + kLogger.debug() + << "Reopening decoder after AAC decoding error" + << decFrameInfo.error + << m_pFaad->GetErrorMessage(decFrameInfo.error) + << getUrlString(); + // Assumption: All samples from the preceding blocks have been + // decoded and consumed before decoding continues with the new, + // reopened decoder. Otherwise the decoded stream of samples + // might be discontinuous, but we can't do anything about it. + retryAfterReopeningDecoder = reopenDecoder(); + // If reopening the decoder failed retrying the same sample + // block with the same decoder instance will fail again. In + // this case we will simply abort the decoding of the stream + // immediately, see below. + } + } + if (!retryAfterReopeningDecoder) { + // A decoding error occurred and no retry is pending kLogger.warning() - << "Failed to read MP4 input data for sample block" - << m_curSampleBlockId << "(" - << "min =" - << kSampleBlockIdMin << "," - << "max =" - << m_maxSampleBlockId << ")"; - break; // abort + << "AAC decoding error:" << decFrameInfo.error + << m_pFaad->GetErrorMessage(decFrameInfo.error) + << getUrlString(); + // In turn the decoding will be aborted } - ++m_curSampleBlockId; - m_inputBufferLength = inputBufferLength; - m_inputBufferOffset = 0; + // Either abort or retry by exiting the inner loop + break; + } else { + // Reset the retry flag after succesfully decoding a block + retryAfterReopeningDecoder = false; + } + // Upon a pending retry the inner loop is exited immediately and + // we must never get to this point. + DEBUG_ASSERT(!retryAfterReopeningDecoder); + + Q_UNUSED(pDecodeResult); // only used in DEBUG_ASSERT + DEBUG_ASSERT(pDecodeResult == + pDecodeBuffer); // verify the in/out parameter + + // Verify the decoded sample data for consistency + VERIFY_OR_DEBUG_ASSERT(getSignalInfo().getChannelCount() == + decFrameInfo.channels) { + kLogger.critical() << "Corrupt or unsupported AAC file:" + << "Unexpected number of channels" + << decFrameInfo.channels << "<>" + << getSignalInfo().getChannelCount(); + break; // abort + } + VERIFY_OR_DEBUG_ASSERT(getSignalInfo().getSampleRate() == + SINT(decFrameInfo.samplerate)) { + kLogger.critical() + << "Corrupt or unsupported AAC file:" + << "Unexpected sample rate" << decFrameInfo.samplerate + << "<>" << getSignalInfo().getSampleRate(); + break; // abort } - } - DEBUG_ASSERT(0 <= m_inputBufferLength); - if (0 == m_inputBufferLength) { - break; // EOF - } - // NOTE(uklotzde): The sample buffer for Decode2 has to - // be big enough for a whole block of decoded samples, which - // contains up to m_framesPerSampleBlock frames. Otherwise - // we need to use a temporary buffer. - CSAMPLE* pDecodeBuffer; // in/out parameter - SINT decodeBufferCapacity; - const SINT decodeBufferCapacityMin = getSignalInfo().frames2samples(m_framesPerSampleBlock); - if (writableSampleFrames.writableData() && - (decodeBufferCapacityMin <= numberOfSamplesRemaining)) { - // Decode samples directly into the output buffer - pDecodeBuffer = writableSampleFrames.writableData(outputSampleOffset); - decodeBufferCapacity = numberOfSamplesRemaining; - } else { - // Decode next sample block into temporary buffer - const SINT maxWriteLength = math_max( - numberOfSamplesRemaining, decodeBufferCapacityMin); - const SampleBuffer::WritableSlice writableSlice( - m_sampleBuffer.growForWriting(maxWriteLength)); - pDecodeBuffer = writableSlice.data(); - decodeBufferCapacity = writableSlice.length(); - } - DEBUG_ASSERT(decodeBufferCapacityMin <= decodeBufferCapacity); - - LibFaadLoader::FrameInfo decFrameInfo; - void* pDecodeResult = m_pFaad->Decode2( - m_hDecoder, &decFrameInfo, &m_inputBuffer[m_inputBufferOffset], m_inputBufferLength, reinterpret_cast(&pDecodeBuffer), decodeBufferCapacity * sizeof(*pDecodeBuffer)); - // Verify the decoding result - if (0 != decFrameInfo.error) { - kLogger.warning() << "AAC decoding error:" - << decFrameInfo.error - << m_pFaad->GetErrorMessage(decFrameInfo.error) - << getUrlString(); - break; // abort - } - Q_UNUSED(pDecodeResult); // only used in DEBUG_ASSERT - DEBUG_ASSERT(pDecodeResult == pDecodeBuffer); // verify the in/out parameter - - // Verify the decoded sample data for consistency - VERIFY_OR_DEBUG_ASSERT(getSignalInfo().getChannelCount() == decFrameInfo.channels) { - kLogger.critical() - << "Corrupt or unsupported AAC file:" - << "Unexpected number of channels" << decFrameInfo.channels - << "<>" << getSignalInfo().getChannelCount(); - break; // abort - } - VERIFY_OR_DEBUG_ASSERT(getSignalInfo().getSampleRate() == SINT(decFrameInfo.samplerate)) { - kLogger.critical() - << "Corrupt or unsupported AAC file:" - << "Unexpected sample rate" << decFrameInfo.samplerate - << "<>" << getSignalInfo().getSampleRate(); - break; // abort - } + // Consume input data + m_inputBufferOffset += decFrameInfo.bytesconsumed; - // Consume input data - m_inputBufferLength -= decFrameInfo.bytesconsumed; - m_inputBufferOffset += decFrameInfo.bytesconsumed; - - // Consume decoded output data - const SINT numberOfSamplesDecoded = decFrameInfo.samples; - DEBUG_ASSERT(numberOfSamplesDecoded <= decodeBufferCapacity); - SINT numberOfSamplesRead; - if (writableSampleFrames.writableData() && - (pDecodeBuffer == writableSampleFrames.writableData(outputSampleOffset))) { - // Decoded in-place - DEBUG_ASSERT(numberOfSamplesDecoded <= numberOfSamplesRemaining); - numberOfSamplesRead = numberOfSamplesDecoded; - outputSampleOffset += numberOfSamplesRead; - } else { - // Decoded into temporary buffer + // Consume decoded output data + const SINT numberOfSamplesDecoded = decFrameInfo.samples; DEBUG_ASSERT(numberOfSamplesDecoded <= decodeBufferCapacity); - // Shrink the size of the buffer to the samples that have - // actually been decoded, i.e. dropping unneeded samples - // from the back of the buffer. - m_sampleBuffer.shrinkAfterWriting(decodeBufferCapacity - numberOfSamplesDecoded); - DEBUG_ASSERT(m_sampleBuffer.readableLength() == numberOfSamplesDecoded); - // Read from the buffer's head - numberOfSamplesRead = - std::min(numberOfSamplesDecoded, numberOfSamplesRemaining); - const SampleBuffer::ReadableSlice readableSlice( - m_sampleBuffer.shrinkForReading(numberOfSamplesRead)); - DEBUG_ASSERT(readableSlice.length() == numberOfSamplesRead); - if (writableSampleFrames.writableData()) { - SampleUtil::copy( - writableSampleFrames.writableData(outputSampleOffset), - readableSlice.data(), - readableSlice.length()); + SINT numberOfSamplesRead; + if (writableSampleFrames.writableData() && + (pDecodeBuffer == writableSampleFrames.writableData(outputSampleOffset))) { + // Decoded in-place + DEBUG_ASSERT(numberOfSamplesDecoded <= numberOfSamplesRemaining); + numberOfSamplesRead = numberOfSamplesDecoded; outputSampleOffset += numberOfSamplesRead; + } else { + // Decoded into temporary buffer + DEBUG_ASSERT(numberOfSamplesDecoded <= decodeBufferCapacity); + // Shrink the size of the buffer to the samples that have + // actually been decoded, i.e. dropping unneeded samples + // from the back of the buffer. + m_sampleBuffer.shrinkAfterWriting(decodeBufferCapacity - numberOfSamplesDecoded); + DEBUG_ASSERT(m_sampleBuffer.readableLength() == numberOfSamplesDecoded); + // Read from the buffer's head + numberOfSamplesRead = + std::min(numberOfSamplesDecoded, numberOfSamplesRemaining); + const SampleBuffer::ReadableSlice readableSlice( + m_sampleBuffer.shrinkForReading(numberOfSamplesRead)); + DEBUG_ASSERT(readableSlice.length() == numberOfSamplesRead); + if (writableSampleFrames.writableData()) { + SampleUtil::copy( + writableSampleFrames.writableData(outputSampleOffset), + readableSlice.data(), + readableSlice.length()); + outputSampleOffset += numberOfSamplesRead; + } } + // The decoder might decode more samples than actually needed + // at the end of the file! When the end of the file has been + // reached decoding can be restarted by seeking to a new + // position. + m_curFrameIndex += getSignalInfo().samples2frames(numberOfSamplesRead); + numberOfSamplesRemaining -= numberOfSamplesRead; } - // The decoder might decode more samples than actually needed - // at the end of the file! When the end of the file has been - // reached decoding can be restarted by seeking to a new - // position. - m_curFrameIndex += getSignalInfo().samples2frames(numberOfSamplesRead); - numberOfSamplesRemaining -= numberOfSamplesRead; - } - - DEBUG_ASSERT(isValidFrameIndex(m_curFrameIndex)); - DEBUG_ASSERT(numberOfSamplesTotal >= numberOfSamplesRemaining); - const SINT numberOfSamples = numberOfSamplesTotal - numberOfSamplesRemaining; - return ReadableSampleFrames( - IndexRange::forward( - firstFrameIndex, - getSignalInfo().samples2frames(numberOfSamples)), - SampleBuffer::ReadableSlice( - writableSampleFrames.writableData(), - std::min(writableSampleFrames.writableLength(), numberOfSamples))); + DEBUG_ASSERT(isValidFrameIndex(m_curFrameIndex)); + if (retryAfterReopeningDecoder) { + // Continue by retrying to decode the current sample block again + // with a new decoder instance after errors occurred. + continue; + } + // The current sample block has been decoded successfully + DEBUG_ASSERT(numberOfSamplesTotal >= numberOfSamplesRemaining); + const SINT numberOfSamples = numberOfSamplesTotal - numberOfSamplesRemaining; + return ReadableSampleFrames( + IndexRange::forward( + firstFrameIndex, + getSignalInfo().samples2frames(numberOfSamples)), + SampleBuffer::ReadableSlice( + writableSampleFrames.writableData(), + std::min(writableSampleFrames.writableLength(), numberOfSamples))); + } while (retryAfterReopeningDecoder); + DEBUG_ASSERT(!"unreachable"); + return {}; } QString SoundSourceProviderM4A::getName() const { @@ -587,7 +824,7 @@ QString SoundSourceProviderM4A::getName() const { QStringList SoundSourceProviderM4A::getSupportedFileExtensions() const { QStringList supportedFileExtensions; - if (LibFaadLoader::Instance()->isLoaded()) { + if (faad2::LibLoader::Instance()->isLoaded()) { supportedFileExtensions.append("m4a"); supportedFileExtensions.append("mp4"); } diff --git a/src/sources/soundsourcem4a.h b/src/sources/soundsourcem4a.h index d13cba6875e..29fff311005 100644 --- a/src/sources/soundsourcem4a.h +++ b/src/sources/soundsourcem4a.h @@ -43,34 +43,39 @@ class SoundSourceM4A : public SoundSource { const OpenParams& params) override; bool openDecoder(); - void closeDecoder(); bool reopenDecoder(); + bool replaceDecoder( + faad2::DecoderHandle hNewDecoder); + void closeDecoder(); bool isValidSampleBlockId(MP4SampleId sampleBlockId) const; void restartDecoding(MP4SampleId sampleBlockId); + faad2::LibLoader* const m_pFaad; + MP4FileHandle m_hFile; MP4TrackId m_trackId; MP4Duration m_framesPerSampleBlock; MP4SampleId m_maxSampleBlockId; + u_int8_t* m_pMP4ESConfigBuffer{}; + u_int32_t m_sizeofMP4ESConfigBuffer; + typedef std::vector InputBuffer; InputBuffer m_inputBuffer; - SINT m_inputBufferLength; - SINT m_inputBufferOffset; + InputBuffer::size_type m_inputBufferLength; + InputBuffer::size_type m_inputBufferOffset; OpenParams m_openParams; - LibFaadLoader::Handle m_hDecoder; + faad2::DecoderHandle m_hDecoder; SINT m_numberOfPrefetchSampleBlocks; MP4SampleId m_curSampleBlockId; ReadAheadSampleBuffer m_sampleBuffer; SINT m_curFrameIndex; - - LibFaadLoader* m_pFaad; }; class SoundSourceProviderM4A : public SoundSourceProvider { diff --git a/src/test/soundproxy_test.cpp b/src/test/soundproxy_test.cpp index a39242cb158..32e3825e25c 100644 --- a/src/test/soundproxy_test.cpp +++ b/src/test/soundproxy_test.cpp @@ -92,14 +92,15 @@ class SoundSourceProxyTest: public MixxxTest { const auto channelCount = mixxx::audio::ChannelCount(2); openParams.setChannelCount(mixxx::audio::ChannelCount(2)); auto pAudioSource = proxy.openAudioSource(openParams); - EXPECT_FALSE(!pAudioSource); - if (pAudioSource->getSignalInfo().getChannelCount() != channelCount) { - // Wrap into proxy object - pAudioSource = mixxx::AudioSourceStereoProxy::create( - pAudioSource, - kMaxReadFrameCount); + if (pAudioSource) { + if (pAudioSource->getSignalInfo().getChannelCount() != channelCount) { + // Wrap into proxy object + pAudioSource = mixxx::AudioSourceStereoProxy::create( + pAudioSource, + kMaxReadFrameCount); + } + EXPECT_EQ(pAudioSource->getSignalInfo().getChannelCount(), channelCount); } - EXPECT_EQ(pAudioSource->getSignalInfo().getChannelCount(), channelCount); return pAudioSource; }