1
votes

I have a problem with releasing memory from CMSampleBuffer in my AVCaptureSession camera. This is my code for setting capture session. If I dispose imageDataSampleBuffer, app crashes.

using MonoTouch.CoreVideo;
using MonoTouch.CoreMedia;
using MonoTouch.AVFoundation;
using MonoTouch.ImageIO;
using MonoTouch.UIKit;
using MonoTouch.CoreFoundation;
using MonoTouch.Foundation;
using System.Drawing;
using System;
namespace myNamespace
{
public class AVFoundationCamera : UIViewController  
{
    public AVFoundationCamera (CameraController parView)
    {
        parentView = parView;
    }

    NSError error;
    AVCaptureSession session;
    AVCaptureDevice device;
    AVCaptureDeviceInput input;
    AVCaptureStillImageOutput output;
    AVCaptureVideoPreviewLayer captureVideoPreviewLayer;
    NSDictionary outputSettings;


    AVCaptureConnection captureConnection;

    UIButton buttCaptureImage;

    public UIImageView imageV;
    NSData imageData;

    CameraController parentView;

    public override void ViewDidAppear (bool animated)
    {
        base.ViewDidAppear (animated);
        CreateControls();
        SetupSession();

    }

    public override void DidReceiveMemoryWarning ()
    {
        imageData.Dispose();
        session.Dispose();
        device.Dispose();
        input.Dispose();
        output.Dispose();
        captureVideoPreviewLayer.Dispose();
        base.DidReceiveMemoryWarning ();
    }

    private void CreateControls()
    {
        imageV = new UIImageView(new RectangleF(0, 0, UIScreen.MainScreen.ApplicationFrame.Width, UIScreen.MainScreen.ApplicationFrame.Height - UIApplication.SharedApplication.StatusBarFrame.Height));
        View.AddSubview(imageV);

        buttCaptureImage = UIButton.FromType(UIButtonType.RoundedRect);
        buttCaptureImage.Frame = new RectangleF(0, 60, 150, 30);
        buttCaptureImage.SetTitle("Take a photo", UIControlState.Normal);
        buttCaptureImage.TouchUpInside += HandleButtCaptureImageTouchUpInside;

        View.AddSubview(buttCaptureImage);
    }

    void HandleButtCaptureImageTouchUpInside (object sender, EventArgs e)
    {
        captureConnection = null;

        foreach (AVCaptureConnection capConn in output.Connections)
        {
            foreach (AVCaptureInputPort port in capConn.inputPorts)
            {
                if (port.MediaType == AVMediaType.Video)
                {
                    captureConnection = capConn;
                    break;
                }
            }
            if (captureConnection != null)
                break;
        }

        output.CaptureStillImageAsynchronously(captureConnection, HandleAVCaptureCompletionHandlercompletionHandler);
        buttCaptureImage.Enabled = false;
    }

    void HandleAVCaptureCompletionHandlercompletionHandler (CMSampleBuffer imageDataSampleBuffer, NSError error)
    {
        try
        {
            using (var pool = new NSAutoreleasePool ()) {
                imageData = AVCaptureStillImageOutput.JpegStillToNSData(imageDataSampleBuffer);
                //imageDataSampleBuffer.Dispose();
                parentView.DismissModalViewControllerAnimated(true);
                parentView.HandlePickedImage(imageData);
                session.StopRunning();
            }
        }
        catch (Exception exc)
        {
            Console.WriteLine(exc);
        }
    }  

    private void SetupSession()
    {

        session = new AVCaptureSession();
        session.BeginConfiguration();
        session.SessionPreset = AVCaptureSession.PresetPhoto;

        captureVideoPreviewLayer = new AVCaptureVideoPreviewLayer(session);
        captureVideoPreviewLayer.Frame = imageV.Bounds;

        imageV.Layer.AddSublayer(captureVideoPreviewLayer);

        device = AVCaptureDevice.DefaultDeviceWithMediaType(AVMediaType.Video);

        input = new AVCaptureDeviceInput(device, out error);

        session.AddInput(input);

        output = new AVCaptureStillImageOutput();
        output.OutputSettings = NSDictionary.FromObjectAndKey(new NSString("AVVideoCodecKey"), new NSString("AVVideoCodecJPEG"));

        session.AddOutput(output);

        session.CommitConfiguration();
        session.StartRunning();
    }
}

}

This is just a regular camera for taking photos. I tried with UIImagePickerController you posted here: https://github.com/migueldeicaza/TweetStation/blob/master/TweetStation/UI/Camera.cs which eliminates UIImagePickerController bug but whenever I click "Take a photo" button, preview window shows up which allocates memory. If I press "Retake", memory is being released, but in FinishedPiCkingMedia event handler I can't release it. So, after few photos it crashes.

Any solution works for me, but it would be great to see what I'm doing wrong.

Thank you once again.

1
Please post the complete sample code - miguel.de.icaza
attach it to a bug report at bugzilla.xamarin.com and give us a link back to this question. thanks! - poupou

1 Answers

1
votes

This was a bug in MonoTouch.

There is a workaround you can use until you get the fix:

[DllImport ("/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation")]
extern static void CFRetain (IntPtr handle);

void HandleAVCaptureCompletionHandlercompletionHandler (CMSampleBuffer imageDataSampleBuffer, NSError error)
{
    try {
        CFRetain (imageDataSampleBuffer.Handle);
        (...)
    } finally {
        imageDataSampleBuffer.Dispose ();
    }
}

I've added a Dispose call, there might be a limited amount of buffers available, and this way you ensure that the app doesn't exhaust them (since it may take a little time before the GC would free it automatically)

Also note that you should remove the workaround once you install a MonoTouch version with the real fix, since you'll leak the buffers otherwise.