//------------------------------------------------------------------------------
// File: TransIP.cpp
//
// Desc: DirectShow base classes - implements class for simple Transform-
//       In-Place filters such as audio.
//
// Copyright (c) 1992-2001 Microsoft Corporation.  All rights reserved.
//------------------------------------------------------------------------------


// How allocators are decided.
//
// An in-place transform tries to do its work in someone else's buffers.
// It tries to persuade the filters on either side to use the same allocator
// (and for that matter the same media type).  In desperation, if the downstream
// filter refuses to supply an allocator and the upstream filter offers only
// a read-only one then it will provide an allocator.
// if the upstream filter insists on a read-only allocator then the transform
// filter will (reluctantly) copy the data before transforming it.
//
// In order to pass an allocator through it needs to remember the one it got
// from the first connection to pass it on to the second one.
//
// It is good if we can avoid insisting on a particular order of connection
// (There is a precedent for insisting on the input
// being connected first.  Insisting on the output being connected first is
// not allowed.  That would break RenderFile.)
//
// The base pin classes (CBaseOutputPin and CBaseInputPin) both have a
// m_pAllocator member which is used in places like
// CBaseOutputPin::GetDeliveryBuffer and CBaseInputPin::Inactive.
// To avoid lots of extra overriding, we should keep these happy
// by using these pointers.
//
// When each pin is connected, it will set the corresponding m_pAllocator
// and will have a single ref-count on that allocator.
//
// Refcounts are acquired by GetAllocator calls which return AddReffed
// allocators and are released in one of:
//     CBaseInputPin::Disconnect
//     CBaseOutputPin::BreakConect
// In each case m_pAllocator is set to NULL after the release, so this
// is the last chance to ever release it.  If there should ever be
// multiple refcounts associated with the same pointer, this had better
// be cleared up before that happens.  To avoid such problems, we'll
// stick with one per pointer.



// RECONNECTING and STATE CHANGES
//
// Each pin could be disconnected, connected with a read-only allocator,
// connected with an upstream read/write allocator, connected with an
// allocator from downstream or connected with its own allocator.
// Five states for each pin gives a data space of 25 states.
//
// Notation:
//
// R/W == read/write
// R-O == read-only
//
// <input pin state> <output pin state> <comments>
//
// 00 means an unconnected pin.
// <- means using a R/W allocator from the upstream filter
// <= means using a R-O allocator from an upstream filter
// || means using our own (R/W) allocator.
// -> means using a R/W allocator from a downstream filter
//    (a R-O allocator from downstream is nonsense, it can't ever work).
//
//
// That makes 25 possible states.  Some states are nonsense (two different
// allocators from the same place).  These are just an artifact of the notation.
//        <=  <-  Nonsense.
//        <-  <=  Nonsense
// Some states are illegal (the output pin never accepts a R-O allocator):
//        00  <=  !! Error !!
//        <=  <=  !! Error !!
//        ||  <=  !! Error !!
//        ->  <=  !! Error !!
// Three states appears to be inaccessible:
//        ->  ||  Inaccessible
//        ||  ->  Inaccessible
//        ||  <-  Inaccessible
// Some states only ever occur as intermediates with a pending reconnect which
// is guaranteed to finish in another state.
//        ->  00  ?? unstable goes to || 00
//        00  <-  ?? unstable goes to 00 ||
//        ->  <-  ?? unstable goes to -> ->
//        <-  ||  ?? unstable goes to <- <-
//        <-  ->  ?? unstable goes to <- <-
// And that leaves 11 possible resting states:
// 1      00  00  Nothing connected.
// 2      <-  00  Input pin connected.
// 3      <=  00  Input pin connected using R-O allocator.
// 4      ||  00  Needs several state changes to get here.
// 5      00  ||  Output pin connected using our allocator
// 6      00  ->  Downstream only connected
// 7      ||  ||  Undesirable but can be forced upon us.
// 8      <=  ||  Copy forced.  <=  -> is preferable
// 9      <=  ->  OK - forced to copy.
// 10     <-  <-  Transform in place (ideal)
// 11     ->  ->  Transform in place (ideal)
//
// The object of the exercise is to ensure that we finish up in states
// 10 or 11 whenever possible.  State 10 is only possible if the upstream
// filter has a R/W allocator (the AVI splitter notoriously
// doesn't) and state 11 is only possible if the downstream filter does
// offer an allocator.
//
// The transition table (entries marked * go via a reconnect)
//
// There are 8 possible transitions:
// A: Connect upstream to filter with R-O allocator that insists on using it.
// B: Connect upstream to filter with R-O allocator but chooses not to use it.
// C: Connect upstream to filter with R/W allocator and insists on using it.
// D: Connect upstream to filter with R/W allocator but chooses not to use it.
// E: Connect downstream to a filter that offers an allocator
// F: Connect downstream to a filter that does not offer an allocator
// G: disconnect upstream
// H: Disconnect downstream
//
//            A      B      C      D      E      F      G      H
//           ---------------------------------------------------------
// 00  00 1 | 3      3      2      2      6      5      .      .      |1  00  00
// <-  00 2 | .      .      .      .      *10/11 10     1      .      |2  <-  00
// <=  00 3 | .      .      .      .      *9/11  *7/8   1      .      |3  <=  00
// ||  00 4 | .      .      .      .      *8     *7     1      .      |4  ||  00
// 00  || 5 | 8      7      *10    7      .      .      .      1      |5  00  ||
// 00  -> 6 | 9      11     *10    11     .      .      .      1      |6  00  ->
// ||  || 7 | .      .      .      .      .      .      5      4      |7  ||  ||
// <=  || 8 | .      .      .      .      .      .      5      3      |8  <=  ||
// <=  -> 9 | .      .      .      .      .      .      6      3      |9  <=  ->
// <-  <- 10| .      .      .      .      .      .      *5/6   2      |10 <-  <-
// ->  -> 11| .      .      .      .      .      .      6      *2/3   |11 ->  ->
//           ---------------------------------------------------------
//            A      B      C      D      E      F      G      H
//
// All these states are accessible without requiring any filter to
// change its behaviour but not all transitions are accessible, for
// instance a transition from state 4 to anywhere other than
// state 8 requires that the upstream filter first offer a R-O allocator
// and then changes its mind and offer R/W.  This is NOT allowable - it
// leads to things like the output pin getting a R/W allocator from
// upstream and then the input pin being told it can only have a R-O one.
// Note that you CAN change (say) the upstream filter for a different one, but
// only as a disconnect / connect, not as a Reconnect.  (Exercise for
// the reader is to see how you get into state 4).
//
// The reconnection stuff goes as follows (some of the cases shown here as
// "no reconnect" may get one to finalise media type - an old story).
// If there is a reconnect where it says "no reconnect" here then the
// reconnection must not change the allocator choice.
//
// state 2: <- 00 transition E <- <- case C <- <- (no change)
//                                   case D -> <- and then to -> ->
//
// state 2: <- 00 transition F <- <- (no reconnect)
//
// state 3: <= 00 transition E <= -> case A <= -> (no change)
//                                   case B -> ->
//                transition F <= || case A <= || (no change)
//                                   case B || ||
//
// state 4: || 00 transition E || || case B -> || and then all cases to -> ->
//                           F || || case B || || (no change)
//
// state 5: 00 || transition A <= || (no reconnect)
//                           B || || (no reconnect)
//                           C <- || all cases     <- <-
//                           D || || (unfortunate, but upstream's choice)
//
// state 6: 00 -> transition A <= -> (no reconnect)
//                           B -> -> (no reconnect)
//                           C <- -> all cases <- <-
//                           D -> -> (no reconnect)
//
// state 10:<- <- transition G 00 <- case E 00 ->
//                                   case F 00 ||
//
// state 11:-> -> transition H -> 00 case A <= 00 (schizo)
//                                   case B <= 00
//                                   case C <- 00 (schizo)
//                                   case D <- 00
//
// The Rules:
// To sort out media types:
// The input is reconnected
//    if the input pin is connected and the output pin connects
// The output is reconnected
//    If the output pin is connected
//    and the input pin connects to a different media type
//
// To sort out allocators:
// The input is reconnected
//    if the output disconnects and the input was using a downstream allocator
// The output pin calls SetAllocator to pass on a new allocator
//    if the output is connected and
//       if the input disconnects and the output was using an upstream allocator
//       if the input acquires an allocator different from the output one
//          and that new allocator is not R-O
//
// Data is copied (i.e. call getbuffer and copy the data before transforming it)
//    if the two allocators are different.



// CHAINS of filters:
//
// We sit between two filters (call them A and Z).  We should finish up
// with the same allocator on both of our pins and that should be the
// same one that A and Z would have agreed on if we hadn't been in the
// way.  Furthermore, it should not matter how many in-place transforms
// are in the way.  Let B, C, D... be in-place transforms ("us").
// Here's how it goes:
//
// 1.
// A connects to B.  They agree on A's allocator.
//   A-a->B
//
// 2.
// B connects to C.  Same story. There is no point in a reconnect, but
// B will request an input reconnect anyway.
//   A-a->B-a->C
//
// 3.
// C connects to Z.
// C insists on using A's allocator, but compromises by requesting a reconnect.
// of C's input.
//   A-a->B-?->C-a->Z
//
// We now have pending reconnects on both A--->B and B--->C
//
// 4.
// The A--->B link is reconnected.
// A asks B for an allocator.  B sees that it has a downstream connection so
// asks its downstream input pin i.e. C's input pin for an allocator.  C sees
// that it too has a downstream connection so asks Z for an allocator.
//
// Even though Z's input pin is connected, it is being asked for an allocator.
// It could refuse, in which case the chain is done and will use A's allocator
// Alternatively, Z may supply one.  A chooses either Z's or A's own one.
// B's input pin gets NotifyAllocator called to tell it the decision and it
// propagates this downstream by calling ReceiveAllocator on its output pin
// which calls NotifyAllocator on the next input pin downstream etc.
// If the choice is Z then it goes:
//   A-z->B-a->C-a->Z
//   A-z->B-z->C-a->Z
//   A-z->B-z->C-z->Z
//
// And that's IT!!  Any further (essentially spurious) reconnects peter out
// with no change in the chain.

#include <streams.h>
#include <measure.h>
#include <transip.h>


// =================================================================
// Implements the CTransInPlaceFilter class
// =================================================================

CTransInPlaceFilter::CTransInPlaceFilter
   ( TCHAR     *pName,
     LPUNKNOWN  pUnk,
     REFCLSID   clsid,
     HRESULT   *phr,
     bool       bModifiesData
   )
   : CTransformFilter(pName, pUnk, clsid),
     m_bModifiesData(bModifiesData) {
#ifdef PERF
    RegisterPerfId();
#endif //  PERF

} // constructor

#ifdef UNICODE
CTransInPlaceFilter::CTransInPlaceFilter
   ( CHAR     *pName,
     LPUNKNOWN  pUnk,
     REFCLSID   clsid,
     HRESULT   *phr,
     bool       bModifiesData
   )
   : CTransformFilter(pName, pUnk, clsid),
     m_bModifiesData(bModifiesData) {
#ifdef PERF
    RegisterPerfId();
#endif //  PERF

} // constructor
#endif

// return a non-addrefed CBasePin * for the user to addref if he holds onto it
// for longer than his pointer to us. We create the pins dynamically when they
// are asked for rather than in the constructor. This is because we want to
// give the derived class an oppportunity to return different pin objects

// As soon as any pin is needed we create both (this is different from the
// usual transform filter) because enumerators, allocators etc are passed
// through from one pin to another and it becomes very painful if the other
// pin isn't there.  If we fail to create either pin we ensure we fail both.

CBasePin *
CTransInPlaceFilter::GetPin(int n) {
    HRESULT hr = S_OK;

    // Create an input pin if not already done

    if(m_pInput == NULL) {

        m_pInput = new CTransInPlaceInputPin(NAME("TransInPlace input pin")
            , this        // Owner filter
            , &hr         // Result code
            , L"Input"    // Pin name
            );

        // Constructor for CTransInPlaceInputPin can't fail
        ASSERT(SUCCEEDED(hr));
    }

    // Create an output pin if not already done

    if(m_pInput!=NULL && m_pOutput == NULL) {

        m_pOutput = new CTransInPlaceOutputPin(NAME("TransInPlace output pin")
            , this       // Owner filter
            , &hr        // Result code
            , L"Output"  // Pin name
            );

        // a failed return code should delete the object

        ASSERT(SUCCEEDED(hr));
        if(m_pOutput == NULL) {
            delete m_pInput;
            m_pInput = NULL;
        }
    }

    // Return the appropriate pin

    ASSERT(n>=0 && n<=1);
    if(n == 0) {
        return m_pInput;
    }
    else if(n==1) {
        return m_pOutput;
    }
    else {
        return NULL;
    }

} // GetPin



// dir is the direction of our pin.
// pReceivePin is the pin we are connecting to.
HRESULT CTransInPlaceFilter::CompleteConnect(PIN_DIRECTION dir,IPin *pReceivePin) {
    UNREFERENCED_PARAMETER(pReceivePin);
    ASSERT(m_pInput);
    ASSERT(m_pOutput);

    // if we are not part of a graph, then don't indirect the pointer
    // this probably prevents use of the filter without a filtergraph
    if(!m_pGraph) {
        return VFW_E_NOT_IN_GRAPH;
    }

    // Always reconnect the input to account for buffering changes
    //
    // Because we don't get to suggest a type on ReceiveConnection
    // we need another way of making sure the right type gets used.
    //
    // One way would be to have our EnumMediaTypes return our output
    // connection type first but more deterministic and simple is to
    // call ReconnectEx passing the type we want to reconnect with
    // via the base class ReconeectPin method.

    if(dir == PINDIR_OUTPUT) {
        if(m_pInput->IsConnected()) {
            return ReconnectPin(m_pInput, &m_pOutput->CurrentMediaType());
        }
        return NOERROR;
    }

    ASSERT(dir == PINDIR_INPUT);

    // Reconnect output if necessary

    if(m_pOutput->IsConnected()) {

        if(m_pInput->CurrentMediaType()
            != m_pOutput->CurrentMediaType()) {
            return ReconnectPin(m_pOutput, &m_pInput->CurrentMediaType());
        }
    }
    return NOERROR;

} // ComnpleteConnect


//
// DecideBufferSize
//
// Tell the output pin's allocator what size buffers we require.
// *pAlloc will be the allocator our output pin is using.
//

HRESULT CTransInPlaceFilter::DecideBufferSize
            ( IMemAllocator *pAlloc
            , ALLOCATOR_PROPERTIES *pProperties
            ) {
    ALLOCATOR_PROPERTIES Request, Actual;
    HRESULT hr;

    // If we are connected upstream, get his views
    if(m_pInput->IsConnected()) {
        // Get the input pin allocator, and get its size and count.
        // we don't care about his alignment and prefix.

        hr = InputPin()->PeekAllocator()->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 = 1;
        Request.cbBuffer = 1;
    }


    DbgLog((LOG_MEMORY,1,TEXT("Setting Allocator Requirements")));
    DbgLog((LOG_MEMORY,1,TEXT("Count %d, Size %d"),
        Request.cBuffers, Request.cbBuffer));

    // Pass the allocator requirements to our output side
    // but do a little sanity checking first or we'll just hit
    // asserts in the allocator.

    pProperties->cBuffers = Request.cBuffers;
    pProperties->cbBuffer = Request.cbBuffer;
    if(pProperties->cBuffers<=0) {pProperties->cBuffers = 1; 
    }
    if(pProperties->cbBuffer<=0) {pProperties->cbBuffer = 1; 
    }
    hr = pAlloc->SetProperties(pProperties, &Actual);

    if(FAILED(hr)) {
        return hr;
    }

    DbgLog((LOG_MEMORY,1,TEXT("Obtained Allocator Requirements")));
    DbgLog((LOG_MEMORY,1,TEXT("Count %d, Size %d, Alignment %d"),
        Actual.cBuffers, Actual.cbBuffer, Actual.cbAlign));

    // Make sure we got the right alignment and at least the minimum required

    if((Request.cBuffers > Actual.cBuffers)
        || (Request.cbBuffer > Actual.cbBuffer)
        || (Request.cbAlign  > Actual.cbAlign)) {
        return E_FAIL;
    }
    return NOERROR;

} // DecideBufferSize

//
// Copy
//
// return a pointer to an identical copy of pSample
IMediaSample * CTransInPlaceFilter::Copy(IMediaSample *pSource) {
    IMediaSample * pDest;

    HRESULT hr;
    REFERENCE_TIME tStart, tStop;
    const BOOL bTime = S_OK == pSource->GetTime(&tStart, &tStop);

    // this may block for an indeterminate amount of time
    hr = OutputPin()->PeekAllocator()->GetBuffer(&pDest
        , bTime ? &tStart : NULL
        , bTime ? &tStop : NULL
        , m_bSampleSkipped ? AM_GBF_PREVFRAMESKIPPED : 0);

    if(FAILED(hr)) {
        return NULL;
    }

    ASSERT(pDest);
    IMediaSample2 *pSample2;
    if(SUCCEEDED(pDest->QueryInterface(IID_IMediaSample2, (void **)&pSample2))) {
        HRESULT hr = pSample2->SetProperties(FIELD_OFFSET(AM_SAMPLE2_PROPERTIES, pbBuffer),
            (PBYTE)m_pInput->SampleProps());
        pSample2->Release();
        if(FAILED(hr)) {
            pDest->Release();
            return NULL;
        }
    }
    else {
        if(bTime) {
            pDest->SetTime(&tStart, &tStop);
        }

        if(S_OK == pSource->IsSyncPoint()) {
            pDest->SetSyncPoint(TRUE);
        }
        if(S_OK == pSource->IsDiscontinuity() || m_bSampleSkipped) {
            pDest->SetDiscontinuity(TRUE);
        }
        if(S_OK == pSource->IsPreroll()) {
            pDest->SetPreroll(TRUE);
        }

        // Copy the media type
        AM_MEDIA_TYPE *pMediaType;
        if(S_OK == pSource->GetMediaType(&pMediaType)) {
            pDest->SetMediaType(pMediaType);
            DeleteMediaType(pMediaType);
        }

    }

    m_bSampleSkipped = FALSE;

    // Copy the sample media times
    REFERENCE_TIME TimeStart, TimeEnd;
    if(pSource->GetMediaTime(&TimeStart,&TimeEnd) == NOERROR) {
        pDest->SetMediaTime(&TimeStart,&TimeEnd);
    }

    // Copy the actual data length and the actual data.
    {
        const long lDataLength = pSource->GetActualDataLength();
        pDest->SetActualDataLength(lDataLength);

        // Copy the sample data
        {
            BYTE *pSourceBuffer, *pDestBuffer;
            long lSourceSize  = pSource->GetSize();
            long lDestSize = pDest->GetSize();

            ASSERT(lDestSize >= lSourceSize && lDestSize >= lDataLength);

            pSource->GetPointer(&pSourceBuffer);
            pDest->GetPointer(&pDestBuffer);
            ASSERT(lDestSize == 0 || pSourceBuffer != NULL && pDestBuffer != NULL);

            CopyMemory((PVOID) pDestBuffer, (PVOID) pSourceBuffer, lDataLength);
        }
    }

    return pDest;

} // Copy


// override this to customize the transform process

HRESULT
CTransInPlaceFilter::Receive(IMediaSample *pSample) {
    /*  Check for other streams and pass them on */
    AM_SAMPLE2_PROPERTIES * const pProps = m_pInput->SampleProps();
    if(pProps->dwStreamId != AM_STREAM_MEDIA) {
        return m_pOutput->Deliver(pSample);
    }
    HRESULT hr;

    // Start timing the TransInPlace (if PERF is defined)
    MSR_START(m_idTransInPlace);

    if(UsingDifferentAllocators()) {

        // We have to copy the data.

        pSample = Copy(pSample);

        if(pSample==NULL) {
            MSR_STOP(m_idTransInPlace);
            return E_UNEXPECTED;
        }
    }

    // have the derived class transform the data
    hr = Transform(pSample);

    // Stop the clock and log it (if PERF is defined)
    MSR_STOP(m_idTransInPlace);

    if(FAILED(hr)) {
        DbgLog((LOG_TRACE, 1, TEXT("Error from TransInPlace")));
        if(UsingDifferentAllocators()) {
            pSample->Release();
        }
        return hr;
    }

    // the Transform() function can return S_FALSE to indicate that the
    // sample should not be delivered; we only deliver the sample if it's
    // really S_OK (same as NOERROR, of course.)
    if(hr == NOERROR) {
        hr = m_pOutput->Deliver(pSample);
    }
    else {
        //  But it would be an error to return this private workaround
        //  to the caller ...
        if(S_FALSE == hr) {
            // S_FALSE returned from Transform is a PRIVATE agreement
            // We should return NOERROR from Receive() in this cause because
            // returning S_FALSE from Receive() means that this is the end
            // of the stream and no more data should be sent.
            m_bSampleSkipped = TRUE;
            if(!m_bQualityChanged) {
                NotifyEvent(EC_QUALITY_CHANGE,0,0);
                m_bQualityChanged = TRUE;
            }
            hr = NOERROR;
        }
    }

    // release the output buffer. If the connected pin still needs it,
    // it will have addrefed it itself.
    if(UsingDifferentAllocators()) {
        pSample->Release();
    }

    return hr;

} // Receive



// =================================================================
// Implements the CTransInPlaceInputPin class
// =================================================================


// constructor

CTransInPlaceInputPin::CTransInPlaceInputPin
    ( TCHAR               *pObjectName
    , CTransInPlaceFilter *pFilter
    , HRESULT             *phr
    , LPCWSTR              pName
    )
    : CTransformInputPin(pObjectName,
                         pFilter,
                         phr,
                         pName)
    , m_bReadOnly(FALSE)
    , m_pTIPFilter(pFilter) {

    DbgLog((LOG_TRACE, 2
        , TEXT("CTransInPlaceInputPin::CTransInPlaceInputPin")));

} // constructor


// =================================================================
// Implements IMemInputPin interface
// =================================================================


// If the downstream filter has one then offer that (even if our own output
// pin is not using it yet.  If the upstream filter chooses it then we will
// tell our output pin to ReceiveAllocator).
// Else if our output pin is using an allocator then offer that.
//     ( This could mean offering the upstream filter his own allocator,
//       it could mean offerring our own
//     ) or it could mean offering the one from downstream
// Else fail to offer any allocator at all.

STDMETHODIMP CTransInPlaceInputPin::GetAllocator(IMemAllocator ** ppAllocator) {
    CheckPointer(ppAllocator,E_POINTER);
    ValidateReadWritePtr(ppAllocator,sizeof(IMemAllocator *));
    CAutoLock cObjectLock(m_pLock);

    HRESULT hr;

    if(m_pTIPFilter->m_pOutput->IsConnected()) {
        //  Store the allocator we got
        hr = m_pTIPFilter->OutputPin()->ConnectedIMemInputPin()
        ->GetAllocator(ppAllocator);
        if(SUCCEEDED(hr)) {
            m_pTIPFilter->OutputPin()->SetAllocator(*ppAllocator);
        }
    }
    else {
        //  Help upstream filter (eg TIP filter which is having to do a copy)
        //  by providing a temp allocator here - we'll never use
        //  this allocator because when our output is connected we'll
        //  reconnect this pin
        hr = CTransformInputPin::GetAllocator(ppAllocator);
    }
    return hr;

} // GetAllocator



/* Get told which allocator the upstream output pin is actually going to use */


STDMETHODIMP
CTransInPlaceInputPin::NotifyAllocator(
    IMemAllocator * pAllocator,
    BOOL bReadOnly) {
    HRESULT hr = S_OK;
    CheckPointer(pAllocator,E_POINTER);
    ValidateReadPtr(pAllocator,sizeof(IMemAllocator));

    CAutoLock cObjectLock(m_pLock);

    m_bReadOnly = bReadOnly;
    //  If we modify data then don't accept the allocator if it's
    //  the same as the output pin's allocator

    //  If our output is not connected just accept the allocator
    //  We're never going to use this allocator because when our
    //  output pin is connected we'll reconnect this pin
    if(!m_pTIPFilter->OutputPin()->IsConnected()) {
        return CTransformInputPin::NotifyAllocator(pAllocator, bReadOnly);
    }

    //  If the allocator is read-only and we're modifying data
    //  and the allocator is the same as the output pin's
    //  then reject
    if(bReadOnly && m_pTIPFilter->m_bModifiesData) {
        IMemAllocator *pOutputAllocator =
            m_pTIPFilter->OutputPin()->PeekAllocator();

        //  Make sure we have an output allocator
        if(pOutputAllocator == NULL) {
            hr = m_pTIPFilter->OutputPin()->ConnectedIMemInputPin()->
            GetAllocator(&pOutputAllocator);
            if(FAILED(hr)) {
                hr = CreateMemoryAllocator(&pOutputAllocator);
            }
            if(SUCCEEDED(hr)) {
                m_pTIPFilter->OutputPin()->SetAllocator(pOutputAllocator);
                pOutputAllocator->Release();
            }
        }
        if(pAllocator == pOutputAllocator) {
            hr = E_FAIL;
        }
        else if(SUCCEEDED(hr)) {
            //  Must copy so set the allocator properties on the output
            ALLOCATOR_PROPERTIES Props, Actual={0};
            hr = pAllocator->GetProperties(&Props);
            if(SUCCEEDED(hr)) {
                hr = pOutputAllocator->SetProperties(&Props, &Actual);
            }
            if(SUCCEEDED(hr)) {
                if((Props.cBuffers > Actual.cBuffers)
                    || (Props.cbBuffer > Actual.cbBuffer)
                    || (Props.cbAlign  > Actual.cbAlign)) {
                    hr =  E_FAIL;
                }
            }

            //  Set the allocator on the output pin
            if(SUCCEEDED(hr)) {
                hr = m_pTIPFilter->OutputPin()->ConnectedIMemInputPin()
                                 ->NotifyAllocator(pOutputAllocator, FALSE);
            }
        }
    }
    else {
        hr = m_pTIPFilter->OutputPin()->ConnectedIMemInputPin()
                         ->NotifyAllocator(pAllocator, bReadOnly);
        if(SUCCEEDED(hr)) {
            m_pTIPFilter->OutputPin()->SetAllocator(pAllocator);
        }
    }

    if(SUCCEEDED(hr)) {

        // It's possible that the old and the new are the same thing.
        // AddRef before release ensures that we don't unload it.
        pAllocator->AddRef();

        if(m_pAllocator != NULL)
            m_pAllocator->Release();

        m_pAllocator = pAllocator;    // We have an allocator for the input pin
    }

    return hr;

} // NotifyAllocator


// EnumMediaTypes
// - pass through to our downstream filter
STDMETHODIMP CTransInPlaceInputPin::EnumMediaTypes( IEnumMediaTypes **ppEnum ) {
    // Can only pass through if connected
    if(!m_pTIPFilter->m_pOutput->IsConnected())
        return VFW_E_NOT_CONNECTED;

    return m_pTIPFilter->m_pOutput->GetConnected()->EnumMediaTypes(ppEnum);

} // EnumMediaTypes


// CheckMediaType
// - agree to anything if not connected,
// otherwise pass through to the downstream filter.
// This assumes that the filter does not change the media type.

HRESULT CTransInPlaceInputPin::CheckMediaType(const CMediaType *pmt ) {
    HRESULT hr = m_pTIPFilter->CheckInputType(pmt);
    if(hr!=S_OK) return hr;

    if(m_pTIPFilter->m_pOutput->IsConnected())
        return m_pTIPFilter->m_pOutput->GetConnected()->QueryAccept(pmt);
    else
        return S_OK;

} // CheckMediaType


// If upstream asks us what our requirements are, we will try to ask downstream
// if that doesn't work, we'll just take the defaults.
STDMETHODIMP
CTransInPlaceInputPin::GetAllocatorRequirements(ALLOCATOR_PROPERTIES *pProps) {

    if(m_pTIPFilter->m_pOutput->IsConnected())
        return m_pTIPFilter->OutputPin()
        ->ConnectedIMemInputPin()->GetAllocatorRequirements(pProps);
    else
        return E_NOTIMPL;

} // GetAllocatorRequirements



// =================================================================
// Implements the CTransInPlaceOutputPin class
// =================================================================


// constructor

CTransInPlaceOutputPin::CTransInPlaceOutputPin(
    TCHAR *pObjectName,
    CTransInPlaceFilter *pFilter,
    HRESULT * phr,
    LPCWSTR pPinName)
    : CTransformOutputPin( pObjectName
                         , pFilter
                         , phr
                         , pPinName),
      m_pTIPFilter(pFilter) {

    DbgLog(( LOG_TRACE, 2
        , TEXT("CTransInPlaceOutputPin::CTransInPlaceOutputPin")));

} // constructor


// EnumMediaTypes
// - pass through to our upstream filter
STDMETHODIMP CTransInPlaceOutputPin::EnumMediaTypes( IEnumMediaTypes **ppEnum ) {
    // Can only pass through if connected.
    if(! m_pTIPFilter->m_pInput->IsConnected())
        return VFW_E_NOT_CONNECTED;

    return m_pTIPFilter->m_pInput->GetConnected()->EnumMediaTypes(ppEnum);

} // EnumMediaTypes



// CheckMediaType
// - agree to anything if not connected,
// otherwise pass through to the upstream filter.

HRESULT CTransInPlaceOutputPin::CheckMediaType(const CMediaType *pmt ) {
    // Don't accept any output pin type changes if we're copying
    // between allocators - it's too late to change the input
    // allocator size.
    if(m_pTIPFilter->UsingDifferentAllocators() && !m_pFilter->IsStopped()) {
        if(*pmt == m_mt) {
            return S_OK;
        }
        else {
            return VFW_E_TYPE_NOT_ACCEPTED;
        }
    }

    // Assumes the type does not change.  That's why we're calling
    // CheckINPUTType here on the OUTPUT pin.
    HRESULT hr = m_pTIPFilter->CheckInputType(pmt);
    if(hr!=S_OK) 
        return hr;

    if(m_pTIPFilter->m_pInput->IsConnected())
        return m_pTIPFilter->m_pInput->GetConnected()->QueryAccept(pmt);
    else
        return S_OK;

} //CheckMediaType


/* Save the allocator pointer in the output pin
*/
void
CTransInPlaceOutputPin::SetAllocator(IMemAllocator * pAllocator) {
    pAllocator->AddRef();
    if(m_pAllocator) {
        m_pAllocator->Release();
    }
    m_pAllocator = pAllocator;
} // receiveAllocator
