2073 lines
55 KiB
C++
2073 lines
55 KiB
C++
//////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// MPEG1Source.h
|
|
// Implements the MPEG-1 media source object.
|
|
//
|
|
// 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 "MPEG1Source.h"
|
|
|
|
//-------------------------------------------------------------------
|
|
//
|
|
// Notes:
|
|
// This sample contains an MPEG-1 source.
|
|
//
|
|
// - The source parses MPEG-1 systems-layer streams and generates
|
|
// samples that contain MPEG-1 payloads.
|
|
// - The source does not support files that contain a raw MPEG-1
|
|
// video or audio stream.
|
|
// - The source does not support seeking.
|
|
//
|
|
//-------------------------------------------------------------------
|
|
|
|
#pragma warning( push )
|
|
#pragma warning( disable : 4355 ) // 'this' used in base member initializer list
|
|
|
|
|
|
HRESULT CreateVideoMediaType(const MPEG1VideoSeqHeader& videoSeqHdr, IMFMediaType **ppType);
|
|
HRESULT CreateAudioMediaType(const MPEG1AudioFrameHeader& audioHeader, IMFMediaType **ppType);
|
|
BOOL SampleRequestMatch(SourceOp *pOp1, SourceOp *pOp2);
|
|
|
|
|
|
/* Public class methods */
|
|
|
|
//-------------------------------------------------------------------
|
|
// Name: CreateInstance
|
|
// Static method to create an instance of the source.
|
|
//
|
|
// ppSource: Receives a ref-counted pointer to the source.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT MPEG1Source::CreateInstance(MPEG1Source **ppSource)
|
|
{
|
|
CheckPointer(ppSource, E_POINTER);
|
|
|
|
HRESULT hr = S_OK;
|
|
MPEG1Source *pSource = new MPEG1Source(hr);
|
|
if (pSource == NULL)
|
|
{
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
*ppSource = pSource;
|
|
(*ppSource)->AddRef();
|
|
}
|
|
|
|
SAFE_RELEASE(pSource);
|
|
return hr;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// IUnknown methods
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT MPEG1Source::QueryInterface(REFIID riid, void** ppv)
|
|
{
|
|
static const QITAB qit[] =
|
|
{
|
|
QITABENT(MPEG1Source, IMFMediaEventGenerator),
|
|
QITABENT(MPEG1Source, IMFMediaSource),
|
|
{ 0 }
|
|
};
|
|
return QISearch(this, qit, riid, ppv);
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// IMFMediaEventGenerator methods
|
|
//
|
|
// All of the IMFMediaEventGenerator methods do the following:
|
|
// 1. Check for shutdown status.
|
|
// 2. Call the event queue helper object.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT MPEG1Source::BeginGetEvent(IMFAsyncCallback* pCallback,IUnknown* punkState)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
AutoLock lock(m_critSec);
|
|
|
|
CHECK_HR(hr = CheckShutdown());
|
|
CHECK_HR(hr = m_pEventQueue->BeginGetEvent(pCallback, punkState));
|
|
|
|
done:
|
|
return hr;
|
|
}
|
|
|
|
HRESULT MPEG1Source::EndGetEvent(IMFAsyncResult* pResult, IMFMediaEvent** ppEvent)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
AutoLock lock(m_critSec);
|
|
|
|
CHECK_HR(hr = CheckShutdown());
|
|
CHECK_HR(hr = m_pEventQueue->EndGetEvent(pResult, ppEvent));
|
|
|
|
done:
|
|
return hr;
|
|
}
|
|
|
|
HRESULT MPEG1Source::GetEvent(DWORD dwFlags, IMFMediaEvent** ppEvent)
|
|
{
|
|
// NOTE:
|
|
// GetEvent can block indefinitely, so we don't hold the critical
|
|
// section. Therefore we need to use a local copy of the event queue
|
|
// pointer, to make sure the pointer remains valid.
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
IMFMediaEventQueue *pQueue = NULL;
|
|
|
|
{ // scope for lock
|
|
|
|
AutoLock lock(m_critSec);
|
|
|
|
// Check shutdown
|
|
CHECK_HR(hr = CheckShutdown());
|
|
|
|
// Cache a local pointer to the queue.
|
|
pQueue = m_pEventQueue;
|
|
pQueue->AddRef();
|
|
|
|
} // release lock
|
|
|
|
// Use the local pointer to call GetEvent.
|
|
CHECK_HR(hr = pQueue->GetEvent(dwFlags, ppEvent));
|
|
|
|
done:
|
|
SAFE_RELEASE(pQueue);
|
|
return hr;
|
|
}
|
|
|
|
HRESULT MPEG1Source::QueueEvent(MediaEventType met, REFGUID guidExtendedType, HRESULT hrStatus, const PROPVARIANT* pvValue)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
AutoLock lock(m_critSec);
|
|
CHECK_HR(hr = CheckShutdown());
|
|
|
|
CHECK_HR(hr = m_pEventQueue->QueueEventParamVar(met, guidExtendedType, hrStatus, pvValue));
|
|
|
|
done:
|
|
return hr;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// IMFMediaSource methods
|
|
//-------------------------------------------------------------------
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// CreatePresentationDescriptor
|
|
// Returns a shallow copy of the source's presentation descriptor.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT MPEG1Source::CreatePresentationDescriptor(
|
|
IMFPresentationDescriptor** ppPresentationDescriptor
|
|
)
|
|
{
|
|
AutoLock lock(m_critSec);
|
|
|
|
if (ppPresentationDescriptor == NULL)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
// Fail if the source is shut down.
|
|
CHECK_HR(hr = CheckShutdown());
|
|
|
|
// Fail if the source was not initialized yet.
|
|
CHECK_HR(hr = IsInitialized());
|
|
|
|
// Do we have a valid presentation descriptor?
|
|
if (m_pPresentationDescriptor == NULL)
|
|
{
|
|
CHECK_HR(hr = MF_E_NOT_INITIALIZED);
|
|
}
|
|
|
|
// Clone our presentation descriptor.
|
|
CHECK_HR(hr = m_pPresentationDescriptor->Clone(ppPresentationDescriptor));
|
|
|
|
done:
|
|
return hr;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// GetCharacteristics
|
|
// Returns capabilities flags.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT MPEG1Source::GetCharacteristics(DWORD* pdwCharacteristics)
|
|
{
|
|
AutoLock lock(m_critSec);
|
|
|
|
if (pdwCharacteristics == NULL)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
CHECK_HR(hr = CheckShutdown());
|
|
|
|
*pdwCharacteristics = MFMEDIASOURCE_CAN_PAUSE;
|
|
|
|
// NOTE: This sample does not implement seeking, so we do not
|
|
// include the MFMEDIASOURCE_CAN_SEEK flag.
|
|
|
|
done:
|
|
return hr;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// Pause
|
|
// Pauses the source.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT MPEG1Source::Pause()
|
|
{
|
|
AutoLock lock(m_critSec);
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
// Fail if the source is shut down.
|
|
CHECK_HR(hr = CheckShutdown());
|
|
|
|
// Queue the operation.
|
|
CHECK_HR(hr = QueueAsyncOperation(SourceOp::OP_PAUSE));
|
|
|
|
done:
|
|
return hr;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Shutdown
|
|
// Shuts down the source and releases all resources.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT MPEG1Source::Shutdown()
|
|
{
|
|
AutoLock lock(m_critSec);
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
MPEG1Stream *pStream = NULL;
|
|
|
|
CHECK_HR(hr = CheckShutdown());
|
|
|
|
// Shut down the stream objects.
|
|
for (DWORD i = 0; i < m_streams.GetCount(); i++)
|
|
{
|
|
(void)m_streams[i]->Shutdown();
|
|
}
|
|
|
|
// Shut down the event queue.
|
|
if (m_pEventQueue)
|
|
{
|
|
(void)m_pEventQueue->Shutdown();
|
|
}
|
|
|
|
// Release objects.
|
|
for (DWORD i = 0; i < m_streams.GetCount(); i++)
|
|
{
|
|
SAFE_RELEASE(m_streams[i]);
|
|
}
|
|
m_streams.SetSize(0);
|
|
m_streamMap.Clear();
|
|
|
|
SAFE_RELEASE(m_pEventQueue);
|
|
SAFE_RELEASE(m_pPresentationDescriptor);
|
|
SAFE_RELEASE(m_pBeginOpenResult);
|
|
SAFE_RELEASE(m_pByteStream);
|
|
SAFE_RELEASE(m_pCurrentOp);
|
|
|
|
CoTaskMemFree(m_pHeader);
|
|
m_pHeader = NULL;
|
|
|
|
SAFE_DELETE(m_pParser);
|
|
|
|
// Set the state.
|
|
m_state = STATE_SHUTDOWN;
|
|
|
|
done:
|
|
return hr;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// Start
|
|
// Starts or seeks the media source.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT MPEG1Source::Start(
|
|
IMFPresentationDescriptor* pPresentationDescriptor,
|
|
const GUID* pguidTimeFormat,
|
|
const PROPVARIANT* pvarStartPosition
|
|
)
|
|
{
|
|
AutoLock lock(m_critSec);
|
|
|
|
HRESULT hr = S_OK;
|
|
SourceOp *pAsyncOp = NULL;
|
|
|
|
// Check parameters.
|
|
// Start position and presentation descriptor cannot be NULL.
|
|
if (pvarStartPosition == NULL || pPresentationDescriptor == NULL)
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
// Check the time format. We support only reference time, which is
|
|
// indicated by a NULL parameter or by time format = GUID_NULL.
|
|
if ((pguidTimeFormat != NULL) && (*pguidTimeFormat != GUID_NULL))
|
|
{
|
|
// Unrecognized time format GUID.
|
|
return MF_E_UNSUPPORTED_TIME_FORMAT;
|
|
}
|
|
|
|
// Check the data type of the start position.
|
|
if ((pvarStartPosition->vt != VT_I8) && (pvarStartPosition->vt != VT_EMPTY))
|
|
{
|
|
return MF_E_UNSUPPORTED_TIME_FORMAT;
|
|
}
|
|
|
|
// Check if this is a seek request.
|
|
// Currently, this sample does not support seeking.
|
|
|
|
if (pvarStartPosition->vt == VT_I8)
|
|
{
|
|
// If the current state is STOPPED, then position 0 is valid.
|
|
|
|
// If the current state is anything else, then the
|
|
// start position must be VT_EMPTY (current position).
|
|
|
|
if ((m_state != STATE_STOPPED) || (pvarStartPosition->hVal.QuadPart != 0))
|
|
{
|
|
return MF_E_INVALIDREQUEST;
|
|
}
|
|
}
|
|
|
|
// Fail if the source is shut down.
|
|
CHECK_HR(hr = CheckShutdown());
|
|
|
|
// Fail if the source was not initialized yet.
|
|
CHECK_HR(hr = IsInitialized());
|
|
|
|
// Perform a sanity check on the caller's presentation descriptor.
|
|
CHECK_HR(hr = ValidatePresentationDescriptor(pPresentationDescriptor));
|
|
|
|
|
|
// The operation looks OK. Complete the operation asynchronously.
|
|
|
|
// Create the state object for the async operation.
|
|
CHECK_HR(hr = SourceOp::CreateStartOp(pPresentationDescriptor, &pAsyncOp));
|
|
|
|
CHECK_HR(hr = pAsyncOp->SetData(*pvarStartPosition));
|
|
|
|
// Queue the operation.
|
|
CHECK_HR(hr = QueueOperation(pAsyncOp));
|
|
|
|
done:
|
|
SAFE_RELEASE(pAsyncOp);
|
|
return hr;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// Stop
|
|
// Stops the media source.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT MPEG1Source::Stop()
|
|
{
|
|
AutoLock lock(m_critSec);
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
// Fail if the source is shut down.
|
|
CHECK_HR(hr = CheckShutdown());
|
|
|
|
// Fail if the source was not initialized yet.
|
|
CHECK_HR(hr = IsInitialized());
|
|
|
|
// Queue the operation.
|
|
CHECK_HR(hr = QueueAsyncOperation(SourceOp::OP_STOP));
|
|
|
|
done:
|
|
return hr;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// Public non-interface methods
|
|
//-------------------------------------------------------------------
|
|
|
|
//-------------------------------------------------------------------
|
|
// BeginOpen
|
|
// Begins reading the byte stream to initialize the source.
|
|
// Called by the byte-stream handler when it creates the source.
|
|
//
|
|
// This method is asynchronous. When the operation completes,
|
|
// the callback is invoked and the byte-stream handler calls
|
|
// EndOpen.
|
|
//
|
|
// pStream: Pointer to the byte stream for the MPEG-1 stream.
|
|
// pCB: Pointer to the byte-stream handler's callback.
|
|
// pState: State object for the async callback. (Can be NULL.)
|
|
//
|
|
// Note: The source reads enough data to find one packet header
|
|
// for each audio or video stream. This enables the source to
|
|
// create a presentation descriptor that describes the format of
|
|
// each stream. The source queues the packets that it reads during
|
|
// BeginOpen.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT MPEG1Source::BeginOpen(IMFByteStream *pStream, IMFAsyncCallback *pCB, IUnknown *pState)
|
|
{
|
|
AutoLock lock(m_critSec);
|
|
|
|
if (pStream == NULL || pCB == NULL)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
if (m_state != STATE_INVALID)
|
|
{
|
|
return MF_E_INVALIDREQUEST;
|
|
}
|
|
|
|
HRESULT hr = S_OK;
|
|
DWORD dwCaps = 0;
|
|
|
|
// Cache the byte-stream pointer.
|
|
m_pByteStream = pStream;
|
|
m_pByteStream->AddRef();
|
|
|
|
// Validate the capabilities of the byte stream.
|
|
// The byte stream must be readable and seekable.
|
|
CHECK_HR(hr = pStream->GetCapabilities(&dwCaps));
|
|
|
|
if ((dwCaps & MFBYTESTREAM_IS_SEEKABLE) == 0)
|
|
{
|
|
CHECK_HR(hr = MF_E_BYTESTREAM_NOT_SEEKABLE);
|
|
}
|
|
if ((dwCaps & MFBYTESTREAM_IS_READABLE) == 0)
|
|
{
|
|
CHECK_HR(hr = E_FAIL);
|
|
}
|
|
|
|
// Reserve space in the read buffer.
|
|
CHECK_HR(hr = m_ReadBuffer.Initalize(INITIAL_BUFFER_SIZE));
|
|
|
|
// Create the MPEG-1 parser.
|
|
m_pParser = new Parser();
|
|
if (m_pParser == NULL)
|
|
{
|
|
CHECK_HR(hr = E_OUTOFMEMORY);
|
|
}
|
|
|
|
// Create an async result object. We'll use it later to invoke the callback.
|
|
CHECK_HR(hr = MFCreateAsyncResult(NULL, pCB, pState, &m_pBeginOpenResult));
|
|
|
|
// Start reading data from the stream.
|
|
CHECK_HR(hr = RequestData(READ_SIZE));
|
|
|
|
// At this point, we now guarantee to invoke the callback.
|
|
m_state = STATE_OPENING;
|
|
|
|
done:
|
|
return hr;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// EndOpen
|
|
// Completes the BeginOpen operation.
|
|
// Called by the byte-stream handler when it creates the source.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT MPEG1Source::EndOpen(IMFAsyncResult *pResult)
|
|
{
|
|
AutoLock lock(m_critSec);
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
hr = pResult->GetStatus();
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
// The source is not designed to recover after failing to open.
|
|
// Switch to shut-down state.
|
|
Shutdown();
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// OnByteStreamRead
|
|
// Called when an asynchronous read completes.
|
|
//
|
|
// Read requests are issued in the RequestData() method.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT MPEG1Source::OnByteStreamRead(IMFAsyncResult *pResult)
|
|
{
|
|
AutoLock lock(m_critSec);
|
|
|
|
HRESULT hr = S_OK;
|
|
DWORD cbRead = 0;
|
|
|
|
IUnknown *pState = NULL;
|
|
|
|
if (m_state == STATE_SHUTDOWN)
|
|
{
|
|
// If we are shut down, then we've already released the
|
|
// byte stream. Nothing to do.
|
|
return S_OK;
|
|
}
|
|
|
|
// Get the state object. This is either NULL or the most
|
|
// recent OP_REQUEST_DATA operation.
|
|
(void)pResult->GetState(&pState);
|
|
|
|
// Complete the read opertation.
|
|
CHECK_HR(hr = m_pByteStream->EndRead(pResult, &cbRead));
|
|
|
|
// If the source stops and restarts in rapid succession, there is
|
|
// a chance this is a "stale" read request, initiated before the
|
|
// stop/restart.
|
|
|
|
// To ensure that we don't deliver stale data, we store the
|
|
// OP_REQUEST_DATA operation as a state object in pResult, and compare
|
|
// this against the current value of m_cRestartCounter.
|
|
|
|
// If they don't match, we discard the data.
|
|
|
|
// NOTE: During BeginOpen, pState is NULL
|
|
|
|
if ((pState == NULL) || ( ((SourceOp*)pState)->Data().ulVal == m_cRestartCounter) )
|
|
{
|
|
// This data is OK to parse.
|
|
|
|
if (cbRead == 0)
|
|
{
|
|
// There is no more data in the stream. Signal end-of-stream.
|
|
CHECK_HR(hr = EndOfMPEGStream());
|
|
}
|
|
else
|
|
{
|
|
// Update the end-position of the read buffer.
|
|
CHECK_HR(hr = m_ReadBuffer.MoveEnd(cbRead));
|
|
|
|
// Parse the new data.
|
|
CHECK_HR(hr = ParseData());
|
|
}
|
|
}
|
|
|
|
done:
|
|
if (FAILED(hr))
|
|
{
|
|
StreamingError(hr);
|
|
}
|
|
SAFE_RELEASE(pState);
|
|
return hr;
|
|
}
|
|
|
|
|
|
|
|
/* Private methods */
|
|
|
|
MPEG1Source::MPEG1Source(HRESULT& hr) :
|
|
OpQueue(m_critSec),
|
|
m_pEventQueue(NULL),
|
|
m_pPresentationDescriptor(NULL),
|
|
m_pBeginOpenResult(NULL),
|
|
m_pParser(NULL),
|
|
m_pByteStream(NULL),
|
|
m_pHeader(NULL),
|
|
m_state(STATE_INVALID),
|
|
m_pCurrentOp(NULL),
|
|
m_pSampleRequest(NULL),
|
|
m_cRestartCounter(0),
|
|
m_OnByteStreamRead(this, &MPEG1Source::OnByteStreamRead)
|
|
{
|
|
// Create the media event queue.
|
|
hr = MFCreateEventQueue(&m_pEventQueue);
|
|
}
|
|
|
|
MPEG1Source::~MPEG1Source()
|
|
{
|
|
if (m_state != STATE_SHUTDOWN)
|
|
{
|
|
Shutdown();
|
|
}
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// CompleteOpen
|
|
//
|
|
// Completes the asynchronous BeginOpen operation.
|
|
//
|
|
// hrStatus: Status of the BeginOpen operation.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT MPEG1Source::CompleteOpen(HRESULT hrStatus)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
if (m_pBeginOpenResult)
|
|
{
|
|
CHECK_HR(hr = m_pBeginOpenResult->SetStatus(hrStatus));
|
|
CHECK_HR(hr = MFInvokeCallback(m_pBeginOpenResult));
|
|
}
|
|
|
|
done:
|
|
SAFE_RELEASE(m_pBeginOpenResult);
|
|
return hr;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// IsInitialized:
|
|
// Returns S_OK if the source is correctly initialized with an
|
|
// MPEG-1 byte stream. Otherwise, returns MF_E_NOT_INITIALIZED.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT MPEG1Source::IsInitialized() const
|
|
{
|
|
if (m_state == STATE_OPENING || m_state == STATE_INVALID)
|
|
{
|
|
return MF_E_NOT_INITIALIZED;
|
|
}
|
|
else
|
|
{
|
|
return S_OK;
|
|
}
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// IsStreamTypeSupported:
|
|
// Returns TRUE if the source supports the specified MPEG-1 stream
|
|
// type.
|
|
//-------------------------------------------------------------------
|
|
|
|
BOOL MPEG1Source::IsStreamTypeSupported(StreamType type) const
|
|
{
|
|
// We support audio and video streams.
|
|
return (type == StreamType_Video || type == StreamType_Audio);
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// IsStreamActive:
|
|
// Returns TRUE if the source should deliver a payload, whose type
|
|
// is indicated by the specified packet header.
|
|
//
|
|
// Note: This method does not test the started/paused/stopped state
|
|
// of the source.
|
|
//-------------------------------------------------------------------
|
|
|
|
BOOL MPEG1Source::IsStreamActive(const MPEG1PacketHeader& packetHdr)
|
|
{
|
|
if (m_state == STATE_OPENING)
|
|
{
|
|
// The source is still opening.
|
|
// Deliver payloads for every supported stream type.
|
|
return IsStreamTypeSupported(packetHdr.type);
|
|
}
|
|
else
|
|
{
|
|
// The source is already opened. Check if the stream is active.
|
|
MPEG1Stream *pStream = GetStream(packetHdr.stream_id);
|
|
|
|
if (pStream == NULL)
|
|
{
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
return pStream->IsActive();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// InitPresentationDescriptor
|
|
// Create the source's presentation descriptor, if possible.
|
|
//
|
|
// During the BeginOpen operation, the source reads packets looking
|
|
// for headers for each stream. This enables the source to create the
|
|
// presentation descriptor, which describes the stream formats.
|
|
//
|
|
// This method tests whether the source has seen enough packets
|
|
// to create the PD. If so, it invokes the callback to complete
|
|
// the BeginOpen operation.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT MPEG1Source::InitPresentationDescriptor()
|
|
{
|
|
HRESULT hr = S_OK;
|
|
DWORD cStreams = 0;
|
|
|
|
assert(m_pPresentationDescriptor == NULL);
|
|
assert(m_state == STATE_OPENING);
|
|
|
|
if (m_pHeader == NULL)
|
|
{
|
|
return E_FAIL;
|
|
}
|
|
|
|
// Calculate how many streams we should have.
|
|
for (DWORD i = 0; i < m_pHeader->cStreams; i++)
|
|
{
|
|
if (IsStreamTypeSupported(m_pHeader->streams[i].type))
|
|
{
|
|
cStreams++;
|
|
}
|
|
}
|
|
|
|
// How many streams do we actually have?
|
|
if (cStreams > m_streams.GetCount())
|
|
{
|
|
// Not enough streams. Keep reading data until we have seen a packet for each stream.
|
|
return S_OK;
|
|
}
|
|
|
|
assert(cStreams == m_streams.GetCount()); // We should never create a stream we don't support.
|
|
|
|
// We're ready to create the presentation descriptor.
|
|
|
|
// Create an array of IMFStreamDescriptor pointers.
|
|
IMFStreamDescriptor **ppSD = new IMFStreamDescriptor*[cStreams];
|
|
if (ppSD == NULL)
|
|
{
|
|
CHECK_HR(hr = E_OUTOFMEMORY);
|
|
}
|
|
ZeroMemory(ppSD, cStreams * sizeof(IMFStreamDescriptor*));
|
|
|
|
// Fill the array by getting the stream descriptors from the MPEG1Stream objects.
|
|
// (We have already initialized these.)
|
|
for (DWORD i = 0; i < cStreams; i++)
|
|
{
|
|
CHECK_HR(hr = m_streams[i]->GetStreamDescriptor(&ppSD[i]));
|
|
}
|
|
|
|
// Create the presentation descriptor.
|
|
CHECK_HR(hr = MFCreatePresentationDescriptor(cStreams, ppSD, &m_pPresentationDescriptor));
|
|
|
|
// Select the first video stream (if any).
|
|
// NOTE: We don't select audio, because this sample does not include an audio decoder.
|
|
for (DWORD i = 0; i < cStreams; i++)
|
|
{
|
|
GUID majorType = GUID_NULL;
|
|
CHECK_HR(hr = GetStreamMajorType(ppSD[i], &majorType));
|
|
|
|
if (majorType == MFMediaType_Video)
|
|
{
|
|
CHECK_HR(hr = m_pPresentationDescriptor->SelectStream(i));
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Switch state from "opening" to "stopped."
|
|
m_state = STATE_STOPPED;
|
|
|
|
// Invoke the async callback to complete the BeginOpen operation.
|
|
CHECK_HR(hr = CompleteOpen(S_OK));
|
|
|
|
done:
|
|
if (ppSD)
|
|
{
|
|
for (DWORD i = 0; i < cStreams; i++)
|
|
{
|
|
SAFE_RELEASE(ppSD[i]);
|
|
}
|
|
delete [] ppSD;
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// QueueAsyncOperation
|
|
// Queue an asynchronous operation.
|
|
//
|
|
// OpType: Type of operation to queue.
|
|
//
|
|
// Note: If the SourceOp object requires additional information, call
|
|
// OpQueue<SourceOp>::QueueOperation, which takes a SourceOp pointer.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT MPEG1Source::QueueAsyncOperation(SourceOp::Operation OpType)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
SourceOp *pOp = NULL;
|
|
|
|
CHECK_HR(hr = SourceOp::CreateOp(OpType, &pOp));
|
|
CHECK_HR(hr = QueueOperation(pOp));
|
|
|
|
done:
|
|
SAFE_RELEASE(pOp);
|
|
return hr;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// BeginAsyncOp
|
|
//
|
|
// Starts an asynchronous operation. Called by the source at the
|
|
// begining of any asynchronous operation.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT MPEG1Source::BeginAsyncOp(SourceOp *pOp)
|
|
{
|
|
// At this point, the current operation should be NULL (the
|
|
// previous operation is NULL) and the new operation (pOp)
|
|
// must not be NULL.
|
|
|
|
if (pOp == NULL || m_pCurrentOp != NULL)
|
|
{
|
|
assert(FALSE);
|
|
return E_FAIL;
|
|
}
|
|
|
|
// Store the new operation as the current operation.
|
|
|
|
m_pCurrentOp = pOp;
|
|
m_pCurrentOp->AddRef();
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// CompleteAsyncOp
|
|
//
|
|
// Completes an asynchronous operation. Called by the source at the
|
|
// end of any asynchronous operation.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT MPEG1Source::CompleteAsyncOp(SourceOp *pOp)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
// At this point, the current operation (m_pCurrentOp)
|
|
// must match the operation that is ending (pOp).
|
|
|
|
if (pOp == NULL || m_pCurrentOp == NULL)
|
|
{
|
|
assert(FALSE);
|
|
return E_FAIL;
|
|
}
|
|
|
|
if (m_pCurrentOp != pOp)
|
|
{
|
|
assert(FALSE);
|
|
return E_FAIL;
|
|
}
|
|
|
|
// Release the current operation.
|
|
SAFE_RELEASE(m_pCurrentOp);
|
|
|
|
// Process the next operation on the queue.
|
|
CHECK_HR(hr = ProcessQueue());
|
|
|
|
done:
|
|
return hr;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// DispatchOperation
|
|
//
|
|
// Performs the asynchronous operation indicated by pOp.
|
|
//
|
|
// NOTE:
|
|
// This method implements the pure-virtual OpQueue::DispatchOperation
|
|
// method. It is always called from a work-queue thread.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT MPEG1Source::DispatchOperation(SourceOp *pOp)
|
|
{
|
|
AutoLock lock(m_critSec);
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
if (m_state == STATE_SHUTDOWN)
|
|
{
|
|
return S_OK; // Already shut down, ignore the request.
|
|
}
|
|
|
|
switch (pOp->Op())
|
|
{
|
|
|
|
// IMFMediaSource methods:
|
|
|
|
case SourceOp::OP_START:
|
|
hr = DoStart((StartOp*)pOp);
|
|
break;
|
|
|
|
case SourceOp::OP_STOP:
|
|
hr = DoStop(pOp);
|
|
break;
|
|
|
|
case SourceOp::OP_PAUSE:
|
|
hr = DoPause(pOp);
|
|
break;
|
|
|
|
// Operations requested by the streams:
|
|
|
|
case SourceOp::OP_REQUEST_DATA:
|
|
hr = OnStreamRequestSample(pOp);
|
|
break;
|
|
|
|
case SourceOp::OP_END_OF_STREAM:
|
|
hr = OnEndOfStream(pOp);
|
|
break;
|
|
|
|
default:
|
|
hr = E_UNEXPECTED;
|
|
}
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
StreamingError(hr);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// ValidateOperation
|
|
//
|
|
// Checks whether the source can perform the operation indicated
|
|
// by pOp at this time.
|
|
//
|
|
// If the source cannot perform the operation now, the method
|
|
// returns MF_E_NOTACCEPTING.
|
|
//
|
|
// NOTE:
|
|
// Implements the pure-virtual OpQueue::ValidateOperation method.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT MPEG1Source::ValidateOperation(SourceOp *pOp)
|
|
{
|
|
if (m_pCurrentOp != NULL)
|
|
{
|
|
return MF_E_NOTACCEPTING;
|
|
}
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// DoStart
|
|
// Perform an async start operation (IMFMediaSource::Start)
|
|
//
|
|
// pOp: Contains the start parameters.
|
|
//
|
|
// Note: This sample currently does not implement seeking, and the
|
|
// Start() method fails if the caller requests a seek.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT MPEG1Source::DoStart(StartOp *pOp)
|
|
{
|
|
TRACE((L"DoStart\n"));
|
|
|
|
assert(pOp->Op() == SourceOp::OP_START);
|
|
|
|
IMFPresentationDescriptor *pPD = NULL;
|
|
IMFMediaEvent *pEvent = NULL;
|
|
|
|
HRESULT hr = S_OK;
|
|
LONGLONG llStartOffset = 0;
|
|
BOOL bRestartFromCurrentPosition = FALSE;
|
|
BOOL bSentEvents = FALSE;
|
|
|
|
CHECK_HR(hr = BeginAsyncOp(pOp));
|
|
|
|
// Get the presentation descriptor from the SourceOp object.
|
|
// This is the PD that the caller passed into the Start() method.
|
|
// The PD has already been validated.
|
|
CHECK_HR(hr = pOp->GetPresentationDescriptor(&pPD));
|
|
|
|
// Because this sample does not support seeking, the start
|
|
// position must be 0 (from stopped) or "current position."
|
|
|
|
// If the sample supported seeking, we would need to get the
|
|
// start position from the PROPVARIANT data contained in pOp.
|
|
|
|
// Select/deselect streams, based on what the caller set in the PD.
|
|
CHECK_HR(hr = SelectStreams(pPD, pOp->Data()));
|
|
|
|
m_state = STATE_STARTED;
|
|
|
|
// Queue the "started" event. The event data is the start position.
|
|
CHECK_HR(hr = m_pEventQueue->QueueEventParamVar(MESourceStarted, GUID_NULL, S_OK, &pOp->Data()));
|
|
|
|
done:
|
|
if (FAILED(hr))
|
|
{
|
|
// Failure. Send the MESourceStarted or MESourceSeeked event with the error code.
|
|
|
|
// Note: It's possible that QueueEvent itself failed, in which case it is likely
|
|
// to fail again. But there is no good way to recover in that case.
|
|
|
|
(void)m_pEventQueue->QueueEventParamVar(MESourceStarted, GUID_NULL, hr, NULL);
|
|
}
|
|
|
|
CompleteAsyncOp(pOp);
|
|
|
|
SAFE_RELEASE(pEvent);
|
|
SAFE_RELEASE(pPD);
|
|
return hr;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// DoStop
|
|
// Perform an async stop operation (IMFMediaSource::Stop)
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT MPEG1Source::DoStop(SourceOp *pOp)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
QWORD qwCurrentPosition = 0;
|
|
|
|
CHECK_HR(hr = BeginAsyncOp(pOp));
|
|
|
|
// Stop the active streams.
|
|
for (DWORD i = 0; i < m_streams.GetCount(); i++)
|
|
{
|
|
if (m_streams[i]->IsActive())
|
|
{
|
|
CHECK_HR(hr = m_streams[i]->Stop());
|
|
}
|
|
}
|
|
|
|
// Seek to the start of the file. If we restart after stopping,
|
|
// we will start from the beginning of the file again.
|
|
CHECK_HR(hr = m_pByteStream->Seek(
|
|
msoBegin,
|
|
0,
|
|
MFBYTESTREAM_SEEK_FLAG_CANCEL_PENDING_IO,
|
|
&qwCurrentPosition
|
|
));
|
|
|
|
// Increment the counter that tracks "stale" read requests.
|
|
++m_cRestartCounter; // This counter is allowed to overflow.
|
|
|
|
SAFE_RELEASE(m_pSampleRequest);
|
|
|
|
m_state = STATE_STOPPED;
|
|
|
|
done:
|
|
|
|
// Send the "stopped" event. This might include a failure code.
|
|
(void)m_pEventQueue->QueueEventParamVar(MESourceStopped, GUID_NULL, hr, NULL);
|
|
|
|
CompleteAsyncOp(pOp);
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// DoPause
|
|
// Perform an async pause operation (IMFMediaSource::Pause)
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT MPEG1Source::DoPause(SourceOp *pOp)
|
|
{
|
|
TRACE((L"DoPause\n"));
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
CHECK_HR(hr = BeginAsyncOp(pOp));
|
|
|
|
// Pause is only allowed while running.
|
|
if (m_state != STATE_STARTED)
|
|
{
|
|
CHECK_HR(hr = MF_E_INVALID_STATE_TRANSITION);
|
|
}
|
|
|
|
// Pause the active streams.
|
|
for (DWORD i = 0; i < m_streams.GetCount(); i++)
|
|
{
|
|
if (m_streams[i]->IsActive())
|
|
{
|
|
CHECK_HR(hr = m_streams[i]->Pause());
|
|
}
|
|
}
|
|
|
|
m_state = STATE_PAUSED;
|
|
|
|
done:
|
|
|
|
// Send the "paused" event. This might include a failure code.
|
|
(void)m_pEventQueue->QueueEventParamVar(MESourcePaused, GUID_NULL, hr, NULL);
|
|
|
|
CompleteAsyncOp(pOp);
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// StreamRequestSample
|
|
// Called by streams when they need more data.
|
|
//
|
|
// Note: This is an async operation. The stream requests more data
|
|
// by queueing an OP_REQUEST_DATA operation.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT MPEG1Source::OnStreamRequestSample(SourceOp *pOp)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
CHECK_HR(hr = BeginAsyncOp(pOp));
|
|
|
|
// Ignore this request if we are already handling an earlier request.
|
|
// (In that case m_pSampleRequest will be non-NULL.)
|
|
|
|
if (m_pSampleRequest == NULL)
|
|
{
|
|
// Add the request counter as data to the operation.
|
|
// This counter tracks whether a read request becomes "stale."
|
|
|
|
PROPVARIANT var;
|
|
var.vt = VT_UI4;
|
|
var.ulVal = m_cRestartCounter;
|
|
|
|
CHECK_HR(hr = pOp->SetData(var));
|
|
|
|
// Store this while the request is pending.
|
|
m_pSampleRequest = pOp;
|
|
m_pSampleRequest->AddRef();
|
|
|
|
// Try to parse data - this will invoke a read request if needed.
|
|
ParseData();
|
|
}
|
|
|
|
CompleteAsyncOp(pOp);
|
|
|
|
done:
|
|
return hr;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// OnEndOfStream
|
|
// Called by each stream when it sends the last sample in the stream.
|
|
//
|
|
// Note: When the media source reaches the end of the MPEG-1 stream,
|
|
// it calls EndOfStream on each stream object. The streams might have
|
|
// data still in their queues. As each stream empties its queue, it
|
|
// notifies the source through an async OP_END_OF_STREAM operation.
|
|
//
|
|
// When every stream notifies the source, the source can send the
|
|
// "end-of-presentation" event.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT MPEG1Source::OnEndOfStream(SourceOp *pOp)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
CHECK_HR(hr = BeginAsyncOp(pOp));
|
|
|
|
// Decrement the count of end-of-stream notifications.
|
|
--m_cPendingEOS;
|
|
if (m_cPendingEOS == 0)
|
|
{
|
|
// No more streams. Send the end-of-presentation event.
|
|
hr = m_pEventQueue->QueueEventParamVar(MEEndOfPresentation, GUID_NULL, S_OK, NULL);
|
|
}
|
|
|
|
CompleteAsyncOp(pOp);
|
|
|
|
done:
|
|
return hr;
|
|
}
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// SelectStreams
|
|
// Called during START operations to select and deselect streams.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT MPEG1Source::SelectStreams(
|
|
IMFPresentationDescriptor *pPD, // Presentation descriptor.
|
|
const PROPVARIANT varStart // New start position.
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
BOOL fSelected = FALSE;
|
|
BOOL fWasSelected = FALSE;
|
|
DWORD stream_id = 0;
|
|
|
|
IMFStreamDescriptor *pSD = NULL;
|
|
|
|
MPEG1Stream *pStream = NULL; // Not add-ref'd
|
|
|
|
// Reset the pending EOS count.
|
|
m_cPendingEOS = 0;
|
|
|
|
// Loop throught the stream descriptors to find which streams are active.
|
|
for (DWORD i = 0; i < m_streams.GetCount(); i++)
|
|
{
|
|
CHECK_HR(hr = pPD->GetStreamDescriptorByIndex(i, &fSelected, &pSD));
|
|
|
|
CHECK_HR(hr = pSD->GetStreamIdentifier(&stream_id));
|
|
|
|
pStream = GetStream((BYTE)stream_id);
|
|
if (pStream == NULL)
|
|
{
|
|
CHECK_HR(hr = E_INVALIDARG);
|
|
}
|
|
|
|
// Was the stream active already?
|
|
fWasSelected = pStream->IsActive();
|
|
|
|
// Activate or deactivate the stream.
|
|
CHECK_HR(hr = pStream->Activate(fSelected));
|
|
|
|
if (fSelected)
|
|
{
|
|
m_cPendingEOS++;
|
|
|
|
if (fWasSelected)
|
|
{
|
|
// This stream was previously selected. Queue the "updated stream" event.
|
|
CHECK_HR(hr = m_pEventQueue->QueueEventParamUnk(MEUpdatedStream, GUID_NULL, hr, pStream));
|
|
}
|
|
else
|
|
{
|
|
// This stream was not previously selected. Queue the "new stream" event.
|
|
CHECK_HR(hr = m_pEventQueue->QueueEventParamUnk(MENewStream, GUID_NULL, hr, pStream));
|
|
}
|
|
|
|
// Start the stream. The stream will send the appropriate stream event.
|
|
CHECK_HR(hr = pStream->Start(varStart));
|
|
}
|
|
|
|
SAFE_RELEASE(pSD);
|
|
}
|
|
|
|
done:
|
|
SAFE_RELEASE(pSD);
|
|
return hr;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// RequestData
|
|
// Request the next batch of data.
|
|
//
|
|
// cbRequest: Amount of data to read, in bytes.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT MPEG1Source::RequestData(DWORD cbRequest)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
// Reserve a sufficient read buffer.
|
|
CHECK_HR(hr = m_ReadBuffer.Reserve(cbRequest));
|
|
|
|
// Submit the async read request.
|
|
// When it completes, our OnByteStreamRead method will be invoked.
|
|
|
|
CHECK_HR(hr = m_pByteStream->BeginRead(
|
|
m_ReadBuffer.DataPtr() + m_ReadBuffer.DataSize(),
|
|
cbRequest,
|
|
&m_OnByteStreamRead,
|
|
m_pSampleRequest
|
|
));
|
|
|
|
done:
|
|
return hr;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// ParseData
|
|
// Parses the next batch of data.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT MPEG1Source::ParseData()
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
DWORD cbNextRequest = 0;
|
|
BOOL bNeedMoreData = FALSE;
|
|
|
|
// Keep processing data until
|
|
// (a) All streams have enough samples, or
|
|
// (b) The parser needs more data in the buffer.
|
|
|
|
while ( StreamsNeedData() )
|
|
{
|
|
DWORD cbAte = 0; // How much data we consumed from the read buffer.
|
|
|
|
// Check if we got the first system header.
|
|
if (m_pHeader == NULL && m_pParser->HasSystemHeader())
|
|
{
|
|
CHECK_HR(hr = m_pParser->GetSystemHeader(&m_pHeader));
|
|
|
|
// Allocate room for the streams.
|
|
CHECK_HR(hr = m_streams.Allocate(m_pHeader->cStreams));
|
|
}
|
|
|
|
if (m_pParser->IsEndOfStream())
|
|
{
|
|
// The parser reached the end of the MPEG-1 stream. Notify the streams.
|
|
CHECK_HR(hr = EndOfMPEGStream());
|
|
}
|
|
else if (m_pParser->HasPacket())
|
|
{
|
|
// The parser reached the start of a new packet.
|
|
CHECK_HR(hr = ReadPayload(&cbAte, &cbNextRequest));
|
|
}
|
|
else
|
|
{
|
|
// Parse more data.
|
|
CHECK_HR(hr = m_pParser->ParseBytes(m_ReadBuffer.DataPtr(), m_ReadBuffer.DataSize(), &cbAte));
|
|
|
|
// Parser::ParseBytes() can return S_FALSE, meaning "Need more data"
|
|
if (hr == S_FALSE)
|
|
{
|
|
bNeedMoreData = TRUE;
|
|
}
|
|
}
|
|
|
|
// Advance the start of the read buffer by the amount consumed.
|
|
CHECK_HR(hr = m_ReadBuffer.MoveStart(cbAte));
|
|
|
|
// If we need more data, start an async read operation.
|
|
if (bNeedMoreData)
|
|
{
|
|
CHECK_HR(hr = RequestData( max(READ_SIZE, cbNextRequest) ));
|
|
|
|
// Break from the loop because we need to wait for the async read to complete.
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Flag our state. If a stream requests more data while we are waiting for an async
|
|
// read to complete, we can ignore the stream's request, because the request will be
|
|
// dispatched as soon as we get more data.
|
|
if (!bNeedMoreData)
|
|
{
|
|
SAFE_RELEASE(m_pSampleRequest);
|
|
}
|
|
|
|
done:
|
|
return hr;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// ReadPayload
|
|
// Read the next MPEG-1 payload.
|
|
//
|
|
// When this method is called:
|
|
// - The read position has reached the beginning of a payload.
|
|
// - We have the packet header, but not necessarily the entire payload.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT MPEG1Source::ReadPayload(DWORD *pcbAte, DWORD *pcbNextRequest)
|
|
{
|
|
assert(m_pParser != NULL);
|
|
assert(m_pParser->HasPacket());
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
DWORD cbPayloadRead = 0;
|
|
DWORD cbPayloadUnread = 0;
|
|
|
|
// At this point, the read buffer might be larger or smaller than the payload.
|
|
// Calculate which portion of the payload has been read.
|
|
if (m_pParser->PayloadSize() > m_ReadBuffer.DataSize())
|
|
{
|
|
cbPayloadUnread = m_pParser->PayloadSize() - m_ReadBuffer.DataSize();
|
|
}
|
|
|
|
cbPayloadRead = m_pParser->PayloadSize() - cbPayloadUnread;
|
|
|
|
// Do we need to deliver this payload?
|
|
if ( !IsStreamActive(m_pParser->PacketHeader()) )
|
|
{
|
|
QWORD qwCurrentPosition = 0;
|
|
|
|
// Skip this payload. Seek past the unread portion of the payload.
|
|
CHECK_HR(hr = m_pByteStream->Seek(
|
|
msoCurrent,
|
|
cbPayloadUnread,
|
|
MFBYTESTREAM_SEEK_FLAG_CANCEL_PENDING_IO,
|
|
&qwCurrentPosition
|
|
));
|
|
|
|
// Advance the data buffer to the end of payload, or the portion
|
|
// that has been read.
|
|
|
|
*pcbAte = cbPayloadRead;
|
|
|
|
// Tell the parser that we are done with this packet.
|
|
m_pParser->ClearPacket();
|
|
|
|
}
|
|
else if (cbPayloadUnread > 0)
|
|
{
|
|
// Some portion of this payload has not been read. Schedule a read.
|
|
*pcbNextRequest = cbPayloadUnread;
|
|
|
|
*pcbAte = 0;
|
|
|
|
hr = S_FALSE; // Need more data.
|
|
}
|
|
else
|
|
{
|
|
// The entire payload is in the data buffer. Deliver the packet.
|
|
CHECK_HR(hr = DeliverPayload());
|
|
|
|
*pcbAte = cbPayloadRead;
|
|
|
|
// Tell the parser that we are done with this packet.
|
|
m_pParser->ClearPacket();
|
|
}
|
|
|
|
done:
|
|
return hr;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// EndOfMPEGStream:
|
|
// Called when the parser reaches the end of the MPEG1 stream.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT MPEG1Source::EndOfMPEGStream()
|
|
{
|
|
// Notify the streams. The streams might have pending samples.
|
|
// When each stream delivers the last sample, it will send the
|
|
// end-of-stream event to the pipeline and then notify the
|
|
// source.
|
|
|
|
// When every stream is done, the source sends the end-of-
|
|
// presentation event.
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
for (DWORD i = 0; i < m_streams.GetCount(); i++)
|
|
{
|
|
if (m_streams[i]->IsActive())
|
|
{
|
|
CHECK_HR(hr = m_streams[i]->EndOfStream());
|
|
}
|
|
}
|
|
|
|
done:
|
|
return hr;
|
|
}
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// StreamsNeedData:
|
|
// Returns TRUE if any streams need more data.
|
|
//-------------------------------------------------------------------
|
|
|
|
BOOL MPEG1Source::StreamsNeedData() const
|
|
{
|
|
BOOL bNeedData = FALSE;
|
|
|
|
switch (m_state)
|
|
{
|
|
case STATE_OPENING:
|
|
// While opening, we always need data (until we get enough
|
|
// to complete the open operation).
|
|
return TRUE;
|
|
|
|
case STATE_SHUTDOWN:
|
|
// While shut down, we never need data.
|
|
return FALSE;
|
|
|
|
default:
|
|
// If none of the above, ask the streams.
|
|
for (DWORD i = 0; i < m_streams.GetCount(); i++)
|
|
{
|
|
if (m_streams[i]->NeedsData())
|
|
{
|
|
bNeedData = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
return bNeedData;
|
|
}
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// DeliverPayload:
|
|
// Delivers an MPEG-1 payload.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT MPEG1Source::DeliverPayload()
|
|
{
|
|
// When this method is called, the read buffer contains a complete
|
|
// payload, and the payload belongs to a stream whose type we support.
|
|
|
|
assert(m_pParser->HasPacket());
|
|
|
|
HRESULT hr = S_OK;
|
|
MPEG1PacketHeader packetHdr;
|
|
MPEG1Stream *pStream = NULL; // not AddRef'd
|
|
|
|
IMFMediaBuffer *pBuffer = NULL;
|
|
IMFSample *pSample = NULL;
|
|
BYTE *pData = NULL; // Pointer to the IMFMediaBuffer data.
|
|
|
|
packetHdr = m_pParser->PacketHeader();
|
|
|
|
if (packetHdr.cbPayload > m_ReadBuffer.DataSize())
|
|
{
|
|
assert(FALSE);
|
|
CHECK_HR(hr = E_UNEXPECTED);
|
|
}
|
|
|
|
// If we are still opening the file, then we might need to create this stream.
|
|
if (m_state == STATE_OPENING)
|
|
{
|
|
CHECK_HR(hr = CreateStream(packetHdr));
|
|
}
|
|
|
|
pStream = GetStream(packetHdr.stream_id);
|
|
assert(pStream != NULL);
|
|
|
|
|
|
// Create a media buffer for the payload.
|
|
CHECK_HR(hr = MFCreateMemoryBuffer(packetHdr.cbPayload, &pBuffer));
|
|
|
|
CHECK_HR(hr = pBuffer->Lock(&pData, NULL, NULL));
|
|
|
|
CopyMemory(pData, m_ReadBuffer.DataPtr(), packetHdr.cbPayload);
|
|
|
|
CHECK_HR(hr = pBuffer->Unlock());
|
|
|
|
CHECK_HR(hr = pBuffer->SetCurrentLength(packetHdr.cbPayload));
|
|
|
|
// Create a sample to hold the buffer.
|
|
CHECK_HR(hr = MFCreateSample(&pSample));
|
|
CHECK_HR(hr = pSample->AddBuffer(pBuffer));
|
|
|
|
// Time stamp the sample.
|
|
if (packetHdr.bHasPTS)
|
|
{
|
|
LONGLONG hnsStart = packetHdr.PTS * 10000 / 90;
|
|
|
|
CHECK_HR(hr = pSample->SetSampleTime(hnsStart));
|
|
}
|
|
|
|
|
|
// Deliver the payload to the stream.
|
|
CHECK_HR(hr = pStream->DeliverPayload(pSample));
|
|
|
|
// If the open operation is still pending, check if we're done.
|
|
if (m_state == STATE_OPENING)
|
|
{
|
|
CHECK_HR(hr = InitPresentationDescriptor());
|
|
}
|
|
|
|
done:
|
|
SAFE_RELEASE(pBuffer);
|
|
SAFE_RELEASE(pSample);
|
|
return hr;
|
|
}
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// CreateStream:
|
|
// Creates a media stream, based on a packet header.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT MPEG1Source::CreateStream(const MPEG1PacketHeader& packetHdr)
|
|
{
|
|
// We validate the stream type before calling this method.
|
|
assert(IsStreamTypeSupported(packetHdr.type));
|
|
|
|
|
|
// First see if the stream already exists.
|
|
if ( GetStream(packetHdr.stream_id) != NULL )
|
|
{
|
|
// The stream already exists. Nothing to do.
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT hr = S_OK;
|
|
DWORD cbAte = 0;
|
|
DWORD cbHeader = 0;
|
|
BYTE *pPayload = NULL;
|
|
DWORD cStreams = m_streams.GetCount();
|
|
|
|
IMFMediaType *pType = NULL;
|
|
IMFStreamDescriptor *pSD = NULL;
|
|
MPEG1Stream *pStream = NULL;
|
|
IMFMediaTypeHandler *pHandler = NULL;
|
|
|
|
MPEG1VideoSeqHeader videoSeqHdr;
|
|
MPEG1AudioFrameHeader audioFrameHeader;
|
|
|
|
// Get the header size and a pointer to the start of the payload.
|
|
cbHeader = packetHdr.cbPacketSize - packetHdr.cbPayload;
|
|
pPayload = m_ReadBuffer.DataPtr();
|
|
|
|
// Create a media type, based on the packet type (audio/video)
|
|
switch (packetHdr.type)
|
|
{
|
|
case StreamType_Video:
|
|
// Video: Read the sequence header and use it to create a media type.
|
|
CHECK_HR(hr = ReadVideoSequenceHeader(pPayload, packetHdr.cbPayload, videoSeqHdr, &cbAte));
|
|
CHECK_HR(hr = CreateVideoMediaType(videoSeqHdr, &pType));
|
|
break;
|
|
|
|
case StreamType_Audio:
|
|
// Audio: Read the frame header and use it to create a media type.
|
|
CHECK_HR(hr = ReadAudioFrameHeader(pPayload, packetHdr.cbPayload, audioFrameHeader, &cbAte));
|
|
CHECK_HR(hr = CreateAudioMediaType(audioFrameHeader, &pType));
|
|
break;
|
|
|
|
default:
|
|
assert(false); // If this case occurs, then IsStreamTypeSupported() is wrong.
|
|
CHECK_HR(hr = E_UNEXPECTED);
|
|
}
|
|
|
|
assert(pType != NULL);
|
|
|
|
// Create the stream descriptor from the media type.
|
|
CHECK_HR(hr = MFCreateStreamDescriptor(packetHdr.stream_id, 1, &pType, &pSD));
|
|
|
|
// Set the default media type on the stream handler.
|
|
CHECK_HR(hr = pSD->GetMediaTypeHandler(&pHandler));
|
|
CHECK_HR(hr = pHandler->SetCurrentMediaType(pType));
|
|
|
|
// Create the new stream.
|
|
pStream = new MPEG1Stream(this, pSD, hr);
|
|
if (pStream == NULL)
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
CHECK_HR(hr);
|
|
|
|
// Resize the stream array.
|
|
CHECK_HR(hr = m_streams.SetSize( cStreams + 1 ));
|
|
|
|
// Add the stream to the array.
|
|
m_streams[cStreams] = pStream;
|
|
m_streams[cStreams]->AddRef();
|
|
|
|
// Add an entry to the map (id/index).
|
|
// This enables us to look up a stream by ID.
|
|
CHECK_HR(hr = m_streamMap.Insert(packetHdr.stream_id, cStreams));
|
|
|
|
done:
|
|
SAFE_RELEASE(pSD);
|
|
SAFE_RELEASE(pStream);
|
|
return hr;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// ValidatePresentationDescriptor:
|
|
// Validates the presentation descriptor that the caller specifies
|
|
// in IMFMediaSource::Start().
|
|
//
|
|
// Note: This method performs a basic sanity check on the PD. It is
|
|
// not intended to be a thorough validation.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT MPEG1Source::ValidatePresentationDescriptor(IMFPresentationDescriptor *pPD)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
BOOL fSelected = FALSE;
|
|
DWORD cStreams = 0;
|
|
|
|
IMFStreamDescriptor *pSD = NULL;
|
|
|
|
if (m_pHeader == NULL)
|
|
{
|
|
return E_UNEXPECTED;
|
|
}
|
|
|
|
// The caller's PD must have the same number of streams as ours.
|
|
CHECK_HR(hr = pPD->GetStreamDescriptorCount(&cStreams));
|
|
|
|
if (cStreams != m_pHeader->cStreams)
|
|
{
|
|
CHECK_HR(hr = E_INVALIDARG);
|
|
}
|
|
|
|
// The caller must select at least one stream.
|
|
for (DWORD i = 0; i < cStreams; i++)
|
|
{
|
|
CHECK_HR(hr = pPD->GetStreamDescriptorByIndex(i, &fSelected, &pSD));
|
|
if (fSelected)
|
|
{
|
|
break;
|
|
}
|
|
SAFE_RELEASE(pSD);
|
|
}
|
|
|
|
if (!fSelected)
|
|
{
|
|
CHECK_HR(hr = E_INVALIDARG);
|
|
}
|
|
|
|
done:
|
|
SAFE_RELEASE(pSD);
|
|
return hr;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// StreamingError:
|
|
// Handles an error that occurs duing an asynchronous operation.
|
|
//
|
|
// hr: Error code of the operation that failed.
|
|
//-------------------------------------------------------------------
|
|
|
|
void MPEG1Source::StreamingError(HRESULT hr)
|
|
{
|
|
if (m_state == STATE_OPENING)
|
|
{
|
|
// An error happened during BeginOpen.
|
|
// Invoke the callback with the status code.
|
|
|
|
CompleteOpen(hr);
|
|
}
|
|
else if (m_state != STATE_SHUTDOWN)
|
|
{
|
|
// An error occurred during streaming. Send the MEError event
|
|
// to notify the pipeline.
|
|
|
|
QueueEvent(MEError, GUID_NULL, hr, NULL);
|
|
}
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// MPEG1Source:
|
|
// Returns a stream by ID.
|
|
//
|
|
// This method can return NULL if the source did not create a
|
|
// stream for this ID. In particular, this can happen if:
|
|
//
|
|
// 1) The stream type is not supported. See IsStreamTypeSupported().
|
|
// 2) The source is still opening.
|
|
//
|
|
// Note: This method does not AddRef the stream object. The source
|
|
// uses this method to access the streams. If the source hands out
|
|
// a stream pointer (e.g. in the MENewStream event), the source
|
|
// must AddRef the stream object.
|
|
//-------------------------------------------------------------------
|
|
|
|
MPEG1Stream* MPEG1Source::GetStream(BYTE stream_id)
|
|
{
|
|
MPEG1Stream *pStream = NULL;
|
|
|
|
DWORD index = 0;
|
|
HRESULT hr = m_streamMap.Find(stream_id, &index);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
assert (m_streams.GetCount() > index);
|
|
pStream = m_streams[index];
|
|
}
|
|
return pStream;
|
|
}
|
|
|
|
|
|
/* SourceOp class */
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// CreateOp
|
|
// Static method to create a SourceOp instance.
|
|
//
|
|
// op: Specifies the async operation.
|
|
// ppOp: Receives a pointer to the SourceOp object.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT SourceOp::CreateOp(SourceOp::Operation op, SourceOp **ppOp)
|
|
{
|
|
if (ppOp == NULL)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
SourceOp *pOp = new SourceOp(op);
|
|
if (pOp == NULL)
|
|
{
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
*ppOp = pOp;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// CreateStartOp:
|
|
// Static method to create a SourceOp instance for the Start()
|
|
// operation.
|
|
//
|
|
// pPD: Presentation descriptor from the caller.
|
|
// ppOp: Receives a pointer to the SourceOp object.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT SourceOp::CreateStartOp(IMFPresentationDescriptor *pPD, SourceOp **ppOp)
|
|
{
|
|
if (ppOp == NULL)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
SourceOp *pOp = new StartOp(pPD);
|
|
if (pOp == NULL)
|
|
{
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
*ppOp = pOp;
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
HRESULT SourceOp::QueryInterface(REFIID riid, void** ppv)
|
|
{
|
|
static const QITAB qit[] =
|
|
{
|
|
QITABENT(SourceOp, IUnknown),
|
|
{ 0 }
|
|
};
|
|
return QISearch(this, qit, riid, ppv);
|
|
}
|
|
|
|
SourceOp::SourceOp(Operation op) : m_op(op)
|
|
{
|
|
PropVariantInit(&m_data);
|
|
}
|
|
|
|
SourceOp::~SourceOp()
|
|
{
|
|
PropVariantClear(&m_data);
|
|
}
|
|
|
|
HRESULT SourceOp::SetData(const PROPVARIANT& var)
|
|
{
|
|
return PropVariantCopy(&m_data, &var);
|
|
}
|
|
|
|
|
|
StartOp::StartOp(IMFPresentationDescriptor *pPD) : SourceOp(SourceOp::OP_START), m_pPD(pPD)
|
|
{
|
|
if (m_pPD)
|
|
{
|
|
m_pPD->AddRef();
|
|
}
|
|
}
|
|
|
|
StartOp::~StartOp()
|
|
{
|
|
SAFE_RELEASE(m_pPD);
|
|
}
|
|
|
|
|
|
HRESULT StartOp::GetPresentationDescriptor(IMFPresentationDescriptor **ppPD)
|
|
{
|
|
if (ppPD == NULL)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
if (m_pPD == NULL)
|
|
{
|
|
return MF_E_INVALIDREQUEST;
|
|
}
|
|
*ppPD = m_pPD;
|
|
(*ppPD)->AddRef();
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
/* Static functions */
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// CreateVideoMediaType:
|
|
// Create a media type from an MPEG-1 video sequence header.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CreateVideoMediaType(const MPEG1VideoSeqHeader& videoSeqHdr, IMFMediaType **ppType)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
// Create the helper object for generating video media types.
|
|
MPEGVideoType video_type;
|
|
|
|
CHECK_HR(hr = video_type.CreateEmptyType());
|
|
|
|
// Subtype = MPEG-1 payload
|
|
CHECK_HR(hr = video_type.SetSubType(MEDIASUBTYPE_MPEG1Payload));
|
|
|
|
// Format details.
|
|
CHECK_HR(hr = video_type.SetFrameDimensions(videoSeqHdr.width, videoSeqHdr.height));
|
|
CHECK_HR(hr = video_type.SetFrameRate(videoSeqHdr.frameRate));
|
|
CHECK_HR(hr = video_type.SetPixelAspectRatio(videoSeqHdr.pixelAspectRatio));
|
|
CHECK_HR(hr = video_type.SetAvgerageBitRate(videoSeqHdr.bitRate));
|
|
CHECK_HR(hr = video_type.SetInterlaceMode(MFVideoInterlace_Progressive));
|
|
|
|
// Copy the sequence header.
|
|
CHECK_HR(hr = video_type.SetMpegSeqHeader(videoSeqHdr.header, videoSeqHdr.cbHeader));
|
|
|
|
// Get the media type from the helper.
|
|
*ppType =video_type.Detach();
|
|
|
|
done:
|
|
return hr;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// CreateAudioMediaType:
|
|
// Create a media type from an MPEG-1 audio frame header.
|
|
//
|
|
// Note: This function fills in an MPEG1WAVEFORMAT structure and then
|
|
// converts the structure to a Media Foundation media type
|
|
// (IMFMediaType). This is somewhat roundabout but it guarantees
|
|
// that the type can be converted back to an MPEG1WAVEFORMAT by the
|
|
// decoder if need be.
|
|
//
|
|
// The WAVEFORMATEX portion of the MPEG1WAVEFORMAT structure is
|
|
// converted into attributes on the IMFMediaType object. The rest of
|
|
// the struct is stored in the MF_MT_USER_DATA attribute.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CreateAudioMediaType(const MPEG1AudioFrameHeader& audioHeader, IMFMediaType **ppType)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
IMFMediaType *pType = NULL;
|
|
|
|
MPEG1WAVEFORMAT format;
|
|
ZeroMemory(&format, sizeof(format));
|
|
|
|
format.wfx.wFormatTag = WAVE_FORMAT_MPEG;
|
|
format.wfx.nChannels = audioHeader.nChannels;
|
|
format.wfx.nSamplesPerSec = audioHeader.dwSamplesPerSec;
|
|
if (audioHeader.dwBitRate > 0)
|
|
{
|
|
format.wfx.nAvgBytesPerSec = (audioHeader.dwBitRate * 1000) / 8;
|
|
}
|
|
format.wfx.nBlockAlign = audioHeader.nBlockAlign;
|
|
format.wfx.wBitsPerSample = 0; // Not used.
|
|
format.wfx.cbSize = sizeof(MPEG1WAVEFORMAT) - sizeof(WAVEFORMATEX);
|
|
|
|
// MPEG-1 audio layer.
|
|
switch (audioHeader.layer)
|
|
{
|
|
case MPEG1_Audio_Layer1:
|
|
format.fwHeadLayer = ACM_MPEG_LAYER1;
|
|
break;
|
|
|
|
case MPEG1_Audio_Layer2:
|
|
format.fwHeadLayer = ACM_MPEG_LAYER2;
|
|
break;
|
|
|
|
case MPEG1_Audio_Layer3:
|
|
format.fwHeadLayer = ACM_MPEG_LAYER3;
|
|
break;
|
|
};
|
|
|
|
format.dwHeadBitrate = audioHeader.dwBitRate * 1000;
|
|
|
|
// Mode
|
|
switch (audioHeader.mode)
|
|
{
|
|
case MPEG1_Audio_Stereo:
|
|
format.fwHeadMode = ACM_MPEG_STEREO;
|
|
break;
|
|
|
|
case MPEG1_Audio_JointStereo:
|
|
format.fwHeadMode = ACM_MPEG_JOINTSTEREO;
|
|
break;
|
|
|
|
case MPEG1_Audio_DualChannel:
|
|
format.fwHeadMode = ACM_MPEG_DUALCHANNEL;
|
|
break;
|
|
|
|
case MPEG1_Audio_SingleChannel:
|
|
format.fwHeadMode = ACM_MPEG_SINGLECHANNEL;
|
|
break;
|
|
};
|
|
|
|
if (audioHeader.mode == ACM_MPEG_JOINTSTEREO)
|
|
{
|
|
// Convert the 'mode_extension' field to the correct MPEG1WAVEFORMAT value.
|
|
if (audioHeader.modeExtension <= 0x03)
|
|
{
|
|
format.fwHeadModeExt = 0x01 << audioHeader.modeExtension;
|
|
}
|
|
}
|
|
|
|
// Convert the 'emphasis' field to the correct MPEG1WAVEFORMAT value.
|
|
if (audioHeader.emphasis <= 0x03)
|
|
{
|
|
format.wHeadEmphasis = audioHeader.emphasis + 1;
|
|
}
|
|
|
|
// The flags translate directly.
|
|
format.fwHeadFlags = audioHeader.wFlags;
|
|
// Add the "MPEG-1" flag, although it's somewhat redundant.
|
|
format.fwHeadFlags |= ACM_MPEG_ID_MPEG1;
|
|
|
|
// Use the structure to initialize the Media Foundation media type.
|
|
CHECK_HR(hr = MFCreateMediaType(&pType));
|
|
CHECK_HR(hr = MFInitMediaTypeFromWaveFormatEx(pType, (const WAVEFORMATEX*)&format, sizeof(format)));
|
|
|
|
*ppType = pType;
|
|
(*ppType)->AddRef();
|
|
|
|
done:
|
|
SAFE_RELEASE(pType);
|
|
return hr;
|
|
}
|
|
|
|
BOOL SampleRequestMatch(SourceOp *pOp1, SourceOp *pOp2)
|
|
{
|
|
if ((pOp1 == NULL) && (pOp2 == NULL))
|
|
{
|
|
return TRUE;
|
|
}
|
|
else if ((pOp1 == NULL) || (pOp2 == NULL))
|
|
{
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
return (pOp1->Data().ulVal == pOp2->Data().ulVal);
|
|
}
|
|
}
|
|
|
|
|
|
#pragma warning( pop ) |