最近在项目中遇到了交错视频,视频显示的时候会出现横纹的现象。如下图:
如果对交错视频的知识不是很了解,可以参考下面两篇博文:
为了让交错视频正常显示,可以通过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)也被提供。软件处理器优化了质量处理,但是速度很慢,因此不能使用在实时视频处理里面。参考软件处理器的GUID为DXVA2_VideoProcSoftwareDevice。
2.创建视频处理器
在用视频处理器处理视频之前,应用程序必须先创建一个视频处理器对象。这里是视频处理步骤的简要概述,后面各节将会有更详细的描述。
1.获取IDirectXVideoProcessorService接口。
2.创建主视频流的视频格式描述。使用这个描述去获取到支持这个视频格式的视频处理器列表。视频处理器用GUID来定义。
3.拿到一个视频处理器以后,获取它的渲染目标格式列表(render-target formats)。这个格式用类型 D3DFORMAT 标识。如果你计划去融合子数据流,你也可以获取支持子流格式的列表。
4.查询对应视频处理器支持的能力集。
5.创建视频处理器对象。
有时你可以省略某些步骤。例如,你能用预先指定的渲染目标格式创建视频处理器对象,看它是否成功。这样就能省掉获取渲染目标格式的过程。一个公用的格式 D3DFMT_X8R8G8B8 通常都能成功。
下面各节将详细描述各个步骤。
获取IDirectXVideoProcessorService接口
IDirectXVideoProcessorService接口从Direct3D device对象中获取。有两种方式去获取这个接口。
- 从Direct3D device中获取
- 从 Direct3D Device Manager中获取。
如果你有一个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);
}