PREVIEW.CPP

//==========================================================================; 
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
// PURPOSE.
//
// Copyright (c) 1992 - 1997 Microsoft Corporation. All Rights Reserved.
//
//--------------------------------------------------------------------------;

/*

Methods for CVidPreview - the preview pin that doesn't use overlay

The basic premise here is that the driver only provides one
stream... we are FAKING this preview stream by only accepting
the same media type as our capture pin, and every once in a while
when the capture pin has some extra time it will send us a copy
of a frame to use as a preview frame. If we had hardware that
can do a h/w overlay, we would have used the overlay type of
preview pin. If we had hardware that can capture two streams
at once, we wouldn't need to fake a preview pin by duplicating
data going out the capture pin, but alas, we are not that lucky.

This is tricky to do. If the capture pin is streaming and using
a capture window to capture with, we can't make our own capture
window or make any calls to the capture pin's capture window (there
can be only one around at a time). So in this case, the capture pin
will send us a frame every once in a while for us to copy and deliver.
If the capture pin is NOT active, and does not have the capture
device open, then we need to create our own capture window and use
it to capture frames and deliver them. We will be able to switch
back and forth.

This pin supports CBaseStreamControl (and thus IAMStreamControl)
for turning preview on and off while the graph is running
*/

#include <streams.h>
#include <mmsystem.h>
#include <vfw.h>
#include "vidcap.h"

// CVidPreview constructor
//
CVidPreview::CVidPreview(TCHAR *pObjectName, CVidCap *pCapture,
HRESULT * phr, LPCWSTR pName)
:
CBaseOutputPin(pObjectName, pCapture, pCapture->pStateLock(), phr, pName),
m_pCap(pCapture),
m_fRunning(FALSE),
m_hThread(NULL),
m_tid(0),
m_hEventRun(NULL),
m_dwAdvise(0),
m_fCapturing(FALSE),
m_hEventActiveChanged(NULL),
m_hEventFrameValid(NULL),
m_lpFrame(NULL),
m_iFrameSize(0),
m_fLastSampleDiscarded(FALSE),
m_fFrameValid(FALSE),
m_hwndCap(NULL)
{
DbgLog((LOG_TRACE,1,TEXT("CVidPreview constructor")));
ASSERT(pCapture);
}


CVidPreview::~CVidPreview()
{
DbgLog((LOG_TRACE,1,TEXT("*Destroying the Preview pin")));
// we left one of these hanging around
if (m_hwndCap)
DestroyCaptureWindow(m_hwndCap);
};


STDMETHODIMP CVidPreview::NonDelegatingQueryInterface(REFIID riid, void ** ppv)
{
if (riid == IID_IAMStreamControl) {
return GetInterface((LPUNKNOWN)(IAMStreamControl *)this, ppv);
} else if (riid == IID_IKsPropertySet) {
return GetInterface((LPUNKNOWN)(IKsPropertySet *)this, ppv);
}

return CBaseOutputPin::NonDelegatingQueryInterface(riid, ppv);
}


// we can only allow being connected with the same media type as the
// capture pin. It may be giving us the frames to deliver.
//
HRESULT CVidPreview::GetMediaType(int iPosition, CMediaType *pmt)
{
DbgLog((LOG_TRACE,3,TEXT("CVidPreview::GetMediaType #%d"), iPosition));

if (iPosition < 0)
return E_INVALIDARG;
if (iPosition > 0)
return VFW_S_NO_MORE_ITEMS;

// we preview the same format as we capture
return m_pCap->m_pCapturePin->GetMediaType(pmt);
}


// We only accept what the capture pin would accept.
//
HRESULT CVidPreview::CheckMediaType(const CMediaType *pMediaType)
{
DbgLog((LOG_TRACE,3,TEXT("CVidPreview::CheckMediaType")));

// Only accept what our capture pin is providing. I will not switch
// our capture pin over to a new format just because somebody changes
// the preview pin.
return m_pCap->m_pCapturePin->CheckMediaType(pMediaType);
}


// we are being RUN. Time to start previewing
//
HRESULT CVidPreview::ActiveRun(REFERENCE_TIME tStart)
{
DbgLog((LOG_TRACE,2,TEXT("CVidPreview Pause->Run")));

ASSERT(IsConnected());

m_fRunning = TRUE;
m_rtRun = tStart;

// tell our thread to start previewing
SetEvent(m_hEventRun);

return NOERROR;
}


// we have stopped running. Our thread will stop previewing
//
HRESULT CVidPreview::ActivePause()
{
DbgLog((LOG_TRACE,2,TEXT("CVidPreview Run->Pause")));

m_fRunning = FALSE;

return NOERROR;
}


// we are starting to stream. Time to create a thread to do preview
//
HRESULT CVidPreview::Active()
{
DbgLog((LOG_TRACE,2,TEXT("CVidPreview Stop->Pause")));

ASSERT(IsConnected());

// This event tells the thread when we are RUN or STOPPED
m_hEventRun = CreateEvent(NULL, TRUE, FALSE, NULL);
if (!m_hEventRun) {
DbgLog((LOG_ERROR,1,TEXT("Can't create Run event")));
return E_OUTOFMEMORY;
}

// This event is set by the thread when it notices the capture pin
// going from inactive->active or vv. We delay returning from
// CapturePinActive() until the thread notices, because the capture
// pin will actually go active after CapturePinActive returns and
// our thread has to have closed the capture window by then.
m_hEventActiveChanged = CreateEvent(NULL, TRUE, FALSE, NULL);
if (!m_hEventActiveChanged) {
DbgLog((LOG_ERROR,1,TEXT("Can't create ActiveChanged event")));
return E_OUTOFMEMORY;
}

// This event tells the thread that a frame is ready to deliver. It
// might have come from the capture pin, or we might have captured it
// ourself.
m_hEventFrameValid = CreateEvent(NULL, TRUE, FALSE, NULL);
if (!m_hEventFrameValid) {
DbgLog((LOG_ERROR,1,TEXT("Can't create FrameValid event")));
return E_OUTOFMEMORY;
}

m_EventAdvise.Reset();
m_fFrameValid = FALSE;

m_hThread = CreateThread(NULL, 0, CVidPreview::ThreadProcInit, this,
0, &m_tid);
if (!m_hThread) {
DbgLog((LOG_ERROR,1,TEXT("Can't create Preview thread")));
return E_OUTOFMEMORY;
}

// now that we're streaming, we need a capture window to use, unless
// of course, our capture pin is active too, in which case we'll use
// its capture window since there can be only one.
if (!m_fCapturing && !m_hwndCap) {
m_hwndCap = CreateCaptureWindow();
ASSERT(m_hwndCap);
}

return CBaseOutputPin::Active();
}


// our pin has stopped streaming. Time to kill the thread.
//
HRESULT CVidPreview::Inactive()
{
DbgLog((LOG_TRACE,2,TEXT("CVidPreview Pause->Stop")));

ASSERT(IsConnected());

// tell our thread to give up and die
SetEvent(m_hEventRun);
SetEvent(m_hEventFrameValid);
SetEvent(m_hEventActiveChanged);

// If our thread is stuck in GetDeliveryBuffer, only this will release it
HRESULT hr = CBaseOutputPin::Inactive();

// We're waiting for an advise that will now never come
if (m_pCap->m_pClock && m_dwAdvise) {
m_pCap->m_pClock->Unadvise(m_dwAdvise);
m_EventAdvise.Set();
}

CloseHandle(m_hEventRun);
CloseHandle(m_hEventActiveChanged);
CloseHandle(m_hEventFrameValid);
m_hEventRun = NULL;
m_hEventActiveChanged = NULL;
m_hEventFrameValid = NULL;

// when our main thread shuts down the capture thread, the capture
// thread will destroy the capture window it made, and this will cause
// USER to send messages to our main thread, and if we're blocked
// waiting for the capture thread to go away, we will deadlock
// preventing user from sending us the messages, and thus our thread
// will never go away. We need to dispatch messages while waiting
// !!! It's more efficient to use MsgWaitForMultipleObjects, I know.
while (WaitForSingleObject(m_hThread, 50) == WAIT_TIMEOUT) {
MSG msg;
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
CloseHandle(m_hThread);
m_tid = 0;
m_hThread = NULL;

// now that we've stopped streaming, the capture pin may need to open
// the device to connect, etc, so we better let it go
if (m_hwndCap)
DestroyCaptureWindow(m_hwndCap);
m_hwndCap = NULL;

return hr;
}


// we want 1 buffer big enough to hold a frame of the type that the capture
// pin is capturing. We do some fancy math to align the buffers if the
// pin we are connected to requires it.
//
HRESULT CVidPreview::DecideBufferSize(IMemAllocator * pAllocator, ALLOCATOR_PROPERTIES *pProperties)
{
DbgLog((LOG_TRACE,2,TEXT("CVidPreview DecideBufferSize")));

ASSERT(pAllocator);
ASSERT(pProperties);

// !!! more preview buffers?
if (pProperties->cBuffers < 1)
pProperties->cBuffers = 1;

if (pProperties->cbAlign == 0)
pProperties->cbAlign = 1;

// This is how big we need each buffer to be
CMediaType cmt;
HRESULT hr = GetMediaType(0, &cmt);
if (hr != S_OK)
return hr;
pProperties->cbBuffer = max(pProperties->cbBuffer,
(long)(HEADER(cmt.Format())->biSizeImage));

// Make the prefix + buffer size meet the alignment restriction
pProperties->cbBuffer = (long)ALIGNUP(pProperties->cbBuffer +
pProperties->cbPrefix, pProperties->cbAlign) -
pProperties->cbPrefix;

ASSERT(pProperties->cbBuffer);

DbgLog((LOG_TRACE,2,TEXT("Preview: %d buffers, prefix %d size %d align %d"),
pProperties->cBuffers, pProperties->cbPrefix,
pProperties->cbBuffer,
pProperties->cbAlign));

ALLOCATOR_PROPERTIES Actual;
return pAllocator->SetProperties(pProperties,&Actual);

// !!! Are we sure we'll be happy with this?

}


// I know my preview sucks. Stop telling me. :-)
HRESULT CVidPreview::Notify(IBaseFilter *pFilter, Quality q)
{
return NOERROR;
}


// The capture pin is going active ==> We must shut down our capture window
// The capture pin is inactive ==> We need our own capture window now
//
HRESULT CVidPreview::CapturePinActive(BOOL fActive)
{
DbgLog((LOG_TRACE,2,TEXT("Capture pin says Active=%d"), fActive));

if (fActive == m_fCapturing)
return S_OK;
m_fCapturing = fActive;

// the capture pin will want to use the h/w. Close our capture window
if (fActive && m_hwndCap) {
DestroyCaptureWindow(m_hwndCap);
m_hwndCap = NULL;
}

// don't make a capture window if we're stopped and don't need it. We'll
// make one when we're activated
if (!fActive && !m_hwndCap && m_hThread) {
m_hwndCap = CreateCaptureWindow();
ASSERT(m_hwndCap);
}

// stop thread from waiting for us to send a valid frame - no more to come
// it will hang waiting forever if we don't do this
SetEvent(m_hEventFrameValid);

// wait until our worker thread notices the difference
if (m_fRunning)
WaitForSingleObject(m_hEventActiveChanged, INFINITE);
ResetEvent(m_hEventActiveChanged);

return S_OK;
}


// The capture pin is sending us a frame to preview
//
HRESULT CVidPreview::ReceivePreviewFrame(LPVOID lpFrame, int iSize)
{
// I'm not the least bit interested in previewing right now, or
// we haven't used the last one yet, or we don't have a place to put it
if (!m_fRunning || m_fFrameValid || m_lpFrame == NULL) {
//DbgLog((LOG_TRACE,4,TEXT("Not interested")));
return S_OK;
}

DbgLog((LOG_TRACE,4,TEXT("Capture pin is giving us a preview frame")));

// !!! can't avoid mem copy without using our own allocator
// !!! we do this copy memory even if preview pin is OFF (IAMStreamControl)
// because we can't risk blocking this call by calling CheckStreamState
CopyMemory(m_lpFrame, lpFrame, iSize);
m_iFrameSize = iSize;
m_fFrameValid = TRUE;
SetEvent(m_hEventFrameValid);
return S_OK;
}


// make a capture window for our preview pin
//
HWND CVidPreview::CreateCaptureWindow()
{
BOOL bErr;

DbgLog((LOG_TRACE,2,TEXT("PREVIEW creating a capture window")));

HWND hwndCapture; // The window to return

hwndCapture = capCreateCaptureWindow(NULL, 0,
0, 0, 150, 150,
NULL, 0 /* ID */);

if (!hwndCapture) {
DbgLog((LOG_ERROR|LOG_TRACE, 1, TEXT("CAP Window could not be created") ));
return NULL;
}

bErr = capDriverConnect(hwndCapture,
m_pCap->m_pCapturePin->m_uiDriverIndex);
if (!bErr) {
DestroyWindow(hwndCapture);
DbgLog((LOG_ERROR|LOG_TRACE, 1, TEXT("Driver failed to connect") ) );
return NULL;
}

// we will be grabbing single frames
capSetCallbackOnFrame(hwndCapture, &VideoCallback);

SetWindowLong(hwndCapture, GWL_USERDATA, (LONG) this);

return hwndCapture;
}


// Disconnect the driver before destroying the window.
//
BOOL CVidPreview::DestroyCaptureWindow(HWND hwnd)
{

ASSERT(hwnd != NULL);

DbgLog((LOG_TRACE,2,TEXT("PREVIEW destroying the capture window")));

// !!! why is this failing?
BOOL bDriverDisconnected = capDriverDisconnect(hwnd);
//DbgLog((LOG_TRACE,2,TEXT("Driver disconnect: %x"), bDriverDisconnected));

BOOL bWindowGone = DestroyWindow(hwnd);
//DbgLog((LOG_TRACE,2,TEXT("Window destroy: %x"), bWindowGone));

return (bDriverDisconnected && bWindowGone);
}


//
// VideoCallback
//
// The AVICap Video callback. Keep a copy of the buffer we are given
// May be called after the worker thread, or even the pin has gone away,
// depending on AVICap's internal timing. Therefore be very careful with the
// pointers we use.
//
LRESULT CALLBACK CVidPreview::VideoCallback(HWND hwnd, LPVIDEOHDR lpVHdr)
{

CVidPreview *pThis = (CVidPreview *)GetWindowLong(hwnd, GWL_USERDATA);
ASSERT(pThis);

// I'm not the least bit interested in previewing right now, or
// we haven't used the last one yet, or we don't have a place to put it
if (!pThis->m_fRunning || pThis->m_fFrameValid ||
pThis->m_lpFrame == NULL) {
//DbgLog((LOG_TRACE,4,TEXT("Not interested")));
return S_OK;
}

DbgLog((LOG_TRACE,4,TEXT("capGrabFrame callback got a preview frame")));

// !!! can't avoid mem copy without using our own allocator
// !!! we do this copy memory even if preview pin is OFF (IAMStreamControl)
// because we can't risk blocking this call by calling CheckStreamState
CopyMemory(pThis->m_lpFrame, lpVHdr->lpData, lpVHdr->dwBufferLength);
pThis->m_iFrameSize = lpVHdr->dwBufferLength;
pThis->m_fFrameValid = TRUE;
SetEvent(pThis->m_hEventFrameValid);
return S_OK;
}


DWORD WINAPI CVidPreview::ThreadProcInit(void *pv)
{
CVidPreview *pThis = (CVidPreview *)pv;
return pThis->ThreadProc();
}


DWORD CVidPreview::ThreadProc()
{
IMediaSample *pSample;
CRefTime rtStart, rtEnd;
HRESULT hr;
BOOL fCaptureActive = m_fCapturing;
int iWait;
HANDLE hWait[2] = {m_hEventFrameValid, m_hEventRun};

DbgLog((LOG_TRACE,2,TEXT("CVidPreview ThreadProc")));

// Send preview frames as long as we're running. Die when not streaming
while (1) {

// only preview while running
if (!m_fRunning) {
DbgLog((LOG_TRACE,3,TEXT("Preview thread waiting for RUN")));
WaitForSingleObject(m_hEventRun, INFINITE);
DbgLog((LOG_TRACE,3,TEXT("Preview thread got RUN")));
}
ResetEvent(m_hEventRun);

// if we stopped instead of ran
if (!m_fRunning)
break;

while (m_fRunning) {
hr = GetDeliveryBuffer(&pSample, NULL, NULL, 0);
if (FAILED(hr))
break;
hr = pSample->GetPointer((LPBYTE *)&m_lpFrame);
if (FAILED(hr))
break;

// try to notice if the capture pin starts/stops streaming
if (m_fCapturing != fCaptureActive) {
DbgLog((LOG_TRACE,3,TEXT("Preview thread noticed Active=%d"),
m_fCapturing));
SetEvent(m_hEventActiveChanged);
fCaptureActive = m_fCapturing;
}

// the capture pin will send us a frame
if (fCaptureActive) {
DbgLog((LOG_TRACE,4,TEXT("PREVIEW using capture picture")));

// m_hEventFrameValid, m_hEventRun
iWait = WaitForMultipleObjects(2, hWait, FALSE, INFINITE);

// time for our thread to die - don't reset the event because
// we may need it to fire when we break out of this loop
if (iWait != WAIT_OBJECT_0) {
DbgLog((LOG_TRACE,2,TEXT("Wait for streaming pic abort1")));
m_lpFrame = NULL;// please don't write here anymore
pSample->Release();
continue;
}

// the streaming pin stopped being active... switch again
if (!m_fFrameValid) {
DbgLog((LOG_TRACE,2,TEXT("Wait for streaming pic abort2")));
ResetEvent(m_hEventFrameValid);
m_lpFrame = NULL;// please don't write here anymore
pSample->Release();
continue;
}

ResetEvent(m_hEventFrameValid);
pSample->SetActualDataLength(m_iFrameSize);

// we have our own capture window and are getting our own frames
} else {
DbgLog((LOG_TRACE,4,TEXT("PREVIEW using capGrabFrame")));

// get a frame, and give it to our callback
capGrabFrame(m_hwndCap);

// m_hEventFrameValid, m_hEventRun
// wait for our callback to get the frame
iWait = WaitForMultipleObjects(2, hWait, FALSE, INFINITE);

// time for our thread to die - don't reset the event because
// we may need it to fire when we break out of this loop
if (iWait != WAIT_OBJECT_0) {
DbgLog((LOG_TRACE,2,TEXT("Wait for capGrabFrame abort1")));
m_lpFrame = NULL;// please don't write here anymore
pSample->Release();
continue;
}

// the streaming pin stopped being active... switch again
if (!m_fFrameValid) {
DbgLog((LOG_TRACE,2,TEXT("Wait for capGrabFrame abort2")));
ResetEvent(m_hEventFrameValid);
m_lpFrame = NULL;// please don't write here anymore
pSample->Release();
continue;
}

ResetEvent(m_hEventFrameValid);
pSample->SetActualDataLength(m_iFrameSize);
}

// time stamp the sample
if (m_pCap->m_pClock) {
m_pCap->m_pClock->GetTime((REFERENCE_TIME *)&rtStart);
rtStart = rtStart - m_pCap->m_tStart;
if (m_mt.IsValid() &&
((VIDEOINFOHEADER *)m_mt.Format())->AvgTimePerFrame)
rtEnd = rtStart +
((VIDEOINFOHEADER *)m_mt.Format())->AvgTimePerFrame;
else
rtEnd = rtStart + 666666;// assume 15fps

// !!! NO TIME STAMPS for preview unless we know the latency
// of the graph... we could drop every frame needlessly since
// they'll all arrive at the renderer late if we're software
// decoding these frames.
//
// We only send another preview frame when this one is done,
// so we won't get a backup if decoding is slow.
//
// Actually, adding a latency time would still be broken
// if the latency was > 1 frame length, because the renderer
// would hold on to the sample until past the time for the
// next frame, and we wouldn't send out the next preview frame
// as soon as we should, and our preview frame rate would suffer
//
// But besides all that, we really need time stamps for
// the stream control stuff to work, so we'll have to live
// with preview frame rates being inferior if we have an
// oustanding stream control request.

AM_STREAM_INFO am;
GetInfo(&am);
if (am.dwFlags & AM_STREAM_INFO_START_DEFINED ||
am.dwFlags & AM_STREAM_INFO_STOP_DEFINED) {
//DbgLog((LOG_TRACE,0,TEXT("TIME STAMPING ANYWAY")));
pSample->SetTime((REFERENCE_TIME *)&rtStart,
(REFERENCE_TIME *)&rtEnd);
}
}

// This is important so ReceivePreviewFrame doesn't screw up
m_lpFrame = NULL;// please don't write here anymore
m_fFrameValid = FALSE;// next time m_lpFrame is set, Ok to write

int iStreamState = CheckStreamState(pSample);
pSample->SetDiscontinuity(FALSE);
if (iStreamState == STREAM_FLOWING) {
DbgLog((LOG_TRACE,4,TEXT("*PREV Sending frame at %d"), (int)rtStart));
if (m_fLastSampleDiscarded)
pSample->SetDiscontinuity(TRUE);
} else {
DbgLog((LOG_TRACE,4,TEXT("*PREVIEW Discarding frame at %d"),
(int)rtStart));
m_fLastSampleDiscarded = TRUE;
}
if (iStreamState == STREAM_FLOWING) {
pSample->SetSyncPoint(TRUE);// !!! not necessarily
pSample->SetPreroll(FALSE);
DbgLog((LOG_TRACE,4,TEXT("*Delivering a preview frame")));
Deliver(pSample);
}
pSample->Release();

// if previewing ourself, wait for time till next frame
// !!! capture pin may wait on this to become active
if (!fCaptureActive && m_pCap->m_pClock) {
hr = m_pCap->m_pClock->AdviseTime(m_rtRun, rtEnd,
(HEVENT)(HANDLE) m_EventAdvise, &m_dwAdvise);
if (SUCCEEDED(hr)) {
m_EventAdvise.Wait();
}
m_dwAdvise = 0;
}
}
// just in case
SetEvent(m_hEventActiveChanged);
}

DbgLog((LOG_TRACE,2,TEXT("CVidPreview ThreadProc is dead")));
return 0;
}


//
// PIN CATEGORIES - let the world know that we are a PREVIEW pin
//

HRESULT CVidPreview::Set(REFGUID guidPropSet, DWORD dwPropID, LPVOID pInstanceData, DWORD cbInstanceData, LPVOID pPropData, DWORD cbPropData)
{
return E_NOTIMPL;
}

// To get a property, the caller allocates a buffer which the called
// function fills in. To determine necessary buffer size, call Get with
// pPropData=NULL and cbPropData=0.
HRESULT CVidPreview::Get(REFGUID guidPropSet, DWORD dwPropID, LPVOID pInstanceData, DWORD cbInstanceData, LPVOID pPropData, DWORD cbPropData, DWORD *pcbReturned)
{
if (guidPropSet != AMPROPSETID_Pin)
return E_PROP_SET_UNSUPPORTED;

if (dwPropID != AMPROPERTY_PIN_CATEGORY)
return E_PROP_ID_UNSUPPORTED;

if (pPropData == NULL && pcbReturned == NULL)
return E_POINTER;

if (pcbReturned)
*pcbReturned = sizeof(GUID);

if (pPropData == NULL)
return S_OK;

if (cbPropData < sizeof(GUID))
return E_UNEXPECTED;

*(GUID *)pPropData = PIN_CATEGORY_PREVIEW;
return S_OK;
}


// QuerySupported must either return E_NOTIMPL or correctly indicate
// if getting or setting the property set and property is supported.
// S_OK indicates the property set and property ID combination is
HRESULT CVidPreview::QuerySupported(REFGUID guidPropSet, DWORD dwPropID, DWORD *pTypeSupport)
{
if (guidPropSet != AMPROPSETID_Pin)
return E_PROP_SET_UNSUPPORTED;

if (dwPropID != AMPROPERTY_PIN_CATEGORY)
return E_PROP_ID_UNSUPPORTED;

if (pTypeSupport)
*pTypeSupport = KSPROPERTY_SUPPORT_GET;
return S_OK;
}