65.9K
CodeProject 正在变化。 阅读更多。
Home

C# 中的 H.264 CUDA 编码器 DirectShow 过滤器

starIconstarIconstarIconstarIconstarIcon

5.00/5 (36投票s)

2012年7月16日

CPOL

2分钟阅读

viewsIcon

231473

downloadIcon

12354

本文介绍了如何使用 C# 中的 NVIDIA 编码器 API 创建 H.264 视频编码器 DirectShow 过滤器

引言

此过滤器的实现基于我的 BaseClasses.NET 库,该库在我之前的文章中描述过(C# 中的纯 .NET DirectShow 过滤器)。 因此,您可以了解如何实现编码器过滤器。 该实现使用 NVIDIA CUDA 编码 API。

实现

过滤器的实现方式与前一篇文章中描述的相同。 它使用 TransformFilter 作为基类并覆盖其方法。 编码设置可以通过 IH264Encoder 接口进行配置

[ComVisible(true)] 
[System.Security.SuppressUnmanagedCodeSecurity] 
[Guid("089C95D9-7DAE-4916-BC52-54B45D3925D2")] 
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 
public interface IH264Encoder 
{ 
    [PreserveSig] 
    int get_Bitrate([Out] out int plValue); 
    [PreserveSig] 
    int put_Bitrate([In] int lValue); 
    [PreserveSig] 
    int get_RateControl([Out] out rate_control pValue); 
    [PreserveSig] 
    int put_RateControl([In] rate_control value); 
    [PreserveSig] 
    int get_MbEncoding([Out] out mb_encoding pValue); 
    [PreserveSig] 
    int put_MbEncoding([In] mb_encoding value); 
    [PreserveSig] 
    int get_Deblocking([Out,MarshalAs(UnmanagedType.Bool)] out bool pValue); 
    [PreserveSig] 
    int put_Deblocking([In,MarshalAs(UnmanagedType.Bool)] bool value); 
    [PreserveSig] 
    int get_GOP([Out,MarshalAs(UnmanagedType.Bool)] out bool pValue); 
    [PreserveSig] 
    int put_GOP([In,MarshalAs(UnmanagedType.Bool)] bool value); 
    [PreserveSig] 
    int get_AutoBitrate([Out,MarshalAs(UnmanagedType.Bool)] out bool pValue); 
    [PreserveSig] 
    int put_AutoBitrate([In,MarshalAs(UnmanagedType.Bool)] bool value); 
    [PreserveSig] 
    int get_Profile([Out] out profile_idc pValue); 
    [PreserveSig] 
    int put_Profile([In] profile_idc value); 
    [PreserveSig] 
    int get_Level([Out] out level_idc pValue); 
    [PreserveSig] 
    int put_Level([In] level_idc value); 
    [PreserveSig] 
    int get_SliceIntervals([Out] out int piIDR,[Out] out int piP); 
    [PreserveSig] 
    int put_SliceIntervals([In] ref int piIDR,[In] ref int piP); 
} 

通过该接口可以配置设置。 如何处理它的实现可以在过滤器属性页实现中找到。

NVIDIA CUDA 编码器 API

首先,我制作了 CUDA 视频编码器 API 的互操作

DllImport("nvcuvenc.dll")] 
public static extern int NVCreateEncoder(out IntPtr pNVEncoder); 
DllImport("nvcuvenc.dll")] 
public static extern int NVDestroyEncoder(IntPtr hNVEncoder); 
DllImport("nvcuvenc.dll")] 
public static extern int NVIsSupportedCodec(IntPtr hNVEncoder, uint dwCodecType); 
DllImport("nvcuvenc.dll")] 
public static extern int NVIsSupportedCodecProfile(IntPtr hNVEncoder, uint dwCodecType, uint dwProfileType); 
DllImport("nvcuvenc.dll")] 
public static extern int NVSetCodec(IntPtr hNVEncoder, uint dwCodecType); 
DllImport("nvcuvenc.dll")] 
public static extern int NVGetCodec(IntPtr hNVEncoder, out uint pdwCodecType); 
DllImport("nvcuvenc.dll")] 
public static extern int NVIsSupportedParam(IntPtr hNVEncoder, NVVE_EncodeParams dwParamType); 
DllImport("nvcuvenc.dll")] 
public static extern int NVSetParamValue(IntPtr hNVEncoder, NVVE_EncodeParams dwParamType, IntPtr pData); 
DllImport("nvcuvenc.dll")] 
public static extern int NVGetParamValue(IntPtr hNVEncoder, NVVE_EncodeParams dwParamType, IntPtr pData); 
DllImport("nvcuvenc.dll")] 
public static extern int NVSetDefaultParam(IntPtr hNVEncoder); 
DllImport("nvcuvenc.dll")] 
public static extern int NVCreateHWEncoder(IntPtr hNVEncoder); 
DllImport("nvcuvenc.dll")] 
public static extern int NVGetSPSPPS(IntPtr hNVEncoder, IntPtr pSPSPPSbfr, 
       int nSizeSPSPPSbfr, out int pDatasize); 
DllImport("nvcuvenc.dll")] 
public static extern int NVEncodeFrame(IntPtr hNVEncoder, 
       [MarshalAs(UnmanagedType.LPStruct)] NVVE_EncodeFrameParams pFrmIn, uint flag, IntPtr pData); 
DllImport("nvcuvenc.dll")] 
public static extern int NVGetHWEncodeCaps(); 
DllImport("nvcuvenc.dll")] 
public static extern void NVRegisterCB(IntPtr hNVEncoder, NVVE_CallbackParams cb, IntPtr pUserdata); 

函数 NVRegiserCB 用于在 NVVE_CallbackParams 结构中注册编码器回调。 为此,我们需要为每个回调方法创建一个委托。

public delegate IntPtr PFNACQUIREBITSTREAM(ref int pBufferSize, IntPtr pUserdata); 
public delegate void PFNRELEASEBITSTREAM(int nBytesInBuffer, IntPtr cb, IntPtr pUserdata);
public delegate void PFNONBEGINFRAME([MarshalAs(UnmanagedType.LPStruct)] NVVE_BeginFrameInfo pbfi, IntPtr pUserdata); 
public delegate void PFNONENDFRAME([MarshalAs(UnmanagedType.LPStruct)] NVVE_EndFrameInfo pefi, IntPtr pUserdata); 

[StructLayout(LayoutKind.Sequential)] 
public struct NVVE_CallbackParams 
{ 
    public IntPtr pfnacquirebitstream; 
    public IntPtr pfnreleasebitstream; 
    public IntPtr pfnonbeginframe; 
    public IntPtr pfnonendframe; 
}

在过滤器中,我们应该使用静态方法来遵循这些委托,并在类中创建委托变量 - 这是确保它们位于类范围内而不是局部范围内的主要事情。

private CUDA.PFNACQUIREBITSTREAM m_fnAcquireBitstreamDelegate = new CUDA.PFNACQUIREBITSTREAM(AcquireBitstream); 
private CUDA.PFNRELEASEBITSTREAM m_fnReleaseBitstreamDelegate = new CUDA.PFNRELEASEBITSTREAM(ReleaseBitstream); 
private CUDA.PFNONBEGINFRAME m_fnOnBeginFrame = new CUDA.PFNONBEGINFRAME(OnBeginFrame); 
private CUDA.PFNONENDFRAME m_fnOnEndFrame = new CUDA.PFNONENDFRAME(OnEndFrame); 

// initialization of the callback
CUDA.NVVE_CallbackParams _callback = new CUDA.NVVE_CallbackParams(); 
_callback.pfnacquirebitstream = Marshal.GetFunctionPointerForDelegate(m_fnAcquireBitstreamDelegate); 
_callback.pfnonbeginframe = Marshal.GetFunctionPointerForDelegate(m_fnOnBeginFrame); 
_callback.pfnonendframe = Marshal.GetFunctionPointerForDelegate(m_fnOnEndFrame); 
_callback.pfnreleasebitstream = Marshal.GetFunctionPointerForDelegate(m_fnReleaseBitstreamDelegate); 

// registering callbacks
CUDA.NVRegisterCB(m_hEncoder, _callback, Marshal.GetIUnknownForObject(this));

配置编码器

用于打开、配置和关闭编码器的主要方法

protected HRESULT OpenEncoder() 
protected HRESULT ConfigureEncoder() 
protected HRESULT CloseEncoder() 

我将配置参数放入指定的结构中

protected struct Config 
{ 
    public uint Profile; 
    public CUDA.NVVE_FIELD_MODE Fields; 
    public CUDA.NVVE_RateCtrlType RateControl; 
    public bool bCabac; 
    public int IDRPeriod; 
    public int PPeriod; 
    public long Bitrate; 
    public bool bGOP; 
    public bool bAutoBitrate; 
    public bool bDeblocking; 
} 

要配置编码器,请使用一个 API NVSetParamValue。 该 API 允许设置所有参数,并接受 IntPtr 作为值的参数,这是一个使用示例

IntPtr _ptr = IntPtr.Zero; 
int[] aiSize = new int[2]; 
aiSize[0] = _bmi.Width; 
aiSize[1] = Math.Abs(_bmi.Height); 
_ptr = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(int)) * aiSize.Length); 
Marshal.Copy(aiSize,0,_ptr,aiSize.Length); 
hr = (HRESULT)CUDA.NVSetParamValue(m_hEncoder, CUDA.NVVE_EncodeParams.NVVE_OUT_SIZE, _ptr); 
Marshal.FreeCoTaskMem(_ptr); 

接收和传递样本

首先,我们覆盖 OnReceive 方法。 在此方法中,我们填充 NVVE_EncodeFrameParams 结构并调用 NVEncodeFrame API。 注意:我们不调用基类 OnReceive 方法。

public override int OnReceive(ref IMediaSampleImpl _sample)
{
    {
        AMMediaType pmt;
        if (S_OK == _sample.GetMediaType(out pmt))
        {
            SetMediaType(PinDirection.Input,pmt);
            Input.CurrentMediaType.Set(pmt);
            pmt.Free();
        }
    }
    CUDA.NVVE_EncodeFrameParams _params = new CUDA.NVVE_EncodeFrameParams();
         _params.Height       = m_nHeight;
    _params.Width        = m_nWidth;
    _params.Pitch        = m_nWidth;
    if (m_SurfaceFormat == CUDA.NVVE_SurfaceFormat.YUY2 || m_SurfaceFormat == CUDA.NVVE_SurfaceFormat.UYVY)
    {
        _params.Pitch *= 2;
    }
    _params.PictureStruc     = CUDA.NVVE_PicStruct.FRAME_PICTURE; 
         _params.SurfFmt          = m_SurfaceFormat;
    _params.progressiveFrame = 1;
         _params.repeatFirstField = 0;
    _params.topfieldfirst    = 0;
    _sample.GetPointer(out _params.picBuf);
    _params.bLast = m_evFlush.WaitOne(0) ? 1 : 0;
    HRESULT hr = (HRESULT)CUDA.NVEncodeFrame(m_hEncoder, _params, 0, m_dptrVideoFrame);
    m_evReady.Reset();
    return hr;
}

样本传递在编码器回调中执行。 在 AccureBitstream 中,我们从输出引脚分配器查询新样本并返回其缓冲区指针

private static IntPtr AcquireBitstream(ref int pBufferSize, IntPtr pUserData)
{
    H264EncoderFilter pEncoder = (H264EncoderFilter)Marshal.GetObjectForIUnknown(pUserData);
    pEncoder.m_pActiveSample = IntPtr.Zero;
    pBufferSize = 0;
    HRESULT hr = (HRESULT)pEncoder.Output.GetDeliveryBuffer(out pEncoder.m_pActiveSample,null,null, AMGBF.None);
    IntPtr pBuffer = IntPtr.Zero;
    if (SUCCEEDED(hr))
    {
                 IMediaSampleImpl pSample = new IMediaSampleImpl(pEncoder.m_pActiveSample);
        {
            AMMediaType pmt;
            if (S_OK == pSample.GetMediaType(out pmt))
            {
                           pEncoder.SetMediaType(PinDirection.Output, pmt);
                pEncoder.Output.CurrentMediaType.Set(pmt);
                pmt.Free();
            }
        }
        pSample.SetSyncPoint(pEncoder.m_bSyncSample);
        pBufferSize = pSample.GetSize();
        pSample.GetPointer(out pBuffer);
    }
    return pBuffer;
}

OnBeginFrame 回调例程中,我们保存样本的设置

private static void OnBeginFrame(CUDA.NVVE_BeginFrameInfo pbfi, IntPtr pUserData)
{
    H264EncoderFilter pEncoder = (H264EncoderFilter)Marshal.GetObjectForIUnknown(pUserData);
    pEncoder.m_bSyncSample = (pbfi != null && pbfi.nPicType == CUDA.NVVE_PIC_TYPE_IFRAME);
}

ReleaseBitstream 方法用于调用样本传递

private static void ReleaseBitstream(int nBytesInBuffer, IntPtr cb, IntPtr pUserData)
{
    H264EncoderFilter pEncoder = (H264EncoderFilter)Marshal.GetObjectForIUnknown(pUserData);
    IMediaSampleImpl pSample = new IMediaSampleImpl(pEncoder.m_pActiveSample);
    pEncoder.m_pActiveSample = IntPtr.Zero;
    if (pSample.IsValid)
    {
        long _start, _stop;
        _start = pEncoder.m_rtPosition;
        _stop = _start + pEncoder.m_rtFrameRate;
        pEncoder.m_rtPosition = _stop;
        pSample.SetTime(_start, _stop);
        pSample.SetActualDataLength(nBytesInBuffer);
        pEncoder.Output.Deliver(ref pSample);
        pSample._Release();
    }
}

滤镜概述

具有可用设置以进行配置的属性页。

过滤器支持以下媒体类型作为输入:NV12、YV12、UYVY、YUY2、YUYV、IYUV 以及格式 VideoInfo amd VideoInfo2。 对于平面类型,图片宽度应为 16 对齐,否则类型将被拒绝,因为过滤器不提供自己的分配器。

过滤器的输出类型是 VideoInfo 和 VideoInfo2 格式的 H264,带有 Mpeg2Video 格式的 AVC1 以及 NALU 大小长度 - 4。我制作了该过滤器,使其可以连接到 Avi Mux 过滤器以及常见的 mp4 mts flv 和 mkv 多路复用器。 我根据 DTS 而不是 PTS 设置媒体样本中的时间戳,但这对于好的解码器来说不是问题,并且即使在使用具有较大 IDR 周期的 closed gop 的情况下,也可以将视频嵌入到 AVI 中。

我使用该过滤器获得了良好的性能,并且在以 30 FPS 播放 1280x720 视频时,CPU 使用率仅为 4%。 这包括 VC-1 解码 H264 编码、解码和预览。

历史

  • 2012-07-15 - 初始版本。
© . All rights reserved.