2025-11-28 00:35:46 +09:00

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;
}