[camera]remove CAS operation and use dispatch queue instead (#4973)
diff --git a/packages/camera/camera/CHANGELOG.md b/packages/camera/camera/CHANGELOG.md
index c088bd5..4b96fd6 100644
--- a/packages/camera/camera/CHANGELOG.md
+++ b/packages/camera/camera/CHANGELOG.md
@@ -1,5 +1,6 @@
-## NEXT
+## 0.9.4+15
+* Uses dispatch queue for pixel buffer synchronization on iOS.
* Minor iOS internal code cleanup related to queue helper functions.
## 0.9.4+14
diff --git a/packages/camera/camera/example/ios/Runner.xcodeproj/project.pbxproj b/packages/camera/camera/example/ios/Runner.xcodeproj/project.pbxproj
index 8c102bf..6c17150 100644
--- a/packages/camera/camera/example/ios/Runner.xcodeproj/project.pbxproj
+++ b/packages/camera/camera/example/ios/Runner.xcodeproj/project.pbxproj
@@ -28,6 +28,7 @@
E0C6E2002770F01A00EA6AA3 /* ThreadSafeMethodChannelTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E0C6E1FD2770F01A00EA6AA3 /* ThreadSafeMethodChannelTests.m */; };
E0C6E2012770F01A00EA6AA3 /* ThreadSafeTextureRegistryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E0C6E1FE2770F01A00EA6AA3 /* ThreadSafeTextureRegistryTests.m */; };
E0C6E2022770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E0C6E1FF2770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m */; };
+ E0CDBAC227CD9729002561D9 /* CameraTestUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = E0CDBAC127CD9729002561D9 /* CameraTestUtils.m */; };
E0F95E3D27A32AB900699390 /* CameraPropertiesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E0F95E3C27A32AB900699390 /* CameraPropertiesTests.m */; };
E487C86026D686A10034AC92 /* CameraPreviewPauseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E487C85F26D686A10034AC92 /* CameraPreviewPauseTests.m */; };
F6EE622F2710A6FC00905E4A /* MockFLTThreadSafeFlutterResult.m in Sources */ = {isa = PBXBuildFile; fileRef = F6EE622E2710A6FC00905E4A /* MockFLTThreadSafeFlutterResult.m */; };
@@ -91,6 +92,8 @@
E0C6E1FD2770F01A00EA6AA3 /* ThreadSafeMethodChannelTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ThreadSafeMethodChannelTests.m; sourceTree = "<group>"; };
E0C6E1FE2770F01A00EA6AA3 /* ThreadSafeTextureRegistryTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ThreadSafeTextureRegistryTests.m; sourceTree = "<group>"; };
E0C6E1FF2770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ThreadSafeEventChannelTests.m; sourceTree = "<group>"; };
+ E0CDBAC027CD9729002561D9 /* CameraTestUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CameraTestUtils.h; sourceTree = "<group>"; };
+ E0CDBAC127CD9729002561D9 /* CameraTestUtils.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CameraTestUtils.m; sourceTree = "<group>"; };
E0F95E3C27A32AB900699390 /* CameraPropertiesTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CameraPropertiesTests.m; sourceTree = "<group>"; };
E487C85F26D686A10034AC92 /* CameraPreviewPauseTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CameraPreviewPauseTests.m; sourceTree = "<group>"; };
F63F9EED27143B19002479BF /* MockFLTThreadSafeFlutterResult.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockFLTThreadSafeFlutterResult.h; sourceTree = "<group>"; };
@@ -132,6 +135,8 @@
E071CF7127B3061B006EF3BA /* FLTCamPhotoCaptureTests.m */,
E071CF7327B31DE4006EF3BA /* FLTCamSampleBufferTests.m */,
E01EE4A72799F3A5008C1950 /* QueueUtilsTests.m */,
+ E0CDBAC027CD9729002561D9 /* CameraTestUtils.h */,
+ E0CDBAC127CD9729002561D9 /* CameraTestUtils.m */,
E487C85F26D686A10034AC92 /* CameraPreviewPauseTests.m */,
F6EE622E2710A6FC00905E4A /* MockFLTThreadSafeFlutterResult.m */,
F63F9EED27143B19002479BF /* MockFLTThreadSafeFlutterResult.h */,
@@ -408,6 +413,7 @@
E071CF7427B31DE4006EF3BA /* FLTCamSampleBufferTests.m in Sources */,
E04F108627A87CA600573D0C /* FLTSavePhotoDelegateTests.m in Sources */,
F6EE622F2710A6FC00905E4A /* MockFLTThreadSafeFlutterResult.m in Sources */,
+ E0CDBAC227CD9729002561D9 /* CameraTestUtils.m in Sources */,
334733EA2668111C00DCC49E /* CameraOrientationTests.m in Sources */,
E032F250279F5E94009E9028 /* CameraCaptureSessionQueueRaceConditionTests.m in Sources */,
E0C6E2022770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m in Sources */,
diff --git a/packages/camera/camera/example/ios/Runner/main.m b/packages/camera/camera/example/ios/Runner/main.m
index 8b3fb8d..d1224fe 100644
--- a/packages/camera/camera/example/ios/Runner/main.m
+++ b/packages/camera/camera/example/ios/Runner/main.m
@@ -11,7 +11,7 @@
// The setup logic in `AppDelegate::didFinishLaunchingWithOptions:` eventually sends camera
// operations on the background queue, which would run concurrently with the test cases during
// unit tests, making the debugging process confusing. This setup is actually not necessary for
- // the unit tests, so here we want to skip the AppDelegate when running unit tests.
+ // the unit tests, so it is better to skip the AppDelegate when running unit tests.
BOOL isTesting = NSClassFromString(@"XCTestCase") != nil;
return UIApplicationMain(argc, argv, nil,
isTesting ? nil : NSStringFromClass([AppDelegate class]));
diff --git a/packages/camera/camera/example/ios/RunnerTests/CameraTestUtils.h b/packages/camera/camera/example/ios/RunnerTests/CameraTestUtils.h
new file mode 100644
index 0000000..9fe67dc
--- /dev/null
+++ b/packages/camera/camera/example/ios/RunnerTests/CameraTestUtils.h
@@ -0,0 +1,18 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+@import camera;
+
+NS_ASSUME_NONNULL_BEGIN
+
+/// Creates an `FLTCam` that runs its capture session operations on a given queue.
+/// @param captureSessionQueue the capture session queue
+/// @return an FLTCam object.
+extern FLTCam *FLTCreateCamWithCaptureSessionQueue(dispatch_queue_t captureSessionQueue);
+
+/// Creates a test sample buffer.
+/// @return a test sample buffer.
+extern CMSampleBufferRef FLTCreateTestSampleBuffer(void);
+
+NS_ASSUME_NONNULL_END
diff --git a/packages/camera/camera/example/ios/RunnerTests/CameraTestUtils.m b/packages/camera/camera/example/ios/RunnerTests/CameraTestUtils.m
new file mode 100644
index 0000000..0ae4887
--- /dev/null
+++ b/packages/camera/camera/example/ios/RunnerTests/CameraTestUtils.m
@@ -0,0 +1,44 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "CameraTestUtils.h"
+#import <OCMock/OCMock.h>
+@import AVFoundation;
+
+FLTCam *FLTCreateCamWithCaptureSessionQueue(dispatch_queue_t captureSessionQueue) {
+ id inputMock = OCMClassMock([AVCaptureDeviceInput class]);
+ OCMStub([inputMock deviceInputWithDevice:[OCMArg any] error:[OCMArg setTo:nil]])
+ .andReturn(inputMock);
+
+ id sessionMock = OCMClassMock([AVCaptureSession class]);
+ OCMStub([sessionMock addInputWithNoConnections:[OCMArg any]]); // no-op
+ OCMStub([sessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES);
+
+ return [[FLTCam alloc] initWithCameraName:@"camera"
+ resolutionPreset:@"medium"
+ enableAudio:true
+ orientation:UIDeviceOrientationPortrait
+ captureSession:sessionMock
+ captureSessionQueue:captureSessionQueue
+ error:nil];
+}
+
+CMSampleBufferRef FLTCreateTestSampleBuffer(void) {
+ CVPixelBufferRef pixelBuffer;
+ CVPixelBufferCreate(kCFAllocatorDefault, 100, 100, kCVPixelFormatType_32BGRA, NULL, &pixelBuffer);
+
+ CMFormatDescriptionRef formatDescription;
+ CMVideoFormatDescriptionCreateForImageBuffer(kCFAllocatorDefault, pixelBuffer,
+ &formatDescription);
+
+ CMSampleTimingInfo timingInfo = {CMTimeMake(1, 44100), kCMTimeZero, kCMTimeInvalid};
+
+ CMSampleBufferRef sampleBuffer;
+ CMSampleBufferCreateReadyWithImageBuffer(kCFAllocatorDefault, pixelBuffer, formatDescription,
+ &timingInfo, &sampleBuffer);
+
+ CFRelease(pixelBuffer);
+ CFRelease(formatDescription);
+ return sampleBuffer;
+}
diff --git a/packages/camera/camera/example/ios/RunnerTests/FLTCamPhotoCaptureTests.m b/packages/camera/camera/example/ios/RunnerTests/FLTCamPhotoCaptureTests.m
index fdb2abd..ed3e6a9 100644
--- a/packages/camera/camera/example/ios/RunnerTests/FLTCamPhotoCaptureTests.m
+++ b/packages/camera/camera/example/ios/RunnerTests/FLTCamPhotoCaptureTests.m
@@ -7,7 +7,9 @@
@import AVFoundation;
@import XCTest;
#import <OCMock/OCMock.h>
+#import "CameraTestUtils.h"
+/// Includes test cases related to photo capture operations for FLTCam class.
@interface FLTCamPhotoCaptureTests : XCTestCase
@end
@@ -22,7 +24,7 @@
dispatch_queue_t captureSessionQueue = dispatch_queue_create("capture_session_queue", NULL);
dispatch_queue_set_specific(captureSessionQueue, FLTCaptureSessionQueueSpecific,
(void *)FLTCaptureSessionQueueSpecific, NULL);
- FLTCam *cam = [self createFLTCamWithCaptureSessionQueue:captureSessionQueue];
+ FLTCam *cam = FLTCreateCamWithCaptureSessionQueue(captureSessionQueue);
AVCapturePhotoSettings *settings = [AVCapturePhotoSettings photoSettings];
id mockSettings = OCMClassMock([AVCapturePhotoSettings class]);
OCMStub([mockSettings photoSettings]).andReturn(settings);
@@ -61,7 +63,7 @@
dispatch_queue_t captureSessionQueue = dispatch_queue_create("capture_session_queue", NULL);
dispatch_queue_set_specific(captureSessionQueue, FLTCaptureSessionQueueSpecific,
(void *)FLTCaptureSessionQueueSpecific, NULL);
- FLTCam *cam = [self createFLTCamWithCaptureSessionQueue:captureSessionQueue];
+ FLTCam *cam = FLTCreateCamWithCaptureSessionQueue(captureSessionQueue);
AVCapturePhotoSettings *settings = [AVCapturePhotoSettings photoSettings];
id mockSettings = OCMClassMock([AVCapturePhotoSettings class]);
@@ -92,23 +94,4 @@
[self waitForExpectationsWithTimeout:1 handler:nil];
}
-/// Creates an `FLTCam` that runs its operations on a given capture session queue.
-- (FLTCam *)createFLTCamWithCaptureSessionQueue:(dispatch_queue_t)captureSessionQueue {
- id inputMock = OCMClassMock([AVCaptureDeviceInput class]);
- OCMStub([inputMock deviceInputWithDevice:[OCMArg any] error:[OCMArg setTo:nil]])
- .andReturn(inputMock);
-
- id sessionMock = OCMClassMock([AVCaptureSession class]);
- OCMStub([sessionMock alloc]).andReturn(sessionMock);
- OCMStub([sessionMock addInputWithNoConnections:[OCMArg any]]); // no-op
- OCMStub([sessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES);
-
- return [[FLTCam alloc] initWithCameraName:@"camera"
- resolutionPreset:@"medium"
- enableAudio:true
- orientation:UIDeviceOrientationPortrait
- captureSessionQueue:captureSessionQueue
- error:nil];
-}
-
@end
diff --git a/packages/camera/camera/example/ios/RunnerTests/FLTCamSampleBufferTests.m b/packages/camera/camera/example/ios/RunnerTests/FLTCamSampleBufferTests.m
index ccc8de5..8f65c4e 100644
--- a/packages/camera/camera/example/ios/RunnerTests/FLTCamSampleBufferTests.m
+++ b/packages/camera/camera/example/ios/RunnerTests/FLTCamSampleBufferTests.m
@@ -7,7 +7,9 @@
@import AVFoundation;
@import XCTest;
#import <OCMock/OCMock.h>
+#import "CameraTestUtils.h"
+/// Includes test cases related to sample buffer handling for FLTCam class.
@interface FLTCamSampleBufferTests : XCTestCase
@end
@@ -15,23 +17,25 @@
@implementation FLTCamSampleBufferTests
- (void)testSampleBufferCallbackQueueMustBeCaptureSessionQueue {
- id inputMock = OCMClassMock([AVCaptureDeviceInput class]);
- OCMStub([inputMock deviceInputWithDevice:[OCMArg any] error:[OCMArg setTo:nil]])
- .andReturn(inputMock);
-
- id sessionMock = OCMClassMock([AVCaptureSession class]);
- OCMStub([sessionMock alloc]).andReturn(sessionMock);
- OCMStub([sessionMock addInputWithNoConnections:[OCMArg any]]); // no-op
- OCMStub([sessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES);
-
dispatch_queue_t captureSessionQueue = dispatch_queue_create("testing", NULL);
- FLTCam *cam = [[FLTCam alloc] initWithCameraName:@"camera"
- resolutionPreset:@"medium"
- enableAudio:true
- orientation:UIDeviceOrientationPortrait
- captureSessionQueue:captureSessionQueue
- error:nil];
- XCTAssertEqual(captureSessionQueue, cam.captureVideoOutput.sampleBufferCallbackQueue);
+ FLTCam *cam = FLTCreateCamWithCaptureSessionQueue(captureSessionQueue);
+ XCTAssertEqual(captureSessionQueue, cam.captureVideoOutput.sampleBufferCallbackQueue,
+ @"Sample buffer callback queue must be the capture session queue.");
+}
+
+- (void)testCopyPixelBuffer {
+ FLTCam *cam = FLTCreateCamWithCaptureSessionQueue(dispatch_queue_create("test", NULL));
+ CMSampleBufferRef capturedSampleBuffer = FLTCreateTestSampleBuffer();
+ CVPixelBufferRef capturedPixelBuffer = CMSampleBufferGetImageBuffer(capturedSampleBuffer);
+ // Mimic sample buffer callback when captured a new video sample
+ [cam captureOutput:cam.captureVideoOutput
+ didOutputSampleBuffer:capturedSampleBuffer
+ fromConnection:OCMClassMock([AVCaptureConnection class])];
+ CVPixelBufferRef deliveriedPixelBuffer = [cam copyPixelBuffer];
+ XCTAssertEqual(deliveriedPixelBuffer, capturedPixelBuffer,
+ @"FLTCam must deliver the latest captured pixel buffer to copyPixelBuffer API.");
+ CFRelease(capturedSampleBuffer);
+ CFRelease(deliveriedPixelBuffer);
}
@end
diff --git a/packages/camera/camera/example/ios/RunnerTests/FLTSavePhotoDelegateTests.m b/packages/camera/camera/example/ios/RunnerTests/FLTSavePhotoDelegateTests.m
index 9e8e244..a70a572 100644
--- a/packages/camera/camera/example/ios/RunnerTests/FLTSavePhotoDelegateTests.m
+++ b/packages/camera/camera/example/ios/RunnerTests/FLTSavePhotoDelegateTests.m
@@ -53,7 +53,7 @@
[completionExpectation fulfill];
}];
- // We can't use OCMClassMock for NSData because some XCTest APIs uses NSData (e.g.
+ // Do not use OCMClassMock for NSData because some XCTest APIs uses NSData (e.g.
// `XCTRunnerIDESession::logDebugMessage:`) on a private queue.
id mockData = OCMPartialMock([NSData data]);
OCMStub([mockData writeToFile:OCMOCK_ANY
@@ -82,7 +82,7 @@
[completionExpectation fulfill];
}];
- // We can't use OCMClassMock for NSData because some XCTest APIs uses NSData (e.g.
+ // Do not use OCMClassMock for NSData because some XCTest APIs uses NSData (e.g.
// `XCTRunnerIDESession::logDebugMessage:`) on a private queue.
id mockData = OCMPartialMock([NSData data]);
OCMStub([mockData writeToFile:filePath options:NSDataWritingAtomic error:[OCMArg setTo:nil]])
@@ -107,7 +107,7 @@
const char *ioQueueSpecific = "io_queue_specific";
dispatch_queue_set_specific(ioQueue, ioQueueSpecific, (void *)ioQueueSpecific, NULL);
- // We can't use OCMClassMock for NSData because some XCTest APIs uses NSData (e.g.
+ // Do not use OCMClassMock for NSData because some XCTest APIs uses NSData (e.g.
// `XCTRunnerIDESession::logDebugMessage:`) on a private queue.
id mockData = OCMPartialMock([NSData data]);
OCMStub([mockData writeToFile:OCMOCK_ANY options:NSDataWritingAtomic error:[OCMArg setTo:nil]])
diff --git a/packages/camera/camera/ios/Classes/FLTCam.h b/packages/camera/camera/ios/Classes/FLTCam.h
index 417a1d7..0cd135e 100644
--- a/packages/camera/camera/ios/Classes/FLTCam.h
+++ b/packages/camera/camera/ios/Classes/FLTCam.h
@@ -31,6 +31,13 @@
// Format used for video and image streaming.
@property(assign, nonatomic) FourCharCode videoFormat;
+/// Initializes an `FLTCam` instance.
+/// @param cameraName a name used to uniquely identify the camera.
+/// @param resolutionPreset the resolution preset
+/// @param enableAudio YES if audio should be enabled for video capturing; NO otherwise.
+/// @param orientation the orientation of camera
+/// @param captureSessionQueue the queue on which camera's capture session operations happen.
+/// @param error report to the caller if any error happened creating the camera.
- (instancetype)initWithCameraName:(NSString *)cameraName
resolutionPreset:(NSString *)resolutionPreset
enableAudio:(BOOL)enableAudio
diff --git a/packages/camera/camera/ios/Classes/FLTCam.m b/packages/camera/camera/ios/Classes/FLTCam.m
index 669ce33..30c177b 100644
--- a/packages/camera/camera/ios/Classes/FLTCam.m
+++ b/packages/camera/camera/ios/Classes/FLTCam.m
@@ -52,7 +52,9 @@
@property(readonly, nonatomic) AVCaptureSession *captureSession;
@property(readonly, nonatomic) AVCaptureInput *captureVideoInput;
-@property(readonly) CVPixelBufferRef volatile latestPixelBuffer;
+/// Tracks the latest pixel buffer sent from AVFoundation's sample buffer delegate callback.
+/// Used to deliver the latest pixel buffer to the flutter engine via the `copyPixelBuffer` API.
+@property(readwrite, nonatomic) CVPixelBufferRef latestPixelBuffer;
@property(readonly, nonatomic) CGSize captureSize;
@property(strong, nonatomic) AVAssetWriter *videoWriter;
@property(strong, nonatomic) AVAssetWriterInput *videoWriterInput;
@@ -76,6 +78,9 @@
@property AVAssetWriterInputPixelBufferAdaptor *videoAdaptor;
/// All FLTCam's state access and capture session related operations should be on run on this queue.
@property(strong, nonatomic) dispatch_queue_t captureSessionQueue;
+/// The queue on which `latestPixelBuffer` property is accessed.
+/// To avoid unnecessary contention, do not access `latestPixelBuffer` on the `captureSessionQueue`.
+@property(strong, nonatomic) dispatch_queue_t pixelBufferSynchronizationQueue;
/// The queue on which captured photos (not videos) are written to disk.
/// Videos are written to disk by `videoAdaptor` on an internal queue managed by AVFoundation.
@property(strong, nonatomic) dispatch_queue_t photoIOQueue;
@@ -92,6 +97,22 @@
orientation:(UIDeviceOrientation)orientation
captureSessionQueue:(dispatch_queue_t)captureSessionQueue
error:(NSError **)error {
+ return [self initWithCameraName:cameraName
+ resolutionPreset:resolutionPreset
+ enableAudio:enableAudio
+ orientation:orientation
+ captureSession:[[AVCaptureSession alloc] init]
+ captureSessionQueue:captureSessionQueue
+ error:error];
+}
+
+- (instancetype)initWithCameraName:(NSString *)cameraName
+ resolutionPreset:(NSString *)resolutionPreset
+ enableAudio:(BOOL)enableAudio
+ orientation:(UIDeviceOrientation)orientation
+ captureSession:(AVCaptureSession *)captureSession
+ captureSessionQueue:(dispatch_queue_t)captureSessionQueue
+ error:(NSError **)error {
self = [super init];
NSAssert(self, @"super init cannot be nil");
@try {
@@ -101,8 +122,10 @@
}
_enableAudio = enableAudio;
_captureSessionQueue = captureSessionQueue;
+ _pixelBufferSynchronizationQueue =
+ dispatch_queue_create("io.flutter.camera.pixelBufferSynchronizationQueue", NULL);
_photoIOQueue = dispatch_queue_create("io.flutter.camera.photoIOQueue", NULL);
- _captureSession = [[AVCaptureSession alloc] init];
+ _captureSession = captureSession;
_captureDevice = [AVCaptureDevice deviceWithUniqueID:cameraName];
_flashMode = _captureDevice.hasFlash ? FLTFlashModeAuto : FLTFlashModeOff;
_exposureMode = FLTExposureModeAuto;
@@ -355,12 +378,17 @@
if (output == _captureVideoOutput) {
CVPixelBufferRef newBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
CFRetain(newBuffer);
- CVPixelBufferRef old = _latestPixelBuffer;
- while (!OSAtomicCompareAndSwapPtrBarrier(old, newBuffer, (void **)&_latestPixelBuffer)) {
- old = _latestPixelBuffer;
- }
- if (old != nil) {
- CFRelease(old);
+
+ __block CVPixelBufferRef previousPixelBuffer = nil;
+ // Use `dispatch_sync` to avoid unnecessary context switch under common non-contest scenarios;
+ // Under rare contest scenarios, it will not block for too long since the critical section is
+ // quite lightweight.
+ dispatch_sync(self.pixelBufferSynchronizationQueue, ^{
+ previousPixelBuffer = self.latestPixelBuffer;
+ self.latestPixelBuffer = newBuffer;
+ });
+ if (previousPixelBuffer) {
+ CFRelease(previousPixelBuffer);
}
if (_onFrameAvailable) {
_onFrameAvailable();
@@ -420,7 +448,7 @@
[planes addObject:planeBuffer];
}
- // Before accessing pixel data, we should lock the base address, and unlock it afterwards.
+ // Lock the base address before accessing pixel data, and unlock it afterwards.
// Done accessing the `pixelBuffer` at this point.
CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
@@ -575,11 +603,12 @@
}
- (CVPixelBufferRef)copyPixelBuffer {
- CVPixelBufferRef pixelBuffer = _latestPixelBuffer;
- while (!OSAtomicCompareAndSwapPtrBarrier(pixelBuffer, nil, (void **)&_latestPixelBuffer)) {
- pixelBuffer = _latestPixelBuffer;
- }
-
+ __block CVPixelBufferRef pixelBuffer = nil;
+ // Use `dispatch_sync` because `copyPixelBuffer` API requires synchronous return.
+ dispatch_sync(self.pixelBufferSynchronizationQueue, ^{
+ pixelBuffer = self.latestPixelBuffer;
+ self.latestPixelBuffer = nil;
+ });
return pixelBuffer;
}
diff --git a/packages/camera/camera/ios/Classes/FLTCam_Test.h b/packages/camera/camera/ios/Classes/FLTCam_Test.h
index db885d0..a1f9f2b 100644
--- a/packages/camera/camera/ios/Classes/FLTCam_Test.h
+++ b/packages/camera/camera/ios/Classes/FLTCam_Test.h
@@ -17,9 +17,25 @@
/// A dictionary to retain all in-progress FLTSavePhotoDelegates. The key of the dictionary is the
/// AVCapturePhotoSettings's uniqueID for each photo capture operation, and the value is the
/// FLTSavePhotoDelegate that handles the result of each photo capture operation. Note that photo
-/// capture operations may overlap, so we have to keep track of multiple delegates in progress,
+/// capture operations may overlap, so FLTCam has to keep track of multiple delegates in progress,
/// instead of just a single delegate reference.
@property(readonly, nonatomic)
NSMutableDictionary<NSNumber *, FLTSavePhotoDelegate *> *inProgressSavePhotoDelegates;
+/// Delegate callback when receiving a new video or audio sample.
+/// Exposed for unit tests.
+- (void)captureOutput:(AVCaptureOutput *)output
+ didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
+ fromConnection:(AVCaptureConnection *)connection;
+
+/// Initializes a camera instance.
+/// Allows for injecting dependencies that are usually internal.
+- (instancetype)initWithCameraName:(NSString *)cameraName
+ resolutionPreset:(NSString *)resolutionPreset
+ enableAudio:(BOOL)enableAudio
+ orientation:(UIDeviceOrientation)orientation
+ captureSession:(AVCaptureSession *)captureSession
+ captureSessionQueue:(dispatch_queue_t)captureSessionQueue
+ error:(NSError **)error;
+
@end
diff --git a/packages/camera/camera/pubspec.yaml b/packages/camera/camera/pubspec.yaml
index 7bc0e45..fcdce02 100644
--- a/packages/camera/camera/pubspec.yaml
+++ b/packages/camera/camera/pubspec.yaml
@@ -4,7 +4,7 @@
Dart.
repository: https://github.com/flutter/plugins/tree/main/packages/camera/camera
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
-version: 0.9.4+14
+version: 0.9.4+15
environment:
sdk: ">=2.14.0 <3.0.0"