Repository: flutter
Issue: [camera] stopImageStream() and dispose() does not completely release memory
Labels: platform-ios,p: camera,package,perf: memory,has reproducible steps,P2,team-ios,triaged-ios,found in release: 3.22,found in release: 3.23

### Steps to reproduce

We were starting to experience crashes due to memory in our production app. To isolate the issue, a test app has been created to replicate the steps in our production app:

1. Initialize a CameraController:  `await cameraController.initialize()`
2. Start an image stream:` await cameraController.startImageStream()`
3. Stop the image stream: `await cameraController.stopImageStream()`
4. Dispose the CameraController: `await cameraController.dispose()`

Starting up the app and memory usage as measured in Xcode is approx 70MB. When initializing the `CameraController` in step 1, the memory usage increases approx 80MB to 150MB. When starting the image stream in step 2 it further increases another 150MB with spikes of 200MB to a total of 300-350MB. 

As the image stream is stopped the expectation is that the memory usage would return to the level before it was started but it does not: After stopping the stream the memory usage is 180MB. 

Further, after disposing the `CameraController` in step 4 the expectation is that the memory usage would return to the same level as before first initialization, but it does not: After disposing the `CameraController` the memory usage is 140MB.

So by initializing a `CameraController` and starting/stopping the image stream and finally disposing the `CameraController`, the memory usage has increased by 70MB. 

We also see spikes in the memory usage, sometimes large especially in the beginning of each image stream cycle.

![image](https://github.com/flutter/flutter/assets/19726458/b5966c63-ce91-4edb-8efa-2cd6272c517b)

We are aware of the recommendation for a low `ResolutionPreset` for performance reasons, but we are not experiencing any issues using `ResolutionPreset.max` except for this memory use growth that occurs with all presets (albeit with lower numbers when using lower resolution)


### Code sample

<details open><summary>Code sample</summary>

```dart
import 'package:flutter/material.dart';
import 'package:camera/camera.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  late CameraController cameraController;
  bool _cameraInitialized = false;
  bool _streamImages = false;

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
  }

  Future<void> initCamera() async {
    final cameras = await availableCameras();
    final firstCamera = cameras.first;
    cameraController =
        CameraController(firstCamera, ResolutionPreset.max);
    await cameraController.initialize().then((_) {
      if (!mounted) {
        return;
      }
      setState(() {
        _cameraInitialized = true;
      });
    });
  }

  Future<void> _toggleStream() async {
    if (cameraController.value.isStreamingImages) {
      await cameraController.stopImageStream();
    } else {
      if (!cameraController.value.isStreamingImages) {
        await cameraController.startImageStream((image) {
          // Process the image here
          // Right now we are doing nothing just to show the example
        });
      }
    }
    setState(() {
      _streamImages = !_streamImages;
    });
  }

  Future<void> _disposeCamera() async {
    await cameraController.dispose();
    setState(() {
      _cameraInitialized = false;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          backgroundColor: Theme.of(context).colorScheme.inversePrimary,
          title: Text(widget.title),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              ElevatedButton(
                  onPressed: () =>
                      _cameraInitialized ? _disposeCamera() : initCamera(),
                  child: Text(
                      _cameraInitialized ? "Dispose Camera" : "Init Camera")),
              _cameraInitialized
                  ? ElevatedButton(
                      onPressed: () => _toggleStream(),
                      child:
                          Text(_streamImages ? "Stop stream" : "Start stream"))
                  : const CircularProgressIndicator(),
            ],
          ),
        ));
  }

  @override
  void dispose() {
    cameraController.dispose();
    super.dispose();
  }
}

```

</details>


### What target platforms are you seeing this bug on?

iOS

### OS/Browser name and version | Device information

* iPhone SE 2nd Gen
* iOS 17.5.1

### Does the problem occur on emulator/simulator as well as on physical devices?

No

### Logs

<details open><summary>Logs</summary>

```console
[Paste your logs here]
```

</details>


### Flutter Doctor output

<details open><summary>Doctor output</summary>

```console
[✓] Flutter (Channel stable, 3.19.1, on macOS 14.1.2 23B92 darwin-arm64, locale en-SE)
    • Flutter version 3.19.1 on channel stable at /opt/homebrew/Caskroom/flutter/2.10.0/flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision abb292a07e (4 months ago), 2024-02-20 14:35:05 -0800
    • Engine revision 04817c99c9
    • Dart version 3.3.0
    • DevTools version 2.31.1

[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0-rc2)
    • Android SDK at /Users/niklaselelid/Library/Android/sdk
    • Platform android-34, build-tools 34.0.0-rc2
    • Java binary at: /Applications/Android Studio.app/Contents/jre/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 11.0.10+0-b96-7281165)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 15.4)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Build 15F31d
    • CocoaPods version 1.15.2

[✓] Android Studio (version 2020.3)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build 11.0.10+0-b96-7281165)

[✓] VS Code (version 1.90.0)
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension version 3.90.0

[✓] Connected device (2 available)
    • iPhone SE (2nd generation) (mobile)  • ios          • iOS 17.5.1 21F90
    • macOS (desktop)                     • macos                     • darwin-arm64 • macOS 14.1.2 23B92
      darwin-arm64
   
[✓] Network resources
    • All expected network resources are available.

• No issues found!
```

</details>