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 SoundSourceFLAC::seekSampleFrame(SINT frameIndex) { DEBUG_ASSERT(isValidFrameIndex(m_curFrameIndex)); DEBUG_ASSERT(isValidFrameIndex(frameIndex)); // Avoid unnecessary seeking // NOTE(uklotzde): Disabling this optimization might reveal rare // seek errors on certain FLAC files were the decoder loses sync! if (m_curFrameIndex == frameIndex) { return m_curFrameIndex; } // Discard decoded sample data before seeking m_sampleBuffer.reset(); // Seek to the new position if (FLAC__stream_decoder_seek_absolute(m_decoder, frameIndex)) { // Set the new position m_curFrameIndex = frameIndex; DEBUG_ASSERT(FLAC__STREAM_DECODER_SEEK_ERROR != FLAC__stream_decoder_get_state(m_decoder)); } else { qWarning() << "Seek error at" << frameIndex << "in" << m_file.fileName(); // Invalidate the current position m_curFrameIndex = getMaxFrameIndex(); if (FLAC__STREAM_DECODER_SEEK_ERROR == FLAC__stream_decoder_get_state(m_decoder)) { // Flush the input stream of the decoder according to the // documentation of FLAC__stream_decoder_seek_absolute() if (!FLAC__stream_decoder_flush(m_decoder)) { qWarning() << "Failed to flush input buffer of the FLAC decoder after seeking in" << m_file.fileName(); // Invalidate the current position... m_curFrameIndex = getMaxFrameIndex(); // ...and abort return m_curFrameIndex; } // Discard previously decoded sample data before decoding // the next block of samples m_sampleBuffer.reset(); // Trigger decoding of the next block to update the current position if (!FLAC__stream_decoder_process_single(m_decoder)) { qWarning() << "Failed to resync FLAC decoder after seeking in" << m_file.fileName(); // Invalidate the current position... m_curFrameIndex = getMaxFrameIndex(); // ...and abort return m_curFrameIndex; } DEBUG_ASSERT(isValidFrameIndex(m_curFrameIndex)); if (m_curFrameIndex < frameIndex) { // Adjust the current position skipSampleFrames(frameIndex - m_curFrameIndex); } } } 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 SoundSourceMp3::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; } SINT seekFrameIndex = findSeekFrameIndex( frameIndex); DEBUG_ASSERT(SINT(m_seekFrameList.size()) > seekFrameIndex); const SINT curSeekFrameIndex = findSeekFrameIndex( m_curFrameIndex); DEBUG_ASSERT(SINT(m_seekFrameList.size()) > curSeekFrameIndex); // some consistency checks DEBUG_ASSERT((curSeekFrameIndex >= seekFrameIndex) || (m_curFrameIndex < frameIndex)); DEBUG_ASSERT((curSeekFrameIndex <= seekFrameIndex) || (m_curFrameIndex > frameIndex)); if ((getMaxFrameIndex() <= m_curFrameIndex) || // out of range (frameIndex < m_curFrameIndex) || // seek backward (seekFrameIndex > (curSeekFrameIndex + kMp3SeekFramePrefetchCount))) { // jump forward // Adjust the seek frame index for prefetching // Implementation note: The type SINT is unsigned so // need to be careful when subtracting! if (kMp3SeekFramePrefetchCount < seekFrameIndex) { // Restart decoding kMp3SeekFramePrefetchCount seek frames // before the expected sync position seekFrameIndex -= kMp3SeekFramePrefetchCount; } else { // Restart decoding at the beginning of the audio stream seekFrameIndex = 0; } m_curFrameIndex = restartDecoding(m_seekFrameList[seekFrameIndex]); if (getMaxFrameIndex() <= m_curFrameIndex) { // out of range -> abort return m_curFrameIndex; } DEBUG_ASSERT(findSeekFrameIndex(m_curFrameIndex) == seekFrameIndex); } // 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 SoundSourceMediaFoundation::seekSampleFrame( SINT frameIndex) { DEBUG_ASSERT(isValidFrameIndex(m_currentFrameIndex)); if (frameIndex >= getMaxFrameIndex()) { // EOF m_currentFrameIndex = getMaxFrameIndex(); return m_currentFrameIndex; } if (frameIndex > m_currentFrameIndex) { // seeking forward SINT skipFramesCount = frameIndex - m_currentFrameIndex; // When to prefer skipping over seeking: // 1) The sample buffer would be discarded before seeking anyway and // skipping those already decoded samples effectively costs nothing // 2) After seeking we need to decode at least kNumberOfPrefetchFrames // before reaching the actual target position -> Only seek if we // need to decode more than 2 * kNumberOfPrefetchFrames frames // while skipping SINT skipFramesCountMax = samples2frames(m_sampleBuffer.getSize()) + 2 * kNumberOfPrefetchFrames; if (skipFramesCount <= skipFramesCountMax) { skipSampleFrames(skipFramesCount); } } if (frameIndex == m_currentFrameIndex) { return m_currentFrameIndex; } // Discard decoded samples m_sampleBuffer.reset(); // Invalidate current position (end of stream) m_currentFrameIndex = getMaxFrameIndex(); if (m_pSourceReader == nullptr) { // reader is dead return m_currentFrameIndex; } // Jump to a position before the actual seeking position. // Prefetching a certain number of frames is necessary for // sample accurate decoding. The decoder needs to decode // some frames in advance to produce the same result at // each position in the stream. SINT seekIndex = std::max(SINT(frameIndex - kNumberOfPrefetchFrames), AudioSource::getMinFrameIndex()); LONGLONG seekPos = m_streamUnitConverter.fromFrameIndex(seekIndex); DEBUG_ASSERT(seekPos >= 0); PROPVARIANT prop; HRESULT hrInitPropVariantFromInt64 = InitPropVariantFromInt64(seekPos, &prop); DEBUG_ASSERT(SUCCEEDED(hrInitPropVariantFromInt64)); // never fails HRESULT hrSetCurrentPosition = m_pSourceReader->SetCurrentPosition(GUID_NULL, prop); PropVariantClear(&prop); if (SUCCEEDED(hrSetCurrentPosition)) { // NOTE(uklotzde): After SetCurrentPosition() the actual position // of the stream is unknown until reading the next samples from // the reader. Please note that the first sample decoded after // SetCurrentPosition() may start BEFORE the actual target position. // See also: https://msdn.microsoft.com/en-us/library/windows/desktop/dd374668(v=vs.85).aspx // "The SetCurrentPosition method does not guarantee exact seeking." ... // "After seeking, the application should call IMFSourceReader::ReadSample // and advance to the desired position. SINT skipFramesCount = frameIndex - seekIndex; if (skipFramesCount > 0) { // We need to fetch at least 1 sample from the reader to obtain the // current position! skipSampleFrames(skipFramesCount); // Now m_currentFrameIndex reflects the actual position of the reader if (m_currentFrameIndex < frameIndex) { // Skip more samples if frameIndex has not yet been reached skipSampleFrames(frameIndex - m_currentFrameIndex); } if (m_currentFrameIndex != frameIndex) { qWarning() << kLogPreamble << "Seek to frame" << frameIndex << "failed"; // Jump to end of stream (= invalidate current position) m_currentFrameIndex = getMaxFrameIndex(); } } else { // We are at the beginning of the stream and don't need // to skip any frames. Calling IMFSourceReader::ReadSample // is not necessary in this special case. DEBUG_ASSERT(frameIndex == AudioSource::getMinFrameIndex()); m_currentFrameIndex = frameIndex; } } else { qWarning() << kLogPreamble << "IMFSourceReader::SetCurrentPosition() failed" << hrSetCurrentPosition; safeRelease(&m_pSourceReader); // kill the reader } return m_currentFrameIndex; }
SINT SoundSourceFLAC::readSampleFrames( SINT numberOfFrames, CSAMPLE* sampleBuffer, SINT sampleBufferSize, bool readStereoSamples) { DEBUG_ASSERT(isValidFrameIndex(m_curFrameIndex)); DEBUG_ASSERT(getSampleBufferSize(numberOfFrames, readStereoSamples) <= sampleBufferSize); const SINT numberOfFramesTotal = math_min(numberOfFrames, getMaxFrameIndex() - m_curFrameIndex); const SINT numberOfSamplesTotal = frames2samples(numberOfFramesTotal); CSAMPLE* outBuffer = sampleBuffer; SINT numberOfSamplesRemaining = numberOfSamplesTotal; while (0 < numberOfSamplesRemaining) { // If our buffer from libflac is empty (either because we explicitly cleared // it or because we've simply used all the samples), ask for a new buffer if (m_sampleBuffer.isEmpty()) { // Save the current frame index const SINT curFrameIndexBeforeProcessing = m_curFrameIndex; // Documentation of FLAC__stream_decoder_process_single(): // "Depending on what was decoded, the metadata or write callback // will be called with the decoded metadata block or audio frame." // See also: https://xiph.org/flac/api/group__flac__stream__decoder.html#ga9d6df4a39892c05955122cf7f987f856 if (!FLAC__stream_decoder_process_single(m_decoder)) { qWarning() << "Failed to decode FLAC file" << m_file.fileName(); break; // abort } // After seeking we might need to skip some samples if the decoder // complained that it has lost sync for some malformed(?) files if (curFrameIndexBeforeProcessing != m_curFrameIndex) { if (curFrameIndexBeforeProcessing > m_curFrameIndex) { qWarning() << "Trying to adjust frame index" << m_curFrameIndex << "<>" << curFrameIndexBeforeProcessing << "while decoding FLAC file" << m_file.fileName(); skipSampleFrames(curFrameIndexBeforeProcessing - m_curFrameIndex); } else { qWarning() << "Unexpected frame index" << m_curFrameIndex << "<>" << curFrameIndexBeforeProcessing << "while decoding FLAC file" << m_file.fileName(); break; // abort } } DEBUG_ASSERT(curFrameIndexBeforeProcessing == m_curFrameIndex); } if (m_sampleBuffer.isEmpty()) { break; // EOF } const SampleBuffer::ReadableChunk readableChunk( m_sampleBuffer.readFromHead(numberOfSamplesRemaining)); const SINT framesToCopy = samples2frames(readableChunk.size()); if (outBuffer) { if (readStereoSamples && (kChannelCountStereo != getChannelCount())) { if (kChannelCountMono == getChannelCount()) { SampleUtil::copyMonoToDualMono(outBuffer, readableChunk.data(), framesToCopy); } else { SampleUtil::copyMultiToStereo(outBuffer, readableChunk.data(), framesToCopy, getChannelCount()); } outBuffer += framesToCopy * kChannelCountStereo; } else { SampleUtil::copy(outBuffer, readableChunk.data(), readableChunk.size()); outBuffer += readableChunk.size(); } } m_curFrameIndex += framesToCopy; numberOfSamplesRemaining -= readableChunk.size(); } DEBUG_ASSERT(isValidFrameIndex(m_curFrameIndex)); DEBUG_ASSERT(numberOfSamplesTotal >= numberOfSamplesRemaining); return samples2frames(numberOfSamplesTotal - numberOfSamplesRemaining); }
SINT SoundSourceFLAC::seekSampleFrame(SINT frameIndex) { DEBUG_ASSERT(isValidFrameIndex(m_curFrameIndex)); DEBUG_ASSERT(isValidFrameIndex(frameIndex)); // Seek to the new position SINT seekFrameIndex = frameIndex; int retryCount = 0; // NOTE(uklotzde): This loop avoids unnecessary seek operations. // If the file is decoded from the beginning to the end during // continuous playback no seek operations are necessary. This // may hide rare seek errors that we have observed in some "flaky" // FLAC files. The retry strategy implemented by this loop tries // to solve these issues when randomly seeking through such a file. while ((seekFrameIndex != m_curFrameIndex) && (retryCount <= kSeekErrorMaxRetryCount)){ // Discard decoded sample data before seeking m_sampleBuffer.reset(); // Invalidate the current position m_curFrameIndex = getMaxFrameIndex(); if (FLAC__stream_decoder_seek_absolute(m_decoder, seekFrameIndex)) { // Success: Set the new position m_curFrameIndex = seekFrameIndex; DEBUG_ASSERT(FLAC__STREAM_DECODER_SEEK_ERROR != FLAC__stream_decoder_get_state(m_decoder)); } else { // Failure qWarning() << "Seek error at" << seekFrameIndex << "in" << m_file.fileName(); if (FLAC__STREAM_DECODER_SEEK_ERROR == FLAC__stream_decoder_get_state(m_decoder)) { // Flush the input stream of the decoder according to the // documentation of FLAC__stream_decoder_seek_absolute() if (!FLAC__stream_decoder_flush(m_decoder)) { qWarning() << "Failed to flush input buffer of the FLAC decoder after seek failure in" << m_file.fileName(); // Invalidate the current position again... m_curFrameIndex = getMaxFrameIndex(); // ...and abort return m_curFrameIndex; } } if (getMinFrameIndex() < seekFrameIndex) { // The next seek position should start at a preceding sample block. // By subtracting max. blocksize from the current seek position it // is guaranteed that the targeted sample blocks of subsequent seek // operations will differ. DEBUG_ASSERT(0 < m_maxBlocksize); seekFrameIndex -= m_maxBlocksize; if (seekFrameIndex < getMinFrameIndex()) { seekFrameIndex = getMinFrameIndex(); } } else { // We have already reached the beginning of the file // and cannot move the seek position backward any // further! break; // exit loop } } } while (m_curFrameIndex != seekFrameIndex); DEBUG_ASSERT(isValidFrameIndex(m_curFrameIndex)); if (frameIndex > m_curFrameIndex) { // Adjust the current position skipSampleFrames(frameIndex - m_curFrameIndex); } return m_curFrameIndex; }