[go: nahoru, domu]

Skip to content

Commit

Permalink
[camera]fix a sample buffer memory leak on pause resume recording (#5927
Browse files Browse the repository at this point in the history
)

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/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].*
  • Loading branch information
hellohuanlin committed Jan 20, 2024
1 parent 44ee590 commit 7b07d18
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 11 deletions.
5 changes: 3 additions & 2 deletions packages/camera/camera_avfoundation/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## NEXT
## 0.9.13+11

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

## 0.9.13+10
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,41 @@ - (void)testCopyPixelBuffer {
CFRelease(deliveriedPixelBuffer);
}

- (void)testDidOutputSampleBuffer_mustNotChangeSampleBufferRetainCountAfterPauseResumeRecording {
FLTCam *cam = FLTCreateCamWithCaptureSessionQueue(dispatch_queue_create("test", NULL));
CMSampleBufferRef sampleBuffer = FLTCreateTestSampleBuffer();

id writerMock = OCMClassMock([AVAssetWriter class]);
OCMStub([writerMock alloc]).andReturn(writerMock);
OCMStub([writerMock initWithURL:OCMOCK_ANY fileType:OCMOCK_ANY error:[OCMArg setTo:nil]])
.andReturn(writerMock);
__block AVAssetWriterStatus status = AVAssetWriterStatusUnknown;
OCMStub([writerMock startWriting]).andDo(^(NSInvocation *invocation) {
status = AVAssetWriterStatusWriting;
});
OCMStub([writerMock status]).andDo(^(NSInvocation *invocation) {
[invocation setReturnValue:&status];
});

FLTThreadSafeFlutterResult *result =
[[FLTThreadSafeFlutterResult alloc] initWithResult:^(id result){
// no-op
}];

// Pause then resume the recording.
[cam startVideoRecordingWithResult:result];
[cam pauseVideoRecordingWithResult:result];
[cam resumeVideoRecordingWithResult:result];

[cam captureOutput:cam.captureVideoOutput
didOutputSampleBuffer:sampleBuffer
fromConnection:OCMClassMock([AVCaptureConnection class])];
XCTAssertEqual(CFGetRetainCount(sampleBuffer), 1,
@"didOutputSampleBuffer must not change the sample buffer retain count after "
@"pause resume recording.");
CFRelease(sampleBuffer);
}

- (void)testDidOutputSampleBufferIgnoreAudioSamplesBeforeVideoSamples {
FLTCam *cam = FLTCreateCamWithCaptureSessionQueue(dispatch_queue_create("testing", NULL));
CMSampleBufferRef videoSample = FLTCreateTestSampleBuffer();
Expand Down
15 changes: 7 additions & 8 deletions packages/camera/camera_avfoundation/ios/Classes/FLTCam.m
Original file line number Diff line number Diff line change
Expand Up @@ -514,7 +514,6 @@ - (void)captureOutput:(AVCaptureOutput *)output
return;
}

CFRetain(sampleBuffer);
CMTime currentSampleTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);

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

if (_audioTimeOffset.value != 0) {
CFRelease(sampleBuffer);
sampleBuffer = [self adjustTime:sampleBuffer by:_audioTimeOffset];
CMSampleBufferRef adjustedSampleBuffer =
[self copySampleBufferWithAdjustedTime:sampleBuffer by:_audioTimeOffset];
[self newAudioSample:adjustedSampleBuffer];
CFRelease(adjustedSampleBuffer);
} else {
[self newAudioSample:sampleBuffer];
}

[self newAudioSample:sampleBuffer];
}

CFRelease(sampleBuffer);
}
}

- (CMSampleBufferRef)adjustTime:(CMSampleBufferRef)sample by:(CMTime)offset CF_RETURNS_RETAINED {
- (CMSampleBufferRef)copySampleBufferWithAdjustedTime:(CMSampleBufferRef)sample by:(CMTime)offset {
CMItemCount count;
CMSampleBufferGetSampleTimingInfoArray(sample, 0, nil, &count);
CMSampleTimingInfo *pInfo = malloc(sizeof(CMSampleTimingInfo) * count);
Expand Down
2 changes: 1 addition & 1 deletion packages/camera/camera_avfoundation/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: camera_avfoundation
description: iOS implementation of the camera plugin.
repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_avfoundation
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
version: 0.9.13+10
version: 0.9.13+11

environment:
sdk: ^3.2.3
Expand Down

0 comments on commit 7b07d18

Please sign in to comment.