1683 lines
45 KiB
C++
1683 lines
45 KiB
C++
//////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// AudioDelayMFT.cpp
|
|
// Implements an audio effect as a Media Foundation transform.
|
|
//
|
|
// 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 "AudioDelayMFT.h"
|
|
#include "AudioDelayUuids.h"
|
|
|
|
#define CHECK_HR(hr) IF_FAILED_GOTO(hr, done)
|
|
|
|
|
|
HRESULT ValidatePCMAudioType(IMFMediaType *pmt);
|
|
|
|
//-------------------------------------------------------------------
|
|
// Name: CreateInstance (static method)
|
|
// Creates a new instance of the MFT.
|
|
//
|
|
// This method is called by the class factory.
|
|
//
|
|
// pUnkOuter: Aggregating IUnknown.
|
|
// iid: IID of the interface to query for.
|
|
// ppv: Receives the interface pointer.
|
|
//-------------------------------------------------------------------
|
|
HRESULT CDelayMFT::CreateInstance(IUnknown *pUnkOuter, REFIID iid, void **ppv)
|
|
{
|
|
if (ppv == NULL)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
// This object does not support aggregation.
|
|
if (pUnkOuter != NULL)
|
|
{
|
|
return CLASS_E_NOAGGREGATION;
|
|
}
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
CDelayMFT *pMFT = new CDelayMFT();
|
|
|
|
if (pMFT == NULL)
|
|
{
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
hr = pMFT->QueryInterface(iid, ppv);
|
|
|
|
SAFE_RELEASE(pMFT);
|
|
|
|
return hr;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Constructor
|
|
//-------------------------------------------------------------------
|
|
|
|
CDelayMFT::CDelayMFT() :
|
|
m_nRefCount(1),
|
|
m_pBuffer(NULL),
|
|
m_pSample(NULL),
|
|
m_pMediaType(NULL),
|
|
m_pAttributes(NULL),
|
|
m_pbInputData(NULL),
|
|
m_cbInputLength(0),
|
|
m_rtTimestamp(0),
|
|
m_bValidTime(NULL),
|
|
m_bInputTypeSet(FALSE),
|
|
m_bOutputTypeSet(FALSE),
|
|
m_pbDelayBuffer(NULL),
|
|
m_cbDelayBuffer(0),
|
|
m_pbDelayPtr(NULL),
|
|
m_dwDelay(DEFAULT_DELAY),
|
|
m_bDraining(FALSE),
|
|
m_cbTailSamples(0)
|
|
{
|
|
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Destructor
|
|
//-------------------------------------------------------------------
|
|
|
|
CDelayMFT::~CDelayMFT()
|
|
{
|
|
FreeStreamingResources(FALSE);
|
|
SAFE_RELEASE(m_pMediaType);
|
|
SAFE_RELEASE(m_pAttributes);
|
|
}
|
|
|
|
|
|
// IUnknown methods.
|
|
|
|
ULONG CDelayMFT::AddRef()
|
|
{
|
|
return InterlockedIncrement(&m_nRefCount);
|
|
}
|
|
|
|
ULONG CDelayMFT::Release()
|
|
{
|
|
assert(m_nRefCount >= 0);
|
|
ULONG uCount = InterlockedDecrement(&m_nRefCount);
|
|
if (uCount == 0)
|
|
{
|
|
delete this;
|
|
}
|
|
// Return the temporary variable, not the member
|
|
// variable, for thread safety.
|
|
return uCount;
|
|
}
|
|
|
|
HRESULT CDelayMFT::QueryInterface(REFIID riid, void **ppv)
|
|
{
|
|
if (NULL == ppv)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
else if (riid == __uuidof(IUnknown))
|
|
{
|
|
*ppv = static_cast<IUnknown*>(this);
|
|
}
|
|
else if (riid == __uuidof(IMFTransform))
|
|
{
|
|
*ppv = static_cast<IMFTransform*>(this);
|
|
}
|
|
else
|
|
{
|
|
*ppv = NULL;
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
AddRef();
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// IMFTransform methods
|
|
//-------------------------------------------------------------------
|
|
|
|
//-------------------------------------------------------------------
|
|
// Name: GetStreamLimits
|
|
// Returns the minimum and maximum number of streams.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CDelayMFT::GetStreamLimits(
|
|
DWORD *pdwInputMinimum,
|
|
DWORD *pdwInputMaximum,
|
|
DWORD *pdwOutputMinimum,
|
|
DWORD *pdwOutputMaximum
|
|
)
|
|
{
|
|
|
|
if ((pdwInputMinimum == NULL) ||
|
|
(pdwInputMaximum == NULL) ||
|
|
(pdwOutputMinimum == NULL) ||
|
|
(pdwOutputMaximum == NULL))
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
|
|
// This MFT has a fixed number of streams.
|
|
*pdwInputMinimum = 1;
|
|
*pdwInputMaximum = 1;
|
|
*pdwOutputMinimum = 1;
|
|
*pdwOutputMaximum = 1;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Name: GetStreamCount
|
|
// Returns the current number of streams.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CDelayMFT::GetStreamCount(DWORD *pcInputStreams, DWORD *pcOutputStreams)
|
|
{
|
|
if ((pcInputStreams == NULL) || (pcOutputStreams == NULL))
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
// This MFT has a fixed number of streams.
|
|
*pcInputStreams = 1;
|
|
*pcOutputStreams = 1;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// Name: GetStreamIDs
|
|
// Returns the stream identifiers.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CDelayMFT::GetStreamIDs(
|
|
DWORD dwInputIDArraySize,
|
|
DWORD *pdwInputIDs,
|
|
DWORD dwOutputIDArraySize,
|
|
DWORD *pdwOutputIDs
|
|
)
|
|
{
|
|
// This MFT has a fixed number of streams and the stream IDs match
|
|
// the zero-based index of the streams. Therefore, it is not required
|
|
// to implement this method.
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Name: GetInputStreamInfo
|
|
// Returns information about the input stream.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CDelayMFT::GetInputStreamInfo(DWORD dwInputStreamID, MFT_INPUT_STREAM_INFO *pStreamInfo)
|
|
{
|
|
AutoLock lock(m_critSec);
|
|
|
|
if (!IsValidInputStream(dwInputStreamID))
|
|
{
|
|
return MF_E_INVALIDSTREAMNUMBER;
|
|
}
|
|
|
|
|
|
pStreamInfo->hnsMaxLatency = 0;
|
|
|
|
// Flags
|
|
pStreamInfo->dwFlags =
|
|
MFT_INPUT_STREAM_WHOLE_SAMPLES | // The MFT must get complete audio frames.
|
|
MFT_INPUT_STREAM_PROCESSES_IN_PLACE | // The MFT can do in-place processing.
|
|
MFT_INPUT_STREAM_FIXED_SAMPLE_SIZE; // Samples (ie, audio frames) are fixed size.
|
|
|
|
pStreamInfo->cbSize = 0; // If no media type is set, use zero.
|
|
pStreamInfo->cbMaxLookahead = 0;
|
|
pStreamInfo->cbAlignment = 0;
|
|
|
|
// When the media type is set, return the minimum buffer size = one audio frame.
|
|
if (IsInputTypeSet())
|
|
{
|
|
pStreamInfo->cbSize = BlockAlign();
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Name: GetOutputStreamInfo
|
|
// Returns information about the output stream.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CDelayMFT::GetOutputStreamInfo(DWORD dwOutputStreamID, MFT_OUTPUT_STREAM_INFO *pStreamInfo)
|
|
{
|
|
AutoLock lock(m_critSec);
|
|
|
|
if (!IsValidOutputStream(dwOutputStreamID))
|
|
{
|
|
return MF_E_INVALIDSTREAMNUMBER;
|
|
}
|
|
|
|
// Flags
|
|
pStreamInfo->dwFlags =
|
|
MFT_OUTPUT_STREAM_WHOLE_SAMPLES | // Output buffers contain complete audio frames.
|
|
MFT_OUTPUT_STREAM_CAN_PROVIDE_SAMPLES | // The MFT can allocate output buffers, or use caller-allocated buffers.
|
|
MFT_OUTPUT_STREAM_FIXED_SAMPLE_SIZE; // Samples (ie, audio frames) are fixed size.
|
|
|
|
pStreamInfo->cbSize = 0; // If no media type is set, use zero.
|
|
pStreamInfo->cbAlignment = 0;
|
|
|
|
// When the media type is set, return the minimum buffer size = one audio frame.
|
|
if (m_bOutputTypeSet)
|
|
{
|
|
pStreamInfo->cbSize = BlockAlign();
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Name: GetAttributes
|
|
// Returns an attribute store for MFT-wide attributes.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CDelayMFT::GetAttributes(IMFAttributes** ppAttributes)
|
|
{
|
|
if (ppAttributes == NULL)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
// Lazily create the attribute store.
|
|
CHECK_HR(hr = CreateAttributeStore());
|
|
|
|
*ppAttributes = m_pAttributes;
|
|
(*ppAttributes)->AddRef();
|
|
|
|
done:
|
|
return hr;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Name: GetInputStreamAttributes
|
|
// Returns an attribute store for input stream attributes.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CDelayMFT::GetInputStreamAttributes(DWORD dwInputStreamID, IMFAttributes **ppAttributes)
|
|
{
|
|
// This MFT does not support any stream-level attributes.
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Name: GetOutputStreamAttributes
|
|
// Returns an attribute store for output stream attributes.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CDelayMFT::GetOutputStreamAttributes(DWORD dwOutputStreamID, IMFAttributes **ppAttributes)
|
|
{
|
|
// This MFT does not support any stream-level attributes.
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Name: DeleteInputStream
|
|
// Deletes an input stream.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CDelayMFT::DeleteInputStream(DWORD dwStreamID)
|
|
{
|
|
// This MFT has a fixed number of streams.
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Name: AddInputStreams
|
|
// Adds one or more input streams.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CDelayMFT::AddInputStreams(DWORD cStreams, DWORD *adwStreamIDs)
|
|
{
|
|
// This MFT has a fixed number of streams.
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// Name: GetInputAvailableType
|
|
// Returns a preferred media type for the input stream.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CDelayMFT::GetInputAvailableType(
|
|
DWORD dwInputStreamID, // Input stream ID.
|
|
DWORD dwTypeIndex, // 0-based index into the list of preferred types.
|
|
IMFMediaType **ppType // Receives a pointer to the media type.
|
|
)
|
|
{
|
|
AutoLock lock(m_critSec);
|
|
|
|
if (ppType == NULL)
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
if (!IsValidInputStream(dwInputStreamID))
|
|
{
|
|
return MF_E_INVALIDSTREAMNUMBER;
|
|
}
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
// If the output type is set, return that type as our preferred input type.
|
|
if (IsOutputTypeSet())
|
|
{
|
|
// Only one preferred type in this case.
|
|
if (dwTypeIndex > 0)
|
|
{
|
|
return MF_E_NO_MORE_TYPES;
|
|
}
|
|
|
|
// ASSERT(m_pMediaType != NULL);
|
|
|
|
*ppType = m_pMediaType;
|
|
(*ppType)->AddRef();
|
|
}
|
|
else
|
|
{
|
|
// The output type is not set. Create a partial media type.
|
|
hr = GetProposedType(dwTypeIndex, ppType);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// Name: GetOutputAvailableType
|
|
// Returns a preferred media type for the output stream.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CDelayMFT::GetOutputAvailableType(
|
|
DWORD dwOutputStreamID, // Output stream ID.
|
|
DWORD dwTypeIndex, // 0-based index into the list of preferred types.
|
|
IMFMediaType **ppType // Receives a pointer to the media type.
|
|
)
|
|
{
|
|
AutoLock lock(m_critSec);
|
|
|
|
if (ppType == NULL)
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
if (!IsValidOutputStream(dwOutputStreamID))
|
|
{
|
|
return MF_E_INVALIDSTREAMNUMBER;
|
|
}
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
// If the input type is set, return that type as our preferred output type.
|
|
if (IsInputTypeSet())
|
|
{
|
|
// Only one preferred type in this case.
|
|
if (dwTypeIndex > 0)
|
|
{
|
|
return MF_E_NO_MORE_TYPES;
|
|
}
|
|
|
|
*ppType = m_pMediaType;
|
|
(*ppType)->AddRef();
|
|
}
|
|
else
|
|
{
|
|
// The input type is not set. Create a partial media type.
|
|
hr = GetProposedType(dwTypeIndex, ppType);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// Name: SetInputType
|
|
// Sets the input type.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CDelayMFT::SetInputType(
|
|
DWORD dwInputStreamID,
|
|
IMFMediaType *pType, // Can be NULL to clear the input type.
|
|
DWORD dwFlags
|
|
)
|
|
{
|
|
AutoLock lock(m_critSec);
|
|
|
|
if (!IsValidInputStream(dwInputStreamID))
|
|
{
|
|
return MF_E_INVALIDSTREAMNUMBER;
|
|
}
|
|
|
|
// Validate flags.
|
|
if (dwFlags & ~MFT_SET_TYPE_TEST_ONLY)
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
if ((dwFlags & MFT_SET_TYPE_TEST_ONLY) && (pType == NULL))
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
// If we have output, the client cannot change the type now.
|
|
if (HasPendingOutput())
|
|
{
|
|
return MF_E_TRANSFORM_CANNOT_CHANGE_MEDIATYPE_WHILE_PROCESSING;
|
|
}
|
|
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
// Does the caller want us to set the type, or just test it?
|
|
BOOL bReallySet = ((dwFlags & MFT_SET_TYPE_TEST_ONLY) == 0);
|
|
|
|
// Validate the type.
|
|
if (pType)
|
|
{
|
|
CHECK_HR(hr = OnCheckInputType(pType));
|
|
}
|
|
|
|
// The type is OK.
|
|
// Set or clear the type, unless the caller was just testing.
|
|
if (bReallySet)
|
|
{
|
|
CHECK_HR(hr = OnSetMediaType(pType, InputStream));
|
|
}
|
|
|
|
done:
|
|
return hr;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Name: SetOutputType
|
|
// Sets the output type.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CDelayMFT::SetOutputType(
|
|
DWORD dwOutputStreamID,
|
|
IMFMediaType *pType, // Can be NULL to clear the output type.
|
|
DWORD dwFlags
|
|
)
|
|
{
|
|
AutoLock lock(m_critSec);
|
|
|
|
if (!IsValidOutputStream(dwOutputStreamID))
|
|
{
|
|
return MF_E_INVALIDSTREAMNUMBER;
|
|
}
|
|
|
|
// Validate flags.
|
|
if (dwFlags & ~MFT_SET_TYPE_TEST_ONLY)
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
if ((dwFlags & MFT_SET_TYPE_TEST_ONLY) && (pType == NULL))
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
// If we have output, the client cannot change the type now.
|
|
if (HasPendingOutput())
|
|
{
|
|
return MF_E_TRANSFORM_CANNOT_CHANGE_MEDIATYPE_WHILE_PROCESSING;
|
|
}
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
// Does the caller want us to set the type, or just test it?
|
|
BOOL bReallySet = ((dwFlags & MFT_SET_TYPE_TEST_ONLY) == 0);
|
|
|
|
// Validate the type.
|
|
if (pType)
|
|
{
|
|
CHECK_HR(hr = OnCheckOutputType(pType));
|
|
}
|
|
|
|
// The type is OK.
|
|
// Set or clear the type, unless the caller was just testing.
|
|
if (bReallySet)
|
|
{
|
|
CHECK_HR(hr = OnSetMediaType(pType, OutputStream));
|
|
}
|
|
|
|
done:
|
|
return hr;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// Name: GetInputCurrentType
|
|
// Returns the current input type.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CDelayMFT::GetInputCurrentType(DWORD dwInputStreamID, IMFMediaType **ppType)
|
|
{
|
|
AutoLock lock(m_critSec);
|
|
|
|
if (ppType == NULL)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
if (!IsValidInputStream(dwInputStreamID))
|
|
{
|
|
return MF_E_INVALIDSTREAMNUMBER;
|
|
}
|
|
|
|
if (!IsInputTypeSet())
|
|
{
|
|
return MF_E_TRANSFORM_TYPE_NOT_SET;
|
|
}
|
|
|
|
*ppType = m_pMediaType;
|
|
(*ppType)->AddRef();
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Name: GetOutputCurrentType
|
|
// Returns the current output type.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CDelayMFT::GetOutputCurrentType(DWORD dwOutputStreamID, IMFMediaType **ppType)
|
|
{
|
|
AutoLock lock(m_critSec);
|
|
|
|
if (ppType == NULL)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
if (!IsValidOutputStream(dwOutputStreamID))
|
|
{
|
|
return MF_E_INVALIDSTREAMNUMBER;
|
|
}
|
|
|
|
if (!m_bOutputTypeSet)
|
|
{
|
|
return MF_E_TRANSFORM_TYPE_NOT_SET;
|
|
}
|
|
|
|
*ppType = m_pMediaType;
|
|
(*ppType)->AddRef();
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// Name: GetInputStatus
|
|
// Queries whether the MFT can accept more input at this time.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CDelayMFT::GetInputStatus(DWORD dwInputStreamID, DWORD *pdwFlags)
|
|
{
|
|
AutoLock lock(m_critSec);
|
|
|
|
if (pdwFlags == NULL)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
if (!IsValidInputStream(dwInputStreamID))
|
|
{
|
|
return MF_E_INVALIDSTREAMNUMBER;
|
|
}
|
|
|
|
// If we have output data to give to the client, then we don't accept
|
|
// new input until the client calls ProcessOutput or Flush.
|
|
if (HasPendingOutput())
|
|
{
|
|
*pdwFlags = 0;
|
|
}
|
|
else
|
|
{
|
|
*pdwFlags = MFT_INPUT_STATUS_ACCEPT_DATA;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// Name: GetOutputStatus
|
|
// Queries whether the MFT can produce output at this time.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CDelayMFT::GetOutputStatus(DWORD *pdwFlags)
|
|
{
|
|
AutoLock lock(m_critSec);
|
|
|
|
if (pdwFlags == NULL)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
if (HasPendingOutput())
|
|
{
|
|
*pdwFlags = MFT_OUTPUT_STATUS_SAMPLE_READY;
|
|
}
|
|
else
|
|
{
|
|
*pdwFlags = 0;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// Name: SetOutputBounds
|
|
// Sets the range of timestamps the client needs for output.
|
|
//
|
|
// Implementation of this method is optional.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CDelayMFT::SetOutputBounds(LONGLONG hnsLowerBound, LONGLONG hnsUpperBound)
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// Name: ProcessEvent
|
|
// Sends a media event to the MFT.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CDelayMFT::ProcessEvent(DWORD dwInputStreamID, IMFMediaEvent *pEvent)
|
|
{
|
|
// This MFT does not respond to any events.
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// Name: ProcessMessage
|
|
// Sends a message to the MFT.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CDelayMFT::ProcessMessage(MFT_MESSAGE_TYPE eMessage, ULONG_PTR ulParam)
|
|
{
|
|
AutoLock lock(m_critSec);
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
switch (eMessage)
|
|
{
|
|
case MFT_MESSAGE_COMMAND_FLUSH:
|
|
FreeStreamingResources(TRUE);
|
|
break;
|
|
|
|
case MFT_MESSAGE_NOTIFY_BEGIN_STREAMING:
|
|
hr = AllocateStreamingResources();
|
|
break;
|
|
|
|
case MFT_MESSAGE_NOTIFY_END_STREAMING:
|
|
FreeStreamingResources(FALSE);
|
|
break;
|
|
|
|
case MFT_MESSAGE_COMMAND_DRAIN:
|
|
// Drain: Tells the MFT not to accept any more input until
|
|
// all of the pending output has been processed.
|
|
hr = OnDrain();
|
|
break;
|
|
|
|
case MFT_MESSAGE_SET_D3D_MANAGER:
|
|
// The pipeline should never send this message unless the MFT
|
|
// has the MF_SA_D3D_AWARE attribute set to TRUE. However, if we
|
|
// do get this message, it's invalid and we don't implement it.
|
|
hr = E_NOTIMPL;
|
|
break;
|
|
|
|
// The remaining messages do not require any action from this MFT.
|
|
case MFT_MESSAGE_NOTIFY_END_OF_STREAM:
|
|
case MFT_MESSAGE_NOTIFY_START_OF_STREAM:
|
|
break;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Name: ProcessInput
|
|
// Gives an input sample to the MFT.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CDelayMFT::ProcessInput(
|
|
DWORD dwInputStreamID,
|
|
IMFSample *pSample,
|
|
DWORD dwFlags
|
|
)
|
|
{
|
|
AutoLock lock(m_critSec);
|
|
|
|
if (pSample == NULL)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
if (!IsValidInputStream(dwInputStreamID))
|
|
{
|
|
return MF_E_INVALIDSTREAMNUMBER;
|
|
}
|
|
|
|
if (dwFlags != 0)
|
|
{
|
|
return E_INVALIDARG; // dwFlags is reserved and must be zero.
|
|
}
|
|
|
|
if (!IsInputTypeSet() || !IsOutputTypeSet())
|
|
{
|
|
// The client must set the input and output types before
|
|
// calling ProcessInput.
|
|
return MF_E_NOTACCEPTING;
|
|
}
|
|
|
|
if (HasPendingOutput())
|
|
{
|
|
// Not accepting input because there is still data to process.
|
|
return MF_E_NOTACCEPTING;
|
|
}
|
|
|
|
HRESULT hr = S_OK;
|
|
DWORD dwBufferCount = 0;
|
|
|
|
// Allocate resources, in case the client did not send the
|
|
// MFT_MESSAGE_NOTIFY_BEGIN_STREAMING message.
|
|
CHECK_HR(hr = AllocateStreamingResources());
|
|
|
|
// Get the input buffer(s) from the sample.
|
|
CHECK_HR(hr = pSample->GetBufferCount(&dwBufferCount));
|
|
|
|
// Convert to a single contiguous buffer.
|
|
// NOTE: This does not cause a copy unless there are multiple buffers
|
|
CHECK_HR(hr = pSample->ConvertToContiguousBuffer(&m_pBuffer));
|
|
|
|
// Cache the sample.
|
|
m_pSample = pSample;
|
|
m_pSample->AddRef();
|
|
|
|
// Save the time stamp, if we got one from the caller.
|
|
// (We don't care about the duration because it is implicit in the size
|
|
// of the audio data.)
|
|
|
|
LONGLONG hnsTime = 0;
|
|
|
|
// Ignore failure if the input sample does not have a time stamp. This is
|
|
// not an error condition. The client may not care about time stamps, and
|
|
// we don't need them.
|
|
if (SUCCEEDED(pSample->GetSampleTime(&hnsTime)))
|
|
{
|
|
m_bValidTime = TRUE;
|
|
m_rtTimestamp = hnsTime;
|
|
}
|
|
else
|
|
{
|
|
m_bValidTime = FALSE;
|
|
}
|
|
|
|
|
|
done:
|
|
return hr;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Name: ProcessOutput
|
|
// Generates output data.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CDelayMFT::ProcessOutput(
|
|
DWORD dwFlags,
|
|
DWORD cOutputBufferCount,
|
|
MFT_OUTPUT_DATA_BUFFER *pOutputSamples, // one per stream
|
|
DWORD *pdwStatus
|
|
)
|
|
{
|
|
|
|
AutoLock lock(m_critSec);
|
|
|
|
// Check input parameters...
|
|
|
|
// The only flag is MFT_PROCESS_OUTPUT_DISCARD_WHEN_NO_BUFFER, which applies
|
|
// if the MFT has "lazy" or "optional" streams. This MFT does not have any
|
|
// lazy or optional streams, so that flag is not valid.
|
|
if (dwFlags != 0)
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
if (pOutputSamples == NULL || pdwStatus == NULL)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
// The MFT has exactly one output stream.
|
|
if (cOutputBufferCount != 1)
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
// NOTE: To support in-place processing, we allow pOutputSamples[0].pSample to be NULL.
|
|
|
|
// Check if we are able to deliver any output at this time.
|
|
if (!HasPendingOutput())
|
|
{
|
|
return MF_E_TRANSFORM_NEED_MORE_INPUT;
|
|
}
|
|
|
|
// We can deliver output, so one or both of the following is true:
|
|
//
|
|
// 1. We have an input buffer.
|
|
// 2. We are draining the effect tail.
|
|
//
|
|
// If both are true, process the input buffer before the tail.
|
|
|
|
assert((m_pBuffer && m_pSample) || (m_bDraining && (m_cbTailSamples > 0)));
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
if (m_pBuffer)
|
|
{
|
|
// Process the input.
|
|
hr = InternalProcessOutput(pOutputSamples[0], pdwStatus);
|
|
}
|
|
else
|
|
{
|
|
// Output the effect tail.
|
|
hr = ProcessEffectTail(pOutputSamples[0], pdwStatus);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// Private methods
|
|
//-------------------------------------------------------------------
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// Name: InternalProcessOutput
|
|
// Does the work of IMFTransform::ProcessOutput, in the case where
|
|
// the MFT has input data.
|
|
//
|
|
// When the MFT is generating the effect tail, the ProcessEffectTail
|
|
// method is used instead.
|
|
//
|
|
// All input parameters are validated before this method is called.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CDelayMFT::InternalProcessOutput(MFT_OUTPUT_DATA_BUFFER& OutputSample, DWORD *pdwStatus)
|
|
{
|
|
IMFMediaBuffer *pOutputBuffer = NULL;
|
|
|
|
HRESULT hr = S_OK;
|
|
BYTE *pbOutputData = NULL; // Pointer to the memory in the output buffer.
|
|
DWORD cbOutputLength = 0; // Size of the output buffer.
|
|
DWORD cbBytesProcessed = 0; // How much data we processed.
|
|
BOOL bComplete = FALSE; // Are we done with the input buffer?
|
|
LONGLONG hnsDuration = 0; // Duration of the output sample.
|
|
UINT32 nBlockAlign = 0;
|
|
|
|
assert(m_pBuffer != NULL);
|
|
|
|
nBlockAlign = BlockAlign();
|
|
if (nBlockAlign == 0)
|
|
{
|
|
return E_UNEXPECTED;
|
|
}
|
|
|
|
// Lock the input buffer.
|
|
if (m_pbInputData == NULL)
|
|
{
|
|
CHECK_HR(hr = m_pBuffer->Lock(&m_pbInputData, NULL, &m_cbInputLength));
|
|
}
|
|
|
|
// If the client provided an output sample, get the output buffer.
|
|
if (OutputSample.pSample != NULL)
|
|
{
|
|
CHECK_HR(hr = OutputSample.pSample->GetBufferByIndex(0, &pOutputBuffer));
|
|
|
|
// Lock the output buffer.
|
|
CHECK_HR(hr = pOutputBuffer->Lock(&pbOutputData, &cbOutputLength, NULL));
|
|
}
|
|
else
|
|
{
|
|
// Client did not provide an output sample. Use the input buffer and transform the data in place.
|
|
pbOutputData = m_pbInputData;
|
|
cbOutputLength = m_cbInputLength;
|
|
|
|
// Return the input sample as the output sample.
|
|
OutputSample.pSample = m_pSample;
|
|
OutputSample.pSample->AddRef();
|
|
}
|
|
|
|
|
|
// Calculate how many audio samples we can process.
|
|
if (m_cbInputLength > cbOutputLength)
|
|
{
|
|
cbBytesProcessed = cbOutputLength;
|
|
}
|
|
else
|
|
{
|
|
cbBytesProcessed = m_cbInputLength;
|
|
bComplete = TRUE;
|
|
}
|
|
|
|
// Round to the next lowest multiple of nBlockAlign.
|
|
cbBytesProcessed -= (cbBytesProcessed % nBlockAlign);
|
|
|
|
// Process the data.
|
|
CHECK_HR(hr = ProcessAudio(pbOutputData, m_pbInputData, cbBytesProcessed / nBlockAlign));
|
|
|
|
// Update the output buffer/sample (if provided)
|
|
if (pOutputBuffer)
|
|
{
|
|
// Set the data length on the output buffer.
|
|
CHECK_HR(hr = pOutputBuffer->SetCurrentLength(cbBytesProcessed));
|
|
|
|
// Set the time stamp, if we have a valid time from the input sample.
|
|
if (m_bValidTime)
|
|
{
|
|
// Estimate how far along we are...
|
|
hnsDuration = (cbBytesProcessed / AvgBytesPerSec()) * UNITS;
|
|
|
|
// Set the time stamp and duration on the output sample.
|
|
CHECK_HR(hr = OutputSample.pSample->SetSampleTime(m_rtTimestamp));
|
|
CHECK_HR(hr = OutputSample.pSample->SetSampleDuration(hnsDuration));
|
|
|
|
m_rtTimestamp += hnsDuration;
|
|
}
|
|
}
|
|
|
|
// Set status flags.
|
|
OutputSample.dwStatus = 0;
|
|
*pdwStatus = 0;
|
|
|
|
if (bComplete)
|
|
{
|
|
// We are done with this input buffer. Release it.
|
|
CHECK_HR(hr = m_pBuffer->Unlock());
|
|
|
|
SAFE_RELEASE(m_pBuffer);
|
|
SAFE_RELEASE(m_pSample);
|
|
|
|
m_pbInputData = 0;
|
|
m_cbInputLength = 0;
|
|
}
|
|
else
|
|
{
|
|
// There is still data in the input buffer.
|
|
|
|
// Update the running count of bytes processed.
|
|
m_cbInputLength -= cbBytesProcessed;
|
|
m_pbInputData += cbBytesProcessed;
|
|
|
|
// Set the "incomplete" flag to notify the caller that we have more output to produce.
|
|
OutputSample.dwStatus |= MFT_OUTPUT_DATA_BUFFER_INCOMPLETE;
|
|
}
|
|
|
|
done:
|
|
// Unlock the output buffer.
|
|
if (pOutputBuffer && pbOutputData)
|
|
{
|
|
pOutputBuffer->Unlock();
|
|
}
|
|
|
|
SAFE_RELEASE(pOutputBuffer);
|
|
return hr;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// ProcessEffectTail
|
|
// Generates the "tail" of the audio effect. The tail is the portion
|
|
// of the delay effect that is heard after the input stream ends.
|
|
//
|
|
// To generate the tail, the client must drain the MFT by sending
|
|
// the MFT_MESSAGE_COMMAND_DRAIN message and then call ProcessOutput
|
|
// to get the tail samples.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CDelayMFT::ProcessEffectTail(MFT_OUTPUT_DATA_BUFFER& OutputSample, DWORD *pdwStatus)
|
|
{
|
|
IMFSample *pSample = NULL;
|
|
IMFMediaBuffer *pOutputBuffer = NULL;
|
|
|
|
HRESULT hr = S_OK;
|
|
BYTE *pbOutputData = NULL; // Pointer to the memory in the output buffer.
|
|
DWORD cbOutputLength = 0; // Size of the output buffer.
|
|
DWORD cbBytesProcessed = 0; // How much data we processed.
|
|
LONGLONG hnsDuration = 0; // Duration of the output sample.
|
|
UINT32 nBlockAlign = 0;
|
|
|
|
nBlockAlign = BlockAlign();
|
|
if (nBlockAlign == 0)
|
|
{
|
|
return E_UNEXPECTED;
|
|
}
|
|
|
|
// If the caller provided an output sample, get the output buffer.
|
|
if (OutputSample.pSample != NULL)
|
|
{
|
|
CHECK_HR(hr = OutputSample.pSample->GetBufferByIndex(0, &pOutputBuffer));
|
|
|
|
pSample = OutputSample.pSample;
|
|
pSample->AddRef();
|
|
}
|
|
else
|
|
{
|
|
// The caller did not provide an output sample. Allocate one.
|
|
CHECK_HR(hr = MFCreateMemoryBuffer(m_cbTailSamples, &pOutputBuffer));
|
|
|
|
CHECK_HR(hr = MFCreateSample(&pSample));
|
|
|
|
CHECK_HR(hr = pSample->AddBuffer(pOutputBuffer));
|
|
|
|
OutputSample.pSample = pSample;
|
|
OutputSample.pSample->AddRef();
|
|
}
|
|
|
|
// Lock the output buffer.
|
|
CHECK_HR(hr = pOutputBuffer->Lock(&pbOutputData, &cbOutputLength, NULL));
|
|
|
|
// Calculate how many audio samples we can process.
|
|
cbBytesProcessed = min(m_cbTailSamples, cbOutputLength);
|
|
|
|
// Round to the next lowest multiple of nBlockAlign.
|
|
cbBytesProcessed -= (cbBytesProcessed % nBlockAlign);
|
|
|
|
// Fill the output buffer with silence, because we are also using it as the input buffer.
|
|
FillBufferWithSilence(pbOutputData, cbBytesProcessed);
|
|
|
|
// Process the data.
|
|
CHECK_HR(hr = ProcessAudio(pbOutputData, pbOutputData, cbBytesProcessed / nBlockAlign));
|
|
|
|
// Set the data length on the output buffer.
|
|
CHECK_HR(hr = pOutputBuffer->SetCurrentLength(cbBytesProcessed));
|
|
|
|
// Set the time stamp, if we have a valid time from the last input sample.
|
|
if (m_bValidTime)
|
|
{
|
|
// Estimate how far along we are...
|
|
hnsDuration = (cbBytesProcessed / AvgBytesPerSec()) * UNITS;
|
|
|
|
// Set the time stamp and duration on the output sample.
|
|
CHECK_HR(hr = OutputSample.pSample->SetSampleTime(m_rtTimestamp));
|
|
CHECK_HR(hr = OutputSample.pSample->SetSampleDuration(hnsDuration));
|
|
|
|
m_rtTimestamp += hnsDuration;
|
|
}
|
|
|
|
// Set status flags.
|
|
OutputSample.dwStatus = 0;
|
|
*pdwStatus = 0;
|
|
|
|
// How many tail samples are left?
|
|
m_cbTailSamples -= cbBytesProcessed;
|
|
|
|
if (m_cbTailSamples >= nBlockAlign)
|
|
{
|
|
// Still some data left.
|
|
OutputSample.dwStatus |= MFT_OUTPUT_DATA_BUFFER_INCOMPLETE;
|
|
}
|
|
else
|
|
{
|
|
// Done.
|
|
m_cbTailSamples = 0;
|
|
m_bDraining = FALSE;
|
|
}
|
|
|
|
done:
|
|
if (pOutputBuffer && pbOutputData)
|
|
{
|
|
pOutputBuffer->Unlock();
|
|
}
|
|
|
|
SAFE_RELEASE(pOutputBuffer);
|
|
SAFE_RELEASE(pSample);
|
|
return hr;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// AllocateStreamingResources
|
|
// Allocates resources needed to process data.
|
|
//
|
|
// This method is called if the client sends the
|
|
// MFT_MESSAGE_NOTIFY_BEGIN_STREAMING message. It is also called from
|
|
// ProcessInput(), because the client is not required to send the
|
|
// MFT_MESSAGE_NOTIFY_BEGIN_STREAMING message.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CDelayMFT::AllocateStreamingResources()
|
|
{
|
|
if (m_pbDelayBuffer != NULL)
|
|
{
|
|
return S_OK; // Already allocated. Nothing to do.
|
|
}
|
|
|
|
// The client must set both media types before allocating streaming resources.
|
|
if (!IsInputTypeSet() || !IsOutputTypeSet())
|
|
{
|
|
return MF_E_TRANSFORM_TYPE_NOT_SET;
|
|
}
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
// Lazily create the attribute store, which holds the attribute for the delay length.
|
|
CHECK_HR(hr = CreateAttributeStore());
|
|
|
|
// Get the delay length.
|
|
m_dwDelay = MFGetAttributeUINT32(m_pAttributes, MF_AUDIODELAY_DELAY_LENGTH, DEFAULT_DELAY);
|
|
|
|
// A zero-length delay buffer will complicate things, so disallow zero.
|
|
// Use the default instead.
|
|
if (m_dwDelay == 0)
|
|
{
|
|
m_dwDelay = DEFAULT_DELAY;
|
|
}
|
|
|
|
// Make sure the delay buffer won't exceed MAXDWORD bytes.
|
|
m_dwDelay = min( m_dwDelay, MAXDWORD / (SamplesPerSec() * BlockAlign()) );
|
|
|
|
|
|
// Allocate the buffer that holds the delayed samples.
|
|
m_cbDelayBuffer = (m_dwDelay * SamplesPerSec() * BlockAlign()) / 1000;
|
|
m_pbDelayBuffer = (BYTE*)CoTaskMemAlloc(m_cbDelayBuffer);
|
|
|
|
if (m_pbDelayBuffer == NULL)
|
|
{
|
|
CHECK_HR(hr = E_OUTOFMEMORY);
|
|
}
|
|
|
|
FillBufferWithSilence(m_pbDelayBuffer, m_cbDelayBuffer);
|
|
|
|
m_pbDelayPtr = m_pbDelayBuffer;
|
|
|
|
done:
|
|
return hr;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// FreeStreamingResources
|
|
// Releases resources allocated during streaming.
|
|
//
|
|
// Called for two messages:
|
|
// - MFT_MESSAGE_NOTIFY_END_STREAMING
|
|
// - MFT_MESSAGE_COMMAND_FLUSH
|
|
//
|
|
// The only difference is that "end streaming" frees the delay buffer,
|
|
// while "flush" fills the delay buffer with silence.
|
|
//-------------------------------------------------------------------
|
|
|
|
void CDelayMFT::FreeStreamingResources(BOOL bFlush)
|
|
{
|
|
// Unlock the input buffer, if locked.
|
|
if (m_pbInputData)
|
|
{
|
|
assert(m_pBuffer != NULL); // We should never release the buffer while it is still locked.
|
|
m_pBuffer->Unlock();
|
|
}
|
|
|
|
// Release the input buffer.
|
|
SAFE_RELEASE(m_pBuffer);
|
|
SAFE_RELEASE(m_pSample);
|
|
|
|
m_pbInputData = NULL;
|
|
m_cbInputLength = 0;
|
|
|
|
if (bFlush)
|
|
{
|
|
// Fill the delay buffer with silence.
|
|
FillBufferWithSilence(m_pbDelayBuffer, m_cbDelayBuffer);
|
|
}
|
|
else
|
|
{
|
|
// Free the delay buffer.
|
|
CoTaskMemFree(m_pbDelayBuffer);
|
|
m_pbDelayBuffer = m_pbDelayPtr = NULL;
|
|
}
|
|
|
|
m_bValidTime = FALSE;
|
|
m_bDraining = FALSE;
|
|
m_cbTailSamples = 0;
|
|
|
|
// Things we do not release in this method:
|
|
// m_pMediaType - The media type should not change unless the caller explicitly changes it.
|
|
// m_pAttributes - We keep the attribute store alive until the destructor is called.
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// CreateAttributeStore
|
|
// Creates the MFT's attribute store, if it does not exist yet.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CDelayMFT::CreateAttributeStore()
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
if (m_pAttributes == NULL)
|
|
{
|
|
// Create the attribute store.
|
|
CHECK_HR(hr = MFCreateAttributes(&m_pAttributes, ATTRIBUTE_COUNT));
|
|
|
|
// Set initial values.
|
|
CHECK_HR(hr = m_pAttributes->SetUINT32(MF_AUDIODELAY_WET_DRY_MIX, DEFAULT_WET_DRY_MIX));
|
|
CHECK_HR(hr = m_pAttributes->SetUINT32(MF_AUDIODELAY_DELAY_LENGTH, m_dwDelay));
|
|
}
|
|
|
|
done:
|
|
return hr;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// Name: GetProposedType
|
|
// Description: Returns a preferred media type from our list.
|
|
//
|
|
// dwTypeIndex: Index into the list of peferred media types.
|
|
// ppmt: Receives a pointer to the media type.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CDelayMFT::GetProposedType(DWORD dwTypeIndex, IMFMediaType **ppmt)
|
|
{
|
|
if (dwTypeIndex > 1)
|
|
{
|
|
return MF_E_NO_MORE_TYPES;
|
|
}
|
|
|
|
IMFMediaType *pType = NULL;
|
|
HRESULT hr = S_OK;
|
|
|
|
CHECK_HR(hr = MFCreateMediaType(&pType));
|
|
|
|
// The major type is always Audio
|
|
CHECK_HR(hr = pType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio));
|
|
|
|
switch (dwTypeIndex)
|
|
{
|
|
case 0:
|
|
// Partial type: PCM audio
|
|
CHECK_HR(hr = pType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_PCM));
|
|
break;
|
|
|
|
case 1:
|
|
// Full type: Propose 48 kHz, 16-bit, 2-channel
|
|
|
|
const UINT32 SamplesPerSec = 48000;
|
|
const UINT32 BitsPerSample = 16;
|
|
const UINT32 NumChannels = 2;
|
|
const UINT32 BlockAlign = NumChannels * BitsPerSample / 8;
|
|
|
|
CHECK_HR(hr = pType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_PCM));
|
|
CHECK_HR(hr = pType->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, SamplesPerSec));
|
|
CHECK_HR(hr = pType->SetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, BitsPerSample));
|
|
CHECK_HR(hr = pType->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, NumChannels));
|
|
CHECK_HR(hr = pType->SetUINT32(MF_MT_AUDIO_BLOCK_ALIGNMENT, BlockAlign));
|
|
CHECK_HR(hr = pType->SetUINT32(MF_MT_AUDIO_AVG_BYTES_PER_SECOND, BlockAlign * SamplesPerSec));
|
|
CHECK_HR(hr = pType->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE));
|
|
break;
|
|
}
|
|
|
|
*ppmt = pType;
|
|
(*ppmt)->AddRef();
|
|
|
|
done:
|
|
SAFE_RELEASE(pType);
|
|
return hr;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// OnCheckInputType
|
|
// Validate an input media type.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CDelayMFT::OnCheckInputType(IMFMediaType *pmt)
|
|
{
|
|
assert(pmt != NULL);
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
// If the output type is set, see if they match.
|
|
if (IsOutputTypeSet())
|
|
{
|
|
DWORD flags = 0;
|
|
hr = pmt->IsEqual(m_pMediaType, &flags);
|
|
|
|
// IsEqual can return S_FALSE. Treat this as failure.
|
|
|
|
if (hr != S_OK)
|
|
{
|
|
hr = MF_E_INVALIDMEDIATYPE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Output type is not set. Just check this type.
|
|
hr = ValidatePCMAudioType(pmt);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// Name: OnCheckOutputType
|
|
// Validate an output media type.
|
|
//
|
|
// Note: This function is exactly parallel to OnCheckInputType.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CDelayMFT::OnCheckOutputType(IMFMediaType *pmt)
|
|
{
|
|
assert(pmt != NULL);
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
// If the input type is set, see if they match.
|
|
if (IsInputTypeSet())
|
|
{
|
|
DWORD flags = 0;
|
|
hr = pmt->IsEqual(m_pMediaType, &flags);
|
|
|
|
// IsEqual can return S_FALSE. Treat this as failure.
|
|
|
|
if (hr != S_OK)
|
|
{
|
|
hr = MF_E_INVALIDMEDIATYPE;
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
// Input type is not set. Just check this type.
|
|
hr = ValidatePCMAudioType(pmt);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Name: OnSetMediaType
|
|
// Set (or clear) the input or output type.
|
|
//
|
|
// When this method is called, the type has already been validated.
|
|
//
|
|
// pType: Pointer to the media type to set. If NULL, clear the type.
|
|
// dir: Specifies whether to set the input type or the output type.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CDelayMFT::OnSetMediaType(IMFMediaType *pType, StreamDirection dir)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
BOOL bInputType = (dir == InputStream);
|
|
|
|
// Note:
|
|
// This MFT requires the input type to match the output type.
|
|
// Therefore, the MFT only stores one type object, and maintains two
|
|
// Boolean flags to indicate whether the client has set the input or
|
|
// output type.
|
|
|
|
if (pType)
|
|
{
|
|
// Store the media type.
|
|
SAFE_RELEASE(m_pMediaType);
|
|
m_pMediaType = pType;
|
|
m_pMediaType->AddRef();
|
|
|
|
// Flag the stream (input or output) as set.
|
|
if (bInputType)
|
|
{
|
|
m_bInputTypeSet = TRUE;
|
|
}
|
|
else
|
|
{
|
|
m_bOutputTypeSet = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Clear the media type.
|
|
if (bInputType)
|
|
{
|
|
m_bInputTypeSet = FALSE;
|
|
}
|
|
else
|
|
{
|
|
m_bOutputTypeSet = FALSE;
|
|
}
|
|
|
|
// If both types (input and output) are not set, we can release our media type pointer.
|
|
if (!m_bOutputTypeSet)
|
|
{
|
|
SAFE_RELEASE(m_pMediaType);
|
|
}
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Name: OnDrain
|
|
// Called when the client send the MFT_MESSAGE_COMMAND_DRAIN message.
|
|
//
|
|
// Swiches the MFT to "drain" mode. In this mode, the MFT does not
|
|
// accept input until the client has generated output for the "tail"
|
|
// of the delay effect. For details, see ProcessEffectTail().
|
|
// (The MFT will also accept new input if the client flushes the MFT.)
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CDelayMFT::OnDrain()
|
|
{
|
|
if (m_cbDelayBuffer > 0)
|
|
{
|
|
m_bDraining = TRUE;
|
|
m_cbTailSamples = m_cbDelayBuffer;
|
|
}
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// Name: ProcessAudio
|
|
// Processes a block of audio data.
|
|
//
|
|
// pbDest: Destination buffer.
|
|
// pbInputData: Buffer that contains the input data.
|
|
// dwQuanta: Number of audio samples to process.
|
|
//
|
|
// Note: pbDest can equal pbInputData, because this MFT supports
|
|
// in-place processing.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CDelayMFT::ProcessAudio(BYTE *pbDest, const BYTE *pbInputData, DWORD dwQuanta)
|
|
{
|
|
assert(m_pbDelayBuffer);
|
|
assert(m_pAttributes);
|
|
|
|
int nWet = 0; // Wet portion of wet/dry mix
|
|
DWORD sample = 0, channel = 0, cChannels = 0;
|
|
|
|
cChannels = NumChannels();
|
|
|
|
// Get the wet/dry mix.
|
|
nWet = (int)MFGetAttributeUINT32(m_pAttributes, MF_AUDIODELAY_WET_DRY_MIX, DEFAULT_WET_DRY_MIX);
|
|
// Clip the value to [0...100]
|
|
nWet = min(nWet, 100);
|
|
|
|
if (Is8Bit())
|
|
{
|
|
for (sample = 0; sample < dwQuanta; ++sample)
|
|
{
|
|
for (channel = 0; channel < cChannels; ++channel)
|
|
{
|
|
// 8-bit sound is 0..255 with 128 == silence
|
|
|
|
// Get the input sample and normalize to -128 .. 127
|
|
int i = pbInputData[sample * cChannels + channel] - 128;
|
|
|
|
// Get the delay sample and normalize to -128 .. 127
|
|
int delay = m_pbDelayPtr[0] - 128;
|
|
|
|
m_pbDelayPtr[0] = static_cast<BYTE>(i + 128);
|
|
IncrementDelayPtr(sizeof(unsigned char));
|
|
|
|
i = (i * (100 - nWet)) / 100 + (delay * nWet) / 100;
|
|
|
|
// Truncate
|
|
if (i > 127)
|
|
{
|
|
i = 127;
|
|
}
|
|
else if (i < -128)
|
|
{
|
|
i = -128;
|
|
}
|
|
|
|
pbDest[sample * cChannels + channel] = (unsigned char)(i+128);
|
|
|
|
}
|
|
}
|
|
}
|
|
else // 16-bit
|
|
{
|
|
for (sample = 0; sample < dwQuanta; ++sample)
|
|
{
|
|
for (channel = 0; channel < cChannels; ++channel)
|
|
{
|
|
int i = ((short*)pbInputData)[sample * cChannels + channel];
|
|
|
|
int delay = ((short*)m_pbDelayPtr)[0];
|
|
|
|
((short*)m_pbDelayPtr)[0] = static_cast<short>(i);
|
|
IncrementDelayPtr(sizeof(short));
|
|
|
|
i = (i * (100 - nWet)) / 100 + (delay * nWet) / 100;
|
|
|
|
// Truncate
|
|
if (i > 32767)
|
|
{
|
|
i = 32767;
|
|
}
|
|
else if (i < -32768)
|
|
{
|
|
i = -32768;
|
|
}
|
|
|
|
((short*)pbDest)[sample * cChannels + channel] = (short)i;
|
|
|
|
}
|
|
}
|
|
}
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// Name: FillBufferWithSilence
|
|
// Fill a buffer with silence.
|
|
//-------------------------------------------------------------------
|
|
void CDelayMFT::FillBufferWithSilence(BYTE *pBuffer, DWORD cb)
|
|
{
|
|
if (pBuffer)
|
|
{
|
|
BYTE fill = 0;
|
|
|
|
// The definition of 'silence' depends on the audio format.
|
|
if (Is8Bit())
|
|
{
|
|
fill = 0x80;
|
|
}
|
|
else
|
|
{
|
|
fill = 0;
|
|
}
|
|
FillMemory(pBuffer, cb, fill);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// Name: ValidatePCMAudioType
|
|
// Validate a PCM audio media type.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT ValidatePCMAudioType(IMFMediaType *pmt)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
GUID majorType = GUID_NULL;
|
|
GUID subtype = GUID_NULL;
|
|
|
|
UINT32 nChannels = 0;
|
|
UINT32 nSamplesPerSec = 0;
|
|
UINT32 nAvgBytesPerSec = 0;
|
|
UINT32 nBlockAlign = 0;
|
|
UINT32 wBitsPerSample = 0;
|
|
|
|
// Get attributes from the media type.
|
|
// Each of these attributes is required for uncompressed PCM
|
|
// audio, so fail if any are not present.
|
|
CHECK_HR(hr = pmt->GetGUID(MF_MT_MAJOR_TYPE, &majorType));
|
|
CHECK_HR(hr = pmt->GetGUID(MF_MT_SUBTYPE, &subtype));
|
|
CHECK_HR(hr = pmt->GetUINT32(MF_MT_AUDIO_NUM_CHANNELS, &nChannels));
|
|
CHECK_HR(hr = pmt->GetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, &nSamplesPerSec));
|
|
CHECK_HR(hr = pmt->GetUINT32(MF_MT_AUDIO_AVG_BYTES_PER_SECOND, &nAvgBytesPerSec));
|
|
CHECK_HR(hr = pmt->GetUINT32(MF_MT_AUDIO_BLOCK_ALIGNMENT, &nBlockAlign));
|
|
CHECK_HR(hr = pmt->GetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, &wBitsPerSample));
|
|
|
|
// Validate the values.
|
|
|
|
if (nChannels != 1 && nChannels != 2)
|
|
{
|
|
CHECK_HR(hr = MF_E_INVALIDMEDIATYPE);
|
|
}
|
|
|
|
if (wBitsPerSample != 8 && wBitsPerSample != 16)
|
|
{
|
|
CHECK_HR(hr = MF_E_INVALIDMEDIATYPE);
|
|
}
|
|
|
|
// Make sure block alignment was calculated correctly.
|
|
if (nBlockAlign != nChannels * (wBitsPerSample / 8))
|
|
{
|
|
CHECK_HR(hr = MF_E_INVALIDMEDIATYPE);
|
|
}
|
|
|
|
// Check possible overflow...
|
|
if (nSamplesPerSec > (DWORD)(MAXDWORD / nBlockAlign)) // Is (nSamplesPerSec * nBlockAlign > MAXDWORD) ?
|
|
{
|
|
CHECK_HR(hr = MF_E_INVALIDMEDIATYPE);
|
|
}
|
|
|
|
// Make sure average bytes per second was calculated correctly.
|
|
if (nAvgBytesPerSec != nSamplesPerSec * nBlockAlign)
|
|
{
|
|
CHECK_HR(hr = MF_E_INVALIDMEDIATYPE);
|
|
}
|
|
|
|
done:
|
|
return hr;
|
|
}
|