2

I'm using MemoryStream to get JPEGBuffer and then decoding that to Bitmap using JpegBitmapDecoder. This is causing the memory usage to increase every time I start reading the Live Video from Nikon Camera.

DispatchTimer tick method is written below (Runs 30 times every second):

private void dispatcherTimer_Tick(object sender, EventArgs e) {
 if (isLiveVideoEnabled) {
  try {
   Nikon.NikonLiveViewImage liveImageFromCamera = ((MainWindow) myParent).currentDevice.GetLiveViewImage();

   using(var liveImageStream = new MemoryStream(liveImageFromCamera.JpegBuffer)) 
   {
    liveImageComponent.Source = BitmapFrame.Create(liveImageStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);

    liveLoop.Set();
   }
  } catch (Nikon.NikonException ex) {
   MessageBox.Show(ex.Message);
  }
 }
}

This is the Code when user presses Capture Button. I'm stopping the Live View and then capturing the Photo from Nikon.

ImageBehavior.AddAnimationCompletedHandler(imageControl,  async (sender , e) => {
          // Disabling Live Stream
          await liveLoop.WaitAsync();
          isLiveVideoEnabled = false;
          (myParent as MainWindow).currentDevice.LiveViewEnabled = false;

          //Starting to Capture Photo
          await (myParent as MainWindow).capturePhoto();
          await (myParent as MainWindow).waitForTheImage();
          string path = (myParent as MainWindow).getPhotoPath();
          Console.WriteLine("********************     " + path + "      *********************");
          System.Windows.Media.Imaging.BitmapImage bImage = new BitmapImage(new Uri(path));
          ImageBehavior.SetAnimatedSource(imageControl, null);
          (sender as Image).Source = bImage;
          Photos.imageDictionary[imageNumber] = path;
          //Enabling the Live Stream

          isLiveVideoEnabled = true;
          currentImageControl = imageControl;
          stopLoading();
          showRetry();
          (myParent as MainWindow).currentDevice.LiveViewEnabled = true;
     });

This Application is in Continuous Operation.

  1. Touch Screen to Capture Photos
  2. Live view starts, user clicks on GIF which is counter
  3. As soon as GIF animation cycle is complete it stops the live view, and captures photo.
  4. User move to feedback section and one Cycle of operation completes here.

When I start my Application it starts with initial Memory 67MB. First Cycle of Operation increases the memory to 185MB, and almost 130MB is added every time Live View Page is started.

First i thought the problem is regarding the WPF Pages but I closely checked Memory Usage, It increases only when we start live camera. Switching on pages doesn't increase any memory.

I think I'm using the wrong approach for MemoryStream. Please guide.

UPDATE 1 [ dispatcherTimer_Tick code is updated ]:

I implemented Clemens solution by introducing using(MemoryStream) inside the dispatcherTimer_Tick method. And Also froze the BitmapImage after BitmapImage.EndInit() But memory consumption is same and had no difference.

I started Profiler in VS and gathered the following information, At least Now I'm looking at the things which i should take care of.

Initial Image where i saw the byte[] stream is still connected and is not collected by GC.

First Profiler Image

After that I started looking further to get where it leads. Here is another image. (GetLiveViewImage is from nikoncswrapper)

enter image description here

And the last image which shows the Function Name:

enter image description here

Now i think i have more information to get to the problem. But I'm unable to understand what more i can do.

I even created a new Method to getBitmapImage:

public BitmapImage getBitmapFromURI(Uri uri) {
 var image = new BitmapImage();
 image.BeginInit();
 image.CacheOption = BitmapCacheOption.OnLoad;
 image.UriSource = uri;
 image.EndInit();
 image.Freeze();
 System.GC.Collect();
 GC.WaitForPendingFinalizers();
 return image;
}
Maher Shahmeer
  • 148
  • 1
  • 10
  • 1
    You never closed and disposed of your stream, you have choices but putting it in a using would fix most of it the simplest way – BugFinder Mar 28 '19 at 08:49
  • 1
    Two notes, `if (liveImageStream != null)` is pointless. You don't need to check the result of the `new` operator for null. But you must not use the `as` operator without checking its result for null, as you do in `(myParent as MainWindow).currentDevice.GetLiveViewImage()`. That should actually be `((MainWindow)myParent).currentDevice.GetLiveViewImage()`. – Clemens Mar 28 '19 at 09:03
  • While it is a good habit to dispose `IDisposables` when you no longer need them, for a `MemoryStream` this isn't necessary. It doesn't own any disposable resources and it won't release memory when disposing. But nevertheless it's a good habit because you might eventually swap the `MemoryStream` for a different one that does require disposing. – Dirk Mar 28 '19 at 09:05
  • `MemoryStream` is only a thin wrapper over a `byte[]` buffer. Even if you call `Dispose()` the buffer won't be freed until the GC runs. It's `BitmapImage` that needs disposing though. – Panagiotis Kanavos Mar 28 '19 at 09:23
  • Where is `liveImageStream` declared? If it's declared *outside* the `dispatcherTimer_Tick` the stream will *not* be garbage collected until it goes out of scope, eg when the form closes or the application terminates. If you *don't* want to use it outside that method, declare it right where you need it, eg `liveImageStream = new MemoryStream (liveImageFromCamera.JpegBuffer);`. This way you won't have to check if it exists and dispose it the *next* time you capture a picture – Panagiotis Kanavos Mar 28 '19 at 09:29
  • Instead of guessing use a memory profiler or Visual Studio's [Diagnostics Tools ](https://devblogs.microsoft.com/devops/diagnostic-tools-debugger-window-in-visual-studio-2015/) window to see what objects are actually created. You can take memory snapshots as your application runs and check what objects are created between snapshots. – Panagiotis Kanavos Mar 28 '19 at 09:36
  • You can improve your code a *lot* though by declaring variables inside the timer method instead of using fields. Otherwise all objects remain in memory until the *next* time the timer fires. This way you won't have to check for null or call Dispose at the start. You should also wrap *all* disposable objects in `using()` blocks. – Panagiotis Kanavos Mar 28 '19 at 09:40
  • @PanagiotisKanavos I tried that approach. Actually when i introduced `using` with `MemoryStream` it froze the frames i was getting before. So here i think Clemens Solution will work most probably. Your point was right as well. – Maher Shahmeer Mar 28 '19 at 10:56
  • Did you check if `Nikon.NikonLiveViewImage` is perhaps an IDisposable? – Clemens Mar 29 '19 at 18:29
  • @Clemens No it is not. Just have JpegHeader and HeaderBuffer as properties. – Maher Shahmeer Mar 29 '19 at 18:30
  • As already suggested, you should use a memory profiler. – Clemens Mar 29 '19 at 18:33
  • I'll update again, when i use Memory Profiler. I'm going for Ants. Thanks a million for support. @Clemens – Maher Shahmeer Mar 29 '19 at 18:35
  • @Clemens , Out of 570 MB, Ants Memory Profiler shows that 480 MB is unmanaged and i can't explore it. Why is that ? – Maher Shahmeer Mar 29 '19 at 18:59
  • I don't know, sorry. – Clemens Mar 29 '19 at 19:04

1 Answers1

1

With BitmapCacheOption.None the BitmapDecoder or BitmapFrame that is decoded from the stream keeps the stream open.

Since MemoryStream is an IDisposable, it should be disposed of, preferably by creating it inside a using declaration. However, that is only possible if the image is decoded immediately by setting BitmapCacheOption.OnLoad.

You also don't need to explicitly use a specific BitmapDecoder. Just use the BitmapFrame.Create method. It automatically selects the appropriate decoder.

using (var liveImageStream = new MemoryStream(liveImageFromCamera.JpegBuffer))
{
    liveImageComponent.Source = BitmapFrame.Create(
        liveImageStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
    ...
}

If there is still an issue, you may take a look at this answer: https://stackoverflow.com/a/6271982/1136211 (although afaik an immediately decoded BitmapFrame should already be frozen).

Clemens
  • 123,504
  • 12
  • 155
  • 268
  • 1
    MemoryStream's [Dispose()](https://referencesource.microsoft.com/#mscorlib/system/io/memorystream.cs,147) doesn't dispose anything. MemoryStream is just a thin wrapper over a `byte[]` buffer that will be GC'd the same way all managed *large* arrays are. On the other hand, `new BitmapImage()` *does* create an object that needs disposing – Panagiotis Kanavos Mar 28 '19 at 09:21
  • this seems to be the closest answer i was looking for. I'll update as soon as i implement this and do the memory analysis. Thanks @Clemens – Maher Shahmeer Mar 28 '19 at 09:28
  • @Clemens check the link to the actual source. It's not `BitmapFrame` that keeps the stream open though. It's defined *outside* the timer. It will be kept alive whether BitmapFrame is used or not. – Panagiotis Kanavos Mar 28 '19 at 09:30
  • @PanagiotisKanavos The point is that the BitmapDecoder keeps the stream open if it's not closed explicitly. BitmapImage however, is not an IDisposable. Despite that it isn't even used here. – Clemens Mar 28 '19 at 09:32
  • @user3057437 You may also try to force garbage collection by calling `GC.Collect()` – Clemens Mar 28 '19 at 09:35
  • @Clemens can you look at the update i added to the Question ? – Maher Shahmeer Mar 29 '19 at 18:23