Memory Leak from ImageSource/CreateBitmapSourceFro

2019-09-05 23:42发布

问题:

I am trying to output whatever is captured from a webcam to an Image control in a WPF window. I am using the AForge.NET library.

Unfortunately, after a few minutes of successful capturing, I am getting an OutOfMemoryException. Likewise, as soon as I start capturing, I can see my memory usage rise continuously in the task manager until the moment of the exception (although there have been a few occasions where memory usage kept rising, then steeply dropped back to its original state, and then kept rising again to the point of the exception).

This is my code for the handler of the NewFrame event of the VideoCaptureDevice class (whose code for converting the Bitmap instance to an ImageSource is largely based on an answer by Sascha Hennig):

[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hObject);

private void videoSource_NewFrame(object sender, NewFrameEventArgs eventArgs)
{
    try
    {
        using (var streamBitmap = (Bitmap)eventArgs.Frame.Clone()) {
            BitmapSource bitmapSourceVideo;

            var hBitmap = streamBitmap.GetHbitmap();
            try
            {
                bitmapSourceVideo = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
                    hBitmap,
                    IntPtr.Zero,
                    Int32Rect.Empty,
                    BitmapSizeOptions.FromEmptyOptions());
            }
            finally
            {
                DeleteObject(hBitmap);
            }
            bitmapSourceVideo.Freeze();

            Dispatcher.BeginInvoke(new ThreadStart(delegate
                                                       {
                                                           videoControl.Source = bitmapSourceVideo;
                                                       }));
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.ToString());
    }
}

In case you wonder, the call to eventArgs.Frame.Clone() seems to be required. Explanations can be found here, and possibly here.

While trying to isolate the source of the issue, I have commented out various portions of this code until I arrived at this state:

[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hObject);

private void videoSource_NewFrame(object sender, NewFrameEventArgs eventArgs)
{
    try
    {
        using (var streamBitmap = (Bitmap)eventArgs.Frame.Clone()) {
            BitmapSource bitmapSourceVideo;

            var hBitmap = streamBitmap.GetHbitmap();
            try
            {/*
                bitmapSourceVideo = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
                    hBitmap,
                    IntPtr.Zero,
                    Int32Rect.Empty,
                    BitmapSizeOptions.FromEmptyOptions());
            */}
            finally
            {
                DeleteObject(hBitmap);
            }
            /*
            bitmapSourceVideo.Freeze();

            Dispatcher.BeginInvoke(new ThreadStart(delegate
                                                       {
                                                           videoControl.Source = bitmapSourceVideo;
                                                       }));*/
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.ToString());
    }
}

(Obviously, this doesn't draw anything to the window, but that is beside the point now.) This version of the method does not feature the memory leak. Removing the comment signs around the statement with the call to CreateBitmapSourceFromHBitmap brings the memory leak back. What am I missing here?

There have been various resources about seemingly similar problems, none of which have helped me find a solution:

  • This answer assumes I am loading from a URI that I could instead load into a stream.
  • This answer seems to assume I am loading the bitmap data directly from a stream that I can access, and likewise, this blogpost suggests to create a stream wrapper.
  • The Freeze solution from this blogpost should not apply, as the leak does not change based on whether I comment or uncomment my call to Freeze. This is further corroborated by this answer.
  • This answer, this answer, this answer, and all answers to this question point out that DeleteObject needs to be invoked once the handle obtained from GetHbitmap is no longer needed. This is also suggested by this blogpost. I am already doing that in the code.
  • Information from this question suggests I need to dispose of the Bitmap, but I am already disposing of any Bitmap instance that I create myself, thanks to the using block.
  • This forum thread sounds vaguely similar, but it ends in an inconclusive way.

回答1:

My instinct tells me that it is something to do with the bitmapSourceVideo being inside the delegate, and therefore creating a closure and not being cleaned up.

EDIT

Try

        Dispatcher.BeginInvoke(new ThreadStart(delegate
        {
            videoControl.Source = bitmapSourceVideo;
            bitmapSourceVideo = null;
        }));