同步 DVD 命令

[与此页面关联的功能 DirectShow 是一项旧功能。 它已被 MediaPlayerIMFMediaEngine媒体基金会中的音频/视频捕获取代。 这些功能已针对Windows 10和Windows 11进行了优化。 Microsoft 强烈建议新代码尽可能使用 MediaPlayerIMFMediaEngineMedia Foundation 中的音频/视频捕获 ,而不是 DirectShow。 如果可能,Microsoft 建议重写使用旧 API 的现有代码以使用新 API。]

DVD 命令并不总是立即完成。 因此, IDvdControl2 中的某些方法是异步的。 其中包括播放方法(如 PlayTitle)和菜单导航方法(如 ShowMenuReturnFromSubmenu)。 异步方法会立即返回,而无需等待命令完成。 方法返回后,其他事件可能会阻止命令完成,即使该方法成功也是如此。 DirectShow 提供了多个用于同步命令的选项,从不同步到使用筛选器图事件完全同步。

所有异步方法都有一个 dwFlags 参数和一个 ppCmd 参数。 dwFlags 参数指定同步行为,ppCmd 参数返回指向可选同步对象的指针。 根据为这些参数提供的值,会导致不同的行为。

无同步

对于基本 DVD 播放应用程序,最佳选择可能只是忽略同步问题。 有时,命令可能会失败,或者 UI 在更新时可能会稍有滞后,但这些错误将以秒为单位。

若要发出不同步的命令,请在 dwFlags 参数中设置 DVD_CMD_FLAG_None 标志,并将 ppCmd 参数设置为 NULL

hr = pDVDControl2->PlayTitle(uTitle, DVD_CMD_FLAG_None, NULL);

阻塞

如果在 dwFlags 参数中设置 EC_DVD_CMD_FLAG_Block 标志, 方法将阻止,直到命令完成:

hr = pDVDControl2->PlayTitle(uTitle, EC_DVD_CMD_FLAG_Block, NULL);

实际上,此标志会将异步方法转换为同步方法。 缺点是,如果你从应用程序线程调用 方法,则 UI 会阻塞。

同步对象

所有异步方法都可以返回同步对象,可以使用该对象等待命令开始或结束。 若要获取此对象,请在 ppCmd 参数中传递 IDvdCmd 指针的地址:

IDvdCmd *pCmdObj = NULL;
hr = pDVDControl2->PlayTitle(uTitle, DVD_CMD_FLAG_None, &pCmdObj);

如果该方法成功,它将返回新的 IDvdCmd 对象。 IDvdCmd::WaitForStart 方法在命令开始之前会阻止,IDvdCmd::WaitForEnd 方法将阻止,直到命令结束。 返回值指示命令的状态。

以下代码在功能上等效于设置前面所示的 EC_DVD_CMD_FLAG_Block 标志。

IDvdCmd *pCmdObj = NULL;
hr = pDVDControl2->PlayTitle(uTitle, DVD_CMD_FLAG_None, &pCmdObj);
if (SUCCEEDED(hr))
{
    // Use pCmdObj to wait for the command to complete.
    hr = pCmdObj->WaitToEnd();
    pCmdObj->Release();
}

在这种情况下, PlayTitle 方法不会阻止,但应用程序会通过调用 WaitForEnd 阻止

命令状态事件

如果在 dwFlags 参数中设置DVD_CMD_FLAG_SendEvents标志,DVD 导航器将在命令开始时发送 EC_DVD_CMD_START 事件,并在命令结束时发送 EC_DVD_CMD_END 事件。

事件的 lParam2 参数是命令的 HRESULT 返回值。 事件的 lParam1 参数提供了一种方法来获取命令的同步对象。 如果将 lParam1 传递给 IDvdInfo2::GetCmdFromEvent 方法,该方法将返回指向同步对象的 IDvdCmd 接口的指针。 可以使用此接口等待命令完成,如前所述。 但是,如果在原始 IDvdControl2 方法中为 ppCmd 参数传递了 NULL,则 DVD 导航器不会创建同步对象,并且 GetCmdFromEvent 将返回E_FAIL。

以下代码演示如何使用没有同步对象的命令状态事件。

hr = pDVDControl2->PlayTitle(uTitle, DVD_CMD_FLAG_SendEvents, NULL);

// In your event handling code:
switch (lEvent)
{
   case EC_DVD_CMD_END:
       HRESULT hr2 = (HRESULT)lParam2;
       /* ... */ 
       break;
}

请注意,如果没有同步对象,则无法判断哪个命令与事件相关联。 以下代码演示如何将事件与同步对象一起使用。 其思路是将同步对象存储在列表中,然后在获取 EC_DVD_CMD_STARTEC_DVD_CMD_END 事件时比较对象指针。

IDvdCmd *pCmdObj = NULL;
hr = pDVDControl2->PlayTitle(uTitle, DVD_CMD_FLAG_SendEvents, &pCmdObj);
if (SUCCEEDED(hr)) 
{
    // Store pCmdObj in a list of pending commands.
}

// In your event handling code:
switch (lEvent)
{
case EC_DVD_CMD_END:
   {
       IDvdCmd *pObj = NULL;
       hr = pDvdInfo2->GetCmdFromEvent(lParam, &pObj);
       if (SUCCEEDED(hr)) 
       {
           // Find this object in your list by comparing IUnknown
           // pointers. Assume the following function is defined in 
           // your application:
           IDvdCmd *pPendingObj = GetPendingCommandFromList(pObj); 
           if (pPendingObj)
           {
               // Update UI accordingly (not shown). 
               pPendingObj->Release();
           }
           pObj->Release();
       }
    }
    break;
} 

刷新 DVD 导航器的缓冲区

播放期间,DVD 导航器会缓冲视频数据。 缓冲的数据量各不相同。 当 DVD 导航器切换到新的视频片段时,管道中已有的数据不会丢失,因此过渡是无缝的。 默认情况下,当 DVD 导航器发出命令时,它不会刷新管道中已有的数据。 因此,可能需要一些延迟才能看到命令的效果,具体取决于缓冲的数据量。 若要提高响应能力,可以通过设置DVD_CMD_FLAG_Flush标志来强制 DVD 导航器刷新。

hr = pDVDControl2->PlayTitle(uTitle, DVD_CMD_FLAG_Flush, NULL);

可以使用按位 OR 将此标志与上述任何标志组合使用。 刷新的副作用是某些视频可能会丢失,因此,如果需要保证视频中没有间隙,请不要使用此标志。

DVD 应用程序