size_t SourceFile::read( Buffer *buffer ) { CI_ASSERT( buffer->getNumChannels() == getNumChannels() ); CI_ASSERT( mReadPos < mNumFrames ); size_t numRead; if( mConverter ) { size_t sourceBufFrames = size_t( buffer->getNumFrames() * (float)getSampleRateNative() / (float)getSampleRate() ); size_t numFramesNeeded = std::min( mFileNumFrames - mReadPos, std::min( getMaxFramesPerRead(), sourceBufFrames ) ); mConverterReadBuffer.setNumFrames( numFramesNeeded ); performRead( &mConverterReadBuffer, 0, numFramesNeeded ); pair<size_t, size_t> count = mConverter->convert( &mConverterReadBuffer, buffer ); numRead = count.second; } else { size_t numFramesNeeded = std::min( mNumFrames - mReadPos, std::min( getMaxFramesPerRead(), buffer->getNumFrames() ) ); numRead = performRead( buffer, 0, numFramesNeeded ); } mReadPos += numRead; return numRead; }
void SourceFile::setupSampleRateConversion() { size_t nativeSampleRate = getSampleRateNative(); size_t outputSampleRate = getSampleRate(); if( ! outputSampleRate ) { outputSampleRate = nativeSampleRate; setSampleRate( nativeSampleRate ); } else if( outputSampleRate != nativeSampleRate ) { mNumFrames = (size_t)std::ceil( (float)mFileNumFrames * (float)outputSampleRate / (float)nativeSampleRate ); if( ! supportsConversion() ) { size_t numChannels = getNumChannels(); mConverter = audio::dsp::Converter::create( nativeSampleRate, outputSampleRate, numChannels, numChannels, getMaxFramesPerRead() ); mConverterReadBuffer.setSize( getMaxFramesPerRead(), numChannels ); } } else { mNumFrames = mFileNumFrames; mConverter.reset(); } }
BufferRef SourceFile::loadBuffer() { seek( 0 ); BufferRef result = make_shared<Buffer>( mNumFrames, getNumChannels() ); if( mConverter ) { // TODO: need BufferView's in order to reduce number of copies Buffer converterDestBuffer( mConverter->getDestMaxFramesPerBlock(), getNumChannels() ); size_t readCount = 0; while( true ) { size_t framesNeeded = min( getMaxFramesPerRead(), mFileNumFrames - readCount ); if( framesNeeded == 0 ) break; // make sourceBuffer num frames match outNumFrames so that Converter doesn't think it has more if( framesNeeded != mConverterReadBuffer.getNumFrames() ) mConverterReadBuffer.setNumFrames( framesNeeded ); size_t outNumFrames = performRead( &mConverterReadBuffer, 0, framesNeeded ); CI_ASSERT( outNumFrames == framesNeeded ); pair<size_t, size_t> count = mConverter->convert( &mConverterReadBuffer, &converterDestBuffer ); result->copyOffset( converterDestBuffer, count.second, mReadPos, 0 ); readCount += outNumFrames; mReadPos += count.second; } } else { size_t readCount = performRead( result.get(), 0, mNumFrames ); mReadPos = readCount; } return result; }
// TODO: test setting MF_LOW_LATENCY attribute void SourceFileMediaFoundation::initReader() { CI_ASSERT( mDataSource ); mFramesRemainingInReadBuffer = 0; ::IMFAttributes *attributes; HRESULT hr = ::MFCreateAttributes( &attributes, 1 ); CI_ASSERT( hr == S_OK ); auto attributesPtr = ci::msw::makeComUnique( attributes ); ::IMFSourceReader *sourceReader; if( mDataSource->isFilePath() ) { hr = ::MFCreateSourceReaderFromURL( mDataSource->getFilePath().wstring().c_str(), attributesPtr.get(), &sourceReader ); if( hr != S_OK ) { string errorString = string( "SourceFileMediaFoundation: Failed to create SourceReader from URL: " ) + mDataSource->getFilePath().string(); if( hr == HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND ) ) errorString += ", file not found."; throw AudioFileExc( errorString ); } } else { mComIStream = ci::msw::makeComUnique( new ci::msw::ComIStream( mDataSource->createStream() ) ); ::IMFByteStream *byteStream; hr = ::MFCreateMFByteStreamOnStream( mComIStream.get(), &byteStream ); CI_ASSERT( hr == S_OK ); mByteStream = ci::msw::makeComUnique( byteStream ); hr = ::MFCreateSourceReaderFromByteStream( byteStream, attributesPtr.get(), &sourceReader ); if( hr != S_OK ) throw AudioFileExc( "SourceFileMediaFoundation: Failed to create SourceReader from resource." ); } mSourceReader = ci::msw::makeComUnique( sourceReader ); // get files native format ::IMFMediaType *nativeType; ::WAVEFORMATEX *nativeFormat; UINT32 formatSize; hr = mSourceReader->GetNativeMediaType( MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, &nativeType ); CI_ASSERT( hr == S_OK ); auto nativeTypeUnique = ci::msw::makeComUnique( nativeType ); hr = ::MFCreateWaveFormatExFromMFMediaType( nativeType, &nativeFormat, &formatSize ); CI_ASSERT( hr == S_OK ); mNumChannels = nativeFormat->nChannels; mSampleRate = nativeFormat->nSamplesPerSec; GUID outputSubType = MFAudioFormat_PCM; // default to PCM 16-bit int, upgrade if we can. mSampleType = SampleType::INT_16; if( nativeFormat->wBitsPerSample == 32 ) { mSampleType = SampleType::FLOAT_32; outputSubType = MFAudioFormat_Float; } else if( nativeFormat->wBitsPerSample == 24 ) { mSampleType = SampleType::INT_24; if( mNumChannels > 1 ) mBitConverterBuffer.setSize( getMaxFramesPerRead(), mNumChannels ); } ::CoTaskMemFree( nativeFormat ); mBytesPerSample = getBytesPerSample( mSampleType ); mReadBuffer.setSize( getMaxFramesPerRead(), mNumChannels ); // set output type, which loads the proper decoder: ::IMFMediaType *outputType; hr = ::MFCreateMediaType( &outputType ); auto outputTypeRef = ci::msw::makeComUnique( outputType ); hr = outputTypeRef->SetGUID( MF_MT_MAJOR_TYPE, MFMediaType_Audio ); CI_ASSERT( hr == S_OK ); hr = outputTypeRef->SetGUID( MF_MT_SUBTYPE, outputSubType ); CI_ASSERT( hr == S_OK ); hr = mSourceReader->SetCurrentMediaType( MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, outputTypeRef.get() ); CI_ASSERT( hr == S_OK ); // after the decoder is loaded, we have to now get the 'complete' output type before retrieving its format ::IMFMediaType *completeOutputType; hr = mSourceReader->GetCurrentMediaType( MF_SOURCE_READER_FIRST_AUDIO_STREAM, &completeOutputType ); CI_ASSERT( hr == S_OK ); ::WAVEFORMATEX *format; hr = ::MFCreateWaveFormatExFromMFMediaType( completeOutputType, &format, &formatSize ); CI_ASSERT( hr == S_OK ); ::CoTaskMemFree( format ); // get seconds: ::PROPVARIANT durationProp; hr = mSourceReader->GetPresentationAttribute( MF_SOURCE_READER_MEDIASOURCE, MF_PD_DURATION, &durationProp ); CI_ASSERT( hr == S_OK ); LONGLONG duration = durationProp.uhVal.QuadPart; mSeconds = nanoSecondsToSeconds( duration ); mNumFrames = mFileNumFrames = size_t( mSeconds * (double)mSampleRate ); ::PROPVARIANT seekProp; hr = mSourceReader->GetPresentationAttribute( MF_SOURCE_READER_MEDIASOURCE, MF_SOURCE_READER_MEDIASOURCE_CHARACTERISTICS, &seekProp ); CI_ASSERT( hr == S_OK ); ULONG flags = seekProp.ulVal; mCanSeek = ( ( flags & MFMEDIASOURCE_CAN_SEEK ) == MFMEDIASOURCE_CAN_SEEK ); }