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

509 lines
11 KiB
C++

//////////////////////////////////////////////////////////////////////////
//
// preview.cpp: Manages video preview.
//
// 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 "MFCaptureD3D.h"
#include <shlwapi.h>
//-------------------------------------------------------------------
// CreateInstance
//
// Static class method to create the CPreview object.
//-------------------------------------------------------------------
HRESULT CPreview::CreateInstance(
HWND hVideo, // Handle to the video window.
HWND hEvent, // Handle to the window to receive notifications.
CPreview **ppPlayer // Receives a pointer to the CPreview object.
)
{
assert(hVideo != NULL);
assert(hEvent != NULL);
if (ppPlayer == NULL)
{
return E_POINTER;
}
CPreview *pPlayer = new (std::nothrow) CPreview(hVideo, hEvent);
// The CPlayer constructor sets the ref count to 1.
if (pPlayer == NULL)
{
return E_OUTOFMEMORY;
}
HRESULT hr = pPlayer->Initialize();
if (SUCCEEDED(hr))
{
*ppPlayer = pPlayer;
(*ppPlayer)->AddRef();
}
SafeRelease(&pPlayer);
return hr;
}
//-------------------------------------------------------------------
// constructor
//-------------------------------------------------------------------
CPreview::CPreview(HWND hVideo, HWND hEvent) :
m_pReader(NULL),
m_hwndVideo(hVideo),
m_hwndEvent(hEvent),
m_nRefCount(1),
m_pwszSymbolicLink(NULL),
m_cchSymbolicLink(0)
{
InitializeCriticalSection(&m_critsec);
}
//-------------------------------------------------------------------
// destructor
//-------------------------------------------------------------------
CPreview::~CPreview()
{
CloseDevice();
m_draw.DestroyDevice();
DeleteCriticalSection(&m_critsec);
}
//-------------------------------------------------------------------
// Initialize
//
// Initializes the object.
//-------------------------------------------------------------------
HRESULT CPreview::Initialize()
{
HRESULT hr = S_OK;
hr = m_draw.CreateDevice(m_hwndVideo);
return hr;
}
//-------------------------------------------------------------------
// CloseDevice
//
// Releases all resources held by this object.
//-------------------------------------------------------------------
HRESULT CPreview::CloseDevice()
{
EnterCriticalSection(&m_critsec);
SafeRelease(&m_pReader);
CoTaskMemFree(m_pwszSymbolicLink);
m_pwszSymbolicLink = NULL;
m_cchSymbolicLink = 0;
LeaveCriticalSection(&m_critsec);
return S_OK;
}
/////////////// IUnknown methods ///////////////
//-------------------------------------------------------------------
// AddRef
//-------------------------------------------------------------------
ULONG CPreview::AddRef()
{
return InterlockedIncrement(&m_nRefCount);
}
//-------------------------------------------------------------------
// Release
//-------------------------------------------------------------------
ULONG CPreview::Release()
{
ULONG uCount = InterlockedDecrement(&m_nRefCount);
if (uCount == 0)
{
delete this;
}
// For thread safety, return a temporary variable.
return uCount;
}
//-------------------------------------------------------------------
// QueryInterface
//-------------------------------------------------------------------
HRESULT CPreview::QueryInterface(REFIID riid, void** ppv)
{
static const QITAB qit[] =
{
QITABENT(CPreview, IMFSourceReaderCallback),
{ 0 },
};
return QISearch(this, qit, riid, ppv);
}
/////////////// IMFSourceReaderCallback methods ///////////////
//-------------------------------------------------------------------
// OnReadSample
//
// Called when the IMFMediaSource::ReadSample method completes.
//-------------------------------------------------------------------
HRESULT CPreview::OnReadSample(
HRESULT hrStatus,
DWORD /* dwStreamIndex */,
DWORD /* dwStreamFlags */,
LONGLONG /* llTimestamp */,
IMFSample *pSample // Can be NULL
)
{
HRESULT hr = S_OK;
IMFMediaBuffer *pBuffer = NULL;
EnterCriticalSection(&m_critsec);
if (FAILED(hrStatus))
{
hr = hrStatus;
}
if (SUCCEEDED(hr))
{
if (pSample)
{
// Get the video frame buffer from the sample.
hr = pSample->GetBufferByIndex(0, &pBuffer);
// Draw the frame.
if (SUCCEEDED(hr))
{
hr = m_draw.DrawFrame(pBuffer);
}
}
}
// Request the next frame.
if (SUCCEEDED(hr))
{
hr = m_pReader->ReadSample(
(DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM,
0,
NULL, // actual
NULL, // flags
NULL, // timestamp
NULL // sample
);
}
if (FAILED(hr))
{
NotifyError(hr);
}
SafeRelease(&pBuffer);
LeaveCriticalSection(&m_critsec);
return hr;
}
//-------------------------------------------------------------------
// TryMediaType
//
// Test a proposed video format.
//-------------------------------------------------------------------
HRESULT CPreview::TryMediaType(IMFMediaType *pType)
{
HRESULT hr = S_OK;
BOOL bFound = FALSE;
GUID subtype = { 0 };
hr = pType->GetGUID(MF_MT_SUBTYPE, &subtype);
if (FAILED(hr))
{
return hr;
}
// Do we support this type directly?
if (m_draw.IsFormatSupported(subtype))
{
bFound = TRUE;
}
else
{
// Can we decode this media type to one of our supported
// output formats?
for (DWORD i = 0; ; i++)
{
// Get the i'th format.
hr = m_draw.GetFormat(i, &subtype);
if (FAILED(hr)) { break; }
hr = pType->SetGUID(MF_MT_SUBTYPE, subtype);
if (FAILED(hr)) { break; }
// Try to set this type on the source reader.
hr = m_pReader->SetCurrentMediaType(
(DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM,
NULL,
pType
);
if (SUCCEEDED(hr))
{
bFound = TRUE;
break;
}
}
}
if (bFound)
{
hr = m_draw.SetVideoType(pType);
}
return hr;
}
//-------------------------------------------------------------------
// SetDevice
//
// Set up preview for a specified video capture device.
//-------------------------------------------------------------------
HRESULT CPreview::SetDevice(IMFActivate *pActivate)
{
HRESULT hr = S_OK;
IMFMediaSource *pSource = NULL;
IMFAttributes *pAttributes = NULL;
IMFMediaType *pType = NULL;
EnterCriticalSection(&m_critsec);
// Release the current device, if any.
hr = CloseDevice();
// Create the media source for the device.
if (SUCCEEDED(hr))
{
hr = pActivate->ActivateObject(
__uuidof(IMFMediaSource),
(void**)&pSource
);
}
// Get the symbolic link.
if (SUCCEEDED(hr))
{
hr = pActivate->GetAllocatedString(
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK,
&m_pwszSymbolicLink,
&m_cchSymbolicLink
);
}
//
// Create the source reader.
//
// Create an attribute store to hold initialization settings.
if (SUCCEEDED(hr))
{
hr = MFCreateAttributes(&pAttributes, 2);
}
if (SUCCEEDED(hr))
{
hr = pAttributes->SetUINT32(MF_READWRITE_DISABLE_CONVERTERS, TRUE);
}
// Set the callback pointer.
if (SUCCEEDED(hr))
{
hr = pAttributes->SetUnknown(
MF_SOURCE_READER_ASYNC_CALLBACK,
this
);
}
if (SUCCEEDED(hr))
{
hr = MFCreateSourceReaderFromMediaSource(
pSource,
pAttributes,
&m_pReader
);
}
// Try to find a suitable output type.
if (SUCCEEDED(hr))
{
for (DWORD i = 0; ; i++)
{
hr = m_pReader->GetNativeMediaType(
(DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM,
i,
&pType
);
if (FAILED(hr)) { break; }
hr = TryMediaType(pType);
SafeRelease(&pType);
if (SUCCEEDED(hr))
{
// Found an output type.
break;
}
}
}
if (SUCCEEDED(hr))
{
// Ask for the first sample.
hr = m_pReader->ReadSample(
(DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM,
0,
NULL,
NULL,
NULL,
NULL
);
}
if (FAILED(hr))
{
if (pSource)
{
pSource->Shutdown();
// NOTE: The source reader shuts down the media source
// by default, but we might not have gotten that far.
}
CloseDevice();
}
SafeRelease(&pSource);
SafeRelease(&pAttributes);
SafeRelease(&pType);
LeaveCriticalSection(&m_critsec);
return hr;
}
//-------------------------------------------------------------------
// ResizeVideo
// Resizes the video rectangle.
//
// The application should call this method if the size of the video
// window changes; e.g., when the application receives WM_SIZE.
//-------------------------------------------------------------------
HRESULT CPreview::ResizeVideo(WORD /*width*/, WORD /*height*/)
{
HRESULT hr = S_OK;
EnterCriticalSection(&m_critsec);
hr = m_draw.ResetDevice();
if (FAILED(hr))
{
MessageBox(NULL, L"ResetDevice failed!", NULL, MB_OK);
}
LeaveCriticalSection(&m_critsec);
return hr;
}
//-------------------------------------------------------------------
// CheckDeviceLost
// Checks whether the current device has been lost.
//
// The application should call this method in response to a
// WM_DEVICECHANGE message. (The application must register for
// device notification to receive this message.)
//-------------------------------------------------------------------
HRESULT CPreview::CheckDeviceLost(DEV_BROADCAST_HDR *pHdr, BOOL *pbDeviceLost)
{
DEV_BROADCAST_DEVICEINTERFACE *pDi = NULL;
if (pbDeviceLost == NULL)
{
return E_POINTER;
}
*pbDeviceLost = FALSE;
if (pHdr == NULL)
{
return S_OK;
}
if (pHdr->dbch_devicetype != DBT_DEVTYP_DEVICEINTERFACE)
{
return S_OK;
}
pDi = (DEV_BROADCAST_DEVICEINTERFACE*)pHdr;
EnterCriticalSection(&m_critsec);
if (m_pwszSymbolicLink)
{
if (_wcsicmp(m_pwszSymbolicLink, pDi->dbcc_name) == 0)
{
*pbDeviceLost = TRUE;
}
}
LeaveCriticalSection(&m_critsec);
return S_OK;
}