SINT SoundSourceM4A::seekSampleFrame(SINT frameIndex) { DEBUG_ASSERT(isValidFrameIndex(m_curFrameIndex)); if (frameIndex >= getMaxFrameIndex()) { // EOF m_curFrameIndex = getMaxFrameIndex(); return m_curFrameIndex; } // 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 (isValidSampleBlockId(m_curSampleBlockId) && (frameIndex <= kNumberOfPrefetchFrames)) { // Workaround: Reset the decoder when seeking near to the beginning // of the stream while decoding. reopenDecoder(); skipSampleFrames(frameIndex); } if (frameIndex == m_curFrameIndex) { return m_curFrameIndex; } MP4SampleId sampleBlockId = kSampleBlockIdMin + (frameIndex / m_framesPerSampleBlock); DEBUG_ASSERT(isValidSampleBlockId(sampleBlockId)); if ((frameIndex < 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); } // Decoding starts before the actual target position DEBUG_ASSERT(m_curFrameIndex <= frameIndex); // Skip (= decode and discard) all samples up to the target position const SINT prefetchFrameCount = frameIndex - m_curFrameIndex; const SINT skipFrameCount = skipSampleFrames(prefetchFrameCount); DEBUG_ASSERT(skipFrameCount <= prefetchFrameCount); if (skipFrameCount < prefetchFrameCount) { qWarning() << "Failed to prefetch sample data while seeking" << skipFrameCount << "<" << prefetchFrameCount; } DEBUG_ASSERT(isValidFrameIndex(m_curFrameIndex)); return m_curFrameIndex; }
SINT SoundSourceM4A::seekSampleFrame(SINT frameIndex) { DEBUG_ASSERT(isValidFrameIndex(m_curFrameIndex)); DEBUG_ASSERT(isValidFrameIndex(frameIndex)); // Handle trivial case if (m_curFrameIndex == frameIndex) { // Nothing to do return m_curFrameIndex; } // Handle edge case if (getMaxFrameIndex() <= frameIndex) { // EOF reached m_curFrameIndex = getMaxFrameIndex(); return m_curFrameIndex; } MP4SampleId sampleBlockId = kSampleBlockIdMin + (frameIndex / m_framesPerSampleBlock); DEBUG_ASSERT(isValidSampleBlockId(sampleBlockId)); if ((frameIndex < 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); } // Decoding starts before the actual target position DEBUG_ASSERT(m_curFrameIndex <= frameIndex); // Skip (= decode and discard) all samples up to the target position const SINT prefetchFrameCount = frameIndex - m_curFrameIndex; const SINT skipFrameCount = skipSampleFrames(prefetchFrameCount); DEBUG_ASSERT(skipFrameCount <= prefetchFrameCount); if (skipFrameCount < prefetchFrameCount) { qWarning() << "Failed to prefetch sample data while seeking" << skipFrameCount << "<" << prefetchFrameCount; } DEBUG_ASSERT(isValidFrameIndex(m_curFrameIndex)); return m_curFrameIndex; }
SINT SoundSourceM4A::seekSampleFrame(SINT frameIndex) { DEBUG_ASSERT(isValidFrameIndex(frameIndex)); if (m_curFrameIndex != frameIndex) { MP4SampleId sampleBlockId = kSampleBlockIdMin + (frameIndex / kFramesPerSampleBlock); DEBUG_ASSERT(isValidSampleBlockId(sampleBlockId)); if ((frameIndex < m_curFrameIndex) || // seeking backwards? !isValidSampleBlockId(m_curSampleBlockId) || // invalid seek position? (sampleBlockId > (m_curSampleBlockId + kNumberOfPrefetchSampleBlocks))) { // 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 + kNumberOfPrefetchSampleBlocks) < sampleBlockId) { sampleBlockId -= kNumberOfPrefetchSampleBlocks; } else { sampleBlockId = kSampleBlockIdMin; } restartDecoding(sampleBlockId); DEBUG_ASSERT(m_curSampleBlockId == sampleBlockId); } // Decoding starts before the actual target position DEBUG_ASSERT(m_curFrameIndex <= frameIndex); // Prefetch (decode and discard) all samples up to the target position const SINT prefetchFrameCount = frameIndex - m_curFrameIndex; const SINT skipFrameCount = skipSampleFrames(prefetchFrameCount); DEBUG_ASSERT(skipFrameCount <= prefetchFrameCount); if (skipFrameCount != prefetchFrameCount) { qWarning() << "Failed to skip over prefetched sample frames after seeking @" << m_curFrameIndex; return m_curFrameIndex; // abort } } DEBUG_ASSERT(m_curFrameIndex == frameIndex); return m_curFrameIndex; }
SINT SoundSourceM4A::readSampleFrames( SINT numberOfFrames, CSAMPLE* sampleBuffer) { DEBUG_ASSERT(isValidFrameIndex(m_curFrameIndex)); const SINT numberOfFramesTotal = math_min( numberOfFrames, getMaxFrameIndex() - m_curFrameIndex); const SINT numberOfSamplesTotal = frames2samples(numberOfFramesTotal); CSAMPLE* pSampleBuffer = sampleBuffer; SINT numberOfSamplesRemaining = numberOfSamplesTotal; while (0 < numberOfSamplesRemaining) { if (!m_sampleBuffer.isEmpty()) { // Consume previously decoded sample data const SampleBuffer::ReadableChunk readableChunk( m_sampleBuffer.readFromHead(numberOfSamplesRemaining)); if (pSampleBuffer) { SampleUtil::copy(pSampleBuffer, readableChunk.data(), readableChunk.size()); pSampleBuffer += readableChunk.size(); } m_curFrameIndex += samples2frames(readableChunk.size()); DEBUG_ASSERT(isValidFrameIndex(m_curFrameIndex)); DEBUG_ASSERT(numberOfSamplesRemaining >= readableChunk.size()); numberOfSamplesRemaining -= readableChunk.size(); if (0 == numberOfSamplesRemaining) { break; // exit loop } } // All previously decoded sample data has been consumed now DEBUG_ASSERT(m_sampleBuffer.isEmpty()); 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, NULL, NULL, NULL, NULL)) { qWarning() << "Failed to read MP4 input data for sample block" << m_curSampleBlockId << "(" << "min =" << kSampleBlockIdMin << "," << "max =" << m_maxSampleBlockId << ")"; break; // abort } ++m_curSampleBlockId; m_inputBufferLength = inputBufferLength; m_inputBufferOffset = 0; } } DEBUG_ASSERT(0 <= m_inputBufferLength); if (0 == m_inputBufferLength) { break; // EOF } // NOTE(uklotzde): The sample buffer for NeAACDecDecode2 has to // be big enough for a whole block of decoded samples, which // contains up to kFramesPerSampleBlock frames. Otherwise // we need to use a temporary buffer. CSAMPLE* pDecodeBuffer; // in/out parameter SINT decodeBufferCapacity; const SINT decodeBufferCapacityMin = frames2samples(kFramesPerSampleBlock); if (pSampleBuffer && (decodeBufferCapacityMin <= numberOfSamplesRemaining)) { // Decode samples directly into sampleBuffer pDecodeBuffer = pSampleBuffer; decodeBufferCapacity = numberOfSamplesRemaining; } else { // Decode next sample block into temporary buffer const SINT writeToTailCount = math_max( numberOfSamplesRemaining, decodeBufferCapacityMin); const SampleBuffer::WritableChunk writableChunk( m_sampleBuffer.writeToTail(writeToTailCount)); pDecodeBuffer = writableChunk.data(); decodeBufferCapacity = writableChunk.size(); } DEBUG_ASSERT(decodeBufferCapacityMin <= decodeBufferCapacity); NeAACDecFrameInfo decFrameInfo; void* pDecodeResult = NeAACDecDecode2( m_hDecoder, &decFrameInfo, &m_inputBuffer[m_inputBufferOffset], m_inputBufferLength, reinterpret_cast<void**>(&pDecodeBuffer), decodeBufferCapacity * sizeof(*pDecodeBuffer)); // Verify the decoding result if (0 != decFrameInfo.error) { qWarning() << "AAC decoding error:" << decFrameInfo.error << NeAACDecGetErrorMessage(decFrameInfo.error) << getUrlString(); break; // abort } DEBUG_ASSERT(pDecodeResult == pDecodeBuffer); // verify the in/out parameter // Verify the decoded sample data for consistency if (getChannelCount() != decFrameInfo.channels) { qWarning() << "Corrupt or unsupported AAC file:" << "Unexpected number of channels" << decFrameInfo.channels << "<>" << getChannelCount(); break; // abort } if (getFrameRate() != SINT(decFrameInfo.samplerate)) { qWarning() << "Corrupt or unsupported AAC file:" << "Unexpected sample rate" << decFrameInfo.samplerate << "<>" << getFrameRate(); break; // abort } // 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 (pDecodeBuffer == pSampleBuffer) { numberOfSamplesRead = math_min(numberOfSamplesDecoded, numberOfSamplesRemaining); pSampleBuffer += numberOfSamplesRead; } else { m_sampleBuffer.readFromTail(decodeBufferCapacity - numberOfSamplesDecoded); const SampleBuffer::ReadableChunk readableChunk( m_sampleBuffer.readFromHead(numberOfSamplesRemaining)); numberOfSamplesRead = readableChunk.size(); if (pSampleBuffer) { SampleUtil::copy(pSampleBuffer, readableChunk.data(), numberOfSamplesRead); pSampleBuffer += 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. DEBUG_ASSERT(numberOfSamplesDecoded >= numberOfSamplesRead); m_curFrameIndex += samples2frames(numberOfSamplesRead); DEBUG_ASSERT(isValidFrameIndex(m_curFrameIndex)); DEBUG_ASSERT(numberOfSamplesRemaining >= numberOfSamplesRead); numberOfSamplesRemaining -= numberOfSamplesRead; } DEBUG_ASSERT(isValidFrameIndex(m_curFrameIndex)); DEBUG_ASSERT(numberOfSamplesTotal >= numberOfSamplesRemaining); return samples2frames(numberOfSamplesTotal - numberOfSamplesRemaining); }