761 lines
16 KiB
C++
761 lines
16 KiB
C++
//////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// capture.cpp: Manages video capture.
|
|
//
|
|
// 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) Microsoft Corporation. All rights reserved.
|
|
//
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
#include <new>
|
|
#include <windows.h>
|
|
#include <mfapi.h>
|
|
#include <mfidl.h>
|
|
#include <mfreadwrite.h>
|
|
#include <Wmcodecdsp.h>
|
|
#include <assert.h>
|
|
#include <Dbt.h>
|
|
#include <shlwapi.h>
|
|
|
|
template <class T> void SafeRelease(T **ppT)
|
|
{
|
|
if (*ppT)
|
|
{
|
|
(*ppT)->Release();
|
|
*ppT = NULL;
|
|
}
|
|
}
|
|
|
|
#include "capture.h"
|
|
|
|
HRESULT CopyAttribute(IMFAttributes *pSrc, IMFAttributes *pDest, const GUID& key);
|
|
|
|
|
|
void DeviceList::Clear()
|
|
{
|
|
for (UINT32 i = 0; i < m_cDevices; i++)
|
|
{
|
|
SafeRelease(&m_ppDevices[i]);
|
|
}
|
|
CoTaskMemFree(m_ppDevices);
|
|
m_ppDevices = NULL;
|
|
|
|
m_cDevices = 0;
|
|
}
|
|
|
|
HRESULT DeviceList::EnumerateDevices()
|
|
{
|
|
HRESULT hr = S_OK;
|
|
IMFAttributes *pAttributes = NULL;
|
|
|
|
Clear();
|
|
|
|
// Initialize an attribute store. We will use this to
|
|
// specify the enumeration parameters.
|
|
|
|
hr = MFCreateAttributes(&pAttributes, 1);
|
|
|
|
// Ask for source type = video capture devices
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = pAttributes->SetGUID(
|
|
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
|
|
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID
|
|
);
|
|
}
|
|
|
|
// Enumerate devices.
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = MFEnumDeviceSources(pAttributes, &m_ppDevices, &m_cDevices);
|
|
}
|
|
|
|
SafeRelease(&pAttributes);
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
HRESULT DeviceList::GetDevice(UINT32 index, IMFActivate **ppActivate)
|
|
{
|
|
if (index >= Count())
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
*ppActivate = m_ppDevices[index];
|
|
(*ppActivate)->AddRef();
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT DeviceList::GetDeviceName(UINT32 index, WCHAR **ppszName)
|
|
{
|
|
if (index >= Count())
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
hr = m_ppDevices[index]->GetAllocatedString(
|
|
MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME,
|
|
ppszName,
|
|
NULL
|
|
);
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// CreateInstance
|
|
//
|
|
// Static class method to create the CCapture object.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CCapture::CreateInstance(
|
|
HWND hwnd, // Handle to the window to receive events
|
|
CCapture **ppCapture // Receives a pointer to the CCapture object.
|
|
)
|
|
{
|
|
if (ppCapture == NULL)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
CCapture *pCapture = new (std::nothrow) CCapture(hwnd);
|
|
|
|
if (pCapture == NULL)
|
|
{
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
// The CCapture constructor sets the ref count to 1.
|
|
*ppCapture = pCapture;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// constructor
|
|
//-------------------------------------------------------------------
|
|
|
|
CCapture::CCapture(HWND hwnd) :
|
|
m_pReader(NULL),
|
|
m_pWriter(NULL),
|
|
m_hwndEvent(hwnd),
|
|
m_nRefCount(1),
|
|
m_bFirstSample(FALSE),
|
|
m_llBaseTime(0),
|
|
m_pwszSymbolicLink(NULL)
|
|
{
|
|
InitializeCriticalSection(&m_critsec);
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// destructor
|
|
//-------------------------------------------------------------------
|
|
|
|
CCapture::~CCapture()
|
|
{
|
|
assert(m_pReader == NULL);
|
|
assert(m_pWriter == NULL);
|
|
DeleteCriticalSection(&m_critsec);
|
|
}
|
|
|
|
|
|
|
|
/////////////// IUnknown methods ///////////////
|
|
|
|
//-------------------------------------------------------------------
|
|
// AddRef
|
|
//-------------------------------------------------------------------
|
|
|
|
ULONG CCapture::AddRef()
|
|
{
|
|
return InterlockedIncrement(&m_nRefCount);
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// Release
|
|
//-------------------------------------------------------------------
|
|
|
|
ULONG CCapture::Release()
|
|
{
|
|
ULONG uCount = InterlockedDecrement(&m_nRefCount);
|
|
if (uCount == 0)
|
|
{
|
|
delete this;
|
|
}
|
|
return uCount;
|
|
}
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// QueryInterface
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CCapture::QueryInterface(REFIID riid, void** ppv)
|
|
{
|
|
static const QITAB qit[] =
|
|
{
|
|
QITABENT(CCapture, IMFSourceReaderCallback),
|
|
{ 0 },
|
|
};
|
|
return QISearch(this, qit, riid, ppv);
|
|
}
|
|
|
|
|
|
/////////////// IMFSourceReaderCallback methods ///////////////
|
|
|
|
//-------------------------------------------------------------------
|
|
// OnReadSample
|
|
//
|
|
// Called when the IMFMediaSource::ReadSample method completes.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CCapture::OnReadSample(
|
|
HRESULT hrStatus,
|
|
DWORD /*dwStreamIndex*/,
|
|
DWORD /*dwStreamFlags*/,
|
|
LONGLONG llTimeStamp,
|
|
IMFSample *pSample // Can be NULL
|
|
)
|
|
{
|
|
EnterCriticalSection(&m_critsec);
|
|
|
|
if (!IsCapturing())
|
|
{
|
|
LeaveCriticalSection(&m_critsec);
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
if (FAILED(hrStatus))
|
|
{
|
|
hr = hrStatus;
|
|
goto done;
|
|
}
|
|
|
|
if (pSample)
|
|
{
|
|
if (m_bFirstSample)
|
|
{
|
|
m_llBaseTime = llTimeStamp;
|
|
m_bFirstSample = FALSE;
|
|
}
|
|
|
|
// rebase the time stamp
|
|
llTimeStamp -= m_llBaseTime;
|
|
|
|
hr = pSample->SetSampleTime(llTimeStamp);
|
|
|
|
if (FAILED(hr)) { goto done; }
|
|
|
|
hr = m_pWriter->WriteSample(0, pSample);
|
|
|
|
if (FAILED(hr)) { goto done; }
|
|
}
|
|
|
|
// Read another sample.
|
|
hr = m_pReader->ReadSample(
|
|
(DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM,
|
|
0,
|
|
NULL, // actual
|
|
NULL, // flags
|
|
NULL, // timestamp
|
|
NULL // sample
|
|
);
|
|
|
|
done:
|
|
if (FAILED(hr))
|
|
{
|
|
NotifyError(hr);
|
|
}
|
|
|
|
LeaveCriticalSection(&m_critsec);
|
|
return hr;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// OpenMediaSource
|
|
//
|
|
// Set up preview for a specified media source.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CCapture::OpenMediaSource(IMFMediaSource *pSource)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
IMFAttributes *pAttributes = NULL;
|
|
|
|
hr = MFCreateAttributes(&pAttributes, 2);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = pAttributes->SetUnknown(MF_SOURCE_READER_ASYNC_CALLBACK, this);
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = MFCreateSourceReaderFromMediaSource(
|
|
pSource,
|
|
pAttributes,
|
|
&m_pReader
|
|
);
|
|
}
|
|
|
|
SafeRelease(&pAttributes);
|
|
return hr;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// StartCapture
|
|
//
|
|
// Start capturing.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CCapture::StartCapture(
|
|
IMFActivate *pActivate,
|
|
const WCHAR *pwszFileName,
|
|
const EncodingParameters& param
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
IMFMediaSource *pSource = NULL;
|
|
|
|
EnterCriticalSection(&m_critsec);
|
|
|
|
// Create the media source for the device.
|
|
hr = pActivate->ActivateObject(
|
|
__uuidof(IMFMediaSource),
|
|
(void**)&pSource
|
|
);
|
|
|
|
// Get the symbolic link. This is needed to handle device-
|
|
// loss notifications. (See CheckDeviceLost.)
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = pActivate->GetAllocatedString(
|
|
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK,
|
|
&m_pwszSymbolicLink,
|
|
NULL
|
|
);
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = OpenMediaSource(pSource);
|
|
}
|
|
|
|
// Create the sink writer
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = MFCreateSinkWriterFromURL(
|
|
pwszFileName,
|
|
NULL,
|
|
NULL,
|
|
&m_pWriter
|
|
);
|
|
}
|
|
|
|
// Set up the encoding parameters.
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = ConfigureCapture(param);
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
m_bFirstSample = TRUE;
|
|
m_llBaseTime = 0;
|
|
|
|
// Request the first video frame.
|
|
|
|
hr = m_pReader->ReadSample(
|
|
(DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM,
|
|
0,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL
|
|
);
|
|
}
|
|
|
|
SafeRelease(&pSource);
|
|
LeaveCriticalSection(&m_critsec);
|
|
return hr;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// EndCaptureSession
|
|
//
|
|
// Stop the capture session.
|
|
//
|
|
// NOTE: This method resets the object's state to State_NotReady.
|
|
// To start another capture session, call SetCaptureFile.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CCapture::EndCaptureSession()
|
|
{
|
|
EnterCriticalSection(&m_critsec);
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
if (m_pWriter)
|
|
{
|
|
hr = m_pWriter->Finalize();
|
|
}
|
|
|
|
SafeRelease(&m_pWriter);
|
|
SafeRelease(&m_pReader);
|
|
|
|
LeaveCriticalSection(&m_critsec);
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
BOOL CCapture::IsCapturing()
|
|
{
|
|
EnterCriticalSection(&m_critsec);
|
|
|
|
BOOL bIsCapturing = (m_pWriter != NULL);
|
|
|
|
LeaveCriticalSection(&m_critsec);
|
|
|
|
return bIsCapturing;
|
|
}
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// CheckDeviceLost
|
|
// Checks whether the video capture device was removed.
|
|
//
|
|
// The application calls this method when is receives a
|
|
// WM_DEVICECHANGE message.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CCapture::CheckDeviceLost(DEV_BROADCAST_HDR *pHdr, BOOL *pbDeviceLost)
|
|
{
|
|
if (pbDeviceLost == NULL)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
EnterCriticalSection(&m_critsec);
|
|
|
|
DEV_BROADCAST_DEVICEINTERFACE *pDi = NULL;
|
|
|
|
*pbDeviceLost = FALSE;
|
|
|
|
if (!IsCapturing())
|
|
{
|
|
goto done;
|
|
}
|
|
if (pHdr == NULL)
|
|
{
|
|
goto done;
|
|
}
|
|
if (pHdr->dbch_devicetype != DBT_DEVTYP_DEVICEINTERFACE)
|
|
{
|
|
goto done;
|
|
}
|
|
|
|
// Compare the device name with the symbolic link.
|
|
|
|
pDi = (DEV_BROADCAST_DEVICEINTERFACE*)pHdr;
|
|
|
|
if (m_pwszSymbolicLink)
|
|
{
|
|
if (_wcsicmp(m_pwszSymbolicLink, pDi->dbcc_name) == 0)
|
|
{
|
|
*pbDeviceLost = TRUE;
|
|
}
|
|
}
|
|
|
|
done:
|
|
LeaveCriticalSection(&m_critsec);
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
/////////////// Private/protected class methods ///////////////
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// ConfigureSourceReader
|
|
//
|
|
// Sets the media type on the source reader.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT ConfigureSourceReader(IMFSourceReader *pReader)
|
|
{
|
|
// The list of acceptable types.
|
|
GUID subtypes[] = {
|
|
MFVideoFormat_NV12, MFVideoFormat_YUY2, MFVideoFormat_UYVY,
|
|
MFVideoFormat_RGB32, MFVideoFormat_RGB24, MFVideoFormat_IYUV
|
|
};
|
|
|
|
HRESULT hr = S_OK;
|
|
BOOL bUseNativeType = FALSE;
|
|
|
|
GUID subtype = { 0 };
|
|
|
|
IMFMediaType *pType = NULL;
|
|
|
|
// If the source's native format matches any of the formats in
|
|
// the list, prefer the native format.
|
|
|
|
// Note: The camera might support multiple output formats,
|
|
// including a range of frame dimensions. The application could
|
|
// provide a list to the user and have the user select the
|
|
// camera's output format. That is outside the scope of this
|
|
// sample, however.
|
|
|
|
hr = pReader->GetNativeMediaType(
|
|
(DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM,
|
|
0, // Type index
|
|
&pType
|
|
);
|
|
|
|
if (FAILED(hr)) { goto done; }
|
|
|
|
hr = pType->GetGUID(MF_MT_SUBTYPE, &subtype);
|
|
|
|
if (FAILED(hr)) { goto done; }
|
|
|
|
for (UINT32 i = 0; i < ARRAYSIZE(subtypes); i++)
|
|
{
|
|
if (subtype == subtypes[i])
|
|
{
|
|
hr = pReader->SetCurrentMediaType(
|
|
(DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM,
|
|
NULL,
|
|
pType
|
|
);
|
|
|
|
bUseNativeType = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!bUseNativeType)
|
|
{
|
|
// None of the native types worked. The camera might offer
|
|
// output a compressed type such as MJPEG or DV.
|
|
|
|
// Try adding a decoder.
|
|
|
|
for (UINT32 i = 0; i < ARRAYSIZE(subtypes); i++)
|
|
{
|
|
hr = pType->SetGUID(MF_MT_SUBTYPE, subtypes[i]);
|
|
|
|
if (FAILED(hr)) { goto done; }
|
|
|
|
hr = pReader->SetCurrentMediaType(
|
|
(DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM,
|
|
NULL,
|
|
pType
|
|
);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
done:
|
|
SafeRelease(&pType);
|
|
return hr;
|
|
}
|
|
|
|
HRESULT ConfigureEncoder(
|
|
const EncodingParameters& params,
|
|
IMFMediaType *pType,
|
|
IMFSinkWriter *pWriter,
|
|
DWORD *pdwStreamIndex
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
IMFMediaType *pType2 = NULL;
|
|
|
|
hr = MFCreateMediaType(&pType2);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = pType2->SetGUID( MF_MT_MAJOR_TYPE, MFMediaType_Video );
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = pType2->SetGUID(MF_MT_SUBTYPE, params.subtype);
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = pType2->SetUINT32(MF_MT_AVG_BITRATE, params.bitrate);
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = CopyAttribute(pType, pType2, MF_MT_FRAME_SIZE);
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = CopyAttribute(pType, pType2, MF_MT_FRAME_RATE);
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = CopyAttribute(pType, pType2, MF_MT_PIXEL_ASPECT_RATIO);
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = CopyAttribute(pType, pType2, MF_MT_INTERLACE_MODE);
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = pWriter->AddStream(pType2, pdwStreamIndex);
|
|
}
|
|
|
|
SafeRelease(&pType2);
|
|
return hr;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// ConfigureCapture
|
|
//
|
|
// Configures the capture session.
|
|
//
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CCapture::ConfigureCapture(const EncodingParameters& param)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
DWORD sink_stream = 0;
|
|
|
|
IMFMediaType *pType = NULL;
|
|
|
|
hr = ConfigureSourceReader(m_pReader);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = m_pReader->GetCurrentMediaType(
|
|
(DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM,
|
|
&pType
|
|
);
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = ConfigureEncoder(param, pType, m_pWriter, &sink_stream);
|
|
}
|
|
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// Register the color converter DSP for this process, in the video
|
|
// processor category. This will enable the sink writer to enumerate
|
|
// the color converter when the sink writer attempts to match the
|
|
// media types.
|
|
|
|
hr = MFTRegisterLocalByCLSID(
|
|
__uuidof(CColorConvertDMO),
|
|
MFT_CATEGORY_VIDEO_PROCESSOR,
|
|
L"",
|
|
MFT_ENUM_FLAG_SYNCMFT,
|
|
0,
|
|
NULL,
|
|
0,
|
|
NULL
|
|
);
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = m_pWriter->SetInputMediaType(sink_stream, pType, NULL);
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = m_pWriter->BeginWriting();
|
|
}
|
|
|
|
SafeRelease(&pType);
|
|
return hr;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// EndCaptureInternal
|
|
//
|
|
// Stops capture.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CCapture::EndCaptureInternal()
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
if (m_pWriter)
|
|
{
|
|
hr = m_pWriter->Finalize();
|
|
}
|
|
|
|
SafeRelease(&m_pWriter);
|
|
SafeRelease(&m_pReader);
|
|
|
|
CoTaskMemFree(m_pwszSymbolicLink);
|
|
m_pwszSymbolicLink = NULL;
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
// CopyAttribute
|
|
//
|
|
// Copy an attribute value from one attribute store to another.
|
|
//-------------------------------------------------------------------
|
|
|
|
HRESULT CopyAttribute(IMFAttributes *pSrc, IMFAttributes *pDest, const GUID& key)
|
|
{
|
|
PROPVARIANT var;
|
|
PropVariantInit(&var);
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
hr = pSrc->GetItem(key, &var);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = pDest->SetItem(key, var);
|
|
}
|
|
|
|
PropVariantClear(&var);
|
|
return hr;
|
|
}
|