3
votes

I'm working on a Winforms based video player control using .NET 4 / C#. We've received some reports from clients however that the frame playback is sometimes jerky - a few times a minute there'll be a noticeable lag in the playback. The display is a picture box - we change the image every 40 ms (for 25fps video).

After fixing a number of things which could have caused it (and making playback far smoother) we still occasionally see slowdown, particularly when the window is maximized and we need to draw a full sized video player to screen. I noticed that the Picturebox can take up to 7-12ms to refresh after updating the image in this scenario and the impact is obvious.

So I tried a different approach, where we have two pictureboxes (PB1 and PB2). PB1 shows the current frame, while PB2 is hidden. We then update PB2 as soon as the next frame is decoded and refesh the image, all before its time to display the next frame. Once its time to display the next frame, we show PB1 and hide PB2. For the next frame, we update PB1, show it and hide PB2. Rinse and repeat. This operation takes 1ms, however we still see the occasional lag at full screen (despite the console log showing the updates are always 40ms apart), even when run in a test app showing the same two frames over and over.

Is this a limitation of Winforms itself? Is there no way to get millisecond accurate updates of the controls? Here's the code that I mention above, using a test app where we're alternating between two preloaded images:

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();
    }
}

}

The timing is spot on and there's no processing happening that could cause a delay. It just seems that the picture box is unable to reliably update once every 40ms.

I'm looking at porting the video player to SFML (embedded in a Winforms) and early tests show no noticeable lag so that's promising. However, is there anyway to get the Picturebox to update reliably or is it just not capable of millisecond accuracy?

1
I know it is fun trying to reinvent the wheel, but have you tried using the good old embedded Media Player or VlcDotNet? msdn.microsoft.com/en-us/library/bb383953(v=vs.90).aspx vlcdotnet.codeplex.com - JuStDaN
Unfortunately those aren't really suitable for our application. It requires backwards and forwards frame stepping which those don't provide as well as custom overlays. The video player itself has actually been used in production for 1-2 years and works quite well other than this issue. At the moment it looks like changing to SFML for the drawing could work. - awr

1 Answers

3
votes

You don't get 40 msec updates. Timer and Thread.Sleep() accuracy is determined by the Windows clock interrupt rate. Which ticks 64 times per second, once every 15.625. So when you ask for 40, you'll get the next integer multiple, 3 x 15.625 = 46.875 msec.

Not the real problem. The code is incomplete, it doesn't show any trace of the code that updates the Image property. Other than the SizeChanged event handler, and it has a classic problem. You are not disposing the old bitmap. It needs to look like this:

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);
}

With no point in calling the Show() and Refresh() methods. If you also forget to call Dispose() when you update the Image properties in the rest of your code then jerky playback is easily explained by the many page faults your program will trigger from using so much unmanaged memory and the hard work the garbage collector needs to do to reclaim it again.

You get further improvements by paying attention to the size and pixel format of the image you create. Resizing to fit the picturebox is quite expensive so be sure it already has the right size so it doesn't need to be rescaled. And PixelFormat.Format32bppPArgb paints ten times faster than all the other ones, always favor it. .NET makes it easy to ignore these details, it will take what you throw at it. But that doesn't come for free.