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

922 lines
22 KiB
C++

//////////////////////////////////////////////////////////////////////////
//
// device.cpp: Manages the Direct3D device
//
// 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 "BufferLock.h"
const DWORD NUM_BACK_BUFFERS = 2;
void TransformImage_RGB24(
BYTE* pDest,
LONG lDestStride,
const BYTE* pSrc,
LONG lSrcStride,
DWORD dwWidthInPixels,
DWORD dwHeightInPixels
);
void TransformImage_RGB32(
BYTE* pDest,
LONG lDestStride,
const BYTE* pSrc,
LONG lSrcStride,
DWORD dwWidthInPixels,
DWORD dwHeightInPixels
);
void TransformImage_YUY2(
BYTE* pDest,
LONG lDestStride,
const BYTE* pSrc,
LONG lSrcStride,
DWORD dwWidthInPixels,
DWORD dwHeightInPixels
);
void TransformImage_NV12(
BYTE* pDst,
LONG dstStride,
const BYTE* pSrc,
LONG srcStride,
DWORD dwWidthInPixels,
DWORD dwHeightInPixels
);
RECT LetterBoxRect(const RECT& rcSrc, const RECT& rcDst);
RECT CorrectAspectRatio(const RECT& src, const MFRatio& srcPAR);
HRESULT GetDefaultStride(IMFMediaType *pType, LONG *plStride);
inline LONG Width(const RECT& r)
{
return r.right - r.left;
}
inline LONG Height(const RECT& r)
{
return r.bottom - r.top;
}
// Static table of output formats and conversion functions.
struct ConversionFunction
{
GUID subtype;
IMAGE_TRANSFORM_FN xform;
};
ConversionFunction g_FormatConversions[] =
{
{ MFVideoFormat_RGB32, TransformImage_RGB32 },
{ MFVideoFormat_RGB24, TransformImage_RGB24 },
{ MFVideoFormat_YUY2, TransformImage_YUY2 },
{ MFVideoFormat_NV12, TransformImage_NV12 }
};
const DWORD g_cFormats = ARRAYSIZE(g_FormatConversions);
//-------------------------------------------------------------------
// Constructor
//-------------------------------------------------------------------
DrawDevice::DrawDevice() :
m_hwnd(NULL),
m_pD3D(NULL),
m_pDevice(NULL),
m_pSwapChain(NULL),
m_format(D3DFMT_UNKNOWN),
m_width(0),
m_height(0),
m_lDefaultStride(0),
m_interlace(MFVideoInterlace_Unknown),
m_convertFn(NULL)
{
m_PixelAR.Denominator = m_PixelAR.Numerator = 1;
ZeroMemory(&m_d3dpp, sizeof(m_d3dpp));
}
//-------------------------------------------------------------------
// Destructor
//-------------------------------------------------------------------
DrawDevice::~DrawDevice()
{
DestroyDevice();
}
//-------------------------------------------------------------------
// GetFormat
//
// Get a supported output format by index.
//-------------------------------------------------------------------
HRESULT DrawDevice::GetFormat(DWORD index, GUID *pSubtype) const
{
if (index < g_cFormats)
{
*pSubtype = g_FormatConversions[index].subtype;
return S_OK;
}
return MF_E_NO_MORE_TYPES;
}
//-------------------------------------------------------------------
// IsFormatSupported
//
// Query if a format is supported.
//-------------------------------------------------------------------
BOOL DrawDevice::IsFormatSupported(REFGUID subtype) const
{
for (DWORD i = 0; i < g_cFormats; i++)
{
if (subtype == g_FormatConversions[i].subtype)
{
return TRUE;
}
}
return FALSE;
}
//-------------------------------------------------------------------
// CreateDevice
//
// Create the Direct3D device.
//-------------------------------------------------------------------
HRESULT DrawDevice::CreateDevice(HWND hwnd)
{
if (m_pDevice)
{
return S_OK;
}
// Create the Direct3D object.
if (m_pD3D == NULL)
{
m_pD3D = Direct3DCreate9(D3D_SDK_VERSION);
if (m_pD3D == NULL)
{
return E_FAIL;
}
}
HRESULT hr = S_OK;
D3DPRESENT_PARAMETERS pp = { 0 };
D3DDISPLAYMODE mode = { 0 };
hr = m_pD3D->GetAdapterDisplayMode(
D3DADAPTER_DEFAULT,
&mode
);
if (FAILED(hr)) { goto done; }
hr = m_pD3D->CheckDeviceType(
D3DADAPTER_DEFAULT,
D3DDEVTYPE_HAL,
mode.Format,
D3DFMT_X8R8G8B8,
TRUE // windowed
);
if (FAILED(hr)) { goto done; }
pp.BackBufferFormat = D3DFMT_X8R8G8B8;
pp.SwapEffect = D3DSWAPEFFECT_COPY;
pp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
pp.Windowed = TRUE;
pp.hDeviceWindow = hwnd;
hr = m_pD3D->CreateDevice(
D3DADAPTER_DEFAULT,
D3DDEVTYPE_HAL,
hwnd,
D3DCREATE_HARDWARE_VERTEXPROCESSING | D3DCREATE_FPU_PRESERVE,
&pp,
&m_pDevice
);
if (FAILED(hr)) { goto done; }
m_hwnd = hwnd;
m_d3dpp = pp;
done:
return hr;
}
//-------------------------------------------------------------------
// SetConversionFunction
//
// Set the conversion function for the specified video format.
//-------------------------------------------------------------------
HRESULT DrawDevice::SetConversionFunction(REFGUID subtype)
{
m_convertFn = NULL;
for (DWORD i = 0; i < g_cFormats; i++)
{
if (g_FormatConversions[i].subtype == subtype)
{
m_convertFn = g_FormatConversions[i].xform;
return S_OK;
}
}
return MF_E_INVALIDMEDIATYPE;
}
//-------------------------------------------------------------------
// SetVideoType
//
// Set the video format.
//-------------------------------------------------------------------
HRESULT DrawDevice::SetVideoType(IMFMediaType *pType)
{
HRESULT hr = S_OK;
GUID subtype = { 0 };
MFRatio PAR = { 0 };
// Find the video subtype.
hr = pType->GetGUID(MF_MT_SUBTYPE, &subtype);
if (FAILED(hr)) { goto done; }
// Choose a conversion function.
// (This also validates the format type.)
hr = SetConversionFunction(subtype);
if (FAILED(hr)) { goto done; }
//
// Get some video attributes.
//
// Get the frame size.
hr = MFGetAttributeSize(pType, MF_MT_FRAME_SIZE, &m_width, &m_height);
if (FAILED(hr)) { goto done; }
// Get the interlace mode. Default: assume progressive.
m_interlace = (MFVideoInterlaceMode)MFGetAttributeUINT32(
pType,
MF_MT_INTERLACE_MODE,
MFVideoInterlace_Progressive
);
// Get the image stride.
hr = GetDefaultStride(pType, &m_lDefaultStride);
if (FAILED(hr)) { goto done; }
// Get the pixel aspect ratio. Default: Assume square pixels (1:1)
hr = MFGetAttributeRatio(
pType,
MF_MT_PIXEL_ASPECT_RATIO,
(UINT32*)&PAR.Numerator,
(UINT32*)&PAR.Denominator
);
if (SUCCEEDED(hr))
{
m_PixelAR = PAR;
}
else
{
m_PixelAR.Numerator = m_PixelAR.Denominator = 1;
}
m_format = (D3DFORMAT)subtype.Data1;
// Create Direct3D swap chains.
hr = CreateSwapChains();
if (FAILED(hr)) { goto done; }
// Update the destination rectangle for the correct
// aspect ratio.
UpdateDestinationRect();
done:
if (FAILED(hr))
{
m_format = D3DFMT_UNKNOWN;
m_convertFn = NULL;
}
return hr;
}
//-------------------------------------------------------------------
// UpdateDestinationRect
//
// Update the destination rectangle for the current window size.
// The destination rectangle is letterboxed to preserve the
// aspect ratio of the video image.
//-------------------------------------------------------------------
void DrawDevice::UpdateDestinationRect()
{
RECT rcClient;
RECT rcSrc = { 0, 0, m_width, m_height };
GetClientRect(m_hwnd, &rcClient);
rcSrc = CorrectAspectRatio(rcSrc, m_PixelAR);
m_rcDest = LetterBoxRect(rcSrc, rcClient);
}
//-------------------------------------------------------------------
// CreateSwapChains
//
// Create Direct3D swap chains.
//-------------------------------------------------------------------
HRESULT DrawDevice::CreateSwapChains()
{
HRESULT hr = S_OK;
D3DPRESENT_PARAMETERS pp = { 0 };
SafeRelease(&m_pSwapChain);
pp.BackBufferWidth = m_width;
pp.BackBufferHeight = m_height;
pp.Windowed = TRUE;
pp.SwapEffect = D3DSWAPEFFECT_FLIP;
pp.hDeviceWindow = m_hwnd;
pp.BackBufferFormat = D3DFMT_X8R8G8B8;
pp.Flags =
D3DPRESENTFLAG_VIDEO | D3DPRESENTFLAG_DEVICECLIP |
D3DPRESENTFLAG_LOCKABLE_BACKBUFFER;
pp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
pp.BackBufferCount = NUM_BACK_BUFFERS;
hr = m_pDevice->CreateAdditionalSwapChain(&pp, &m_pSwapChain);
return hr;
}
//-------------------------------------------------------------------
// DrawFrame
//
// Draw the video frame.
//-------------------------------------------------------------------
HRESULT DrawDevice::DrawFrame(IMFMediaBuffer *pBuffer)
{
if (m_convertFn == NULL)
{
return MF_E_INVALIDREQUEST;
}
HRESULT hr = S_OK;
BYTE *pbScanline0 = NULL;
LONG lStride = 0;
D3DLOCKED_RECT lr;
IDirect3DSurface9 *pSurf = NULL;
IDirect3DSurface9 *pBB = NULL;
if (m_pDevice == NULL || m_pSwapChain == NULL)
{
return S_OK;
}
VideoBufferLock buffer(pBuffer); // Helper object to lock the video buffer.
hr = TestCooperativeLevel();
if (FAILED(hr)) { goto done; }
// Lock the video buffer. This method returns a pointer to the first scan
// line in the image, and the stride in bytes.
hr = buffer.LockBuffer(m_lDefaultStride, m_height, &pbScanline0, &lStride);
if (FAILED(hr)) { goto done; }
// Get the swap-chain surface.
hr = m_pSwapChain->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &pSurf);
if (FAILED(hr)) { goto done; }
// Lock the swap-chain surface.
hr = pSurf->LockRect(&lr, NULL, D3DLOCK_NOSYSLOCK );
if (FAILED(hr)) { goto done; }
// Convert the frame. This also copies it to the Direct3D surface.
m_convertFn(
(BYTE*)lr.pBits,
lr.Pitch,
pbScanline0,
lStride,
m_width,
m_height
);
hr = pSurf->UnlockRect();
if (FAILED(hr)) { goto done; }
// Color fill the back buffer.
hr = m_pDevice->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &pBB);
if (FAILED(hr)) { goto done; }
hr = m_pDevice->ColorFill(pBB, NULL, D3DCOLOR_XRGB(0, 0, 0x80));
if (FAILED(hr)) { goto done; }
// Blit the frame.
hr = m_pDevice->StretchRect(pSurf, NULL, pBB, &m_rcDest, D3DTEXF_LINEAR);
if (FAILED(hr)) { goto done; }
// Present the frame.
hr = m_pDevice->Present(NULL, NULL, NULL, NULL);
done:
SafeRelease(&pBB);
SafeRelease(&pSurf);
return hr;
}
//-------------------------------------------------------------------
// TestCooperativeLevel
//
// Test the cooperative-level status of the Direct3D device.
//-------------------------------------------------------------------
HRESULT DrawDevice::TestCooperativeLevel()
{
if (m_pDevice == NULL)
{
return E_FAIL;
}
HRESULT hr = S_OK;
// Check the current status of D3D9 device.
hr = m_pDevice->TestCooperativeLevel();
switch (hr)
{
case D3D_OK:
break;
case D3DERR_DEVICELOST:
hr = S_OK;
case D3DERR_DEVICENOTRESET:
hr = ResetDevice();
break;
default:
// Some other failure.
break;
}
return hr;
}
//-------------------------------------------------------------------
// ResetDevice
//
// Resets the Direct3D device.
//-------------------------------------------------------------------
HRESULT DrawDevice::ResetDevice()
{
HRESULT hr = S_OK;
if (m_pDevice)
{
D3DPRESENT_PARAMETERS d3dpp = m_d3dpp;
hr = m_pDevice->Reset(&d3dpp);
if (FAILED(hr))
{
DestroyDevice();
}
}
if (m_pDevice == NULL)
{
hr = CreateDevice(m_hwnd);
if (FAILED(hr)) { goto done; }
}
if ((m_pSwapChain == NULL) && (m_format != D3DFMT_UNKNOWN))
{
hr = CreateSwapChains();
if (FAILED(hr)) { goto done; }
UpdateDestinationRect();
}
done:
return hr;
}
//-------------------------------------------------------------------
// DestroyDevice
//
// Release all Direct3D resources.
//-------------------------------------------------------------------
void DrawDevice::DestroyDevice()
{
SafeRelease(&m_pSwapChain);
SafeRelease(&m_pDevice);
SafeRelease(&m_pD3D);
}
//-------------------------------------------------------------------
//
// Conversion functions
//
//-------------------------------------------------------------------
__forceinline BYTE Clip(int clr)
{
return (BYTE)(clr < 0 ? 0 : ( clr > 255 ? 255 : clr ));
}
__forceinline RGBQUAD ConvertYCrCbToRGB(
int y,
int cr,
int cb
)
{
RGBQUAD rgbq;
int c = y - 16;
int d = cb - 128;
int e = cr - 128;
rgbq.rgbRed = Clip(( 298 * c + 409 * e + 128) >> 8);
rgbq.rgbGreen = Clip(( 298 * c - 100 * d - 208 * e + 128) >> 8);
rgbq.rgbBlue = Clip(( 298 * c + 516 * d + 128) >> 8);
return rgbq;
}
//-------------------------------------------------------------------
// TransformImage_RGB24
//
// RGB-24 to RGB-32
//-------------------------------------------------------------------
void TransformImage_RGB24(
BYTE* pDest,
LONG lDestStride,
const BYTE* pSrc,
LONG lSrcStride,
DWORD dwWidthInPixels,
DWORD dwHeightInPixels
)
{
for (DWORD y = 0; y < dwHeightInPixels; y++)
{
RGBTRIPLE *pSrcPel = (RGBTRIPLE*)pSrc;
DWORD *pDestPel = (DWORD*)pDest;
for (DWORD x = 0; x < dwWidthInPixels; x++)
{
pDestPel[x] = D3DCOLOR_XRGB(
pSrcPel[x].rgbtRed,
pSrcPel[x].rgbtGreen,
pSrcPel[x].rgbtBlue
);
}
pSrc += lSrcStride;
pDest += lDestStride;
}
}
//-------------------------------------------------------------------
// TransformImage_RGB32
//
// RGB-32 to RGB-32
//
// Note: This function is needed to copy the image from system
// memory to the Direct3D surface.
//-------------------------------------------------------------------
void TransformImage_RGB32(
BYTE* pDest,
LONG lDestStride,
const BYTE* pSrc,
LONG lSrcStride,
DWORD dwWidthInPixels,
DWORD dwHeightInPixels
)
{
MFCopyImage(pDest, lDestStride, pSrc, lSrcStride, dwWidthInPixels * 4, dwHeightInPixels);
}
//-------------------------------------------------------------------
// TransformImage_YUY2
//
// YUY2 to RGB-32
//-------------------------------------------------------------------
void TransformImage_YUY2(
BYTE* pDest,
LONG lDestStride,
const BYTE* pSrc,
LONG lSrcStride,
DWORD dwWidthInPixels,
DWORD dwHeightInPixels
)
{
for (DWORD y = 0; y < dwHeightInPixels; y++)
{
RGBQUAD *pDestPel = (RGBQUAD*)pDest;
WORD *pSrcPel = (WORD*)pSrc;
for (DWORD x = 0; x < dwWidthInPixels; x += 2)
{
// Byte order is U0 Y0 V0 Y1
int y0 = (int)LOBYTE(pSrcPel[x]);
int u0 = (int)HIBYTE(pSrcPel[x]);
int y1 = (int)LOBYTE(pSrcPel[x + 1]);
int v0 = (int)HIBYTE(pSrcPel[x + 1]);
pDestPel[x] = ConvertYCrCbToRGB(y0, v0, u0);
pDestPel[x + 1] = ConvertYCrCbToRGB(y1, v0, u0);
}
pSrc += lSrcStride;
pDest += lDestStride;
}
}
//-------------------------------------------------------------------
// TransformImage_NV12
//
// NV12 to RGB-32
//-------------------------------------------------------------------
void TransformImage_NV12(
BYTE* pDst,
LONG dstStride,
const BYTE* pSrc,
LONG srcStride,
DWORD dwWidthInPixels,
DWORD dwHeightInPixels
)
{
const BYTE* lpBitsY = pSrc;
const BYTE* lpBitsCb = lpBitsY + (dwHeightInPixels * srcStride);;
const BYTE* lpBitsCr = lpBitsCb + 1;
for (UINT y = 0; y < dwHeightInPixels; y += 2)
{
const BYTE* lpLineY1 = lpBitsY;
const BYTE* lpLineY2 = lpBitsY + srcStride;
const BYTE* lpLineCr = lpBitsCr;
const BYTE* lpLineCb = lpBitsCb;
LPBYTE lpDibLine1 = pDst;
LPBYTE lpDibLine2 = pDst + dstStride;
for (UINT x = 0; x < dwWidthInPixels; x += 2)
{
int y0 = (int)lpLineY1[0];
int y1 = (int)lpLineY1[1];
int y2 = (int)lpLineY2[0];
int y3 = (int)lpLineY2[1];
int cb = (int)lpLineCb[0];
int cr = (int)lpLineCr[0];
RGBQUAD r = ConvertYCrCbToRGB(y0, cr, cb);
lpDibLine1[0] = r.rgbBlue;
lpDibLine1[1] = r.rgbGreen;
lpDibLine1[2] = r.rgbRed;
lpDibLine1[3] = 0; // Alpha
r = ConvertYCrCbToRGB(y1, cr, cb);
lpDibLine1[4] = r.rgbBlue;
lpDibLine1[5] = r.rgbGreen;
lpDibLine1[6] = r.rgbRed;
lpDibLine1[7] = 0; // Alpha
r = ConvertYCrCbToRGB(y2, cr, cb);
lpDibLine2[0] = r.rgbBlue;
lpDibLine2[1] = r.rgbGreen;
lpDibLine2[2] = r.rgbRed;
lpDibLine2[3] = 0; // Alpha
r = ConvertYCrCbToRGB(y3, cr, cb);
lpDibLine2[4] = r.rgbBlue;
lpDibLine2[5] = r.rgbGreen;
lpDibLine2[6] = r.rgbRed;
lpDibLine2[7] = 0; // Alpha
lpLineY1 += 2;
lpLineY2 += 2;
lpLineCr += 2;
lpLineCb += 2;
lpDibLine1 += 8;
lpDibLine2 += 8;
}
pDst += (2 * dstStride);
lpBitsY += (2 * srcStride);
lpBitsCr += srcStride;
lpBitsCb += srcStride;
}
}
//-------------------------------------------------------------------
// LetterBoxDstRect
//
// Takes a src rectangle and constructs the largest possible
// destination rectangle within the specifed destination rectangle
// such thatthe video maintains its current shape.
//
// This function assumes that pels are the same shape within both the
// source and destination rectangles.
//
//-------------------------------------------------------------------
RECT LetterBoxRect(const RECT& rcSrc, const RECT& rcDst)
{
// figure out src/dest scale ratios
int iSrcWidth = Width(rcSrc);
int iSrcHeight = Height(rcSrc);
int iDstWidth = Width(rcDst);
int iDstHeight = Height(rcDst);
int iDstLBWidth;
int iDstLBHeight;
if (MulDiv(iSrcWidth, iDstHeight, iSrcHeight) <= iDstWidth) {
// Column letter boxing ("pillar box")
iDstLBWidth = MulDiv(iDstHeight, iSrcWidth, iSrcHeight);
iDstLBHeight = iDstHeight;
}
else {
// Row letter boxing.
iDstLBWidth = iDstWidth;
iDstLBHeight = MulDiv(iDstWidth, iSrcHeight, iSrcWidth);
}
// Create a centered rectangle within the current destination rect
RECT rc;
LONG left = rcDst.left + ((iDstWidth - iDstLBWidth) / 2);
LONG top = rcDst.top + ((iDstHeight - iDstLBHeight) / 2);
SetRect(&rc, left, top, left + iDstLBWidth, top + iDstLBHeight);
return rc;
}
//-----------------------------------------------------------------------------
// CorrectAspectRatio
//
// Converts a rectangle from the source's pixel aspect ratio (PAR) to 1:1 PAR.
// Returns the corrected rectangle.
//
// For example, a 720 x 486 rect with a PAR of 9:10, when converted to 1x1 PAR,
// is stretched to 720 x 540.
//-----------------------------------------------------------------------------
RECT CorrectAspectRatio(const RECT& src, const MFRatio& srcPAR)
{
// Start with a rectangle the same size as src, but offset to the origin (0,0).
RECT rc = {0, 0, src.right - src.left, src.bottom - src.top};
if ((srcPAR.Numerator != 1) || (srcPAR.Denominator != 1))
{
// Correct for the source's PAR.
if (srcPAR.Numerator > srcPAR.Denominator)
{
// The source has "wide" pixels, so stretch the width.
rc.right = MulDiv(rc.right, srcPAR.Numerator, srcPAR.Denominator);
}
else if (srcPAR.Numerator < srcPAR.Denominator)
{
// The source has "tall" pixels, so stretch the height.
rc.bottom = MulDiv(rc.bottom, srcPAR.Denominator, srcPAR.Numerator);
}
// else: PAR is 1:1, which is a no-op.
}
return rc;
}
//-----------------------------------------------------------------------------
// GetDefaultStride
//
// Gets the default stride for a video frame, assuming no extra padding bytes.
//
//-----------------------------------------------------------------------------
HRESULT GetDefaultStride(IMFMediaType *pType, LONG *plStride)
{
LONG lStride = 0;
// Try to get the default stride from the media type.
HRESULT hr = pType->GetUINT32(MF_MT_DEFAULT_STRIDE, (UINT32*)&lStride);
if (FAILED(hr))
{
// Attribute not set. Try to calculate the default stride.
GUID subtype = GUID_NULL;
UINT32 width = 0;
UINT32 height = 0;
// Get the subtype and the image size.
hr = pType->GetGUID(MF_MT_SUBTYPE, &subtype);
if (SUCCEEDED(hr))
{
hr = MFGetAttributeSize(pType, MF_MT_FRAME_SIZE, &width, &height);
}
if (SUCCEEDED(hr))
{
hr = MFGetStrideForBitmapInfoHeader(subtype.Data1, width, &lStride);
}
// Set the attribute for later reference.
if (SUCCEEDED(hr))
{
(void)pType->SetUINT32(MF_MT_DEFAULT_STRIDE, UINT32(lStride));
}
}
if (SUCCEEDED(hr))
{
*plStride = lStride;
}
return hr;
}