Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1660 lines
44 KiB

//+-------------------------------------------------------------------------
//
// Microsoft Windows
//
// Copyright (C) Microsoft Corporation, 1997 - 1999
//
// File: mxfilter.cpp
//
//--------------------------------------------------------------------------
///////////////////////////////////////////////////////////////////////////////
//
// File: mxfilter.cpp
//
// Description: Implements the mixer filter class
//
///////////////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#if !defined(MIXER_IN_DXMRTP)
#include <initguid.h>
#define INITGUID
#endif
#include <uuids.h>
#include "mxfilter.h"
#include "g711tab.h"
#include "template.h"
//
// I know I do this don't tell me anymore!
//
#pragma warning(disable:4355)
///////////////////////////////////////////////////////////////////////////////
//***************************************************************************//
//* *//
//* Audio Mixing Code *//
//* *//
//* NOTE: I've used classes and templates to abstract common mixing code *//
//* based on the size & type. The following classes represent data *//
//* types. Each has an operator+ to which implements how data of *//
//* that size & type is combined. It is important that these classes *//
//* be indisguishable from the data types they represent so each has *//
//* a single data member and no virtual functions. That cannot *//
//* change! *//
//* *//
//***************************************************************************//
///////////////////////////////////////////////////////////////////////////////
struct CuLaw
{
//
// m_data should be the first and only data member. This allows casting
// from a BYTE.
//
BYTE m_data;
CuLaw() : m_data(0x80) { ASSERT(sizeof(CuLaw) == sizeof(BYTE)); }
//CuLaw(BYTE data) : m_data(data) { ASSERT(sizeof(CuLaw) == sizeof(BYTE)); }
inline BYTE data() {return m_data;}
//operator BYTE() { return m_data; }
// Peg and CopyBuffer functions only required for destination types
friend int operator+(const CuLaw &samp1, const CuLaw &samp2);
friend short operator+(const short samp1, const CuLaw &samp2);
};
inline int operator+(const CuLaw &samp1, const CuLaw &samp2)
{ return UlawToPCM16Table[samp1.m_data] + UlawToPCM16Table[samp2.m_data]; }
inline int operator+(const int samp1, const CuLaw &samp2)
{ return samp1 + + UlawToPCM16Table[samp2.m_data]; }
///////////////////////////////////////////////////////////////////////////////
//***************************************************************************//
///////////////////////////////////////////////////////////////////////////////
struct C8Bit
{
//
// m_data should be the first and only data member. This allows casting
// from a BYTE.
//
BYTE m_data;
C8Bit() : m_data(0x80) { ASSERT(sizeof(C8Bit) == sizeof(BYTE)); }
C8Bit(BYTE data) : m_data(data) { ASSERT(sizeof(C8Bit) == sizeof(BYTE)); }
inline BYTE data() {return m_data;}
static C8Bit Peg(short iInput)
{
BYTE bVal;
if (iInput < 0)
{
bVal = 0;
}
else if (iInput > 0xFF)
{
bVal = 0xFF;
}
return bVal;
}
static C8Bit Peg(int iInput)
{
#if 0
//
// Scale volume to 50%
//
iInput = (int)(iInput * 0.50);
#endif
// First peg the short
SHORT sVal;
if (iInput > MAXSHORT)
{
sVal = MAXSHORT;
}
else if (iInput < -MAXSHORT)
{
sVal = -MAXSHORT;
}
else
{
sVal = (short)iInput;
}
// return converted to byte
return sVal / 0xFF + 0x80;
}
static int InitSum() { return 0x80; }
void CopyBuffer(C8Bit *psrc, LONG lLen)
{
ASSERT(sizeof(C8Bit) == sizeof(BYTE));
memcpy(this, psrc, lLen * sizeof(BYTE));
}
void CopyBuffer(CuLaw *psrc, LONG lLen)
{
BYTE *pDest = &m_data;
for (WORD wC = 0; wC < lLen; wC++)
{
*pDest++ = UlawToPCM8Table[(psrc + wC)->data()];
}
}
friend short operator+(const C8Bit &samp1, const C8Bit &samp2);
friend short operator+(const int samp1, const C8Bit &samp2);
};
inline short operator+(const C8Bit &samp1, const C8Bit &samp2)
{ return samp1.m_data + samp2.m_data - 0x80; }
inline short operator+(const int samp1, const C8Bit &samp2)
{ return samp1 + samp2.m_data - 0x80; }
///////////////////////////////////////////////////////////////////////////////
//***************************************************************************//
///////////////////////////////////////////////////////////////////////////////
struct C16Bit
{
//
// m_data should be the first and only data member. This allows casting
// from a short.
//
short m_data;
C16Bit() : m_data(0) { ASSERT(sizeof(C16Bit) == sizeof(short)); }
C16Bit(short data) : m_data(data) { ASSERT(sizeof(C16Bit) == sizeof(short)); }
//short operator() () {return m_data;}
inline short data() {return m_data;}
static C16Bit Peg(int iInput)
{
SHORT sVal;
if (iInput > MAXSHORT)
{
sVal = MAXSHORT;
}
else if (iInput < -MAXSHORT)
{
sVal = -MAXSHORT;
}
else
{
sVal = (short)iInput;
}
return sVal;
}
static int InitSum() { return 0; }
void CopyBuffer(C16Bit *psrc, LONG lLen)
{
ASSERT(sizeof(C16Bit) == sizeof(short));
memcpy(this, psrc, lLen * sizeof(short));
}
void CopyBuffer(CuLaw *psrc, LONG lLen)
{
short *pDest = &m_data;
for (WORD wC = 0; wC < lLen; wC++)
{
*pDest++ = UlawToPCM16Table[(psrc + wC)->data()];
}
}
friend int operator+(const C16Bit &samp1, const C16Bit &samp2);
friend int operator+(const int samp1, const C16Bit &samp2);
};
inline int operator+(const C16Bit &samp1, const C16Bit &samp2)
{ return samp1.m_data + samp2.m_data; }
inline int operator+(const int samp1, const C16Bit &samp2)
{ return samp1 + samp2.m_data; }
///////////////////////////////////////////////////////////////////////////////
//***************************************************************************//
///////////////////////////////////////////////////////////////////////////////
//
// sample mixer
// the template allows us to have one mix code base and generate different
// implementations.
//
template<class MXSDEST, class MXSSRC>
void MixBuffers(MXSDEST *pDest, MXSSRC *pWaveSrc[], int iNumWaves, LONG lLenSrc)
{
int i;
WORD wC;
ASSERT(iNumWaves > 0);
//
// Switch some special cases to aid the compiler in optimizing.
//
switch (iNumWaves)
{
case 1:
//
// If there is only one wave then this is just a copy.
//
if ((void*)pDest != (void*)pWaveSrc[0])
{
// memcpy(pDest, pWaveSrc[0], lLen * sizeof(MXSRC));
//
// Can't use memcpy now because we may translate
//
pDest->CopyBuffer(pWaveSrc[0], lLenSrc);
}
break;
case 2:
for (wC = 0; wC < lLenSrc; wC++)
{
*pDest++ = MXSDEST::Peg
((*(pWaveSrc[0] + wC)
+ *(pWaveSrc[1] + wC)) / 2
);
}
break;
case 3:
for (wC = 0; wC < lLenSrc; wC++)
{
*pDest++ = MXSDEST::Peg
((*(pWaveSrc[0] + wC)
+ *(pWaveSrc[1] + wC)
+ *(pWaveSrc[2] + wC)) / 3
);
}
break;
case 4:
for (wC = 0; wC < lLenSrc; wC++)
{
*pDest++ = MXSDEST::Peg
((*(pWaveSrc[0] + wC)
+ *(pWaveSrc[1] + wC)
+ *(pWaveSrc[2] + wC)
+ *(pWaveSrc[3] + wC)) / 4
);
}
break;
case 5:
for (wC = 0; wC < lLenSrc; wC++)
{
*pDest++ = MXSDEST::Peg
((*(pWaveSrc[0] + wC)
+ *(pWaveSrc[1] + wC)
+ *(pWaveSrc[2] + wC)
+ *(pWaveSrc[3] + wC)
+ *(pWaveSrc[4] + wC)) / 5
);
}
break;
default:
//
// After so many just loop to sum
//
for (wC = 0; wC < lLenSrc; wC++)
{
int iSum = MXSDEST::InitSum();
for (i = 0; i < iNumWaves; i++)
{
iSum = iSum + *(pWaveSrc[i] + wC);
}
*pDest++ = MXSDEST::Peg(iSum / iNumWaves);
}
break;
}
return;
}
///////////////////////////////////////////////////////////////////////////////
//***************************************************************************//
//* *//
//* Setup Data *//
//* *//
//***************************************************************************//
///////////////////////////////////////////////////////////////////////////////
#define MIXER_FILTER_NAME "Microsoft PCM Audio Mixer"
static AMOVIESETUP_MEDIATYPE sudPinTypes =
{
&MEDIATYPE_Audio, // clsMajorType
&MEDIASUBTYPE_PCM // clsMinorType
};
static AMOVIESETUP_PIN psudPins[] =
{
{ L"Input" // strName
, FALSE // bRendered
, FALSE // bOutput
, TRUE // bZero
, TRUE // bMany
, &CLSID_NULL // clsConnectsToFilter
, L"Output" // strConnectsToPin
, 1 // nTypes
, &sudPinTypes }, // lpTypes
{ L"Output" // strName
, FALSE // bRendered
, TRUE // bOutput
, FALSE // bZero
, FALSE // bMany
, &CLSID_NULL // clsConnectsToFilter
, L"Input" // strConnectsToPin
, 1 // nTypes
, &sudPinTypes } // lpTypes
};
AMOVIESETUP_FILTER sudMixer =
{
&CLSID_AudioMixFilter // clsID
, LMIXER_FILTER_NAME // strName
, MERIT_DO_NOT_USE // dwMerit
, 2 // nPins
, psudPins // lpPin
};
#if !defined(MIXER_IN_DXMRTP)
CFactoryTemplate g_Templates [] = {
CFT_MIXER_ALL_FILTERS
};
int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]);
#endif
///////////////////////////////////////////////////////////////////////////////
//***************************************************************************//
//* *//
//* CMixer Implementation *//
//* *//
//***************************************************************************//
///////////////////////////////////////////////////////////////////////////////
BOOL
GetRegValue(
IN LPCTSTR szName,
OUT DWORD *pdwValue
)
/*++
Routine Description:
Get a dword from the registry in the Mixer key.
Arguments:
szName - The name of the value.
pdwValue - a pointer to the dword returned.
Return Value:
TURE - SUCCEED.
FALSE - FAIL
--*/
{
HKEY hKey;
DWORD dwDataSize, dwDataType;
if (::RegOpenKeyEx(
HKEY_LOCAL_MACHINE,
MIXER_KEY,
0,
KEY_READ,
&hKey) != NOERROR)
{
return FALSE;
}
dwDataSize = sizeof(DWORD);
if (::RegQueryValueEx(
hKey,
szName,
0,
&dwDataType,
(LPBYTE) pdwValue,
&dwDataSize) != NOERROR)
{
RegCloseKey (hKey);
return FALSE;
}
RegCloseKey (hKey);
return TRUE;
}
///////////////////////////////////////////////////////////////////////////////
//
// CreateInstance
//
// Creator function for the class ID
//
///////////////////////////////////////////////////////////////////////////////
CUnknown * WINAPI CMixer::CreateInstance(LPUNKNOWN pUnk, HRESULT *phr)
{
return new CMixer(NAME(MIXER_FILTER_NAME), pUnk, phr);
}
///////////////////////////////////////////////////////////////////////////////
//
// Constructor
//
///////////////////////////////////////////////////////////////////////////////
CMixer::CMixer(TCHAR *pName, LPUNKNOWN pUnk, HRESULT *phr)
: m_listInputPins(NAME("Mixer Input Pin list"))
, m_cInputPins(0)
, m_iNextInputPinNumber(0)
, m_pMixerOutput(NULL)
, m_fReconnect(FALSE)
, CBaseFilter(NAME(MIXER_FILTER_NAME), pUnk, this, CLSID_AudioMixFilter)
, m_dwSpurtStartTime(0)
, m_dwLastTime(0)
, m_lTotalDelayBufferSize(0)
{
ASSERT(phr);
// Create a single output pin at this time
InitInputPinsList();
m_pMixerOutput = new CMixerOutputPin(
NAME("Mixer Output"),
this,
phr,
L"Mixer Output");
if (FAILED(*phr))
{
return;
}
else if (!m_pMixerOutput)
{
*phr = E_OUTOFMEMORY;
return;
}
if (!SpawnNewInput())
{
*phr = E_OUTOFMEMORY;
return;
}
if (!GetRegValue(JITTERBUFFER, (DWORD *)&m_lJitterBufferTime))
{
m_lJitterBufferTime = DEFAULT_JITTERBUFFERTIME;
}
if (!GetRegValue(MIXBUFFER, (DWORD *)&m_lMixDelayTime))
{
m_lMixDelayTime = DEFAULT_MIXDELAYTIME;
}
}
///////////////////////////////////////////////////////////////////////////////
//
// Destructor
//
///////////////////////////////////////////////////////////////////////////////
CMixer::~CMixer()
{
InitInputPinsList();
delete m_pMixerOutput;
DbgLog((LOG_TRACE, 1, TEXT("~Mixer returns")));
}
///////////////////////////////////////////////////////////////////////////////
//
// GetSetupData - Part of the self-registration mechanism
//
///////////////////////////////////////////////////////////////////////////////
LPAMOVIESETUP_FILTER CMixer::GetSetupData()
{
return &sudMixer;
}
HRESULT CMixer::FillSilentBuffer(IMediaSample *pSilentSamp, DWORD dwSize)
/*++
Routine Description:
Fill a media sample with silence.
Arguments:
pSilentSamp - The sample being filled with silence.
dwSize - The length of the silence(in bytes).
Return Value:
HRESULT from GetPointer function or S_OK.
--*/
{
HRESULT hr;
BYTE *pSilBuf;
hr = pSilentSamp->GetPointer(&pSilBuf);
ASSERT(SUCCEEDED(hr));
DWORD dwLen = min((DWORD)pSilentSamp->GetSize(), dwSize);
// Fill the buffer with the appropriate silent data for it's size
switch(m_wOutputBitsPerSample)
{
case 8:
FillMemory(pSilBuf, dwLen, 128);
break;
case 16:
FillMemory(pSilBuf, dwLen, 0);
break;
}
pSilentSamp->SetActualDataLength(dwLen);
return S_OK;
}
CBasePin *CMixer::GetPin(int n)
/*++
Routine Description:
Get the Nth pin on this filter.
Arguments:
n - The index of the pin. Zero is output.
Return Value:
a pointer to the pin.
--*/
{
// Pin zero is the one and only output pin
if (n == OUTPUT_PIN)
return (CBasePin*)m_pMixerOutput;
// return the input pin at position n (one based)
return (CBasePin*)GetInputPinNFromList(n);
}
void CMixer::InitInputPinsList()
/*++
Routine Description:
Initialize the pin list. Relase all the pins currently in the list.
Arguments:
Return Value:
--*/
{
POSITION pos = m_listInputPins.GetHeadPosition();
while(pos)
{
CMixerInputPin *pInputPin = m_listInputPins.GetNext(pos);
pInputPin->Release();
}
m_cInputPins = 0;
m_listInputPins.RemoveAll();
}
BOOL CMixer::SpawnNewInput()
/*++
Routine Description:
Create a new input pin. Also create a queue for the samples of this
new input pin. The queues are maintained by the filter in a list.
Arguments:
Return Value:
TRUE - succeed.
FALSE - fail.
--*/
{
int n = GetFreePinCount();
ASSERT(n <= 1);
if (n == 1 || m_cInputPins == MAX_QUEUES) return FALSE;
// First create a queue for this input pin.
CBufferQueue *pQueue = new CBufferQueue();
if (pQueue == NULL)
{
DbgLog((LOG_ERROR, 1, TEXT("CMixer, can't create queue.")));
return NULL;
}
if (!pQueue->Initialize(MAX_QUEUE_STORAGE))
{
DbgLog((LOG_ERROR, 1, TEXT("CMixer, can't initialize queue.")));
return NULL;
}
// construct a pin name.
WCHAR szbuf[20]; // Temporary scratch buffer
wsprintfW(szbuf, L"Input%d", m_iNextInputPinNumber);
// Create the pin object itself
HRESULT hr = S_OK;
CMixerInputPin *pPin = new CMixerInputPin(
NAME("Mixer Input"),
this,
&hr,
szbuf,
m_iNextInputPinNumber,
pQueue
);
if (FAILED(hr) || pPin == NULL)
{
delete pPin; // delete NULL ok.
delete pQueue;
DbgLog((LOG_ERROR, 1, TEXT("CMixer, can't create a pin.")));
return FALSE;
}
// Add the pin into the list.
pPin->AddRef();
m_listInputPins.AddTail(pPin);
// Add the new queue into the filter's queue list.
m_queues[m_cInputPins] = pQueue;
m_iNextInputPinNumber++; // Next number to use for pin
m_cInputPins++;
IncrementPinVersion();
return TRUE;
}
CMixerInputPin *CMixer::GetInputPinNFromList(int n)
/*++
Routine Description:
Get the Nth input pin on this filter.
Arguments:
n - The index of the pin. base 1.
Return Value:
a pointer to the pin.
--*/
{
// Validate the position being asked for
if (n > m_cInputPins) return NULL;
// Get the head of the list
POSITION pos = m_listInputPins.GetHeadPosition();
CMixerInputPin *pInputPin;
while(n)
{
pInputPin = m_listInputPins.GetNext(pos);
n--;
}
return pInputPin;
}
void CMixer::DeleteInputPin(CMixerInputPin *pPin, CBufferQueue *pQueue)
/*++
Routine Description:
Delete an input pin from the filter. Also involves freeing the sample
queue that the pin was using.
Arguments:
pPin - A pointer to the pin.
pQueue - a pointer to the sample queue.
Return Value:
--*/
{
DbgLog((LOG_TRACE, 1, TEXT("CMixer::DeleteInputPin called")));
POSITION pos = m_listInputPins.GetHeadPosition();
while(pos)
{
POSITION posold = pos; // Remember this position
CMixerInputPin *pInputPin = m_listInputPins.GetNext(pos);
if (pInputPin == pPin)
{
// Found the pin, remove it from the list.
m_listInputPins.Remove(posold);
// Find the queue allocated for this pin and remove it.
for (int i = 0; i < m_cInputPins; i ++)
{
if (m_queues[i] == pQueue)
{
m_queues[i] = m_queues[m_cInputPins - 1];
break;
}
}
delete pQueue;
delete pPin;
m_cInputPins--;
// ASSERT(pInputPin->m_pInputQueue == NULL);
IncrementPinVersion();
break;
}
}
}
///////////////////////////////////////////////////////////////////////////////
//
// Verify that we can send this output based on our input. This filter can
// do a simple transformation from uLaw to PCM so we account for that here.
//
// One special condition is applied here. That is, unless we transform from
// uLaw to PCM we don't do sample size conversions (i.e. we assume the input
// bits per sample is the same as the output).
//
///////////////////////////////////////////////////////////////////////////////
HRESULT CMixer::CheckOutputMediaType(const CMediaType* pMediaType)
{
if (!GetInput0()->IsConnected()) {
DbgLog((LOG_ERROR, 1,
TEXT("CMixer::CheckOutputMediaType, input is not connected")
));
return E_UNEXPECTED;
}
CMediaType &rMT = GetInput0()->CurrentMediaType();
//
// Grose checks first.
//
if (pMediaType == NULL) {
DbgLog((LOG_ERROR, 1,
TEXT("CMixer::CheckOutputMediaType, input pin mediatype error")
));
return E_INVALIDARG;
}
if (*pMediaType->Type() != MEDIATYPE_Audio) {
DbgLog((LOG_ERROR, 1,
TEXT("CMixer::CheckOutputMediaType, mediatype is not audio")
));
return E_INVALIDARG;
}
if (*pMediaType->FormatType() != FORMAT_WaveFormatEx) {
DbgLog((LOG_ERROR, 1,
TEXT("CMixer::CheckOutputMediaType, not wave format")
));
return E_INVALIDARG;
}
WAVEFORMATEX *pInFmt, *pOutFmt;
pInFmt = (WAVEFORMATEX*)rMT.Format();
pOutFmt = (WAVEFORMATEX*)pMediaType->Format();
if (pInFmt == NULL) {
DbgLog((LOG_ERROR, 1,
TEXT("CMixer::CheckOutputMediaType, input format error")
));
return E_UNEXPECTED;
}
if (pOutFmt == NULL) {
DbgLog((LOG_ERROR, 1,
TEXT("CMixer::CheckOutputMediaType, output format error")
));
return E_INVALIDARG;
}
if (pInFmt->nSamplesPerSec != pOutFmt->nSamplesPerSec)
{
DbgLog((LOG_ERROR, 1,
TEXT("CMixer::CheckOutputMediaType, wrong sample rate")
));
return E_INVALIDARG;
}
return S_OK;
}
///////////////////////////////////////////////////////////////////////////////
//
// Verify that we actually know how to support the data that we will be
// passed.
//
///////////////////////////////////////////////////////////////////////////////
HRESULT CMixer::CheckMediaType(const CMediaType* pMediaType)
{
//
// Grose checks first.
//
if (pMediaType == NULL) {
DbgLog((LOG_ERROR, 1,
TEXT("CMixer::CheckMediaType, NULL media type")
));
return E_INVALIDARG;
}
if (*pMediaType->Type() != MEDIATYPE_Audio) {
DbgLog((LOG_ERROR, 1,
TEXT("CMixer::CheckMediaType, not audio")
));
return E_INVALIDARG;
}
if (*pMediaType->FormatType() != FORMAT_WaveFormatEx) {
DbgLog((LOG_ERROR, 1,
TEXT("CMixer::CheckMediaType, not wave")
));
return E_INVALIDARG;
}
WAVEFORMATEX *pwfx = (WAVEFORMATEX *)pMediaType->Format();
//
// Attempt to make shure the adjacent filter doesn't mess us
//
if (pwfx == NULL)
{
DbgLog((LOG_ERROR, 1,
TEXT("CMixer::CheckMediaType, can't get wave format")
));
return E_INVALIDARG;
}
//
// Check the basic requirements
//
if ((pwfx->wFormatTag != WAVE_FORMAT_PCM)
&& (pwfx->wFormatTag != WAVE_FORMAT_MULAW))
{
DbgLog((LOG_ERROR, 1,
TEXT("CMixer::CheckMediaType, unsupported format: %d"),
pwfx->wFormatTag
));
return E_FAIL;
}
//
// If the input pin 0 is connected it dictates this!
//
if (GetInput0()->IsConnected())
{
if (*pMediaType != GetInput0()->CurrentMediaType())
{
DbgLog((LOG_ERROR, 1,
TEXT("CMixer::CheckMediaType, different from the first pin.")
));
return E_FAIL;
}
}
return S_OK;
}
///////////////////////////////////////////////////////////////////////////////
//
// Return the currently selected media type
//
///////////////////////////////////////////////////////////////////////////////
CMediaType &CMixer::CurrentMediaType()
{
return ((CMixerInputPin*)GetInput0())->CurrentMediaType();
}
///////////////////////////////////////////////////////////////////////////////
//
// Store some properties deal with allocator negoation.
//
///////////////////////////////////////////////////////////////////////////////
HRESULT CMixer::CompleteConnect()
{
return S_OK; // BUGBUG: This could be removed!
}
///////////////////////////////////////////////////////////////////////////////
//
// Deal with allocator negoation.
//
///////////////////////////////////////////////////////////////////////////////
HRESULT CMixer::DisconnectInput()
{
return S_OK; // BUGBUG: This could be removed!
}
///////////////////////////////////////////////////////////////////////////////
//
// Must set the properties for the allocator.
//
///////////////////////////////////////////////////////////////////////////////
HRESULT CMixer::DecideBufferSize(
IMemAllocator * pAlloc,
ALLOCATOR_PROPERTIES * pProperties
)
{
IMemAllocator *pMemAllocator;
ALLOCATOR_PROPERTIES Request, Actual;
HRESULT hr;
// If we are connected upstream, get his views
if (GetInput0()->IsConnected() && GetInput0()->GetAllocator() != NULL)
{
// Get the input pin allocator, and get its size and count.
// we don't care about his alignment and prefix.
hr = GetInput0()->GetAllocator()->GetProperties(&Request);
if (FAILED(hr))
{
// Input connected but with a secretive allocator - enough!
return hr;
}
}
else
{
// We're reduced to blind guessing. Let's guess one byte and if
// this isn't enough then when the other pin does get connected
// we can revise it.
ZeroMemory(&Request, sizeof(Request));
}
Request.cBuffers = 8; // limit the buffer size to 8 samples.
//
// Input requirements dictate the output requirements.
//
pProperties->cBuffers = Request.cBuffers; // limit the buffer size to 8 samples.
pProperties->cbBuffer = Request.cbBuffer;
if (pProperties->cbBuffer == 0) { pProperties->cbBuffer = 1; }
if (pProperties->cBuffers == 0) { pProperties->cBuffers = 1; }
//
// Now adjust the buffersize just in case we translate from uLaw to
// PCM16.
//
//
// First look at the input pin.
//
CMediaType &pmt = GetInput(0)->CurrentMediaType();
ASSERT(pmt.formattype == FORMAT_WaveFormatEx);
WAVEFORMATEX *pwfx = (WAVEFORMATEX *)pmt.Format();
ASSERT(pwfx != NULL);
m_wInputFormatTag = pwfx->wFormatTag;
// remember the input bytes per millisecond.
m_dwInputBytesPerMS = pwfx->nAvgBytesPerSec / 1000;
ASSERT(m_dwInputBytesPerMS != 0);
// remember the input bits per sample.
m_wInputBitsPerSample = pwfx->wBitsPerSample;
ASSERT(m_wInputBitsPerSample != 0);
DbgLog((LOG_TRACE, 1,
TEXT("CMixer::DecideBufferSizes:\nInput: BitsPerSample %d, BytesPerMS %d"),
m_wInputBitsPerSample,
m_dwInputBytesPerMS
));
//
// Now look at the output pin.
//
CMediaType &pmt2 = GetOutput()->CurrentMediaType();
ASSERT(pmt2.formattype == FORMAT_WaveFormatEx);
WAVEFORMATEX *pwfx2 = (WAVEFORMATEX *)pmt2.Format();
ASSERT(pwfx2 != NULL);
// remember the output bits per sample.
m_wOutputBitsPerSample = pwfx2->wBitsPerSample;
ASSERT(m_wOutputBitsPerSample != 0);
// Adjust the buffer size.
pProperties->cbBuffer *= m_wOutputBitsPerSample / m_wInputBitsPerSample;
DbgLog((LOG_TRACE, 1,
TEXT("CMixer::DecideBufferSizes:\nOutput: BitsPerSample %d"),
m_wOutputBitsPerSample
));
//
// Send these to the downstream filter
//
hr = pAlloc->SetProperties(pProperties, &Actual);
if (FAILED(hr))
{
return hr;
}
// Make sure we got the right alignment and at least the minimum required
DbgLog((LOG_TRACE, 1,
TEXT("CMixer::DecideBufferSizes:\ncBuffers %d, cbBuffer %d"),
Actual.cBuffers,
Actual.cbBuffer
));
if ( (Request.cBuffers > Actual.cBuffers)
|| (Request.cbBuffer > Actual.cbBuffer)
|| (Request.cbAlign > Actual.cbAlign)
)
{
DbgLog((LOG_ERROR, 1,
TEXT("CMixer::DecideBufferSize, Actual size less that request.")
));
return E_FAIL;
}
return NOERROR;
}
STDMETHODIMP CMixer::Run(REFERENCE_TIME tStart)
{
if (!GetOutput()->IsConnected()) {
DbgLog((LOG_ERROR, 1,
TEXT("CMixer::Run, output is not connected")
));
return E_UNEXPECTED;
}
// This is the function we reinterpret all the parameters. This means all
// the setting only take effect when the stream is restarting again.
// This is the length of silence we preplay before the first sample
// of a spurt of sound. We need more for the mixing case.
m_lTotalDelayTime = m_lJitterBufferTime +
((m_cInputPins == 2) ? 0 : m_lMixDelayTime);
m_lTotalDelayBufferSize = m_lTotalDelayTime * m_dwInputBytesPerMS;
m_lTimeDelta = 0;
m_lSampleTime = 0;
m_dwLastTime = timeGetTime();
DbgLog((LOG_TRACE, 1,
TEXT("CMixer::Run: JitterBuffer time: %d, size:%d"),
m_lTotalDelayTime,
m_lTotalDelayBufferSize
));
return CBaseFilter::Run(tStart);
}
void CMixer::FlushAllQueues()
{
for (int i = 0; i < m_cInputPins; i++)
{
m_queues[i]->Flush();
}
}
void CMixer::FlushQueue(CBufferQueue *pQueue)
{
#if USE_LOCK
CAutoLock l(&m_cQueues);
#endif
pQueue->Flush();
}
HRESULT CMixer::PrePlay()
/*++
Routine Description:
Plays certain amount of silence before playing a sound spurt. This will
keep the waveout device busy for that amount of time while we buffer up
some samples to play without gap. We could have set a timer to do the
same, but that needs another thread and the thread turn around time is
not accurate. Feed some samples to the waveout device provides a free
and accurate jitter buffer.
Arguments:
Return Value:
HRESULT.
--*/
{
IMediaSample *pMediaSample;
HRESULT hr = GetOutput()->GetDeliveryBuffer(
&pMediaSample, NULL, NULL, 0);
if (FAILED(hr))
{
// no free buffer from the wave out device.
DbgLog((LOG_TRACE, 1, TEXT("get buffer failed: %x"), hr));
return hr;
}
// The size of the silence is determined by how long the samples
// have to wait before playing. In point to point to case, we can
// deliver buffers as soon as we get them. The mixing case needs
// longer buffer.
DWORD dwSilenceSize = m_lTotalDelayBufferSize;
// let's play some silence.
hr = FillSilentBuffer(pMediaSample,
dwSilenceSize * m_wOutputBitsPerSample / m_wInputBitsPerSample);
if (FAILED(hr))
{
DbgLog((LOG_TRACE, 1, TEXT("fill sience failed: %x"), hr));
pMediaSample->Release();
return hr;
}
// Deliver the silence buffer.
hr = GetOutput()->Deliver(pMediaSample);
DbgLog((LOG_TRACE, 0x3ff,
TEXT("---------Insert silence, size:%d"),
dwSilenceSize
));
pMediaSample->Release();
if (FAILED(hr))
{
DbgLog((LOG_TRACE, 1,
TEXT("deliver failed: %x"), hr));
return hr;
}
// adjust the delta between system time and stream time.
m_lTimeDelta += dwSilenceSize / m_dwInputBytesPerMS;
return hr;
}
BOOL CMixer::ResetQueuesIfNecessary()
/*++
Routine Description:
Check to see if we have not received any sample for a while. If yes,
reset all the queues to empty.
TimeDelta = TotalSampleTime + JitterBufferTime -
(CurrentTime - SpurtStartTime);
If timedelta is negative, the device is starving. This is the sign of
a new spurt. We could also set a negative threshold to make the algorithm
more tolerable to temporary increase of delay.
Arguments:
Return Value:
TRUE - yes, the queues are reset.
FALSE - no, the queues are not touched.
--*/
{
// Get current time.
DWORD dwThisTime = timeGetTime();
// Calculate the accumulated delta between the system time and stream time.
m_lTimeDelta -= (dwThisTime - m_dwLastTime);
DbgLog((LOG_TRACE, 0x3ff,
TEXT("*******last:%dms, this:%dms, Delta:%dms"),
m_dwLastTime,
dwThisTime,
m_lTimeDelta
));
// update with new reading of system time.
m_dwLastTime = dwThisTime;
if (m_lTimeDelta < 0)
{
// The data is behind the playout. Assume this is a new spurt.
m_dwSpurtStartTime = dwThisTime;
// reset the time delta for this new spurt.
m_lTimeDelta = 0;
// Reset the queues that used to be active.
for (int i = 0; i < m_cInputPins; i ++)
{
m_queues[i]->Flush();
}
return TRUE;
}
return FALSE;
}
HRESULT CMixer::SendSample()
/*++
Routine Description:
There are samples in the queues that need to be sent. Mix them
and then send them out.
Arguments:
Return Value:
S_OK - samples are delivered.
S_FALSE - there is nothing to deliver.
--*/
{
IMediaSample *pMediaSample;
HRESULT hr = GetOutput()->GetDeliveryBuffer(
&pMediaSample, NULL, NULL, 0);
if (FAILED(hr))
{
// no free buffer from the wave out device.
DbgLog((LOG_TRACE, 1, TEXT("get buffer failed: %x"), hr));
return hr;
}
long lCount = 0;
IMediaSample *ppSamples[MAX_QUEUES];
// Get samples from the active queues.
for (int i = 0; i < m_cInputPins; i ++)
{
if (m_queues[i]->IsActive())
{
// only get the samples that meets the schedule.
IMediaSample *pSample = m_queues[i]->DeQ(
(m_cInputPins == 2), m_dwLastTime
);
if (pSample != NULL)
{
ppSamples[lCount++] = pSample;
}
}
}
if (lCount != 0)
{
// The samples are released in this function after mixing.
hr = MixOneSample(
pMediaSample,
ppSamples,
lCount
);
if (FAILED(hr))
{
DbgLog((LOG_TRACE, 1, TEXT("mix one sample failed: %x"), hr));
pMediaSample->Release();
return hr;
}
// deliver the mixed buffer.
hr = GetOutput()->Deliver(pMediaSample);
DbgLog((LOG_TRACE, 0x3ff, TEXT("sample deliverd.")));
if (FAILED(hr))
{
DbgLog((LOG_TRACE, 1, TEXT("deliver failed: %x"), hr));
pMediaSample->Release();
return hr;
}
// adjust the time delta. The input gains some ground.
m_lTimeDelta += m_lSampleTime;
hr = S_OK;
}
else
{
hr = S_FALSE;
}
pMediaSample->Release();
return hr;
}
HRESULT CMixer::Receive(CBufferQueue *pQueue, IMediaSample * pSample)
/*++
Routine Description:
Get a new sample for a certain queue. If this is the first sample after
a period of silence, preplay some silence to give us some time to absorb
the jitter. Otherwise, if this queue has queued long enought sample, this
queue will trigue a send sample operation. If there is only one queue,
the sample is delivered without wait.
We are ignoring timestamps for the samples we send. Waveout filter will
send this kinds of sample directly to the device, which is just what we
want.
Arguments:
pQueue - The queue the the sample is supposed to go.
pSample - The sample just received.
Return Value:
TRUE - yes, the queues are reset.
FALSE - no, the queues are not touched.
--*/
{
ASSERT(pQueue != NULL);
#if USE_LOCK
// Since there is only one thread delivering audio samples, there is
// no need to lock.
CAutoLock l(&m_cQueues);
#endif
// BUGBUG, we are still assuming all the samples to be mixed are of
// the same size. Under this assumption, the mixing can be done
// with n reads and 1 write. Otherwise, it will be n reads and
// n writes.
long lDataLen = pSample->GetActualDataLength();
if (m_lSampleTime == 0)
{
m_lSampleTime = lDataLen / m_dwInputBytesPerMS;
ASSERT(m_lSampleTime > 0);
DbgLog((LOG_TRACE, 1, TEXT("sample size: %dms"), m_lSampleTime));
}
// First test if this is the first sample of a new sound spurt.
BOOL fNewBurst = ResetQueuesIfNecessary();
// Check if we are in overrun state. Discard the packet if it is true.
// The sound driver needs some time to render existing samples.
if (m_lTimeDelta > MAX_TIMEDELTA)
{
return S_FALSE;
}
DWORD dwStartTime = m_dwSpurtStartTime;
if (!fNewBurst && !pQueue->IsActive())
{
// This is a stream just joined, it is scheduled to join mixing
// at a later slot in order to have some jitter buffering.
dwStartTime =
((m_dwLastTime - m_dwSpurtStartTime + m_lTotalDelayTime)
/ m_lSampleTime) * m_lSampleTime + m_dwSpurtStartTime;
}
// Put the sample into the queue, set the queue's scheduled deliver time
// and adjustment for each sample.
pQueue->EnQ(pSample, dwStartTime, m_lSampleTime);
// After we put the sample in our queue, we can only return S_OK because
// we have accepted the sample.
if (fNewBurst)
{
// If this is the first sample after silence, we need to insert some
// silence to provide some jitter absorbing time.
if (FAILED(PrePlay()))
{
// If we can send silence, just return.
return S_OK;
}
}
// If there is only one stream, we can send the sample as soon as
// we get it.
if (m_cInputPins == 2)
{
SendSample();
return S_OK;
}
// If we are ready to mix, get a sample from each queue and mix them.
// The following "if" statement is derived as follows:
//
// (CurrentTime - SpurtStartTime - PlayedTime > SampleTime) triggers mixing
//
// delta = JitterBufferTime + PlayedTime - (CurrentTime - SpurtStartTime);
//
// ==> (JitterBufferTime - delta > SampleTime) triggers mixing.
//
DbgLog((LOG_TRACE, 0x3ff, TEXT("mix trigger: %dms"),
m_lTotalDelayTime - m_lTimeDelta - m_lSampleTime));
HRESULT hr = S_OK;
while ((m_lTotalDelayTime - m_lTimeDelta - m_lSampleTime > 0)
&& hr == S_OK)
{
hr = SendSample();
DbgLog((LOG_TRACE, 0x3ff, TEXT("mix trigger: %dms"),
m_lTotalDelayTime - m_lTimeDelta - m_lSampleTime));
}
return S_OK;
}
///////////////////////////////////////////////////////////////////////////////
//
// Mix one IMediaSample
//
///////////////////////////////////////////////////////////////////////////////
HRESULT CMixer::MixOneSample(
IMediaSample *pMixedSample,
IMediaSample ** ppSample,
long lCount
)
/*++
Routine Description:
Mix one sample from an array of samples.
Arguments:
pMixedSample - The sample that stores the mixed data.
ppSample - The array of samples to be mixed.
lCount - The number of samples in the array.
Return Value:
HRESULT.
--*/
{
BYTE *ppBuffer[MAX_QUEUES];
BYTE *pMixBuffer;
HRESULT hr;
hr = pMixedSample->GetPointer(&pMixBuffer);
ASSERT(SUCCEEDED(hr));
for (int i = 0; i < lCount; i ++)
{
hr = ppSample[i]->GetPointer(&ppBuffer[i]);
ASSERT(SUCCEEDED(hr));
}
long lDataLen = ppSample[0]->GetActualDataLength();
long lMixBufferSize = pMixedSample->GetSize();
long lMixDataLen = lDataLen;
//
// Mix based on format and sample size
//
switch(m_wInputFormatTag)
{
case WAVE_FORMAT_PCM:
if (m_wInputBitsPerSample != m_wOutputBitsPerSample)
{
DbgLog((LOG_ERROR, 1,
TEXT("sample size mismatch. Input: %d, output: %d"),
m_wInputBitsPerSample, m_wOutputBitsPerSample
));
return E_FAIL;
}
switch(m_wOutputBitsPerSample)
{
case 8:
if (lMixDataLen > lMixBufferSize)
{
DbgLog((LOG_ERROR, 1,
TEXT("Sample too big, sample size:%d, buffer size:%d"),
lMixDataLen, lMixBufferSize
));
break;
}
MixBuffers<C8Bit, C8Bit>(
(C8Bit*)pMixBuffer,
(C8Bit**) (BYTE**) ppBuffer,
lCount, lDataLen
);
break;
case 16:
if (lMixDataLen > lMixBufferSize)
{
DbgLog((LOG_ERROR, 1,
TEXT("Sample too big, sample size:%d, buffer size:%d"),
lMixDataLen, lMixBufferSize
));
break;
}
MixBuffers<C16Bit, C16Bit>(
(C16Bit*)pMixBuffer,
(C16Bit**)(BYTE**)ppBuffer,
lCount, lDataLen/2
);
break;
default:
DbgLog((LOG_ERROR, 1,
TEXT("Unknow bits per sample:%d"),
m_wOutputBitsPerSample
));
}
break;
case WAVE_FORMAT_MULAW:
switch(m_wOutputBitsPerSample)
{
case 8:
if (lMixDataLen > lMixBufferSize)
{
DbgLog((LOG_ERROR, 1,
TEXT("Sample too big, sample size:%d, buffer size:%d"),
lMixDataLen, lMixBufferSize
));
break;
}
MixBuffers<C8Bit, CuLaw>(
(C8Bit*)pMixBuffer,
(CuLaw**)(BYTE**)ppBuffer,
lCount, lDataLen
);
break;
case 16:
lMixDataLen *= 2; // change from 8 bits to 16 bits.
if (lMixDataLen > lMixBufferSize)
{
DbgLog((LOG_ERROR, 1,
TEXT("Sample too big, sample size:%d, buffer size:%d"),
lMixDataLen, lMixBufferSize
));
break;
}
MixBuffers<C16Bit, CuLaw>(
(C16Bit*)pMixBuffer,
(CuLaw**)(BYTE**)ppBuffer,
lCount, lDataLen
);
break;
default:
DbgLog((LOG_ERROR, 1,
TEXT("Unknow bits per sample:%d"),
m_wOutputBitsPerSample
));
}
break;
default:
DbgLog((LOG_ERROR, 1,
TEXT("Unknow format tag:%d"),
m_wInputFormatTag
));
}
DbgLog((LOG_TRACE, 6,
TEXT("mixed length:%d, buffer size:%d"),
lMixDataLen, lMixBufferSize
));
hr = pMixedSample->SetActualDataLength(lMixDataLen);
ASSERT(SUCCEEDED(hr));
//
// Release all the buffers
//
for (i = 0; i < lCount; i++)
{
ppSample[i]->Release();
}
return S_OK;
}
///////////////////////////////////////////////////////////////////////////////
//***************************************************************************//
//* *//
//* Automagic registration *//
//* *//
//***************************************************************************//
///////////////////////////////////////////////////////////////////////////////
//
// DllRegisterServer
//
#if !defined(MIXER_IN_DXMRTP)
STDAPI DllRegisterServer()
{
return AMovieDllRegisterServer2(TRUE);
}
//
// DllUnregisterServer
//
STDAPI DllUnregisterServer()
{
return AMovieDllRegisterServer2(FALSE);
}
#endif