Result SoundSourceModPlug::tryOpen(const AudioSourceConfig& /*audioSrcCfg*/) { ScopedTimer t("SoundSourceModPlug::open()"); // read module file to byte array const QString fileName(getLocalFileName()); QFile modFile(fileName); qDebug() << "[ModPlug] Loading ModPlug module " << modFile.fileName(); modFile.open(QIODevice::ReadOnly); m_fileBuf = modFile.readAll(); modFile.close(); // get ModPlugFile descriptor for later access m_pModFile = ModPlug::ModPlug_Load(m_fileBuf.constData(), m_fileBuf.length()); if (m_pModFile == NULL) { // an error occured t.cancel(); qDebug() << "[ModPlug] Could not load module file: " << fileName; return ERR; } DEBUG_ASSERT(0 == (kChunkSizeInBytes % sizeof(m_sampleBuf[0]))); const SINT chunkSizeInSamples = kChunkSizeInBytes / sizeof(m_sampleBuf[0]); const SampleBuffer::size_type bufferSizeLimitInSamples = s_bufferSizeLimit / sizeof(m_sampleBuf[0]); // Estimate size of sample buffer (for better performance) aligned // with the chunk size. Beware: Module length estimation is unreliable // due to loops! const SampleBuffer::size_type estimateMilliseconds = ModPlug::ModPlug_GetLength(m_pModFile); const SampleBuffer::size_type estimateSamples = estimateMilliseconds * kChannelCount * kSamplingRate; const SampleBuffer::size_type estimateChunks = (estimateSamples + (chunkSizeInSamples - 1)) / chunkSizeInSamples; const SampleBuffer::size_type sampleBufferCapacity = math_min( estimateChunks * chunkSizeInSamples, bufferSizeLimitInSamples); m_sampleBuf.reserve(sampleBufferCapacity); qDebug() << "[ModPlug] Reserved " << m_sampleBuf.capacity() << " #samples"; // decode samples into sample buffer while (m_sampleBuf.size() < bufferSizeLimitInSamples) { // reserve enough space in sample buffer const SampleBuffer::size_type currentSize = m_sampleBuf.size(); m_sampleBuf.resize(currentSize + chunkSizeInSamples); const int bytesRead = ModPlug::ModPlug_Read(m_pModFile, &m_sampleBuf[currentSize], kChunkSizeInBytes); // adjust size of sample buffer after reading if (0 < bytesRead) { DEBUG_ASSERT(0 == (bytesRead % sizeof(m_sampleBuf[0]))); const SampleBuffer::size_type samplesRead = bytesRead / sizeof(m_sampleBuf[0]); m_sampleBuf.resize(currentSize + samplesRead); } else { // nothing read -> EOF m_sampleBuf.resize(currentSize); break; // exit loop } } qDebug() << "[ModPlug] Filled Sample buffer with " << m_sampleBuf.size() << " samples."; qDebug() << "[ModPlug] Sample buffer has " << m_sampleBuf.capacity() - m_sampleBuf.size() << " samples unused capacity."; setChannelCount(kChannelCount); setSamplingRate(kSamplingRate); setFrameCount(samples2frames(m_sampleBuf.size())); m_seekPos = 0; return OK; }
ReadableSampleFrames SoundSourceFLAC::readSampleFramesClamped( WritableSampleFrames writableSampleFrames) { const SINT firstFrameIndex = writableSampleFrames.frameIndexRange().start(); if (m_curFrameIndex != firstFrameIndex) { // Seek to the new position SINT seekFrameIndex = firstFrameIndex; 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.clear(); invalidateCurFrameIndex(); 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 kLogger.warning() << "Seek error at" << seekFrameIndex << "in file" << 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)) { kLogger.warning() << "Failed to flush input buffer of the FLAC decoder after seek failure" << "in file" << m_file.fileName(); invalidateCurFrameIndex(); // ...and abort return ReadableSampleFrames( IndexRange::between( m_curFrameIndex, m_curFrameIndex)); } } if (frameIndexMin() < 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 < frameIndexMin()) { seekFrameIndex = frameIndexMin(); } } else { // We have already reached the beginning of the file // and cannot move the seek position backwards any // further! 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)); } } DEBUG_ASSERT(m_curFrameIndex == firstFrameIndex); const SINT numberOfSamplesTotal = frames2samples(writableSampleFrames.frameLength()); SINT numberOfSamplesRemaining = numberOfSamplesTotal; SINT outputSampleOffset = 0; 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.empty()) { // 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)) { kLogger.warning() << "Failed to decode FLAC file" << m_file.fileName(); break; // abort } // After decoding we might first need to skip some samples if the // decoder complained that it has lost sync for some malformed(?) // files if (m_curFrameIndex != curFrameIndexBeforeProcessing) { if (m_curFrameIndex < curFrameIndexBeforeProcessing) { kLogger.warning() << "Trying to adjust frame index" << m_curFrameIndex << "<" << curFrameIndexBeforeProcessing << "while decoding FLAC file" << m_file.fileName(); const auto skipFrames = IndexRange::between(m_curFrameIndex, curFrameIndexBeforeProcessing); if (skipFrames != readSampleFramesClamped(WritableSampleFrames(skipFrames)).frameIndexRange()) { kLogger.warning() << "Failed to skip sample frames" << skipFrames << "while decoding FLAC file" << m_file.fileName(); break; // abort } } else { kLogger.warning() << "Unexpected frame index" << m_curFrameIndex << ">" << curFrameIndexBeforeProcessing << "while decoding FLAC file" << m_file.fileName(); break; // abort } } DEBUG_ASSERT(curFrameIndexBeforeProcessing == m_curFrameIndex); } if (m_sampleBuffer.empty()) { break; // EOF } const SINT numberOfSamplesRead = std::min(m_sampleBuffer.readableLength(), 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; } m_curFrameIndex += samples2frames(numberOfSamplesRead); numberOfSamplesRemaining -= numberOfSamplesRead; } DEBUG_ASSERT(isValidFrameIndex(m_curFrameIndex)); DEBUG_ASSERT(numberOfSamplesTotal >= numberOfSamplesRemaining); const SINT numberOfSamples = numberOfSamplesTotal - numberOfSamplesRemaining; return ReadableSampleFrames( IndexRange::forward(firstFrameIndex, samples2frames(numberOfSamples)), SampleBuffer::ReadableSlice( writableSampleFrames.writableData(), std::min(writableSampleFrames.writableLength(), numberOfSamples))); }
FLAC__StreamDecoderWriteStatus SoundSourceFLAC::flacWrite( const FLAC__Frame* frame, const FLAC__int32* const buffer[]) { const SINT numChannels = frame->header.channels; if (channelCount() > numChannels) { kLogger.warning() << "Corrupt or unsupported FLAC file:" << "Invalid number of channels in FLAC frame header" << frame->header.channels << "<>" << channelCount(); return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; } if (sampleRate() != SINT(frame->header.sample_rate)) { kLogger.warning() << "Corrupt or unsupported FLAC file:" << "Invalid sample rate in FLAC frame header" << frame->header.sample_rate << "<>" << sampleRate(); return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; } const SINT numReadableFrames = frame->header.blocksize; if (numReadableFrames > m_maxBlocksize) { kLogger.warning() << "Corrupt or unsupported FLAC file:" << "Block size in FLAC frame header exceeds the maximum block size" << frame->header.blocksize << ">" << m_maxBlocksize; return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; } // According to the API docs the decoder will always report the current // position in "FLAC samples" (= "Mixxx frames") for convenience DEBUG_ASSERT(frame->header.number_type == FLAC__FRAME_NUMBER_TYPE_SAMPLE_NUMBER); m_curFrameIndex = frame->header.number.sample_number; // Decode buffer should be empty before decoding the next frame DEBUG_ASSERT(m_sampleBuffer.empty()); const SampleBuffer::WritableSlice writableSlice( m_sampleBuffer.growForWriting(frames2samples(numReadableFrames))); const SINT numWritableFrames = samples2frames(writableSlice.length()); DEBUG_ASSERT(numWritableFrames <= numReadableFrames); if (numWritableFrames < numReadableFrames) { kLogger.warning() << "Sample buffer has not enough free space for all decoded FLAC samples:" << numWritableFrames << "<" << numReadableFrames; } CSAMPLE* pSampleBuffer = writableSlice.data(); DEBUG_ASSERT(channelCount() <= numChannels); switch (channelCount()) { case 1: { // optimized code for 1 channel (mono) for (SINT i = 0; i < numWritableFrames; ++i) { *pSampleBuffer++ = convertDecodedSample(buffer[0][i], m_bitsPerSample); } break; } case 2: { // optimized code for 2 channels (stereo) for (SINT i = 0; i < numWritableFrames; ++i) { *pSampleBuffer++ = convertDecodedSample(buffer[0][i], m_bitsPerSample); *pSampleBuffer++ = convertDecodedSample(buffer[1][i], m_bitsPerSample); } break; } default: { // generic code for multiple channels for (SINT i = 0; i < numWritableFrames; ++i) { for (SINT j = 0; j < channelCount(); ++j) { *pSampleBuffer++ = convertDecodedSample(buffer[j][i], m_bitsPerSample); } } } } return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; }
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); }
SINT SoundSourceMediaFoundation::readSampleFrames( SINT numberOfFrames, CSAMPLE* sampleBuffer) { SINT numberOfFramesRemaining = numberOfFrames; CSAMPLE* pSampleBuffer = sampleBuffer; while (numberOfFramesRemaining > 0) { SampleBuffer::ReadableChunk readableChunk( m_sampleBuffer.readFromHead( frames2samples(numberOfFramesRemaining))); DEBUG_ASSERT(readableChunk.size() <= frames2samples(numberOfFramesRemaining)); if (readableChunk.size() > 0) { DEBUG_ASSERT(m_currentFrameIndex < getMaxFrameIndex()); if (sampleBuffer != nullptr) { SampleUtil::copy( pSampleBuffer, readableChunk.data(), readableChunk.size()); pSampleBuffer += readableChunk.size(); } m_currentFrameIndex += samples2frames(readableChunk.size()); numberOfFramesRemaining -= samples2frames(readableChunk.size()); } if (numberOfFramesRemaining == 0) { break; // finished reading } // No more decoded sample frames available DEBUG_ASSERT(m_sampleBuffer.isEmpty()); if (m_pSourceReader == nullptr) { break; // abort if reader is dead } DWORD dwFlags = 0; LONGLONG streamPos = 0; IMFSample* pSample = nullptr; HRESULT hrReadSample = m_pSourceReader->ReadSample( kStreamIndex, // [in] DWORD dwStreamIndex, 0, // [in] DWORD dwControlFlags, nullptr, // [out] DWORD *pdwActualStreamIndex, &dwFlags, // [out] DWORD *pdwStreamFlags, &streamPos, // [out] LONGLONG *pllTimestamp, &pSample); // [out] IMFSample **ppSample if (FAILED(hrReadSample)) { qWarning() << kLogPreamble << "IMFSourceReader::ReadSample() failed" << hrReadSample << "-> abort decoding"; DEBUG_ASSERT(pSample == nullptr); break; // abort } if (dwFlags & MF_SOURCE_READERF_ERROR) { qWarning() << kLogPreamble << "IMFSourceReader::ReadSample()" << "detected stream errors" << "(MF_SOURCE_READERF_ERROR)" << "-> abort and stop decoding"; DEBUG_ASSERT(pSample == nullptr); safeRelease(&m_pSourceReader); // kill the reader break; // abort } else if (dwFlags & MF_SOURCE_READERF_ENDOFSTREAM) { DEBUG_ASSERT(pSample == nullptr); break; // finished reading } else if (dwFlags & MF_SOURCE_READERF_CURRENTMEDIATYPECHANGED) { qWarning() << kLogPreamble << "IMFSourceReader::ReadSample()" << "detected that the media type has changed" << "(MF_SOURCE_READERF_CURRENTMEDIATYPECHANGED)" << "-> abort decoding"; DEBUG_ASSERT(pSample == nullptr); break; // abort } DEBUG_ASSERT(pSample != nullptr); SINT readerFrameIndex = m_streamUnitConverter.toFrameIndex(streamPos); DEBUG_ASSERT( (m_currentFrameIndex == getMaxFrameIndex()) || // unknown position after seeking (m_currentFrameIndex == readerFrameIndex)); m_currentFrameIndex = readerFrameIndex; DWORD dwSampleBufferCount = 0; HRESULT hrGetBufferCount = pSample->GetBufferCount(&dwSampleBufferCount); if (FAILED(hrGetBufferCount)) { qWarning() << kLogPreamble << "IMFSample::GetBufferCount() failed" << hrGetBufferCount << "-> abort decoding"; safeRelease(&pSample); break; // abort } DWORD dwSampleTotalLengthInBytes = 0; HRESULT hrGetTotalLength = pSample->GetTotalLength(&dwSampleTotalLengthInBytes); if (FAILED(hrGetTotalLength)) { qWarning() << kLogPreamble << "IMFSample::GetTotalLength() failed" << hrGetTotalLength << "-> abort decoding"; safeRelease(&pSample); break; // abort } // Enlarge temporary buffer (if necessary) DEBUG_ASSERT((dwSampleTotalLengthInBytes % kBytesPerSample) == 0); SINT numberOfSamplesToBuffer = dwSampleTotalLengthInBytes / kBytesPerSample; SINT sampleBufferCapacity = m_sampleBuffer.getCapacity(); DEBUG_ASSERT(sampleBufferCapacity > 0); while (sampleBufferCapacity < numberOfSamplesToBuffer) { sampleBufferCapacity *= 2; } if (m_sampleBuffer.getCapacity() < sampleBufferCapacity) { qDebug() << kLogPreamble << "Enlarging sample buffer capacity" << m_sampleBuffer.getCapacity() << "->" << sampleBufferCapacity; m_sampleBuffer.resetCapacity(sampleBufferCapacity); } DWORD dwSampleBufferIndex = 0; while (dwSampleBufferIndex < dwSampleBufferCount) { IMFMediaBuffer* pMediaBuffer = nullptr; HRESULT hrGetBufferByIndex = pSample->GetBufferByIndex(dwSampleBufferIndex, &pMediaBuffer); if (FAILED(hrGetBufferByIndex)) { qWarning() << kLogPreamble << "IMFSample::GetBufferByIndex() failed" << hrGetBufferByIndex << "-> abort decoding"; DEBUG_ASSERT(pMediaBuffer == nullptr); break; // prematurely exit buffer loop } CSAMPLE* pLockedSampleBuffer = nullptr; DWORD lockedSampleBufferLengthInBytes = 0; HRESULT hrLock = pMediaBuffer->Lock( reinterpret_cast<quint8**>(&pLockedSampleBuffer), nullptr, &lockedSampleBufferLengthInBytes); if (FAILED(hrLock)) { qWarning() << kLogPreamble << "IMFMediaBuffer::Lock() failed" << hrLock << "-> abort decoding"; safeRelease(&pMediaBuffer); break; // prematurely exit buffer loop } DEBUG_ASSERT((lockedSampleBufferLengthInBytes % sizeof(pLockedSampleBuffer[0])) == 0); SINT lockedSampleBufferCount = lockedSampleBufferLengthInBytes / sizeof(pLockedSampleBuffer[0]); SINT copySamplesCount = std::min( frames2samples(numberOfFramesRemaining), lockedSampleBufferCount); if (copySamplesCount > 0) { // Copy samples directly into output buffer if possible if (pSampleBuffer != nullptr) { SampleUtil::copy( pSampleBuffer, pLockedSampleBuffer, copySamplesCount); pSampleBuffer += copySamplesCount; } pLockedSampleBuffer += copySamplesCount; lockedSampleBufferCount -= copySamplesCount; m_currentFrameIndex += samples2frames(copySamplesCount); numberOfFramesRemaining -= samples2frames(copySamplesCount); } // Buffer the remaining samples SampleBuffer::WritableChunk writableChunk( m_sampleBuffer.writeToTail(lockedSampleBufferCount)); // The required capacity has been calculated in advance (see above) DEBUG_ASSERT(writableChunk.size() == lockedSampleBufferCount); SampleUtil::copy( writableChunk.data(), pLockedSampleBuffer, writableChunk.size()); HRESULT hrUnlock = pMediaBuffer->Unlock(); VERIFY_OR_DEBUG_ASSERT(SUCCEEDED(hrUnlock)) { qWarning() << kLogPreamble << "IMFMediaBuffer::Unlock() failed" << hrUnlock; // ignore and continue } safeRelease(&pMediaBuffer); ++dwSampleBufferIndex; } safeRelease(&pSample); if (dwSampleBufferIndex < dwSampleBufferCount) { // Failed to read data from all buffers -> kill the reader qWarning() << kLogPreamble << "Failed to read all buffered samples" << "-> abort and stop decoding"; safeRelease(&m_pSourceReader); break; // abort } } return numberOfFrames - numberOfFramesRemaining; }
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 SoundSourceMediaFoundation::readSampleFrames( SINT numberOfFrames, CSAMPLE* sampleBuffer) { if (sDebug) { qDebug() << "read()" << numberOfFrames; } SINT framesNeeded(numberOfFrames); // first, copy frames from leftover buffer IF the leftover buffer is at // the correct frame if (m_leftoverBufferLength > 0 && m_leftoverBufferPosition == m_nextFrame) { copyFrames(sampleBuffer, &framesNeeded, m_leftoverBuffer, m_leftoverBufferLength); if (m_leftoverBufferLength > 0) { if (framesNeeded != 0) { qWarning() << __FILE__ << __LINE__ << "WARNING: Expected frames needed to be 0. Abandoning this file."; m_dead = true; } m_leftoverBufferPosition += numberOfFrames; } } else { // leftoverBuffer already empty or in the wrong position, clear it m_leftoverBufferLength = 0; } while (!m_dead && framesNeeded > 0) { HRESULT hr(S_OK); DWORD dwFlags(0); qint64 timestamp(0); IMFSample *pSample(nullptr); bool error(false); // set to true to break after releasing hr = m_pReader->ReadSample(MF_SOURCE_READER_FIRST_AUDIO_STREAM, // [in] DWORD dwStreamIndex, 0, // [in] DWORD dwControlFlags, nullptr, // [out] DWORD *pdwActualStreamIndex, &dwFlags, // [out] DWORD *pdwStreamFlags, ×tamp, // [out] LONGLONG *pllTimestamp, &pSample); // [out] IMFSample **ppSample if (FAILED(hr)) { qWarning() << "ReadSample failed!"; break; // abort } if (sDebug) { qDebug() << "ReadSample timestamp:" << timestamp << "frame:" << frameFromMF(timestamp, getSamplingRate()) << "dwflags:" << dwFlags; } if (dwFlags & MF_SOURCE_READERF_ERROR) { // our source reader is now dead, according to the docs qWarning() << "SSMF: ReadSample set ERROR, SourceReader is now dead"; m_dead = true; break; } else if (dwFlags & MF_SOURCE_READERF_ENDOFSTREAM) { qDebug() << "SSMF: End of input file."; break; } else if (dwFlags & MF_SOURCE_READERF_CURRENTMEDIATYPECHANGED) { qWarning() << "SSMF: Type change"; break; } else if (pSample == nullptr) { // generally this will happen when dwFlags contains ENDOFSTREAM, // so it'll be caught before now -bkgood qWarning() << "SSMF: No sample"; continue; } // we now own a ref to the instance at pSample IMFMediaBuffer *pMBuffer(nullptr); // I know this does at least a memcopy and maybe a malloc, if we have // xrun issues with this we might want to look into using // IMFSample::GetBufferByIndex (although MS doesn't recommend this) if (FAILED(hr = pSample->ConvertToContiguousBuffer(&pMBuffer))) { error = true; goto releaseSample; } CSAMPLE *buffer(nullptr); DWORD bufferLengthInBytes(0); hr = pMBuffer->Lock(reinterpret_cast<quint8**>(&buffer), nullptr, &bufferLengthInBytes); if (FAILED(hr)) { error = true; goto releaseMBuffer; } SINT bufferLength = samples2frames(bufferLengthInBytes / sizeof(buffer[0])); if (m_seeking) { qint64 bufferPosition(frameFromMF(timestamp, getSamplingRate())); if (sDebug) { qDebug() << "While seeking to " << m_nextFrame << "WMF put us at" << bufferPosition; } if (m_nextFrame < bufferPosition) { // Uh oh. We are farther forward than our seek target. Emit // silence? We can't seek backwards here. CSAMPLE* pBufferCurpos = sampleBuffer + frames2samples(numberOfFrames - framesNeeded); qint64 offshootFrames = bufferPosition - m_nextFrame; // If we can correct this immediately, write zeros and adjust // m_nextFrame to pretend it never happened. if (offshootFrames <= framesNeeded) { qWarning() << __FILE__ << __LINE__ << "Working around inaccurate seeking. Writing silence for" << offshootFrames << "frames"; // Set offshootFrames samples to zero. memset(pBufferCurpos, 0, sizeof(*pBufferCurpos) * frames2samples(offshootFrames)); // Now m_nextFrame == bufferPosition m_nextFrame += offshootFrames; framesNeeded -= offshootFrames; } else { // It's more complicated. The buffer we have just decoded is // more than framesNeeded frames away from us. It's too hard // for us to handle this correctly currently, so let's just // try to get on with our lives. m_seeking = false; m_nextFrame = bufferPosition; qWarning() << __FILE__ << __LINE__ << "Seek offshoot is too drastic. Cutting losses and pretending the current decoded audio buffer is the right seek point."; } } if (m_nextFrame >= bufferPosition && m_nextFrame < bufferPosition + bufferLength) { // m_nextFrame is in this buffer. buffer += frames2samples(m_nextFrame - bufferPosition); bufferLength -= m_nextFrame - bufferPosition; m_seeking = false; } else { // we need to keep going forward goto releaseRawBuffer; } } // If the bufferLength is larger than the leftover buffer, re-allocate // it with 2x the space. if (frames2samples(bufferLength) > m_leftoverBufferSize) { SINT newSize = m_leftoverBufferSize; while (newSize < frames2samples(bufferLength)) { newSize *= 2; } CSAMPLE* newBuffer = new CSAMPLE[newSize]; memcpy(newBuffer, m_leftoverBuffer, sizeof(m_leftoverBuffer[0]) * m_leftoverBufferSize); delete[] m_leftoverBuffer; m_leftoverBuffer = newBuffer; m_leftoverBufferSize = newSize; } copyFrames( sampleBuffer + frames2samples(numberOfFrames - framesNeeded), &framesNeeded, buffer, bufferLength); releaseRawBuffer: hr = pMBuffer->Unlock(); // I'm ignoring this, MSDN for IMFMediaBuffer::Unlock stipulates // nothing about the state of the instance if this fails so might as // well just let it be released. //if (FAILED(hr)) break; releaseMBuffer: safeRelease(&pMBuffer); releaseSample: safeRelease(&pSample); if (error) break; } SINT framesRead = numberOfFrames - framesNeeded; m_iCurrentPosition += framesRead; m_nextFrame += framesRead; if (m_leftoverBufferLength > 0) { if (framesNeeded != 0) { qWarning() << __FILE__ << __LINE__ << "WARNING: Expected frames needed to be 0. Abandoning this file."; m_dead = true; } m_leftoverBufferPosition = m_nextFrame; } if (sDebug) { qDebug() << "read()" << numberOfFrames << "returning" << framesRead; } return framesRead; }
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); }