Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit 5bb5051

Browse files
committed
[camera]remove cas operation and use dispatch queue instead
1 parent 02b5b3f commit 5bb5051

File tree

9 files changed

+134
-48
lines changed

9 files changed

+134
-48
lines changed

packages/camera/camera/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.9.4+15
2+
3+
* Uses dispatch queue for pixel buffer synchronization on iOS.
4+
15
## 0.9.4+14
26

37
* Restores compatibility with Flutter 2.5 and 2.8.

packages/camera/camera/example/ios/Runner.xcodeproj/project.pbxproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
E0C6E2002770F01A00EA6AA3 /* ThreadSafeMethodChannelTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E0C6E1FD2770F01A00EA6AA3 /* ThreadSafeMethodChannelTests.m */; };
2929
E0C6E2012770F01A00EA6AA3 /* ThreadSafeTextureRegistryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E0C6E1FE2770F01A00EA6AA3 /* ThreadSafeTextureRegistryTests.m */; };
3030
E0C6E2022770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E0C6E1FF2770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m */; };
31+
E0CDBAC227CD9729002561D9 /* CameraTestUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = E0CDBAC127CD9729002561D9 /* CameraTestUtils.m */; };
3132
E0F95E3D27A32AB900699390 /* CameraPropertiesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E0F95E3C27A32AB900699390 /* CameraPropertiesTests.m */; };
3233
E487C86026D686A10034AC92 /* CameraPreviewPauseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E487C85F26D686A10034AC92 /* CameraPreviewPauseTests.m */; };
3334
F6EE622F2710A6FC00905E4A /* MockFLTThreadSafeFlutterResult.m in Sources */ = {isa = PBXBuildFile; fileRef = F6EE622E2710A6FC00905E4A /* MockFLTThreadSafeFlutterResult.m */; };
@@ -91,6 +92,8 @@
9192
E0C6E1FD2770F01A00EA6AA3 /* ThreadSafeMethodChannelTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ThreadSafeMethodChannelTests.m; sourceTree = "<group>"; };
9293
E0C6E1FE2770F01A00EA6AA3 /* ThreadSafeTextureRegistryTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ThreadSafeTextureRegistryTests.m; sourceTree = "<group>"; };
9394
E0C6E1FF2770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ThreadSafeEventChannelTests.m; sourceTree = "<group>"; };
95+
E0CDBAC027CD9729002561D9 /* CameraTestUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CameraTestUtils.h; sourceTree = "<group>"; };
96+
E0CDBAC127CD9729002561D9 /* CameraTestUtils.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CameraTestUtils.m; sourceTree = "<group>"; };
9497
E0F95E3C27A32AB900699390 /* CameraPropertiesTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CameraPropertiesTests.m; sourceTree = "<group>"; };
9598
E487C85F26D686A10034AC92 /* CameraPreviewPauseTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CameraPreviewPauseTests.m; sourceTree = "<group>"; };
9699
F63F9EED27143B19002479BF /* MockFLTThreadSafeFlutterResult.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockFLTThreadSafeFlutterResult.h; sourceTree = "<group>"; };
@@ -131,6 +134,8 @@
131134
E04F108527A87CA600573D0C /* FLTSavePhotoDelegateTests.m */,
132135
E071CF7127B3061B006EF3BA /* FLTCamPhotoCaptureTests.m */,
133136
E071CF7327B31DE4006EF3BA /* FLTCamSampleBufferTests.m */,
137+
E0CDBAC027CD9729002561D9 /* CameraTestUtils.h */,
138+
E0CDBAC127CD9729002561D9 /* CameraTestUtils.m */,
134139
E01EE4A72799F3A5008C1950 /* QueueHelperTests.m */,
135140
E487C85F26D686A10034AC92 /* CameraPreviewPauseTests.m */,
136141
F6EE622E2710A6FC00905E4A /* MockFLTThreadSafeFlutterResult.m */,
@@ -408,6 +413,7 @@
408413
E071CF7427B31DE4006EF3BA /* FLTCamSampleBufferTests.m in Sources */,
409414
E04F108627A87CA600573D0C /* FLTSavePhotoDelegateTests.m in Sources */,
410415
F6EE622F2710A6FC00905E4A /* MockFLTThreadSafeFlutterResult.m in Sources */,
416+
E0CDBAC227CD9729002561D9 /* CameraTestUtils.m in Sources */,
411417
334733EA2668111C00DCC49E /* CameraOrientationTests.m in Sources */,
412418
E032F250279F5E94009E9028 /* CameraCaptureSessionQueueRaceConditionTests.m in Sources */,
413419
E0C6E2022770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m in Sources */,
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
@import camera;
6+
7+
NS_ASSUME_NONNULL_BEGIN
8+
9+
@interface CameraTestUtils : NSObject
10+
11+
/// Creates an `FLTCam` that runs its capture session operations on a given queue.
12+
/// @param captureSessionQueue the capture session queue
13+
/// @return an FLTCam object.
14+
+ (FLTCam *)createFLTCamWithCaptureSessionQueue:(dispatch_queue_t)captureSessionQueue;
15+
16+
/// Creates a test sample buffer.
17+
/// @return a test sample buffer.
18+
+ (CMSampleBufferRef)createTestSampleBuffer;
19+
20+
@end
21+
22+
NS_ASSUME_NONNULL_END
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#import "CameraTestUtils.h"
6+
#import <OCMock/OCMock.h>
7+
@import AVFoundation;
8+
9+
@implementation CameraTestUtils
10+
11+
+ (FLTCam *)createFLTCamWithCaptureSessionQueue:(dispatch_queue_t)captureSessionQueue {
12+
id inputMock = OCMClassMock([AVCaptureDeviceInput class]);
13+
OCMStub([inputMock deviceInputWithDevice:[OCMArg any] error:[OCMArg setTo:nil]])
14+
.andReturn(inputMock);
15+
16+
id sessionMock = OCMClassMock([AVCaptureSession class]);
17+
OCMStub([sessionMock alloc]).andReturn(sessionMock);
18+
OCMStub([sessionMock addInputWithNoConnections:[OCMArg any]]); // no-op
19+
OCMStub([sessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES);
20+
21+
return [[FLTCam alloc] initWithCameraName:@"camera"
22+
resolutionPreset:@"medium"
23+
enableAudio:true
24+
orientation:UIDeviceOrientationPortrait
25+
captureSessionQueue:captureSessionQueue
26+
error:nil];
27+
}
28+
29+
+ (CMSampleBufferRef)createTestSampleBuffer {
30+
CVPixelBufferRef pixelBuffer;
31+
CVPixelBufferCreate(kCFAllocatorDefault, 100, 100, kCVPixelFormatType_32BGRA, NULL, &pixelBuffer);
32+
33+
CMFormatDescriptionRef formatDescription;
34+
CMVideoFormatDescriptionCreateForImageBuffer(kCFAllocatorDefault, pixelBuffer,
35+
&formatDescription);
36+
37+
CMSampleTimingInfo timingInfo = {CMTimeMake(1, 44100), kCMTimeZero, kCMTimeInvalid};
38+
39+
CMSampleBufferRef sampleBuffer;
40+
CMSampleBufferCreateReadyWithImageBuffer(kCFAllocatorDefault, pixelBuffer, formatDescription,
41+
&timingInfo, &sampleBuffer);
42+
43+
CFRelease(pixelBuffer);
44+
CFRelease(formatDescription);
45+
return sampleBuffer;
46+
}
47+
48+
@end

packages/camera/camera/example/ios/RunnerTests/FLTCamPhotoCaptureTests.m

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
@import AVFoundation;
88
@import XCTest;
99
#import <OCMock/OCMock.h>
10+
#import "CameraTestUtils.h"
1011

12+
/// Includes test cases related to photo capture operations for FLTCam class.
1113
@interface FLTCamPhotoCaptureTests : XCTestCase
1214

1315
@end
@@ -22,7 +24,7 @@ - (void)testCaptureToFile_mustReportErrorToResultIfSavePhotoDelegateCompletionsW
2224
dispatch_queue_t captureSessionQueue = dispatch_queue_create("capture_session_queue", NULL);
2325
dispatch_queue_set_specific(captureSessionQueue, FLTCaptureSessionQueueSpecific,
2426
(void *)FLTCaptureSessionQueueSpecific, NULL);
25-
FLTCam *cam = [self createFLTCamWithCaptureSessionQueue:captureSessionQueue];
27+
FLTCam *cam = [CameraTestUtils createFLTCamWithCaptureSessionQueue:captureSessionQueue];
2628
AVCapturePhotoSettings *settings = [AVCapturePhotoSettings photoSettings];
2729
id mockSettings = OCMClassMock([AVCapturePhotoSettings class]);
2830
OCMStub([mockSettings photoSettings]).andReturn(settings);
@@ -61,7 +63,7 @@ - (void)testCaptureToFile_mustReportPathToResultIfSavePhotoDelegateCompletionsWi
6163
dispatch_queue_t captureSessionQueue = dispatch_queue_create("capture_session_queue", NULL);
6264
dispatch_queue_set_specific(captureSessionQueue, FLTCaptureSessionQueueSpecific,
6365
(void *)FLTCaptureSessionQueueSpecific, NULL);
64-
FLTCam *cam = [self createFLTCamWithCaptureSessionQueue:captureSessionQueue];
66+
FLTCam *cam = [CameraTestUtils createFLTCamWithCaptureSessionQueue:captureSessionQueue];
6567

6668
AVCapturePhotoSettings *settings = [AVCapturePhotoSettings photoSettings];
6769
id mockSettings = OCMClassMock([AVCapturePhotoSettings class]);
@@ -92,23 +94,4 @@ - (void)testCaptureToFile_mustReportPathToResultIfSavePhotoDelegateCompletionsWi
9294
[self waitForExpectationsWithTimeout:1 handler:nil];
9395
}
9496

95-
/// Creates an `FLTCam` that runs its operations on a given capture session queue.
96-
- (FLTCam *)createFLTCamWithCaptureSessionQueue:(dispatch_queue_t)captureSessionQueue {
97-
id inputMock = OCMClassMock([AVCaptureDeviceInput class]);
98-
OCMStub([inputMock deviceInputWithDevice:[OCMArg any] error:[OCMArg setTo:nil]])
99-
.andReturn(inputMock);
100-
101-
id sessionMock = OCMClassMock([AVCaptureSession class]);
102-
OCMStub([sessionMock alloc]).andReturn(sessionMock);
103-
OCMStub([sessionMock addInputWithNoConnections:[OCMArg any]]); // no-op
104-
OCMStub([sessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES);
105-
106-
return [[FLTCam alloc] initWithCameraName:@"camera"
107-
resolutionPreset:@"medium"
108-
enableAudio:true
109-
orientation:UIDeviceOrientationPortrait
110-
captureSessionQueue:captureSessionQueue
111-
error:nil];
112-
}
113-
11497
@end

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

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,31 +7,35 @@
77
@import AVFoundation;
88
@import XCTest;
99
#import <OCMock/OCMock.h>
10+
#import "CameraTestUtils.h"
1011

12+
/// Includes test cases related to sample buffer handling for FLTCam class.
1113
@interface FLTCamSampleBufferTests : XCTestCase
1214

1315
@end
1416

1517
@implementation FLTCamSampleBufferTests
1618

1719
- (void)testSampleBufferCallbackQueueMustBeCaptureSessionQueue {
18-
id inputMock = OCMClassMock([AVCaptureDeviceInput class]);
19-
OCMStub([inputMock deviceInputWithDevice:[OCMArg any] error:[OCMArg setTo:nil]])
20-
.andReturn(inputMock);
21-
22-
id sessionMock = OCMClassMock([AVCaptureSession class]);
23-
OCMStub([sessionMock alloc]).andReturn(sessionMock);
24-
OCMStub([sessionMock addInputWithNoConnections:[OCMArg any]]); // no-op
25-
OCMStub([sessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES);
26-
2720
dispatch_queue_t captureSessionQueue = dispatch_queue_create("testing", NULL);
28-
FLTCam *cam = [[FLTCam alloc] initWithCameraName:@"camera"
29-
resolutionPreset:@"medium"
30-
enableAudio:true
31-
orientation:UIDeviceOrientationPortrait
32-
captureSessionQueue:captureSessionQueue
33-
error:nil];
34-
XCTAssertEqual(captureSessionQueue, cam.captureVideoOutput.sampleBufferCallbackQueue);
21+
FLTCam *cam = [CameraTestUtils createFLTCamWithCaptureSessionQueue:captureSessionQueue];
22+
XCTAssertEqual(captureSessionQueue, cam.captureVideoOutput.sampleBufferCallbackQueue,
23+
@"Sample buffer callback queue must be the capture session queue.");
24+
}
25+
26+
- (void)testCopyPixelBuffer {
27+
FLTCam *cam =
28+
[CameraTestUtils createFLTCamWithCaptureSessionQueue:dispatch_queue_create("test", NULL)];
29+
CMSampleBufferRef capturedSampleBuffer = [CameraTestUtils createTestSampleBuffer];
30+
CVPixelBufferRef capturedPixelBuffer = CMSampleBufferGetImageBuffer(capturedSampleBuffer);
31+
// Mimic sample buffer callback when captured a new video sample
32+
[cam captureOutput:cam.captureVideoOutput
33+
didOutputSampleBuffer:capturedSampleBuffer
34+
fromConnection:OCMClassMock([AVCaptureConnection class])];
35+
CVPixelBufferRef deliveriedPixelBuffer = [cam copyPixelBuffer];
36+
XCTAssertEqual(deliveriedPixelBuffer, capturedPixelBuffer,
37+
@"FLTCam must deliver the latest captured pixel buffer to copyPixelBuffer API.");
38+
CFRelease(capturedSampleBuffer);
3539
}
3640

3741
@end

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

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@ @interface FLTCam () <AVCaptureVideoDataOutputSampleBufferDelegate,
5252
@property(readonly, nonatomic) AVCaptureSession *captureSession;
5353

5454
@property(readonly, nonatomic) AVCaptureInput *captureVideoInput;
55-
@property(readonly) CVPixelBufferRef volatile latestPixelBuffer;
55+
/// Tracks the latest pixel buffer sent from AVFoundation's sample buffer delegate callback.
56+
/// Used to deliver the latest pixel buffer to the flutter engine via the `copyPixelBuffer` API.
57+
@property(readwrite, nonatomic) CVPixelBufferRef latestPixelBuffer;
5658
@property(readonly, nonatomic) CGSize captureSize;
5759
@property(strong, nonatomic) AVAssetWriter *videoWriter;
5860
@property(strong, nonatomic) AVAssetWriterInput *videoWriterInput;
@@ -76,6 +78,9 @@ @interface FLTCam () <AVCaptureVideoDataOutputSampleBufferDelegate,
7678
@property AVAssetWriterInputPixelBufferAdaptor *videoAdaptor;
7779
/// All FLTCam's state access and capture session related operations should be on run on this queue.
7880
@property(strong, nonatomic) dispatch_queue_t captureSessionQueue;
81+
/// The queue on which `latestPixelBuffer` property is accessed.
82+
/// To avoid unnecessary contention, we should not reuse the captureSessionQueue.
83+
@property(strong, nonatomic) dispatch_queue_t pixelBufferSynchronizationQueue;
7984
/// The queue on which captured photos (not videos) are written to disk.
8085
/// Videos are written to disk by `videoAdaptor` on an internal queue managed by AVFoundation.
8186
@property(strong, nonatomic) dispatch_queue_t photoIOQueue;
@@ -101,6 +106,8 @@ - (instancetype)initWithCameraName:(NSString *)cameraName
101106
}
102107
_enableAudio = enableAudio;
103108
_captureSessionQueue = captureSessionQueue;
109+
_pixelBufferSynchronizationQueue =
110+
dispatch_queue_create("io.flutter.camera.pixelBufferSynchronizationQueue", NULL);
104111
_photoIOQueue = dispatch_queue_create("io.flutter.camera.photoIOQueue", NULL);
105112
_captureSession = [[AVCaptureSession alloc] init];
106113
_captureDevice = [AVCaptureDevice deviceWithUniqueID:cameraName];
@@ -355,10 +362,15 @@ - (void)captureOutput:(AVCaptureOutput *)output
355362
if (output == _captureVideoOutput) {
356363
CVPixelBufferRef newBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
357364
CFRetain(newBuffer);
358-
CVPixelBufferRef old = _latestPixelBuffer;
359-
while (!OSAtomicCompareAndSwapPtrBarrier(old, newBuffer, (void **)&_latestPixelBuffer)) {
360-
old = _latestPixelBuffer;
361-
}
365+
366+
__block CVPixelBufferRef old = nil;
367+
// Use `dispatch_sync` to avoid unnecessary context switch under common non-contest scenarios;
368+
// Under rare contest scenarios, it will not block for too long since the critical section is
369+
// quite lightweight.
370+
dispatch_sync(self.pixelBufferSynchronizationQueue, ^{
371+
old = self.latestPixelBuffer;
372+
self.latestPixelBuffer = newBuffer;
373+
});
362374
if (old != nil) {
363375
CFRelease(old);
364376
}
@@ -575,11 +587,12 @@ - (void)dealloc {
575587
}
576588

577589
- (CVPixelBufferRef)copyPixelBuffer {
578-
CVPixelBufferRef pixelBuffer = _latestPixelBuffer;
579-
while (!OSAtomicCompareAndSwapPtrBarrier(pixelBuffer, nil, (void **)&_latestPixelBuffer)) {
580-
pixelBuffer = _latestPixelBuffer;
581-
}
582-
590+
__block CVPixelBufferRef pixelBuffer = nil;
591+
// `copyPixelBuffer` API requires synchronous return, so we have to use `dispatch_sync`.
592+
dispatch_sync(self.pixelBufferSynchronizationQueue, ^{
593+
pixelBuffer = self.latestPixelBuffer;
594+
self.latestPixelBuffer = nil;
595+
});
583596
return pixelBuffer;
584597
}
585598

packages/camera/camera/ios/Classes/FLTCam_Test.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,10 @@
2222
@property(readonly, nonatomic)
2323
NSMutableDictionary<NSNumber *, FLTSavePhotoDelegate *> *inProgressSavePhotoDelegates;
2424

25+
/// Delegate callback when receive a new video or audio sample.
26+
/// Exposed for unit tests.
27+
- (void)captureOutput:(AVCaptureOutput *)output
28+
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
29+
fromConnection:(AVCaptureConnection *)connection;
30+
2531
@end

packages/camera/camera/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ description: A Flutter plugin for controlling the camera. Supports previewing
44
Dart.
55
repository: https://github.com/flutter/plugins/tree/main/packages/camera/camera
66
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
7-
version: 0.9.4+14
7+
version: 0.9.4+15
88

99
environment:
1010
sdk: ">=2.14.0 <3.0.0"

0 commit comments

Comments
 (0)