Skip to content

Commit 7b07d18

Browse files
authored
[camera]fix a sample buffer memory leak on pause resume recording (flutter#5927)
This memory leak happens when pause/resume the recording. Depending on camera resolution, about 1-2MB leaks for every resume, so it can add up pretty fast if we frequently pause/resume. We only reference this sample buffer inside the scope of this method (e.g. directly piping into file IO), so no need to retain it again, as it's already retained and will be released afterwards by apple. (We did keep around the pixel buffer, but we already manually [retain](https://github.com/flutter/packages/blob/d7ee75ad59ad7bc45e659d0599e935e9e7981ea1/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m#L403) and [release](https://github.com/flutter/packages/blob/d7ee75ad59ad7bc45e659d0599e935e9e7981ea1/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m#L415) that) Bug was introduced in flutter/plugins#1370. *List which issues are fixed by this PR. You must list at least one issue.* flutter#132013 *If you had to change anything in the [flutter/tests] repo, include a link to the migration guide as per the [breaking change policy].*
1 parent 44ee590 commit 7b07d18

File tree

4 files changed

+46
-11
lines changed

4 files changed

+46
-11
lines changed

packages/camera/camera_avfoundation/CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
## NEXT
1+
## 0.9.13+11
22

3-
* Remove development team from example app.
3+
* Fixes a memory leak of sample buffer when pause and resume the video recording.
4+
* Removes development team from example app.
45
* Updates minimum iOS version to 12.0 and minimum Flutter version to 3.16.6.
56

67
## 0.9.13+10

packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamSampleBufferTests.m

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,41 @@ - (void)testCopyPixelBuffer {
3838
CFRelease(deliveriedPixelBuffer);
3939
}
4040

41+
- (void)testDidOutputSampleBuffer_mustNotChangeSampleBufferRetainCountAfterPauseResumeRecording {
42+
FLTCam *cam = FLTCreateCamWithCaptureSessionQueue(dispatch_queue_create("test", NULL));
43+
CMSampleBufferRef sampleBuffer = FLTCreateTestSampleBuffer();
44+
45+
id writerMock = OCMClassMock([AVAssetWriter class]);
46+
OCMStub([writerMock alloc]).andReturn(writerMock);
47+
OCMStub([writerMock initWithURL:OCMOCK_ANY fileType:OCMOCK_ANY error:[OCMArg setTo:nil]])
48+
.andReturn(writerMock);
49+
__block AVAssetWriterStatus status = AVAssetWriterStatusUnknown;
50+
OCMStub([writerMock startWriting]).andDo(^(NSInvocation *invocation) {
51+
status = AVAssetWriterStatusWriting;
52+
});
53+
OCMStub([writerMock status]).andDo(^(NSInvocation *invocation) {
54+
[invocation setReturnValue:&status];
55+
});
56+
57+
FLTThreadSafeFlutterResult *result =
58+
[[FLTThreadSafeFlutterResult alloc] initWithResult:^(id result){
59+
// no-op
60+
}];
61+
62+
// Pause then resume the recording.
63+
[cam startVideoRecordingWithResult:result];
64+
[cam pauseVideoRecordingWithResult:result];
65+
[cam resumeVideoRecordingWithResult:result];
66+
67+
[cam captureOutput:cam.captureVideoOutput
68+
didOutputSampleBuffer:sampleBuffer
69+
fromConnection:OCMClassMock([AVCaptureConnection class])];
70+
XCTAssertEqual(CFGetRetainCount(sampleBuffer), 1,
71+
@"didOutputSampleBuffer must not change the sample buffer retain count after "
72+
@"pause resume recording.");
73+
CFRelease(sampleBuffer);
74+
}
75+
4176
- (void)testDidOutputSampleBufferIgnoreAudioSamplesBeforeVideoSamples {
4277
FLTCam *cam = FLTCreateCamWithCaptureSessionQueue(dispatch_queue_create("testing", NULL));
4378
CMSampleBufferRef videoSample = FLTCreateTestSampleBuffer();

packages/camera/camera_avfoundation/ios/Classes/FLTCam.m

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -514,7 +514,6 @@ - (void)captureOutput:(AVCaptureOutput *)output
514514
return;
515515
}
516516

517-
CFRetain(sampleBuffer);
518517
CMTime currentSampleTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
519518

520519
if (_videoWriter.status != AVAssetWriterStatusWriting) {
@@ -564,18 +563,18 @@ - (void)captureOutput:(AVCaptureOutput *)output
564563
_lastAudioSampleTime = currentSampleTime;
565564

566565
if (_audioTimeOffset.value != 0) {
567-
CFRelease(sampleBuffer);
568-
sampleBuffer = [self adjustTime:sampleBuffer by:_audioTimeOffset];
566+
CMSampleBufferRef adjustedSampleBuffer =
567+
[self copySampleBufferWithAdjustedTime:sampleBuffer by:_audioTimeOffset];
568+
[self newAudioSample:adjustedSampleBuffer];
569+
CFRelease(adjustedSampleBuffer);
570+
} else {
571+
[self newAudioSample:sampleBuffer];
569572
}
570-
571-
[self newAudioSample:sampleBuffer];
572573
}
573-
574-
CFRelease(sampleBuffer);
575574
}
576575
}
577576

578-
- (CMSampleBufferRef)adjustTime:(CMSampleBufferRef)sample by:(CMTime)offset CF_RETURNS_RETAINED {
577+
- (CMSampleBufferRef)copySampleBufferWithAdjustedTime:(CMSampleBufferRef)sample by:(CMTime)offset {
579578
CMItemCount count;
580579
CMSampleBufferGetSampleTimingInfoArray(sample, 0, nil, &count);
581580
CMSampleTimingInfo *pInfo = malloc(sizeof(CMSampleTimingInfo) * count);

packages/camera/camera_avfoundation/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: camera_avfoundation
22
description: iOS implementation of the camera plugin.
33
repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_avfoundation
44
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
5-
version: 0.9.13+10
5+
version: 0.9.13+11
66

77
environment:
88
sdk: ^3.2.3

0 commit comments

Comments
 (0)