#pragma once #include "capture_error.h" #include "capture_color.h" #include "capture_input.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace piik::capture { // One inbox decoder owns the compressed reference chain. Consumers must finish // copying/retaining an output sample before the callback returns. class H264Decoder final { public: using Consumer = std::function; explicit H264Decoder(IMFDXGIDeviceManager* manager) { Check(CoCreateInstance(__uuidof(CMSH264DecoderMFT), nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&decoder_)), "create"); Microsoft::WRL::ComPtr attributes; Check(decoder_->GetAttributes(&attributes), "attributes"); UINT32 aware = FALSE; if (manager && SUCCEEDED(attributes->GetUINT32(MF_SA_D3D11_AWARE, &aware)) && aware) { Check(decoder_->ProcessMessage(MFT_MESSAGE_SET_D3D_MANAGER, reinterpret_cast(manager)), "device-manager"); } Microsoft::WRL::ComPtr codec; Check(decoder_.As(&codec), "codec-api"); for (const auto& setting : { std::pair{CODECAPI_AVLowLatencyMode, TRUE}, std::pair{CODECAPI_AVDecVideoMaxCodedWidth, 2560}, std::pair{CODECAPI_AVDecVideoMaxCodedHeight, 1440}}) { VARIANT value; VariantInit(&value); value.vt = VT_UI4; value.ulVal = setting.second; Check(codec->SetValue(&setting.first, &value), "codec-setting"); } } ~H264Decoder() { if (configured_) (void)decoder_->ProcessMessage(MFT_MESSAGE_COMMAND_FLUSH, 0); } H264Decoder(const H264Decoder&) = delete; H264Decoder& operator=(const H264Decoder&) = delete; DXGI_COLOR_SPACE_TYPE ColorSpace() const { Microsoft::WRL::ComPtr type; Check(decoder_->GetOutputCurrentType(0, &type), "current-output-type"); return windows::DecodedVideoColor(type.Get()); } void Decode(const InputEnvelope& input, const Consumer& consume) { if (input.kind != 2 || input.data.empty() || input.data.size() > 4 * 1024 * 1024) throw std::runtime_error("Invalid H264 decoder input"); if (!configured_) { if (!input.recovery) throw std::runtime_error("H264 decoder needs an independent recovery input"); Microsoft::WRL::ComPtr type; Check(MFCreateMediaType(&type), "input-type"); Check(type->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video), "input-major"); Check(type->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_H264), "input-subtype"); Check(MFSetAttributeSize(type.Get(), MF_MT_FRAME_SIZE, input.width, input.height), "input-size"); Check(type->SetUINT32(MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive), "input-progressive"); Check(decoder_->SetInputType(0, type.Get(), 0), "set-input"); SelectOutput(); Check(decoder_->ProcessMessage(MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, 0), "begin"); Check(decoder_->ProcessMessage(MFT_MESSAGE_NOTIFY_START_OF_STREAM, 0), "start"); configured_ = true; } Microsoft::WRL::ComPtr buffer; Check(MFCreateMemoryBuffer(static_cast(input.data.size()), &buffer), "input-buffer"); BYTE* bytes = nullptr; Check(buffer->Lock(&bytes, nullptr, nullptr), "input-lock"); std::copy(input.data.begin(), input.data.end(), bytes); Check(buffer->Unlock(), "input-unlock"); Check(buffer->SetCurrentLength(static_cast(input.data.size())), "input-length"); Microsoft::WRL::ComPtr sample; Check(MFCreateSample(&sample), "input-sample"); Check(sample->AddBuffer(buffer.Get()), "input-sample-buffer"); Check(sample->SetSampleTime(static_cast(input.timestamp)), "input-timestamp"); Check(sample->SetSampleDuration(static_cast(input.duration)), "input-duration"); HRESULT result = decoder_->ProcessInput(0, sample.Get(), 0); if (result == MF_E_NOTACCEPTING) { Drain(consume); result = decoder_->ProcessInput(0, sample.Get(), 0); } Check(result, "process-input"); Drain(consume); } private: static void Check(HRESULT result, const char* operation) { windows::Check(result, std::string("h264-decoder-") + operation); } void SelectOutput() { for (DWORD index = 0; index < 64; ++index) { Microsoft::WRL::ComPtr type; const HRESULT result = decoder_->GetOutputAvailableType(0, index, &type); if (result == MF_E_NO_MORE_TYPES) break; Check(result, "output-type"); GUID subtype{}; Check(type->GetGUID(MF_MT_SUBTYPE, &subtype), "output-subtype"); if (subtype != MFVideoFormat_NV12) continue; UINT32 width = 0, height = 0; Check(MFGetAttributeSize(type.Get(), MF_MT_FRAME_SIZE, &width, &height), "output-size"); if (width < 2 || height < 2 || width > 2560 || height > 1440 || (width & 1) || (height & 1)) { throw std::runtime_error("H264 decoder output dimensions exceed the source bound"); } Check(decoder_->SetOutputType(0, type.Get(), 0), "set-output"); UINT32 stride = 0; if (FAILED(type->GetUINT32(MF_MT_DEFAULT_STRIDE, &stride))) { LONG fallback = 0; Check(MFGetStrideForBitmapInfoHeader(MFVideoFormat_NV12.Data1, width, &fallback), "output-stride"); stride = static_cast(fallback); } if (static_cast(stride) < static_cast(width)) throw std::runtime_error("H264 decoder returned an invalid NV12 stride"); width_ = width; height_ = height; stride_ = static_cast(stride); return; } throw std::runtime_error("H264 decoder has no NV12 output"); } void Drain(const Consumer& consume) { for (unsigned iteration = 0; iteration < 64; ++iteration) { MFT_OUTPUT_STREAM_INFO info{}; Check(decoder_->GetOutputStreamInfo(0, &info), "output-info"); Microsoft::WRL::ComPtr owned; if (!(info.dwFlags & (MFT_OUTPUT_STREAM_PROVIDES_SAMPLES | MFT_OUTPUT_STREAM_CAN_PROVIDE_SAMPLES))) { const DWORD expected = static_cast(stride_) * height_ * 3 / 2; const DWORD size = std::max(info.cbSize, expected); if (size > 2560 * 1440 * 2) throw std::runtime_error("H264 decoder output allocation exceeds its bound"); Microsoft::WRL::ComPtr buffer; Check(MFCreateAlignedMemoryBuffer(size, info.cbAlignment ? info.cbAlignment - 1 : 0, &buffer), "output-buffer"); Check(MFCreateSample(&owned), "output-sample"); Check(owned->AddBuffer(buffer.Get()), "output-sample-buffer"); } MFT_OUTPUT_DATA_BUFFER output{}; output.pSample = owned.Get(); DWORD status = 0; const HRESULT result = decoder_->ProcessOutput(0, 1, &output, &status); Microsoft::WRL::ComPtr events; events.Attach(output.pEvents); if (!owned) owned.Attach(output.pSample); if (result == MF_E_TRANSFORM_NEED_MORE_INPUT) return; if (result == MF_E_TRANSFORM_STREAM_CHANGE) { SelectOutput(); continue; } Check(result, "process-output"); if (owned && !(output.dwStatus & MFT_OUTPUT_DATA_BUFFER_NO_SAMPLE)) { consume(owned.Get(), width_, height_, stride_); } } throw std::runtime_error("H264 decoder exceeded its bounded output pump"); } Microsoft::WRL::ComPtr decoder_; UINT32 width_ = 0; UINT32 height_ = 0; LONG stride_ = 0; bool configured_ = false; }; } // namespace piik::capture