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.
2969 lines
113 KiB
2969 lines
113 KiB
// Copyright (c) 1996 - 1999 Microsoft Corporation. All Rights Reserved.
|
|
|
|
//
|
|
// ActiveMovie Line 21 Decoder Filter: Filter Interface
|
|
//
|
|
|
|
#include <streams.h>
|
|
#include <windowsx.h>
|
|
|
|
#ifdef FILTER_DLL
|
|
#include <initguid.h>
|
|
#endif /* FILTER_DLL */
|
|
|
|
#include <IL21Dec.h>
|
|
#include "L21DBase.h"
|
|
#include "L21DGDI.h"
|
|
#include "L21Decod.h"
|
|
#include "L21DFilt.h"
|
|
|
|
#include <mpconfig.h> // IMixerPinConfig at connection
|
|
|
|
|
|
//
|
|
// Setup Data
|
|
//
|
|
/* const */ AMOVIESETUP_MEDIATYPE sudLine21DecInType =
|
|
{
|
|
&MEDIATYPE_AUXLine21Data, // MajorType
|
|
&MEDIASUBTYPE_NULL // MinorType
|
|
} ;
|
|
|
|
/* const */ AMOVIESETUP_MEDIATYPE sudLine21DecOutType =
|
|
{
|
|
&MEDIATYPE_Video, // MajorType
|
|
&MEDIASUBTYPE_NULL // MinorType
|
|
} ;
|
|
|
|
/* const */ AMOVIESETUP_PIN psudLine21DecPins[] =
|
|
{
|
|
{ L"Input", // strName
|
|
FALSE, // bRendered
|
|
FALSE, // bOutput
|
|
FALSE, // bZero
|
|
FALSE, // bMany
|
|
&CLSID_NULL, // clsConnectsToFilter
|
|
L"Output", // strConnectsToPin
|
|
1, // nTypes
|
|
&sudLine21DecInType // lpTypes
|
|
},
|
|
{ L"Output", // strName
|
|
FALSE, // bRendered
|
|
TRUE, // bOutput
|
|
FALSE, // bZero
|
|
FALSE, // bMany
|
|
&CLSID_NULL, // clsConnectsToFilter
|
|
L"Input", // strConnectsToPin
|
|
1, // nTypes
|
|
&sudLine21DecOutType // lpTypes
|
|
}
|
|
} ;
|
|
|
|
const AMOVIESETUP_FILTER sudLine21Dec =
|
|
{
|
|
&CLSID_Line21Decoder, // clsID
|
|
L"Line 21 Decoder", // strName
|
|
MERIT_NORMAL, // dwMerit
|
|
2, // nPins
|
|
psudLine21DecPins, // lpPin
|
|
} ;
|
|
|
|
// Nothing to say about the output pin
|
|
|
|
#ifdef FILTER_DLL
|
|
|
|
// list of class ids and creator functions for class factory
|
|
CFactoryTemplate g_Templates[] =
|
|
{
|
|
{ L"Line 21 Decoder",
|
|
&CLSID_Line21Decoder,
|
|
CLine21DecFilter::CreateInstance,
|
|
NULL,
|
|
&sudLine21Dec
|
|
}
|
|
} ;
|
|
|
|
int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]);
|
|
|
|
//
|
|
// Exported entry points for registration and unregistration (in this case
|
|
// they only call through to default implmentations).
|
|
//
|
|
HRESULT DllRegisterServer()
|
|
{
|
|
return AMovieDllRegisterServer2(TRUE) ;
|
|
}
|
|
|
|
HRESULT DllUnregisterServer()
|
|
{
|
|
return AMovieDllRegisterServer2(FALSE) ;
|
|
}
|
|
|
|
#endif // FILTER_DLL
|
|
|
|
|
|
|
|
#ifndef UNALIGNED
|
|
#define UNALIGNED // __unaligned
|
|
#endif // UNALIGNED
|
|
|
|
|
|
//
|
|
// CLine21DecFilter class implementation
|
|
//
|
|
|
|
// static member init at file scope
|
|
CMessageWindow * CLine21DecFilter::m_pMsgWnd = NULL ;
|
|
|
|
//
|
|
// Constructor
|
|
//
|
|
CLine21DecFilter::CLine21DecFilter(TCHAR *pName, LPUNKNOWN pUnk, HRESULT *phr)
|
|
: CTransformFilter(pName, pUnk, CLSID_Line21Decoder),
|
|
|
|
m_pbOutBuffer(NULL),
|
|
m_L21Dec(),
|
|
m_eSubTypeIDIn(AM_L21_CCSUBTYPEID_Invalid),
|
|
m_eGOP_CCType(GOP_CCTYPE_Unknown),
|
|
m_rtTimePerSample((LONGLONG) 166833), // 333667),
|
|
m_rtStart((LONGLONG) 0),
|
|
m_rtStop((LONGLONG) 0),
|
|
m_rtLastSample((LONGLONG) 0),
|
|
m_llMediaStart((LONGLONG) 0),
|
|
m_llMediaStop((LONGLONG) 0),
|
|
m_pviDefFmt(NULL),
|
|
m_dwDefFmtSize(0),
|
|
m_bMustOutput(FALSE),
|
|
m_bDiscontLast(FALSE),
|
|
m_uTimerID(0),
|
|
m_uTimerCount(0),
|
|
m_bTimerClearReqd(FALSE),
|
|
m_pPinDown(NULL),
|
|
m_bBlendingState(TRUE), // so that we read it once at least
|
|
m_dwBlendParam(1000) // invalid by default -- valid value on setting to FALSE
|
|
{
|
|
CAutoLock Lock(&m_csFilter) ;
|
|
|
|
DbgLog((LOG_TRACE, 1,
|
|
TEXT("CLine21DecFilter::CLine21DecFilter() -- Instantiating Line 21 Decoder filter"))) ;
|
|
|
|
ASSERT(pName) ;
|
|
ASSERT(phr) ;
|
|
|
|
//
|
|
// Create the message window and make sure that it has been created right; else error out
|
|
//
|
|
if (NULL == m_pMsgWnd)
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("Message handler window has to be created."))) ;
|
|
m_pMsgWnd = new CMessageWindow ;
|
|
if (NULL == m_pMsgWnd || NULL == m_pMsgWnd->GetHandle())
|
|
{
|
|
DbgLog((LOG_ERROR, 0, TEXT("Timer message handler window creation failed. Can't go ahead."))) ;
|
|
ASSERT(phr) ;
|
|
*phr = E_UNEXPECTED ; // what else to say!!
|
|
return ;
|
|
}
|
|
}
|
|
m_pMsgWnd->AddCount() ;
|
|
|
|
#ifdef PERF
|
|
#pragma message("Building for PERF measurements")
|
|
m_idDelvWait = MSR_REGISTER(TEXT("L21DPerf - Wait on Deliver")) ;
|
|
#endif // PERF
|
|
}
|
|
|
|
|
|
//
|
|
// Destructor
|
|
//
|
|
CLine21DecFilter::~CLine21DecFilter()
|
|
{
|
|
CAutoLock Lock(&m_csFilter) ;
|
|
|
|
DbgLog((LOG_TRACE, 1,
|
|
TEXT("CLine21DecFilter::~CLine21DecFilter() -- Destructing Line 21 Decoder filter"))) ;
|
|
|
|
// In case the downstream pin interface wasn't released...
|
|
if (m_pPinDown)
|
|
{
|
|
m_pPinDown->Release() ;
|
|
m_pPinDown = NULL ;
|
|
}
|
|
|
|
// Release all the buffers allocated
|
|
if (m_pviDefFmt)
|
|
{
|
|
delete m_pviDefFmt ;
|
|
m_pviDefFmt = NULL ;
|
|
}
|
|
|
|
ASSERT(m_pMsgWnd) ;
|
|
if (m_pMsgWnd && m_pMsgWnd->ReleaseCount() <= 0) // -ve means bad!!!
|
|
{
|
|
delete m_pMsgWnd ;
|
|
m_pMsgWnd = NULL ;
|
|
}
|
|
|
|
// Make sure we are not holding onto any DDraw surfaces (should be
|
|
// released during disconnect)
|
|
DbgLog((LOG_TRACE, 1, TEXT("* Destroying the Line 21 Decoder filter *"))) ;
|
|
}
|
|
|
|
|
|
//
|
|
// NonDelegatingQueryInterface
|
|
//
|
|
STDMETHODIMP CLine21DecFilter::NonDelegatingQueryInterface(REFIID riid, void **ppv)
|
|
{
|
|
if (ppv)
|
|
*ppv = NULL ;
|
|
|
|
DbgLog((LOG_TRACE, 6, TEXT("somebody's querying my interface"))) ;
|
|
if (IID_IAMLine21Decoder == riid)
|
|
{
|
|
return GetInterface((IAMLine21Decoder *) this, ppv) ;
|
|
}
|
|
return CTransformFilter::NonDelegatingQueryInterface(riid, ppv) ;
|
|
}
|
|
|
|
|
|
//
|
|
// CreateInstance: Goes in the factory template table to create new instances
|
|
//
|
|
CUnknown * CLine21DecFilter::CreateInstance(LPUNKNOWN pUnk, HRESULT * phr)
|
|
{
|
|
return new CLine21DecFilter(TEXT("Line 21 Decoder filter"), pUnk, phr) ;
|
|
}
|
|
|
|
|
|
STDMETHODIMP CLine21DecFilter::GetDecoderLevel(AM_LINE21_CCLEVEL *lpLevel)
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("CLine21DecFilter::GetDecoderLevel(0x%lx)"), lpLevel)) ;
|
|
// CAutoLock Lock(&m_csFilter) ;
|
|
|
|
if (IsBadWritePtr(lpLevel, sizeof(AM_LINE21_CCLEVEL)))
|
|
return E_INVALIDARG ;
|
|
|
|
*lpLevel = m_L21Dec.GetDecoderLevel() ;
|
|
return NOERROR ;
|
|
}
|
|
|
|
STDMETHODIMP CLine21DecFilter::GetCurrentService(AM_LINE21_CCSERVICE *lpService)
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("CLine21DecFilter::GetCurrentService(0x%lx)"), lpService)) ;
|
|
// CAutoLock Lock(&m_csFilter) ;
|
|
|
|
if (IsBadWritePtr(lpService, sizeof(AM_LINE21_CCSERVICE)))
|
|
return E_INVALIDARG ;
|
|
|
|
*lpService = m_L21Dec.GetCurrentService() ;
|
|
return NOERROR ;
|
|
}
|
|
|
|
STDMETHODIMP CLine21DecFilter::SetCurrentService(AM_LINE21_CCSERVICE Service)
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("CLine21DecFilter::SetCurrentService(%lu)"), Service)) ;
|
|
CAutoLock Lock(&m_csFilter) ;
|
|
|
|
if (Service < AM_L21_CCSERVICE_None || Service > AM_L21_CCSERVICE_XDS)
|
|
return E_INVALIDARG ;
|
|
|
|
if (Service >= AM_L21_CCSERVICE_Text1) // we don't have support for Text1/2 or XDS now.
|
|
return E_NOTIMPL ;
|
|
|
|
if (m_L21Dec.SetCurrentService(Service)) // if we must refresh output
|
|
m_bMustOutput = TRUE ; // then flag it here.
|
|
|
|
return NOERROR ;
|
|
}
|
|
|
|
STDMETHODIMP CLine21DecFilter::GetServiceState(AM_LINE21_CCSTATE *lpState)
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("CLine21DecFilter::GetServiceState(0x%lx)"), lpState)) ;
|
|
// CAutoLock Lock(&m_csFilter) ;
|
|
|
|
if (IsBadWritePtr(lpState, sizeof(AM_LINE21_CCSTATE)))
|
|
return E_INVALIDARG ;
|
|
|
|
*lpState = m_L21Dec.GetServiceState() ;
|
|
return NOERROR ;
|
|
}
|
|
|
|
STDMETHODIMP CLine21DecFilter::SetServiceState(AM_LINE21_CCSTATE State)
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("CLine21DecFilter::SetServiceState(%lu)"), State)) ;
|
|
CAutoLock Lock(&m_csFilter) ;
|
|
|
|
if (State < AM_L21_CCSTATE_Off || State > AM_L21_CCSTATE_On)
|
|
return E_INVALIDARG ;
|
|
|
|
if (m_L21Dec.SetServiceState(State)) // if we must refresh output
|
|
m_bMustOutput = TRUE ; // then flag it here.
|
|
|
|
return NOERROR ;
|
|
}
|
|
|
|
STDMETHODIMP CLine21DecFilter::GetOutputFormat(LPBITMAPINFOHEADER lpbmih)
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("CLine21DecFilter::GetOutputFormat(0x%lx)"), lpbmih)) ;
|
|
// CAutoLock Lock(&m_csFilter) ;
|
|
return m_L21Dec.GetOutputFormat(lpbmih) ;
|
|
}
|
|
|
|
STDMETHODIMP CLine21DecFilter::SetOutputFormat(LPBITMAPINFO lpbmi)
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("CLine21DecFilter::SetOutputFormat(0x%lx)"), lpbmi)) ;
|
|
// CAutoLock Lock(&m_csFilter) ;
|
|
|
|
return E_NOTIMPL ; // for now, until we do it properly
|
|
|
|
#if 0
|
|
m_L21Dec.DeleteOutputDC() ; // delete current DIB section
|
|
|
|
HRESULT hr = m_L21Dec.SetOutputOutFormat(lpbmi) ;
|
|
if (FAILED(hr))
|
|
return hr ;
|
|
|
|
// if the format details changed in any way, we should get the default
|
|
// format data again (just to make sure).
|
|
hr = GetDefaultFormatInfo() ;
|
|
|
|
//
|
|
// ONLY if we are running/paused, we need to create internal DIB section
|
|
//
|
|
if (m_State != State_Stopped)
|
|
{
|
|
if (! m_L21Dec.CreateOutputDC() ) // new DIBSection creation failed
|
|
{
|
|
DbgLog((LOG_ERROR, 0, TEXT("CreateOutputDC() failed!!!"))) ;
|
|
return E_UNEXPECTED ;
|
|
}
|
|
}
|
|
|
|
return hr ;
|
|
#endif // #if 0
|
|
}
|
|
|
|
STDMETHODIMP CLine21DecFilter::GetBackgroundColor(DWORD *pdwPhysColor)
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("CLine21DecFilter::GetBackgroundColor(0x%lx)"), pdwPhysColor)) ;
|
|
// CAutoLock Lock(&m_csFilter) ;
|
|
|
|
if (IsBadWritePtr(pdwPhysColor, sizeof(DWORD)))
|
|
return E_INVALIDARG ;
|
|
|
|
m_L21Dec.GetBackgroundColor(pdwPhysColor) ;
|
|
return NOERROR ;
|
|
}
|
|
|
|
STDMETHODIMP CLine21DecFilter::SetBackgroundColor(DWORD dwPhysColor)
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("CLine21DecFilter::SetBackgroundColor(0x%lx)"), dwPhysColor)) ;
|
|
CAutoLock Lock(&m_csFilter) ;
|
|
|
|
if (m_L21Dec.SetBackgroundColor(dwPhysColor)) // color key has really changed
|
|
{
|
|
// refill the output buffer only if we are not in stopped state
|
|
if (State_Stopped != m_State)
|
|
m_L21Dec.FillOutputBuffer() ;
|
|
}
|
|
|
|
return NOERROR ;
|
|
}
|
|
|
|
STDMETHODIMP CLine21DecFilter::GetRedrawAlways(LPBOOL lpbOption)
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("CLine21DecFilter::GetRedrawAlways(0x%lx)"), lpbOption)) ;
|
|
CAutoLock Lock(&m_csFilter) ;
|
|
|
|
if (IsBadWritePtr(lpbOption, sizeof(BOOL)))
|
|
return E_INVALIDARG ;
|
|
*lpbOption = m_L21Dec.GetRedrawAlways() ;
|
|
return NOERROR ;
|
|
}
|
|
|
|
STDMETHODIMP CLine21DecFilter::SetRedrawAlways(BOOL bOption)
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("CLine21DecFilter::SetRedrawAlways(%lu)"), bOption)) ;
|
|
CAutoLock Lock(&m_csFilter) ;
|
|
|
|
m_L21Dec.SetRedrawAlways(bOption) ;
|
|
return NOERROR ;
|
|
}
|
|
|
|
STDMETHODIMP CLine21DecFilter::GetDrawBackgroundMode(AM_LINE21_DRAWBGMODE *lpMode)
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("CLine21DecFilter::GetDrawBackgroundMode(0x%lx)"), lpMode)) ;
|
|
CAutoLock Lock(&m_csFilter) ;
|
|
|
|
if (IsBadWritePtr(lpMode, sizeof(AM_LINE21_DRAWBGMODE)))
|
|
return E_INVALIDARG ;
|
|
|
|
*lpMode = m_L21Dec.GetDrawBackgroundMode() ;
|
|
return NOERROR ;
|
|
}
|
|
|
|
STDMETHODIMP CLine21DecFilter::SetDrawBackgroundMode(AM_LINE21_DRAWBGMODE Mode)
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("CLine21DecFilter::SetDrawBackgroundMode(%lu)"), Mode)) ;
|
|
CAutoLock Lock(&m_csFilter) ;
|
|
|
|
if (Mode < AM_L21_DRAWBGMODE_Opaque || Mode > AM_L21_DRAWBGMODE_Transparent)
|
|
return E_INVALIDARG ;
|
|
m_L21Dec.SetDrawBackgroundMode(Mode) ;
|
|
return NOERROR ;
|
|
}
|
|
|
|
|
|
//
|
|
// VerifyGOPUDPacketData: Private helper method to verify GOP user data
|
|
// packet integrity.
|
|
//
|
|
BOOL CLine21DecFilter::VerifyGOPUDPacketData(PAM_L21_GOPUD_PACKET pGOPUDPacket)
|
|
{
|
|
return (AM_L21_GOPUD_HDR_STARTCODE == GETGOPUD_L21STARTCODE(pGOPUDPacket->Header) && // valid start code
|
|
AM_L21_GOPUD_HDR_INDICATOR == GETGOPUD_L21INDICATOR(pGOPUDPacket->Header) && // Line21 indicator
|
|
AM_L21_GOPUD_HDR_RESERVED == GETGOPUD_L21RESERVED(pGOPUDPacket->Header) && // reserved bits
|
|
GETGOPUD_NUMELEMENTS(pGOPUDPacket) > 0) ; // +ve # elements
|
|
}
|
|
|
|
|
|
//
|
|
// VerifyATSCUDPacketData: Private helper method to verify ATSC user data
|
|
// packet integrity.
|
|
//
|
|
BOOL CLine21DecFilter::VerifyATSCUDPacketData(PAM_L21_ATSCUD_PACKET pATSCUDPacket)
|
|
{
|
|
if (AM_L21_ATSCUD_HDR_STARTCODE != GETATSCUD_STARTCODE(pATSCUDPacket->Header) || // invalid start code
|
|
AM_L21_ATSCUD_HDR_IDENTIFIER != GETATSCUD_IDENTIFIER(pATSCUDPacket->Header)) // not ATSC Identifier
|
|
return FALSE ;
|
|
|
|
if (! ISATSCUD_TYPE_EIA(pATSCUDPacket) ) // not EIA-type CC
|
|
return FALSE ;
|
|
|
|
// Either EM or valid CC data is acceptable
|
|
return (ISATSCUD_EM_DATA(pATSCUDPacket) || // EM data type OR
|
|
(ISATSCUD_CC_DATA(pATSCUDPacket) && // CC data type AND
|
|
GETATSCUD_NUMELEMENTS(pATSCUDPacket) > 0)) ; // +ve # CC elements
|
|
}
|
|
|
|
|
|
//
|
|
// DetectGOPPacketDataType: Private helper method to detect if GOP user data
|
|
// packet is from a DVD disc, ATSC stream or others.
|
|
//
|
|
GOPPACKET_CCTYPE CLine21DecFilter::DetectGOPPacketDataType(BYTE *pGOPPacket)
|
|
{
|
|
if (VerifyGOPUDPacketData((PAM_L21_GOPUD_PACKET) pGOPPacket))
|
|
return GOP_CCTYPE_DVD ;
|
|
else if (VerifyATSCUDPacketData((PAM_L21_ATSCUD_PACKET) pGOPPacket))
|
|
return GOP_CCTYPE_ATSC ;
|
|
else if (IsFillerPacket(pGOPPacket))
|
|
return GOP_CCTYPE_None ; // not a valid packet -- just ignore it
|
|
else
|
|
return GOP_CCTYPE_Unknown ; // it's some unknown format of CC packet
|
|
}
|
|
|
|
|
|
//
|
|
// IsFillerPacket: Private helper method to check if the packet (at least header)
|
|
// contains only 0 bytes, which means it's a filler.
|
|
//
|
|
BOOL CLine21DecFilter::IsFillerPacket(BYTE *pGOPPacket)
|
|
{
|
|
DWORD dwStartCode = ((DWORD)(pGOPPacket[0]) << 24 | \
|
|
(DWORD)(pGOPPacket[1]) << 16 | \
|
|
(DWORD)(pGOPPacket[2]) << 8 | \
|
|
(DWORD)(pGOPPacket[3])) ;
|
|
|
|
// If first 4 bytes of packet is NOT the start code (0x1B2) then it's a filler
|
|
return (AM_L21_GOPUD_HDR_STARTCODE != dwStartCode) ;
|
|
}
|
|
|
|
|
|
//
|
|
// The Timer Story:
|
|
// We needed 2 timers -- one to fire every 33 mSec for completing scrolling and
|
|
// another to fire after 3 Sec to time out CC in byte pair mode. But we use
|
|
// the "this" pointer (to the CLine21DecFilter object) as the uEventID in the
|
|
// SetTimer() call so that we can access the filter object's properties in
|
|
// TimerProc (which is essential).
|
|
// We can't create two different timers (with diff IDs) using the same event ID.
|
|
// So we settled for one timer that fires every 30 mSec (close to 33 mSec). We
|
|
// can set up the timer for
|
|
// (a) scrolling only (DVD) or (b) scrolling and CC erasing (TV).
|
|
// We maintain a flag to differentiate between these two reasons. If we are in the
|
|
// middle scrolling, we always do that. Otherwise if we opted for (a), we just exit
|
|
// TimerProc(); in case (b), we just increment a counter, then see if it's >= 100
|
|
// as well as the last output sample we have sent down was a NON-clear one then we
|
|
// create a sample to sent down and also turn off the timer.
|
|
//
|
|
void CALLBACK CLine21DecFilter::TimerProc(HWND hWnd, UINT uMsg, UINT_PTR uID, DWORD dwTime)
|
|
{
|
|
DbgLog((LOG_TRACE, 1, TEXT("CLine21DecFilter::TimerProc(0x%p, 0x%lx, %lu, 0x%lx)"),
|
|
(void*)hWnd, uMsg, uID, dwTime)) ;
|
|
|
|
//
|
|
// Verify that we are not handling some invalid messages
|
|
//
|
|
if (uMsg != WM_TIMER)
|
|
{
|
|
DbgLog((LOG_ERROR, 0, TEXT("WARNING: Who sent us this (%lu) message??"), uMsg)) ;
|
|
return ;
|
|
}
|
|
|
|
// We specified "this" pointer as the ID to SetTimer() so that we get it here
|
|
CLine21DecFilter *pL21Dec = (CLine21DecFilter *) uID ;
|
|
|
|
CAutoLock Lock2(&(pL21Dec->m_csFilter)) ; // don't mess until we are done
|
|
CAutoLock Lock1(&(pL21Dec->m_csReceive)) ; // don't receive next sample until we are done
|
|
|
|
if (0 == pL21Dec->m_uTimerID) // timer has been killed in between
|
|
{
|
|
// that means we are still rolling; just skip the rest -- it's OK
|
|
DbgLog((LOG_TRACE, 1, TEXT("INFO: Timer killed before TimerProc() kicked in"))) ;
|
|
return ;
|
|
}
|
|
|
|
BOOL bClearCC = FALSE ; // assume we are not doing that here
|
|
|
|
// First check if we are scrolling
|
|
if (! pL21Dec->m_L21Dec.IsScrolling() )
|
|
{
|
|
DbgLog((LOG_TRACE, 3, TEXT("TimerProc(): Not scrolling now"))) ;
|
|
if (pL21Dec->m_bTimerClearReqd) // timer is serving dual purpose
|
|
{
|
|
pL21Dec->m_uTimerCount++ ;
|
|
if (pL21Dec->m_uTimerCount < 100) // 100 means 3 Secs (with 30 mSec timer)
|
|
{
|
|
DbgLog((LOG_TRACE, 3, TEXT("TimerProc(): Timer reqd to erase CC. But not yet time..."))) ;
|
|
return ;
|
|
}
|
|
else // time to erase old CC
|
|
{
|
|
if (pL21Dec->m_L21Dec.IsOutDIBClear()) // last sample sent down was clear. Turn off timer and get out
|
|
{
|
|
DbgLog((LOG_TRACE, 1, TEXT("TimerProc(): Clear sample already sent out. Skip the rest."))) ;
|
|
pL21Dec->FreeTimer() ;
|
|
return ;
|
|
}
|
|
else // clear old CC now
|
|
{
|
|
DbgLog((LOG_TRACE, 1, TEXT("TimerProc(): Old CC needs to be cleared."))) ;
|
|
// pL21Dec->m_L21Dec.MakeClearSample() ;
|
|
pL21Dec->m_L21Dec.FlushInternalStates() ;
|
|
bClearCC = TRUE ; // set a flag to test at the end of this function
|
|
}
|
|
}
|
|
}
|
|
else // not scrolling and timer not for clearing old CC. Get out of here.
|
|
{
|
|
DbgLog((LOG_TRACE, 3, TEXT("TimerProc(): Timer not reqd for erasing CC"))) ;
|
|
return ;
|
|
}
|
|
}
|
|
else // we are scrolling!!!
|
|
{
|
|
DbgLog((LOG_TRACE, 3, TEXT("TimerProc(): We are scrolling now. Deliver next sample."))) ;
|
|
pL21Dec->m_uTimerCount = 0 ; // non-clear sample is being sent now. Wait for 3 secs more
|
|
}
|
|
|
|
//
|
|
// Looks like we got to send a sample down
|
|
//
|
|
DbgLog((LOG_TRACE, 1, TEXT("*** Preparing output sample in TimerProc() ***"))) ;
|
|
HRESULT hr ;
|
|
IMediaSample *pOut ;
|
|
hr = pL21Dec->m_pOutput->GetDeliveryBuffer(&pOut, NULL, NULL,
|
|
pL21Dec->m_bSampleSkipped ? AM_GBF_PREVFRAMESKIPPED : 0) ;
|
|
if (FAILED(hr))
|
|
{
|
|
DbgLog((LOG_ERROR, 0, TEXT("WARNING: GetDeliveryBuffer() on out pin failed (Error 0x%lx)"), hr)) ;
|
|
return ;
|
|
}
|
|
pL21Dec->Transform(NULL, pOut) ; // check if output buffer changed, use pIn=NULL
|
|
|
|
// Copy the reqd scan lines from internal output DIBSection for next output sample
|
|
pL21Dec->m_L21Dec.CopyOutputDIB() ;
|
|
|
|
// Set time stamps etc. and increment the timestamps by their current difference
|
|
REFERENCE_TIME rtDiff = pL21Dec->m_rtStop - pL21Dec->m_rtStart ;
|
|
pL21Dec->m_rtStart = pL21Dec->m_rtStop ;
|
|
pL21Dec->m_rtStop = pL21Dec->m_rtStop + rtDiff ;
|
|
hr = pOut->SetTime(&(pL21Dec->m_rtStart), &(pL21Dec->m_rtStop)) ;
|
|
ASSERT(NOERROR == hr) ;
|
|
pOut->SetSyncPoint(FALSE) ;
|
|
pOut->SetDiscontinuity(pL21Dec->m_bSampleSkipped) ;
|
|
pL21Dec->m_bSampleSkipped = FALSE ;
|
|
|
|
// Now deliver the next output sample
|
|
pL21Dec->SetBlendingState(TRUE) ; // turn on blending first
|
|
// Can't call MSR_xxx inside a static member function.
|
|
// MSR_START(m_idDelvWait) ; // delivering output sample
|
|
hr = pL21Dec->m_pOutput->Deliver(pOut) ;
|
|
// MSR_STOP(m_idDelvWait) ; // done delivering output sample
|
|
pOut->Release() ;
|
|
DbgLog((LOG_TRACE, 1, TEXT("TimerProc(): Deliver() returned 0x%lx"), hr)) ;
|
|
pL21Dec->m_rtLastSample = pL21Dec->m_rtStart ; // remember this
|
|
|
|
if (SUCCEEDED(hr)) // if out sample was delivered right
|
|
{
|
|
DbgLog((LOG_TRACE, 1, TEXT("*** Delivered %s output sample in TimerProc() (for time %s -> %s) ***"),
|
|
bClearCC ? "clear" : "non-clear",
|
|
(LPCTSTR)CDisp(pL21Dec->m_rtStart), (LPCTSTR)CDisp(pL21Dec->m_rtStop))) ;
|
|
if (bClearCC) // if a clear sample was sent down,...
|
|
{
|
|
pL21Dec->FreeTimer() ; // ...we don't need a timer any more.
|
|
pL21Dec->SetBlendingState(FALSE) ; // turn off blending if clear sample delivered above
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void CLine21DecFilter::SetupTimerIfReqd(BOOL bTimerClearReqd)
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("CLine21DecFilter::SetupTimerIfReqd(%s)"),
|
|
bTimerClearReqd ? "TRUE" : "FALSE")) ;
|
|
// CAutoLock Lock(&m_csFilter) ;
|
|
|
|
// If we are running AND either we need CC ime-out or we are scrolling
|
|
if ( State_Running == m_State &&
|
|
( bTimerClearReqd ||
|
|
m_L21Dec.IsScrolling()) )
|
|
{
|
|
// A callback to TimerProc (a static member fn) every 30 mSec.
|
|
// The "this" pointer is passed in to identify decoder instance.
|
|
// Receiving message window will call the TimerProc to do the work.
|
|
m_uTimerID = SetTimer(m_pMsgWnd->GetHandle(), (DWORD_PTR)(LPVOID)this, 30, NULL /* TimerProc */) ;
|
|
if (0 == m_uTimerID)
|
|
{
|
|
DbgLog((LOG_ERROR, 0, TEXT("WARNING: SetTimer(0x%p, 0x%p, ...) failed (Error %ld)"),
|
|
(LPVOID)m_pMsgWnd->GetHandle(), (LPVOID)this, GetLastError())) ;
|
|
ASSERT(FALSE) ;
|
|
}
|
|
else
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("SetTimer(0x%lx, ..) created timer 0x%x (%s CC Timeout)"),
|
|
m_pMsgWnd->GetHandle(), m_uTimerID, bTimerClearReqd ? "Need" : "No")) ;
|
|
m_bTimerClearReqd = bTimerClearReqd ;
|
|
m_uTimerCount = 0 ; // init timer count here
|
|
}
|
|
}
|
|
else
|
|
DbgLog((LOG_TRACE, 5, TEXT("Timer NOT started as we are not running/scrolling/NoCC-timeout"))) ;
|
|
}
|
|
|
|
|
|
void CLine21DecFilter::FreeTimer(void)
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("CLine21DecFilter::FreeTimer()"))) ;
|
|
// CAutoLock Lock(&m_csFilter) ;
|
|
|
|
// If we have a valid timer then release it here
|
|
if (m_uTimerID)
|
|
{
|
|
if (0 == KillTimer(m_pMsgWnd->GetHandle(), m_uTimerID))
|
|
{
|
|
DbgLog((LOG_ERROR, 0, TEXT("ERROR: KillTimer(0x%lx, 0x%x) failed (Error %ld)"),
|
|
m_pMsgWnd->GetHandle(), m_uTimerID, GetLastError())) ;
|
|
ASSERT(FALSE) ; // just so that we know
|
|
}
|
|
else
|
|
{
|
|
DbgLog((LOG_TRACE, 3, TEXT("TIMER (Id 0x%x) killed"), m_uTimerID)) ;
|
|
m_uTimerID = 0 ;
|
|
m_bTimerClearReqd = FALSE ; // reset flag and...
|
|
m_uTimerCount = 0 ; // counter, just for safety
|
|
}
|
|
}
|
|
else
|
|
DbgLog((LOG_TRACE, 5, TEXT("Timer NOT set -- Timer ID=0x%x"), m_uTimerID)) ;
|
|
}
|
|
|
|
|
|
BOOL CLine21DecFilter::IsValidFormat(BYTE *pbFormat)
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("CLine21DecFilter::IsValidFormat(0x%lx)"), pbFormat)) ;
|
|
// CAutoLock Lock(&m_csFilter) ; -- can't do that as it may cause deadlock
|
|
|
|
if (NULL == pbFormat)
|
|
return FALSE ;
|
|
|
|
BITMAPINFOHEADER *lpBMIH = HEADER(pbFormat) ;
|
|
if (! ( 8 == lpBMIH->biBitCount || 16 == lpBMIH->biBitCount ||
|
|
24 == lpBMIH->biBitCount || 32 == lpBMIH->biBitCount) ) // bad bitdepth
|
|
return FALSE ;
|
|
if ( !(BI_RGB == lpBMIH->biCompression || BI_BITFIELDS == lpBMIH->biCompression) ) // bad compression
|
|
return FALSE ;
|
|
if (DIBSIZE(*lpBMIH) != lpBMIH->biSizeImage) // invalid dimensions/size
|
|
return FALSE ;
|
|
|
|
return TRUE ; // hopefully it's a valid video info header
|
|
}
|
|
|
|
|
|
void CLine21DecFilter::SetBlendingState(BOOL bState)
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("CLine21DecFilter::SetBlendingState(%s)"),
|
|
bState ? "TRUE" : "FALSE")) ;
|
|
// CAutoLock Lock(&m_csFilter) ;
|
|
|
|
if (m_bBlendingState == bState) // nothing to change
|
|
return ;
|
|
|
|
if (NULL == m_pPinDown)
|
|
{
|
|
DbgLog((LOG_ERROR, 1, TEXT("WARNING: Downstream pin interface is not available"))) ;
|
|
return ;
|
|
}
|
|
|
|
IMixerPinConfig *pMPC ;
|
|
HRESULT hr = m_pPinDown->QueryInterface(IID_IMixerPinConfig, (LPVOID *)&pMPC) ;
|
|
if (FAILED(hr) || NULL == pMPC)
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("IMixerPinConfig not available on pin %s"),
|
|
(LPCTSTR) CDisp(m_pPinDown))) ;
|
|
return ;
|
|
}
|
|
|
|
if (bState) // turn it on -- CC needs to be mixed
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("Calling SetBlendingParameter(%lu)"), m_dwBlendParam)) ;
|
|
ASSERT( m_dwBlendParam <= 255) ;
|
|
hr = pMPC->SetBlendingParameter(m_dwBlendParam) ;
|
|
ASSERT(SUCCEEDED(hr)) ;
|
|
}
|
|
else // turn it off -- CC need NOT be mixed
|
|
{
|
|
hr = pMPC->GetBlendingParameter(&m_dwBlendParam) ;
|
|
ASSERT(SUCCEEDED(hr) && m_dwBlendParam <= 255) ;
|
|
|
|
DbgLog((LOG_TRACE, 5, TEXT("Calling SetBlendingParameter(0)"))) ;
|
|
hr = pMPC->SetBlendingParameter(0) ;
|
|
ASSERT(SUCCEEDED(hr)) ;
|
|
}
|
|
m_bBlendingState = bState ; // save last blending operation flag
|
|
|
|
pMPC->Release() ;
|
|
}
|
|
|
|
|
|
HRESULT CLine21DecFilter::SendOutputSample(IMediaSample *pIn,
|
|
REFERENCE_TIME *prtStart, REFERENCE_TIME *prtStop)
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("CLine21DecFilter::SendOutputSample(0x%lx, %s, %s)"),
|
|
pIn, prtStart ? (LPCTSTR)CDisp(*prtStart) : TEXT("NULL"),
|
|
prtStop ? (LPCTSTR)CDisp(*prtStop) : TEXT("NULL"))) ;
|
|
|
|
HRESULT hr ;
|
|
|
|
// Get the new sample's bounding rect and compare it to the last one
|
|
BOOL bMTChangeOK = FALSE ;
|
|
RECT rectNew ;
|
|
m_L21Dec.CalcOutputRect(&rectNew) ;
|
|
if ( !ISRECTEQUAL(rectNew, m_rectLastOutput) ) // bounding rect changed
|
|
{
|
|
DbgLog((LOG_TRACE, 1,
|
|
TEXT("Bounding rect changed ((%ld, %ld, %ld, %ld) -> (%ld, %ld, %ld, %ld)). Change mediatype on output sample."),
|
|
m_rectLastOutput.left, m_rectLastOutput.top, m_rectLastOutput.right, m_rectLastOutput.bottom,
|
|
rectNew.left, rectNew.top, rectNew.right, rectNew.bottom)) ;
|
|
VIDEOINFOHEADER *pVIH = (VIDEOINFOHEADER *) (m_mtOutput.Format()) ;
|
|
ASSERT(pVIH) ;
|
|
pVIH->rcSource = rectNew ;
|
|
pVIH->rcTarget = rectNew ;
|
|
if (m_pPinDown && S_OK == (hr = m_pPinDown->QueryAccept((AM_MEDIA_TYPE *) &m_mtOutput)))
|
|
{
|
|
DbgLog((LOG_TRACE, 1, TEXT("Mediatype OK to downstream pin. Rect:(L=%ld, T=%ld, R=%ld, B=%ld)"),
|
|
rectNew.left, rectNew.top, rectNew.right, rectNew.bottom)) ; // log trace=3
|
|
bMTChangeOK = TRUE ;
|
|
}
|
|
else
|
|
{
|
|
DbgLog((LOG_TRACE, 1, TEXT("Mediatype NOT acceptable (Error 0x%lx) to downstream pin. Skipping rect spec-ing."), hr)) ;
|
|
pVIH->rcSource = m_rectLastOutput ; // restore old rect
|
|
pVIH->rcTarget = m_rectLastOutput ; // restore old rect
|
|
}
|
|
}
|
|
|
|
// Turn on blending param, Deliver the sample, release mediasample i/f and set blending param
|
|
SetBlendingState(TRUE) ; // turn it on before delivering next sample
|
|
|
|
// Get the output sample address before decoding
|
|
IMediaSample *pOut ;
|
|
hr = m_pOutput->GetDeliveryBuffer(&pOut, NULL, NULL,
|
|
m_bSampleSkipped ? AM_GBF_PREVFRAMESKIPPED : 0) ;
|
|
if (FAILED(hr))
|
|
{
|
|
DbgLog((LOG_ERROR, 0, TEXT("WARNING: GetDeliveryBuffer() on out pin failed (Error 0x%lx)"), hr)) ;
|
|
SetBlendingState(! m_L21Dec.IsOutDIBClear() ) ; // restore blending state
|
|
return NOERROR ; // no point complaining -- probably the graph is stopping
|
|
}
|
|
Transform(pIn, pOut) ; // check if output buffer address changed
|
|
|
|
hr = pOut->SetTime(prtStart, prtStop) ; // set the start & stop time on output sample
|
|
ASSERT(SUCCEEDED(hr)) ;
|
|
|
|
// Change the bounding rect ONLY IF the new rect was acceptable above
|
|
if (bMTChangeOK)
|
|
{
|
|
hr = pOut->SetMediaType((AM_MEDIA_TYPE *) &m_mtOutput) ;
|
|
ASSERT(SUCCEEDED(hr)) ;
|
|
m_rectLastOutput = rectNew ; // save this for next round
|
|
}
|
|
|
|
// The time stamp and other settings now
|
|
if (NULL == pIn) // preparing out sample w/o valid in sample
|
|
{
|
|
// We assume that it must be a discontinuity as it's a forced output sample
|
|
pOut->SetSyncPoint(TRUE) ;
|
|
pOut->SetDiscontinuity(TRUE) ;
|
|
}
|
|
else // input sample is valid
|
|
{
|
|
LONGLONG *pllMediaStart, *pllMediaStop ;
|
|
if (SUCCEEDED(pIn->GetMediaTime(&m_llMediaStart, &m_llMediaStop)))
|
|
{
|
|
if (m_llMediaStop < m_llMediaStart + m_rtTimePerSample)
|
|
m_llMediaStop = m_llMediaStart + m_rtTimePerSample ;
|
|
pllMediaStart = (LONGLONG *)&m_llMediaStart ;
|
|
pllMediaStop = (LONGLONG *)&m_llMediaStop ;
|
|
}
|
|
else
|
|
{
|
|
pllMediaStart = pllMediaStop = NULL ;
|
|
}
|
|
hr = pOut->SetMediaTime(pllMediaStart, pllMediaStop) ;
|
|
ASSERT(NOERROR == hr) ;
|
|
|
|
pOut->SetSyncPoint(pIn->IsSyncPoint() == S_OK) ;
|
|
pOut->SetDiscontinuity(m_bSampleSkipped ||S_OK == pIn->IsDiscontinuity()) ;
|
|
}
|
|
m_bSampleSkipped = FALSE ;
|
|
|
|
// Copy output bitmap data to output buffer
|
|
m_L21Dec.CopyOutputDIB() ;
|
|
|
|
// Now deliver the output sample
|
|
MSR_START(m_idDelvWait) ; // delivering output sample
|
|
hr = m_pOutput->Deliver(pOut) ;
|
|
MSR_STOP(m_idDelvWait) ; // done delivering output sample
|
|
if (FAILED(hr)) // Deliver failed for some reason. Eat the error and just go ahead.
|
|
{
|
|
DbgLog((LOG_ERROR, 0, TEXT("WARNING: Deliver() of output sample failed (Error 0x%lx)"), hr)) ;
|
|
// Should we send an error notification to the graph?
|
|
}
|
|
pOut->Release() ; // release the output sample
|
|
|
|
SetBlendingState(! m_L21Dec.IsOutDIBClear() ) ; // turn off/on based on output clear or not
|
|
|
|
return NOERROR ;
|
|
}
|
|
|
|
|
|
// #define PACKET_DUMP
|
|
#ifdef PACKET_DUMP // only for debug builds
|
|
//
|
|
// A helper function to dump the GOP Packets with Line21 data for internal debugging ONLY
|
|
//
|
|
void DumpPacket(PAM_L21_GOPUD_PACKET pGOPUDPacket)
|
|
{
|
|
AM_L21_GOPUD_ELEMENT Elem ;
|
|
TCHAR achBuffer[100] ;
|
|
BOOL bDumped = TRUE ;
|
|
int iElems = GETGOPUD_NUMELEMENTS(pGOPUDPacket) ;
|
|
|
|
DbgLog((LOG_TRACE, 0, TEXT("# Elements: %d (%2.2x)"),
|
|
iElems, pGOPUDPacket->Header.bTopField_Rsrvd_NumElems)) ;
|
|
ZeroMemory(achBuffer, sizeof(achBuffer)) ; // just to clean it
|
|
for (int i = 0 ; i < iElems ; i++)
|
|
{
|
|
Elem = GETGOPUDPACKET_ELEMENT(pGOPUDPacket, i) ;
|
|
wsprintf(achBuffer + 12 * (i % 6), TEXT("(%2.2x %2.2x %2.2x)"),
|
|
(int)Elem.bMarker_Switch, (int)Elem.chFirst, (int)Elem.chSecond) ;
|
|
if (GETGOPUD_ELEM_MARKERBITS(Elem) == AM_L21_GOPUD_ELEM_MARKERBITS &&
|
|
GETGOPUD_ELEM_SWITCHBITS(Elem) == AM_L21_GOPUD_ELEM_VALIDFLAG)
|
|
achBuffer[12 * (i % 6) + 10] = TEXT(' ') ;
|
|
else
|
|
achBuffer[12 * (i % 6) + 10] = TEXT('*') ; // indicates bad marker bit
|
|
achBuffer[12 * (i % 6) + 11] = TEXT(' ') ; // separator space
|
|
bDumped = FALSE ; // something not dumped yet
|
|
|
|
if (0 == (i+1) % 6) // 6 elems per line
|
|
{
|
|
DbgLog((LOG_TRACE, 0, achBuffer)) ;
|
|
bDumped = TRUE ;
|
|
}
|
|
} // end of for (i)
|
|
|
|
// if there is something that's not been dumped yet, pad it with NULLs to the end
|
|
// and then dump.
|
|
if (!bDumped)
|
|
{
|
|
ZeroMemory(achBuffer + 12 * (i % 6), sizeof(TCHAR) * (100 - 12 * (i % 6))) ;
|
|
DbgLog((LOG_TRACE, 0, achBuffer)) ;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// A helper function to dump the ATSC Packets with Line21 data for internal debugging ONLY
|
|
//
|
|
void DumpATSCPacket(PAM_L21_ATSCUD_PACKET pATSCUDPacket)
|
|
{
|
|
AM_L21_ATSCUD_ELEMENT Elem ;
|
|
TCHAR achBuffer[100] ;
|
|
BOOL bDumped = TRUE ;
|
|
int iElems = GETATSCUD_NUMELEMENTS(pATSCUDPacket) ;
|
|
|
|
DbgLog((LOG_TRACE, 0, TEXT("Data Flags: %sEM, %sCC, %sAdditional"),
|
|
ISATSCUD_EM_DATA(pATSCUDPacket) ? TEXT("") : TEXT("Not "),
|
|
ISATSCUD_CC_DATA(pATSCUDPacket) ? TEXT("") : TEXT("Not "),
|
|
ISATSCUD_ADDL_DATA(pATSCUDPacket) ? TEXT("") : TEXT("Not "))) ;
|
|
DbgLog((LOG_TRACE, 0, TEXT("# Elements: %d"), iElems)) ;
|
|
DbgLog((LOG_TRACE, 0, TEXT("EM Data: 0x%x"), GETATSCUD_EM_DATA(pATSCUDPacket))) ;
|
|
|
|
if (ISATSCUD_CC_DATA(pATSCUDPacket)) // if CC data present then dump that
|
|
{
|
|
ZeroMemory(achBuffer, sizeof(achBuffer)) ; // just to clear it
|
|
for (int i = 0 ; i < iElems ; i++)
|
|
{
|
|
Elem = GETATSCUDPACKET_ELEMENT(pATSCUDPacket, i) ;
|
|
wsprintf(achBuffer + 12 * (i % 6), TEXT("(%2.2x %2.2x %2.2x)"),
|
|
(int)Elem.bCCMarker_Valid_Type, (int)Elem.chFirst, (int)Elem.chSecond) ;
|
|
if (ISATSCUD_ELEM_MARKERBITS_VALID(Elem) && ISATSCUD_ELEM_CCVALID(Elem))
|
|
achBuffer[12 * (i % 6) + 10] = ' ' ;
|
|
else
|
|
achBuffer[12 * (i % 6) + 10] = '*' ; // indicates bad marker bit
|
|
achBuffer[12 * (i % 6) + 11] = ' ' ; // separator space
|
|
bDumped = FALSE ; // something not dumped yet
|
|
|
|
if (0 == (i+1) % 6) // 6 elems per line
|
|
{
|
|
DbgLog((LOG_TRACE, 0, achBuffer)) ;
|
|
bDumped = TRUE ;
|
|
}
|
|
} // end of for (i)
|
|
|
|
// if there is something that's not been dumped yet, pad it with NULLs to the end
|
|
// and then dump.
|
|
if (!bDumped)
|
|
{
|
|
ZeroMemory(achBuffer + 12 * (i % 6), 100 - 12 * (i % 6)) ;
|
|
DbgLog((LOG_TRACE, 0, achBuffer)) ;
|
|
}
|
|
}
|
|
|
|
DbgLog((LOG_TRACE, 0, TEXT("Marker bits: 0x%x"), GETATSCUD_MARKERBITS(pATSCUDPacket))) ;
|
|
}
|
|
|
|
#endif // PACKET_DUMP
|
|
|
|
|
|
HRESULT CLine21DecFilter::ProcessGOPPacket_DVD(IMediaSample *pIn)
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("CLine21DecFilter::ProcessGOPPacket_DVD(0x%lx)"), pIn)) ;
|
|
|
|
HRESULT hr ;
|
|
REFERENCE_TIME *prtStart, *prtStop ;
|
|
LONGLONG *pllMediaStart, *pllMediaStop ;
|
|
LONGLONG llMediaInterval ;
|
|
BOOL bCapUpdated ; // has caption been updated?
|
|
|
|
// Get the input data packet and verify that the contents are OK
|
|
PAM_L21_GOPUD_PACKET pGOPUDPacket ;
|
|
hr = pIn->GetPointer((LPBYTE *)&pGOPUDPacket) ;
|
|
ASSERT(hr == NOERROR) ;
|
|
if (! VerifyGOPUDPacketData(pGOPUDPacket) )
|
|
{
|
|
DbgLog((LOG_ERROR, 0, TEXT("Packet verification failed"))) ;
|
|
return S_FALSE ;
|
|
}
|
|
if (pIn->GetActualDataLength() != GETGOPUD_PACKETSIZE(pGOPUDPacket))
|
|
{
|
|
DbgLog((LOG_ERROR, 0,
|
|
TEXT("pIn->GetActualDataLength() [%d] and data size [%d] in packet mismatched"),
|
|
pIn->GetActualDataLength(), GETGOPUD_PACKETSIZE(pGOPUDPacket))) ;
|
|
return S_FALSE ;
|
|
}
|
|
|
|
#ifdef PACKET_DUMP
|
|
DumpPacket(pGOPUDPacket) ;
|
|
#endif // PACKET_DUMP
|
|
|
|
// The checks are done.
|
|
AM_L21_GOPUD_ELEMENT Elem ;
|
|
REFERENCE_TIME rtInterval ;
|
|
int iElems = GETGOPUD_NUMELEMENTS(pGOPUDPacket) ;
|
|
if (0 == iElems)
|
|
{
|
|
ASSERT(iElems > 0) ;
|
|
return S_OK ;
|
|
}
|
|
|
|
if (NOERROR == pIn->GetTime(&m_rtStart, &m_rtStop))
|
|
{
|
|
//
|
|
// We need at least 16.7msec/frame in the GOP for each bytepair
|
|
//
|
|
REFERENCE_TIME rtTemp ;
|
|
rtTemp = m_rtStart + m_rtTimePerSample * iElems ;
|
|
DbgLog((LOG_TRACE, 3, TEXT("Received an input sample (Start=%s, Stop=%s (%s)) discon(%d)"),
|
|
(LPCTSTR)CDisp(m_rtStart), (LPCTSTR)CDisp(rtTemp), (LPCTSTR)CDisp(m_rtStop),
|
|
S_OK == pIn->IsDiscontinuity())) ;
|
|
if (m_rtStop < rtTemp)
|
|
m_rtStop = rtTemp ;
|
|
|
|
prtStart = (REFERENCE_TIME *)&m_rtStart ;
|
|
prtStop = (REFERENCE_TIME *)&m_rtStop ;
|
|
rtInterval = (m_rtStop - m_rtStart) / iElems ;
|
|
}
|
|
else
|
|
{
|
|
DbgLog((LOG_TRACE, 1, TEXT("Received an input sample with no timestamp"))) ;
|
|
prtStart = prtStop = NULL ;
|
|
rtInterval = 0 ;
|
|
}
|
|
|
|
if (SUCCEEDED(pIn->GetMediaTime(&m_llMediaStart, &m_llMediaStop)))
|
|
{
|
|
//
|
|
// We need at least 33msec/frame in the GOP for each bytepair
|
|
//
|
|
LONGLONG llTemp ;
|
|
llTemp = m_llMediaStart + m_rtTimePerSample * iElems ;
|
|
if (m_llMediaStop < llTemp)
|
|
m_llMediaStop = llTemp ;
|
|
pllMediaStart = (LONGLONG *)&m_llMediaStart ;
|
|
pllMediaStop = (LONGLONG *)&m_llMediaStop ;
|
|
llMediaInterval = (m_llMediaStop - m_llMediaStart) / iElems ;
|
|
}
|
|
else
|
|
{
|
|
pllMediaStart = pllMediaStop = NULL ;
|
|
llMediaInterval = 0 ;
|
|
}
|
|
|
|
BOOL bFoundGood = FALSE ; // until a pair is decoded successfully
|
|
BOOL bReady ;
|
|
BOOL bTopFirst = ISGOPUD_TOPFIELDFIRST(pGOPUDPacket) ;
|
|
DbgLog((LOG_TRACE, 5,
|
|
TEXT("Got a Line21 packet with %d elements, %s field first"),
|
|
iElems, bTopFirst ? "Top" : "Bottom")) ;
|
|
for (int i = bTopFirst ? 0 : 1 ; // if top field is not first,
|
|
i < iElems ; i++) // pick next field to start with
|
|
{
|
|
m_rtStop = m_rtStart + rtInterval ; // m_rtTimePerSample ;
|
|
m_llMediaStop = m_llMediaStart + llMediaInterval ;
|
|
Elem = GETGOPUDPACKET_ELEMENT(pGOPUDPacket, i) ;
|
|
if (GETGOPUD_ELEM_MARKERBITS(Elem) == AM_L21_GOPUD_ELEM_MARKERBITS &&
|
|
GETGOPUD_ELEM_SWITCHBITS(Elem) == AM_L21_GOPUD_ELEM_VALIDFLAG)
|
|
{
|
|
//
|
|
// In the WB titles the bottom field's data has wrong marker
|
|
// bit set so that we don't try to decode them. But the titles
|
|
// from Columbia/Tristar (and God knows who else) doesn't do
|
|
// that causing us to look at every field's data which causes
|
|
// CC to flash away with the arrival of the next EOC (14 2F),
|
|
// because it's not recognized as the repeat of the last EOC
|
|
// due to the (0, 0) pair with valid marker bit. So we knowingly
|
|
// skip the alternate field's data to avoid this problem.
|
|
//
|
|
if ( (bTopFirst && (i & 0x01)) || // top first & odd index
|
|
(!bTopFirst && 0 == (i & 0x01)) ) // bottom first & even index
|
|
{
|
|
DbgLog((LOG_TRACE, 5,
|
|
TEXT("(0x%x, 0x%x) decode skipped for element %d -- the 'other' field"),
|
|
Elem.chFirst, Elem.chSecond, i)) ;
|
|
// Advance the time stamps anyway
|
|
m_rtStart = m_rtStop ;
|
|
m_llMediaStart = m_llMediaStop ;
|
|
continue ;
|
|
}
|
|
|
|
// Now decode this element; if fails (i.e, bad data), just
|
|
// ignore it and try the next element.
|
|
if (! m_L21Dec.DecodeBytePair(Elem.chFirst, Elem.chSecond) )
|
|
{
|
|
// if we must output a sample because:
|
|
// a) we haven't sent down any sample in this play session or
|
|
// b) we need to refresh the output because some component
|
|
// set this flag, e.g,
|
|
// * SetServiceState(.._Off) or
|
|
// * we got a discontinuity sample
|
|
// * no valid packet came for last 3 secs
|
|
// So we must deliver one output sample with current caption content.
|
|
if (m_bMustOutput)
|
|
{
|
|
DbgLog((LOG_TRACE, 1,
|
|
TEXT("(0x%x, 0x%x) decode failed, but allowing one output sample"),
|
|
Elem.chFirst, Elem.chSecond)) ;
|
|
}
|
|
else if (m_L21Dec.IsScrolling())
|
|
{
|
|
DbgLog((LOG_TRACE, 5,
|
|
TEXT("(0x%x, 0x%x) decode failed, but scrolling now; so..."),
|
|
Elem.chFirst, Elem.chSecond)) ;
|
|
}
|
|
else
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("(0x%x, 0x%x) decode failed"),
|
|
Elem.chFirst, Elem.chSecond)) ;
|
|
|
|
// We need to increment the time stamp though;
|
|
// stop time for this sample is start time for next sample
|
|
m_rtStart = m_rtStop ;
|
|
m_llMediaStart = m_llMediaStop ;
|
|
continue ; // bad data; proceed to next pair...
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bFoundGood = TRUE ; // got one good pair
|
|
DbgLog((LOG_TRACE, 5, TEXT("(0x%x, 0x%x) decode succeeded"),
|
|
Elem.chFirst, Elem.chSecond)) ;
|
|
}
|
|
|
|
// If we are in non-PopOn mode, update caption, if reqd.
|
|
bCapUpdated = m_L21Dec.UpdateCaptionOutput() ;
|
|
|
|
// Output a sample only if either
|
|
// a) we must output (the flag is set) or
|
|
// b) we are in non-PopOn mode and need to update captions or
|
|
// c) we are in the middle of scrolling or
|
|
// d) we are in PopOn mode AND caption needs to/should be updated
|
|
if (m_bMustOutput || // (a)
|
|
bCapUpdated || // (b)
|
|
m_L21Dec.IsScrolling() || // (c)
|
|
(bReady = m_L21Dec.IsOutputReady())) // (d)
|
|
{
|
|
DbgLog((LOG_TRACE, 3,
|
|
TEXT("Preparing output sample because Must=%s, CapUpdtd=%s, Ready=%s"),
|
|
m_bMustOutput ? "T" : "F", bCapUpdated ? "T" : "F", bReady ? "T" : "F")) ;
|
|
|
|
// Now send the output sample down
|
|
hr = SendOutputSample(pIn, prtStart, prtStop) ;
|
|
if (FAILED(hr))
|
|
{
|
|
DbgLog((LOG_ERROR, 0, TEXT("WARNING: Sending output sample failed (Error 0x%lx)"), hr)) ;
|
|
// return hr ;
|
|
}
|
|
else
|
|
{
|
|
DbgLog((LOG_TRACE, 3,
|
|
TEXT("Delivered an output sample (Time: Start=%s, Stop=%s)"),
|
|
(LPCTSTR)CDisp(m_rtStart), (LPCTSTR)CDisp(m_rtStop))) ;
|
|
m_bMustOutput = FALSE; // we have output just now
|
|
}
|
|
|
|
// The DVD titles don't turn off caption when there is no conversation.
|
|
// So we keep track of when we delivered the last output sample, so that
|
|
// in 3 seconds if we don't get the next valid input packet, we flush our
|
|
// buffers and clear CC output by forced delivery of a clear sample.
|
|
m_rtLastSample = m_rtStart ; // remember this
|
|
|
|
} // end of if (should/must we output?)
|
|
}
|
|
else
|
|
DbgLog((LOG_TRACE, 5,
|
|
TEXT("Ignored an element (0x%x, 0x%x, 0x%x) with invalid flag"),
|
|
Elem.bMarker_Switch, Elem.chFirst, Elem.chSecond)) ;
|
|
|
|
// stop time for this sample is start time for next sample
|
|
m_rtStart = m_rtStop ;
|
|
m_llMediaStart = m_llMediaStop ;
|
|
} // end of for(i)
|
|
|
|
//
|
|
// Flush the current caption buffer contents and set the
|
|
// "must output on next chance" flag so that a clear sample is
|
|
// delivered next time around, if
|
|
// a) we didn't find any good pair in this packet AND
|
|
// b) the last sample we sent down wasn't a clear sample AND
|
|
// c) it has already been 3 seconds since we sent the last output sample
|
|
//
|
|
if ( ! bFoundGood &&
|
|
! m_L21Dec.IsOutDIBClear() &&
|
|
(m_rtStart > m_rtLastSample + (LONGLONG)30000000))
|
|
{
|
|
DbgLog((LOG_TRACE, 1, TEXT("Long gap after last sample. Clearing CC. (Good=%s, Clear=%s)"),
|
|
bFoundGood ? "T" : "F", m_L21Dec.IsOutDIBClear() ? "T" : "F")) ;
|
|
// m_L21Dec.MakeClearSample() ;
|
|
m_L21Dec.FlushInternalStates() ;
|
|
m_bMustOutput = TRUE ;
|
|
}
|
|
|
|
return S_OK ;
|
|
}
|
|
|
|
|
|
HRESULT CLine21DecFilter::ProcessGOPPacket_ATSC(IMediaSample *pIn)
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("CLine21DecFilter::ProcessGOPPacket_ATSC(0x%lx)"), pIn)) ;
|
|
|
|
HRESULT hr ;
|
|
REFERENCE_TIME *prtStart, *prtStop ;
|
|
LONGLONG *pllMediaStart, *pllMediaStop ;
|
|
LONGLONG llMediaInterval ;
|
|
BOOL bCapUpdated ; // has caption been updated?
|
|
|
|
// Get the input data packet and verify that the contents are OK
|
|
PAM_L21_ATSCUD_PACKET pATSCUDPacket ;
|
|
|
|
// Get the input data packet and verify that the contents are OK
|
|
hr = pIn->GetPointer((LPBYTE *)&pATSCUDPacket) ;
|
|
ASSERT(hr == NOERROR) ;
|
|
if (! VerifyATSCUDPacketData(pATSCUDPacket) )
|
|
{
|
|
DbgLog((LOG_ERROR, 0, TEXT("ATSC Packet verification failed"))) ;
|
|
return S_FALSE ;
|
|
}
|
|
if (pIn->GetActualDataLength() < GETATSCUD_PACKETSIZE(pATSCUDPacket))
|
|
{
|
|
DbgLog((LOG_ERROR, 0,
|
|
TEXT("pIn->GetActualDataLength() [%d] is less than minm ATSC packet data size [%d]"),
|
|
pIn->GetActualDataLength(), GETATSCUD_PACKETSIZE(pATSCUDPacket))) ;
|
|
return S_FALSE ;
|
|
}
|
|
|
|
#ifdef PACKET_DUMP
|
|
DumpATSCPacket(pATSCUDPacket) ;
|
|
#endif // PACKET_DUMP
|
|
|
|
// The checks are done.
|
|
AM_L21_ATSCUD_ELEMENT Elem ;
|
|
REFERENCE_TIME rtInterval ;
|
|
int iElems = GETATSCUD_NUMELEMENTS(pATSCUDPacket) ;
|
|
if (0 == iElems)
|
|
{
|
|
ASSERT(iElems > 0) ;
|
|
return S_OK ;
|
|
}
|
|
|
|
if (NOERROR == pIn->GetTime(&m_rtStart, &m_rtStop))
|
|
{
|
|
//
|
|
// We need at least 16.7msec/frame in the ATSC for each bytepair
|
|
//
|
|
REFERENCE_TIME rtTemp ;
|
|
rtTemp = m_rtStart + m_rtTimePerSample * iElems ;
|
|
DbgLog((LOG_TRACE, 3, TEXT("Received an input sample (Start=%s, Stop=%s (%s)) discon(%d)"),
|
|
(LPCTSTR)CDisp(m_rtStart), (LPCTSTR)CDisp(rtTemp), (LPCTSTR)CDisp(m_rtStop),
|
|
S_OK == pIn->IsDiscontinuity())) ;
|
|
if (m_rtStop < rtTemp)
|
|
m_rtStop = rtTemp ;
|
|
|
|
prtStart = (REFERENCE_TIME *)&m_rtStart ;
|
|
prtStop = (REFERENCE_TIME *)&m_rtStop ;
|
|
rtInterval = (m_rtStop - m_rtStart) / iElems ;
|
|
}
|
|
else
|
|
{
|
|
DbgLog((LOG_TRACE, 1, TEXT("Received an input sample with no timestamp"))) ;
|
|
prtStart = prtStop = NULL ;
|
|
rtInterval = 0 ;
|
|
}
|
|
|
|
if (SUCCEEDED(pIn->GetMediaTime(&m_llMediaStart, &m_llMediaStop)))
|
|
{
|
|
//
|
|
// We need at least 33msec/frame in the ATSC for each bytepair
|
|
//
|
|
LONGLONG llTemp ;
|
|
llTemp = m_llMediaStart + m_rtTimePerSample * iElems ;
|
|
if (m_llMediaStop < llTemp)
|
|
m_llMediaStop = llTemp ;
|
|
pllMediaStart = (LONGLONG *)&m_llMediaStart ;
|
|
pllMediaStop = (LONGLONG *)&m_llMediaStop ;
|
|
llMediaInterval = (m_llMediaStop - m_llMediaStart) / iElems ;
|
|
}
|
|
else
|
|
{
|
|
pllMediaStart = pllMediaStop = NULL ;
|
|
llMediaInterval = 0 ;
|
|
}
|
|
|
|
BOOL bFoundGood = FALSE ; // until a pair is decoded successfully
|
|
BOOL bReady ;
|
|
for (int i = 0 ; i < iElems ; i++)
|
|
{
|
|
m_rtStop = m_rtStart + rtInterval ; // m_rtTimePerSample ;
|
|
m_llMediaStop = m_llMediaStart + llMediaInterval ;
|
|
Elem = GETATSCUDPACKET_ELEMENT(pATSCUDPacket, i) ;
|
|
if (ISATSCUD_ELEM_MARKERBITS_VALID(Elem) && ISATSCUD_ELEM_CCVALID(Elem))
|
|
{
|
|
// Now decode this element; if fails (i.e, bad data), just
|
|
// ignore it and try the next element.
|
|
if (! m_L21Dec.DecodeBytePair(Elem.chFirst, Elem.chSecond) )
|
|
{
|
|
// if we must output a sample because:
|
|
// a) we haven't sent down any sample in this play session or
|
|
// b) we need to refresh the output because some component
|
|
// set this flag, e.g,
|
|
// * SetServiceState(.._Off) or
|
|
// * we got a discontinuity sample
|
|
// * no valid packet came for last 3 secs
|
|
// So we must deliver one output sample with current caption content.
|
|
if (m_bMustOutput)
|
|
{
|
|
DbgLog((LOG_TRACE, 1,
|
|
TEXT("(0x%x, 0x%x) decode failed, but allowing one output sample"),
|
|
Elem.chFirst, Elem.chSecond)) ;
|
|
}
|
|
else if (m_L21Dec.IsScrolling())
|
|
{
|
|
DbgLog((LOG_TRACE, 5,
|
|
TEXT("(0x%x, 0x%x) decode failed, but scrolling now; so..."),
|
|
Elem.chFirst, Elem.chSecond)) ;
|
|
}
|
|
else
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("(0x%x, 0x%x) decode failed"),
|
|
Elem.chFirst, Elem.chSecond)) ;
|
|
|
|
// We need to increment the time stamp though;
|
|
// stop time for this sample is start time for next sample
|
|
m_rtStart = m_rtStop ;
|
|
m_llMediaStart = m_llMediaStop ;
|
|
continue ; // bad data; proceed to next pair...
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bFoundGood = TRUE ; // got one good pair
|
|
DbgLog((LOG_TRACE, 5, TEXT("(0x%x, 0x%x) decode succeeded"),
|
|
Elem.chFirst, Elem.chSecond)) ;
|
|
}
|
|
|
|
// If we are in non-PopOn mode, update caption, if reqd.
|
|
bCapUpdated = m_L21Dec.UpdateCaptionOutput() ;
|
|
|
|
// Output a sample only if either
|
|
// a) we must output (the flag is set) or
|
|
// b) we are in non-PopOn mode and need to update captions or
|
|
// c) we are in the middle of scrolling or
|
|
// d) we are in PopOn mode AND caption needs to/should be updated
|
|
if (m_bMustOutput || // (a)
|
|
bCapUpdated || // (b)
|
|
m_L21Dec.IsScrolling() || // (c)
|
|
(bReady = m_L21Dec.IsOutputReady())) // (d)
|
|
{
|
|
DbgLog((LOG_TRACE, 3,
|
|
TEXT("Preparing output sample because Must=%s, CapUpdtd=%s, Ready=%s"),
|
|
m_bMustOutput ? "T" : "F", bCapUpdated ? "T" : "F", bReady ? "T" : "F")) ;
|
|
|
|
// Now send the output sample down
|
|
hr = SendOutputSample(pIn, prtStart, prtStop) ;
|
|
if (FAILED(hr))
|
|
{
|
|
DbgLog((LOG_ERROR, 0, TEXT("WARNING: Sending output sample failed (Error 0x%lx)"), hr)) ;
|
|
// return hr ;
|
|
}
|
|
else
|
|
{
|
|
DbgLog((LOG_TRACE, 3,
|
|
TEXT("Delivered an output sample (Time: Start=%s, Stop=%s)"),
|
|
(LPCTSTR)CDisp(m_rtStart), (LPCTSTR)CDisp(m_rtStop))) ;
|
|
m_bMustOutput = FALSE; // we have output just now
|
|
}
|
|
|
|
// The DVD titles don't turn off caption when there is no conversation.
|
|
// So we keep track of when we delivered the last output sample, so that
|
|
// in 3 seconds if we don't get the next valid input packet, we flush our
|
|
// buffers and clear CC output by forced delivery of a clear sample.
|
|
m_rtLastSample = m_rtStart ; // remember this
|
|
|
|
} // end of if (should/must we output?)
|
|
}
|
|
else
|
|
DbgLog((LOG_TRACE, 5,
|
|
TEXT("Ignored an element (0x%x, 0x%x, 0x%x) with invalid marker/type flag"),
|
|
Elem.bCCMarker_Valid_Type, Elem.chFirst, Elem.chSecond)) ;
|
|
|
|
// stop time for this sample is start time for next sample
|
|
m_rtStart = m_rtStop ;
|
|
m_llMediaStart = m_llMediaStop ;
|
|
} // end of for(i)
|
|
|
|
//
|
|
// Flush the current caption buffer contents and set the
|
|
// "must output on next chance" flag so that a clear sample is
|
|
// delivered next time around, if
|
|
// a) we didn't find any good pair in this packet AND
|
|
// b) the last sample we sent down wasn't a clear sample AND
|
|
// c) it has already been 3 seconds since we sent the last output sample
|
|
//
|
|
if ( ! bFoundGood &&
|
|
! m_L21Dec.IsOutDIBClear() &&
|
|
(m_rtStart > m_rtLastSample + (LONGLONG)30000000))
|
|
{
|
|
DbgLog((LOG_TRACE, 1, TEXT("Long gap after last sample. Clearing CC. (Good=%s, Clear=%s)"),
|
|
bFoundGood ? "T" : "F", m_L21Dec.IsOutDIBClear() ? "T" : "F")) ;
|
|
// m_L21Dec.MakeClearSample() ;
|
|
m_L21Dec.FlushInternalStates() ;
|
|
m_bMustOutput = TRUE ;
|
|
}
|
|
|
|
return S_OK ;
|
|
}
|
|
|
|
|
|
//
|
|
// Receive: It's the real place where the output samples are created by
|
|
// decoding the byte pairs out of the input stream.
|
|
//
|
|
HRESULT CLine21DecFilter::Receive(IMediaSample * pIn)
|
|
{
|
|
CAutoLock lock(&m_csReceive);
|
|
HRESULT hr ;
|
|
|
|
DbgLog((LOG_TRACE, 3, TEXT("CLine21DecFilter::Receive(0x%p)"), pIn)) ;
|
|
|
|
//
|
|
// First check if we must do anything at all
|
|
//
|
|
if (!m_bMustOutput && // not a must output
|
|
(AM_L21_CCSTATE_Off == m_L21Dec.GetServiceState() || // CC turned off
|
|
AM_L21_CCSERVICE_None == m_L21Dec.GetCurrentService())) // no CC selected
|
|
{
|
|
DbgLog((LOG_TRACE, 1,
|
|
TEXT("Captioning is off AND we don't HAVE TO output. Skipping everything."))) ;
|
|
return NOERROR ; // we are done with this sample
|
|
}
|
|
|
|
// Get the input format info; we'll use the same for output
|
|
ASSERT(m_pOutput != NULL) ;
|
|
|
|
//
|
|
// The real decoding part is here
|
|
//
|
|
REFERENCE_TIME *prtStart, *prtStop ;
|
|
BYTE *pbInBytePair = NULL ; // to shut up compiler
|
|
LONG lInDataSize ;
|
|
BOOL bCapUpdated ; // has caption been updated?
|
|
|
|
//
|
|
// Process the sample based on filter's input format type
|
|
//
|
|
switch (m_eSubTypeIDIn)
|
|
{
|
|
case AM_L21_CCSUBTYPEID_BytePair:
|
|
{
|
|
hr = pIn->GetPointer(&pbInBytePair) ; // Get the input byte pair
|
|
lInDataSize = pIn->GetActualDataLength() ; // se how much data we got
|
|
if (FAILED(hr) || 2 != lInDataSize) // bad data -- complain and just skip it
|
|
{
|
|
DbgLog((LOG_ERROR, 0, TEXT("%d bytes of data sent as Line21 data (hr = 0x%lx)"),
|
|
lInDataSize, hr)) ;
|
|
break ;
|
|
}
|
|
|
|
//
|
|
// m_rtTimePerSample is set to 166833 for DVD GOP packet case.
|
|
// We don't use this member's value here. If we need in future,
|
|
// we have to set some suitable value here.
|
|
//
|
|
|
|
if (NOERROR == (hr = pIn->GetTime(&m_rtStart, &m_rtStop)))
|
|
{
|
|
prtStart = (REFERENCE_TIME *)&m_rtStart ;
|
|
prtStop = (REFERENCE_TIME *)&m_rtStop ;
|
|
}
|
|
else
|
|
{
|
|
DbgLog((LOG_TRACE, 0, TEXT("WARNING: GetTime() failed (Error 0x%lx)"), hr)) ;
|
|
prtStart = prtStop = NULL ;
|
|
}
|
|
|
|
//
|
|
// We are here with some data; so don't need a timer for now
|
|
//
|
|
FreeTimer() ;
|
|
|
|
hr = pIn->IsDiscontinuity() ;
|
|
if (S_OK == hr) // got a discontinuity; flush everything, refresh output
|
|
{
|
|
// If we got a discontinuity in the last sample, we flushed and all.
|
|
// We can skip this one safely.
|
|
if (m_bDiscontLast)
|
|
{
|
|
DbgLog((LOG_TRACE, 1, TEXT("Got a discontinuity sample after another. Skipping everything."))) ;
|
|
break ;
|
|
}
|
|
|
|
// Flush the internal buffers (caption and output DIB section)
|
|
DbgLog((LOG_TRACE, 0, TEXT("Got a discontinuity sample. Flushing all data..."))) ;
|
|
m_L21Dec.FlushInternalStates() ;
|
|
|
|
// Send the clear sample down as output
|
|
hr = SendOutputSample(pIn, prtStart, prtStop) ;
|
|
if (FAILED(hr))
|
|
{
|
|
DbgLog((LOG_ERROR, 0, TEXT("WARNING: Sending output sample failed (Error 0x%lx)"), hr)) ;
|
|
return hr ;
|
|
}
|
|
m_rtLastSample = m_rtStart ; // remember this
|
|
m_bDiscontLast = TRUE ; // remember we handled a discontinuity
|
|
DbgLog((LOG_TRACE, 1, TEXT("Sent a clear sample for discont."))) ;
|
|
break ;
|
|
}
|
|
DbgLog((LOG_TRACE, 3, TEXT("Got sample with bytes 0x%x, 0x%x (Time: %s -> %s)"),
|
|
pbInBytePair[0], pbInBytePair[1],
|
|
(LPCTSTR)CDisp(m_rtStart), (LPCTSTR)CDisp(m_rtStop))) ; // log trace=?
|
|
m_bDiscontLast = FALSE ; // remember we got a normal sample
|
|
|
|
// Now decode into the received output sample buffer; if fails, we don't
|
|
// need to do the rest, mostly.
|
|
if (! m_L21Dec.DecodeBytePair(pbInBytePair[0], pbInBytePair[1]) )
|
|
{
|
|
// if we must output a sample such as:
|
|
// a) if we haven't sent down any sample in this play session
|
|
// b) if some component set this flag (e.g, SetServiceState(.._Off)
|
|
// and as a result we need to refresh the output
|
|
// we need to deliver one output sample with current caption content.
|
|
if (m_bMustOutput)
|
|
{
|
|
DbgLog((LOG_TRACE, 1,
|
|
TEXT("(0x%x, 0x%x) decode failed, but allowing one output sample"),
|
|
pbInBytePair[0], pbInBytePair[1])) ;
|
|
}
|
|
else if (m_L21Dec.IsScrolling())
|
|
{
|
|
DbgLog((LOG_TRACE, 5,
|
|
TEXT("(0x%x, 0x%x) decode failed, but scrolling now; so..."),
|
|
pbInBytePair[0], pbInBytePair[1])) ;
|
|
}
|
|
else
|
|
{
|
|
DbgLog((LOG_TRACE, 3, TEXT("(0x%x, 0x%x) decode failed"),
|
|
pbInBytePair[0], pbInBytePair[1])) ;
|
|
|
|
//
|
|
// Flush the current caption buffer contents and set the
|
|
// "must output on next chance" flag so that a clear sample is
|
|
// delivered by the code below, if
|
|
// a) the last sample we sent down wasn't a clear sample AND
|
|
// b) it has already been 6 seconds since we sent the last output sample
|
|
//
|
|
if ( ! m_L21Dec.IsOutDIBClear() &&
|
|
(m_rtStart > m_rtLastSample + (LONGLONG)60000000))
|
|
{
|
|
DbgLog((LOG_TRACE, 0,
|
|
TEXT("Long gap after last sample. Clearing CC. (Clear=%s)"),
|
|
m_L21Dec.IsOutDIBClear() ? "T" : "F")) ;
|
|
// m_L21Dec.MakeClearSample() ; --- I would rather flush everything
|
|
m_L21Dec.FlushInternalStates() ;
|
|
m_bMustOutput = TRUE ; // will be delivered below...
|
|
}
|
|
// else // it was just bad data; ignore it and ...
|
|
// break ; // ...proceed to next pair
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("(0x%x, 0x%x) decode succeeded"),
|
|
pbInBytePair[0], pbInBytePair[1])) ;
|
|
m_rtLastSample = m_rtStart ; // remember last valid byte pair time
|
|
}
|
|
|
|
// Update caption output for non-PopOn mode, if reqd.
|
|
bCapUpdated = m_L21Dec.UpdateCaptionOutput() ;
|
|
|
|
// Output a sample only if either
|
|
// a) we must output (the flag is set) or
|
|
// b) we are in non-PopOn mode and need to update captions or
|
|
// c) we are in the middle of scrolling or
|
|
// d) we are in PopOn mode AND caption needs to/should be updated
|
|
if (m_bMustOutput || // (a)
|
|
bCapUpdated || // (b)
|
|
m_L21Dec.IsScrolling() || // (c)
|
|
m_L21Dec.IsOutputReady()) // (d)
|
|
{
|
|
hr = SendOutputSample(pIn, prtStart, prtStop) ;
|
|
if (FAILED(hr))
|
|
{
|
|
DbgLog((LOG_ERROR, 0, TEXT("WARNING: Sending output sample failed (Error 0x%lx)"), hr)) ;
|
|
// return hr ;
|
|
}
|
|
else
|
|
{
|
|
m_bMustOutput = FALSE ; // we have successfully output a sample
|
|
m_rtLastSample = m_rtStart ; // remember this
|
|
DbgLog((LOG_TRACE, 3, TEXT("Output sample delivered for (0x%x, 0x%x)"),
|
|
pbInBytePair[0], pbInBytePair[1])) ;
|
|
}
|
|
} // end if (must/should we output?)
|
|
|
|
//
|
|
// If we are scrolling, we may need a timer to later tell us it's
|
|
// time to produce and deliver more output samples, even though there
|
|
// is no input data coming in.
|
|
//
|
|
SetupTimerIfReqd(TRUE) ; // CC time-out reqd
|
|
|
|
break ;
|
|
}
|
|
|
|
case AM_L21_CCSUBTYPEID_VBIRawData:
|
|
DbgLog((LOG_TRACE, 1, TEXT("Raw byte pair case has not been implemented yet"))) ;
|
|
break ;
|
|
|
|
case AM_L21_CCSUBTYPEID_GOPPacket:
|
|
{
|
|
//
|
|
// We are here with some data; so don't need a timer for now
|
|
//
|
|
FreeTimer() ;
|
|
|
|
// First check if this is a discontinuity sample. If so just clear everything
|
|
hr = pIn->IsDiscontinuity() ;
|
|
if (S_OK == hr) // got a discontinuity; flush everything, refresh output
|
|
{
|
|
// If we got a discontinuity in the last sample, we flushed and all.
|
|
// We can skip this one safely.
|
|
if (m_bDiscontLast)
|
|
{
|
|
DbgLog((LOG_TRACE, 1, TEXT("Got a discontinuity sample after another. Skipping everything."))) ;
|
|
break ;
|
|
}
|
|
|
|
if (NOERROR == pIn->GetTime(&m_rtStart, &m_rtStop))
|
|
{
|
|
if (m_rtStop < m_rtStart + m_rtTimePerSample)
|
|
m_rtStop = m_rtStart + m_rtTimePerSample ;
|
|
DbgLog((LOG_TRACE, 0, TEXT("Received a **discontinuity** : Start=%s, Stop=%s"),
|
|
(LPCTSTR)CDisp(m_rtStart), (LPCTSTR)CDisp(m_rtStop))) ;
|
|
}
|
|
else // cook up something reasonable
|
|
{
|
|
m_rtStart = (REFERENCE_TIME) 0 ;
|
|
m_rtStop = m_rtStart + m_rtTimePerSample ;
|
|
DbgLog((LOG_TRACE, 1, TEXT("Cooked up **discontinuity** time as Start=%s, Stop=%s"),
|
|
(LPCTSTR)CDisp(m_rtStart), (LPCTSTR)CDisp(m_rtStop))) ;
|
|
}
|
|
prtStart = (REFERENCE_TIME *)&m_rtStart ;
|
|
prtStop = (REFERENCE_TIME *)&m_rtStop ;
|
|
|
|
// Flush the internal buffers (caption and output DIB section)
|
|
m_L21Dec.FlushInternalStates() ;
|
|
|
|
// Now send the clear sample down
|
|
hr = SendOutputSample(pIn, prtStart, prtStop) ;
|
|
if (FAILED(hr))
|
|
{
|
|
DbgLog((LOG_ERROR, 0, TEXT("WARNING: Sending clear output sample failed (Error 0x%lx)"), hr)) ;
|
|
// return hr ;
|
|
}
|
|
else
|
|
{
|
|
DbgLog((LOG_TRACE, 0, TEXT("Clear output sample delivered for discont."))) ;
|
|
m_bMustOutput = FALSE ; // we have just delivered an output sample
|
|
m_bDiscontLast = TRUE ; // we handled a disocntinuity sample
|
|
m_rtLastSample = m_rtStart ; // remember this
|
|
}
|
|
|
|
m_rtStart = m_rtStop ;
|
|
m_llMediaStart = m_llMediaStop ;
|
|
}
|
|
else
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("Got a normal CC data sample"))) ;
|
|
m_bDiscontLast = FALSE ; // got a normal sample
|
|
}
|
|
|
|
//
|
|
// Even if it's a discontinuity sample it may have some data too (??).
|
|
// Handle as necessary. No harm in checking!!!
|
|
//
|
|
|
|
BYTE *pbGOPPacket ;
|
|
hr = pIn->GetPointer((LPBYTE *)&pbGOPPacket) ;
|
|
ASSERT(hr == NOERROR) ;
|
|
GOPPACKET_CCTYPE eGOP_CCType = DetectGOPPacketDataType(pbGOPPacket) ;
|
|
if (GOP_CCTYPE_None != eGOP_CCType && // NOT filler CC packet AND...
|
|
m_eGOP_CCType != eGOP_CCType) // change of CC type
|
|
{
|
|
DbgLog((LOG_TRACE, 3, TEXT("GOPPacket CC type changed from %d to %d"),
|
|
m_eGOP_CCType, eGOP_CCType)) ;
|
|
|
|
// Flush internal caption buffers and output sample buffer
|
|
m_L21Dec.FlushInternalStates() ; // clear CC internal data buffers
|
|
m_L21Dec.FillOutputBuffer() ; // clear existing CC on output sample
|
|
m_bMustOutput = TRUE ; // we must output a sample NOW
|
|
|
|
m_eGOP_CCType = eGOP_CCType ; // switch to new CC type
|
|
}
|
|
|
|
switch (m_eGOP_CCType)
|
|
{
|
|
case GOP_CCTYPE_DVD:
|
|
hr = ProcessGOPPacket_DVD(pIn) ;
|
|
break ;
|
|
|
|
case GOP_CCTYPE_ATSC:
|
|
hr = ProcessGOPPacket_ATSC(pIn) ;
|
|
break ;
|
|
|
|
default:
|
|
DbgLog((LOG_TRACE, 3, TEXT("Unknown GOP packet data type (%d)"), m_eGOP_CCType)) ;
|
|
break ;
|
|
} // end of switch (.._CCType)
|
|
|
|
//
|
|
// If we are scrolling, we may need a timer to later tell us it's
|
|
// time to produce and deliver more output samples, even though there
|
|
// is no input data coming in.
|
|
//
|
|
SetupTimerIfReqd(FALSE) ; // CC time-out NOT reqd as (invalid) data keeps coming
|
|
|
|
break ;
|
|
} // end of case ..._GOPPacket
|
|
|
|
default: // it's a bad data format type (how could we get into it?)
|
|
DbgLog((LOG_ERROR, 0, TEXT("We are in a totally unexpected format type"))) ;
|
|
return E_FAIL ; // or E_UNEXPECTED ; ???
|
|
}
|
|
|
|
//
|
|
// Decoding for this sample is done
|
|
//
|
|
|
|
return NOERROR ;
|
|
}
|
|
|
|
|
|
//
|
|
// Transform: It's mainly a place holder because we HAVE to override it.
|
|
// The actual work is done in Receive() itself. Here we detect
|
|
// if the buffer addres provided by downstream filter's allocator
|
|
// has changed or not; if yes, we have to re-write entire text.
|
|
//
|
|
HRESULT CLine21DecFilter::Transform(IMediaSample * pIn, IMediaSample * pOut)
|
|
{
|
|
DbgLog((LOG_TRACE, 3, TEXT("CLine21DecFilter::Transform(0x%p, 0x%p)"),
|
|
pIn, pOut)) ;
|
|
|
|
UNREFERENCED_PARAMETER(pIn) ;
|
|
|
|
HRESULT hr ;
|
|
LPBITMAPINFO lpbiNew ;
|
|
BITMAPINFOHEADER biCurr ;
|
|
|
|
// Check if there has been any dynamic format change; if so, adjust output
|
|
// width, height, bitdepth accordingly.
|
|
AM_MEDIA_TYPE *pmt ;
|
|
hr = pOut->GetMediaType(&pmt) ;
|
|
ASSERT(SUCCEEDED(hr)) ;
|
|
if (S_OK == hr) // i.e, format has changed
|
|
{
|
|
hr = pOut->SetMediaType(NULL) ; // just to tell OverlayMixer, I am not changing again
|
|
ASSERT(SUCCEEDED(hr)) ;
|
|
m_mtOutput = *pmt ;
|
|
lpbiNew = (LPBITMAPINFO) HEADER(((VIDEOINFO *)(pmt->pbFormat))) ;
|
|
m_L21Dec.GetOutputFormat(&biCurr) ;
|
|
if (0 != memcmp(lpbiNew, &biCurr, sizeof(BITMAPINFOHEADER)))
|
|
{
|
|
// output format has been changed -- update our internel values now
|
|
DbgLog((LOG_TRACE, 2, TEXT("Output format has been dynamically changed"))) ;
|
|
m_L21Dec.DeleteOutputDC() ; // delete current DIB section first
|
|
m_L21Dec.SetOutputOutFormat(lpbiNew) ;
|
|
GetDefaultFormatInfo() ; // to pick any change in format data
|
|
|
|
//
|
|
// We must be running/paused; so we need to create internal DIB section
|
|
//
|
|
ASSERT(m_State != State_Stopped) ;
|
|
if (m_State != State_Stopped)
|
|
{
|
|
if (! m_L21Dec.CreateOutputDC() ) // new DIBSection creation failed
|
|
{
|
|
DbgLog((LOG_ERROR, 0, TEXT("CreateOutputDC() failed!!!"))) ;
|
|
return E_UNEXPECTED ;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If key color has changed, we need to use the new color from now
|
|
//
|
|
#pragma message("Most probably the following call is redundant (and risky)")
|
|
DbgLog((LOG_TRACE, 0, TEXT("Should have called GetColorKey() in dyna format change"))) ;
|
|
// GetActualColorKey() ;
|
|
}
|
|
|
|
m_pOutput->CurrentMediaType() = *pmt ;
|
|
DeleteMediaType(pmt) ;
|
|
}
|
|
|
|
// Check if the out put buffer has changed; if so, store new buffer address
|
|
LPBYTE pbOutBuffer ;
|
|
pOut->GetPointer(&pbOutBuffer) ;
|
|
if (m_pbOutBuffer != pbOutBuffer) // different output buffer this time
|
|
{
|
|
m_pbOutBuffer = pbOutBuffer ;
|
|
m_L21Dec.SetOutputBuffer(pbOutBuffer) ;
|
|
}
|
|
|
|
return S_OK ;
|
|
}
|
|
|
|
|
|
//
|
|
// BeginFlush: We have to implement this as we have overridden Receive()
|
|
//
|
|
HRESULT CLine21DecFilter::BeginFlush(void)
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("CLine21DecFilter::BeginFlush()"))) ;
|
|
|
|
CAutoLock Lock(&m_csFilter) ;
|
|
|
|
HRESULT hr = NOERROR ;
|
|
if (NULL != m_pOutput)
|
|
{
|
|
hr = m_pOutput->DeliverBeginFlush() ;
|
|
if (FAILED(hr))
|
|
DbgLog((LOG_ERROR, 0, TEXT("WARNING: DeliverBeginFlush() on out pin failed (Error 0x%lx)"), hr)) ;
|
|
}
|
|
|
|
return hr ;
|
|
}
|
|
|
|
|
|
//
|
|
// EndFlush: We have to implement this as we have overridden Receive()
|
|
//
|
|
HRESULT CLine21DecFilter::EndFlush(void)
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("CLine21DecFilter::EndFlush()"))) ;
|
|
|
|
CAutoLock Lock(&m_csFilter) ;
|
|
|
|
HRESULT hr = NOERROR ;
|
|
if (NULL != m_pOutput)
|
|
{
|
|
hr = m_pOutput->DeliverEndFlush() ;
|
|
if (FAILED(hr))
|
|
DbgLog((LOG_ERROR, 0, TEXT("WARNING: DeliverEndFlush() on out pin failed (Error 0x%lx)"), hr)) ;
|
|
}
|
|
|
|
return hr ;
|
|
}
|
|
|
|
|
|
//
|
|
// EndOfStream: We have to implement this as we have overridden Receive()
|
|
//
|
|
HRESULT CLine21DecFilter::EndOfStream(void)
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("CLine21DecFilter::EndOfStream()"))) ;
|
|
CAutoLock Lock(&m_csFilter) ;
|
|
|
|
HRESULT hr = NOERROR ;
|
|
|
|
//
|
|
// Make sure we are not in the middle of a scrolling. If so,
|
|
// force a few NULLs (specially in byte pair format) to make
|
|
// the scrolling complete.
|
|
//
|
|
// m_L21Dec.CompleteScrolling() ; // It doesn't do anything now
|
|
|
|
// Now send EOS downstream
|
|
if (NULL != m_pOutput)
|
|
{
|
|
hr = m_pOutput->DeliverEndOfStream() ;
|
|
if (FAILED(hr))
|
|
DbgLog((LOG_ERROR, 0, TEXT("WARNING: DeliverEndOfStream() on out pin failed (Error 0x%lx)"), hr)) ;
|
|
}
|
|
|
|
return hr ;
|
|
}
|
|
|
|
|
|
HRESULT CLine21DecFilter::GetDefaultFormatInfo(void)
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("CLine21DecFilter::GetDefaultFormatInfo()"))) ;
|
|
//
|
|
// We can't take the lock in this method, because it is called in Transform()
|
|
// which is called from Receive() causing us to take m_csReceive and then
|
|
// m_csFilter which is opposite of what Stop, Pause etc. methods do thereby
|
|
// causing a potential for deadlock.
|
|
//
|
|
|
|
// build a VIDEOINFO struct with default internal BITMAPINFO
|
|
DWORD dwSize ;
|
|
m_L21Dec.GetDefaultFormatInfo(NULL, &dwSize) ;
|
|
|
|
if (m_dwDefFmtSize != dwSize + SIZE_PREHEADER)
|
|
{
|
|
if (m_pviDefFmt)
|
|
{
|
|
delete m_pviDefFmt ;
|
|
m_pviDefFmt = NULL ;
|
|
m_dwDefFmtSize = 0 ;
|
|
}
|
|
m_pviDefFmt = (VIDEOINFO *) new BYTE[dwSize + SIZE_PREHEADER] ;
|
|
if (NULL == m_pviDefFmt)
|
|
{
|
|
DbgLog((LOG_ERROR, 0, TEXT("WARNING: Out of memory for format block VIDEOINFO struct"))) ;
|
|
return E_OUTOFMEMORY ;
|
|
}
|
|
m_dwDefFmtSize = dwSize + SIZE_PREHEADER; // total size of default format data
|
|
}
|
|
|
|
// We want to get BITMAPINFO part of VIDEOINFO struct from our GDI class
|
|
m_L21Dec.GetDefaultFormatInfo((LPBITMAPINFO) &(m_pviDefFmt->bmiHeader), &dwSize) ; // get default data
|
|
|
|
// Set the other fields
|
|
LARGE_INTEGER li ;
|
|
li.QuadPart = (LONGLONG) 333667 ; // => 29.97 fps
|
|
RECT rc ;
|
|
rc.left = rc.top = 0 ;
|
|
rc.right = HEADER(m_pviDefFmt)->biWidth ;
|
|
rc.bottom = abs(HEADER(m_pviDefFmt)->biHeight) ; // just make sure rect fields are +ve
|
|
m_pviDefFmt->rcSource = rc ;
|
|
m_pviDefFmt->rcTarget = rc ;
|
|
m_pviDefFmt->dwBitRate = MulDiv(HEADER(m_pviDefFmt)->biSizeImage,
|
|
80000000, li.LowPart) ;
|
|
m_pviDefFmt->dwBitErrorRate = 0 ;
|
|
m_pviDefFmt->AvgTimePerFrame = (LONGLONG) 333667L ; // => 29.97 fps
|
|
|
|
return NOERROR ;
|
|
}
|
|
|
|
|
|
//
|
|
// CheckInputType: Check if you can support the input data type
|
|
//
|
|
HRESULT CLine21DecFilter::CheckInputType(const CMediaType* pmtIn)
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("CLine21DecFilter::CheckInputType(0x%lx)"), pmtIn)) ;
|
|
// CAutoLock Lock(&m_csFilter) ; -- shouldn't as that causes deadlock
|
|
|
|
if (NULL == pmtIn)
|
|
{
|
|
DbgLog((LOG_TRACE, 3, TEXT("Rejecting: media type info is NULL"))) ;
|
|
return E_INVALIDARG ;
|
|
}
|
|
|
|
// We only support MEDIATYPE_AUXLine21Data and
|
|
// MEDIASUBTYPE_Line21_BytePair or MEDIASUBTYPE_Line21_GOPPacket
|
|
// or MEDIASUBTYPE_Line21_VBIRawData (never)
|
|
GUID SubTypeIn = *pmtIn->Subtype() ;
|
|
m_eSubTypeIDIn = MapGUIDToID(&SubTypeIn) ;
|
|
if (! (MEDIATYPE_AUXLine21Data == *pmtIn->Type() &&
|
|
ISSUBTYPEVALID(m_eSubTypeIDIn)) )
|
|
{
|
|
DbgLog((LOG_ERROR, 0, TEXT("WARNING: Rejecting invalid Line 21 Data subtype"))) ;
|
|
return E_INVALIDARG ;
|
|
}
|
|
|
|
// Check that this is a valid format type
|
|
if (FORMAT_VideoInfo == *pmtIn->FormatType())
|
|
{
|
|
ASSERT(m_pOutput != NULL) ;
|
|
|
|
//
|
|
// Make sure the given input format is valid. If not, reject it and use our
|
|
// own default format data.
|
|
//
|
|
if (! IsValidFormat(pmtIn->Format()) )
|
|
{
|
|
DbgLog((LOG_TRACE, 0, TEXT("Invalid format data given -- using our own format data."))) ;
|
|
if (NULL == m_pviDefFmt)
|
|
{
|
|
HRESULT hr = GetDefaultFormatInfo() ;
|
|
if (FAILED(hr))
|
|
{
|
|
DbgLog((LOG_ERROR, 0, TEXT("WARNING: Couldn't get default format data"))) ;
|
|
return hr ;
|
|
}
|
|
}
|
|
// We should fix the input mediatype too (with the default VideoInfo data).
|
|
m_pInput->CurrentMediaType().SetFormat((LPBYTE) m_pviDefFmt, m_dwDefFmtSize) ;
|
|
m_pOutput->CurrentMediaType().SetFormatType(pmtIn->FormatType()) ;
|
|
m_pOutput->CurrentMediaType().SetFormat((LPBYTE) m_pviDefFmt, m_dwDefFmtSize) ;
|
|
}
|
|
else // seems to be valid format spec.
|
|
{
|
|
//
|
|
// Get the specified input format info; we'll use the same for output
|
|
//
|
|
if (pmtIn->FormatLength() > 0) // only if there is some format data
|
|
{
|
|
m_pOutput->CurrentMediaType().SetFormatType(pmtIn->FormatType()) ;
|
|
m_pOutput->CurrentMediaType().SetFormat(pmtIn->Format(), pmtIn->FormatLength()) ;
|
|
}
|
|
else
|
|
{
|
|
DbgLog((LOG_ERROR, 0, TEXT("WARNING: FORMAT_VideoInfo and no format block specified."))) ;
|
|
// #if 0 // for now
|
|
return E_INVALIDARG ;
|
|
// #endif // #if 0
|
|
}
|
|
}
|
|
}
|
|
else if (GUID_NULL == *pmtIn->FormatType() || // wild card
|
|
FORMAT_None == *pmtIn->FormatType()) // no format
|
|
{
|
|
//
|
|
// input pin didn't get a format type info; use our own
|
|
//
|
|
DbgLog((LOG_TRACE, 3, TEXT("No format type specified -- using our own format type."))) ;
|
|
if (NULL == m_pviDefFmt)
|
|
{
|
|
HRESULT hr = GetDefaultFormatInfo() ;
|
|
if (FAILED(hr))
|
|
{
|
|
DbgLog((LOG_ERROR, 0, TEXT("WARNING: Couldn't get default format data"))) ;
|
|
return hr ;
|
|
}
|
|
}
|
|
m_pOutput->CurrentMediaType().SetFormat((LPBYTE) m_pviDefFmt, m_dwDefFmtSize) ;
|
|
}
|
|
else // something weird that we don't like
|
|
{
|
|
DbgLog((LOG_TRACE, 3, TEXT("Rejecting invalid format type"))) ;
|
|
// tell what input type too??
|
|
return E_INVALIDARG ;
|
|
}
|
|
|
|
// some more level 3 debug log here???
|
|
|
|
// We should branch based on what format type we got, because ..GOPPacket
|
|
// type needs to be unwrapped and parsed whereas the ..BytePair format
|
|
// is to be directly parsed.
|
|
|
|
// do we have a case for -- return VFW_E_TYPE_NOT_ACCEPTED ???
|
|
|
|
return NOERROR ;
|
|
}
|
|
|
|
|
|
//
|
|
// CheckTransform: check if this input to this output transform is supported
|
|
//
|
|
HRESULT CLine21DecFilter::CheckTransform(const CMediaType* pmtIn,
|
|
const CMediaType* pmtOut)
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("CLine21DecFilter::CheckTransform(0x%lx, 0x%lx)"),
|
|
pmtIn, pmtOut)) ;
|
|
// CAutoLock Lock(&m_csFilter) ; -- shouldn't as that causes deadlock
|
|
|
|
if (NULL == pmtIn || NULL == pmtOut)
|
|
{
|
|
DbgLog((LOG_TRACE, 3, TEXT("Rejecting: media type info is NULL"))) ;
|
|
return E_INVALIDARG ;
|
|
}
|
|
|
|
// We only support MEDIATYPE_AUXLine21Data and
|
|
// MEDIASUBTYPE_Line21_BytePair or MEDIASUBTYPE_Line21_GOPPacket
|
|
// or MEDIASUBTYPE_Line21_VBIRawData (never)
|
|
// Check that input is a valid subtype type
|
|
// and format is VideoInfo or None
|
|
GUID SubTypeIn = *pmtIn->Subtype() ;
|
|
m_eSubTypeIDIn = MapGUIDToID(&SubTypeIn) ;
|
|
if (! (MEDIATYPE_AUXLine21Data == *pmtIn->Type() && // line21 data type and...
|
|
ISSUBTYPEVALID(m_eSubTypeIDIn) && // valid subtype (bytepair/GOPPacket) and...
|
|
(FORMAT_VideoInfo == *pmtIn->FormatType() || // format VideoInfo or
|
|
FORMAT_None == *pmtIn->FormatType() || // format None (KS wild card) or
|
|
GUID_NULL == *pmtIn->FormatType())) ) // GUID Null (DShow wild card)
|
|
{
|
|
DbgLog((LOG_TRACE, 3, TEXT("Rejecting: input type not Line21 / subtype / formattype invalid"))) ;
|
|
return E_INVALIDARG ;
|
|
}
|
|
|
|
// and we only accept video as output
|
|
if (MEDIATYPE_Video != *pmtOut->Type())
|
|
{
|
|
DbgLog((LOG_TRACE, 3, TEXT("Rejecting: output type is not VIDEO"))) ;
|
|
return E_INVALIDARG ;
|
|
}
|
|
|
|
// check output is VIDEOINFO type
|
|
if (FORMAT_VideoInfo != *pmtOut->FormatType())
|
|
{
|
|
DbgLog((LOG_TRACE, 3, TEXT("Rejecting: output format type is not VIDEOINFO"))) ;
|
|
return E_INVALIDARG ;
|
|
}
|
|
|
|
//
|
|
// Verify that the output size specified by the input and output mediatype
|
|
// are acceptable.
|
|
//
|
|
if ( !IsValidFormat(pmtOut->Format()) || // invalid output format data OR
|
|
!m_L21Dec.IsSizeOK(HEADER(pmtOut->Format())) || // output size is NOT acceptable OR
|
|
(FORMAT_VideoInfo == *pmtIn->FormatType() && // valid input format type and...
|
|
IsValidFormat(pmtIn->Format()) && // valid input format data and...
|
|
!m_L21Dec.IsSizeOK(HEADER(pmtIn->Format()))) ) // output size is NOT acceptable
|
|
{
|
|
DbgLog((LOG_TRACE, 1, TEXT("Rejecting: Input/output-specified output size is unacceptable"))) ;
|
|
return E_INVALIDARG ;
|
|
}
|
|
|
|
#if 0
|
|
|
|
#define rcS1 ((VIDEOINFO *)(pmtOut->Format()))->rcSource
|
|
#define rcT1 ((VIDEOINFO *)(pmtOut->Format()))->rcTarget
|
|
|
|
DbgLog((LOG_TRACE, 3,
|
|
TEXT("Input Width x Height x Bitdepth: %ld x %ld x %ld"),
|
|
HEADER(pmtIn->Format())->biWidth,
|
|
HEADER(pmtIn->Format())->biHeight,
|
|
HEADER(pmtIn->Format())->biBitCount)) ;
|
|
DbgLog((LOG_TRACE, 3,
|
|
TEXT("Output Width x Height x Bitdepth: %ld x %ld x %ld"),
|
|
HEADER(pmtOut->Format())->biWidth,
|
|
HEADER(pmtOut->Format())->biHeight,
|
|
HEADER(pmtOut->Format())->biBitCount)) ;
|
|
DbgLog((LOG_TRACE, 3,
|
|
TEXT("rcSrc: (%ld, %ld, %ld, %ld)"),
|
|
rcS1.left, rcS1.top, rcS1.right, rcS1.bottom)) ;
|
|
DbgLog((LOG_TRACE, 3,
|
|
TEXT("rcDst: (%ld, %ld, %ld, %ld)"),
|
|
rcT1.left, rcT1.top, rcT1.right, rcT1.bottom)) ;
|
|
|
|
DWORD dwErr ;
|
|
|
|
// If we've been given rectangles, use What???
|
|
if (!IsRectEmpty(&rcS1) || !IsRectEmpty(&rcT1))
|
|
{
|
|
DbgLog((LOG_TRACE, 4, TEXT("Either source or dest rect is empty"))) ;
|
|
dwErr = 0 ; // what to do here??
|
|
}
|
|
else
|
|
{
|
|
DbgLog((LOG_TRACE, 4, TEXT("Source or dest rects are not empty")));
|
|
dwErr = 0 ; // what to do here??
|
|
}
|
|
|
|
if (dwErr != 0) // or what to check against??
|
|
{
|
|
DbgLog((LOG_ERROR, 1, TEXT("decoder rejected this transform"))) ;
|
|
return E_FAIL ;
|
|
}
|
|
|
|
#endif // #if 0
|
|
|
|
return NOERROR ;
|
|
}
|
|
|
|
|
|
//
|
|
// CompleteConnect: Overridden to know when a connection is made to this filter
|
|
//
|
|
HRESULT CLine21DecFilter::CompleteConnect(PIN_DIRECTION dir, IPin *pReceivePin)
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("CLine21DecFilter::CompleteConnect(%s, 0x%lx)"),
|
|
dir == PINDIR_INPUT ? TEXT("Input") : TEXT("Output"), pReceivePin)) ;
|
|
CAutoLock Lock(&m_csFilter) ;
|
|
|
|
LPBITMAPINFO lpbmi ;
|
|
HRESULT hr ;
|
|
|
|
if (PINDIR_OUTPUT == dir)
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("L21D output pin connecting to %s"), (LPCTSTR)CDisp(pReceivePin))) ;
|
|
|
|
//
|
|
// This version of the line21 decoder should NOT work with the VMR
|
|
//
|
|
IVMRVideoStreamControl *pVMRSC ;
|
|
hr = pReceivePin->QueryInterface(IID_IVMRVideoStreamControl, (LPVOID *) &pVMRSC) ;
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("Downstream input pin supports IVMR* interface"))) ;
|
|
pVMRSC->Release() ;
|
|
return E_FAIL ;
|
|
}
|
|
else
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("Downstream input pin does NOT support IVMR* interface"))) ;
|
|
}
|
|
|
|
//
|
|
// Now get the the output pin's mediatype and use that for our
|
|
// output size etc.
|
|
//
|
|
const CMediaType *pmt = &(m_pOutput->CurrentMediaType()) ;
|
|
ASSERT(MEDIATYPE_Video == *pmt->Type() &&
|
|
FORMAT_VideoInfo == *pmt->FormatType()) ;
|
|
m_mtOutput = *pmt ; // this is our output mediatype for now
|
|
if (pmt->FormatLength() > 0) // only if there is some format data
|
|
{
|
|
lpbmi = (LPBITMAPINFO) HEADER(((VIDEOINFO *)(pmt->Format()))) ;
|
|
ASSERT(lpbmi) ;
|
|
|
|
// Set the output format info coming from downstream
|
|
m_L21Dec.SetOutputOutFormat(lpbmi) ;
|
|
GetDefaultFormatInfo() ; // to pick any change in format data
|
|
|
|
//
|
|
// We are definitely not running/paused. So no need to delete/
|
|
// create output DIB section here at all.
|
|
//
|
|
|
|
//
|
|
// If are being connected to the OverlayMixer, we need to tell it
|
|
// that we are a transparent stream that covers the whole output
|
|
// window.
|
|
//
|
|
IMixerPinConfig *pMPC ;
|
|
hr = pReceivePin->QueryInterface(IID_IMixerPinConfig, (LPVOID *)&pMPC) ;
|
|
if (SUCCEEDED(hr) && pMPC)
|
|
{
|
|
DbgLog((LOG_TRACE, 3, TEXT("Receiving pin supports IMixerPinConfig"))) ;
|
|
hr = pMPC->SetStreamTransparent(TRUE) ;
|
|
ASSERT(SUCCEEDED(hr) || E_NOTIMPL == hr) ; // as Kapil says
|
|
hr = pMPC->SetRelativePosition(0, 0, 10000, 10000) ; // full window
|
|
ASSERT(SUCCEEDED(hr) || E_NOTIMPL == hr) ; // as Kapil says
|
|
hr = pMPC->SetAspectRatioMode(AM_ARMODE_STRETCHED_AS_PRIMARY) ; // aspect ratio same as primary
|
|
ASSERT(SUCCEEDED(hr) || hr == E_INVALIDARG) ; // as Kapil says
|
|
pMPC->Release() ; // done; let it go.
|
|
}
|
|
else
|
|
{
|
|
DbgLog((LOG_TRACE, 3, TEXT("Downstream pin doesn't support IMixerPinConfig"))) ;
|
|
}
|
|
}
|
|
|
|
return NOERROR ;
|
|
}
|
|
|
|
ASSERT(PINDIR_INPUT == dir) ;
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("L21D input pin connecting to %s"), (LPCTSTR)CDisp(pReceivePin))) ;
|
|
|
|
// const CMediaType *pmt = &(m_pInput->CurrentMediaType()) ;
|
|
AM_MEDIA_TYPE mt ;
|
|
hr = pReceivePin->ConnectionMediaType(&mt) ;
|
|
if (SUCCEEDED(hr)) // ONLY if upstream filter provides mediatype used in the connection
|
|
{
|
|
// If format type (and format data) has been specified then save it as
|
|
// input-side output format
|
|
if (FORMAT_VideoInfo == mt.formattype &&
|
|
mt.cbFormat > 0)
|
|
{
|
|
lpbmi = (LPBITMAPINFO) HEADER(((VIDEOINFO *)(mt.pbFormat))) ;
|
|
ASSERT(lpbmi) ;
|
|
|
|
// Store whatever output format info is specified by upstream filter
|
|
m_L21Dec.SetOutputInFormat(lpbmi) ;
|
|
GetDefaultFormatInfo() ; // to pick any change in format data
|
|
|
|
//
|
|
// We are definitely not running/paused. So no need to delete/
|
|
// create output DIB section here at all.
|
|
//
|
|
}
|
|
|
|
FreeMediaType(mt) ;
|
|
} // end of if ()
|
|
}
|
|
|
|
//
|
|
// We MUST clear the caption data buffers and any exisiting internal state
|
|
// now. This is most important in this cases where the filter has been
|
|
// used to decode some Line 21 data, disconnected from the source and then
|
|
// reconnected again to play another stream of data.
|
|
//
|
|
m_L21Dec.InitState() ;
|
|
m_L21Dec.InitColorNLastChar() ; // reset color and last char info
|
|
|
|
return NOERROR ;
|
|
}
|
|
|
|
|
|
//
|
|
// BreakConnect: Overridden to know when a connection is broken to our pin
|
|
//
|
|
HRESULT CLine21DecFilter::BreakConnect(PIN_DIRECTION dir)
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("CLine21DecFilter::BreakConnect(%s)"),
|
|
dir == PINDIR_INPUT ? TEXT("Input") : TEXT("Output"))) ;
|
|
CAutoLock Lock(&m_csFilter) ;
|
|
|
|
if (PINDIR_OUTPUT == dir)
|
|
{
|
|
// If not connected yet, just return (but indicate with S_FALSE)
|
|
if (! m_pOutput->IsConnected() )
|
|
return S_FALSE ;
|
|
|
|
m_L21Dec.SetOutputOutFormat(NULL) ; // no output format from downstream
|
|
GetDefaultFormatInfo() ; // to pick any change in format data
|
|
m_pbOutBuffer = NULL ; // locally cached pointer
|
|
m_L21Dec.SetOutputBuffer(NULL) ; // output buffer not available now
|
|
|
|
//
|
|
// NOTE 1: We are definitely not running/paused. So no need to delete/
|
|
// create output DIB section here.
|
|
//
|
|
|
|
//
|
|
// NOTE 2: We don't do CBaseOutputPin::BreakConnect(), because the
|
|
// base class code for CTransformOutputPin::BreakConnect() already
|
|
// does that.
|
|
//
|
|
return NOERROR ;
|
|
}
|
|
|
|
ASSERT(PINDIR_INPUT == dir) ;
|
|
|
|
// If not connected yet, just return (but indicate with S_FALSE)
|
|
if (! m_pInput->IsConnected() )
|
|
return S_FALSE ;
|
|
|
|
m_L21Dec.SetOutputInFormat(NULL) ; // no output format from upstream
|
|
GetDefaultFormatInfo() ; // to pick any change in format data
|
|
|
|
//
|
|
// NOTE 1: We are definitely not running/paused. So no need to delete/
|
|
// create output DIB section here.
|
|
//
|
|
|
|
//
|
|
// NOTE 2: We don't do CBaseOutputPin::BreakConnect(), because the
|
|
// base class code for CTransformOutputPin::BreakConnect() already
|
|
// does that.
|
|
//
|
|
return NOERROR ;
|
|
}
|
|
|
|
//
|
|
// SetMediaType: overriden to know when the media type is actually set
|
|
//
|
|
HRESULT CLine21DecFilter::SetMediaType(PIN_DIRECTION direction, const CMediaType *pmt)
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("CLine21DecFilter::SetMediaType(%s, 0x%lx)"),
|
|
direction == PINDIR_INPUT ? TEXT("Input") : TEXT("Output"), pmt)) ;
|
|
// CAutoLock Lock(&m_csFilter) ;
|
|
|
|
LPTSTR alpszFormatIDs[] = { TEXT("Invalid"), TEXT("BytePair"),
|
|
TEXT("GOPPacket"), TEXT("VBIRawData") } ;
|
|
|
|
if (PINDIR_OUTPUT == direction)
|
|
{
|
|
DbgLog((LOG_TRACE, 3, TEXT("Output type: %d x %d x %d"),
|
|
HEADER(m_pOutput->CurrentMediaType().Format())->biWidth,
|
|
HEADER(m_pOutput->CurrentMediaType().Format())->biHeight,
|
|
HEADER(m_pOutput->CurrentMediaType().Format())->biBitCount)) ;
|
|
return NOERROR ;
|
|
}
|
|
|
|
ASSERT(PINDIR_INPUT == direction) ;
|
|
DbgLog((LOG_TRACE, 3, TEXT("Input type: <%s>"),
|
|
alpszFormatIDs[MapGUIDToID(m_pInput->CurrentMediaType().Subtype())])) ;
|
|
|
|
if (m_pOutput && m_pOutput->IsConnected())
|
|
{
|
|
DbgLog((LOG_TRACE, 2, TEXT("*** Changing IN when OUT already connected"))) ;
|
|
DbgLog((LOG_TRACE, 2, TEXT("Reconnecting the output pin..."))) ;
|
|
return m_pGraph->Reconnect(m_pOutput) ;
|
|
}
|
|
|
|
return NOERROR ;
|
|
}
|
|
|
|
|
|
#if 0 // Quality Management is deferred for now as OvMixer always says (Flood, 1000)
|
|
|
|
//
|
|
// AlterQuality: overriden to handle quality messages and not pass them upstream
|
|
//
|
|
HRESULT CLine21DecFilter::AlterQuality(Quality q)
|
|
{
|
|
DbgLog((LOG_TRACE, 0, TEXT("QM: CLine21DecFilter::AlterQuality(%s, %ld)"),
|
|
Flood == q.Type ? TEXT("Flood") : TEXT("Famine"), q.Proportion)) ; // log trace=5
|
|
|
|
if (1000 == q.Proportion)
|
|
{
|
|
DbgLog((LOG_TRACE, 0, TEXT("QM: Quality is just right. Don't change anything."))) ;
|
|
return S_OK ;
|
|
}
|
|
|
|
if (Flood == q.Type) // Flood: too much output
|
|
{
|
|
if (q.Proportion > 500 && q.Proportion <= 900)
|
|
{
|
|
m_iSkipSamples += 1 ;
|
|
}
|
|
else if (q.Proportion > 300 && q.Proportion <= 500)
|
|
{
|
|
m_iSkipSamples += 2 ;
|
|
}
|
|
else if (q.Proportion <= 300)
|
|
{
|
|
m_iSkipSamples += 3 ;
|
|
}
|
|
m_iSkipSamples = min(m_iSkipSamples, 10) ; // at least 1 in 10 is shown
|
|
}
|
|
else // Famine: send more output
|
|
{
|
|
if (q.Proportion > 1200) // could take 20% more
|
|
{
|
|
m_iSkipSamples-- ;
|
|
if (m_iSkipSamples < 0)
|
|
m_iSkipSamples = 0 ;
|
|
}
|
|
}
|
|
|
|
DbgLog((LOG_TRACE, 0, TEXT("QM: Adjusted rate is %d samples are skipped."), m_iSkipSamples)) ;
|
|
return S_OK ;
|
|
}
|
|
#endif // #if 0 -- end of commented out AlterQuality() implementation
|
|
|
|
|
|
// Return our preferred output media types (in order)
|
|
// remember that we do not need to support all of these formats -
|
|
// if one is considered potentially suitable, our CheckTransform method
|
|
// will be called to check if it is acceptable right now.
|
|
// Remember that the enumerator calling this will stop enumeration as soon as
|
|
// it receives a S_FALSE return.
|
|
//
|
|
// GetMediaType: Get our preferred media type (in order)
|
|
//
|
|
HRESULT CLine21DecFilter::GetMediaType(int iPosition, CMediaType *pmt)
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("CLine21DecFilter::GetMediaType(%d, 0x%lx)"),
|
|
iPosition, pmt)) ;
|
|
CAutoLock Lock(&m_csFilter) ;
|
|
|
|
LARGE_INTEGER li ;
|
|
CMediaType cmt ;
|
|
LPBITMAPINFOHEADER lpbi ;
|
|
|
|
if (NULL == pmt)
|
|
{
|
|
DbgLog((LOG_TRACE, 3, TEXT("Media type is NULL, Sorry!!"))) ;
|
|
return E_INVALIDARG ;
|
|
}
|
|
|
|
// Output choices depend on the input connected
|
|
if (! m_pInput->CurrentMediaType().IsValid() )
|
|
{
|
|
DbgLog((LOG_TRACE, 3, TEXT("No input type set yet, Sorry!!"))) ;
|
|
return E_FAIL ;
|
|
}
|
|
|
|
if (iPosition < 0)
|
|
{
|
|
return E_INVALIDARG ;
|
|
}
|
|
|
|
// Find the format info specified in the input VideoInfo struct
|
|
cmt = m_pInput->CurrentMediaType() ;
|
|
BITMAPINFOHEADER bih ;
|
|
BOOL bOutKnown = (S_OK == m_L21Dec.GetOutputOutFormat(&bih)) ;
|
|
if (! bOutKnown )
|
|
GetOutputFormat(&bih) ;
|
|
|
|
BOOL bInKnown = NULL != cmt.Format() && IsValidFormat(cmt.Format()) ; // just to be sure
|
|
VIDEOINFOHEADER vih ;
|
|
if (bInKnown)
|
|
CopyMemory(&vih, (VIDEOINFOHEADER *)(cmt.Format()), sizeof(VIDEOINFOHEADER)) ;
|
|
|
|
// Offer the decoder's default output format (Video) first
|
|
switch (iPosition)
|
|
{
|
|
case 0: // RGB 8bpp
|
|
{
|
|
DbgLog((LOG_TRACE, 3, TEXT("Media Type 0: 8 bit RGB"))) ;
|
|
|
|
// First allocate enough space to hold the pertinent info
|
|
cmt.ReallocFormatBuffer(SIZE_PREHEADER + sizeof(BITMAPINFOHEADER) + SIZE_PALETTE);
|
|
|
|
// Use some info from input format and use our choices too
|
|
lpbi = HEADER(cmt.Format()) ;
|
|
if (!bOutKnown && bInKnown) // output format not known and input format spec-ed
|
|
CopyMemory(lpbi, &(vih.bmiHeader), sizeof(BITMAPINFOHEADER)) ;
|
|
else // if output format known or input format not spec-ed
|
|
CopyMemory(lpbi, &bih, sizeof(BITMAPINFOHEADER)) ;
|
|
lpbi->biBitCount = 8 ;
|
|
lpbi->biCompression = BI_RGB ;
|
|
lpbi->biSizeImage = DIBSIZE(*lpbi) ;
|
|
|
|
// Get some palette data from system/our own (for non-8 bpp)
|
|
m_L21Dec.GetPaletteForFormat(lpbi) ; // this sets biClrUsed member
|
|
|
|
cmt.SetType(&MEDIATYPE_Video) ;
|
|
cmt.SetSubtype(&MEDIASUBTYPE_RGB8) ;
|
|
|
|
break ;
|
|
}
|
|
|
|
case 1: // RGB 16bpp (555)
|
|
{
|
|
DbgLog((LOG_TRACE, 3, TEXT("Media Type 1: 16 bit RGB 555"))) ;
|
|
|
|
// First allocate enough space to hold the pertinent info
|
|
cmt.ReallocFormatBuffer(SIZE_PREHEADER + sizeof(BITMAPINFOHEADER));
|
|
|
|
// Use some info from input format and use our choices too
|
|
lpbi = HEADER(cmt.Format()) ;
|
|
if (!bOutKnown && bInKnown) // output format not known and input format spec-ed
|
|
CopyMemory(lpbi, &(vih.bmiHeader), sizeof(BITMAPINFOHEADER)) ;
|
|
else // if output format known or input format not spec-ed
|
|
CopyMemory(lpbi, &bih, sizeof(BITMAPINFOHEADER)) ;
|
|
lpbi->biBitCount = 16 ;
|
|
lpbi->biCompression = BI_RGB ;
|
|
lpbi->biSizeImage = DIBSIZE(*lpbi) ;
|
|
lpbi->biClrUsed = 0 ; // for true color modes
|
|
|
|
// Now set the output mediatype of type Video using the input
|
|
// format info
|
|
cmt.SetType(&MEDIATYPE_Video) ;
|
|
cmt.SetSubtype(&MEDIASUBTYPE_RGB555) ;
|
|
break ;
|
|
}
|
|
|
|
case 2: // 16bpp (565)
|
|
{
|
|
DbgLog((LOG_TRACE, 3, TEXT("Media Type 2: 16 bit RGB 565"))) ;
|
|
|
|
// First allocate enough space to hold the pertinent info
|
|
cmt.ReallocFormatBuffer(SIZE_PREHEADER + sizeof(BITMAPINFOHEADER) + SIZE_MASKS);
|
|
|
|
// Use some info from input format and use our choices too
|
|
lpbi = HEADER(cmt.Format()) ;
|
|
if (!bOutKnown && bInKnown) // output format not known and input format spec-ed
|
|
CopyMemory(lpbi, &(vih.bmiHeader), sizeof(BITMAPINFOHEADER)) ;
|
|
else // if output format known or input format not spec-ed
|
|
CopyMemory(lpbi, &bih, sizeof(BITMAPINFOHEADER)) ;
|
|
lpbi->biBitCount = 16 ;
|
|
lpbi->biCompression = BI_BITFIELDS ;
|
|
lpbi->biSizeImage = DIBSIZE(*lpbi) ;
|
|
lpbi->biClrUsed = 0 ; // for true color modes
|
|
|
|
// Set the masks too
|
|
DWORD *pdw = (DWORD *)(lpbi + 1) ;
|
|
pdw[iRED] = bits565[iRED] ;
|
|
pdw[iGREEN] = bits565[iGREEN] ;
|
|
pdw[iBLUE] = bits565[iBLUE] ;
|
|
|
|
cmt.SetType(&MEDIATYPE_Video) ;
|
|
cmt.SetSubtype(&MEDIASUBTYPE_RGB565) ;
|
|
break ;
|
|
}
|
|
|
|
case 3: // RGB 24bpp
|
|
{
|
|
DbgLog((LOG_TRACE, 3, TEXT("Media Type 3: 24 bit RGB"))) ;
|
|
|
|
// First allocate enough space to hold the pertinent info
|
|
cmt.ReallocFormatBuffer(SIZE_PREHEADER + sizeof(BITMAPINFOHEADER));
|
|
|
|
// Use some info from input format and use our choices too
|
|
lpbi = HEADER(cmt.Format()) ;
|
|
if (!bOutKnown && bInKnown) // output format not known and input format spec-ed
|
|
CopyMemory(lpbi, &(vih.bmiHeader), sizeof(BITMAPINFOHEADER)) ;
|
|
else // if output format known or input format not spec-ed
|
|
CopyMemory(lpbi, &bih, sizeof(BITMAPINFOHEADER)) ;
|
|
lpbi->biBitCount = 24 ;
|
|
lpbi->biCompression = BI_RGB ;
|
|
lpbi->biSizeImage = DIBSIZE(*lpbi) ;
|
|
lpbi->biClrUsed = 0 ; // for true color modes
|
|
|
|
cmt.SetType(&MEDIATYPE_Video) ;
|
|
cmt.SetSubtype(&MEDIASUBTYPE_RGB24) ;
|
|
break ;
|
|
}
|
|
|
|
case 4: // 32bpp
|
|
{
|
|
DbgLog((LOG_TRACE, 3, TEXT("Media Type 4: 32 bit RGB"))) ;
|
|
|
|
// First allocate enough space to hold the pertinent info
|
|
cmt.ReallocFormatBuffer(SIZE_PREHEADER + sizeof(BITMAPINFOHEADER) + SIZE_MASKS);
|
|
|
|
// Use some info from input format and use our choices too
|
|
lpbi = HEADER(cmt.Format()) ;
|
|
if (!bOutKnown && bInKnown) // output format not known and input format spec-ed
|
|
CopyMemory(lpbi, &(vih.bmiHeader), sizeof(BITMAPINFOHEADER)) ;
|
|
else // if output format known or input format not spec-ed
|
|
CopyMemory(lpbi, &bih, sizeof(BITMAPINFOHEADER)) ;
|
|
lpbi->biBitCount = 32 ;
|
|
lpbi->biCompression = BI_BITFIELDS ;
|
|
lpbi->biSizeImage = DIBSIZE(*lpbi) ;
|
|
lpbi->biClrUsed = 0 ; // for true color modes
|
|
|
|
// Set the masks too
|
|
DWORD *pdw = (DWORD *)(lpbi + 1) ;
|
|
pdw[iRED] = bits888[iRED] ;
|
|
pdw[iGREEN] = bits888[iGREEN] ;
|
|
pdw[iBLUE] = bits888[iBLUE] ;
|
|
|
|
cmt.SetType(&MEDIATYPE_Video) ;
|
|
cmt.SetSubtype(&MEDIASUBTYPE_RGB32) ;
|
|
break ;
|
|
}
|
|
|
|
default:
|
|
return VFW_S_NO_MORE_ITEMS ;
|
|
|
|
} // end of switch (iPosition)
|
|
|
|
// Now set the output formattype and sample size
|
|
cmt.SetSampleSize(lpbi->biSizeImage) ;
|
|
cmt.SetFormatType(&FORMAT_VideoInfo) ;
|
|
|
|
// The fields of VIDEOINFOHEADER needs to be filled now
|
|
if (! bInKnown ) // if the upstream filter didn't specify anything
|
|
{
|
|
RECT Rect ;
|
|
Rect.left = 0 ;
|
|
Rect.top = 0 ;
|
|
Rect.right = lpbi->biWidth ;
|
|
Rect.bottom = abs(lpbi->biHeight) ; // biHeight could be -ve, but rect fields are +ve
|
|
|
|
// We set some default values for time/frame, src and target rects etc. etc.
|
|
li.QuadPart = (LONGLONG) 333667 ; // => 29.97 fps
|
|
((VIDEOINFOHEADER *)(cmt.pbFormat))->AvgTimePerFrame = (LONGLONG) 333667 ; // => 29.97 fps
|
|
((VIDEOINFOHEADER *)(cmt.pbFormat))->rcSource = Rect ;
|
|
((VIDEOINFOHEADER *)(cmt.pbFormat))->rcTarget = Rect ;
|
|
}
|
|
else
|
|
li.QuadPart = vih.AvgTimePerFrame ;
|
|
|
|
((VIDEOINFOHEADER *)(cmt.pbFormat))->dwBitRate =
|
|
MulDiv(lpbi->biSizeImage, 80000000, li.LowPart) ;
|
|
((VIDEOINFOHEADER *)(cmt.pbFormat))->dwBitErrorRate = 0L ;
|
|
|
|
// Set temporal compression and copy the prepared data now
|
|
cmt.SetTemporalCompression(FALSE) ;
|
|
*pmt = cmt ;
|
|
|
|
return NOERROR ;
|
|
}
|
|
|
|
|
|
//
|
|
// DecideBufferSize: Called from CBaseOutputPin to prepare the allocator's
|
|
// count of buffers and sizes. It makes sense only when
|
|
// the input is connected.
|
|
//
|
|
HRESULT CLine21DecFilter::DecideBufferSize(IMemAllocator * pAllocator,
|
|
ALLOCATOR_PROPERTIES *pProperties)
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("CLine21DecFilter::DecideBufferSize(0x%lx, 0x%lx)"),
|
|
pAllocator, pProperties)) ;
|
|
CAutoLock Lock(&m_csFilter) ;
|
|
|
|
// Is the input pin connected
|
|
if (! m_pInput->IsConnected())
|
|
{
|
|
return E_UNEXPECTED ;
|
|
}
|
|
|
|
ASSERT(m_pOutput->CurrentMediaType().IsValid()) ;
|
|
ASSERT(pAllocator) ;
|
|
ASSERT(pProperties) ;
|
|
|
|
// set the size of buffers based on the expected output bitmap size, and
|
|
// the count of buffers to 1.
|
|
pProperties->cBuffers = 1 ;
|
|
pProperties->cbBuffer = m_pOutput->CurrentMediaType().GetSampleSize() ;
|
|
|
|
ASSERT(pProperties->cbBuffer) ;
|
|
|
|
ALLOCATOR_PROPERTIES Actual ;
|
|
HRESULT hr = pAllocator->SetProperties(pProperties, &Actual) ;
|
|
if (FAILED(hr))
|
|
{
|
|
DbgLog((LOG_ERROR, 0, TEXT("Error in SetProperties()"))) ;
|
|
return hr ;
|
|
}
|
|
|
|
if (Actual.cbBuffer < pProperties->cbBuffer ||
|
|
Actual.cBuffers < pProperties->cBuffers)
|
|
{
|
|
// can't use this allocator
|
|
DbgLog((LOG_ERROR, 0, TEXT("Can't use allocator (only %d buffer of size %d given)"),
|
|
Actual.cBuffers, Actual.cbBuffer)) ;
|
|
return E_INVALIDARG ;
|
|
}
|
|
DbgLog((LOG_TRACE, 3, TEXT(" %d buffers of %ld bytes each"),
|
|
pProperties->cBuffers, pProperties->cbBuffer)) ;
|
|
|
|
ASSERT(Actual.cbAlign == 1) ;
|
|
ASSERT(Actual.cbPrefix == 0) ;
|
|
|
|
return S_OK ;
|
|
}
|
|
|
|
// We're stopping the stream -- release output DC to reduce memory footprint etc.
|
|
STDMETHODIMP CLine21DecFilter::Stop(void)
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("CLine21DecFilter::Stop()"))) ;
|
|
CAutoLock Lock(&m_csFilter) ;
|
|
|
|
if (State_Running == m_State ||
|
|
State_Paused == m_State)
|
|
{
|
|
DbgLog((LOG_TRACE, 1, TEXT("We are stopping -- release output DC etc."))) ;
|
|
m_L21Dec.DeleteOutputDC() ; // release internal DIBSection now
|
|
m_L21Dec.SetOutputBuffer(NULL) ; // no output buffer anymore
|
|
m_pbOutBuffer = NULL ; // must be same as mL21Dec's m_pbOutBuffer
|
|
|
|
// Release the prev downstream pin's interface now
|
|
if (m_pPinDown)
|
|
{
|
|
m_pPinDown->Release() ;
|
|
m_pPinDown = NULL ;
|
|
}
|
|
}
|
|
|
|
HRESULT hr = CTransformFilter::Stop() ;
|
|
|
|
FreeTimer() ; // To be sure, we don't need a timer in case one is hanging around
|
|
return hr ;
|
|
}
|
|
|
|
// We're starting/stopping to stream -- based on that acquire or release output DC
|
|
// to reduce memory footprint etc.
|
|
STDMETHODIMP CLine21DecFilter::Pause(void)
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("CLine21DecFilter::Pause()"))) ;
|
|
CAutoLock Lock(&m_csFilter) ;
|
|
|
|
if (State_Stopped == m_State)
|
|
{
|
|
// Try to make sure we have at least 2 buffers
|
|
IMemAllocator *pAlloc;
|
|
if (SUCCEEDED(m_pInput->GetAllocator(&pAlloc))) {
|
|
ALLOCATOR_PROPERTIES props;
|
|
ALLOCATOR_PROPERTIES propsActual;
|
|
pAlloc->GetProperties(&props);
|
|
if (props.cBuffers < 4) {
|
|
props.cBuffers = 4;
|
|
props.cbBuffer = 200;
|
|
props.cbAlign = max(props.cbAlign, 1);
|
|
props.cbPrefix = 0;
|
|
HRESULT hr = pAlloc->SetProperties(&props, &propsActual);
|
|
DbgLog((LOG_TRACE, 2, TEXT("Setproperties returned %8.8X"), hr));
|
|
}
|
|
pAlloc->Release();
|
|
}
|
|
|
|
DbgLog((LOG_TRACE, 1, TEXT("We are running -- get output DC etc."))) ;
|
|
if (! m_L21Dec.CreateOutputDC() )
|
|
{
|
|
DbgLog((LOG_ERROR, 0, TEXT("WARNING: CLine21DecFilter::Pause() failed"))) ;
|
|
return E_FAIL ; // should at least fail to avoid faulting later
|
|
}
|
|
|
|
//
|
|
// Get actual key color and store it for future use.
|
|
//
|
|
GetActualColorKey() ;
|
|
|
|
m_L21Dec.FillOutputBuffer() ; // just to clear any existing junk
|
|
|
|
// We are starting a new play session; we do an exception to allow
|
|
// the first output sample to be sent down even though the byte pair
|
|
// wasn't valid for decoding.
|
|
m_bMustOutput = TRUE ; // we are pausing again for this new play session
|
|
m_bDiscontLast = FALSE ; // no discontinuity from prev session remembered
|
|
m_eGOP_CCType = GOP_CCTYPE_Unknown ; // reset GOP packet CC type
|
|
|
|
SetRect(&m_rectLastOutput, 0, 0, 0, 0) ; // start with no rect
|
|
|
|
// If we somehow didn't release the prev downstream pin's interface, do that now
|
|
if (m_pPinDown)
|
|
{
|
|
DbgLog((LOG_ERROR, 1, TEXT("WARNING: downstream pin interface wasn't released properly"))) ;
|
|
m_pPinDown->Release() ;
|
|
m_pPinDown = NULL ;
|
|
}
|
|
|
|
// Get the downstream pin's interface so that we can set rects on it later on
|
|
m_pOutput->ConnectedTo(&m_pPinDown) ;
|
|
if (NULL == m_pPinDown)
|
|
DbgLog((LOG_TRACE, 3, TEXT("Running w/o connecting our output pin!!!"))) ;
|
|
else
|
|
DbgLog((LOG_TRACE, 5, TEXT("L21D Output pin connected to %s"), (LPCTSTR)CDisp(m_pPinDown))) ;
|
|
|
|
#if 0 // No QM for now
|
|
// Reset the sample skipping count for QM handling
|
|
ResetSkipSamples() ;
|
|
#endif // #if 0
|
|
}
|
|
else if (State_Running == m_State)
|
|
{
|
|
DbgLog((LOG_TRACE, 1, TEXT("We are pausing from running"))) ;
|
|
//
|
|
// We are not sending output samples down anymore. So we don't need a
|
|
// timer for now.
|
|
//
|
|
FreeTimer() ;
|
|
}
|
|
|
|
return CTransformFilter::Pause() ;
|
|
}
|
|
|
|
//
|
|
// we don't send any data during PAUSE, so to avoid hanging renderers, we
|
|
// need to return VFW_S_CANT_CUE when paused
|
|
//
|
|
HRESULT CLine21DecFilter::GetState(DWORD dwMSecs, FILTER_STATE *State)
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("CLine21DecFilter::GetState()"))) ;
|
|
CAutoLock Lock(&m_csFilter) ;
|
|
|
|
UNREFERENCED_PARAMETER(dwMSecs);
|
|
CheckPointer(State,E_POINTER);
|
|
ValidateReadWritePtr(State,sizeof(FILTER_STATE));
|
|
|
|
*State = m_State;
|
|
if (m_State == State_Paused)
|
|
return VFW_S_CANT_CUE;
|
|
else
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
void CLine21DecFilter::GetActualColorKey(void)
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("CLine21DecFilter::GetActualColorKey()"))) ;
|
|
// Can't take the filter lock as it can cause deadlock.
|
|
|
|
//
|
|
// Does the pin connected to our output support IMixerPinConfig?
|
|
// If so, get the color key info and set the relative position;
|
|
// otherwise, it may be the Video Renderer and such -- use default
|
|
// color key based on current bitdepth.
|
|
//
|
|
DWORD dwPhysColor = -1 ;
|
|
IPin *pPin ;
|
|
HRESULT hr ;
|
|
hr = m_pOutput->ConnectedTo(&pPin) ;
|
|
if (SUCCEEDED(hr) && pPin)
|
|
{
|
|
IMixerPinConfig *pMPC ;
|
|
hr = pPin->QueryInterface(IID_IMixerPinConfig, (LPVOID *)&pMPC) ;
|
|
if (SUCCEEDED(hr) && pMPC)
|
|
{
|
|
// Temporary addition to track down any color key value change
|
|
DWORD dwOldPhysColor ;
|
|
m_L21Dec.GetBackgroundColor(&dwOldPhysColor) ;
|
|
DbgLog((LOG_TRACE, 3, TEXT("Downstream pin supports IMixerPinConfig"))) ;
|
|
hr = pMPC->GetColorKey(NULL, &dwPhysColor) ;
|
|
DbgLog((LOG_TRACE, 1, TEXT("GetActualColorKey() gave 0x%lx (old is 0x%lx)"),
|
|
dwPhysColor, dwOldPhysColor)) ;
|
|
// Kapil says that we can ignore this error as it's a bad error case and
|
|
// the OverlayMixer will take care of it by stopping this stream anyway.
|
|
if (FAILED(hr))
|
|
DbgLog((LOG_TRACE, 1, TEXT("IMixerPinConfig::GetColorKey() failed (Error 0x%lx)."), hr)) ;
|
|
pMPC->Release() ; // done; let it go.
|
|
}
|
|
else
|
|
{
|
|
DbgLog((LOG_TRACE, 3, TEXT("Downstream pin doesn't support IMixerPinConfig"))) ;
|
|
}
|
|
|
|
pPin->Release() ; // done with the pin
|
|
}
|
|
|
|
m_L21Dec.SetBackgroundColor(dwPhysColor) ;
|
|
}
|
|
|
|
|
|
AM_LINE21_CCSUBTYPEID CLine21DecFilter::MapGUIDToID(const GUID *pFormatIn)
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("CLine21DecFilter::MapGUIDToID(0x%lx)"), pFormatIn)) ;
|
|
|
|
if (MEDIASUBTYPE_Line21_BytePair == *pFormatIn)
|
|
return AM_L21_CCSUBTYPEID_BytePair ;
|
|
else if (MEDIASUBTYPE_Line21_GOPPacket == *pFormatIn)
|
|
return AM_L21_CCSUBTYPEID_GOPPacket ;
|
|
// else if (MEDIASUBTYPE_Line21_VBIRawData == *pFormatIn)
|
|
// return AM_L21_CCSUBTYPEID_VBIRawData ;
|
|
else
|
|
return AM_L21_CCSUBTYPEID_Invalid ;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// CMessageWindow class implementation
|
|
//
|
|
|
|
LPCTSTR gpszClassName = TEXT("L21DecMsgWnd") ;
|
|
|
|
CMessageWindow::CMessageWindow()
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("CMessageWindow::CMessageWindow() -- Instantiating message window"))) ;
|
|
|
|
m_hWnd = NULL ;
|
|
m_iCount = 0 ;
|
|
|
|
//
|
|
// Register message window class, only if it's not already registered
|
|
//
|
|
WNDCLASS wc ;
|
|
if (! GetClassInfo(GetModuleHandle(NULL), gpszClassName, &wc))
|
|
{
|
|
ZeroMemory(&wc, sizeof(wc)) ;
|
|
wc.lpfnWndProc = MsgWndProc ;
|
|
wc.hInstance = GetModuleHandle(NULL) ;
|
|
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1) ;
|
|
wc.lpszClassName = gpszClassName ;
|
|
if (0 == RegisterClass(&wc)) // Oops, just leave; we'll catch later...
|
|
{
|
|
DbgLog((LOG_ERROR, 0,
|
|
TEXT("ERROR: RegisterClass() for app class failed (Error %ld)"),
|
|
GetLastError())) ;
|
|
return ;
|
|
}
|
|
}
|
|
|
|
m_hWnd = CreateWindowEx(WS_EX_TOOLWINDOW, gpszClassName, TEXT(""),
|
|
WS_ICONIC, 0, 0, 1, 1, NULL, NULL,
|
|
GetModuleHandle(NULL), NULL);
|
|
if (NULL == m_hWnd) // Oops, just leave; we'll catch later...
|
|
{
|
|
DbgLog((LOG_ERROR, 0,
|
|
TEXT("ERROR: CreateWindowEx() failed (Error %ld)"),
|
|
GetLastError())) ;
|
|
return ;
|
|
}
|
|
|
|
ShowWindow(m_hWnd, SW_HIDE) ;
|
|
}
|
|
|
|
CMessageWindow::~CMessageWindow()
|
|
{
|
|
DbgLog((LOG_TRACE, 5, TEXT("CMessageWindow::~CMessageWindow() -- Destructing message window"))) ;
|
|
|
|
DWORD_PTR dwRes ;
|
|
if (0 == SendMessageTimeout(m_hWnd, WM_CLOSE, 0, 0, SMTO_NORMAL, 1000, &dwRes)) // 1 sec wait
|
|
{
|
|
ASSERT(0 == dwRes) ; // just to be informedd
|
|
DWORD dwErr = GetLastError() ;
|
|
DbgLog((LOG_ERROR, 0, TEXT("WARNING: SendMessageTimeOut() failed (Result=%lu, Error=%lu). Try again..."),
|
|
dwRes, dwErr)) ;
|
|
}
|
|
else
|
|
DbgLog((LOG_ERROR, 5, TEXT("SendMessageTimeOut() closed window"))) ;
|
|
|
|
#if 0
|
|
if (! UnregisterClass(gpszClassName, GetModuleHandle(NULL))) // if failed for some reason
|
|
{
|
|
DbgLog((LOG_ERROR, 0, TEXT("WARNING: UnregisterClass(L21DecMsgWnd) failed (Error %ld)"), GetLastError())) ;
|
|
ASSERT(FALSE) ; // just so that we know
|
|
}
|
|
#endif // #if 0
|
|
}
|
|
|
|
LRESULT CALLBACK CMessageWindow::MsgWndProc(HWND hWnd, UINT uMsg,
|
|
WPARAM wParam, LPARAM lParam)
|
|
{
|
|
switch (uMsg)
|
|
{
|
|
case WM_TIMER:
|
|
DbgLog((LOG_TRACE, 3, TEXT("MsgWndProc(, uMsg = WM_TIMER, wParam = 0x%0x, )"),
|
|
wParam)) ;
|
|
((CLine21DecFilter *) wParam)->TimerProc(hWnd, uMsg, wParam, 0 /* dwTime */) ;
|
|
return 0 ;
|
|
}
|
|
DbgLog((LOG_TRACE, 5, TEXT("MsgWndProc(, uMsg = 0x%x, wParam = 0x%0x, )"),
|
|
uMsg, wParam)) ;
|
|
return DefWindowProc(hWnd, uMsg, wParam, lParam) ;
|
|
}
|