目录
一、DXVA2 对象的真实层级
二、正确创建顺序(FFmpeg DXVA2 官方同款)
1️. D3D9
2️. D3D9 Device
3️. Device Manager
4️. Video Decoder Service(从 devmgr 拿)
5️. Decode Surfaces
6️. Video Decoder
三、正确释放顺序
铁律顺序
代码版
四、为什么“先 Release devmgr”会出事?
五、FFmpeg 不会替你 Release 的东西
六、D3DXFont / SwapChain 插在哪?
七、RAII 是终局方案
八、退出干净的标志
九、总结
DXVA2 资源必须遵循"底层设备→服务接口→表面池→解码/处理实例"的创建顺序,释放时严格逆序执行,且设备重置前需先销毁所有依赖该设备的 DXVA2 资源 。
如果你在 Windows 上写过 FFmpeg + DXVA2 解码器,大概率经历过这种退出现场:
IDirectXVideoDecoderService ref: 1
IDirect3DDevice9 ref: 12
d3d9.dll 崩溃
不是泄漏,不是驱动坏,是创建顺序和释放顺序写反了。
DXVA2 不是“创建完就能用、用完随便 Release”的 COM 集合,它有一条隐式生命链。这篇把它焊死。
一、DXVA2 对象的真实层级
不是引用计数层级,是逻辑托管层级:
IDirect3D9
└─ IDirect3DDevice9
└─ IDirect3DDeviceManager9
└─ IDirectXVideoDecoderService
└─ IDirectXVideoDecoder
└─ IDirect3DSurface9 (Decode Surfaces)
规则只有一条:
下层不死,上层不 Release;
创建自下而上,释放自上而下。
二、正确创建顺序(FFmpeg DXVA2 官方同款)
1️. D3D9
IDirect3D9* d3d9 = Direct3DCreate9(D3D_SDK_VERSION);
2️. D3D9 Device
d3d9->CreateDevice(
D3DADAPTER_DEFAULT,
D3DDEVTYPE_HAL,
hwnd,
D3DCREATE_HARDWARE_VERTEXPROCESSING | D3DCREATE_MULTITHREADED,
&d3dpp,
&device
);
必须 D3DCREATE_MULTITHREADED,否则 FFmpeg 解码线程会炸。
3️. Device Manager
IDirect3DDeviceManager9* devmgr = nullptr;
DXVA2CreateDirect3DDeviceManager9(&resetToken, &devmgr);
devmgr->ResetDevice(device, resetToken);
4️. Video Decoder Service(从 devmgr 拿)
IDirectXVideoDecoderService* dec_svc = nullptr;
devmgr->GetVideoService(device, IID_IDirectXVideoDecoderService, (void**)&dec_svc);
📌这一步 AddRef 了 service,你必须 Release
5️. Decode Surfaces
dec_svc->CreateSurface(…); // × N (NV12 / D3DFMT_NV12)
6️. Video Decoder
dec_svc->CreateVideoDecoder(…, &decoder);
FFmpeg 的 hwcontext_dxva2.c到这里才接管。
三、正确释放顺序
铁律顺序
IDirectXVideoDecoder
→ IDirect3DSurface9[]
→ IDirectXVideoDecoderService
→ IDirect3DDeviceManager9
→ IDirect3DDevice9
→ IDirect3D9
代码版
if (decoder) {
decoder->Release();
decoder = nullptr;
}
for (auto s : surfaces) {
if (s) s->Release();
}
surfaces.clear();
if (dec_svc) { // ★ 必须在 devmgr 之前
dec_svc->Release();
dec_svc = nullptr;
}
if (devmgr) {
devmgr->Release();
devmgr = nullptr;
}
if (swapchain) {
swapchain->Release();
swapchain = nullptr;
}
if (device) {
device->Release();
device = nullptr;
}
if (d3d9) {
d3d9->Release();
d3d9 = nullptr;
}
四、为什么“先 Release devmgr”会出事?
因为:
-
IDirectXVideoDecoderService是 devmgr 的逻辑子对象
-
但 COM 不保证它对 devmgr 有强引用
-
你先 Release devmgr → devmgr ref 0 → 内部状态销毁
-
service 还活着 → 内部指针悬空
-
再 Release service → UB / 驱动层抱怨
日志表现就是:
IDirect3DDeviceManager9 ref: 0
IDirectXVideoDecoderService ref: 1 ← 孤儿
IDirect3DDevice9 ref: 12
顺序反了,不是漏 Release
五、FFmpeg 不会替你 Release 的东西
FFmpeg hwcontext_dxva2.c内部会:
-
Release IDirectXVideoDecoder
-
Release surfaces(通过 hwframe ctx)
-
用完临时 service 会 Release
但不会:
-
Release 你保存在 ctx->decoder_service的那个指针
-
Release 你的 devmgr / device / d3d9
这些是你的责任
六、D3DXFont / SwapChain 插在哪?
它们不是 DXVA 对象,但持有 Device:
Device
├─ SwapChain
├─ ID3DXFont
└─ DeviceManager → DecoderService → Decoder/Surfaces
释放顺序补成:
Decoder
Surfaces
DecoderService
D3DXFont ← 必须在 Device 前
SwapChain
DeviceManager
Device
D3D9
任何“持有 Device 的东西”,都比 Device 先死
七、RAII 是终局方案
struct ComDeleter {
template<typename T>
void operator()(T* p) const { if (p) p->Release(); }
};
using D3D9Ptr = std::unique_ptr<IDirect3D9, ComDeleter>;
using DevPtr = std::unique_ptr<IDirect3DDevice9, ComDeleter>;
using MgrPtr = std::unique_ptr<IDirect3DDeviceManager9, ComDeleter>;
using SvcPtr = std::unique_ptr<IDirectXVideoDecoderService, ComDeleter>;
using DecPtr = std::unique_ptr<IDirectXVideoDecoder, ComDeleter>;
析构顺序由成员声明顺序决定,天然不会反。
八、退出干净的标志
程序退出前最后打印:
IDirectXVideoDecoder ref: 0
IDirectXVideoDecoderService ref: 0
IDirect3DDeviceManager9 ref: 0
IDirect3DDevice9 ref: 0
IDirect3D9 ref: 0
出现这组 → DXVA2 生命周期正确
九、总结
DXVA2 的创建是“D3D9 → Device → DevMgr → Service → Decoder → Surfaces”,释放必须完全镜像;
IDirectXVideoDecoderService永远在 IDirect3DDeviceManager9之前 Release,这是 Windows 视频播放器能干净退出的唯一真理。

