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; }
int AudioDecoderMediaFoundation::read(int size, const SAMPLE *destination) { assert(size < sizeof(m_destBufferShort)); if (sDebug) { std::cout << "read() " << size << std::endl; } //TODO: Change this up if we want to support just short samples again -- Albert SHORT_SAMPLE *destBuffer = m_destBufferShort; size_t framesRequested(size / m_iChannels); size_t framesNeeded(framesRequested); // first, copy frames from leftover buffer IF the leftover buffer is at // the correct frame if (m_leftoverBufferLength > 0 && m_leftoverBufferPosition == m_nextFrame) { copyFrames(destBuffer, &framesNeeded, m_leftoverBuffer, m_leftoverBufferLength); if (m_leftoverBufferLength > 0) { if (framesNeeded != 0) { std::cerr << __FILE__ << __LINE__ << "WARNING: Expected frames needed to be 0. Abandoning this file."; m_dead = true; } m_leftoverBufferPosition += framesRequested; } } 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); __int64 timestamp(0); IMFSample *pSample(NULL); 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, NULL, // [out] DWORD *pdwActualStreamIndex, &dwFlags, // [out] DWORD *pdwStreamFlags, ×tamp, // [out] LONGLONG *pllTimestamp, &pSample); // [out] IMFSample **ppSample if (FAILED(hr)) { if (sDebug) { std::cout << "ReadSample failed." << std::endl; } break; } if (sDebug) { std::cout << "ReadSample timestamp: " << timestamp << "frame: " << frameFromMF(timestamp) << "dwflags: " << dwFlags << std::endl; } if (dwFlags & MF_SOURCE_READERF_ERROR) { // our source reader is now dead, according to the docs std::cerr << "SSMF: ReadSample set ERROR, SourceReader is now dead"; m_dead = true; break; } else if (dwFlags & MF_SOURCE_READERF_ENDOFSTREAM) { std::cout << "SSMF: End of input file." << std::endl; break; } else if (dwFlags & MF_SOURCE_READERF_CURRENTMEDIATYPECHANGED) { std::cerr << "SSMF: Type change"; break; } else if (pSample == NULL) { // generally this will happen when dwFlags contains ENDOFSTREAM, // so it'll be caught before now -bkgood std::cerr << "SSMF: No sample"; continue; } // we now own a ref to the instance at pSample IMFMediaBuffer *pMBuffer(NULL); // 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; } short *buffer(NULL); size_t bufferLength(0); hr = pMBuffer->Lock(reinterpret_cast<unsigned __int8**>(&buffer), NULL, reinterpret_cast<DWORD*>(&bufferLength)); if (FAILED(hr)) { error = true; goto releaseMBuffer; } bufferLength /= (m_iBitsPerSample / 8 * m_iChannels); // now in frames if (m_seeking) { __int64 bufferPosition(frameFromMF(timestamp)); if (sDebug) { std::cout << "While seeking to " << m_nextFrame << "WMF put us at " << bufferPosition << std::endl; } if (m_nextFrame < bufferPosition) { // Uh oh. We are farther forward than our seek target. Emit // silence? We can't seek backwards here. SHORT_SAMPLE* pBufferCurpos = destBuffer + (size - framesNeeded * m_iChannels); __int64 offshootFrames = bufferPosition - m_nextFrame; // If we can correct this immediately, write zeros and adjust // m_nextFrame to pretend it never happened. if (offshootFrames <= framesNeeded) { std::cerr << __FILE__ << __LINE__ << "Working around inaccurate seeking. Writing silence for" << offshootFrames << "frames"; // Set offshootFrames * m_iChannels samples to zero. memset(pBufferCurpos, 0, sizeof(*pBufferCurpos) * offshootFrames * m_iChannels); // 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; std::cerr << __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 += (m_nextFrame - bufferPosition) * m_iChannels; 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 (bufferLength * m_iChannels > m_leftoverBufferSize) { int newSize = m_leftoverBufferSize; while (newSize < bufferLength * m_iChannels) { newSize *= 2; } SHORT_SAMPLE* newBuffer = new SHORT_SAMPLE[newSize]; memcpy(newBuffer, m_leftoverBuffer, sizeof(m_leftoverBuffer[0]) * m_leftoverBufferSize); delete [] m_leftoverBuffer; m_leftoverBuffer = newBuffer; m_leftoverBufferSize = newSize; } copyFrames(destBuffer + (size - framesNeeded * m_iChannels), &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; } m_nextFrame += framesRequested - framesNeeded; if (m_leftoverBufferLength > 0) { if (framesNeeded != 0) { std::cerr << __FILE__ << __LINE__ << "WARNING: Expected frames needed to be 0. Abandoning this file." << std::endl; m_dead = true; } m_leftoverBufferPosition = m_nextFrame; } long samples_read = size - framesNeeded * m_iChannels; m_iCurrentPosition += samples_read; if (sDebug) { std::cout << "read() " << size << " returning " << samples_read << std::endl; } const int sampleMax = 1 << (m_iBitsPerSample-1); //Convert to float samples if (m_iChannels == 2) { SAMPLE *destBufferFloat(const_cast<SAMPLE*>(destination)); for (unsigned long i = 0; i < samples_read; i++) { destBufferFloat[i] = destBuffer[i] / (float)sampleMax; } } else //Assuming mono, duplicate into stereo frames... { SAMPLE *destBufferFloat(const_cast<SAMPLE*>(destination)); for (unsigned long i = 0; i < samples_read; i++) { destBufferFloat[i] = destBuffer[i] / (float)sampleMax; } } return samples_read; }