487 lines
12 KiB
C++
487 lines
12 KiB
C++
//////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// RiffParser.cpp : RIFF file parsing.
|
|
//
|
|
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
|
|
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
|
|
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
|
|
// PARTICULAR PURPOSE.
|
|
//
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
#include "WavSource.h"
|
|
#include "RiffParser.h"
|
|
|
|
// FOURCCs that we need
|
|
const FOURCC ckid_WAVE_FILE = FCC('WAVE'); // RIFF type for .wav files
|
|
const FOURCC ckid_WAVE_FMT = FCC('fmt '); // WAVEFORMATEX chunk
|
|
const FOURCC ckid_WAVE_DATA = FCC('data'); // Audio data chunk
|
|
|
|
CRiffChunk::CRiffChunk()
|
|
{
|
|
this->fcc = 0;
|
|
this->cb = 0;
|
|
}
|
|
|
|
CRiffChunk::CRiffChunk(const RIFFCHUNK& c)
|
|
{
|
|
this->fcc = c.fcc;
|
|
this->cb = c.cb;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// CRiffParser constructor
|
|
//
|
|
// pStream: Stream to read from RIFF file
|
|
// id: FOURCC of the RIFF container. Should be 'RIFF' or 'LIST'.
|
|
// cbStartofContainer: Start of the container, as an offset into the stream.
|
|
// hr: Receives the success or failure of the constructor
|
|
//-------------------------------------------------------------------
|
|
|
|
CRiffParser::CRiffParser(IMFByteStream *pStream, FOURCC id, LONGLONG cbStartOfContainer, HRESULT& hr) :
|
|
m_fccID(id),
|
|
m_fccType(0),
|
|
m_llContainerOffset(cbStartOfContainer),
|
|
m_dwContainerSize(0),
|
|
m_llCurrentChunkOffset(0),
|
|
m_dwBytesRemaining(0)
|
|
{
|
|
if (pStream == NULL)
|
|
{
|
|
hr = E_POINTER;
|
|
}
|
|
else
|
|
{
|
|
m_pStream = pStream;
|
|
m_pStream->AddRef();
|
|
|
|
hr = ReadRiffHeader();
|
|
}
|
|
}
|
|
|
|
CRiffParser::~CRiffParser()
|
|
{
|
|
SafeRelease(&m_pStream);
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// Name: ReadRiffHeader
|
|
// Description:
|
|
// Read the container header section. (The 'RIFF' or 'LIST' header.)
|
|
//
|
|
// This method verifies the header is well-formed and caches the
|
|
// container's FOURCC type.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CRiffParser::ReadRiffHeader()
|
|
{
|
|
// Riff chunks must be WORD aligned
|
|
if ((m_llContainerOffset % 2) != 0)
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
// Offset must be positive.
|
|
if (m_llContainerOffset < 0)
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
// Offset + the size of header must not overflow.
|
|
if (MAXLONGLONG - m_llContainerOffset <= sizeof(RIFFLIST))
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
HRESULT hr = S_OK;
|
|
RIFFLIST header = { 0 };
|
|
ULONG cbRead = 0;
|
|
|
|
// Seek to the start of the container.
|
|
hr = m_pStream->SetCurrentPosition(m_llContainerOffset);
|
|
|
|
// Read the header.
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = m_pStream->Read((BYTE*)&header, sizeof(header), &cbRead);
|
|
}
|
|
|
|
// Make sure we read the number of bytes we expected.
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if (cbRead != sizeof(header))
|
|
{
|
|
hr = E_INVALIDARG;
|
|
}
|
|
}
|
|
|
|
// Make sure the header ID matches what the caller expected.
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if (header.fcc != m_fccID)
|
|
{
|
|
hr = E_INVALIDARG;
|
|
}
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// The size given in the RIFF header does not include the 8-byte header.
|
|
// However, our m_llContainerOffset is the offset from the start of the
|
|
// header. Therefore our container size = listed size + size of header.
|
|
|
|
m_dwContainerSize = header.cb + sizeof(RIFFCHUNK);
|
|
m_fccType = header.fccListType;
|
|
|
|
// Start of the first chunk = start of container + size of container header
|
|
m_llCurrentChunkOffset = m_llContainerOffset + sizeof(RIFFLIST);
|
|
|
|
hr = ReadChunkHeader();
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Name: ReadChunkHeader
|
|
// Description:
|
|
// Reads the chunk header. Caller must ensure that the current file
|
|
// pointer is located at the start of the chunk header.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CRiffParser::ReadChunkHeader()
|
|
{
|
|
// Offset + the size of header must not overflow.
|
|
if (MAXLONGLONG - m_llCurrentChunkOffset <= sizeof(RIFFCHUNK))
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
ULONG cbRead;
|
|
HRESULT hr = S_OK;
|
|
|
|
hr = m_pStream->Read((BYTE*)&m_chunk, sizeof(RIFFCHUNK), &cbRead);
|
|
|
|
// Make sure we got the number of bytes we expected.
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if (cbRead != sizeof(RIFFCHUNK))
|
|
{
|
|
hr = E_INVALIDARG;
|
|
}
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
m_dwBytesRemaining = m_chunk.DataSize();
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Name: MoveToNextChunk
|
|
// Description:
|
|
// Advance to the start of the next chunk and read the chunk header.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CRiffParser::MoveToNextChunk()
|
|
{
|
|
// chunk offset is always bigger than container offset,
|
|
// and both are always non-negative.
|
|
assert(m_llCurrentChunkOffset > m_llContainerOffset);
|
|
assert(m_llCurrentChunkOffset >= 0);
|
|
assert(m_llContainerOffset >= 0);
|
|
|
|
HRESULT hr = S_OK;
|
|
LONGLONG maxChunkSize = 0;
|
|
|
|
// Update current chunk offset to the start of the next chunk
|
|
m_llCurrentChunkOffset = m_llCurrentChunkOffset + ChunkActualSize();
|
|
|
|
// Are we at the end?
|
|
if ((m_llCurrentChunkOffset - m_llContainerOffset) >= m_dwContainerSize)
|
|
{
|
|
return E_FAIL;
|
|
}
|
|
|
|
// Current chunk offset + size of current chunk
|
|
if (MAXLONGLONG - m_llCurrentChunkOffset <= ChunkActualSize())
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
// Seek to the start of the chunk.
|
|
hr = m_pStream->SetCurrentPosition(m_llCurrentChunkOffset);
|
|
|
|
// Read the header.
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = ReadChunkHeader();
|
|
}
|
|
|
|
// This chunk cannot be any larger than (container size - (chunk offset - container offset) )
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
maxChunkSize = (LONGLONG)m_dwContainerSize - (m_llCurrentChunkOffset - m_llContainerOffset);
|
|
|
|
if (maxChunkSize < ChunkActualSize())
|
|
{
|
|
hr = E_INVALIDARG;
|
|
}
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
m_dwBytesRemaining = m_chunk.DataSize();
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Name: MoveToChunkOffset
|
|
// Description:
|
|
// Move the file pointer to a byte offset from the start of the
|
|
// current chunk.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CRiffParser::MoveToChunkOffset(DWORD dwOffset)
|
|
{
|
|
if (dwOffset > m_chunk.DataSize())
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
hr = m_pStream->SetCurrentPosition(m_llCurrentChunkOffset + dwOffset + sizeof(RIFFCHUNK));
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
m_dwBytesRemaining = m_chunk.DataSize() - dwOffset;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// Name: MoveToChunkOffset
|
|
// Description:
|
|
// Move the file pointer to the start of the current chunk.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CRiffParser::MoveToStartOfChunk()
|
|
{
|
|
return MoveToChunkOffset(0);
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// Name: ReadDataFromChunk
|
|
// Description:
|
|
// Read data from the current chunk. (Starts at the current file ptr.)
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CRiffParser::ReadDataFromChunk( BYTE* pData, DWORD dwLengthInBytes )
|
|
{
|
|
if (dwLengthInBytes > m_dwBytesRemaining)
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
HRESULT hr = S_OK;
|
|
ULONG cbRead = 0;
|
|
|
|
hr = m_pStream->Read(pData, dwLengthInBytes, &cbRead);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
m_dwBytesRemaining -= cbRead;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
|
|
|
|
/////////////////
|
|
|
|
// CWavRiffParser is a specialization of the generic RIFF parser object,
|
|
// and is designed to parse .wav files.
|
|
|
|
CWavRiffParser::CWavRiffParser(IMFByteStream *pStream, HRESULT& hr) :
|
|
m_pWaveFormat(NULL), m_cbWaveFormat(0), m_rtDuration(0),
|
|
CRiffParser(pStream, FOURCC_RIFF, 0, hr)
|
|
{
|
|
|
|
}
|
|
|
|
CWavRiffParser::~CWavRiffParser()
|
|
{
|
|
CoTaskMemFree(m_pWaveFormat);
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// Name: Create
|
|
// Description: Static creation function.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CWavRiffParser::Create(IMFByteStream *pStream, CWavRiffParser **ppParser)
|
|
{
|
|
if (ppParser == NULL)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
// Create a riff parser for the 'RIFF' container
|
|
CWavRiffParser *pParser = new (std::nothrow) CWavRiffParser(pStream, hr);
|
|
|
|
if (pParser == NULL)
|
|
{
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
// Check the RIFF file type.
|
|
if (pParser->RiffType() != ckid_WAVE_FILE)
|
|
{
|
|
hr = MF_E_INVALID_FILE_FORMAT;
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
*ppParser = pParser;
|
|
}
|
|
else
|
|
{
|
|
delete pParser;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// Name: ParseWAVEHeader
|
|
// Description: Parsers the RIFF WAVE header.
|
|
//
|
|
// Note:
|
|
// .wav files should look like this:
|
|
//
|
|
// RIFF ('WAVE'
|
|
// 'fmt ' = WAVEFORMATEX structure
|
|
// 'data' = audio data
|
|
// )
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CWavRiffParser::ParseWAVEHeader()
|
|
{
|
|
HRESULT hr = S_OK;
|
|
BOOL bFoundData = FALSE;
|
|
|
|
// Iterate through the RIFF chunks. Ignore chunks we don't recognize.
|
|
while (SUCCEEDED(hr))
|
|
{
|
|
if (Chunk().FourCC() == ckid_WAVE_FMT)
|
|
{
|
|
// Read the WAVEFORMATEX structure allegedly contained in this chunk.
|
|
// This method does NOT validate the contents of the structure.
|
|
hr = ReadFormatBlock();
|
|
}
|
|
else if (Chunk().FourCC() == ckid_WAVE_DATA)
|
|
{
|
|
// Found the start of the audio data. The format chunk should precede the
|
|
// data chunk. If we did not find the formt chunk yet, that is a failure
|
|
// case (see below)
|
|
bFoundData = TRUE;
|
|
break;
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = MoveToNextChunk();
|
|
}
|
|
}
|
|
|
|
// To be valid, the file must have a format chunk and a data chunk.
|
|
// Fail if either of these conditions is not met.
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if (m_pWaveFormat == NULL || !bFoundData)
|
|
{
|
|
hr = MF_E_INVALID_FILE_FORMAT;
|
|
}
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
m_rtDuration = AudioDurationFromBufferSize(m_pWaveFormat, Chunk().DataSize());
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Name: ReadFormatBlock
|
|
// Description: Reads the WAVEFORMATEX structure from the file header.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CWavRiffParser::ReadFormatBlock()
|
|
{
|
|
assert(Chunk().FourCC() == ckid_WAVE_FMT);
|
|
assert(m_pWaveFormat == NULL);
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
// Some .wav files do not include the cbSize field of the WAVEFORMATEX
|
|
// structure. For uncompressed PCM audio, field is always zero.
|
|
const DWORD cbMinFormatSize = sizeof(WAVEFORMATEX) - sizeof(WORD);
|
|
|
|
DWORD cbFormatSize = 0; // Size of the actual format block in the file.
|
|
|
|
// Validate the size
|
|
if (Chunk().DataSize() < cbMinFormatSize)
|
|
{
|
|
return MF_E_INVALID_FILE_FORMAT;
|
|
}
|
|
|
|
// Allocate a buffer for the WAVEFORMAT structure.
|
|
cbFormatSize = Chunk().DataSize();
|
|
|
|
// We store a WAVEFORMATEX structure, so our format block must be at
|
|
// least sizeof(WAVEFORMATEX) even if the format block in the file
|
|
// is smaller. See note above about cbMinFormatSize.
|
|
m_cbWaveFormat = max(cbFormatSize, sizeof(WAVEFORMATEX));
|
|
|
|
m_pWaveFormat = (WAVEFORMATEX*)CoTaskMemAlloc(m_cbWaveFormat);
|
|
if (m_pWaveFormat == NULL)
|
|
{
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
// Zero our structure, in case cbFormatSize < m_cbWaveFormat.
|
|
ZeroMemory(m_pWaveFormat, m_cbWaveFormat);
|
|
|
|
// Now read cbFormatSize bytes from the file.
|
|
hr = ReadDataFromChunk((BYTE*)m_pWaveFormat, cbFormatSize);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
CoTaskMemFree(m_pWaveFormat);
|
|
m_pWaveFormat = NULL;
|
|
m_cbWaveFormat = 0;
|
|
}
|
|
return hr;
|
|
}
|
|
|