ONVIF ODM在onvif领域里名气很大,是一款开源的NVC实现。其实现采用了c# c++ F#。项目很大,也很复杂。最近研究了一下,自己调用其类库写了一个c#版的RTSP的播放器。难度不大。但要明白其中原理,还需要多研究研究ODM源码。live555+ffmpeg
效果图:
目前难点在于解码过程,BGR转为RGB排列,兼顾效率使用了unsafe 指针。对于不是专门搞图像的,还是需要慢慢理解。
private void DecoderFrame(Bitmap bitmap, VideoBuffer videoBuffer, PlaybackStatistics statistics)
{
try
{
using (var md = videoBuffer.Lock())
{
Rectangle rect = new Rectangle(0, 0, videoBuffer.width, videoBuffer.height);
//将位图锁定到内存中
BitmapData bitmapData = bitmap.LockBits(rect, ImageLockMode.ReadWrite, bitmap.PixelFormat);
//BitmapData bitmapData = bitmap.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
//获取扫描宽度,例如1280*3=3840 1280为宽度即1280个像素点,每个像素有R\G\B三种,3字节
bitmapData.Stride = videoBuffer.stride;
byte[] buffer = new byte[videoBuffer.size];
//将md中数据copy到数组buffer
Marshal.Copy(md.value.scan0Ptr, buffer, 0, videoBuffer.size);
//将buffer中数据copy到bitmapData
Marshal.Copy(buffer, 0, bitmapData.Scan0, w * h * 3);
//循环处理 ,将BGR转换为RGB排列
//http://blog.csdn.net/lulu831110/article/details/4820377
unsafe
{
byte* ptr = (byte*)(bitmapData.Scan0);
for (int i = 0; i < bitmapData.Height; i++)
{
for (int j = 0; j < bitmapData.Width; j++)
{
//将B和R对调,即第一个和第三个对调
byte B = *ptr;//B的值
byte R = *(ptr + 2);//R的值
//对调
*ptr = R;
*(ptr + 2) = B;
ptr += 3;
}
/*
* 表示跨过无用的区域,跳过这些多余的字节,让指针指向下一行,
* 其原因是图像数据在内存中存储时是按4字节对齐的,
* 具体解释如下:假设有一张图片宽度为6,
* 假设是Format24bppRgb格式的(每像素3字节,在以下的讨论中,除非特别说明,
* 否则Bitmap都被认为是24位RGB)。显然,每一行需要6*3=18个字节存储。
* 对于Bitmap就是如此。
* 但对于BitmapData,虽然data.Width还是等于image.Width,
* 但大概是出于显示性能的考虑,
* 每行的实际的字节数将变成大于等于它的那个离它最近的4的整倍数,
* 此时的实际字节数就是Stride。就此例而言,18不是4的整倍数,
* 而比18大的离18最近的4的倍数是20,所以这个data.Stride = 20。
* 显然,当宽度本身就是4的倍数时,bitmapData.Stride = image.Width * 3。
*
* |-------Stride-------------|
* |-------Width----------| |
* Scan0:
* BGR BGR BGR BGR BGR BGR XX
* BGR BGR BGR BGR BGR BGR XX
* BGR BGR BGR BGR BGR BGR XX
*
* 首先用data.Scan0找到第0个像素的第0个分量的地址,
* 这个地址指向的是个byte类型,所以当时定义为byte* ptr。
* 行扫描时,在当前指针位置(不妨看成当前像素的第0个颜色分量)
* 连续取出三个值(3个原色分量。
* 注意,0 1 2代表的次序是B G R。在取指针指向的值时,
* 貌似p[n]和p += n再取p[0]是等价的),然后下移3个位置(ptr += 3,
* 看成指到下一个像素的第0个颜色分量)。做过Bitmap.Width次操作后,
* 就到达了Bitmap.Width * 3的位置,
* 应该要跳过图中标记为X的字节了(共有Stride - Width * 3个字节),
* 代码中就是 ptr += dataIn.Stride - dataIn.Width * 3。
*
*
*
* 一般来说,如果一个像素是一个字节的话(你的代码做这样的假设,其实很不好),
* bmpData.Stride 应该等于bmpWidth,但实际上往往不相等,要差几个字节,
* 因为bmpData.Stride必须是4的倍数,如果不足,则补上几个字节,
* 让bmpData.Stride是4的倍数,这些多余的字节不会存储任何颜色数据,
* ptr += bmpData.Stride - bmpWidth;只是跳过这些多余的字节,
* 让指针指向下一行
*
*
*/
ptr += bitmapData.Stride - bitmapData.Width * 3;
}
}
bitmap.UnlockBits(bitmapData);
Bitmap bt = (Bitmap)bitmap.Clone();
lock (this)
{
bitmap_queue.Enqueue(bt);
}
if (statistics.isNoSignal)
{
if (!panel1.Visible)
{
showLable(true);
}
Console.WriteLine("No Signal");
}
else
{
if (panel1.Visible)
{
showLable(false);
}
}
}
}
catch
{
}
}