对最近的RTP和H264学习进行总结整理-04.20
虽然还是没有搞出来,但总感觉快了哈哈(哪来的自信)
1、RTP协议接受数据
#region 1-RTP协议变量声明
RTPSession session;
RTPReceiver receiver;
RTPParticipant participant;
private Dictionary<uint, List<RTPPacket>> Clients;
#endregion #region 对RTP进行初始化,并接收数据,调用之后就可以接收数据了
session = new RTPSession();
receiver = new RTPReceiver();
IPEndPoint rtpEp = new IPEndPoint(IPAddress.Parse("192.168.1.109"), 5000);
participant = new RTPParticipant(rtpEp);
receiver.AddParticipant(participant);
session.NewRTPPacket = new RTPSession.NewRTPPacket_Callback(NewRTPPacket);
session.AddReceiver(receiver);
Clients = new Dictionary<uint, List<RTPPacket>>();
#endregion
其中NewRTPPackt是
public delegate bool NewRTPPacket_Callback(
RTPPacket packet
)
类型的委托。packet为接收到的RTP包,我们就对这些包进行处理得到想要的帧,然后再把帧进行解码,得到想要的图像(我是这样理解的)
2、H.264进行解码
我从网络上搜索到了一个海思的DLL,可以对H.264进行解码
#region 解码器相关变量声明
/// <summary>
/// 数据的句柄
/// </summary>
IntPtr pData;
/// <summary>
/// 这是解码器属性信息
/// </summary>
public H264Dec.hiH264_DEC_ATTR_S decAttr;
/// <summary>
/// 这是解码器输出图像信息
/// </summary>
public H264Dec.hiH264_DEC_FRAME_S _decodeFrame = new H264Dec.hiH264_DEC_FRAME_S();
/// <summary>
/// 解码器句柄
/// </summary>
public IntPtr _decHandle;
#endregion #region 解码器相关初始化,一般在窗口load中进行初始化
decAttr = new H264Dec.hiH264_DEC_ATTR_S();
decAttr.uPictureFormat = ;
decAttr.uStreamInType = ;
decAttr.uPicWidthInMB = >> ;
decAttr.uPicHeightInMB = >> ;
decAttr.uBufNum = ;
decAttr.uWorkMode = ;
//创建、初始化解码器句柄
_decHandle = H264Dec.Hi264DecCreate(ref decAttr);
//_decodeFrame = new H264Dec.hiH264_DEC_FRAME_S();
#endregion //这一写代码就是h264解码的代码,其中未声明的函数和变量会在下面进行声明给出,主要是讲YUV转为RGB,在保存为Bitmap文件
if (H264Dec.Hi264DecAU(_decHandle, pData, (uint)newData.Length, , ref _decodeFrame, ) == )
{
if (_decodeFrame.bError == )
{
//策画 y u v 的长度
var yLength = _decodeFrame.uHeight * _decodeFrame.uYStride;
var uLength = _decodeFrame.uHeight * _decodeFrame.uUVStride / ;
var vLength = uLength;
var yBytes = new byte[yLength];
var uBytes = new byte[uLength];
var vBytes = new byte[vLength];
var decodedBytes = new byte[yLength + uLength + vLength]; //_decodeFrame 是解码后的数据对象,里面包含 YUV 数据、宽度、高度等信息 Marshal.Copy(_decodeFrame.pY, yBytes, , (int)yLength);
Marshal.Copy(_decodeFrame.pU, uBytes, , (int)uLength);
Marshal.Copy(_decodeFrame.pV, vBytes, , (int)vLength); //将从 _decodeFrame 中取出的 YUV 数据放入 decodedBytes 中
Array.Copy(yBytes, decodedBytes, yLength);
Array.Copy(uBytes, , decodedBytes, yLength, uLength);
Array.Copy(vBytes, , decodedBytes, yLength + uLength, vLength); ConvertYUV2RGB(yuv, rgb, width, height);
ConvertYUV2RGB(decodedBytes, rgb, width, height);
// 写 BMP 文件。
WriteBMP(rgb, width, height, string.Format("E:\\test\\yuv2bmp_{0}.bmp", index++));
}
}
其中pData为需要的一帧数据,因为pData为Intptr类型,而一帧数据是byte[]类型,所以我从网上查了查怎么转换,下面是代码,newData是byte【】,pData是intptr类型。
GCHandle hObject = GCHandle.Alloc(newData, GCHandleType.Pinned);
pData = hObject.AddrOfPinnedObject();
H264解码类
public class H264Dec
{
public const int HI_SUCCESS = ; public const int HI_FAILURE = -; public const int HI_LITTLE_ENDIAN = ; public const int HI_BIG_ENDIAN = ; public const int HI_DECODER_SLEEP_TIME = ; public const int HI_H264DEC_OK = ; public const int HI_H264DEC_NEED_MORE_BITS = -; public const int HI_H264DEC_NO_PICTURE = -; public const int HI_H264DEC_ERR_HANDLE = -; [DllImport("hi_h264dec_w.dll", EntryPoint = "Hi264DecImageEnhance", CallingConvention = CallingConvention.Cdecl)]
public static extern int Hi264DecImageEnhance(IntPtr hDec, ref hiH264_DEC_FRAME_S pDecFrame, uint uEnhanceCoeff); [DllImport("hi_h264dec_w.dll", EntryPoint = "Hi264DecCreate", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr Hi264DecCreate(ref hiH264_DEC_ATTR_S pDecAttr); [DllImport("hi_h264dec_w.dll", EntryPoint = "Hi264DecDestroy", CallingConvention = CallingConvention.Cdecl)]
public static extern void Hi264DecDestroy(IntPtr hDec); [DllImport("hi_h264dec_w.dll", EntryPoint = "Hi264DecGetInfo", CallingConvention = CallingConvention.Cdecl)]
public static extern int Hi264DecGetInfo(ref hiH264_LIBINFO_S pLibInfo); /// <summary>
/// 对输入的一段码流进行解码并按帧输出图像
/// </summary>
/// <param name="hDec">解码器句柄</param>
/// <param name="pStream">码流起始地址</param>
/// <param name="iStreamLen">码流长度</param>
/// <param name="ullPTS">时间戳信息</param>
/// <param name="pDecFrame">图像信息</param>
/// <param name="uFlags">解码模式 0:正常解码;1、解码完毕并要求解码器输出残留图像</param>
/// <returns></returns>
[DllImport("hi_h264dec_w.dll", EntryPoint = "Hi264DecFrame", CallingConvention = CallingConvention.Cdecl)]
public static extern int Hi264DecFrame(IntPtr hDec, IntPtr pStream, uint iStreamLen, ulong ullPTS, ref hiH264_DEC_FRAME_S pDecFrame, uint uFlags); [DllImport("hi_h264dec_w.dll", EntryPoint = "Hi264DecAU", CallingConvention = CallingConvention.Cdecl)]
public static extern int Hi264DecAU(IntPtr hDec, IntPtr pStream, uint iStreamLen, ulong ullPTS, ref hiH264_DEC_FRAME_S pDecFrame, uint uFlags);
/// <summary>
/// 解码器属性信息。
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct hiH264_DEC_ATTR_S
{
/// <summary>
/// 解码器输出图像格式,目前解码库只支持YUV420图像格式
/// </summary>
public uint uPictureFormat;
/// <summary>
/// 输入码流格式 0x00: 目前解码库只支持以“00 00 01”为nalu分割符的流式H.264码流
/// </summary>
public uint uStreamInType;
/// <summary>
/// 图像宽度
/// </summary>
public uint uPicWidthInMB;
/// <summary>
/// 图像高度
/// </summary>
public uint uPicHeightInMB;
/// <summary>
/// 参考帧数目
/// </summary>
public uint uBufNum;
/// <summary>
/// 解码器工作模式
/// </summary>
public uint uWorkMode;
/// <summary>
/// 用户私有数据
/// </summary>
public IntPtr pUserData;
/// <summary>
/// 保留字
/// </summary>
public uint uReserved; } /// <summary>
/// 解码器输出图像信息数据结构
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct hiH264_DEC_FRAME_S
{
/// <summary>
/// Y分量地址
/// </summary>
public IntPtr pY;
/// <summary>
/// U分量地址
/// </summary>
public IntPtr pU;
/// <summary>
/// V分量地址
/// </summary>
public IntPtr pV;
/// <summary>
/// 图像宽度(以像素为单位)
/// </summary>
public uint uWidth;
/// <summary>
/// 图像高度(以像素为单位)
/// </summary>
public uint uHeight;
/// <summary>
/// 输出Y分量的stride (以像素为单位)
/// </summary>
public uint uYStride;
/// <summary>
/// 输出UV分量的stride (以像素为单位)
/// </summary>
public uint uUVStride;
/// <summary>
/// 图像裁减信息:左边界裁减像素数
/// </summary>
public uint uCroppingLeftOffset;
/// <summary>
/// 图像裁减信息:右边界裁减像素数
/// </summary>
public uint uCroppingRightOffset;
/// <summary>
/// 图像裁减信息:上边界裁减像素数
/// </summary>
public uint uCroppingTopOffset;
/// <summary>
/// 图像裁减信息:下边界裁减像素数
/// </summary>
public uint uCroppingBottomOffset;
/// <summary>
/// 输出图像在dpb中的序号
/// </summary>
public uint uDpbIdx;
/// <summary>
/// 图像类型:0:帧; 1:顶场; 2:底场 */
/// </summary>
public uint uPicFlag;
/// <summary>
/// 图像类型:0:帧; 1:顶场; 2:底场 */
/// </summary>
public uint bError;
/// <summary>
/// 图像是否为IDR帧:0:非IDR帧;1:IDR帧
/// </summary>
public uint bIntra;
/// <summary>
/// 时间戳
/// </summary>
public ulong ullPTS;
/// <summary>
/// 图像信号
/// </summary>
public uint uPictureID;
/// <summary>
/// 保留字
/// </summary>
public uint uReserved;
/// <summary>
/// 指向用户私有数据
/// </summary>
public IntPtr pUserData; } /// <summary>
/// 解码库版本、版权和能力集信息。
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct hiH264_LIBINFO_S
{
/// <summary>
/// 主编号
/// </summary>
public uint uMajor;
/// <summary>
/// 次编号
/// </summary>
public uint uMinor;
/// <summary>
/// 发布编号
/// </summary>
public uint uRelease;
/// <summary>
/// 建构编号
/// </summary>
public uint uBuild;
/// <summary>
/// 版本信息
/// </summary>
[MarshalAs(UnmanagedType.LPStr)]
public string sVersion;
/// <summary>
/// 版权信息
/// </summary>
[MarshalAs(UnmanagedType.LPStr)]
public string sCopyRight;
/// <summary>
/// 解码库能力集
/// </summary>
public uint uFunctionSet;
/// <summary>
/// 支持的输出图像格式
/// </summary>
public uint uPictureFormat;
/// <summary>
/// 输入码流格式
/// </summary>
public uint uStreamInType;
/// <summary>
/// 最大图像宽度(以像素为单位)
/// </summary>
public uint uPicWidth;
/// <summary>
/// 最大图像高度(以像素为单位)
/// </summary>
public uint uPicHeight;
/// <summary>
/// 最大参考帧数目
/// </summary>
public uint uBufNum;
/// <summary>
/// 保留字
/// </summary>
public uint uReserved; } /// <summary>
/// 用户私有数据信息。
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct hiH264_USERDATA_S
{
/// <summary>
/// 用户数据类型
/// </summary>
public uint uUserDataType;
/// <summary>
/// 用户数据长度
/// </summary>
public uint uUserDataSize;
/// <summary>
/// 用户数据缓冲区
/// </summary>
public IntPtr pData;
/// <summary>
/// 指向下一段用户数据
/// </summary>
public IntPtr pNext;
}
}
这是YUV转RGB图像。
/// <summary>
/// 将转换后的 RGB 图像数据按照 BMP 格式写入文件。
/// </summary>
/// <param name="rgbFrame">RGB 格式图像数据。</param>
/// <param name="width">图像宽(单位:像素)。</param>
/// <param name="height">图像高(单位:像素)。</param>
/// <param name="bmpFile"> BMP 文件名。</param>
static void WriteBMP(byte[] rgbFrame, int width, int height, string bmpFile)
{
// 写 BMP 图像文件。
int yu = width * % ;
int bytePerLine = ;
yu = yu != ? - yu : yu;
bytePerLine = width * + yu; using (FileStream fs = File.Open(bmpFile, FileMode.Create))
{
using (BinaryWriter bw = new BinaryWriter(fs))
{
bw.Write('B');
bw.Write('M');
bw.Write(bytePerLine * height + );
bw.Write();
bw.Write();
bw.Write();
bw.Write(width);
bw.Write(height);
bw.Write((ushort));
bw.Write((ushort));
bw.Write();
bw.Write(bytePerLine * height);
bw.Write();
bw.Write();
bw.Write();
bw.Write(); byte[] data = new byte[bytePerLine * height];
int gIndex = width * height;
int bIndex = gIndex * ; for (int y = height - , j = ; y >= ; y--, j++)
{
for (int x = , i = ; x < width; x++)
{
data[y * bytePerLine + i++] = rgbFrame[bIndex + j * width + x]; // B
data[y * bytePerLine + i++] = rgbFrame[gIndex + j * width + x]; // G
data[y * bytePerLine + i++] = rgbFrame[j * width + x]; // R
}
} bw.Write(data, , data.Length);
bw.Flush();
}
}
} /// <summary>
/// 将一桢 YUV 格式的图像转换为一桢 RGB 格式图像。
/// </summary>
/// <param name="yuvFrame">YUV 格式图像数据。</param>
/// <param name="rgbFrame">RGB 格式图像数据。</param>
/// <param name="width">图像宽(单位:像素)。</param>
/// <param name="height">图像高(单位:像素)。</param>
static void ConvertYUV2RGB(byte[] yuvFrame, byte[] rgbFrame, int width, int height)
{
int uIndex = width * height;
int vIndex = uIndex + ((width * height) >> );
int gIndex = width * height;
int bIndex = gIndex * ; int temp = ; for (int y = ; y < height; y++)
{
for (int x = ; x < width; x++)
{
// R分量
temp = (int)(yuvFrame[y * width + x] + (yuvFrame[vIndex + (y / ) * (width / ) + x / ] - ) * YUV2RGB_CONVERT_MATRIX[, ]);
rgbFrame[y * width + x] = (byte)(temp < ? : (temp > ? : temp)); // G分量
temp = (int)(yuvFrame[y * width + x] + (yuvFrame[uIndex + (y / ) * (width / ) + x / ] - ) * YUV2RGB_CONVERT_MATRIX[, ] + (yuvFrame[vIndex + (y / ) * (width / ) + x / ] - ) * YUV2RGB_CONVERT_MATRIX[, ]);
rgbFrame[gIndex + y * width + x] = (byte)(temp < ? : (temp > ? : temp)); // B分量
temp = (int)(yuvFrame[y * width + x] + (yuvFrame[uIndex + (y / ) * (width / ) + x / ] - ) * YUV2RGB_CONVERT_MATRIX[, ]);
rgbFrame[bIndex + y * width + x] = (byte)(temp < ? : (temp > ? : temp));
}
}
}
3、这可能就是我遇到问题的地方了,怎么把RTPPack中的包数据转换为一帧图像信息,我找到的资料是;
#region 对收到的数据进行处理
if (!Clients.ContainsKey(packet.SSRC))//如果接受端第一次接受到某源的数据,则加入到
{
if (Clients.Count < )//如果发送端为4,则丢弃包
{
Clients.Add(packet.SSRC, new List<RTPPacket> { packet });
//ImagesBoxMapping[ImagesBoxMapping.First(pair => pair.Value == null).Key] = packet.SSRC;
}
}
else
{
Clients[packet.SSRC].Add(packet);
} if (packet.Marker)//如果已经发送完毕
{
//丢包检测
var orderPackets = Clients[packet.SSRC].OrderBy(rtpPacket => rtpPacket.SequenceNumber);
if (Clients[packet.SSRC].Count != (orderPackets.Last().SequenceNumber - orderPackets.First().SequenceNumber + ))
{
Clients[packet.SSRC].Clear();//清空缓存区
return true;
} //1.包重组
var count = Clients[packet.SSRC].Sum(rtpPacket => rtpPacket.DataSize);//数据总数 var newData = new byte[count]; long offSet = ;
foreach (var rtpPacket in Clients[packet.SSRC])
{
Array.Copy(rtpPacket.DataPointer, , newData, offSet, rtpPacket.DataSize);
offSet += rtpPacket.DataSize;
}
Clients[packet.SSRC].Clear();//清空缓存区
这里我理解的是newData里面就是一帧数据,但我测试了一下不对(晕)。
4、总结
这几天一直想要尽快做出来,却总没有办法深入去研究视频方面的东西。比如得到的包怎么变为一帧,怎么从一帧里面提取需要的数据,什么PPS、SPS、IDR都是什么,虽然知道名词,但总没法很明确的说出来。
我的解码思路是:RTP协议收到包后(这一步没有问题),将包的数据转为帧(这个地方可能出问题了,也可能是传过来的帧数据不符合解码的要求),再把一帧的数据传给H264解码类解码,解码后输出的是YUV,YUV->RGB->图片进行显示就可以了。这是我的思路,但没有成功。如果读者您懂这一方面,还希望给我指导。谢谢
每天写一点点,就能进步一点点.
晚上更新:
H264起始码有时是0x00000001,有时是0x000001,这两种的区别是:一共有两种起始码:3字节的0x000001和4字节的0x00000001,3字节的0x000001只有一种场合下使用,就是一个完整的帧被编为多个slice的时候,包含这些slice的nalu使用3字节起始码。其余场合都是4字节的。而海思的解码库中说的很清楚,只能解0x000001起始码的nalu,而我测试的都是0x00000001四个字节的,所以这方面可能出了点问题。哎,基础只是不好就是容易出现错误。使用VLC.NET开源可以解决RTP发送的H264码流,明天进行总结.
对最近的RTP和H264学习进行总结整理-04.20的更多相关文章
- (转)基于RTP的H264视频数据打包解包类
最近考虑使用RTP替换原有的高清视频传输协议,遂上网查找有关H264视频RTP打包.解包的文档和代码.功夫不负有心人,找到不少有价值的文档和代码.参考这些资料,写了H264 RTP打包类.解包类,实现 ...
- rtp传输h264
---恢复内容开始--- 基本概念的理解 H.264的主要目标:1.高的视频压缩比2.良好的网络亲和性 解决方案:VCL video coding layer 视频编码层NAL network abs ...
- canvas学习之API整理笔记(二)
前面我整理过一篇文章canvas学习之API整理笔记(一),从这篇文章我们已经可以基本了解到常用绘图的API.简单的变换和动画.而本篇文章的主要内容包括高级动画.像素操作.性能优化等知识点,讲解每个知 ...
- Caffe学习笔记2--Ubuntu 14.04 64bit 安装Caffe(GPU版本)
0.检查配置 1. VMWare上运行的Ubuntu,并不能支持真实的GPU(除了特定版本的VMWare和特定的GPU,要求条件严格,所以我在VMWare上搭建好了Caffe环境后,又重新在Windo ...
- 一份关于Swift语言学习资源的整理文件
一份关于Swift语言学习资源的整理文件 周银辉 在这里下载 https://github.com/ipader/SwiftGuide
- 【资源】C++学习资料 - 逆天整理 - 精华无密版【最新】
再失效就太无语了,链接都是多份的~~—————————————————基础——————————————C++环境搭建(全套)http://pan.baidu.com/s/1o6y0smY链接:http ...
- iOS学习笔记-精华整理
iOS学习笔记总结整理 一.内存管理情况 1- autorelease,当用户的代码在持续运行时,自动释放池是不会被销毁的,这段时间内用户可以安全地使用自动释放的对象.当用户的代码运行告一段 落,开始 ...
- iOS学习笔记总结整理
来源:http://mobile.51cto.com/iphone-386851_all.htm 学习IOS开发这对于一个初学者来说,是一件非常挠头的事情.其实学习IOS开发无外乎平时的积累与总结.下 ...
- 学习ReactNative笔记整理一___JavaScript基础
学习ReactNative笔记整理一___JavaScript基础 ★★★笔记时间- 2017-1-9 ★★★ 前言: 现在跨平台是一个趋势,这样可以减少开发和维护的成本.第一次看是看的ReactNa ...
随机推荐
- JSP读取My SQL数据乱码问题的解决
用jsp读取My SQL数据库里面的数据,结果读出来的是乱码,把jsp页面的charset.pageEncoding属性都改成了UTF-8,My SQL数据库的Collate属性也改成了UTF-8,还 ...
- When to close cursors using MySQLdb
http://stackoverflow.com/questions/5669878/when-to-close-cursors-using-mysqldb I'm building a WSGI w ...
- 18 个命令&工具帮你定位 Linux 性能问题
1.TopTop命令是一个性能监控程序,它按一定的顺序显示所有正在运行而且处于活动状态的实时进程,而且会定期更新显示结果.这条命令显示了CPU的使用率.内存使用率.交换内存使用大小.高速缓存使用大小. ...
- TestNG 三 测试方法
一.设置参数 测试方法是可以带有参数的.每个测试方法都可以带有任意数量的参数,并且可以通过使用TestNG的@Parameters向方法传递正确的参数. 设置方式有两种方法:使用testng.xml或 ...
- Java的“友好的”访问指示符(修饰符)
如果根本不指定访问指示符,就象本章之前的所有例子那样,这时会出现什么情况呢?默认的访问没有关键字,但它通常称为"友好"(Friendly)访问.这意味着当前包内的其他所有类都能访问 ...
- jbox演示30种不同的调用方法
在线预览 插件说明 - jbox 是一款基于 jQuery 的多功能对话框插件,能够实现网站的整体风格效果,给用户一个新的视觉享受. 运行环境 - 兼容 IE6+.Firefox.Chrome.Sa ...
- windows 80 端口占用
1. cmd 2. regidit 3. 注册表 KEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\HTTP'右边有一个'start'的DWORD ...
- CSS3动画(个人理解)
随着学习的深入,越来越觉得Css3动画的重要,虽然JQ自定义动画和动画回调函数必须掌握,但是css3动画做起来更加绚丽,更加方便!1.常规使用1.1 使用transition属性,一般我们是配合hov ...
- URL-统一资源定位器
URL - Uniform Resource Locator URL 可以由单词组成,比如 “w3school.com.cn”,或者是因特网协议(IP)地址:192.168.1.253.大多数人在网上 ...
- JavaScript一些基础技巧和注意事项,你了解这些吗?
总结了一些JavaScript在开发编码中的使用技巧,如有不对,欢迎指正. 一.JavaScript在HTML和XHTML的使用 使用<script>元素有两种方式:直接在页面中嵌入Jav ...