AudioFile::SampleType AudioFileReaderInternal::getSampleType() const throw() { const AudioFile::Encoding encoding = getEncoding(); const int bytesPerSample = getBytesPerSample(); const bool isPCM = encoding & AudioFile::EncodingFlagPCM; const bool isFloat = encoding & AudioFile::EncodingFlagFloat; if (isPCM) { switch (bytesPerSample) { case 2: return AudioFile::Short; case 3: return AudioFile::Int24; case 4: return AudioFile::Int; case 1: return AudioFile::Char; default: goto exit; } } else if (isFloat) { switch (bytesPerSample) { case 4: return AudioFile::Float; case 8: return AudioFile::Double; default: goto exit; } } exit: return AudioFile::Invalid; }
void AudioDecoderThread::planarToInterleaved(char* pOutput, AVFrame* pInputFrame, int numChannels, int numSamples) { AVG_ASSERT(numChannels <= 8); if (numSamples == 0) { // Fishy, some ogg files have no proper frame_size set. But outputBufferSamples // worked for sample ogg file. numSamples = m_AP.m_OutputBufferSamples; } int i, j; int bytesPerSample = getBytesPerSample(m_InputSampleFormat); char * pPlanes[8] = {}; for (i=0; i<numChannels; i++) { pPlanes[i] = (char*)(pInputFrame->data[i]); } for (i=0; i<numSamples; i++) { for (j=0; j<numChannels; j++) { memcpy(pOutput, pPlanes[j], bytesPerSample); pOutput += bytesPerSample; pPlanes[j] += bytesPerSample; } } }
void AudioDecoderThread::decodePacket(AVPacket* pPacket) { char* pDecodedData = 0; AVPacket* pTempPacket = new AVPacket; av_init_packet(pTempPacket); pTempPacket->data = pPacket->data; pTempPacket->size = pPacket->size; AVFrame* pDecodedFrame; pDecodedFrame = avcodec_alloc_frame(); while (pTempPacket->size > 0) { int gotFrame = 0; int bytesDecoded; int bytesConsumed = avcodec_decode_audio4(m_pStream->codec, pDecodedFrame, &gotFrame, pTempPacket); if (gotFrame) { bytesDecoded = av_samples_get_buffer_size(0, m_pStream->codec->channels, pDecodedFrame->nb_samples, m_pStream->codec->sample_fmt, 1); pDecodedData = (char*)(pDecodedFrame->data[0]); } else { bytesDecoded = 0; } // This is triggered for some strange/broken videos. // AVG_ASSERT(bytesConsumed != 0); if (bytesConsumed < 0) { // Error decoding -> throw away current packet. bytesDecoded = 0; pTempPacket->size = 0; } else { pTempPacket->data += bytesConsumed; pTempPacket->size -= bytesConsumed; } if (bytesDecoded > 0) { int framesDecoded = bytesDecoded/(m_pStream->codec->channels* getBytesPerSample(m_InputSampleFormat)); AudioBufferPtr pBuffer; bool bNeedsResample = (m_InputSampleRate != m_AP.m_SampleRate || m_InputSampleFormat != AV_SAMPLE_FMT_S16 || m_pStream->codec->channels != m_AP.m_Channels); bool bIsPlanar = false; #if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51, 27, 0) bIsPlanar = av_sample_fmt_is_planar((AVSampleFormat)m_InputSampleFormat); if (bIsPlanar) { char* pPackedData = (char*)av_malloc(AVCODEC_MAX_AUDIO_FRAME_SIZE + FF_INPUT_BUFFER_PADDING_SIZE); planarToInterleaved(pPackedData, pDecodedFrame, m_pStream->codec->channels, framesDecoded); pBuffer = resampleAudio(pPackedData, framesDecoded, av_get_packed_sample_fmt((AVSampleFormat)m_InputSampleFormat)); av_free(pPackedData); bNeedsResample = false; } #endif if (bNeedsResample) { pBuffer = resampleAudio(pDecodedData, framesDecoded, m_InputSampleFormat); } else if (!bIsPlanar) { pBuffer = AudioBufferPtr(new AudioBuffer(framesDecoded, m_AP)); memcpy(pBuffer->getData(), pDecodedData, bytesDecoded); } m_LastFrameTime += float(pBuffer->getNumFrames())/m_AP.m_SampleRate; pushAudioMsg(pBuffer, m_LastFrameTime); } } #if LIBAVCODEC_VERSION_MAJOR > 53 avcodec_free_frame(&pDecodedFrame); delete pDecodedFrame; #endif delete pTempPacket; }
TargetFileMediaFoundation::TargetFileMediaFoundation( const DataTargetRef &dataTarget, size_t sampleRate, size_t numChannels, SampleType sampleType, const std::string &extension ) : TargetFile( dataTarget, sampleRate, numChannels, sampleType ), mStreamIndex( 0 ) { MediaFoundationInitializer::initMediaFoundation(); ::IMFSinkWriter *sinkWriter; HRESULT hr = ::MFCreateSinkWriterFromURL( dataTarget->getFilePath().wstring().c_str(), NULL, NULL, &sinkWriter ); if( hr != S_OK ) { string errorString = string( "TargetFileMediaFoundation: Failed to create SinkWriter from URL: " ) + dataTarget->getFilePath().string(); if( hr == HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND ) ) errorString += ", file not found."; throw AudioFileExc( errorString ); } mSinkWriter = ci::msw::makeComUnique( sinkWriter ); mSampleSize = getBytesPerSample( mSampleType ); const UINT32 bitsPerSample = 8 * mSampleSize; const WORD blockAlignment = mNumChannels * mSampleSize; const DWORD averageBytesPerSecond = mSampleRate * blockAlignment; // Set the output media type. IMFMediaType *mediaType; hr = ::MFCreateMediaType( &mediaType ); CI_ASSERT( hr == S_OK ); auto mediaTypeManaged = ci::msw::makeComUnique( mediaType ); hr = mediaType->SetGUID( MF_MT_MAJOR_TYPE, MFMediaType_Audio ); CI_ASSERT( hr == S_OK ); const ::GUID audioFormat = getMfAudioFormat( mSampleType ); hr = mediaType->SetGUID( MF_MT_SUBTYPE, audioFormat ); CI_ASSERT( hr == S_OK ); hr = mediaType->SetUINT32( MF_MT_AUDIO_SAMPLES_PER_SECOND, (UINT32)mSampleRate ); CI_ASSERT( hr == S_OK ); hr = mediaType->SetUINT32( MF_MT_AUDIO_BITS_PER_SAMPLE, bitsPerSample ); CI_ASSERT( hr == S_OK ); hr = mediaType->SetUINT32( MF_MT_AUDIO_BLOCK_ALIGNMENT, blockAlignment ); CI_ASSERT( hr == S_OK ); hr = mediaType->SetUINT32( MF_MT_AUDIO_AVG_BYTES_PER_SECOND, averageBytesPerSecond ); CI_ASSERT( hr == S_OK ); hr = mediaType->SetUINT32( MF_MT_AUDIO_NUM_CHANNELS, (UINT32)mNumChannels ); CI_ASSERT( hr == S_OK ); hr = mediaType->SetUINT32( MF_MT_ALL_SAMPLES_INDEPENDENT, 1 ); CI_ASSERT( hr == S_OK ); hr = mediaType->SetUINT32( MF_MT_FIXED_SIZE_SAMPLES, 1 ); CI_ASSERT( hr == S_OK ); hr = mSinkWriter->AddStream( mediaType, &mStreamIndex ); CI_ASSERT( hr == S_OK ); // Tell the sink writer to start accepting data. hr = mSinkWriter->BeginWriting(); CI_ASSERT( hr == S_OK ); }
// 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 ); }
IFrame* AudioFrameBuffer::getFrameSampleNb(const size_t sampleNb) { const size_t expectedSize = sampleNb * getBytesPerSample(); return getFrame(expectedSize); }