DXVA 处理交错视频

最近在项目中遇到了交错视频,视频显示的时候会出现横纹的现象。如下图:

如果对交错视频的知识不是很了解,可以参考下面两篇博文:

交错、反交错与IVTC —— 从入门到放弃

去交错原理介绍

为了让交错视频正常显示,可以通过ffmpeg的滤镜,dxva 等技术来解决。由于我们的项目对性能要求比较高,所以我这里是通过dxva来处理的(dxva是调用GPU处理,ffmpeg的滤镜是通过CPU来处理)。

由于国内关于dxva的知识讲解的特别少,为了解决这个问题,花费了不少时间来研究英文文档,为了能让dxva的新手少走弯路,这里总结一下这段时间对dxva的研究,希望能对大家有所帮助。

标题:DXVA视频处理器(DXVA Video Processing)

DXVA视频处理器封装了图像硬件的一些功能集,目的是为了对解码出来的视频画面做后处理,比如对视频画面做反交错和视频融合处理。

这篇文章包含下面各节:

1.概览

2.创建视频处理器对象

  • 获取 IDirectXVideoProcessorService 对象指针
  • 枚举 视频处理器对象
  • 枚举 渲染目标格式对象
  • 查询 视频处理器设备能力集
  • 创建视频处理器对象

3.处理视频画面(video Process Blit)

  • Blit 变量
  • 输入源(Input Samples)

4.图像融合(Image Composition)

  • 例1:黑边处理
  • 例2:拉伸子流视频画面
  • 例3 :不匹配的流高度
  • 例4:目标矩形小于目标surface
  • 例5:源矩形
  • 例6: 相交的目标矩形
  • 例7: 拉伸和裁剪视频

5.输入源序列

下文为对上面各节的详细介绍:

1.概览:

图形硬件能使用GPU处理解码完成的视频画面。一个视频处理器(video processing device)是一个封装了一定功能的软件组件,应用程序能是用它处理功能如下:

  • 反交错(Deinterlacing)和 Inverse Telecine
  • 将视频子流图像合并到视频主流图像上面
  • 颜色调整和图像过滤
  • 图像缩放
  • 颜色空间转换
  • 透明通道融合

下面的图表展示了视频处理管线( video processing pipeline)的各个阶段。这个图表展示的并不是真正的代码实现。例如硬件驱动可能会合并其中的几个阶段到一个操作里面。所有的这些操作能合并到一个单个的操作里面。这里显示的一些操作可能硬件驱动不支持,例如noise和detail过滤器。

视频处理管线总是包含一个主视频流( a primary video stream),它包含主要的视频图像数据。主视频流决定了输出视频的帧率,它总是不透明的,每一个像素都没有透明通道数据。主视频流有可能是逐行扫描视频,也可能是交错视频。

视频处理管线可以最多接收15个视频子数据流。一个子数据流包含一个辅助的图像数据,例如闭合字幕和DVD字幕。这些辅助图像一般覆盖到主视频流上面,和主视频流做融合处理。子视频流图片一般包含透明通道数据并且总是逐行扫描帧。视频处理器(video processing device)从主视频流里面取出反交错的视频帧,然后将子视频流通过透明通道融合(alpha-blends)进主视频流的帧里面。

在以后各节,术语“picture”被认为是输入到视频处理器的输入数据。一个picture可以由一个逐行帧,单场,或者两个隔行的场组成。输出图片总是反交错的帧。

一个硬件驱动能实现多个视频处理器(video processing device),每个视频处理器能提供不同的功能。每个视频处理器通过唯一GUID来标识。下面的GUID被预先定义好了:

  • DXVA2_VideoProcBobDevice. 这个视频处理器执行 bob 反交错(bob deinterlacing)。
  • DXVA2_VideoProcProgressiveDevice。如果视频只包含逐行的帧没有交错帧,这个视频处理器可以被使用

每个支持DXVA的硬件驱动至少要实现这两个视频处理器。硬件驱动也能支持其他的视频处理器,并且指定它们的GUID。例如,硬件驱动可能会实现一些专有的反交错算法,去获取比bob反交错更好的图像质量。一些反交错算法可能需要从主视频流里面获取前后的参考帧。像这样,调用者必须提供正确的图片序列给硬件驱动,稍后我们将更详细的描述。

参考软件处理器(reference software device)也被提供。软件处理器优化了质量处理,但是速度很慢,因此不能使用在实时视频处理里面。参考软件处理器的GUIDDXVA2_VideoProcSoftwareDevice

2.创建视频处理器

在用视频处理器处理视频之前,应用程序必须先创建一个视频处理器对象。这里是视频处理步骤的简要概述,后面各节将会有更详细的描述。

1.获取IDirectXVideoProcessorService接口

2.创建主视频流的视频格式描述。使用这个描述去获取到支持这个视频格式的视频处理器列表。视频处理器用GUID来定义。

3.拿到一个视频处理器以后,获取它的渲染目标格式列表(render-target formats)。这个格式用类型 D3DFORMAT 标识。如果你计划去融合子数据流,你也可以获取支持子流格式的列表。

4.查询对应视频处理器支持的能力集。

5.创建视频处理器对象。

有时你可以省略某些步骤。例如,你能用预先指定的渲染目标格式创建视频处理器对象,看它是否成功。这样就能省掉获取渲染目标格式的过程。一个公用的格式 D3DFMT_X8R8G8B8 通常都能成功。

下面各节将详细描述各个步骤。

获取IDirectXVideoProcessorService接口

IDirectXVideoProcessorService接口从Direct3D device对象中获取。有两种方式去获取这个接口。

如果你有一个Direct3D device对象指针,你能通过调用DXVA2CreateVideoService函数获取一个IDirectXVideoProcessorService接口指针。次函数声明如下:

HRESULT DXVA2CreateVideoService( IDirect3DDevice9 *pDD, REFIID riid, void **ppService );

调用代码如下:

// Create the DXVA-2 Video Processor service.
    hr = DXVA2CreateVideoService(g_pD3DD9, IID_PPV_ARGS(&g_pDXVAVPS));

有时一个对象创建了Direct3D device对象,然后其他对象能通过Direct3D Device Manager对象共享这个Direct3D device对象。

这个时候你能通过调用Direct3D Device Manager对象的IDirect3DDeviceManager9::GetVideoService 函数获取 IDirectXVideoProcessorService接口指针。代码如下:

HRESULT GetVideoProcessorService(
    IDirect3DDeviceManager9 *pDeviceManager,
    IDirectXVideoProcessorService **ppVPService
    )
{
    *ppVPService = NULL;

    HANDLE hDevice;

    HRESULT hr = pDeviceManager->OpenDeviceHandle(&hDevice);
    if (SUCCEEDED(hr))
    {
        // Get the video processor service 
        HRESULT hr2 = pDeviceManager->GetVideoService(
            hDevice, 
            IID_PPV_ARGS(ppVPService)
            );

        // Close the device handle.
        hr = pDeviceManager->CloseDeviceHandle(hDevice);

        if (FAILED(hr2))
        {
            hr = hr2;
        }
    }

    if (FAILED(hr))
    {
        SafeRelease(ppVPService);
    }

    return hr;
}

枚举视频处理器对象

用主视频流的格式信息填充DXVA2_VideoDesc 结构体,然后将此结构体对象传递给 IDirectXVideoProcessorService::GetVideoProcessorDeviceGuids 函数去获取到视频处理器对象列表。这个函数返回一个GUID数组。其中每一个GUID所代表的视频处理器对象都能处理这个主视频流。

我们现在假设一个应用程序渲染一个视频流的信息为:YUY2格式,BT.709 YUV 颜色空间,帧率为29.97,视频由逐行帧组成。下面的代码展示了怎样去填充DXVA2_VideoDesc结构体对象并获取GUIDs:

// Initialize the video descriptor.

    g_VideoDesc.SampleWidth                         = VIDEO_MAIN_WIDTH;
    g_VideoDesc.SampleHeight                        = VIDEO_MAIN_HEIGHT;
    g_VideoDesc.SampleFormat.VideoChromaSubsampling = DXVA2_VideoChromaSubsampling_MPEG2;
    g_VideoDesc.SampleFormat.NominalRange           = DXVA2_NominalRange_16_235;
    g_VideoDesc.SampleFormat.VideoTransferMatrix    = EX_COLOR_INFO[g_ExColorInfo][0];
    g_VideoDesc.SampleFormat.VideoLighting          = DXVA2_VideoLighting_dim;
    g_VideoDesc.SampleFormat.VideoPrimaries         = DXVA2_VideoPrimaries_BT709;
    g_VideoDesc.SampleFormat.VideoTransferFunction  = DXVA2_VideoTransFunc_709;
    g_VideoDesc.SampleFormat.SampleFormat           = DXVA2_SampleProgressiveFrame;
    g_VideoDesc.Format                              = VIDEO_MAIN_FORMAT;
    g_VideoDesc.InputSampleFreq.Numerator           = VIDEO_FPS;
    g_VideoDesc.InputSampleFreq.Denominator         = 1;
    g_VideoDesc.OutputFrameFreq.Numerator           = VIDEO_FPS;
    g_VideoDesc.OutputFrameFreq.Denominator         = 1;

    // Query the video processor GUID.

    UINT count;
    GUID* guids = NULL;

    hr = g_pDXVAVPS->GetVideoProcessorDeviceGuids(&g_VideoDesc, &count, &guids);

在这个例子中,GUID 的数组是通过 GetVideoProcessorDeviceGuids 函数获取的,因此应用程序必须调用CoTaskMemFree 函数释放这个数组。接下来的步骤可以使用这个GUID数组中的任何一个。

枚举渲染目标格式(Render-Target Formats)

通过传参数GUID 和 DXVA2_VideoDesc 结构体给IDirectXVideoProcessorService::GetVideoProcessorRenderTargets 函数来获取GUID所标识的视频处理器支持的渲染目标格式(Render-Target Formats)列表。代码如下:

// Query the supported render-target formats.

    UINT i, count;
    D3DFORMAT* formats = NULL;

    HRESULT hr = g_pDXVAVPS->GetVideoProcessorRenderTargets(
        guid, &g_VideoDesc, &count, &formats);

    if (FAILED(hr))
    {
        DBGMSG((L"GetVideoProcessorRenderTargets failed: 0x%x.\n", hr));
        return FALSE;
    }

    for (i = 0; i < count; i++)
    {
        if (formats[i] == VIDEO_RENDER_TARGET_FORMAT)
        {
            break;
        }
    }

    CoTaskMemFree(formats);

    if (i >= count)
    {
        DBGMSG((L"The device does not support the render-target format.\n"));
        return FALSE;
    }

这个方法返回一个 D3DFORMAT 数组。在个例子中,输入类型为YUY2,获取到的输出列表可能是D3DFMT_X8R8G8B8 (32-bit RGB) 和 D3DMFT_YUY2。但是,真实的列表还是依赖于硬件驱动。

子流的渲染目标格式格式列表依赖于主流渲染目标格式和输入格式。要获取一个子流的渲染目标格式列表,可以通过传递GUID, DXVA2_VideoDesc 结构体和主流渲染目标格式给IDirectXVideoProcessorService::GetVideoProcessorSubStreamFormats  函数来获取。代码如下:

// Query the supported substream formats.

    formats = NULL;

    hr = g_pDXVAVPS->GetVideoProcessorSubStreamFormats(
        guid, &g_VideoDesc, VIDEO_RENDER_TARGET_FORMAT, &count, &formats);

    if (FAILED(hr))
    {
        DBGMSG((L"GetVideoProcessorSubStreamFormats failed: 0x%x.\n", hr));
        return FALSE;
    }

    for (i = 0; i < count; i++)
    {
        if (formats[i] == VIDEO_SUB_FORMAT)
        {
            break;
        }
    }

    CoTaskMemFree(formats);

    if (i >= count)
    {
        DBGMSG((L"The device does not support the substream format.\n"));
        return FALSE;
    }

这个函数返回另外一个D3DFORMAT 数组。通常子流的渲染目标格式为AYUV 和 AI44。

查询设备内管理集

通过传递GUID,DXVA2_VideoDesc结构体,渲染目标格式给IDirectXVideoProcessorService::GetVideoProcessorCaps  函数来获取特定设备的能力集。这个方法通过结构体 DXVA2_VideoProcessorCaps 来返回对应设备的能力集。代码如下:

// Query video processor capabilities.

    hr = g_pDXVAVPS->GetVideoProcessorCaps(
        guid, &g_VideoDesc, VIDEO_RENDER_TARGET_FORMAT, &g_VPCaps);

    if (FAILED(hr))
    {
        DBGMSG((L"GetVideoProcessorCaps failed: 0x%x.\n", hr));
        return FALSE;
    }

创建设备

最后通过调用IDirectXVideoProcessorService::CreateVideoProcessor 函数来创建视频处理器。输入参数为GUID,DXVA2_VideoDesc结构体,渲染目标格式、你打算去混合的最大子流数量。这个方法返回一个指向 IDirectXVideoProcessor 接口的对象 。它代表一个视频处理器对象。代码如下:

// Finally create a video processor device.

    hr = g_pDXVAVPS->CreateVideoProcessor(
        guid,
        &g_VideoDesc,
        VIDEO_RENDER_TARGET_FORMAT,
        SUB_STREAM_COUNT,
        &g_pDXVAVPD
        );

3.处理视频画面(video Process Blit)

主要的视频处理操作是video processing blit (一个blit 联合2个或者更多的位图为一个位图),通过调用函数IDirectXVideoProcessor::VideoProcessBlt 来执行一个video processing blit。这个函数传递 video samples 的一个集合给视频处理器对象。视频处理器处理Video Samples里面的位图来得到一个输出帧。处理包括反交错,颜色空间转换,子流混合。输出被绘制到一个目标表面(IDirect3DSurface9 对象,并且目标是一个Render Target对象)。

VideoProcessBlt 函数拥有下面的参数:

  • pRT 是一个指向IDirect3DSurface9 渲染目标对象的指针,处理完的视频画面将绘制到这个目标表面。
  • pBltParams指向一个  DXVA2_VideoProcessBltParams 结构体对象指针。这个结构体的参数控制了blit的一部分行为。
  • pSamples 指向一个 DXVA2_VideoSample 结构体数组的地址。这个结构体数组包含blit行为的 input samples。 
  • NumSamples标识了pSamples指向数组的大小。
  • Reserved 为保留参数,设置为NULL.

在pSamples数组中,调用者必须提供下面  input samples:

  • 主视频流的当前位图。
  • 如果反交错算法需要,需要包含参考前后帧。
  • 最多15路的子流位图。

硬件驱动希望这个数组是指定的序列。具体描述参考后面。

DXVA2_VideoProcessBltParams 参数介绍

重要成员如下所示:

  • TargetFrame 标识输出帧的显示时间。针对逐行帧,这个时间必须和主视频流当前帧的开始时间相等。这个时间包含在 input sample中的DXVA2_VideoSample 结构体的Start 成员变量中。对于交错视频,一帧是由两个交错的场组成,可以生成两个不同的输出帧。第一个输出帧的时间和主视频流中的DXVA2_VideoSample结构体的Start 相等,第二个输出帧的时间是当前帧和下一帧时间的中间值。例如,如果输入视频 25帧每秒(50 场每秒),输出帧将有下面图表中显示的时间戳。这个图表的时间单位为 100 纳秒。

如果交错的视频是由单个的场组成,输出帧的时间总是和输入帧的时间相同。同逐行视频一样。

  • TargetRect 定义了一个目标表面的矩形区域。blit 操作只会到这个矩形区域输出。这个矩形定义了所有输入视频流的绘制边界。每一路流在TargetRect 矩形中的绘制区域在VideoProcessBlt函数的pSamples 变量中定义。
  • BackgroundColor 指定没有视频位图绘制地方的背景色。例如,一个16:9的视频显示在一个4:3的区域里面,会产生一个黑边,这个黑边将用这个颜色绘制。背景色仅仅会在TargetRect 变量指定的区域显示。TargetRect 区域外的像素将不做任何修改。
  • DestFormat 变量将描述输出视频的颜色空间。例如,是否 ITU-R BT.709 或者 BT.601颜色空间被使用。这个参数能影响位图显示的效果。想要知道更多的信息,参考Extended Color Information

其他变量的描述请参考 DXVA2_VideoProcessBltParams 结构体。

Input Samples参数介绍

函数 IDirectXVideoProcessor::VideoProcessBlt 的 pSamples 变量指向 DXVA2_VideoSample结构体数组。每个这样的结构体包含一个 input sample 信息和 一个 IDirect3DSurface 对象。每个Sample 是下面中的一个:

  • 主视频流中的当前位图。
  • 反交错视频的时候,主视频流中的前后参考帧。
  • 一个子流位图。

pSamples 指向的数组里面Sample 的序列稍后将有详细的描述。

虽然大部分时候视频应用程序仅仅需要一个子视频流,但是我们能增加子视频流到15个。每次调用函数VideoProcessBlt 都可以改变子流的数量。通过设置 DXVA2_VideoSample 结构的SampleFormat.SampleFormat 成员变量为DXVA2_SampleSubStream来标识这一个Sample为子流位图。对于主视频流,这个变量标识了交错视频的信息。想要查看更多的知识,请查看DXVA2_SampleFormat 结构体信息。

针对主视频流, DXVA2_VideoSample 结构体的 Start 和End 变量标识了此 input sample的开始和结束时间。针对子视频流,设置这些值为0。因为显示时间总是从主视频流当中获取。应用程序要负责在合适的时间将子视频流的位图提交给 VideoProcessBlt 函数。

下面两个矩形定义了每一路视频流在目标显示区域的呈现方式:

  • DXVA2_VideoSample 结构体的 SrcRect 变量指定了 输入帧的哪一部分要显示出来。可以用这个参数来对输入帧做裁剪。
  • DXVA2_VideoSample 结构体的 DstRect 变量指定了在目标Surface上面的显示区域。

硬件驱动将输入帧中SrcRect 区域指定的像素数据复制到目标Surface的DstRect 区域中。两个区域可能大小和纵横比例不同(驱动会更根据需要缩放对应位图)。此外,每个子数据流可以有不同的缩放因子。一般使用的子流格式为 AYUV 和AI44。

4.图像融合(Image Composition)

每次图像处理操作都包含下面几个矩形:

  • TargetRect 矩形变量标识了目标Surface 显示的区域。输出图像通过这个矩形来裁剪。
  • DstRect 矩形变量标识了子流位图在合成图像中显示的位置。
  • SrcRect 矩形变量标识了子流位图中那一部分要显示出来。

详细描述可以参考下图:

视频处理器做 透明通道融合操作时,使用下面的 alpha 数据:

  • 子流当中每个像素数据的 alpha 数据。
  • 每个子流中一个平面的alpha 数据。  在DXVA2_VideoSample结构体的PlanarAlpha 变量中指定。
  • 合成后的图片与背景色融合的alpha数据。在 DXVA2_VideoProcessBltParams 结构体的Alpha变量中指定。 

这一节将通过一系列的例子展示如何对图像融合。

例1:黑边处理

这个例子展示怎么对输入源图像做黑边处理,通过设置DstRect小于TargetRect可以达到这个效果。在这个例子中,主视频流的输入源大小为720*480,它 的纵横比例为16:9,但是目标Surface为640*480,纵横比为4:3,为了让视频的纵横比保持不变,因此应该将DstRect的大小设置为640*360。为了简单起见,这个例子不包含子数据流,下图展示了输入源和目标Surface大小:

上图显示的矩形大小为:

  • Target rectangle: { 0, 0, 640, 480 }

  • Primary video:

    • Source rectangle: { 0, 0, 720, 480 }
    • Destination rectangle: { 0, 60, 640, 420 }

硬件驱动将对视频进行反交错处理,缩小反交错帧到640 × 360的大小。然后将数据填充到DstRect标识目标区域内。TargetRect比DstRect矩形大,因此驱动将使用指定背景色填充其他区域。背景色在 DXVA2_VideoProcessBltParams 结构体中指定。

例2:拉伸子流视频画面

The preceding diagram shows the following rectangles:

  • Target rectangle: { 0, 0, 854, 480 }

  • Primary video:

    • Source rectangle: { 0, 0, 720, 480 }
    • Destination rectangle: { 0, 107, 474, 480 }
  • Substream:

    • Source rectangle: { 0, 0, 720, 480 }
    • Destination rectangle: { 0, 0, 854, 480 }

例3 :不匹配的流高度

The preceding diagram shows the following rectangles:

  • Target rectangle: { 0, 0, 150, 85 }
  • Primary video:
    • Source rectangle: { 0, 0, 150, 50 }
    • Destination rectangle: { 0, 17, 150, 67 }
  • Substream:
    • Source rectangle: { 0, 0, 100, 85 }
    • Destination rectangle: { 25, 0, 125, 85 }

例4:目标矩形小于目标surface

The preceding diagram shows the following rectangles:

  • Destination surface: { 0, 0, 300, 200 }
  • Target rectangle: { 0, 0, 150, 85 }
  • Primary video:
    • Source rectangle: { 0, 0, 150, 50 }
    • Destination rectangle: { 0, 17, 150, 67 }
  • Substream:
    • Source rectangle: { 0, 0, 100, 85 }
    • Destination rectangle: { 25, 0, 125, 85 }

例5:源矩形

The preceding diagram shows the following rectangles:

  • Target rectangle: { 0, 0, 720, 576 }
  • Primary video:
    • Source surface size: { 0, 0, 720, 480 }
    • Source rectangle: { 360, 240, 720, 480 }
    • Destination rectangle: { 0, 0, 360, 240 }
  • Substream:
    • Source surface size: { 0, 0, 640, 576 }
    • Source rectangle: { 0, 288, 320, 576 }
    • Destination rectangle: { 400, 0, 720, 288 }

例6: 相交的目标矩形

The preceding diagram shows the following rectangles:

  • Target rectangle: { 0, 0, 720, 576 }
  • Primary video:
    • Source surface size: { 0, 0, 720, 480 }
    • Source rectangle: { 260, 92, 720, 480 }
    • Destination rectangle: { 0, 0, 460, 388 }
  • Substream:
    • Source surface size: { 0, 0, 640, 576 }
    • Source rectangle: { 0, 0, 460, 388 }
    • Destination rectangle: { 260, 188, 720, 576 }

例7: 拉伸和裁剪视频

The preceding diagram shows the following rectangles:

  • Target rectangle: { 0, 0, 720, 480 }
  • Primary video:
    • Source surface size: { 0, 0, 360, 240 }
    • Source rectangle: { 180, 120, 360, 240 }
    • Destination rectangle: { 0, 0, 360, 240 }
  • Substream:
    • Source surface size: { 0, 0, 360, 240 }
    • Source rectangle: { 0, 0, 180, 120 }
    • Destination rectangle: { 360, 240, 720, 480 }

5.输入源序列

 VideoProcessBlt 函数的pSamples 变量指定了一个 input samples数组。主视频流的Samples 最先显示。下面是子视频流位图序列:

  • 按时间顺序,主视频流的Samples 最先显示。为了实现反交错处理,驱动可能需要从主视频流当中获取更多的参考 samples。 在结构体 DXVA2_VideoProcessorCaps 中的NumForwardRefSamples 和变量NumBackwardRefSamples 指定了向前和向后参考帧的数量。调用者必须提供参考的samples ,即使输入视频帧为逐行帧。列如,当一个输入源同时包含逐行帧和交错帧。
  • 在主视频流的samples后面, 数组能包含15个子流samples ,顺序为从低到上。子流总是逐行帧,并且没有参考帧。

任何时候,主视频流总是能在逐行和交错帧之间切换。子流的数量也能改变。

DXVA2_VideoSample结构体的SampleFormat.SampleFormat变量标识了图片的类型。针对子流类型,这个变量为DXVA2_SampleSubStream。针对逐行帧,这个变量为DXVA2_SampleProgressiveFrame,针对为交错帧,这个变量依赖具体的场类型。

如果驱动要求前后参考帧,全部的samples可能无效在视频开始序列,在这种情况下,丢掉DXVA2_SampleUnknown类型的Samples.

DXVA2_VideoSample结构体的Start 和End变量给定了每个sample显示的时间。这个仅仅在主视频流中有用,对于子视频流,设置这两个变量为0.

下面的例子将帮助我们弄清楚这个序列。

例子1:

最简单的情况是:没有子数据流,并且反交错的算法不要求前后参考帧。Bob 反交错算法就是这样的。pSamples 数组包含一个输入数据流。如下图:

这个时间值T被假设为当前视频帧的开始显示时间。

例子2:

这个例子将2个子数据流与一个主视频流融合。反交错算法不请求参考samples,下表显示了samples 在pSamples 数组中显示的顺序:

例子3:

这个例子有2个子视频流,一个主视频流,还请求一个向前参考帧,一个向后参考帧。下表显示了samples 在pSamples 数组中显示的顺序:

时间T-1为当前帧的前一帧的开始时间,T+1为当前帧的后一帧的开始时间。

如果当前视频流切换到逐行帧,使用相同的反交错模式。应用程序必须提供相同的samples序列。序列显示如下表:

例子4:

在刚开始播放视频的时候,向前或者向后的参考帧可能是无效的。这个时候无效的Sample用类型DXVA2_SampleUnknown标识。

假设一个交错算法需要一个先前和一个向后参考帧,前3次调用VideoProcessBlt 函数 Samples 的序列为:

写了一天多终于把这篇博客写完了,希望自己的劳动成果能对大家有所帮助,也感谢我女朋友(吕文静)对我写博客的支持。

这里贴出我在项目中使用dxva进行反交错视频的部分代码:

//获取IDirectXVideoProcessorService接口对象
DXVA2CreateVideoService(m_pd3dDevice, IID_PPV_ARGS(&m_pDXVAVPS));
....




//创建IDirectXVideoProcessor接口对象
bool CD3DRenderEx::CreateVideoProcessor(AVFrame * pFrame, void ** ppVideoProcessor)
{
	if (!m_pDXVAVPS || !ppVideoProcessor)
		return false;

	IDirectXVideoProcessor *pDXVAVPD = NULL;
	*ppVideoProcessor = NULL;

	DXVA2_VideoDesc desc = { 0 };
	desc.SampleWidth = pFrame->width;
	desc.SampleHeight = pFrame->height;
	if (pFrame->format == AV_PIX_FMT_DXVA2_VLD || 
		pFrame->format== AV_PIX_FMT_NV12)
	{
		desc.Format = (D3DFORMAT)MAKEFOURCC('N', 'V', '1', '2');
	}
	else if (pFrame->format == AV_PIX_FMT_YUV420P)
	{
		desc.Format = (D3DFORMAT)MAKEFOURCC('Y', 'V', '1', '2');
	}
	else
	{
		return false;
	}
	
	HR(m_pDXVAVPS->CreateVideoProcessor(DXVA2_VideoProcBobDevice, &desc, D3DFMT_X8R8G8B8, 0, &pDXVAVPD));
	*ppVideoProcessor = pDXVAVPD;
	return true;
}
...



//调用VideoProcessBlt函数来处理输入的surface,得到处理好的Surface
int CD3DRenderEx::PutFrameToVideoTextureByDeinterlaced(AVFrame * pFrame, LONGLONG pts, LONGLONG duration, void * pTexture, void* pVideoProcessor)
{
	if (!m_pd3dDevice || !pFrame || !pTexture || !pVideoProcessor)
	{
		return -1;
	}

	IDirectXVideoProcessor *pDXVAVPD = (IDirectXVideoProcessor*)pVideoProcessor;
	LPD3DVIDEOTEXTURE pD3DVideoTexuture = (LPD3DVIDEOTEXTURE)pTexture;

	DXVA2_VideoProcessBltParams blitParam = { 0 };
	blitParam.TargetFrame = pts * 10000;
	blitParam.TargetRect.right = pD3DVideoTexuture->iTexWidth;
	blitParam.TargetRect.bottom = pD3DVideoTexuture->iTexHeight;
	blitParam.BackgroundColor.Alpha = 0xFFFF;
	blitParam.DestFormat.NominalRange = DXVA2_NominalRange_Unknown;
	blitParam.DestFormat.SampleFormat = DXVA2_SampleFieldInterleavedOddFirst;
	//blitParam.DestData = DXVA2_DestData_RFF;

	DXVA2_ValueRange valueRange;
	pDXVAVPD->GetProcAmpRange(DXVA2_ProcAmp_Brightness, &valueRange);
	blitParam.ProcAmpValues.Brightness = valueRange.DefaultValue;
	pDXVAVPD->GetProcAmpRange(DXVA2_ProcAmp_Contrast, &valueRange);
	blitParam.ProcAmpValues.Contrast = valueRange.DefaultValue;
	pDXVAVPD->GetProcAmpRange(DXVA2_ProcAmp_Hue, &valueRange);
	blitParam.ProcAmpValues.Hue = valueRange.DefaultValue;
	pDXVAVPD->GetProcAmpRange(DXVA2_ProcAmp_Saturation, &valueRange);
	blitParam.ProcAmpValues.Saturation = valueRange.DefaultValue;
	blitParam.Alpha = DXVA2_Fixed32OpaqueAlpha();

	DXVA2_VideoSample videoSample = { 0 };
	videoSample.Start = pts * 10000;
	videoSample.End = (pts + duration) * 10000;
	videoSample.SampleFormat.NominalRange = DXVA2_NominalRange_Unknown;
	videoSample.SampleFormat.SampleFormat = DXVA2_SampleFieldInterleavedOddFirst;
	videoSample.SrcRect.right = pFrame->width;
	videoSample.SrcRect.bottom = pFrame->height;
	videoSample.DstRect.right = pD3DVideoTexuture->iTexWidth;
	videoSample.DstRect.bottom = pD3DVideoTexuture->iTexHeight;
	//videoSample.SampleData = DXVA2_DestData_RFF;

	do
	{
		CAutoExclLocker lock(&pD3DVideoTexuture->srwLock);

		//SUN test硬解
		if (pFrame->format == AV_PIX_FMT_DXVA2_VLD)
		{
			pD3DVideoTexuture->dataFormat = DATA_FMT_BGRA;

			if (!pD3DVideoTexuture->pDisplayTexture)
			{
				if (!CreateDisplayTexture(pD3DVideoTexuture->iTexWidth, pD3DVideoTexuture->iTexHeight, &pD3DVideoTexuture->pDisplayTexture))
				{
					return -1;
				}
			}

			IDirect3DSurface9 * pSurface = NULL;
			pD3DVideoTexuture->pDisplayTexture->GetSurfaceLevel(0, &pSurface);
			IDirect3DSurface9* pDXVASurface = (IDirect3DSurface9*)pFrame->data[3];

			videoSample.SrcSurface = pDXVASurface;

			if (FAILED(pDXVAVPD->VideoProcessBlt(pSurface, &blitParam, &videoSample, 1, NULL)))
			{
				RELEASE(pSurface);
				return -1;
			}
			RELEASE(pSurface);
			return 0;
		}
		else if (pFrame->format == AV_PIX_FMT_NV12)
		{
			int iFrameWidth = pFrame->width & 0xFFFFFFFE;
			int iFrameHeight = pFrame->height & 0xFFFFFFFE;
			if (pD3DVideoTexuture->pOffScreenSurface == NULL)
			{
				if (FAILED(m_pd3dDevice->CreateOffscreenPlainSurface(
					iFrameWidth,
					iFrameHeight,
					(D3DFORMAT)MAKEFOURCC('N', 'V', '1', '2'),
					D3DPOOL_DEFAULT,
					&pD3DVideoTexuture->pOffScreenSurface,
					NULL)))
				{
					return -1;
				}
			}
			D3DLOCKED_RECT d3d_rect;
			if (FAILED(pD3DVideoTexuture->pOffScreenSurface->LockRect(&d3d_rect, NULL, D3DLOCK_DISCARD)))
			{
				return -1;
			}

			byte * pDest = (BYTE *)d3d_rect.pBits;
			int stride = d3d_rect.Pitch;
			pD3DVideoTexuture->dataFormat = DATA_FMT_BGRA;
			for (int i = 0; i < iFrameHeight; i++) {
				memcpy(pDest + i * stride, pFrame->data[0] + i * pFrame->linesize[0], iFrameWidth);
			}
			for (int i = 0; i < iFrameHeight / 2; i++)
			{
				memcpy(pDest + stride * iFrameHeight + i*stride, pFrame->data[1] + i * pFrame->linesize[1], iFrameWidth);
			}
			pD3DVideoTexuture->pOffScreenSurface->UnlockRect();

			if (!pD3DVideoTexuture->pDisplayTexture)
			{
				if (!CreateDisplayTexture(pD3DVideoTexuture->iTexWidth, pD3DVideoTexuture->iTexHeight, &pD3DVideoTexuture->pDisplayTexture, false))
				{
					return -1;
				}
			}

			IDirect3DSurface9 * pSurface = NULL;
			pD3DVideoTexuture->pDisplayTexture->GetSurfaceLevel(0, &pSurface);

			videoSample.SrcSurface = pD3DVideoTexuture->pOffScreenSurface;

			if (FAILED(pDXVAVPD->VideoProcessBlt(pSurface, &blitParam, &videoSample, 1, NULL)))
			{
				RELEASE(pSurface);
				return -1;
			}
			RELEASE(pSurface);
			return 0;	
		}
		else if(pFrame->format == AV_PIX_FMT_YUV420P)
		{
			int iFrameWidth = pFrame->width & 0xFFFFFFFE;
			int iFrameHeight = pFrame->height & 0xFFFFFFFE;
			if (pD3DVideoTexuture->pOffScreenSurface == NULL)
			{
				if (FAILED(m_pd3dDevice->CreateOffscreenPlainSurface(
					iFrameWidth,
					iFrameHeight,
					(D3DFORMAT)MAKEFOURCC('Y', 'V', '1', '2'),
					D3DPOOL_DEFAULT,
					&pD3DVideoTexuture->pOffScreenSurface,
					NULL)))
				{
					return -1;
				}
			}
			D3DLOCKED_RECT d3d_rect;
			if (FAILED(pD3DVideoTexuture->pOffScreenSurface->LockRect(&d3d_rect, NULL, D3DLOCK_DISCARD)))
			{
				return -1;
			}

			byte * pDest = (BYTE *)d3d_rect.pBits;
			int stride = d3d_rect.Pitch;
			pD3DVideoTexuture->dataFormat = DATA_FMT_BGRA;
			for (int i = 0; i < iFrameHeight; i++) {
				memcpy(pDest + i * stride, pFrame->data[0] + i * pFrame->linesize[0], iFrameWidth);
			}
			for (int i = 0; i < iFrameHeight / 2; i++) {
				memcpy(pDest + stride * iFrameHeight + i * stride / 2, pFrame->data[2] + i * pFrame->linesize[1], iFrameWidth / 2);
			}
			for (int i = 0; i < iFrameHeight / 2; i++) {
				memcpy(pDest + stride * iFrameHeight + stride * iFrameHeight / 4 + i * stride / 2, pFrame->data[1] + i * pFrame->linesize[1], iFrameWidth / 2);
			}
			pD3DVideoTexuture->pOffScreenSurface->UnlockRect();

			if (!pD3DVideoTexuture->pDisplayTexture)
			{
				if (!CreateDisplayTexture(pD3DVideoTexuture->iTexWidth, pD3DVideoTexuture->iTexHeight, &pD3DVideoTexuture->pDisplayTexture, false))
				{
					return -1;
				}
			}

			IDirect3DSurface9 * pSurface = NULL;
			pD3DVideoTexuture->pDisplayTexture->GetSurfaceLevel(0, &pSurface);

			videoSample.SrcSurface = pD3DVideoTexuture->pOffScreenSurface;

			if (FAILED(pDXVAVPD->VideoProcessBlt(pSurface, &blitParam, &videoSample, 1, NULL)))
			{
				RELEASE(pSurface);
				return -1;
			}
			
			RELEASE(pSurface);
			return 0;
		}
		else
		{
			return 1;
		}
	} while (0);
}

参考链接:DXVA Video Processing - Win32 apps | Microsoft Learn

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值