#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using Microsoft::WRL::ComPtr; namespace { struct MimirMfGpuCapture { ComPtr source; ComPtr reader; ComPtr device; ComPtr context; ComPtr deviceManager; ComPtr sharedTexture; ComPtr producerFence; ComPtr fenceContext; HANDLE sharedHandle = nullptr; HANDLE producerFenceHandle = nullptr; int width = 0; int height = 0; DXGI_FORMAT format = DXGI_FORMAT_UNKNOWN; std::uint64_t sequence = 0; }; bool debugEnabled() { return GetEnvironmentVariableA("MIMIR_MF_GPU_DEBUG", nullptr, 0) > 0; } void debugLog(const char* format, ...) { if (!debugEnabled()) { return; } std::fprintf(stderr, "mimir-mf-gpu: "); va_list args; va_start(args, format); std::vfprintf(stderr, format, args); va_end(args); std::fprintf(stderr, "\n"); } std::wstring widen(const char* value) { if (!value) { return {}; } const int chars = MultiByteToWideChar(CP_UTF8, 0, value, -1, nullptr, 0); if (chars <= 1) { return {}; } std::wstring result(static_cast(chars - 1), L'\0'); MultiByteToWideChar(CP_UTF8, 0, value, -1, result.data(), chars); return result; } std::wstring lowercase(std::wstring value) { std::transform(value.begin(), value.end(), value.begin(), [](wchar_t c) { return static_cast(towlower(c)); }); return value; } GUID subtypeFromText(const char* text) { const std::string value = text ? text : ""; std::string upper; upper.resize(value.size()); std::transform(value.begin(), value.end(), upper.begin(), [](unsigned char c) { return static_cast(std::toupper(c)); }); if (upper == "MJPG") return MFVideoFormat_MJPG; if (upper == "H264") return MFVideoFormat_H264; if (upper == "NV12") return MFVideoFormat_NV12; if (upper == "YUY2") return MFVideoFormat_YUY2; if (upper == "RGB32" || upper == "BGRA") return MFVideoFormat_RGB32; return GUID_NULL; } DXGI_FORMAT dxgiFormatForSubtype(const GUID& subtype) { if (subtype == MFVideoFormat_NV12) return DXGI_FORMAT_NV12; if (subtype == MFVideoFormat_RGB32) return DXGI_FORMAT_B8G8R8A8_UNORM; if (subtype == MFVideoFormat_YUY2) return DXGI_FORMAT_YUY2; return DXGI_FORMAT_UNKNOWN; } const char* textForSubtype(const GUID& subtype) { if (subtype == MFVideoFormat_NV12) return "NV12"; if (subtype == MFVideoFormat_RGB32) return "Bgra8"; if (subtype == MFVideoFormat_YUY2) return "YUY2"; return "Unknown"; } bool findVideoSource(const wchar_t* needle, IMFMediaSource** mediaSource) { *mediaSource = nullptr; ComPtr attributes; if (FAILED(MFCreateAttributes(&attributes, 2))) { debugLog("MFCreateAttributes for devices failed"); return false; } attributes->SetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID); IMFActivate** rawDevices = nullptr; UINT32 count = 0; if (FAILED(MFEnumDeviceSources(attributes.Get(), &rawDevices, &count))) { debugLog("MFEnumDeviceSources failed"); return false; } debugLog("device sources=%u", count); const std::wstring target = lowercase(needle ? needle : L""); bool found = false; for (UINT32 index = 0; index < count; ++index) { ComPtr activate(rawDevices[index]); rawDevices[index] = nullptr; WCHAR* symbolicLink = nullptr; UINT32 symbolicLength = 0; activate->GetAllocatedString(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, &symbolicLink, &symbolicLength); const std::wstring link = lowercase(symbolicLink ? symbolicLink : L""); if (debugEnabled()) { std::fwprintf(stderr, L"mimir-mf-gpu: source[%u]=%ls\n", index, link.c_str()); } CoTaskMemFree(symbolicLink); if (!target.empty() && link.find(target) == std::wstring::npos) { continue; } if (SUCCEEDED(activate->ActivateObject(IID_PPV_ARGS(mediaSource)))) { debugLog("activated source index=%u", index); found = true; break; } } for (UINT32 index = 0; index < count; ++index) { if (rawDevices[index]) { rawDevices[index]->Release(); } } CoTaskMemFree(rawDevices); return found; } bool mediaTypeMatches(IMFMediaType* type, int width, int height, const GUID& subtype, double minFps) { GUID actualSubtype{}; UINT32 actualWidth = 0; UINT32 actualHeight = 0; UINT32 fpsNumerator = 0; UINT32 fpsDenominator = 0; if (FAILED(type->GetGUID(MF_MT_SUBTYPE, &actualSubtype)) || actualSubtype != subtype || FAILED(MFGetAttributeSize(type, MF_MT_FRAME_SIZE, &actualWidth, &actualHeight)) || static_cast(actualWidth) != width || static_cast(actualHeight) != height) { return false; } if (SUCCEEDED(MFGetAttributeRatio(type, MF_MT_FRAME_RATE, &fpsNumerator, &fpsDenominator)) && fpsDenominator != 0) { return static_cast(fpsNumerator) / fpsDenominator >= minFps; } return minFps <= 0.0; } bool setInputType(IMFSourceReader* reader, int width, int height, const GUID& subtype, double minFps) { for (DWORD index = 0;; ++index) { ComPtr type; const HRESULT hr = reader->GetNativeMediaType(MF_SOURCE_READER_FIRST_VIDEO_STREAM, index, &type); if (hr == MF_E_NO_MORE_TYPES) { debugLog("no input type matched %dx%d minFps=%.3f", width, height, minFps); return false; } if (FAILED(hr)) { debugLog("GetNativeMediaType failed index=%lu hr=0x%08lx", index, static_cast(hr)); return false; } if (debugEnabled()) { GUID actualSubtype{}; UINT32 actualWidth = 0; UINT32 actualHeight = 0; UINT32 fpsNumerator = 0; UINT32 fpsDenominator = 0; type->GetGUID(MF_MT_SUBTYPE, &actualSubtype); MFGetAttributeSize(type.Get(), MF_MT_FRAME_SIZE, &actualWidth, &actualHeight); MFGetAttributeRatio(type.Get(), MF_MT_FRAME_RATE, &fpsNumerator, &fpsDenominator); debugLog( "nativeType[%lu] %ux%u subtype=%08lx fps=%u/%u", index, actualWidth, actualHeight, static_cast(actualSubtype.Data1), fpsNumerator, fpsDenominator); } if (mediaTypeMatches(type.Get(), width, height, subtype, minFps)) { const HRESULT setHr = reader->SetCurrentMediaType(MF_SOURCE_READER_FIRST_VIDEO_STREAM, nullptr, type.Get()); debugLog("SetCurrentMediaType input index=%lu hr=0x%08lx", index, static_cast(setHr)); return SUCCEEDED(setHr); } } } bool setOutputType(IMFSourceReader* reader, const GUID& subtype) { ComPtr output; if (FAILED(MFCreateMediaType(&output))) { return false; } output->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video); output->SetGUID(MF_MT_SUBTYPE, subtype); const HRESULT hr = reader->SetCurrentMediaType(MF_SOURCE_READER_FIRST_VIDEO_STREAM, nullptr, output.Get()); debugLog("SetCurrentMediaType output hr=0x%08lx", static_cast(hr)); return SUCCEEDED(hr); } bool createSharedTexture(MimirMfGpuCapture& capture, const GUID& outputSubtype) { ComPtr current; if (FAILED(capture.reader->GetCurrentMediaType(MF_SOURCE_READER_FIRST_VIDEO_STREAM, ¤t))) { debugLog("GetCurrentMediaType output failed"); return false; } UINT32 width = 0; UINT32 height = 0; GUID subtype{}; if (FAILED(MFGetAttributeSize(current.Get(), MF_MT_FRAME_SIZE, &width, &height)) || FAILED(current->GetGUID(MF_MT_SUBTYPE, &subtype))) { return false; } const auto format = dxgiFormatForSubtype(subtype); if (format == DXGI_FORMAT_UNKNOWN || subtype != outputSubtype) { debugLog("output texture subtype/format unsupported subtype=%08lx", static_cast(subtype.Data1)); return false; } D3D11_TEXTURE2D_DESC desc{}; desc.Width = width; desc.Height = height; desc.MipLevels = 1; desc.ArraySize = 1; desc.Format = format; desc.SampleDesc.Count = 1; desc.Usage = D3D11_USAGE_DEFAULT; desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; desc.MiscFlags = D3D11_RESOURCE_MISC_SHARED_NTHANDLE | D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX; const HRESULT textureHr = capture.device->CreateTexture2D(&desc, nullptr, &capture.sharedTexture); if (FAILED(textureHr)) { debugLog("CreateTexture2D shared NT failed hr=0x%08lx; retrying legacy shared", static_cast(textureHr)); desc.MiscFlags = D3D11_RESOURCE_MISC_SHARED; const HRESULT legacyTextureHr = capture.device->CreateTexture2D(&desc, nullptr, &capture.sharedTexture); if (FAILED(legacyTextureHr)) { debugLog("CreateTexture2D legacy shared failed hr=0x%08lx", static_cast(legacyTextureHr)); return false; } } ComPtr dxgiResource; const HRESULT asHr = capture.sharedTexture.As(&dxgiResource); const HRESULT handleHr = SUCCEEDED(asHr) ? dxgiResource->CreateSharedHandle(nullptr, DXGI_SHARED_RESOURCE_READ | DXGI_SHARED_RESOURCE_WRITE, nullptr, &capture.sharedHandle) : asHr; if (FAILED(asHr) || FAILED(handleHr)) { ComPtr legacyResource; const HRESULT legacyAsHr = capture.sharedTexture.As(&legacyResource); const HRESULT legacyHandleHr = SUCCEEDED(legacyAsHr) ? legacyResource->GetSharedHandle(&capture.sharedHandle) : legacyAsHr; if (FAILED(legacyAsHr) || FAILED(legacyHandleHr)) { debugLog( "CreateSharedHandle failed as=0x%08lx handle=0x%08lx legacyAs=0x%08lx legacyHandle=0x%08lx", static_cast(asHr), static_cast(handleHr), static_cast(legacyAsHr), static_cast(legacyHandleHr)); return false; } } capture.width = static_cast(width); capture.height = static_cast(height); capture.format = format; return true; } bool createProducerFence(MimirMfGpuCapture& capture) { ComPtr device5; if (FAILED(capture.device.As(&device5)) || FAILED(capture.context.As(&capture.fenceContext))) { debugLog("D3D11 fence interfaces unavailable; shared texture will be unsynchronized"); return true; } const HRESULT fenceHr = device5->CreateFence( 0, D3D11_FENCE_FLAG_SHARED, IID_PPV_ARGS(&capture.producerFence)); if (FAILED(fenceHr)) { debugLog("CreateFence failed hr=0x%08lx; shared texture will be unsynchronized", static_cast(fenceHr)); capture.fenceContext.Reset(); return true; } const HRESULT handleHr = capture.producerFence->CreateSharedHandle(nullptr, GENERIC_ALL, nullptr, &capture.producerFenceHandle); if (FAILED(handleHr)) { debugLog("CreateSharedHandle fence failed hr=0x%08lx; shared texture will be unsynchronized", static_cast(handleHr)); capture.producerFence.Reset(); capture.fenceContext.Reset(); } return true; } } // namespace extern "C" { __declspec(dllexport) MimirMfGpuCapture* mimir_mf_gpu_create( const char* pathNeedle, int width, int height, const char* inputSubtypeText, const char* outputSubtypeText, double minFps) { CoInitializeEx(nullptr, COINIT_MULTITHREADED); if (FAILED(MFStartup(MF_VERSION, MFSTARTUP_NOSOCKET))) { debugLog("MFStartup failed"); return nullptr; } auto capture = new MimirMfGpuCapture(); const UINT deviceFlags = D3D11_CREATE_DEVICE_VIDEO_SUPPORT | D3D11_CREATE_DEVICE_BGRA_SUPPORT; D3D_FEATURE_LEVEL featureLevels[] = { D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0 }; D3D_FEATURE_LEVEL featureLevel{}; const HRESULT deviceHr = D3D11CreateDevice( nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, deviceFlags, featureLevels, ARRAYSIZE(featureLevels), D3D11_SDK_VERSION, &capture->device, &featureLevel, &capture->context); if (FAILED(deviceHr)) { debugLog("D3D11CreateDevice failed hr=0x%08lx", static_cast(deviceHr)); delete capture; return nullptr; } ComPtr multithread; if (SUCCEEDED(capture->device.As(&multithread))) { multithread->SetMultithreadProtected(TRUE); } UINT resetToken = 0; if (FAILED(MFCreateDXGIDeviceManager(&resetToken, &capture->deviceManager)) || FAILED(capture->deviceManager->ResetDevice(capture->device.Get(), resetToken))) { debugLog("DXGI device manager setup failed"); delete capture; return nullptr; } const auto needle = widen(pathNeedle); if (!findVideoSource(needle.c_str(), &capture->source)) { debugLog("no source matched needle"); delete capture; return nullptr; } ComPtr readerAttributes; if (FAILED(MFCreateAttributes(&readerAttributes, 4))) { debugLog("MFCreateAttributes reader failed"); delete capture; return nullptr; } readerAttributes->SetUnknown(MF_SOURCE_READER_D3D_MANAGER, capture->deviceManager.Get()); readerAttributes->SetUINT32(MF_READWRITE_ENABLE_HARDWARE_TRANSFORMS, TRUE); readerAttributes->SetUINT32(MF_LOW_LATENCY, TRUE); readerAttributes->SetUINT32(MF_SOURCE_READER_ENABLE_ADVANCED_VIDEO_PROCESSING, TRUE); const HRESULT readerHr = MFCreateSourceReaderFromMediaSource(capture->source.Get(), readerAttributes.Get(), &capture->reader); if (FAILED(readerHr)) { debugLog("MFCreateSourceReaderFromMediaSource failed hr=0x%08lx", static_cast(readerHr)); delete capture; return nullptr; } const auto inputSubtype = subtypeFromText(inputSubtypeText); const auto outputSubtype = subtypeFromText(outputSubtypeText && outputSubtypeText[0] ? outputSubtypeText : "NV12"); if (inputSubtype == GUID_NULL || outputSubtype == GUID_NULL || !setInputType(capture->reader.Get(), width, height, inputSubtype, minFps) || !setOutputType(capture->reader.Get(), outputSubtype) || !createSharedTexture(*capture, outputSubtype) || !createProducerFence(*capture)) { debugLog("create failed input=%s output=%s", inputSubtypeText ? inputSubtypeText : "", outputSubtypeText ? outputSubtypeText : ""); delete capture; return nullptr; } return capture; } __declspec(dllexport) int mimir_mf_gpu_read( MimirMfGpuCapture* capture, HANDLE* sharedHandle, int* width, int* height, char* format, int formatCapacity, std::int64_t* timestampNs, std::uint64_t* sequence, HANDLE* producerFenceHandle) { if (!capture || !capture->reader || !capture->sharedTexture || !sharedHandle) { return 0; } DWORD streamIndex = 0; DWORD flags = 0; LONGLONG timestamp = 0; ComPtr sample; const HRESULT hr = capture->reader->ReadSample( MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, &streamIndex, &flags, ×tamp, &sample); if (FAILED(hr) || !sample || (flags & MF_SOURCE_READERF_STREAMTICK) != 0) { return 0; } ComPtr buffer; ComPtr dxgiBuffer; ComPtr decodedTexture; UINT subresource = 0; if (FAILED(sample->GetBufferByIndex(0, &buffer)) || FAILED(buffer.As(&dxgiBuffer)) || FAILED(dxgiBuffer->GetResource(IID_PPV_ARGS(&decodedTexture))) || FAILED(dxgiBuffer->GetSubresourceIndex(&subresource))) { return 0; } const auto nextSequence = capture->sequence + 1; capture->context->CopySubresourceRegion(capture->sharedTexture.Get(), 0, 0, 0, 0, decodedTexture.Get(), subresource, nullptr); if (capture->producerFence && capture->fenceContext) { capture->fenceContext->Signal(capture->producerFence.Get(), nextSequence); } capture->context->Flush(); *sharedHandle = capture->sharedHandle; if (width) *width = capture->width; if (height) *height = capture->height; if (format && formatCapacity > 0) { const char* text = capture->format == DXGI_FORMAT_NV12 ? textForSubtype(MFVideoFormat_NV12) : capture->format == DXGI_FORMAT_B8G8R8A8_UNORM ? textForSubtype(MFVideoFormat_RGB32) : textForSubtype(MFVideoFormat_YUY2); std::snprintf(format, static_cast(formatCapacity), "%s", text); } if (timestampNs) *timestampNs = timestamp * 100; capture->sequence = nextSequence; if (sequence) *sequence = capture->sequence; if (producerFenceHandle) *producerFenceHandle = capture->producerFenceHandle; return 1; } __declspec(dllexport) void mimir_mf_gpu_destroy(MimirMfGpuCapture* capture) { if (!capture) { return; } if (capture->sharedHandle) { CloseHandle(capture->sharedHandle); } if (capture->producerFenceHandle) { CloseHandle(capture->producerFenceHandle); } if (capture->source) { capture->source->Shutdown(); } delete capture; MFShutdown(); CoUninitialize(); } }