2025-11-28 00:35:46 +09:00

828 lines
20 KiB
C++

// 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 "Capture.h"
#include "resource.h"
IMFDXGIDeviceManager* g_pDXGIMan = NULL;
ID3D11Device* g_pDX11Device = NULL;
UINT g_ResetToken = 0;
STDMETHODIMP CaptureManager::CaptureEngineCB::QueryInterface(REFIID riid, void** ppv)
{
static const QITAB qit[] =
{
QITABENT(CaptureEngineCB, IMFCaptureEngineOnEventCallback),
{ 0 }
};
return QISearch(this, qit, riid, ppv);
}
STDMETHODIMP_(ULONG) CaptureManager::CaptureEngineCB::AddRef()
{
return InterlockedIncrement(&m_cRef);
}
STDMETHODIMP_(ULONG) CaptureManager::CaptureEngineCB::Release()
{
LONG cRef = InterlockedDecrement(&m_cRef);
if (cRef == 0)
{
delete this;
}
return cRef;
}
// Callback method to receive events from the capture engine.
STDMETHODIMP CaptureManager::CaptureEngineCB::OnEvent( _In_ IMFMediaEvent* pEvent)
{
// Post a message to the application window, so the event is handled
// on the application's main thread.
if (m_fSleeping && m_pManager != NULL)
{
// We're about to fall asleep, that means we've just asked the CE to stop the preview
// and record. We need to handle it here since our message pump may be gone.
GUID guidType;
HRESULT hrStatus;
HRESULT hr = pEvent->GetStatus(&hrStatus);
if (FAILED(hr))
{
hrStatus = hr;
}
hr = pEvent->GetExtendedType(&guidType);
if (SUCCEEDED(hr))
{
if (guidType == MF_CAPTURE_ENGINE_PREVIEW_STOPPED)
{
m_pManager->OnPreviewStopped(hrStatus);
SetEvent(m_pManager->m_hEvent);
}
else if (guidType == MF_CAPTURE_ENGINE_RECORD_STOPPED)
{
m_pManager->OnRecordStopped(hrStatus);
SetEvent(m_pManager->m_hEvent);
}
else
{
// This is an event we don't know about, we don't really care and there's
// no clean way to report the error so just set the event and fall through.
SetEvent(m_pManager->m_hEvent);
}
}
return S_OK;
}
else
{
pEvent->AddRef(); // The application will release the pointer when it handles the message.
PostMessage(m_hwnd, WM_APP_CAPTURE_EVENT, (WPARAM)pEvent, 0L);
}
return S_OK;
}
HRESULT CreateDX11Device(_Out_ ID3D11Device** ppDevice, _Out_ ID3D11DeviceContext** ppDeviceContext, _Out_ D3D_FEATURE_LEVEL* pFeatureLevel )
{
HRESULT hr = S_OK;
static const D3D_FEATURE_LEVEL levels[] = {
D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0,
D3D_FEATURE_LEVEL_9_3,
D3D_FEATURE_LEVEL_9_2,
D3D_FEATURE_LEVEL_9_1
};
hr = D3D11CreateDevice(
nullptr,
D3D_DRIVER_TYPE_HARDWARE,
nullptr,
D3D11_CREATE_DEVICE_VIDEO_SUPPORT,
levels,
ARRAYSIZE(levels),
D3D11_SDK_VERSION,
ppDevice,
pFeatureLevel,
ppDeviceContext
);
if(SUCCEEDED(hr))
{
ID3D10Multithread* pMultithread;
hr = ((*ppDevice)->QueryInterface(IID_PPV_ARGS(&pMultithread)));
if(SUCCEEDED(hr))
{
pMultithread->SetMultithreadProtected(TRUE);
}
SafeRelease(&pMultithread);
}
return hr;
}
HRESULT CreateD3DManager()
{
HRESULT hr = S_OK;
D3D_FEATURE_LEVEL FeatureLevel;
ID3D11DeviceContext* pDX11DeviceContext;
hr = CreateDX11Device(&g_pDX11Device, &pDX11DeviceContext, &FeatureLevel);
if(SUCCEEDED(hr))
{
hr = MFCreateDXGIDeviceManager(&g_ResetToken, &g_pDXGIMan);
}
if(SUCCEEDED(hr))
{
hr = g_pDXGIMan->ResetDevice(g_pDX11Device, g_ResetToken);
}
SafeRelease(&pDX11DeviceContext);
return hr;
}
HRESULT
CaptureManager::InitializeCaptureManager(HWND hwndPreview, IUnknown* pUnk)
{
HRESULT hr = S_OK;
IMFAttributes* pAttributes = NULL;
IMFCaptureEngineClassFactory* pFactory = NULL;
DestroyCaptureEngine();
m_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if (NULL == m_hEvent)
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto Exit;
}
m_pCallback = new (std::nothrow) CaptureEngineCB(m_hwndEvent);
if (m_pCallback == NULL)
{
hr = E_OUTOFMEMORY;
goto Exit;
}
m_pCallback->m_pManager = this;
m_hwndPreview = hwndPreview;
//Create a D3D Manager
hr = CreateD3DManager();
if (FAILED(hr))
{
goto Exit;
}
hr = MFCreateAttributes(&pAttributes, 1);
if (FAILED(hr))
{
goto Exit;
}
hr = pAttributes->SetUnknown(MF_CAPTURE_ENGINE_D3D_MANAGER, g_pDXGIMan);
if (FAILED(hr))
{
goto Exit;
}
// Create the factory object for the capture engine.
hr = CoCreateInstance(CLSID_MFCaptureEngineClassFactory, NULL,
CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pFactory));
if (FAILED(hr))
{
goto Exit;
}
// Create and initialize the capture engine.
hr = pFactory->CreateInstance(CLSID_MFCaptureEngine, IID_PPV_ARGS(&m_pEngine));
if (FAILED(hr))
{
goto Exit;
}
hr = m_pEngine->Initialize(m_pCallback, pAttributes, NULL, pUnk);
if (FAILED(hr))
{
goto Exit;
}
Exit:
if (NULL != pAttributes)
{
pAttributes->Release();
pAttributes = NULL;
}
if (NULL != pFactory)
{
pFactory->Release();
pFactory = NULL;
}
return hr;
}
// Handle an event from the capture engine.
// NOTE: This method is called from the application's UI thread.
HRESULT CaptureManager::OnCaptureEvent(WPARAM wParam, LPARAM lParam)
{
GUID guidType;
HRESULT hrStatus;
IMFMediaEvent *pEvent = reinterpret_cast<IMFMediaEvent*>(wParam);
HRESULT hr = pEvent->GetStatus(&hrStatus);
if (FAILED(hr))
{
hrStatus = hr;
}
hr = pEvent->GetExtendedType(&guidType);
if (SUCCEEDED(hr))
{
#ifdef _DEBUG
LPOLESTR str;
if (SUCCEEDED(StringFromCLSID(guidType, &str)))
{
DBGMSG((L"MF_CAPTURE_ENGINE_EVENT: %s (hr = 0x%X)\n", str, hrStatus));
CoTaskMemFree(str);
}
#endif
if (guidType == MF_CAPTURE_ENGINE_INITIALIZED)
{
OnCaptureEngineInitialized(hrStatus);
SetErrorID(hrStatus, IDS_ERR_INITIALIZE);
}
else if (guidType == MF_CAPTURE_ENGINE_PREVIEW_STARTED)
{
OnPreviewStarted(hrStatus);
SetErrorID(hrStatus, IDS_ERR_PREVIEW);
}
else if (guidType == MF_CAPTURE_ENGINE_PREVIEW_STOPPED)
{
OnPreviewStopped(hrStatus);
SetErrorID(hrStatus, IDS_ERR_PREVIEW);
}
else if (guidType == MF_CAPTURE_ENGINE_RECORD_STARTED)
{
OnRecordStarted(hrStatus);
SetErrorID(hrStatus, IDS_ERR_RECORD);
}
else if (guidType == MF_CAPTURE_ENGINE_RECORD_STOPPED)
{
OnRecordStopped(hrStatus);
SetErrorID(hrStatus, IDS_ERR_RECORD);
}
else if (guidType == MF_CAPTURE_ENGINE_PHOTO_TAKEN)
{
m_bPhotoPending = false;
SetErrorID(hrStatus, IDS_ERR_PHOTO);
}
else if (guidType == MF_CAPTURE_ENGINE_ERROR)
{
DestroyCaptureEngine();
SetErrorID(hrStatus, IDS_ERR_CAPTURE);
}
else if (FAILED(hrStatus))
{
SetErrorID(hrStatus, IDS_ERR_CAPTURE);
}
}
pEvent->Release();
SetEvent(m_hEvent);
return hrStatus;
}
void CaptureManager::OnCaptureEngineInitialized(HRESULT& hrStatus)
{
if (hrStatus == MF_E_NO_CAPTURE_DEVICES_AVAILABLE)
{
hrStatus = S_OK; // No capture device. Not an application error.
}
}
void CaptureManager::OnPreviewStarted(HRESULT& hrStatus)
{
m_bPreviewing = SUCCEEDED(hrStatus);
}
void CaptureManager::OnPreviewStopped(HRESULT& hrStatus)
{
m_bPreviewing = false;
}
void CaptureManager::OnRecordStarted(HRESULT& hrStatus)
{
m_bRecording = SUCCEEDED(hrStatus);
}
void CaptureManager::OnRecordStopped(HRESULT& hrStatus)
{
m_bRecording = false;
}
HRESULT CaptureManager::StartPreview()
{
if (m_pEngine == NULL)
{
return MF_E_NOT_INITIALIZED;
}
if (m_bPreviewing == true)
{
return S_OK;
}
IMFCaptureSink *pSink = NULL;
IMFMediaType *pMediaType = NULL;
IMFMediaType *pMediaType2 = NULL;
IMFCaptureSource *pSource = NULL;
HRESULT hr = S_OK;
// Get a pointer to the preview sink.
if (m_pPreview == NULL)
{
hr = m_pEngine->GetSink(MF_CAPTURE_ENGINE_SINK_TYPE_PREVIEW, &pSink);
if (FAILED(hr))
{
goto done;
}
hr = pSink->QueryInterface(IID_PPV_ARGS(&m_pPreview));
if (FAILED(hr))
{
goto done;
}
hr = m_pPreview->SetRenderHandle(m_hwndPreview);
if (FAILED(hr))
{
goto done;
}
hr = m_pEngine->GetSource(&pSource);
if (FAILED(hr))
{
goto done;
}
// Configure the video format for the preview sink.
hr = pSource->GetCurrentDeviceMediaType((DWORD)MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_VIDEO_PREVIEW , &pMediaType);
if (FAILED(hr))
{
goto done;
}
hr = CloneVideoMediaType(pMediaType, MFVideoFormat_RGB32, &pMediaType2);
if (FAILED(hr))
{
goto done;
}
hr = pMediaType2->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE);
if (FAILED(hr))
{
goto done;
}
// Connect the video stream to the preview sink.
DWORD dwSinkStreamIndex;
hr = m_pPreview->AddStream((DWORD)MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_VIDEO_PREVIEW, pMediaType2, NULL, &dwSinkStreamIndex);
if (FAILED(hr))
{
goto done;
}
}
hr = m_pEngine->StartPreview();
if (!m_fPowerRequestSet && m_hpwrRequest != INVALID_HANDLE_VALUE)
{
// NOTE: By calling this, on SOC systems (AOAC enabled), we're asking the system to not go
// into sleep/connected standby while we're streaming. However, since we don't want to block
// the device from ever entering connected standby/sleep, we're going to latch ourselves to
// the monitor on/off notification (RegisterPowerSettingNotification(GUID_MONITOR_POWER_ON)).
// On SOC systems, this notification will fire when the user decides to put the device in
// connected standby mode--we can trap this, turn off our media streams and clear this
// power set request to allow the device to go into the lower power state.
m_fPowerRequestSet = (TRUE == PowerSetRequest(m_hpwrRequest, PowerRequestExecutionRequired));
}
done:
SafeRelease(&pSink);
SafeRelease(&pMediaType);
SafeRelease(&pMediaType2);
SafeRelease(&pSource);
return hr;
}
HRESULT CaptureManager::StopPreview()
{
HRESULT hr = S_OK;
if (m_pEngine == NULL)
{
return MF_E_NOT_INITIALIZED;
}
if (!m_bPreviewing)
{
return S_OK;
}
hr = m_pEngine->StopPreview();
if (FAILED(hr))
{
goto done;
}
WaitForResult();
if (m_fPowerRequestSet && m_hpwrRequest != INVALID_HANDLE_VALUE)
{
PowerClearRequest(m_hpwrRequest, PowerRequestExecutionRequired);
m_fPowerRequestSet = false;
}
done:
return hr;
}
// Helper function to get the frame size from a video media type.
inline HRESULT GetFrameSize(IMFMediaType *pType, UINT32 *pWidth, UINT32 *pHeight)
{ return MFGetAttributeSize(pType, MF_MT_FRAME_SIZE, pWidth, pHeight);}
// Helper function to get the frame rate from a video media type.
inline HRESULT GetFrameRate(
IMFMediaType *pType,
UINT32 *pNumerator,
UINT32 *pDenominator
)
{
return MFGetAttributeRatio(
pType,
MF_MT_FRAME_RATE,
pNumerator,
pDenominator
);
}
HRESULT GetEncodingBitrate(IMFMediaType *pMediaType, UINT32 *uiEncodingBitrate)
{
UINT32 uiWidth;
UINT32 uiHeight;
float uiBitrate;
UINT32 uiFrameRateNum;
UINT32 uiFrameRateDenom;
HRESULT hr = GetFrameSize(pMediaType, &uiWidth, &uiHeight);
if (FAILED(hr))
{
goto done;
}
hr = GetFrameRate(pMediaType, &uiFrameRateNum, &uiFrameRateDenom);
if (FAILED(hr))
{
goto done;
}
uiBitrate = uiWidth / 3.0f * uiHeight * uiFrameRateNum / uiFrameRateDenom;
*uiEncodingBitrate = (UINT32) uiBitrate;
done:
return hr;
}
HRESULT ConfigureVideoEncoding(IMFCaptureSource *pSource, IMFCaptureRecordSink *pRecord, REFGUID guidEncodingType)
{
IMFMediaType *pMediaType = NULL;
IMFMediaType *pMediaType2 = NULL;
GUID guidSubType = GUID_NULL;
// Configure the video format for the recording sink.
HRESULT hr = pSource->GetCurrentDeviceMediaType((DWORD)MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_VIDEO_RECORD , &pMediaType);
if (FAILED(hr))
{
goto done;
}
hr = CloneVideoMediaType(pMediaType, guidEncodingType, &pMediaType2);
if (FAILED(hr))
{
goto done;
}
hr = pMediaType->GetGUID(MF_MT_SUBTYPE, &guidSubType);
if(FAILED(hr))
{
goto done;
}
if(guidSubType == MFVideoFormat_H264_ES || guidSubType == MFVideoFormat_H264)
{
//When the webcam supports H264_ES or H264, we just bypass the stream. The output from Capture engine shall be the same as the native type supported by the webcam
hr = pMediaType2->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_H264);
}
else
{
UINT32 uiEncodingBitrate;
hr = GetEncodingBitrate(pMediaType2, &uiEncodingBitrate);
if (FAILED(hr))
{
goto done;
}
hr = pMediaType2->SetUINT32(MF_MT_AVG_BITRATE, uiEncodingBitrate);
}
if (FAILED(hr))
{
goto done;
}
// Connect the video stream to the recording sink.
DWORD dwSinkStreamIndex;
hr = pRecord->AddStream((DWORD)MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_VIDEO_RECORD, pMediaType2, NULL, &dwSinkStreamIndex);
done:
SafeRelease(&pMediaType);
SafeRelease(&pMediaType2);
return hr;
}
HRESULT ConfigureAudioEncoding(IMFCaptureSource *pSource, IMFCaptureRecordSink *pRecord, REFGUID guidEncodingType)
{
IMFCollection *pAvailableTypes = NULL;
IMFMediaType *pMediaType = NULL;
IMFAttributes *pAttributes = NULL;
// Configure the audio format for the recording sink.
HRESULT hr = MFCreateAttributes(&pAttributes, 1);
if(FAILED(hr))
{
goto done;
}
// Enumerate low latency media types
hr = pAttributes->SetUINT32(MF_LOW_LATENCY, TRUE);
if(FAILED(hr))
{
goto done;
}
// Get a list of encoded output formats that are supported by the encoder.
hr = MFTranscodeGetAudioOutputAvailableTypes(guidEncodingType, MFT_ENUM_FLAG_ALL | MFT_ENUM_FLAG_SORTANDFILTER,
pAttributes, &pAvailableTypes);
if (FAILED(hr))
{
goto done;
}
// Pick the first format from the list.
hr = GetCollectionObject(pAvailableTypes, 0, &pMediaType);
if (FAILED(hr))
{
goto done;
}
// Connect the audio stream to the recording sink.
DWORD dwSinkStreamIndex;
hr = pRecord->AddStream((DWORD)MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_AUDIO, pMediaType, NULL, &dwSinkStreamIndex);
if(hr == MF_E_INVALIDSTREAMNUMBER)
{
//If an audio device is not present, allow video only recording
hr = S_OK;
}
done:
SafeRelease(&pAvailableTypes);
SafeRelease(&pMediaType);
SafeRelease(&pAttributes);
return hr;
}
HRESULT CaptureManager::StartRecord(PCWSTR pszDestinationFile)
{
if (m_pEngine == NULL)
{
return MF_E_NOT_INITIALIZED;
}
if (m_bRecording == true)
{
return MF_E_INVALIDREQUEST;
}
PWSTR pszExt = PathFindExtension(pszDestinationFile);
GUID guidVideoEncoding;
GUID guidAudioEncoding;
if (wcscmp(pszExt, L".mp4") == 0)
{
guidVideoEncoding = MFVideoFormat_H264;
guidAudioEncoding = MFAudioFormat_AAC;
}
else if (wcscmp(pszExt, L".wmv") == 0)
{
guidVideoEncoding = MFVideoFormat_WMV3;
guidAudioEncoding = MFAudioFormat_WMAudioV9;
}
else if (wcscmp(pszExt, L".wma") == 0)
{
guidVideoEncoding = GUID_NULL;
guidAudioEncoding = MFAudioFormat_WMAudioV9;
}
else
{
return MF_E_INVALIDMEDIATYPE;
}
IMFCaptureSink *pSink = NULL;
IMFCaptureRecordSink *pRecord = NULL;
IMFCaptureSource *pSource = NULL;
HRESULT hr = m_pEngine->GetSink(MF_CAPTURE_ENGINE_SINK_TYPE_RECORD, &pSink);
if (FAILED(hr))
{
goto done;
}
hr = pSink->QueryInterface(IID_PPV_ARGS(&pRecord));
if (FAILED(hr))
{
goto done;
}
hr = m_pEngine->GetSource(&pSource);
if (FAILED(hr))
{
goto done;
}
// Clear any existing streams from previous recordings.
hr = pRecord->RemoveAllStreams();
if (FAILED(hr))
{
goto done;
}
hr = pRecord->SetOutputFileName(pszDestinationFile);
if (FAILED(hr))
{
goto done;
}
// Configure the video and audio streams.
if (guidVideoEncoding != GUID_NULL)
{
hr = ConfigureVideoEncoding(pSource, pRecord, guidVideoEncoding);
if (FAILED(hr))
{
goto done;
}
}
if (guidAudioEncoding != GUID_NULL)
{
hr = ConfigureAudioEncoding(pSource, pRecord, guidAudioEncoding);
if (FAILED(hr))
{
goto done;
}
}
hr = m_pEngine->StartRecord();
if (FAILED(hr))
{
goto done;
}
m_bRecording = true;
done:
SafeRelease(&pSink);
SafeRelease(&pSource);
SafeRelease(&pRecord);
return hr;
}
HRESULT CaptureManager::StopRecord()
{
HRESULT hr = S_OK;
if (m_bRecording)
{
hr = m_pEngine->StopRecord(TRUE, FALSE);
WaitForResult();
}
return hr;
}
HRESULT CaptureManager::TakePhoto(PCWSTR pszFileName)
{
IMFCaptureSink *pSink = NULL;
IMFCapturePhotoSink *pPhoto = NULL;
IMFCaptureSource *pSource;
IMFMediaType *pMediaType = 0;
IMFMediaType *pMediaType2 = 0;
bool bHasPhotoStream = true;
// Get a pointer to the photo sink.
HRESULT hr = m_pEngine->GetSink(MF_CAPTURE_ENGINE_SINK_TYPE_PHOTO, &pSink);
if (FAILED(hr))
{
goto done;
}
hr = pSink->QueryInterface(IID_PPV_ARGS(&pPhoto));
if (FAILED(hr))
{
goto done;
}
hr = m_pEngine->GetSource(&pSource);
if (FAILED(hr))
{
goto done;
}
hr = pSource->GetCurrentDeviceMediaType((DWORD)MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_PHOTO , &pMediaType);
if (FAILED(hr))
{
goto done;
}
//Configure the photo format
hr = CreatePhotoMediaType(pMediaType, &pMediaType2);
if (FAILED(hr))
{
goto done;
}
hr = pPhoto->RemoveAllStreams();
if (FAILED(hr))
{
goto done;
}
DWORD dwSinkStreamIndex;
// Try to connect the first still image stream to the photo sink
if(bHasPhotoStream)
{
hr = pPhoto->AddStream((DWORD)MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_PHOTO, pMediaType2, NULL, &dwSinkStreamIndex);
}
if(FAILED(hr))
{
goto done;
}
hr = pPhoto->SetOutputFileName(pszFileName);
if (FAILED(hr))
{
goto done;
}
hr = m_pEngine->TakePhoto();
if (FAILED(hr))
{
goto done;
}
m_bPhotoPending = true;
done:
SafeRelease(&pSink);
SafeRelease(&pPhoto);
SafeRelease(&pSource);
SafeRelease(&pMediaType);
SafeRelease(&pMediaType2);
return hr;
}