WPF Image控件显示视频数据流

注:此解决方案image控件视频数据流很大时会白屏,后来又用PictureBox来显示视频了。请看我后面的文章。

在没有WPF时,我们用PictureBox控件显示图片和视频流。公司新项目用WPF做,而且WPF已有的框架中添加2.0的PictrueBox兼容性太差,所以,用Image控件来显示视频。


服务端 用的视频数据传到客户端的是没有压缩的bitmap位图文件数,通讯协议是UDP 。在做这个之前,对bitmap位图整体地进行了一次学习。可以看下别人写的位图文件结构 博客,由于传过来的视频数据没有文件头,而Image控件绑定的BitmapImage必须有位图文件头才能EndInit成功,所以在创建文件头上花了不少功夫。

首先前台XAML的Image控件和其Source属性对后台的ShowBackground进行了依赖。

<Grid Background="{TemplateBinding ClientAreaBackground}" >
    <Image Stretch="Fill" Source ="{TemplateBinding ShowBackground}"/>
</Grid>

服务端的数据包是一次发送位图的一行数据,如果一个位图的像素高度是240,那一张图片就要发240次数据包。数据包的大小是根据宽度算出来的,一张图片的大小 是长*宽计算出来的。 一张图片240*320的长宽,一秒30帧,一张图片100多KB,一秒钟要处理3MB的数据,CPU使用率30%多,全速的情况下还是很相当耗资源的 。如果降低帧数到一秒3张图片,比较流畅,一样的,视频不流畅了…………

后台代码

        /// <summary>
        /// Gets/Sets 回显Image控件依赖的图像属性
        /// </summary>
        public ImageSource ShowBackground
        {
            get { return (ImageSource)GetValue(ShowBackgroundProperty); }
            set
            {
                SetValue(ShowBackgroundProperty, value);
            }
        }
        public static DependencyProperty ShowBackgroundProperty =
            DependencyProperty.Register("ShowBackground", typeof(ImageSource), typeof(VirtualWindow),
            new UIPropertyMetadata(new BitmapImage(new Uri("F:\\OpenAll.png", UriKind.Relative)))//给个默认图片
            );
        public Socket _WinSkt = null;
        private BitmapImage _memBmp = new BitmapImage();
        public Thread _RdThread;
        byte[] _header = new byte[54];//保存bitmap图片的头信息
        private delegate void ShowPicHandler(MemoryStream imgstream);
        private ShowPicHandler _dShowpic;//显示图片委托
        public int _PortNum;
        private int _lenstart;//记录包是位图中的哪一行
        public int _width;//图片宽度
        public int _height;//图片高度
        private IPAddress _ipa;
        //此方法是启动方法,启动时将IP和端口数据传给服务端在另外一个类里做的,这里只管接收
        public void InitPreview()
        {
            _WinSkt = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.IP);
            while (true)
            {
                _PortNum = new Random().Next(1, 65534);
                try
                {
                    IPEndPoint myIP = new IPEndPoint(_ipa, _PortNum);
                    _WinSkt.Bind(myIP);
                    _WinSkt.ReceiveBufferSize = 1024 * 768 * 3;
                }
                catch
                {
                    continue;
                }
                break;
            }
            //实例化显示图片委托
            _dShowpic = new ShowPicHandler(ShowImage);
            //开启线程接收数据
            _RdThread = new Thread(new ThreadStart(RdRcvThread));
            _RdThread.Start();
        }
        private void RdRcvThread()
        {
            byte[] nMsgLenBuf = new byte[65535];
            byte[] RgbBuf = new byte[6000];         //两行数据,最大像素点1000 * 3 * 2
            int nRev = 0;
            //用来依次写入图片包数据
            MemoryStream buffstream = new MemoryStream();
            //存储一张完成的图片并绘到image
            MemoryStream imgstream=new MemoryStream();

                #region VIDEO信号
                int tmpInt = Convert.ToInt32(_width * 3);//传过去的参数要是4的倍数,这是适应位图数据的一些逻辑,可以不用细究
                int i = tmpInt / 4;
                int j = 0;
                if (tmpInt % 4 != 0) j = (i + 1) * 4;
                else j = tmpInt;
                int row = 0;
                _length = j * _height;//计算一张图片大小
                CreateHeader();//创建文件头
                buffstream.Write(_header, 0, 54);//将文件头写入内存
                while (true)
                {
                    try
                    {
                        nRev = _WinSkt.Receive(nMsgLenBuf);
                    }
                    catch { return; }

                    try
                    {
                        _lenstart = nMsgLenBuf[0] * 256 + nMsgLenBuf[1];//包的头两个byte是此包所在的图片位置
                        //丢包的图片数据内存区域没有处理
                        //丢失的数据包直接不管,找到当前包其所在的位置并写入
                        buffstream.Position = j * _lenstart + 54;
                        buffstream.Write(nMsgLenBuf,0,j);

                        if (_lenstart >= _height - 1)//一张图片传结束了
                        {
                            //解决buff被接收线程修改的问题,将buff中的数据复制到图片内存区
                            imgstream.Position = 0;
                            imgstream.Write(buffstream.GetBuffer(), 0, _length + 54);
                            //显示图片
                            Application.Current.Dispatcher.BeginInvoke(_dShowpic, imgstream);
                        }
                    }
                    catch 
                    {
                        buffstream.Dispose();
                        buffstream = new MemoryStream();
                        buffstream.Write(_header, 0, 54);
                    }
                }
                #endregion
        }
        private void CreateHeader()
        {
            MemoryStream stream = new MemoryStream(14 + 40);   //为头腾出54个长度的空间
            byte[] buffer = new byte[13];
            buffer[0] = 0x42;               //Bitmap固定常数
            buffer[1] = 0x4d;               //Bitmap固定常数
            stream.Write(buffer, 0, 2);     //先写入头的前两个字节
            //把我们之前获得的数据流的长度转换成字节,
            //这个是用来告诉“头”我们的实际图像数据有多大
            byte[] bytes = BitConverter.GetBytes(_length + 54);
            stream.Write(bytes, 0, 4);      //把这个长度写入头中去
            buffer[0] = 0; buffer[1] = 0;
            buffer[2] = 0; buffer[3] = 0;
            stream.Write(buffer, 0, 4);     //在写入4个字节长度的数据到头中去
            int num2 = 0x36;                //Bitmap固定常数
            bytes = BitConverter.GetBytes(num2);
            stream.Write(bytes, 0, 4);      //在写入最后4个字节的长度

            tagBITMAPINFOHEADER sInfoHead = new tagBITMAPINFOHEADER();
            sInfoHead.biSize = 40;          //本结构所占字节数,实际上该结构占用40个字节,但Windows每次还是需要您亲自添上
            sInfoHead.biWidth = _width;     //Convert.ToInt32(GqyFactVWWidth+1);
            sInfoHead.biHeight = -_height;   //Convert.ToInt32(GqyFactVWHight);
            sInfoHead.biPlanes = 1;         //目标设备的平面数,约定必须为1
            sInfoHead.biSizeImage = 0;
            sInfoHead.biBitCount = 24;      //每个像素所需的位数,必须是1(双色)、 4(16色)、8(256色)、24(真彩色)或32(32位真彩)之一
            sInfoHead.biCompression = 0;    //位图压缩类型,必须是0(不压缩)、1(BI_RLE8压缩类型)或2(BI_RLE4压缩类型)之一
            sInfoHead.biXPelsPerMeter = 0;  //水平分辨率,每米像素数,一般不用关心,设为0
            sInfoHead.biYPelsPerMeter = 0;  //垂直分辨率,每米像素数,一般不用关心,设为0
            sInfoHead.biClrUsed = 0;        //位图实际使用的颜色表中的颜色数,一般不用关心,设为0
            sInfoHead.biClrImportant = 0;
            byte[] bufffffff = new byte[40];
            bufffffff = StructToBytes(sInfoHead);
            stream.Write(bufffffff, 0, 40);

            _header = stream.GetBuffer();   //将bmp文件头保存为全局变量 
        }
        private void ShowImage(MemoryStream imgstream)
        {
            try
            {
                _memBmp = new BitmapImage();
                _memBmp.BeginInit();
                _memBmp.StreamSource = imgstream;
                _memBmp.EndInit();
                ShowBackground = _memBmp;//将最终的图片给image控件的资源依赖项属性
            }
            catch { }
        }
        private byte[] StructToBytes(object structObj)
        {
            int size = Marshal.SizeOf(structObj);//得到结构体的大小
            byte[] bytes = new byte[size];//创建byte数组
            IntPtr structPtr = Marshal.AllocHGlobal(size);//分配结构体大小的内存空间
            Marshal.StructureToPtr(structObj, structPtr, false);//将结构体拷到分配好的内存空间
            Marshal.Copy(structPtr, bytes, 0, size); //从内存空间拷到byte数组
            Marshal.FreeHGlobal(structPtr); //释放内存空间
            return bytes;//返回byte数组
        }
文件头结构体字节15-54部分。另外一部分在代码中自己创建了,结构体在上面链接的博客中也有详细介绍。
struct tagBITMAPINFOHEADER
    {
        public uint biSize; //本结构所占字节数,实际上该结构占用40个字节,但Windows每次还是需要您亲自添上
        public int biWidth; //位图的宽度,单位为像素
        public int biHeight; //位图的高度,单位为像素
        public ushort biPlanes; //目标设备的平面数,约定必须为1
        public ushort biBitCount;//每个像素所需的位数,必须是1(双色)、 4(16色)、8(256色)、24(真彩色)或32(32位真彩)之一
        public uint biCompression; //位图压缩类型,必须是0(不压缩)、1(BI_RLE8压缩类型)或2(BI_RLE4压缩类型)之一
        public uint biSizeImage; //位图的大小,以字节为单位,对于BI_RGB必须设置为0,对于压缩文件请参考MSDN
        public int biXPelsPerMeter; //水平分辨率,每米像素数,一般不用关心,设为0
        public int biYPelsPerMeter; //垂直分辨率,每米像素数,一般不用关心,设为0
        public uint biClrUsed;//位图实际使用的颜色表中的颜色数,一般不用关心,设为0
        public uint biClrImportant;//位图显示过程中重要的颜色数,一般不用关心,设为0
    } //该结构占据40个字节。
  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值