Supporting DXVA 2.0 in DirectShow


This thread is direct forward from Microsoft MSDN website: http://technet.microsoft.com/zh-cn/aa965245

Easlier in this month, I was researching hardware video encoding/decoding supports for Linux base environments, which involves Intel Media SDK & VA-API(libva).

Happen to see this DXVA related post in MSDN, so I decided to copy it to my blog.

This topic describes how to support DirectX Video Acceleration (DXVA) 2.0 in a DirectShow decoder filter. Specifically, it describes the communication between the decoder and the video renderer. This topic does not describe how to implement DXVA decoding.

Prerequisites

This topic assumes that you are familiar with writing DirectShow filters. For more information, see the topic Writing DirectShow Filters in the DirectShow SDK documentation. The code examples in this topic assume that the decoder filter is derived from the CTransformFilter class, with the following class definition:

In the remainder of this topic, the term decoder refers to the decoder filter, which receives compressed video and outputs uncompressed video. The term decoder device refers to a hardware video accelerator implemented by the graphics driver.

Here are the basic steps that a decoder filter must perform to support DXVA 2.0:

  1. Negotiate a media type.
  2. Find a DXVA decoder configuration.
  3. Notify the video renderer that the decoder is using DXVA decoding.
  4. Provide a custom allocator that allocates Direct3D surfaces.

These steps are described in more detail in the remainder of this topic.

Migration Notes

If you are migrating from DXVA 1.0, you should be aware of some significant differences between the two versions:

  • DXVA 2.0 does not use the IAMVideoAccelerator and IAMVideoAcceleratorNotify interfaces, because the decoder can access the DXVA 2.0 APIs directly through the IDirectXVideoDecoder interface.
  • During media type negotiation, the decoder does not use a video acceleration GUID as the subtype. Instead, the subtype is just the uncompressed video format (such as NV12), as with software decoding.
  • The procedure for configuring the accelerator has changed. In DXVA 1.0, the decoder calls Execute with a DXVA_ConfigPictureDecode structure to configure the accerlator. In DXVA 2.0, the decoder uses the IDirectXVideoDecoderService interface, as described in the next section.
  • The decoder allocates the uncompressed buffers. The video renderer no longer allocates them.
  • Instead of calling IAMVideoAccelerator::DisplayFrame to display the decoded frame, the decoder delivers the frame to the renderer by calling IMemInputPin::Receive, as with software decoding.
  • The decoder is no longer responsible for checking when data buffers are safe for updates. Therefore DXVA 2.0 does not have any method equivalent to IAMVideoAccelerator::QueryRenderStatus.
  • Subpicture blending is done by the video renderer, using the DXVA2.0 video processor APIs. Decoders that provide subpictures (for example, DVD decoders) should send subpicture data on a separate output pin.

For decoding operations, DXVA 2.0 uses the same data structures as DXVA 1.0.

The enhanced video renderer (EVR) filter supports DXVA 2.0. The Video Mixing Renderer filters (VMR-7 and VMR-9) support DXVA 1.0 only.

Finding a Decoder Configuration

After the decoder negotiates the output media type, it must find a compatible configuration for the DXVA decoder device. You can perform this step inside the output pin’s CBaseOutputPin::CompleteConnect method. This step ensures that the graphics driver supports the capabilities needed by the decoder, before the decoder commits to using DXVA.

To find a configuration for the decoder device, do the following:

  1. Query the renderer’s input pin for the IMFGetService interface.
  2. Call IMFGetService::GetService to get a pointer to the IDirect3DDeviceManager9 interface. The service GUID is MR_VIDEO_ACCELERATION_SERVICE.
  3. Call IDirect3DDeviceManager9::OpenDeviceHandle to get a handle to the renderer’s Direct3D device.
  4. Call IDirect3DDeviceManager9::GetVideoService and pass in the device handle. This method returns a pointer to the IDirectXVideoDecoderService interface.
  5. Call IDirectXVideoDecoderService::GetDecoderDeviceGuids. This method returns an array of decoder device GUIDs.
  6. Loop through the array of decoder GUIDs to find the ones that the decoder filter supports. For example, for an MPEG-2 decoder, you would look for DXVA2_ModeMPEG2_MOCOMP, DXVA2_ModeMPEG2_IDCT, or DXVA2_ModeMPEG2_VLD.
  7. When you find a candidate decoder device GUID, pass the GUID to the IDirectXVideoDecoderService::GetDecoderRenderTargets method. This method returns an array of render target formats, specified as D3DFORMAT values.
  8. Loop through the render target formats and look for one that matches your output format. Typically, a decoder device supports a single render target format. The decoder filter should connect to the renderer using this subtype. In the first call to CompleteConnect, the decoder can determing the render target format and then return this format as a preferred output type.
  9. Call IDirectXVideoDecoderService::GetDecoderConfigurations. Pass in the same decoder device GUID, along with a DXVA2_VideoDesc structure that describes the proposed format. The method returns an array of DXVA2_ConfigPictureDecode structures. Each structure describes one possible configuration for the decoder device.
  10. Assuming that the previous steps are successful, store the Direct3D device handle, the decoder device GUID, and the configuration structure. The filter will use this information to create the decoder device.

The following code shows how to find a decoder configuration.

Because this example is generic, some of the logic has been placed in helper functions that would need to be implemented by the decoder. The following code shows the declarations for these functions:

Notifying the Video Renderer

If the decoder finds a decoder configuration, the next step is to notify the video renderer that the decoder will use hardware acceleration. You can perform this step inside the CompleteConnect method. This step must occur before the allocator is selected, because it affects how the allocator is selected.

  1. Query the renderer’s input pin for the IMFGetService interface.
  2. Call IMFGetService::GetService to get a pointer to the IDirectXVideoMemoryConfiguration interface. The service GUID is MR_VIDEO_ACCELERATION_SERVICE.
  3. Call IDirectXVideoMemoryConfiguration::GetAvailableSurfaceTypeByIndex in a loop, incrementing the dwTypeIndex variable from zero. Stop when the method returns the value DXVA2_SurfaceType_DecoderRenderTarget in the pdwType parameter. This step ensures that the video renderer supports hardware-accelerated decoding. This step will always succeed for the EVR filter.
  4. If the previous step succeeded, call IDirectXVideoMemoryConfiguration::SetSurfaceType with the value DXVA2_SurfaceType_DecoderRenderTarget. Calling SetSurfaceType with this value puts the video renderer into DXVA mode. When the video renderer is in this mode, the decoder must provide its own allocator.

The following code shows how to notify the video renderer.

If the decoder finds a valid configuration and successfully notifies the video renderer, the decoder can use DXVA for decoding. The decoder must implement a custom allocator for its output pin, as described in the next section.

Allocating Uncompressed Buffers

In DXVA 2.0, the decoder is responsible for allocating Direct3D surfaces to use as uncompressed video buffers. Therefore, the decoder must implement a custom allocator that will create the surfaces. The media samples provided by this allocator will hold pointers to the Direct3D surfaces. The EVR retrieves a pointer to the surface by calling IMFGetService::GetService on the media sample. The service identifier is MR_BUFFER_SERVICE.

To provide the custom allocator, perform the following steps:

  1. Define a class for the media samples. This class can derive from the CMediaSample class. Inside this class, do the following:
    • Store a pointer to the Direct3D surface.
    • Implement the IMFGetService interface. In the GetService method, if the service GUID is MR_BUFFER_SERVICE, query the Direct3D surface for the requested interface. Otherwise, GetService can return MF_E_UNSUPPORTED_SERVICE.
    • Override the CMediaSample::GetPointer method to return E_NOTIMPL.
  2. Define a class for the allocator. The allocator can derive from the CBaseAllocator class. Inside this class, do the following.
  3. In your filter’s output pin, override the CBaseOutputPin::InitAllocator method. Inside this method, create an instance of your custom allocator.
  4. In your filter, implement the CTransformFilter::DecideBufferSize method. The pProperties parameter indicates the number of surfaces that the EVR requires. Add to this value the number of surfaces that your decoder requires, and call IMemAllocator::SetProperties on the allocator.

The following code shows how to implement the media sample class:

The following code shows how to implement the Alloc method on the allocator.

Here is the code for the Free method:

For more information about implementing custom allocators, see the topic Providing a Custom Allocator in the DirectShow SDK documentation.

Decoding

To create the decoder device, call IDirectXVideoDecoderService::CreateVideoDecoder. The method returns a pointer to the IDirectXVideoDecoder interface of the decoder device.

On each frame, call IDirect3DDeviceManager9::TestDevice to test the device handle. If the device has changed, the method returns DXVA2_E_NEW_VIDEO_DEVICE. If this occurs, do the following:

  1. Close the device handle by calling IDirect3DDeviceManager9::CloseDeviceHandle.
  2. Release the IDirectXVideoDecoderService and IDirectXVideoDecoder pointers.
  3. Open a new device handle.
  4. Negotiate a new decoder configuration, as described in the section Finding a Decoder Configuration.
  5. Create a new decoder device.

Assuming that the device handle is valid, the decoding process works as follows:

  1. Call IDirectXVideoDecoder::BeginFrame.
  2. Do the following one or more times:
    1. Call IDirectXVideoDecoder::GetBuffer to get a DXVA decoder buffer.
    2. Fill the buffer.
    3. Call IDirectXVideoDecoder::ReleaseBuffer.
  3. Call IDirectXVideoDecoder::Execute to perform the decoding operations on the frame.

DXVA 2.0 uses the same data structures as DXVA 1.0 for decoding operations. For the original set of DXVA profiles (for H.261, H.263, and MPEG-2), these data structures are described in the DXVA 1.0 specification.

Within each pair of BeginFrame/ Execute calls, you may call GetBuffer multiple times, but only once for each type of DXVA buffer. If you call it twice with the same buffer type, you will overwrite the data.

After calling Execute, call IMemInputPin::Receive to deliver the frame to the video renderer, as with software decoding. The Receive method is asynchronous; after it returns, the decoder can continue decoding the next frame. The display driver prevents any decoding commands from overwriting the buffer while the buffer is in use. The decoder should not reuse a surface to decode another frame until the renderer has released the sample. When the renderer releases the sample, the allocator puts the sample back into its pool of available samples. To get the next available sample, call CBaseOutputPin::GetDeliveryBuffer, which in turn calls IMemAllocator::GetBuffer. For more information, see the topic Overview of Data Flow in DirectShow in the DirectShow documentation.

Related topics

DirectX Video Acceleration 2.0

 

Leave a comment

Your email address will not be published. Required fields are marked *