#include "mf_encoder.h" #include #include #include #include #include #include "../../common/bt709_video.h" #pragma comment(lib, "mfplat.lib") #pragma comment(lib, "mfreadwrite.lib") #pragma comment(lib, "mf.lib") #pragma comment(lib, "mfuuid.lib") MFEncoder::MFEncoder() {} MFEncoder::~MFEncoder() { finalize(); } bool MFEncoder::initialize(const std::wstring& outputPath, int width, int height, int fps, ID3D11Device* device, ID3D11DeviceContext* context) { if (initialized_) return false; if (width % 2 != 0 || height % 2 != 0) { std::cerr << "ERROR: Encoder dimensions must be even, got " << width << "x" << height << std::endl; return false; } width_ = width; height_ = height; fps_ = fps; device_ = device; context_ = context; HRESULT hr = MFStartup(MF_VERSION); if (FAILED(hr)) { std::cerr << "ERROR: MFStartup failed: 0x" << std::hex << hr << std::endl; return false; } // Output media type (H.264) ComPtr outputType; hr = MFCreateMediaType(&outputType); if (FAILED(hr)) return false; outputType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video); outputType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_H264); outputType->SetUINT32(MF_MT_AVG_BITRATE, 20000000); MFSetAttributeSize(outputType.Get(), MF_MT_FRAME_SIZE, width_, height_); MFSetAttributeRatio(outputType.Get(), MF_MT_FRAME_RATE, fps_, 1); MFSetAttributeRatio(outputType.Get(), MF_MT_PIXEL_ASPECT_RATIO, 1, 1); outputType->SetUINT32(MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive); hr = setBt709LimitedVideoAttributes(outputType.Get()); if (FAILED(hr)) return false; // Input media type (NV12) ComPtr inputType; hr = MFCreateMediaType(&inputType); if (FAILED(hr)) return false; inputType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video); inputType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_NV12); MFSetAttributeSize(inputType.Get(), MF_MT_FRAME_SIZE, width_, height_); MFSetAttributeRatio(inputType.Get(), MF_MT_FRAME_RATE, fps_, 1); MFSetAttributeRatio(inputType.Get(), MF_MT_PIXEL_ASPECT_RATIO, 1, 1); inputType->SetUINT32(MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive); hr = setBt709LimitedVideoAttributes(inputType.Get()); if (FAILED(hr)) return false; // Create SinkWriter with MPEG4 container ComPtr writerAttrs; hr = MFCreateAttributes(&writerAttrs, 1); if (FAILED(hr)) return false; writerAttrs->SetUINT32(MF_READWRITE_ENABLE_HARDWARE_TRANSFORMS, TRUE); hr = MFCreateSinkWriterFromURL(outputPath.c_str(), nullptr, writerAttrs.Get(), &sinkWriter_); if (FAILED(hr)) { std::cerr << "ERROR: MFCreateSinkWriterFromURL failed: 0x" << std::hex << hr << std::endl; return false; } hr = sinkWriter_->AddStream(outputType.Get(), &streamIndex_); if (FAILED(hr)) { std::cerr << "ERROR: AddStream failed: 0x" << std::hex << hr << std::endl; return false; } hr = sinkWriter_->SetInputMediaType(streamIndex_, inputType.Get(), nullptr); if (FAILED(hr)) { std::cerr << "ERROR: SetInputMediaType failed: 0x" << std::hex << hr << std::endl; return false; } hr = sinkWriter_->BeginWriting(); if (FAILED(hr)) { std::cerr << "ERROR: BeginWriting failed: 0x" << std::hex << hr << std::endl; return false; } // Pre-allocate staging texture D3D11_TEXTURE2D_DESC stagingDesc = {}; stagingDesc.Width = width_; stagingDesc.Height = height_; stagingDesc.MipLevels = 1; stagingDesc.ArraySize = 1; stagingDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; stagingDesc.SampleDesc.Count = 1; stagingDesc.Usage = D3D11_USAGE_STAGING; stagingDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; hr = device_->CreateTexture2D(&stagingDesc, nullptr, &stagingTexture_); if (FAILED(hr)) { std::cerr << "ERROR: Failed to create staging texture: 0x" << std::hex << hr << std::endl; return false; } // Pre-allocate NV12 buffer const int ySize = width_ * height_; const int uvSize = (width_ / 2) * (height_ / 2) * 2; nv12Buffer_.resize(ySize + uvSize); initialized_ = true; return true; } bool MFEncoder::writeFrame(ID3D11Texture2D* texture, int64_t timestampHns) { if (!initialized_ || !sinkWriter_) return false; context_->CopyResource(stagingTexture_.Get(), texture); D3D11_MAPPED_SUBRESOURCE mapped; HRESULT hr = context_->Map(stagingTexture_.Get(), 0, D3D11_MAP_READ, 0, &mapped); if (FAILED(hr)) return false; // Convert full-range desktop BGRA to explicitly tagged BT.709 video-range NV12. const uint8_t* bgra = static_cast(mapped.pData); const int bgraPitch = static_cast(mapped.RowPitch); convertBgraToBt709LimitedNv12(bgra, bgraPitch, width_, height_, nv12Buffer_); context_->Unmap(stagingTexture_.Get(), 0); // Create MF sample DWORD bufferSize = static_cast(nv12Buffer_.size()); ComPtr buffer; hr = MFCreateMemoryBuffer(bufferSize, &buffer); if (FAILED(hr)) return false; BYTE* bufferData = nullptr; hr = buffer->Lock(&bufferData, nullptr, nullptr); if (FAILED(hr)) return false; std::memcpy(bufferData, nv12Buffer_.data(), bufferSize); buffer->Unlock(); buffer->SetCurrentLength(bufferSize); ComPtr sample; hr = MFCreateSample(&sample); if (FAILED(hr)) return false; sample->AddBuffer(buffer.Get()); sample->SetSampleTime(timestampHns); sample->SetSampleDuration(10000000LL / fps_); hr = sinkWriter_->WriteSample(streamIndex_, sample.Get()); return SUCCEEDED(hr); } bool MFEncoder::finalize() { if (!initialized_) return false; initialized_ = false; stagingTexture_.Reset(); nv12Buffer_.clear(); nv12Buffer_.shrink_to_fit(); if (!sinkWriter_) return false; HRESULT hr = sinkWriter_->Finalize(); sinkWriter_.Reset(); MFShutdown(); return SUCCEEDED(hr); }