#include "video_decoder.h" #include #include #include using Microsoft::WRL::ComPtr; // ─── Minimal SPS bit-reader ─────────────────────────────────────────────────── struct BitReader { const uint8_t* data; size_t pos = 0; uint32_t Bit() { uint32_t v = (data[pos / 8] >> (7 - pos % 8)) & 1; ++pos; return v; } uint32_t Bits(int n) { uint32_t v = 0; for (int i = 0; i < n; ++i) v = (v << 1) | Bit(); return v; } uint32_t UEG() { int lz = 0; while (Bit() == 0 && lz < 32) ++lz; if (lz == 0) return 0; return (1u << lz) - 1 + Bits(lz); } int32_t SEG() { uint32_t code = UEG(); return (code & 1) ? static_cast((code + 1) / 2) : -static_cast(code / 2); } }; // ─── SPS parser ─────────────────────────────────────────────────────────────── bool VideoDecoder::ParseSpsSize(const uint8_t* data, size_t len, int& out_w, int& out_h) { size_t nal_start = SIZE_MAX; for (size_t i = 0; i + 4 <= len; ++i) { if (data[i] == 0 && data[i+1] == 0 && data[i+2] == 0 && data[i+3] == 1 && i + 4 < len && (data[i+4] & 0x1F) == 7) { nal_start = i + 4; break; } if (data[i] == 0 && data[i+1] == 0 && data[i+2] == 1 && i + 3 < len && (data[i+3] & 0x1F) == 7) { nal_start = i + 3; break; } } if (nal_start == SIZE_MAX) return false; BitReader br{data + nal_start + 1}; uint8_t profile_idc = static_cast(br.Bits(8)); br.Bits(8); br.Bits(8); br.UEG(); static const uint8_t kHiProfiles[] = {100,110,122,244,44,83,86,118,128}; bool hi = false; for (uint8_t p : kHiProfiles) if (profile_idc == p) { hi = true; break; } if (hi) { uint32_t chroma = br.UEG(); if (chroma == 3) br.Bit(); br.UEG(); br.UEG(); br.Bit(); if (br.Bit()) { for (int idx = 0; idx < (chroma != 3 ? 8 : 12); ++idx) { if (br.Bit()) { int sz = idx < 6 ? 16 : 64; int last = 8, next = 8; for (int j = 0; j < sz; ++j) { if (next != 0) next = (last + br.SEG() + 256) % 256; last = next == 0 ? last : next; } } } } } br.UEG(); uint32_t poc = br.UEG(); if (poc == 0) br.UEG(); else if (poc == 1) { br.Bit(); br.SEG(); br.SEG(); uint32_t nc=br.UEG(); for(uint32_t j=0;j(user_data)->pixel_buf_; } // ─── VideoDecoder ───────────────────────────────────────────────────────────── VideoDecoder::VideoDecoder(FlutterDesktopTextureRegistrarRef tex_reg, const uint8_t* sps_pps, size_t sps_pps_len) : tex_reg_(tex_reg) { Init(sps_pps, sps_pps_len); } VideoDecoder::~VideoDecoder() { if (texture_id_ >= 0) { HANDLE done = CreateEvent(nullptr, TRUE, FALSE, nullptr); FlutterDesktopTextureRegistrarUnregisterExternalTexture( tex_reg_, texture_id_, [](void* ud) { SetEvent(static_cast(ud)); }, done); WaitForSingleObject(done, 3000); CloseHandle(done); } if (decoder_) decoder_->ProcessMessage(MFT_MESSAGE_NOTIFY_END_OF_STREAM, 0); } bool VideoDecoder::Init(const uint8_t* sps_pps, size_t sps_pps_len) { int w = 0, h = 0; if (!ParseSpsSize(sps_pps, sps_pps_len, w, h)) { w = GetSystemMetrics(SM_CXSCREEN); h = GetSystemMetrics(SM_CYSCREEN); } width_ = w; height_ = h; MFT_REGISTER_TYPE_INFO in_info{MFMediaType_Video, MFVideoFormat_H264}; UINT32 count = 0; IMFActivate** acts = nullptr; HRESULT hr = MFTEnumEx(MFT_CATEGORY_VIDEO_DECODER, MFT_ENUM_FLAG_SYNCMFT | MFT_ENUM_FLAG_SORTANDFILTER, &in_info, nullptr, &acts, &count); if (FAILED(hr) || count == 0) return false; hr = acts[0]->ActivateObject(IID_PPV_ARGS(&decoder_)); for (UINT32 i = 0; i < count; ++i) acts[i]->Release(); CoTaskMemFree(acts); if (FAILED(hr)) return false; ComPtr in_type; MFCreateMediaType(&in_type); in_type->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video); in_type->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_H264); MFSetAttributeSize(in_type.Get(), MF_MT_FRAME_SIZE, (UINT32)w, (UINT32)h); if (FAILED(decoder_->SetInputType(0, in_type.Get(), 0))) return false; ComPtr out_type; MFCreateMediaType(&out_type); out_type->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video); out_type->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_NV12); MFSetAttributeSize(out_type.Get(), MF_MT_FRAME_SIZE, (UINT32)w, (UINT32)h); if (FAILED(decoder_->SetOutputType(0, out_type.Get(), 0))) return false; decoder_->ProcessMessage(MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, 0); decoder_->ProcessMessage(MFT_MESSAGE_NOTIFY_START_OF_STREAM, 0); // Register Flutter BGRA texture via C API frame_data_.assign(static_cast(w * h * 4), 0); pixel_buf_.buffer = frame_data_.data(); pixel_buf_.width = static_cast(w); pixel_buf_.height = static_cast(h); FlutterDesktopTextureInfo info{}; info.type = kFlutterDesktopPixelBufferTexture; info.pixel_buffer_config.callback = &VideoDecoder::PixelBufferCallback; info.pixel_buffer_config.user_data = this; texture_id_ = FlutterDesktopTextureRegistrarRegisterExternalTexture( tex_reg_, &info); // Prime decoder with SPS+PPS FeedNal(sps_pps, sps_pps_len); return texture_id_ >= 0; } // ─── NV12 → BGRA ────────────────────────────────────────────────────────────── void VideoDecoder::Nv12ToBgra(const uint8_t* nv12, int stride, uint8_t* bgra, int w, int h) { const uint8_t* Y = nv12; const uint8_t* UV = nv12 + stride * h; for (int row = 0; row < h; ++row) { for (int col = 0; col < w; ++col) { int y = Y[row * stride + col]; int uv = (row / 2) * stride + (col & ~1); int u = UV[uv]; int v = UV[uv + 1]; int c = y - 16, d = u - 128, e = v - 128; int r = (298*c + 409*e + 128) >> 8; int g = (298*c - 100*d - 208*e + 128) >> 8; int b = (298*c + 516*d + 128) >> 8; uint8_t* p = bgra + (row * w + col) * 4; p[0] = static_cast(b < 0 ? 0 : b > 255 ? 255 : b); p[1] = static_cast(g < 0 ? 0 : g > 255 ? 255 : g); p[2] = static_cast(r < 0 ? 0 : r > 255 ? 255 : r); p[3] = 255; } } } // ─── FeedNal ────────────────────────────────────────────────────────────────── void VideoDecoder::FeedNal(const uint8_t* nal, size_t len) { std::lock_guard lk(mu_); if (!decoder_ || !len) return; ComPtr in_sample; ComPtr in_buf; MFCreateMemoryBuffer((DWORD)len, &in_buf); { BYTE* ptr = nullptr; in_buf->Lock(&ptr, nullptr, nullptr); memcpy(ptr, nal, len); in_buf->Unlock(); } in_buf->SetCurrentLength((DWORD)len); MFCreateSample(&in_sample); in_sample->SetSampleTime(input_ts_); in_sample->SetSampleDuration(333333); in_sample->AddBuffer(in_buf.Get()); input_ts_ += 333333; if (FAILED(decoder_->ProcessInput(0, in_sample.Get(), 0))) return; MFT_OUTPUT_STREAM_INFO si{}; decoder_->GetOutputStreamInfo(0, &si); while (true) { ComPtr out_sample; ComPtr out_buf; if (!(si.dwFlags & MFT_OUTPUT_STREAM_PROVIDES_SAMPLES)) { DWORD sz = si.cbSize ? si.cbSize : static_cast(width_ * height_ * 3 / 2); MFCreateMemoryBuffer(sz, &out_buf); MFCreateSample(&out_sample); out_sample->AddBuffer(out_buf.Get()); } MFT_OUTPUT_DATA_BUFFER out_data{}; out_data.pSample = out_sample.Get(); DWORD status = 0; HRESULT hr = decoder_->ProcessOutput(0, 1, &out_data, &status); if (out_data.pEvents) out_data.pEvents->Release(); if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) break; if (FAILED(hr)) break; ComPtr result(out_data.pSample); if (!result) break; ComPtr flat; result->ConvertToContiguousBuffer(&flat); BYTE* nv12_ptr = nullptr; DWORD nv12_len = 0; flat->Lock(&nv12_ptr, nullptr, &nv12_len); Nv12ToBgra(nv12_ptr, width_, frame_data_.data(), width_, height_); flat->Unlock(); FlutterDesktopTextureRegistrarMarkExternalTextureFrameAvailable( tex_reg_, texture_id_); } }