Windows窗体图片框无法可靠刷新

时间:2013-10-07 23:57:03

标签: c# .net winforms

我正在使用.NET 4 / C#开发基于Winforms的视频播放器控件。我们收到了一些来自客户的报告,但是帧播放有时是不稳定的 - 每分钟几次,播放时会有明显的滞后。显示屏是一个图片框 - 我们每40毫秒更换一次图像(25fps视频)。

在修复了一些可能导致它(并使播放更顺畅)的事情后,我们偶尔会看到减速,特别是当窗口最大化时我们需要绘制一个全尺寸的视频播放器来进行屏幕显示。我注意到在这种情况下更新图像后,Picturebox最多需要7到12毫秒才能刷新,影响很明显。

所以我尝试了另一种方法,我们有两个图片盒(PB1和PB2)。 PB1显示当前帧,而PB2隐藏。然后,我们在下一帧被解码后立即更新PB2并重新映射图像,所有这些都在它显示下一帧之前。一旦显示下一帧的时间,我们显示PB1并隐藏PB2。对于下一帧,我们更新PB1,显示它并隐藏PB2。冲洗并重复。此操作需要1毫秒,但我们仍然会看到全屏偶尔出现延迟(尽管控制台日志显示更新总是相隔40毫秒),即使在测试应用程序中运行时也会反复显示相同的两个帧。

这是Winforms本身的限制吗?有没有办法让毫秒准确更新控件?这是我上面提到的代码,使用测试应用程序,我们在两个预加载的图像之间交替:

namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{

    private Bitmap image1;
    private Bitmap image2;
    private int i = 0;

    public Form1()
    {
        InitializeComponent();
        image1 = new Bitmap("image1.bmp");
        image2 = new Bitmap("image2.bmp");
        pictureBox1.Image = ResizeBitmap(image1, pictureBox1.Width, pictureBox1.Height);
        pictureBox2.Image = ResizeBitmap(image2, pictureBox2.Width, pictureBox2.Height);

        pictureBox1.Show();
        pictureBox2.Hide();
    }

    public delegate void invoke();

    private void redraw()
    {
        System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();

        Console.WriteLine(DateTime.UtcNow.Millisecond);

        stopwatch.Restart();

        if (i % 2 == 0)
        {
            pictureBox1.BringToFront();                
        }
        else
        {
            pictureBox2.BringToFront();                
        }


        stopwatch.Stop();
        Console.WriteLine("Bring to front: " + stopwatch.ElapsedMilliseconds);
        Console.WriteLine(DateTime.UtcNow.Millisecond);

        i++;
    }

    private Bitmap ResizeBitmap(Bitmap sourceBMP, int width, int height)
    {
        Bitmap result = new Bitmap(width, height);
        using (Graphics g = Graphics.FromImage(result))
            g.DrawImage(sourceBMP, 0, 0, width, height);
        return result;
    }

    private void Form1_SizeChanged(object sender, EventArgs e)
    {
        pictureBox1.Show();
        pictureBox2.Show();
        pictureBox1.Image = ResizeBitmap(image1, pictureBox1.Width, pictureBox1.Height);
        pictureBox2.Image = ResizeBitmap(image2, pictureBox2.Width, pictureBox2.Height);
        pictureBox1.Refresh();
        pictureBox2.Refresh();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        Action<object> action = (object obj) =>
        {
            while (true)
            {
                BeginInvoke(new invoke(redraw));                    
                System.Threading.Thread.Sleep(40);
            }
        };

        System.Threading.Tasks.Task t1 = new System.Threading.Tasks.Task(action, "a");
        t1.Start();
    }
}

}

时间点是正确的,并且没有可能导致延迟的处理。看起来图片框似乎无法每40毫秒可靠地更新一次。

我正在考虑将视频播放器移植到SFML(嵌入在Winforms中),早期的测试显示没有明显的滞后,所以这很有希望。但是,无论如何都要让Picturebox可靠地更新,还是只能达到毫秒精度?

1 个答案:

答案 0 :(得分:3)

您没有获得40毫秒的更新。定时器和Thread.Sleep()精度由Windows时钟中断率决定。每秒64次,每15.625次。所以当你要求40时,你将得到下一个整数倍,3 x 15.625 = 46.875毫秒。

不是真正的问题。代码不完整,它不显示更新Image属性的代码的任何跟踪。除了SizeChanged事件处理程序之外,它还有一个经典问题。您没有处理旧的位图。它需要看起来像这样:

private void Form1_SizeChanged(object sender, EventArgs e)
{
    if (pictureBox1.Image != null) pictureBox1.Image.Dispose();
    pictureBox1.Image = ResizeBitmap(image1, pictureBox1.Width, pictureBox1.Height);
    if (pictureBox2.Image != null) pictureBox2.Image.Dispose();
    pictureBox2.Image = ResizeBitmap(image2, pictureBox2.Width, pictureBox2.Height);
}

没有必要调用Show()和Refresh()方法。如果您在更新代码的其余部分时忘记调用Dispose(),则可以通过使用如此多的非托管内存以及垃圾收集器需要的繁重工作触发程序将导致的许多页面错误来轻松解释生涩的回放要再次收回它。

通过关注您创建的图像的大小和像素格式,您可以获得进一步的改进。调整大小以适应图片框是非常昂贵的,所以请确保它已经具有正确的大小,因此它不需要重新调整。而PixelFormat.Format32bppPArgb绘制十倍的速度比其他所有人快,总是喜欢它。 .NET可以很容易地忽略这些细节,它将采取你所投入的内容。但这不是免费的。