需求:

使用WPF开发应用程序来预览多个摄像头视频数据。


设计思路:

1)通过FFMPEG获取摄像头视频数据。

2)通过SDL来显示视频图像。


技术问题点:

SDL只能在窗体显示,要么弹出新窗体,要么在程序UI相关控件(比如PictureBox控件,也可以是其他Static类控件)显示。显然,获取一个视频设备弹出一个新窗体,不符合需求。那只能把SDL显示窗体嵌入到UI控件里。

要想把SDL嵌入UI控件,就必须要把该UI控件的句柄传给SDL的SDL_CreateWindowFrom()函数。

但是,WPF的UI控件没有句柄这概念,需要在WPF应用程序嵌入Win Form的控件。先创建WindowsFormsHost控件,就可以插入Win Form控件了,代码示例:

<WindowsFormsHost Canvas.Left="10" Canvas.Top="70" Width="230" Height="180" Background="Black">
                    <wf:PictureBox />
                </WindowsFormsHost>



但是,生成的效果如下:

wpf MVVM 监控窗体变大变小事件_wpf MVVM 监控窗体变大变小事件

我们发现,在滚动时WindowsFormsHost控件始终都是置顶显示,查询类似问题,答复是“wpf嵌套Win Form组件必定置顶,无解,wpf控件无句柄”。

那显然此需求不能使用SDL来显示视频图像,只好想办法直接使用WPF的Image控件。

新的问题来了,要在Image控件显示,就需要把视频数据转换成Image控件能识别的BitmapImage。

网上有Bitmap转换BitmapImage的方法(参考:http://blog.sina.com.cn/s/blog_6e6941e10100n2yn.html),其代码如下:

public static BitmapImage BitmapToBitmapImage(Bitmap bitmap)
        {
            Bitmap bitmapSource = new Bitmap(bitmap.Width, bitmap.Height);
            bitmapSource.MakeTransparent();
            int i, j;
            for (i = 0; i < bitmap.Width; i++)
                for (j = 0; j < bitmap.Height; j++)
                {

                    Color pixelColor = bitmap.GetPixel(i, j);
                    Color newColor = Color.FromArgb(pixelColor.R, pixelColor.G, pixelColor.B);
                    if (newColor == Color.FromArgb(255, 0, 0, 0))
                    {
                        newColor = Color.FromArgb(255, 255, 255, 255);
                    }
                    bitmapSource.SetPixel(i, j, newColor);
                }
            MemoryStream ms = new MemoryStream();
            bitmapSource.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp);
            BitmapImage bitmapImage = new BitmapImage();
            bitmapImage.BeginInit();
            bitmapImage.StreamSource = new MemoryStream(ms.ToArray());
            bitmapImage.EndInit();
            return bitmapImage;
        }

但是,我们从FFMPEG直接得到的视频数据是某种格式的数据,比如AV_PIX_FMT_RGB24格式。我们没有必要把AV_PIX_FMT_RGB24格式数据先转换到Bitmap,直接转换到BitmapImage即可,其代码如下:

/// <summary>
        /// 格式为AV_PIX_FMT_RGB24的视频数据,转换成WPF Image控件所能识别的BitmapImage类的数据
        /// 
        /// </summary>
        /// <param name="data">AV_PIX_FMT_RGB24的视频数据</param>
        /// <param name="imgWidth">显示图像宽度</param>
        /// <param name="imgHeight">显示图像高度</param>
        /// <returns></returns>
        public static BitmapImage VideoDataToBitmapImage(byte []data, int imgWidth, int imgHeight)
        {
            Bitmap bitmapSource = new Bitmap(imgWidth, imgHeight);
            bitmapSource.MakeTransparent();
            int i, j;
#if true         
            for (i = 0; i < imgWidth; i++)
                for (j = 0; j < imgHeight; j++)
                {
                    Color newColor = Color.FromArgb(
                        data[j * imgWidth * 3 + i * 3 + 0],
                        data[j * imgWidth * 3 + i * 3 + 1],
                        data[j * imgWidth * 3 + i * 3 + 2]);
                    if (newColor == Color.FromArgb(255, 0, 0, 0))
                    {
                        newColor = Color.FromArgb(255, 255, 255, 255);
                    }
                    bitmapSource.SetPixel(i, j, newColor);
                }
#else
            for (i = 0; i < imgHeight; i++)
                for (j = 0; j < imgWidth; j++)
                {
                    Color newColor = Color.FromArgb(
                        data[i * imgWidth * 3 + j * 3 + 0],
                        data[i * imgWidth * 3 + j * 3 + 1],
                        data[i * imgWidth * 3 + j * 3 + 2]);
                    if (newColor == Color.FromArgb(255, 0, 0, 0))
                    {
                        newColor = Color.FromArgb(255, 255, 255, 255);
                    }
                    bitmapSource.SetPixel(j, i, newColor);
                }
#endif
            MemoryStream ms = new MemoryStream();
            bitmapSource.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp);
            BitmapImage bitmapImage = new BitmapImage();
            bitmapImage.BeginInit();
            bitmapImage.StreamSource = new MemoryStream(ms.ToArray());
            bitmapImage.EndInit();
            return bitmapImage;
        }

上述代码,我调试了两天,才能正常显示图像,差点开始怀疑人生……上述函数重点是如何给每个像素点设置颜色,即处理SetPixel()函数。直到意识到这一点才得到正确的算法:我参考的代码的填充流程是一列一列的填充,但是获取的视频数据是一行一行的顺序。