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

Commit 4e1c90e

Browse files
authored
[camera]remove CAS operation and use dispatch queue instead (#4973)
1 parent b906ea5 commit 4e1c90e

File tree

12 files changed

+166
-58
lines changed

12 files changed

+166
-58
lines changed

packages/camera/camera/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
## NEXT
1+
## 0.9.4+15
22

3+
* Uses dispatch queue for pixel buffer synchronization on iOS.
34
* Minor iOS internal code cleanup related to queue helper functions.
45

56
## 0.9.4+14

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>"; };
@@ -132,6 +135,8 @@
132135
E071CF7127B3061B006EF3BA /* FLTCamPhotoCaptureTests.m */,
133136
E071CF7327B31DE4006EF3BA /* FLTCamSampleBufferTests.m */,
134137
E01EE4A72799F3A5008C1950 /* QueueUtilsTests.m */,
138+
E0CDBAC027CD9729002561D9 /* CameraTestUtils.h */,
139+
E0CDBAC127CD9729002561D9 /* CameraTestUtils.m */,
135140
E487C85F26D686A10034AC92 /* CameraPreviewPauseTests.m */,
136141
F6EE622E2710A6FC00905E4A /* MockFLTThreadSafeFlutterResult.m */,
137142
F63F9EED27143B19002479BF /* MockFLTThreadSafeFlutterResult.h */,
@@ -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 */,

packages/camera/camera/example/ios/Runner/main.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ int main(int argc, char *argv[]) {
1111
// The setup logic in `AppDelegate::didFinishLaunchingWithOptions:` eventually sends camera
1212
// operations on the background queue, which would run concurrently with the test cases during
1313
// unit tests, making the debugging process confusing. This setup is actually not necessary for
14-
// the unit tests, so here we want to skip the AppDelegate when running unit tests.
14+
// the unit tests, so it is better to skip the AppDelegate when running unit tests.
1515
BOOL isTesting = NSClassFromString(@"XCTestCase") != nil;
1616
return UIApplicationMain(argc, argv, nil,
1717
isTesting ? nil : NSStringFromClass([AppDelegate class]));
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
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+
/// Creates an `FLTCam` that runs its capture session operations on a given queue.
10+
/// @param captureSessionQueue the capture session queue
11+
/// @return an FLTCam object.
12+
extern FLTCam *FLTCreateCamWithCaptureSessionQueue(dispatch_queue_t captureSessionQueue);
13+
14+
/// Creates a test sample buffer.
15+
/// @return a test sample buffer.
16+
extern CMSampleBufferRef FLTCreateTestSampleBuffer(void);
17+
18+
NS_ASSUME_NONNULL_END
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
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+
FLTCam *FLTCreateCamWithCaptureSessionQueue(dispatch_queue_t captureSessionQueue) {
10+
id inputMock = OCMClassMock([AVCaptureDeviceInput class]);
11+
OCMStub([inputMock deviceInputWithDevice:[OCMArg any] error:[OCMArg setTo:nil]])
12+
.andReturn(inputMock);
13+
14+
id sessionMock = OCMClassMock([AVCaptureSession class]);
15+
OCMStub([sessionMock addInputWithNoConnections:[OCMArg any]]); // no-op
16+
OCMStub([sessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES);
17+
18+
return [[FLTCam alloc] initWithCameraName:@"camera"
19+
resolutionPreset:@"medium"
20+
enableAudio:true
21+
orientation:UIDeviceOrientationPortrait
22+
captureSession:sessionMock
23+
captureSessionQueue:captureSessionQueue
24+
error:nil];
25+
}
26+
27+
CMSampleBufferRef FLTCreateTestSampleBuffer(void) {
28+
CVPixelBufferRef pixelBuffer;
29+
CVPixelBufferCreate(kCFAllocatorDefault, 100, 100, kCVPixelFormatType_32BGRA, NULL, &pixelBuffer);
30+
31+
CMFormatDescriptionRef formatDescription;
32+
CMVideoFormatDescriptionCreateForImageBuffer(kCFAllocatorDefault, pixelBuffer,
33+
&formatDescription);
34+
35+
CMSampleTimingInfo timingInfo = {CMTimeMake(1, 44100), kCMTimeZero, kCMTimeInvalid};
36+
37+
CMSampleBufferRef sampleBuffer;
38+
CMSampleBufferCreateReadyWithImageBuffer(kCFAllocatorDefault, pixelBuffer, formatDescription,
39+
&timingInfo, &sampleBuffer);
40+
41+
CFRelease(pixelBuffer);
42+
CFRelease(formatDescription);
43+
return sampleBuffer;
44+
}

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 = FLTCreateCamWithCaptureSessionQueue(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 = FLTCreateCamWithCaptureSessionQueue(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 = FLTCreateCamWithCaptureSessionQueue(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 = FLTCreateCamWithCaptureSessionQueue(dispatch_queue_create("test", NULL));
28+
CMSampleBufferRef capturedSampleBuffer = FLTCreateTestSampleBuffer();
29+
CVPixelBufferRef capturedPixelBuffer = CMSampleBufferGetImageBuffer(capturedSampleBuffer);
30+
// Mimic sample buffer callback when captured a new video sample
31+
[cam captureOutput:cam.captureVideoOutput
32+
didOutputSampleBuffer:capturedSampleBuffer
33+
fromConnection:OCMClassMock([AVCaptureConnection class])];
34+
CVPixelBufferRef deliveriedPixelBuffer = [cam copyPixelBuffer];
35+
XCTAssertEqual(deliveriedPixelBuffer, capturedPixelBuffer,
36+
@"FLTCam must deliver the latest captured pixel buffer to copyPixelBuffer API.");
37+
CFRelease(capturedSampleBuffer);
38+
CFRelease(deliveriedPixelBuffer);
3539
}
3640

3741
@end

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ - (void)testHandlePhotoCaptureResult_mustCompleteWithErrorIfFailedToWrite {
5353
[completionExpectation fulfill];
5454
}];
5555

56-
// We can't use OCMClassMock for NSData because some XCTest APIs uses NSData (e.g.
56+
// Do not use OCMClassMock for NSData because some XCTest APIs uses NSData (e.g.
5757
// `XCTRunnerIDESession::logDebugMessage:`) on a private queue.
5858
id mockData = OCMPartialMock([NSData data]);
5959
OCMStub([mockData writeToFile:OCMOCK_ANY
@@ -82,7 +82,7 @@ - (void)testHandlePhotoCaptureResult_mustCompleteWithFilePathIfSuccessToWrite {
8282
[completionExpectation fulfill];
8383
}];
8484

85-
// We can't use OCMClassMock for NSData because some XCTest APIs uses NSData (e.g.
85+
// Do not use OCMClassMock for NSData because some XCTest APIs uses NSData (e.g.
8686
// `XCTRunnerIDESession::logDebugMessage:`) on a private queue.
8787
id mockData = OCMPartialMock([NSData data]);
8888
OCMStub([mockData writeToFile:filePath options:NSDataWritingAtomic error:[OCMArg setTo:nil]])
@@ -107,7 +107,7 @@ - (void)testHandlePhotoCaptureResult_bothProvideDataAndSaveFileMustRunOnIOQueue
107107
const char *ioQueueSpecific = "io_queue_specific";
108108
dispatch_queue_set_specific(ioQueue, ioQueueSpecific, (void *)ioQueueSpecific, NULL);
109109

110-
// We can't use OCMClassMock for NSData because some XCTest APIs uses NSData (e.g.
110+
// Do not use OCMClassMock for NSData because some XCTest APIs uses NSData (e.g.
111111
// `XCTRunnerIDESession::logDebugMessage:`) on a private queue.
112112
id mockData = OCMPartialMock([NSData data]);
113113
OCMStub([mockData writeToFile:OCMOCK_ANY options:NSDataWritingAtomic error:[OCMArg setTo:nil]])

packages/camera/camera/ios/Classes/FLTCam.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@ NS_ASSUME_NONNULL_BEGIN
3131
// Format used for video and image streaming.
3232
@property(assign, nonatomic) FourCharCode videoFormat;
3333

34+
/// Initializes an `FLTCam` instance.
35+
/// @param cameraName a name used to uniquely identify the camera.
36+
/// @param resolutionPreset the resolution preset
37+
/// @param enableAudio YES if audio should be enabled for video capturing; NO otherwise.
38+
/// @param orientation the orientation of camera
39+
/// @param captureSessionQueue the queue on which camera's capture session operations happen.
40+
/// @param error report to the caller if any error happened creating the camera.
3441
- (instancetype)initWithCameraName:(NSString *)cameraName
3542
resolutionPreset:(NSString *)resolutionPreset
3643
enableAudio:(BOOL)enableAudio

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

Lines changed: 43 additions & 14 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, do not access `latestPixelBuffer` on 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;
@@ -92,6 +97,22 @@ - (instancetype)initWithCameraName:(NSString *)cameraName
9297
orientation:(UIDeviceOrientation)orientation
9398
captureSessionQueue:(dispatch_queue_t)captureSessionQueue
9499
error:(NSError **)error {
100+
return [self initWithCameraName:cameraName
101+
resolutionPreset:resolutionPreset
102+
enableAudio:enableAudio
103+
orientation:orientation
104+
captureSession:[[AVCaptureSession alloc] init]
105+
captureSessionQueue:captureSessionQueue
106+
error:error];
107+
}
108+
109+
- (instancetype)initWithCameraName:(NSString *)cameraName
110+
resolutionPreset:(NSString *)resolutionPreset
111+
enableAudio:(BOOL)enableAudio
112+
orientation:(UIDeviceOrientation)orientation
113+
captureSession:(AVCaptureSession *)captureSession
114+
captureSessionQueue:(dispatch_queue_t)captureSessionQueue
115+
error:(NSError **)error {
95116
self = [super init];
96117
NSAssert(self, @"super init cannot be nil");
97118
@try {
@@ -101,8 +122,10 @@ - (instancetype)initWithCameraName:(NSString *)cameraName
101122
}
102123
_enableAudio = enableAudio;
103124
_captureSessionQueue = captureSessionQueue;
125+
_pixelBufferSynchronizationQueue =
126+
dispatch_queue_create("io.flutter.camera.pixelBufferSynchronizationQueue", NULL);
104127
_photoIOQueue = dispatch_queue_create("io.flutter.camera.photoIOQueue", NULL);
105-
_captureSession = [[AVCaptureSession alloc] init];
128+
_captureSession = captureSession;
106129
_captureDevice = [AVCaptureDevice deviceWithUniqueID:cameraName];
107130
_flashMode = _captureDevice.hasFlash ? FLTFlashModeAuto : FLTFlashModeOff;
108131
_exposureMode = FLTExposureModeAuto;
@@ -355,12 +378,17 @@ - (void)captureOutput:(AVCaptureOutput *)output
355378
if (output == _captureVideoOutput) {
356379
CVPixelBufferRef newBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
357380
CFRetain(newBuffer);
358-
CVPixelBufferRef old = _latestPixelBuffer;
359-
while (!OSAtomicCompareAndSwapPtrBarrier(old, newBuffer, (void **)&_latestPixelBuffer)) {
360-
old = _latestPixelBuffer;
361-
}
362-
if (old != nil) {
363-
CFRelease(old);
381+
382+
__block CVPixelBufferRef previousPixelBuffer = nil;
383+
// Use `dispatch_sync` to avoid unnecessary context switch under common non-contest scenarios;
384+
// Under rare contest scenarios, it will not block for too long since the critical section is
385+
// quite lightweight.
386+
dispatch_sync(self.pixelBufferSynchronizationQueue, ^{
387+
previousPixelBuffer = self.latestPixelBuffer;
388+
self.latestPixelBuffer = newBuffer;
389+
});
390+
if (previousPixelBuffer) {
391+
CFRelease(previousPixelBuffer);
364392
}
365393
if (_onFrameAvailable) {
366394
_onFrameAvailable();
@@ -420,7 +448,7 @@ - (void)captureOutput:(AVCaptureOutput *)output
420448

421449
[planes addObject:planeBuffer];
422450
}
423-
// Before accessing pixel data, we should lock the base address, and unlock it afterwards.
451+
// Lock the base address before accessing pixel data, and unlock it afterwards.
424452
// Done accessing the `pixelBuffer` at this point.
425453
CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
426454

@@ -575,11 +603,12 @@ - (void)dealloc {
575603
}
576604

577605
- (CVPixelBufferRef)copyPixelBuffer {
578-
CVPixelBufferRef pixelBuffer = _latestPixelBuffer;
579-
while (!OSAtomicCompareAndSwapPtrBarrier(pixelBuffer, nil, (void **)&_latestPixelBuffer)) {
580-
pixelBuffer = _latestPixelBuffer;
581-
}
582-
606+
__block CVPixelBufferRef pixelBuffer = nil;
607+
// Use `dispatch_sync` because `copyPixelBuffer` API requires synchronous return.
608+
dispatch_sync(self.pixelBufferSynchronizationQueue, ^{
609+
pixelBuffer = self.latestPixelBuffer;
610+
self.latestPixelBuffer = nil;
611+
});
583612
return pixelBuffer;
584613
}
585614

0 commit comments

Comments
 (0)