diff options
author | Uwe Klotz <uklotz@mixxx.org> | 2020-06-07 15:38:18 +0200 |
---|---|---|
committer | Uwe Klotz <uklotz@mixxx.org> | 2020-06-07 20:40:16 +0200 |
commit | 7c94bffb4f83a7b10b889120192a181edd4d4db7 (patch) | |
tree | 9470ef2a2a6fe93c5370dd2d79b7190d1af57b0d /src/sources | |
parent | 5309babd083679e46bcfe1bbbbf4684e84526793 (diff) |
FAAD2: Reconfigure decoder after channel config errors
Diffstat (limited to 'src/sources')
-rw-r--r-- | src/sources/audiosource.cpp | 13 | ||||
-rw-r--r-- | src/sources/libfaadloader.cpp | 34 | ||||
-rw-r--r-- | src/sources/libfaadloader.h | 18 | ||||
-rw-r--r-- | src/sources/soundsourcem4a.cpp | 720 | ||||
-rw-r--r-- | src/sources/soundsourcem4a.h | 12 |
5 files changed, 501 insertions, 296 deletions
diff --git a/src/sources/audiosource.cpp b/src/sources/audiosource.cpp index d603ff7b0c..5a4ec747be 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 a27338c09c..e2ec2d77f3 100644 --- a/src/sources/libfaadloader.cpp +++ b/src/sources/libfaadloader.cpp @@ -20,6 +20,7 @@ LibLoader::LibLoader() : m_neAACDecOpen(nullptr), m_neAACDecGetCurrentConfiguration(nullptr), m_neAACDecSetConfiguration(nullptr), + m_neAACDecInit(nullptr), m_neAACDecInit2(nullptr), m_neAACDecClose(nullptr), m_neAACDecPostSeekReset(nullptr), @@ -62,6 +63,8 @@ LibLoader::LibLoader() m_pLibrary->resolve("NeAACDecGetCurrentConfiguration")); m_neAACDecSetConfiguration = reinterpret_cast<NeAACDecSetConfiguration_t>( m_pLibrary->resolve("NeAACDecSetConfiguration")); + m_neAACDecInit = reinterpret_cast<NeAACDecInit_t>( + m_pLibrary->resolve("NeAACDecInit")); m_neAACDecInit2 = reinterpret_cast<NeAACDecInit2_t>( m_pLibrary->resolve("NeAACDecInit2")); m_neAACDecClose = reinterpret_cast<NeAACDecClose_t>( @@ -78,6 +81,7 @@ LibLoader::LibLoader() if (!m_neAACDecOpen || !m_neAACDecGetCurrentConfiguration || !m_neAACDecSetConfiguration || + !m_neAACDecInit || !m_neAACDecInit2 || !m_neAACDecClose || !m_neAACDecPostSeekReset || @@ -87,6 +91,7 @@ LibLoader::LibLoader() 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; @@ -96,6 +101,7 @@ LibLoader::LibLoader() m_neAACDecOpen = nullptr; m_neAACDecGetCurrentConfiguration = nullptr; m_neAACDecSetConfiguration = nullptr; + m_neAACDecInit = nullptr; m_neAACDecInit2 = nullptr; m_neAACDecClose = nullptr; m_neAACDecPostSeekReset = nullptr; @@ -148,18 +154,36 @@ unsigned char LibLoader::SetConfiguration( return 0; } -// Init the library using a DecoderSpecificInfo +long LibLoader::Init( + DecoderHandle hDecoder, + unsigned char* pBuffer, + 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 SizeOfDecoderSpecificInfo, - unsigned long* pSamplerate, + 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: diff --git a/src/sources/libfaadloader.h b/src/sources/libfaadloader.h index 07c1222b86..a0822559e4 100644 --- a/src/sources/libfaadloader.h +++ b/src/sources/libfaadloader.h @@ -89,12 +89,20 @@ class LibLoader { unsigned char SetConfiguration( 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( DecoderHandle hDecoder, unsigned char* pBuffer, - unsigned long SizeOfDecoderSpecificInfo, - unsigned long* pSamplerate, + unsigned long sizeofBuffer, + unsigned long* pSampleRate, unsigned char* pChannels) const; void Close(DecoderHandle hDecoder) const; @@ -134,6 +142,10 @@ class LibLoader { 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)( DecoderHandle, unsigned char*, unsigned long, unsigned long*, unsigned char*); NeAACDecInit2_t m_neAACDecInit2; diff --git a/src/sources/soundsourcem4a.cpp b/src/sources/soundsourcem4a.cpp index 80b011bc29..64fa9df7ff 100644 --- a/src/sources/soundsourcem4a.cpp +++ b/src/sources/soundsourcem4a.cpp @@ -146,6 +146,43 @@ 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) @@ -257,35 +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) { - kLogger.warning() << "Failed to open the AAC decoder"; - return false; - } - - if (!initDecoder()) { - kLogger.warning() << "Failed to initialize the 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 @@ -294,90 +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; } -bool SoundSourceM4A::initDecoder() { - faad2::Configuration* pDecoderConfig = - m_pFaad->GetCurrentConfiguration(m_hDecoder); - if (!pDecoderConfig) { - kLogger.warning() - << "Failed to get the current AAC decoder configuration"; +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; } - if (getSignalInfo().getSampleRate().isValid() && - getSignalInfo().getSampleRate() != - mixxx::audio::SampleRate(pDecoderConfig->defSampleRate)) { + 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() - << "Sample rate unexpectedly changed from" - << getSignalInfo().getSampleRate() - << "to" - << pDecoderConfig->defSampleRate - << "while re-initializing the AAC decoder"; + << "Failed to get the current (new) AAC decoder configuration"; return false; } if (kLogger.traceEnabled()) { kLogger.trace() - << "Default AAC decoder configuration" - << *pDecoderConfig; + << "Current AAC decoder configuration" + << *pNewConfig; } - pDecoderConfig->outputFormat = faad2::FMT_FLOAT; - if ((m_openParams.getSignalInfo().getChannelCount() == 1) || - (m_openParams.getSignalInfo().getChannelCount() == 2)) { - pDecoderConfig->downMatrix = 1; + + if (pOldConfig) { + if (pOldConfig->defSampleRate > 0) { + pNewConfig->defSampleRate = pOldConfig->defSampleRate; + } + pNewConfig->defObjectType = pOldConfig->defObjectType; + pNewConfig->outputFormat = pOldConfig->outputFormat; + pNewConfig->downMatrix = pOldConfig->downMatrix; } else { - pDecoderConfig->downMatrix = 0; + 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 (!m_pFaad->SetConfiguration(m_hDecoder, pDecoderConfig)) { + if (kLogger.traceEnabled()) { + kLogger.trace() + << "Desired AAC decoder configuration" + << *pNewConfig; + } + if (!m_pFaad->SetConfiguration(hNewDecoder, pNewConfig)) { kLogger.warning() - << "Failed to configure AAC decoder!"; + << "Failed to configure AAC decoder" + << *pNewConfig; return false; } - if (kLogger.traceEnabled()) { - kLogger.trace() - << "Modified AAC decoder configuration" - << *pDecoderConfig; + + 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; } - return true; -} -void SoundSourceM4A::closeDecoder() { - if (m_hDecoder != nullptr) { + 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; @@ -385,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) { @@ -398,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(); @@ -407,229 +510,292 @@ 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(); - } - - 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; + 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 {}; + } } - 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)); - } - } - DEBUG_ASSERT(m_curFrameIndex == firstFrameIndex); - - const SINT numberOfSamplesTotal = getSignalInfo().frames2samples(writableSampleFrames.frameLength()); - - SINT numberOfSamplesRemaining = numberOfSamplesTotal; - SINT outputSampleOffset = 0; - bool frameErrorOccurred = false; - 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(); + 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); } - 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 + + // 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)); } } - // 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)) { - kLogger.warning() - << "Failed to read MP4 input data for sample block" - << m_curSampleBlockId << "(" - << "min =" - << kSampleBlockIdMin << "," - << "max =" - << m_maxSampleBlockId << ")"; - break; // abort + 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_curSampleBlockId; - m_inputBufferLength = inputBufferLength; - m_inputBufferOffset = 0; } - } - DEBUG_ASSERT(0 <= m_inputBufferLength); - if (0 == m_inputBufferLength) { - break; // EOF - } + // 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); + } + } + } + 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; - void* pDecodeResult = m_pFaad->Decode2( - m_hDecoder, &decFrameInfo, &m_inputBuffer[m_inputBufferOffset], m_inputBufferLength, reinterpret_cast<void**>(&pDecodeBuffer), decodeBufferCapacity * sizeof(*pDecodeBuffer)); - // Verify the decoding result - if (decFrameInfo.error != 0) { - if (faad2::FrameError(decFrameInfo.error) == - faad2::FrameError::InvalidNumberOfChannels || - faad2::FrameError(decFrameInfo.error) == - faad2::FrameError::InvalidChannelConfiguration) { - // Workaround for bug in FAAD2 2.9.x, adopted from VLC - // Try to re-initialize the decoder... - if (!frameErrorOccurred && initDecoder()) { - // ...and try again + // 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<void**>(&pDecodeBuffer), + decodeBufferCapacity * sizeof(*pDecodeBuffer)); + // Verify the decoding result + if (decFrameInfo.error != 0) { + if (retryAfterReopeningDecoder) { + // Reset the retry flag before continuing with the next block + retryAfterReopeningDecoder = false; + kLogger.warning() + << "Skipping block" + << m_curSampleBlockId + << "of length" + << m_inputBufferLength + << "after an AAC decoding error occurred"; + m_inputBufferLength = 0; + m_inputBufferOffset = 0; continue; + } else { + if (faad2::FrameError(decFrameInfo.error) == + faad2::FrameError:: + InvalidNumberOfChannels || + faad2::FrameError(decFrameInfo.error) == + faad2::FrameError:: + InvalidChannelConfiguration) { + kLogger.debug() + << "Reopening decoder after AAC decoding error" + << decFrameInfo.error + << m_pFaad->GetErrorMessage(decFrameInfo.error) + << getUrlString(); + retryAfterReopeningDecoder = reopenDecoder(); + } } - // Prevent unlimited retries otherwise - frameErrorOccurred = true; + if (!retryAfterReopeningDecoder) { + kLogger.warning() + << "AAC decoding error:" << decFrameInfo.error + << m_pFaad->GetErrorMessage(decFrameInfo.error) + << getUrlString(); + } + // Either abort or retry + break; + } else { + // Reset the retry flag after |