最近做视频监控监控项目,学习了一下如何在 C# WinForm 下进行 H264 解码。下面贴一下代码,让大家了解一下如何使用海思的 H264 解码库进行解码,以方便其他有需要的人使用。
1.首先根据海思解码库的头文件做 P/Invoke 调用封装处理,代码如下
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 using System; 2 using System.Runtime.InteropServices; 3 4 namespace Demo 5 { 6 public class hi_h264dec 7 { 8 public const int HI_SUCCESS = 0; 9 public const int HI_FAILURE = -1; 10 public const int HI_LITTLE_ENDIAN = 1234; 11 public const int HI_BIG_ENDIAN = 4321; 12 public const int HI_DECODER_SLEEP_TIME = 60000; 13 14 public const int HI_H264DEC_OK = 0; 15 public const int HI_H264DEC_NEED_MORE_BITS = -1; 16 public const int HI_H264DEC_NO_PICTURE = -2; 17 public const int HI_H264DEC_ERR_HANDLE = -3; 18 19 [DllImport("hi_h264dec_w.dll", EntryPoint = "Hi264DecImageEnhance", CallingConvention = CallingConvention.Cdecl)] 20 public static extern int Hi264DecImageEnhance(IntPtr hDec, ref hiH264_DEC_FRAME_S pDecFrame, uint uEnhanceCoeff); 21 22 [DllImport("hi_h264dec_w.dll", EntryPoint = "Hi264DecCreate", CallingConvention = CallingConvention.Cdecl)] 23 public static extern IntPtr Hi264DecCreate(ref hiH264_DEC_ATTR_S pDecAttr); 24 25 [DllImport("hi_h264dec_w.dll", EntryPoint = "Hi264DecDestroy", CallingConvention = CallingConvention.Cdecl)] 26 public static extern void Hi264DecDestroy(IntPtr hDec); 27 28 [DllImport("hi_h264dec_w.dll", EntryPoint = "Hi264DecGetInfo", CallingConvention = CallingConvention.Cdecl)] 29 public static extern int Hi264DecGetInfo(ref hiH264_LIBINFO_S pLibInfo); 30 31 [DllImport("hi_h264dec_w.dll", EntryPoint = "Hi264DecFrame", CallingConvention = CallingConvention.Cdecl)] 32 public static extern int Hi264DecFrame(IntPtr hDec, IntPtr pStream, uint iStreamLen, ulong ullPTS, ref hiH264_DEC_FRAME_S pDecFrame, uint uFlags); 33 34 [DllImport("hi_h264dec_w.dll", EntryPoint = "Hi264DecAU", CallingConvention = CallingConvention.Cdecl)] 35 public static extern int Hi264DecAU(IntPtr hDec, IntPtr pStream, uint iStreamLen, ulong ullPTS, ref hiH264_DEC_FRAME_S pDecFrame, uint uFlags); 36 37 [StructLayout(LayoutKind.Sequential)] 38 public struct hiH264_DEC_ATTR_S 39 { 40 public uint uPictureFormat; 41 public uint uStreamInType; 42 public uint uPicWidthInMB; 43 public uint uPicHeightInMB; 44 public uint uBufNum; 45 public uint uWorkMode; 46 public IntPtr pUserData; 47 public uint uReserved; 48 } 49 50 [StructLayout(LayoutKind.Sequential)] 51 public struct hiH264_DEC_FRAME_S 52 { 53 public IntPtr pY; 54 public IntPtr pU; 55 public IntPtr pV; 56 public uint uWidth; 57 public uint uHeight; 58 public uint uYStride; 59 public uint uUVStride; 60 public uint uCroppingLeftOffset; 61 public uint uCroppingRightOffset; 62 public uint uCroppingTopOffset; 63 public uint uCroppingBottomOffset; 64 public uint uDpbIdx; 65 public uint uPicFlag; 66 public uint bError; 67 public uint bIntra; 68 public ulong ullPTS; 69 public uint uPictureID; 70 public uint uReserved; 71 public IntPtr pUserData; 72 } 73 74 [StructLayout(LayoutKind.Sequential)] 75 public struct hiH264_LIBINFO_S 76 { 77 public uint uMajor; 78 public uint uMinor; 79 public uint uRelease; 80 public uint uBuild; 81 [MarshalAs(UnmanagedType.LPStr)] public string sVersion; 82 [MarshalAs(UnmanagedType.LPStr)] public string sCopyRight; 83 public uint uFunctionSet; 84 public uint uPictureFormat; 85 public uint uStreamInType; 86 public uint uPicWidth; 87 public uint uPicHeight; 88 public uint uBufNum; 89 public uint uReserved; 90 } 91 92 [StructLayout(LayoutKind.Sequential)] 93 public struct hiH264_USERDATA_S 94 { 95 public uint uUserDataType; 96 public uint uUserDataSize; 97 public IntPtr pData; 98 public IntPtr pNext; 99 } 100 } 101 }
2.海思库解码分为两种方式:
第一种,输入分断的码流数据进行解码
第二种,输入一帧完整的码流数据进行解码
第一种方式的代码:
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 //初始化解码器,可以在 FormLoad 事件里完成 2 var decAttr = new hi_h264dec.hiH264_DEC_ATTR_S(); 3 decAttr.uPictureFormat = 0; 4 decAttr.uStreamInType = 0; 5 decAttr.uPicWidthInMB = 480 >> 4; 6 decAttr.uPicHeightInMB = 640 >> 4; 7 decAttr.uBufNum = 8; 8 decAttr.uWorkMode = 16; 9 IntPtr _decHandle = hi_h264dec.Hi264DecCreate(ref decAttr); 10 11 hi_h264dec.hiH264_DEC_FRAME_S _decodeFrame = new hi_h264dec.hiH264_DEC_FRAME_S(); 12 //解码 13 //pData 为需要解码的 H264 nalu 数据,length 为该数据的长度 14 var resCode = hi_h264dec.Hi264DecFrame(_decHandle, pData, (uint)length, 0, ref _decodeFrame, 0); 15 while (resCode != hi_h264dec.HI_H264DEC_NEED_MORE_BITS) 16 { 17 if (resCode == hi_h264dec.HI_H264DEC_OK) 18 { 19 if (_decodeFrame.bError == 0) 20 { 21 //计算 y u v 的长度 22 var yLength = _decodeFrame.uHeight * _decodeFrame.uYStride; 23 var uLength = _decodeFrame.uHeight * _decodeFrame.uUVStride / 2; 24 var vLength = uLength; 25 26 var yBytes = new byte[yLength]; 27 var uBytes = new byte[uLength]; 28 var vBytes = new byte[vLength]; 29 var decodedBytes = new byte[yLength + uLength + vLength]; 30 31 //_decodeFrame 是解码后的数据对象,里面包含 YUV 数据、宽度、高度等信息 32 Marshal.Copy(_decodeFrame.pY, yBytes, 0, (int)yLength); 33 Marshal.Copy(_decodeFrame.pU, uBytes, 0, (int)uLength); 34 Marshal.Copy(_decodeFrame.pV, vBytes, 0, (int)vLength); 35 36 //将从 _decodeFrame 中取出的 YUV 数据放入 decodedBytes 中 37 Array.Copy(yBytes, decodedBytes, yLength); 38 Array.Copy(uBytes, 0, decodedBytes, yLength, uLength); 39 Array.Copy(vBytes, 0, decodedBytes, yLength + uLength, vLength); 40 41 //decodedBytes 为yuv数据,可以将其转换为 RGB 数据后再转换为 BitMap 然后通过 PictureBox 控件即可显示 42 //这类代码网上比较常见,我就不贴了 43 } 44 } 45 46 resCode = hi_h264dec.Hi264DecFrame(_decHandle, IntPtr.Zero, 0, 0, ref _decodeFrame, 0); 47 } 48 49 //当所有解码操作完成后需要释放解码库,可以放在 FormClosing 事件里做 50 hi_h264dec.Hi264DecDestroy(_decHandle);
第二种,完整帧方式代码:
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 //初始化解码器,可以在 FormLoad 事件里完成 2 var decAttr = new hi_h264dec.hiH264_DEC_ATTR_S(); 3 decAttr.uPictureFormat = 0; 4 decAttr.uStreamInType = 0; 5 decAttr.uPicWidthInMB = 480 >> 4; 6 decAttr.uPicHeightInMB = 640 >> 4; 7 decAttr.uBufNum = 8; 8 decAttr.uWorkMode = 16; 9 IntPtr _decHandle = hi_h264dec.Hi264DecCreate(ref decAttr); 10 11 hi_h264dec.hiH264_DEC_FRAME_S _decodeFrame = new hi_h264dec.hiH264_DEC_FRAME_S(); 12 //解码 13 //pData 为需要解码的 H264 nalu 数据,length 为该数据的长度 14 if (hi_h264dec.Hi264DecAU(_decHandle, pData, (uint) length, 0, ref _decodeFrame, 0) == 0) 15 { 16 if (_decodeFrame.bError == 0) 17 { 18 //计算 y u v 的长度 19 var yLength = _decodeFrame.uHeight * _decodeFrame.uYStride; 20 var uLength = _decodeFrame.uHeight * _decodeFrame.uUVStride / 2; 21 var vLength = uLength; 22 var yBytes = new byte[yLength]; 23 var uBytes = new byte[uLength]; 24 var vBytes = new byte[vLength]; 25 var decodedBytes = new byte[yLength + uLength + vLength]; 26 //_decodeFrame 是解码后的数据对象,里面包含 YUV 数据、宽度、高度等信息 27 Marshal.Copy(_decodeFrame.pY, yBytes, 0, (int)yLength); 28 Marshal.Copy(_decodeFrame.pU, uBytes, 0, (int)uLength); 29 Marshal.Copy(_decodeFrame.pV, vBytes, 0, (int)vLength); 30 //将从 _decodeFrame 中取出的 YUV 数据放入 decodedBytes 中 31 Array.Copy(yBytes, decodedBytes, yLength); 32 Array.Copy(uBytes, 0, decodedBytes, yLength, uLength); 33 Array.Copy(vBytes, 0, decodedBytes, yLength + uLength, vLength); 34 35 //decodedBytes 为yuv数据,可以将其转换为 RGB 数据后再转换为 BitMap 然后通过 PictureBox 控件即可显示 36 //这类代码网上比较常见,我就不贴了 37 } 38 } 39 40 //当所有解码操作完成后需要释放解码库,可以放在 FormClosing 事件里做 41 hi_h264dec.Hi264DecDestroy(_decHandle);
对于在设备 SDK 回调里每次都能获取到一帧完整的 H264 裸数据时,建议使用第二种方式。而对于读取文件进行本地播放时,建议使用第一种方式。
另外,需要注意海思解码库为商业软件,在解码输出时,画面中会包含海思公司水印,自己想办法去除吧。
附上海思解码库下载(包含库文件、头文件、文档以及官方测试程序和源码):
http://files.cnblogs.com/tracky/Hi_H.264_DEC_SDK_V2.1.0.3.rar
转自:https://www.cnblogs.com/tracky/archive/2013/06/05/3118205.html?utm_source=tuicool