#include "sources/soundsourcemediafoundation.h" #include #include #include #include "util/logger.h" #include "util/sample.h" namespace { const mixxx::Logger kLogger("SoundSourceMediaFoundation"); constexpr SINT kUnknownFrameIndex = -1; constexpr SINT kBytesPerSample = sizeof(CSAMPLE); constexpr SINT kBitsPerSample = kBytesPerSample * 8; constexpr SINT kLeftoverSize = 4096; // in CSAMPLE's, this seems to be the size MF AAC // Decoding will be restarted one or more blocks of samples // before the actual position after seeking randomly in the // audio stream to avoid audible glitches. // // "AAC Audio - Encoder Delay and Synchronization: The 2112 Sample Assumption" // https://developer.apple.com/library/ios/technotes/tn2258/_index.html // "It must also be assumed that without an explicit value, the playback // system will trim 2112 samples from the AAC decoder output when starting // playback from any point in the bitstream." constexpr SINT kNumberOfPrefetchFrames = 2112; // Only read the first audio stream constexpr DWORD kStreamIndex = MF_SOURCE_READER_FIRST_AUDIO_STREAM; /** Microsoft examples use this snippet often. */ template static void safeRelease(T** ppT) { if (*ppT) { (*ppT)->Release(); *ppT = nullptr; } } } // anonymous namespace namespace mixxx { //static const QString SoundSourceProviderMediaFoundation::kDisplayName = QStringLiteral("Microsoft Media Foundation"); //static const QStringList SoundSourceProviderMediaFoundation::kSupportedFileExtensions = { QStringLiteral("aac"), QStringLiteral("m4a"), QStringLiteral("m4v"), QStringLiteral("mp4"), }; SoundSourceProviderPriority SoundSourceProviderMediaFoundation::getPriorityHint( const QString& supportedFileExtension) const { Q_UNUSED(supportedFileExtension) // On Windows SoundSourceMediaFoundation is the preferred decoder for all // supported audio formats. return SoundSourceProviderPriority::Higher; } SoundSourcePointer SoundSourceProviderMediaFoundation::newSoundSource(const QUrl& url) { return newSoundSourceFromUrl(url); } SoundSourceMediaFoundation::SoundSourceMediaFoundation(const QUrl& url) : SoundSource(url), m_hrCoInitialize(E_FAIL), m_hrMFStartup(E_FAIL), m_pSourceReader(nullptr), m_currentFrameIndex(0) { } SoundSourceMediaFoundation::~SoundSourceMediaFoundation() { close(); } SoundSource::OpenResult SoundSourceMediaFoundation::tryOpen( OpenMode /*mode*/, const OpenParams& params) { VERIFY_OR_DEBUG_ASSERT(!SUCCEEDED(m_hrCoInitialize)) { kLogger.warning() << "Cannot reopen file" << getUrlString(); return OpenResult::Failed; } const QString fileName(getLocalFileName()); // Initialize the COM library. m_hrCoInitialize = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); if (FAILED(m_hrCoInitialize)) { kLogger.warning() << "failed to initialize COM"; return OpenResult::Failed; } // Initialize the Media Foundation platform. m_hrMFStartup = MFStartup(MF_VERSION); if (FAILED(m_hrCoInitialize)) { kLogger.warning() << "failed to initialize Media Foundation"; return OpenResult::Failed; } // Create the source reader to read the input file. // Note: we cannot use QString::toStdWString since QT 4 is compiled with // '/Zc:wchar_t-' flag and QT 5 not const ushort* const fileNameUtf16 = fileName.utf16(); static_assert(sizeof(wchar_t) == sizeof(ushort), "QString::utf16(): wchar_t and ushort have different sizes"); HRESULT hr = MFCreateSourceReaderFromURL( reinterpret_cast(fileNameUtf16), nullptr, &m_pSourceReader); if (FAILED(hr)) { kLogger.warning() << "Error opening input file:" << fileName; return OpenResult::Failed; } if (!configureAudioStream(params)) { kLogger.warning() << "Failed to configure audio stream"; return OpenResult::Failed; } m_streamUnitConverter = StreamUnitConverter(this); if (!readProperties()) { kLogger.warning() << "Failed to read file properties"; return OpenResult::Failed; } //Seek to first position, which forces us to skip over all the header frames. //This makes sure we're ready to just let the Analyzer rip and it'll //get the number of samples it expects (ie. no header frames). seekSampleFrame(frameIndexMin()); return OpenResult::Succeeded; } void SoundSourceMediaFoundation::close() { safeRelease(&m_pSourceReader); if (SUCCEEDED(m_hrMFStartup)) { MFShutdown(); m_hrMFStartup = E_FAIL; } if (SUCCEEDED(m_hrCoInitialize)) { CoUninitialize(); m_hrCoInitialize = E_FAIL; } } void SoundSourceMediaFoundation::seekSampleFrame(SINT frameIndex) { DEBUG_ASSERT(isValidFrameIndex(frameIndex)); if (isValidFrameIndex(m_currentFrameIndex) && (m_currentFrameIndex < frameIndex)) { // seeking forward const auto skipFrames = IndexRange::between(m_currentFrameIndex, frameIndex); // 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 = getSignalInfo().samples2frames(m_sampleBuffer.readableLength()) + 2 * kNumberOfPrefetchFrames; if (skipFrames.length() <= skipFramesCountMax) { if (skipFrames != readSampleFramesClamped(WritableSampleFrames(skipFrames)).frameIndexRange()) { kLogger.warning() << "Failed to skip frames before decoding" << skipFrames; return; // abort } } } if (m_currentFrameIndex != frameIndex) { // Discard decoded samples m_sampleBuffer.clear(); // Invalidate current position (end of stream to prevent further reading) m_currentFrameIndex = frameIndexMax(); if (m_pSourceReader == nullptr) { // reader is dead return; // abort } // 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), frameIndexMin()); DEBUG_ASSERT(isValidFrameIndex(seekIndex)); 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. auto skipFrames = IndexRange::between(seekIndex, frameIndex); if (skipFrames.empty()) { // 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 == frameIndexMin()); m_currentFrameIndex = frameIndex; } else { // We need to fetch at least 1 sample from the reader to obtain the // current position! m_currentFrameIndex = kUnknownFrameIndex; // prevent further seeking DEBUG_ASSERT(!isValidFrameIndex(m_currentFrameIndex)); if (skipFrames != readSampleFramesClamped(WritableSampleFrames(skipFrames)).frameIndexRange()) { kLogger.warning() << "Failed to skip frames while seeking" << skipFrames; return; // abort } // Now m_currentFrameIndex reflects the actual position of the reader if (m_currentFrameIndex < frameIndex) { // Skip more frames if the seek has taken us to a position before // the requested target position (see comment above about the behavior // of SetCurrentPosition() skipFrames = IndexRange::between(m_currentFrameIndex, frameIndex); // Skip more samples if frameIndex has not yet been reached if (skipFrames != readSampleFramesClamped(WritableSampleFrames(skipFrames)).frameIndexRange()) { kLogger.warning() << "Failed to skip frames while seeking" << skipFrames; return; // abort } } if (m_currentFrameIndex != frameIndex) { kLogger.warning() << "Seeking to frame" << frameIndex << "failed"; // Jump to end of stream to prevent further reading m_currentFrameIndex = frameIndexMax(); } } } else { kLogger.warning() << "IMFSourceReader::SetCurrentPosition() failed" << hrSetCurrentPosition; safeRelease(&m_pSourceReader); // kill the reader } } } ReadableSampleFrames SoundSourceMediaFoundation::readSampleFramesClamped( const WritableSampleFrames& writableSampleFrames) { const SINT firstFrameIndex = writableSampleFrames.frameIndexRange().start(); if (m_currentFrameIndex != kUnknownFrameIndex) { seekSampleFrame(firstFrameIndex); if (m_currentFrameIndex != firstFrameIndex) { kLogger.warning() << "Failed to position reader at beginning of decoding range" << writableSampleFrames.frameIndexRange(); // Abort return ReadableSampleFrames( mixxx::IndexRange::between( m_currentFrameIndex, m_currentFrameIndex)); } DEBUG_ASSERT(m_currentFrameIndex == firstFrameIndex); } else { // Unknown position should only occur after seeking // when all temporary buffers are empty DEBUG_ASSERT(!isValidFrameIndex(m_currentFrameIndex)); DEBUG_ASSERT(m_sampleBuffer.empty()); } const SINT numberOfFramesTotal = writableSampleFrames.frameLength(); CSAMPLE* pSampleBuffer = writableSampleFrames.writableData(); SINT numberOfFramesRemaining = numberOfFramesTotal; while (numberOfFramesRemaining > 0) { SampleBuffer::ReadableSlice readableSlice( m_sampleBuffer.shrinkForReading( getSignalInfo().frames2samples(numberOfFramesRemaining))); DEBUG_ASSERT(readableSlice.length() <= getSignalInfo().frames2samples(numberOfFramesRemaining)); if (readableSlice.length() > 0) { DEBUG_ASSERT(isValidFrameIndex(m_currentFrameIndex)); DEBUG_ASSERT(m_currentFrameIndex < frameIndexMax()); if (pSampleBuffer) { SampleUtil::copy( pSampleBuffer, readableSlice.data(), readableSlice.length()); pSampleBuffer += readableSlice.length(); } m_currentFrameIndex += getSignalInfo().samples2frames(readableSlice.length()); numberOfFramesRemaining -= getSignalInfo().samples2frames(readableSlice.length()); } if (numberOfFramesRemaining == 0) { break; // finished reading } // No more decoded sample frames available DEBUG_ASSERT(m_sampleBuffer.empty()); 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)) { kLogger.warning() << "IMFSourceReader::ReadSample() failed" << hrReadSample << "-> abort decoding"; DEBUG_ASSERT(pSample == nullptr); break; // abort } if (dwFlags & MF_SOURCE_READERF_ERROR) { kLogger.warning() << "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) { kLogger.warning() << "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); // TODO: Fix debug assertion in else arm. It has been commented // out deliberately to prevent crashes in debug builds. // https://bugs.launchpad.net/mixxx/+bug/1899242 if (m_currentFrameIndex == kUnknownFrameIndex) { // Unknown position after seeking m_currentFrameIndex = readerFrameIndex; // kLogger.debug() // << "Stream position (in sample frames) after seeking:" // << "target =" << writableSampleFrames.frameIndexRange().end() // << "current =" << readerFrameIndex; // } else { // // Both positions should match, otherwise readerFrameIndex // // is inaccurate due to rounding errors after conversion from // // stream units to frames! But if this ever happens we better // // trust m_currentFrameIndex that is continuously updated while // // reading in forward direction. // VERIFY_OR_DEBUG_ASSERT(m_currentFrameIndex == readerFrameIndex) { // kLogger.debug() // << "streamPos [100 ns] =" << streamPos // << ", sampleRate =" << getSignalInfo().getSampleRate(); // kLogger.warning() // << "Stream position (in sample frames) while reading is inaccurate:" // << "expected =" << m_currentFrameIndex // << "actual =" << readerFrameIndex; // } } DWORD dwSampleBufferCount = 0; HRESULT hrGetBufferCount = pSample->GetBufferCount(&dwSampleBufferCount); if (FAILED(hrGetBufferCount)) { kLogger.warning() << "IMFSample::GetBufferCount() failed" << hrGetBufferCount << "-> abort decoding"; safeRelease(&pSample); break; // abort } DWORD dwSampleTotalLengthInBytes = 0; HRESULT hrGetTotalLength = pSample->GetTotalLength(&dwSampleTotalLengthInBytes); if (FAILED(hrGetTotalLength)) { kLogger.warning() << "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.capacity(); DEBUG_ASSERT(sampleBufferCapacity > 0); while (sampleBufferCapacity < numberOfSamplesToBuffer) { sampleBufferCapacity *= 2; } if (m_sampleBuffer.capacity() < sampleBufferCapacity) { kLogger.debug() << "Enlarging sample buffer capacity" << m_sampleBuffer.capacity() << "->" << sampleBufferCapacity; m_sampleBuffer.adjustCapacity(sampleBufferCapacity); } DWORD dwSampleBufferIndex = 0; while (dwSampleBufferIndex < dwSampleBufferCount) { IMFMediaBuffer* pMediaBuffer = nullptr; HRESULT hrGetBufferByIndex = pSample->GetBufferByIndex(dwSampleBufferIndex, &pMediaBuffer); if (FAILED(hrGetBufferByIndex)) { kLogger.warning() << "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(&pLockedSampleBuffer), nullptr, &lockedSampleBufferLengthInBytes); if (FAILED(hrLock)) { kLogger.warning() << "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( getSignalInfo().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 += getSignalInfo().samples2frames(copySamplesCount); numberOfFramesRemaining -= getSignalInfo().samples2frames(copySamplesCount); } // Buffer the remaining samples SampleBuffer::WritableSlice writableSlice( m_sampleBuffer.growForWriting(lockedSampleBufferCount)); // The required capacity has been calculated in advance (see above) DEBUG_ASSERT(writableSlice.length() == lockedSampleBufferCount); SampleUtil::copy( writableSlice.data(), pLockedSampleBuffer, writableSlice.length()); HRESULT hrUnlock = pMediaBuffer->Unlock(); VERIFY_OR_DEBUG_ASSERT(SUCCEEDED(hrUnlock)) { kLogger.warning() << "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 kLogger.warning() << "Failed to read all buffered samples" << "-> abort and stop decoding"; safeRelease(&m_pSourceReader); break; // abort } } DEBUG_ASSERT(isValidFrameIndex(m_currentFrameIndex)); DEBUG_ASSERT(numberOfFramesTotal >= numberOfFramesRemaining); const SINT numberOfFrames = numberOfFramesTotal - numberOfFramesRemaining; return ReadableSampleFrames( IndexRange::forward(firstFrameIndex, numberOfFrames), SampleBuffer::ReadableSlice( writableSampleFrames.writableData(), std::min(writableSampleFrames.writableLength(), getSignalInfo().frames2samples(numberOfFrames)))); } namespace { bool configureMediaType( IMFSourceReader* pSourceReader, SINT* pBitrate, const AudioSource::OpenParams& params = AudioSource::OpenParams()) { DEBUG_ASSERT(pSourceReader); DEBUG_ASSERT(pBitrate); HRESULT hr; IMFMediaType* pAudioType = nullptr; hr = pSourceReader->GetCurrentMediaType( kStreamIndex, &pAudioType); if (FAILED(hr)) { kLogger.warning() << hr << "failed to get current media type from stream"; return false; } //------ Get bitrate from the file, before we change it to get uncompressed audio UINT32 avgBytesPerSecond; hr = pAudioType->GetUINT32(MF_MT_AUDIO_AVG_BYTES_PER_SECOND, &avgBytesPerSecond); if (FAILED(hr)) { kLogger.warning() << hr << "error getting MF_MT_AUDIO_AVG_BYTES_PER_SECOND"; safeRelease(&pAudioType); return false; } *pBitrate = (avgBytesPerSecond * 8) / 1000; hr = pAudioType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio); if (FAILED(hr)) { kLogger.warning() << hr << "failed to set major type to audio"; safeRelease(&pAudioType); return false; } hr = pAudioType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_Float); if (FAILED(hr)) { kLogger.warning() << hr << "failed to set subtype format to float"; safeRelease(&pAudioType); return false; } hr = pAudioType->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, true); if (FAILED(hr)) { kLogger.warning() << hr << "failed to set all samples independent"; safeRelease(&pAudioType); return false; } hr = pAudioType->SetUINT32(MF_MT_FIXED_SIZE_SAMPLES, true); if (FAILED(hr)) { kLogger.warning() << hr << "failed to set fixed size samples"; safeRelease(&pAudioType); return false; } hr = pAudioType->SetUINT32( MF_MT_AUDIO_BITS_PER_SAMPLE, kBitsPerSample); if (FAILED(hr)) { kLogger.warning() << hr << "failed to set bits per sample:" << kBitsPerSample; safeRelease(&pAudioType); return false; } const UINT sampleSize = kLeftoverSize * kBytesPerSample; hr = pAudioType->SetUINT32( MF_MT_SAMPLE_SIZE, sampleSize); if (FAILED(hr)) { kLogger.warning() << hr << "failed to set sample size:" << sampleSize; safeRelease(&pAudioType); return false; } UINT32 numChannels; hr = pAudioType->GetUINT32( MF_MT_AUDIO_NUM_CHANNELS, &numChannels); if (FAILED(hr)) { kLogger.warning() << hr << "failed to get actual number of channels"; safeRelease(&pAudioType); return false; } kLogger.debug() << "Number of channels in input stream" << numChannels; if (params.getSignalInfo().getChannelCount().isValid()) { numChannels = params.getSignalInfo().getChannelCount(); hr = pAudioType->SetUINT32( MF_MT_AUDIO_NUM_CHANNELS, numChannels); if (FAILED(hr)) { kLogger.warning() << hr << "failed to set number of channels:" << numChannels; safeRelease(&pAudioType); return false; } kLogger.debug() << "Requested number of channels" << numChannels; } UINT32 samplesPerSecond; hr = pAudioType->GetUINT32( MF_MT_AUDIO_SAMPLES_PER_SECOND, &samplesPerSecond); if (FAILED(hr)) { kLogger.warning() << hr << "failed to get samples per second"; safeRelease(&pAudioType); return false; } kLogger.debug() << "Samples per second in input stream" << samplesPerSecond; if (params.getSignalInfo().getSampleRate().isValid()) { samplesPerSecond = params.getSignalInfo().getSampleRate(); hr = pAudioType->SetUINT32( MF_MT_AUDIO_SAMPLES_PER_SECOND, samplesPerSecond); if (FAILED(hr)) { kLogger.warning() << hr << "failed to set samples per second:" << samplesPerSecond; safeRelease(&pAudioType); return false; } kLogger.debug() << "Requested samples per second" << samplesPerSecond; } // Set this type on the source reader. The source reader will // load the necessary decoder. hr = pSourceReader->SetCurrentMediaType( kStreamIndex, nullptr, pAudioType); if (FAILED(hr)) { kLogger.warning() << hr << "failed to set media type"; safeRelease(&pAudioType); return false; } safeRelease(&pAudioType); return true; } } // anonymous namespace //------------------------------------------------------------------- // configureAudioStream // // Selects an audio stream from the source file, and configures the // stream to deliver decoded PCM audio. //------------------------------------------------------------------- /** Cobbled together from: http://msdn.microsoft.com/en-us/library/dd757929(v=vs.85).aspx and http://msdn.microsoft.com/en-us/library/dd317928(VS.85).aspx -- Albert If anything in here fails, just bail. I'm not going to decode HRESULTS. -- Bill */ bool SoundSourceMediaFoundation::configureAudioStream(const OpenParams& openParams) { HRESULT hr; // deselect all streams, we only want the first hr = m_pSourceReader->SetStreamSelection( MF_SOURCE_READER_ALL_STREAMS, false); if (FAILED(hr)) { kLogger.warning() << hr << "failed to deselect all streams"; return false; } hr = m_pSourceReader->SetStreamSelection( kStreamIndex, true); if (FAILED(hr)) { kLogger.warning() << hr << "failed to select first audio stream"; return false; } SINT bitrate; if (!configureMediaType(m_pSourceReader, &bitrate, openParams)) { // Fallback: Ignore custom params if (!configureMediaType(m_pSourceReader, &bitrate)) { kLogger.warning() << "Unsupported media type"; return false; } } initBitrateOnce(bitrate); // Get the resulting output format. IMFMediaType* pAudioType = nullptr; hr = m_pSourceReader->GetCurrentMediaType( kStreamIndex, &pAudioType); if (FAILED(hr)) { kLogger.warning() << hr << "failed to retrieve completed media type"; safeRelease(&pAudioType); return false; } // Ensure the stream is selected. hr = m_pSourceReader->SetStreamSelection( kStreamIndex, true); if (FAILED(hr)) { kLogger.warning() << hr << "failed to select first audio stream (again)"; safeRelease(&pAudioType); return false; } UINT32 numChannels; hr = pAudioType->GetUINT32( MF_MT_AUDIO_NUM_CHANNELS, &numChannels); if (FAILED(hr)) { kLogger.warning() << hr << "failed to get actual number of channels"; safeRelease(&pAudioType); return false; } initChannelCountOnce(numChannels); UINT32 samplesPerSecond; hr = pAudioType->GetUINT32( MF_MT_AUDIO_SAMPLES_PER_SECOND, &samplesPerSecond); if (FAILED(hr)) { kLogger.warning() << hr << "failed to get the actual sample rate"; safeRelease(&pAudioType); return false; } initSampleRateOnce(samplesPerSecond); UINT32 leftoverBufferSizeInBytes = 0; hr = pAudioType->GetUINT32(MF_MT_SAMPLE_SIZE, &leftoverBufferSizeInBytes); if (FAILED(hr)) { kLogger.warning() << hr << "failed to get sample buffer size (in bytes)"; safeRelease(&pAudioType); return false; } DEBUG_ASSERT((leftoverBufferSizeInBytes % kBytesPerSample) == 0); const SINT sampleBufferCapacity = leftoverBufferSizeInBytes / kBytesPerSample; if (m_sampleBuffer.capacity() < sampleBufferCapacity) { m_sampleBuffer.adjustCapacity(sampleBufferCapacity); } DEBUG_ASSERT(m_sampleBuffer.capacity() > 0); kLogger.debug() << "Sample buffer capacity" << m_sampleBuffer.capacity(); safeRelease(&pAudioType); return true; } bool SoundSourceMediaFoundation::readProperties() { PROPVARIANT prop; HRESULT hr = S_OK; //Get the duration, provided as a 64-bit integer of 100-nanosecond units hr = m_pSourceReader->GetPresentationAttribute(MF_SOURCE_READER_MEDIASOURCE, MF_PD_DURATION, &prop); if (FAILED(hr)) { kLogger.warning() << "error getting duration"; return false; } initFrameIndexRangeOnce( mixxx::IndexRange::forward( 0, m_streamUnitConverter.toFrameIndex(prop.hVal.QuadPart))); kLogger.debug() << "Frame index range" << frameIndexRange(); PropVariantClear(&prop); return true; } } // namespace mixxx