我试图了解 IDirectXVideoDecoder 的一些细微差别。警告:以下结论并非基于 DirectX 文档或任何其他官方来源,而是我自己的观察和理解。那就是说...
在正常使用中,IDirectXVideoDecoder 很容易以任何合理的帧速率处理帧。但是,如果您不基于时间码渲染帧,而是“尽可能快地”进行渲染,那么您最终会在解码器中遇到瓶颈,并且 IDirectXVideoDecoder::BeginFrame 开始返回 E_PENDING。
显然,在任何给定时间,系统只能在解码器中激活 X 帧。尝试提交 X + 1 会出现此错误,直到前一帧完成。在我的(有点旧的)盒子上,X == 4。在我的新盒子上,X == 8。
这让我们想到了我的第一个问题:
Q1:我如何知道一个系统支持多少同时解码操作?什么属性/属性描述了这一点?
然后是遇到此错误时该怎么办的问题。我可以想到 3 种不同的方法,但它们都有缺点:
1)只需循环等待解码器释放:
do {
hr = m_pVideoDecoder->BeginFrame(pSurface9Video[y], NULL);
} while(hr == E_PENDING);
从好的方面来说,这种方法提供了最快的吞吐量。不利的一面是,这会导致大量 CPU 时间被烧毁,等待解码器释放(>93% 的执行时间都花在了这里)。
2)做一个循环,并添加一个睡眠:
do {
hr = m_pVideoDecoder->BeginFrame(pSurface9Video[y], NULL);
if (hr == E_PENDING)
Sleep(1);
} while(hr == E_PENDING);
从好的方面来说,这会显着降低 CPU 利用率。但不利的一面是,它最终会减慢总吞吐量。
在试图弄清楚为什么它会减慢速度时,我做了一些观察:
- 在我的系统上处理帧的正常时间约为 4 毫秒。
- Sleep(1) 最多可以休眠 8 毫秒,即使有 CPU 可供运行。
- 发送到解码器的帧不会被添加到队列中并一次解码一个。它实际上同时执行 X 个解码。
所有这一切的结果是,如果您尝试休眠,其中一个解码器经常会闲置。
3) 在提交下一帧进行解码之前,等待前一帧完成:
// LockRect doesn't return until the surface is ready.
D3DLOCKED_RECT lr;
// I don't think this matters. It may always return the whole frame.
RECT r = {0, 0, 2, 2};
hr = pSurface9Video[old]->LockRect(&lr, &r, D3DLOCK_READONLY);
if (SUCCEEDED(hr))
pSurface9Video[old]->UnlockRect();
这也会降低 CPU 使用率,但也会降低吞吐量。可能是因为“表面”的使用时间比“解码器”长,但更有可能是因为将帧(毫无意义地)传输回内存所需的时间。
这就引出了第二个问题:
Q2:这里有什么方法可以最大化吞吐量而不会对 CPU 造成毫无意义的冲击吗?
最后的想法:
- 看来 LockRect 必须在做一个 WaitForSingleObject。如果我可以访问该句柄,那么等待它(同时不复制框架)似乎是最好的解决方案。但我不知道从哪里得到它。我已经尝试过 GetDC、GetPrivateData,甚至查看了 IDirect3DSurface9 的调试数据成员。我没有找到它。
- IDirectXVideoDecoder::EndFrame在名为 的参数中输出句柄
pHandleComplete
。这听起来正是我需要的。不幸的是,它被标记为“保留”并且似乎不起作用。除非有绝招? - 我对 DirectX 很陌生,所以也许我错了?
更新1:
关于 Q1:原来我的两台机器都只支持 4 个解码器(哎呀)。这将使我更难确定我正在寻找的属性。虽然很少有属性(实际上没有)在一台机器上返回 8 而在另一台机器上返回 4,但有几个返回 4。
Re Q2:由于(4)解码器(大概)在应用程序之间共享,因此通过(以某种方式)查询解码器是否空闲来确定解码是否完成的想法是不可行的。
创建表面的调用不会创建句柄(句柄计数在整个调用过程中保持不变)。因此,等待“表面处理”的想法似乎也不会成功。
我剩下的唯一想法是通过使用它进行一些其他调用(除了 LockRect)来查看表面是否可用。到目前为止,我已经尝试在解码器“仍在使用”的表面上调用 StretchRect 和 ColorFill,但它们没有错误地完成,而不是像 LockRect 那样阻塞。
这里可能没有更好的答案。到目前为止,为了获得最佳性能,我应该使用#1。如果 CPU 利用率是一个问题,那么 #2 比 #1 好。如果我无论如何都要将表面读回内存,那么#3是有道理的,否则,坚持使用1或2。