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

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 )