//==========================================================================; // // 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 - 1998 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 #include #include #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->dwBytesUsed); pThis->m_iFrameSize = lpVHdr->dwBytesUsed; 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; } } // make sure it wasn't set again if we didn't notice a run->pause->run // transition ResetEvent(m_hEventRun); // 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; }