From d226a69d14217d313d6bdd9bb612ec6549696e3c Mon Sep 17 00:00:00 2001 From: misos1 <30872003+misos1@users.noreply.github.com> Date: Wed, 2 Apr 2025 13:36:38 +0200 Subject: [PATCH 1/6] handle interruptions and use single offset --- .../camera/lib/src/camera_controller.dart | 11 +- .../camera/camera_avfoundation/CHANGELOG.md | 5 + .../example/ios/RunnerTests/CameraTestUtils.h | 8 + .../example/ios/RunnerTests/CameraTestUtils.m | 24 ++- .../ios/RunnerTests/FLTCamSampleBufferTests.m | 166 ++++++++++++++++++ .../Sources/camera_avfoundation/FLTCam.m | 148 ++++++++-------- .../include/camera_avfoundation/FLTCam_Test.h | 3 + .../camera/camera_avfoundation/pubspec.yaml | 2 +- 8 files changed, 288 insertions(+), 79 deletions(-) diff --git a/packages/camera/camera/lib/src/camera_controller.dart b/packages/camera/camera/lib/src/camera_controller.dart index 3f39d3202b8f..8fbe65fdb1f0 100644 --- a/packages/camera/camera/lib/src/camera_controller.dart +++ b/packages/camera/camera/lib/src/camera_controller.dart @@ -174,7 +174,7 @@ class CameraValue { }) { return CameraValue( isInitialized: isInitialized ?? this.isInitialized, - errorDescription: errorDescription, + errorDescription: errorDescription ?? this.errorDescription, previewSize: previewSize ?? this.previewSize, isRecordingVideo: isRecordingVideo ?? this.isRecordingVideo, isTakingPicture: isTakingPicture ?? this.isTakingPicture, @@ -353,6 +353,15 @@ class CameraController extends ValueNotifier { initializeCompleter.complete(event); })); + _unawaited(CameraPlatform.instance + .onCameraError(_cameraId) + .first + .then((CameraErrorEvent event) { + value = value.copyWith( + errorDescription: event.description, + ); + })); + await CameraPlatform.instance.initializeCamera( _cameraId, imageFormatGroup: imageFormatGroup ?? ImageFormatGroup.unknown, diff --git a/packages/camera/camera_avfoundation/CHANGELOG.md b/packages/camera/camera_avfoundation/CHANGELOG.md index d40aeca0e555..ada64a50290a 100644 --- a/packages/camera/camera_avfoundation/CHANGELOG.md +++ b/packages/camera/camera_avfoundation/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.9.18+14 + +* Handle video and audio interruptions and errors. +* Use a single time offset for both video and audio. + ## 0.9.18+1 * Refactors implementations to reduce usage of OCMock in internal testing. diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h index 008ba9df48f5..6b9c08876ee9 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h @@ -47,8 +47,16 @@ extern FLTCam *FLTCreateCamWithVideoDimensionsForFormat( /// @return a test sample buffer. extern CMSampleBufferRef FLTCreateTestSampleBuffer(void); +/// Creates a test sample buffer with timing. +/// @return a test sample buffer. +extern CMSampleBufferRef FLTCreateTestSampleBufferWithTiming(CMTime timestamp, CMTime duration); + /// Creates a test audio sample buffer. /// @return a test audio sample buffer. extern CMSampleBufferRef FLTCreateTestAudioSampleBuffer(void); +/// Creates a test audio sample buffer with timing. +/// @return a test audio sample buffer. +extern CMSampleBufferRef FLTCreateTestAudioSampleBufferWithTiming(CMTime timestamp, CMTime duration); + NS_ASSUME_NONNULL_END diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m index f10a3b122d7b..4494958f55d7 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m @@ -54,10 +54,12 @@ OCMStub([videoSessionMock addInputWithNoConnections:[OCMArg any]]); OCMStub([videoSessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES); + OCMStub([videoSessionMock isRunning]).andReturn(YES); id audioSessionMock = OCMClassMock([AVCaptureSession class]); OCMStub([audioSessionMock addInputWithNoConnections:[OCMArg any]]); OCMStub([audioSessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES); + OCMStub([audioSessionMock isRunning]).andReturn(YES); id frameRateRangeMock1 = OCMClassMock([AVFrameRateRange class]); OCMStub([frameRateRangeMock1 minFrameRate]).andReturn(3); @@ -178,7 +180,7 @@ error:nil]; } -CMSampleBufferRef FLTCreateTestSampleBuffer(void) { +CMSampleBufferRef FLTCreateTestSampleBufferWithTiming(CMTime timestamp, CMTime duration) { CVPixelBufferRef pixelBuffer; CVPixelBufferCreate(kCFAllocatorDefault, 100, 100, kCVPixelFormatType_32BGRA, NULL, &pixelBuffer); @@ -186,7 +188,7 @@ CMSampleBufferRef FLTCreateTestSampleBuffer(void) { CMVideoFormatDescriptionCreateForImageBuffer(kCFAllocatorDefault, pixelBuffer, &formatDescription); - CMSampleTimingInfo timingInfo = {CMTimeMake(1, 44100), kCMTimeZero, kCMTimeInvalid}; + CMSampleTimingInfo timingInfo = {duration, timestamp, kCMTimeInvalid}; CMSampleBufferRef sampleBuffer; CMSampleBufferCreateReadyWithImageBuffer(kCFAllocatorDefault, pixelBuffer, formatDescription, @@ -197,21 +199,29 @@ CMSampleBufferRef FLTCreateTestSampleBuffer(void) { return sampleBuffer; } -CMSampleBufferRef FLTCreateTestAudioSampleBuffer(void) { +CMSampleBufferRef FLTCreateTestSampleBuffer(void) { + return FLTCreateTestSampleBufferWithTiming(kCMTimeZero, CMTimeMake(1, 44100)); +} + +CMSampleBufferRef FLTCreateTestAudioSampleBufferWithTiming(CMTime timestamp, CMTime duration) { CMBlockBufferRef blockBuffer; - CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault, NULL, 100, kCFAllocatorDefault, NULL, 0, - 100, kCMBlockBufferAssureMemoryNowFlag, &blockBuffer); + CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault, NULL, duration.value, kCFAllocatorDefault, NULL, 0, + duration.value, kCMBlockBufferAssureMemoryNowFlag, &blockBuffer); CMFormatDescriptionRef formatDescription; - AudioStreamBasicDescription basicDescription = {44100, kAudioFormatLinearPCM, 0, 1, 1, 1, 1, 8}; + AudioStreamBasicDescription basicDescription = {duration.timescale, kAudioFormatLinearPCM, 0, 1, 1, 1, 1, 8}; CMAudioFormatDescriptionCreate(kCFAllocatorDefault, &basicDescription, 0, NULL, 0, NULL, NULL, &formatDescription); CMSampleBufferRef sampleBuffer; CMAudioSampleBufferCreateReadyWithPacketDescriptions( - kCFAllocatorDefault, blockBuffer, formatDescription, 1, kCMTimeZero, NULL, &sampleBuffer); + kCFAllocatorDefault, blockBuffer, formatDescription, duration.value, timestamp, NULL, &sampleBuffer); CFRelease(blockBuffer); CFRelease(formatDescription); return sampleBuffer; } + +CMSampleBufferRef FLTCreateTestAudioSampleBuffer(void) { + return FLTCreateTestAudioSampleBufferWithTiming(kCMTimeZero, CMTimeMake(1, 44100)); +} diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamSampleBufferTests.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamSampleBufferTests.m index e92d1d21da5c..cbcd947773d5 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamSampleBufferTests.m +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamSampleBufferTests.m @@ -361,4 +361,170 @@ - (void)testStartVideoRecordingWithCompletionShouldNotDisableMixWithOthers { @"Category should be PlayAndRecord."); } +- (void)testDidOutputSampleBufferMustUseSingleOffsetForVideoAndAudio { + FLTCam *cam = FLTCreateCamWithCaptureSessionQueue(dispatch_queue_create("testing", NULL)); + + id connectionMock = OCMClassMock([AVCaptureConnection class]); + + 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]; + }); + + __block CMTime appendedTime; + + id adaptorMock = OCMClassMock([AVAssetWriterInputPixelBufferAdaptor class]); + OCMStub([adaptorMock assetWriterInputPixelBufferAdaptorWithAssetWriterInput:OCMOCK_ANY + sourcePixelBufferAttributes:OCMOCK_ANY]) + .andReturn(adaptorMock); + OCMStub([adaptorMock appendPixelBuffer:[OCMArg anyPointer] withPresentationTime:kCMTimeZero]) + .ignoringNonObjectArgs() + .andDo(^(NSInvocation *invocation) { + [invocation getArgument:&appendedTime atIndex:3]; + }); + + id inputMock = OCMClassMock([AVAssetWriterInput class]); + OCMStub([inputMock assetWriterInputWithMediaType:OCMOCK_ANY outputSettings:OCMOCK_ANY]) + .andReturn(inputMock); + OCMStub([inputMock isReadyForMoreMediaData]).andReturn(YES); + OCMStub([inputMock appendSampleBuffer:[OCMArg anyPointer]]).andDo(^(NSInvocation *invocation) { + CMSampleBufferRef sampleBuffer; + [invocation getArgument:&sampleBuffer atIndex:2]; + appendedTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer); + }); + + [cam + startVideoRecordingWithCompletion:^(FlutterError *_Nullable error) { + } + messengerForStreaming:nil]; + + appendedTime = kCMTimeInvalid; + [cam pauseVideoRecording]; + [cam resumeVideoRecording]; + [cam captureOutput:cam.captureVideoOutput + didOutputSampleBuffer:FLTCreateTestSampleBufferWithTiming(CMTimeMake(1, 1), kCMTimeInvalid) + fromConnection:connectionMock]; + XCTAssert(CMTIME_COMPARE_INLINE(appendedTime, ==, CMTimeMake(1, 1))); + + appendedTime = kCMTimeInvalid; + [cam pauseVideoRecording]; + [cam resumeVideoRecording]; + [cam captureOutput:cam.captureVideoOutput + didOutputSampleBuffer:FLTCreateTestSampleBufferWithTiming(CMTimeMake(11, 1), kCMTimeInvalid) + fromConnection:connectionMock]; + XCTAssert(CMTIME_COMPARE_INLINE(appendedTime, ==, kCMTimeInvalid)); + [cam captureOutput:cam.captureVideoOutput + didOutputSampleBuffer:FLTCreateTestSampleBufferWithTiming(CMTimeMake(12, 1), kCMTimeInvalid) + fromConnection:connectionMock]; + XCTAssert(CMTIME_COMPARE_INLINE(appendedTime, ==, CMTimeMake(2, 1))); + + appendedTime = kCMTimeInvalid; + [cam pauseVideoRecording]; + [cam resumeVideoRecording]; + [cam captureOutput:nil + didOutputSampleBuffer:FLTCreateTestAudioSampleBufferWithTiming(CMTimeMake(20, 1), CMTimeMake(2, 1)) + fromConnection:connectionMock]; + XCTAssert(CMTIME_COMPARE_INLINE(appendedTime, ==, kCMTimeInvalid)); + [cam captureOutput:cam.captureVideoOutput + didOutputSampleBuffer:FLTCreateTestSampleBufferWithTiming(CMTimeMake(23, 1), kCMTimeInvalid) + fromConnection:connectionMock]; + XCTAssert(CMTIME_COMPARE_INLINE(appendedTime, ==, CMTimeMake(3, 1))); + + appendedTime = kCMTimeInvalid; + [cam pauseVideoRecording]; + [cam resumeVideoRecording]; + [cam captureOutput:cam.captureVideoOutput + didOutputSampleBuffer:FLTCreateTestSampleBufferWithTiming(CMTimeMake(28, 1), kCMTimeInvalid) + fromConnection:connectionMock]; + XCTAssert(CMTIME_COMPARE_INLINE(appendedTime, ==, kCMTimeInvalid)); + [cam captureOutput:nil + didOutputSampleBuffer:FLTCreateTestAudioSampleBufferWithTiming(CMTimeMake(30, 1), CMTimeMake(2, 1)) + fromConnection:connectionMock]; + XCTAssert(CMTIME_COMPARE_INLINE(appendedTime, ==, kCMTimeInvalid)); + [cam captureOutput:cam.captureVideoOutput + didOutputSampleBuffer:FLTCreateTestSampleBufferWithTiming(CMTimeMake(33, 1), kCMTimeInvalid) + fromConnection:connectionMock]; + XCTAssert(CMTIME_COMPARE_INLINE(appendedTime, ==, kCMTimeInvalid)); + [cam captureOutput:nil + didOutputSampleBuffer:FLTCreateTestAudioSampleBufferWithTiming(CMTimeMake(32, 1), CMTimeMake(2, 1)) + fromConnection:connectionMock]; + XCTAssert(CMTIME_COMPARE_INLINE(appendedTime, ==, CMTimeMake(2, 1))); +} + +- (void)testDidOutputSampleBufferMustConnectVideoAfterSessionInterruption { + FLTCam *cam = FLTCreateCamWithCaptureSessionQueue(dispatch_queue_create("testing", NULL)); + + id connectionMock = OCMClassMock([AVCaptureConnection class]); + + 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]; + }); + + __block CMTime appendedTime; + + id adaptorMock = OCMClassMock([AVAssetWriterInputPixelBufferAdaptor class]); + OCMStub([adaptorMock assetWriterInputPixelBufferAdaptorWithAssetWriterInput:OCMOCK_ANY + sourcePixelBufferAttributes:OCMOCK_ANY]) + .andReturn(adaptorMock); + OCMStub([adaptorMock appendPixelBuffer:[OCMArg anyPointer] withPresentationTime:kCMTimeZero]) + .ignoringNonObjectArgs() + .andDo(^(NSInvocation *invocation) { + [invocation getArgument:&appendedTime atIndex:3]; + }); + + id inputMock = OCMClassMock([AVAssetWriterInput class]); + OCMStub([inputMock assetWriterInputWithMediaType:OCMOCK_ANY outputSettings:OCMOCK_ANY]) + .andReturn(inputMock); + OCMStub([inputMock isReadyForMoreMediaData]).andReturn(YES); + OCMStub([inputMock appendSampleBuffer:[OCMArg anyPointer]]).andDo(^(NSInvocation *invocation) { + CMSampleBufferRef sampleBuffer; + [invocation getArgument:&sampleBuffer atIndex:2]; + appendedTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer); + }); + + [cam + startVideoRecordingWithCompletion:^(FlutterError *_Nullable error) { + } + messengerForStreaming:nil]; + + [cam captureOutput:cam.captureVideoOutput + didOutputSampleBuffer:FLTCreateTestSampleBufferWithTiming(CMTimeMake(1, 1), kCMTimeInvalid) + fromConnection:connectionMock]; + [cam captureOutput:nil + didOutputSampleBuffer:FLTCreateTestAudioSampleBufferWithTiming(CMTimeMake(1, 1), CMTimeMake(1, 1)) + fromConnection:connectionMock]; + + [NSNotificationCenter.defaultCenter postNotificationName:AVCaptureSessionWasInterruptedNotification object:cam.audioCaptureSession]; + + appendedTime = kCMTimeInvalid; + [cam captureOutput:nil + didOutputSampleBuffer:FLTCreateTestAudioSampleBufferWithTiming(CMTimeMake(11, 1), CMTimeMake(1, 1)) + fromConnection:connectionMock]; + XCTAssert(CMTIME_COMPARE_INLINE(appendedTime, ==, kCMTimeInvalid)); + [cam captureOutput:cam.captureVideoOutput + didOutputSampleBuffer:FLTCreateTestSampleBufferWithTiming(CMTimeMake(12, 1), kCMTimeInvalid) + fromConnection:connectionMock]; + XCTAssert(CMTIME_COMPARE_INLINE(appendedTime, ==, CMTimeMake(2, 1))); + appendedTime = kCMTimeInvalid; + [cam captureOutput:nil + didOutputSampleBuffer:FLTCreateTestAudioSampleBufferWithTiming(CMTimeMake(12, 1), CMTimeMake(1, 1)) + fromConnection:connectionMock]; + XCTAssert(CMTIME_COMPARE_INLINE(appendedTime, ==, CMTimeMake(2, 1))); +} + @end diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTCam.m b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTCam.m index 577460b1a868..99680b0e0570 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTCam.m +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTCam.m @@ -56,8 +56,6 @@ @interface FLTCam () , _lastAppendedVideoSampleTime)) { [_videoAdaptor appendPixelBuffer:nextBuffer withPresentationTime:nextSampleTime]; + _lastAppendedVideoSampleTime = nextSampleTime; } } else { - CMTime dur = CMSampleBufferGetDuration(sampleBuffer); - - if (dur.value > 0) { - currentSampleTime = CMTimeAdd(currentSampleTime, dur); - } - - if (_audioIsDisconnected) { - _audioIsDisconnected = NO; - - if (_audioTimeOffset.value == 0) { - _audioTimeOffset = CMTimeSubtract(currentSampleTime, _lastAudioSampleTime); - } else { - CMTime offset = CMTimeSubtract(currentSampleTime, _lastAudioSampleTime); - _audioTimeOffset = CMTimeAdd(_audioTimeOffset, offset); - } - - return; - } - - _lastAudioSampleTime = currentSampleTime; - - if (_audioTimeOffset.value != 0) { + if (_recordingTimeOffset.value != 0) { CMSampleBufferRef adjustedSampleBuffer = - [self copySampleBufferWithAdjustedTime:sampleBuffer by:_audioTimeOffset]; + [self copySampleBufferWithAdjustedTime:sampleBuffer by:_recordingTimeOffset]; [self newAudioSample:adjustedSampleBuffer]; CFRelease(adjustedSampleBuffer); } else { @@ -820,10 +832,8 @@ - (void)newVideoSample:(CMSampleBufferRef)sampleBuffer { } return; } - if (_videoWriterInput.readyForMoreMediaData) { - if (![_videoWriterInput appendSampleBuffer:sampleBuffer]) { - [self reportErrorMessage:@"Unable to write to video input"]; - } + if (![_videoWriterInput appendSampleBuffer:sampleBuffer]) { + [self reportErrorMessage:@"Unable to write to video input"]; } } @@ -834,10 +844,8 @@ - (void)newAudioSample:(CMSampleBufferRef)sampleBuffer { } return; } - if (_audioWriterInput.readyForMoreMediaData) { - if (![_audioWriterInput appendSampleBuffer:sampleBuffer]) { - [self reportErrorMessage:@"Unable to write to audio input"]; - } + if (![_audioWriterInput appendSampleBuffer:sampleBuffer]) { + [self reportErrorMessage:@"Unable to write to audio input"]; } } @@ -862,6 +870,7 @@ - (void)dealloc { CFRelease(_latestPixelBuffer); } [_motionManager stopAccelerometerUpdates]; + [NSNotificationCenter.defaultCenter removeObserver:self]; } - (CVPixelBufferRef)copyPixelBuffer { @@ -907,10 +916,10 @@ - (void)startVideoRecordingWithCompletion:(void (^)(FlutterError *_Nullable))com _isFirstVideoSample = YES; _isRecording = YES; _isRecordingPaused = NO; - _videoTimeOffset = CMTimeMake(0, 1); - _audioTimeOffset = CMTimeMake(0, 1); - _videoIsDisconnected = NO; - _audioIsDisconnected = NO; + _isRecordingDisconnected = NO; + _recordingTimeOffset = CMTimeMake(0, 1); + _outputForOffsetAdjusting = _captureVideoOutput; + _lastAppendedVideoSampleTime = kCMTimeNegativeInfinity; completion(nil); } else { completion([FlutterError errorWithCode:@"Error" @@ -950,8 +959,7 @@ - (void)stopVideoRecordingWithCompletion:(void (^)(NSString *_Nullable, - (void)pauseVideoRecording { _isRecordingPaused = YES; - _videoIsDisconnected = YES; - _audioIsDisconnected = YES; + _isRecordingDisconnected = YES; } - (void)resumeVideoRecording { diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCam_Test.h b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCam_Test.h index 7e27e31a8880..a9ce437d2512 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCam_Test.h +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCam_Test.h @@ -40,6 +40,9 @@ typedef id (^CaptureDeviceFactory)(void); /// True when images from the camera are being streamed. @property(assign, nonatomic) BOOL isStreamingImages; +@property(readonly, nonatomic) AVCaptureSession *videoCaptureSession; +@property(readonly, nonatomic) AVCaptureSession *audioCaptureSession; + /// 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 diff --git a/packages/camera/camera_avfoundation/pubspec.yaml b/packages/camera/camera_avfoundation/pubspec.yaml index d5546a0fafb7..1d40fd04c635 100644 --- a/packages/camera/camera_avfoundation/pubspec.yaml +++ b/packages/camera/camera_avfoundation/pubspec.yaml @@ -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.18+1 +version: 0.9.18+14 environment: sdk: ^3.4.0 From aecdf795382c7c0161bce4271bf126e33ab2abb3 Mon Sep 17 00:00:00 2001 From: misos1 <30872003+misos1@users.noreply.github.com> Date: Wed, 2 Apr 2025 15:53:23 +0200 Subject: [PATCH 2/6] fix format --- .../example/ios/RunnerTests/CameraTestUtils.h | 3 ++- .../example/ios/RunnerTests/CameraTestUtils.m | 13 ++++++++----- .../ios/RunnerTests/FLTCamSampleBufferTests.m | 18 ++++++++++++------ .../Sources/camera_avfoundation/FLTCam.m | 9 ++++++--- 4 files changed, 28 insertions(+), 15 deletions(-) diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h index 6b9c08876ee9..1d3ae18e4aa0 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h @@ -57,6 +57,7 @@ extern CMSampleBufferRef FLTCreateTestAudioSampleBuffer(void); /// Creates a test audio sample buffer with timing. /// @return a test audio sample buffer. -extern CMSampleBufferRef FLTCreateTestAudioSampleBufferWithTiming(CMTime timestamp, CMTime duration); +extern CMSampleBufferRef FLTCreateTestAudioSampleBufferWithTiming(CMTime timestamp, + CMTime duration); NS_ASSUME_NONNULL_END diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m index 4494958f55d7..f784bb0664c9 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m @@ -205,17 +205,20 @@ CMSampleBufferRef FLTCreateTestSampleBuffer(void) { CMSampleBufferRef FLTCreateTestAudioSampleBufferWithTiming(CMTime timestamp, CMTime duration) { CMBlockBufferRef blockBuffer; - CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault, NULL, duration.value, kCFAllocatorDefault, NULL, 0, - duration.value, kCMBlockBufferAssureMemoryNowFlag, &blockBuffer); + CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault, NULL, duration.value, kCFAllocatorDefault, + NULL, 0, duration.value, kCMBlockBufferAssureMemoryNowFlag, + &blockBuffer); CMFormatDescriptionRef formatDescription; - AudioStreamBasicDescription basicDescription = {duration.timescale, kAudioFormatLinearPCM, 0, 1, 1, 1, 1, 8}; + AudioStreamBasicDescription basicDescription = { + duration.timescale, kAudioFormatLinearPCM, 0, 1, 1, 1, 1, 8}; CMAudioFormatDescriptionCreate(kCFAllocatorDefault, &basicDescription, 0, NULL, 0, NULL, NULL, &formatDescription); CMSampleBufferRef sampleBuffer; - CMAudioSampleBufferCreateReadyWithPacketDescriptions( - kCFAllocatorDefault, blockBuffer, formatDescription, duration.value, timestamp, NULL, &sampleBuffer); + CMAudioSampleBufferCreateReadyWithPacketDescriptions(kCFAllocatorDefault, blockBuffer, + formatDescription, duration.value, timestamp, + NULL, &sampleBuffer); CFRelease(blockBuffer); CFRelease(formatDescription); diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamSampleBufferTests.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamSampleBufferTests.m index cbcd947773d5..7de7c49070ec 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamSampleBufferTests.m +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamSampleBufferTests.m @@ -429,7 +429,8 @@ - (void)testDidOutputSampleBufferMustUseSingleOffsetForVideoAndAudio { [cam pauseVideoRecording]; [cam resumeVideoRecording]; [cam captureOutput:nil - didOutputSampleBuffer:FLTCreateTestAudioSampleBufferWithTiming(CMTimeMake(20, 1), CMTimeMake(2, 1)) + didOutputSampleBuffer:FLTCreateTestAudioSampleBufferWithTiming(CMTimeMake(20, 1), + CMTimeMake(2, 1)) fromConnection:connectionMock]; XCTAssert(CMTIME_COMPARE_INLINE(appendedTime, ==, kCMTimeInvalid)); [cam captureOutput:cam.captureVideoOutput @@ -445,7 +446,8 @@ - (void)testDidOutputSampleBufferMustUseSingleOffsetForVideoAndAudio { fromConnection:connectionMock]; XCTAssert(CMTIME_COMPARE_INLINE(appendedTime, ==, kCMTimeInvalid)); [cam captureOutput:nil - didOutputSampleBuffer:FLTCreateTestAudioSampleBufferWithTiming(CMTimeMake(30, 1), CMTimeMake(2, 1)) + didOutputSampleBuffer:FLTCreateTestAudioSampleBufferWithTiming(CMTimeMake(30, 1), + CMTimeMake(2, 1)) fromConnection:connectionMock]; XCTAssert(CMTIME_COMPARE_INLINE(appendedTime, ==, kCMTimeInvalid)); [cam captureOutput:cam.captureVideoOutput @@ -453,7 +455,8 @@ - (void)testDidOutputSampleBufferMustUseSingleOffsetForVideoAndAudio { fromConnection:connectionMock]; XCTAssert(CMTIME_COMPARE_INLINE(appendedTime, ==, kCMTimeInvalid)); [cam captureOutput:nil - didOutputSampleBuffer:FLTCreateTestAudioSampleBufferWithTiming(CMTimeMake(32, 1), CMTimeMake(2, 1)) + didOutputSampleBuffer:FLTCreateTestAudioSampleBufferWithTiming(CMTimeMake(32, 1), + CMTimeMake(2, 1)) fromConnection:connectionMock]; XCTAssert(CMTIME_COMPARE_INLINE(appendedTime, ==, CMTimeMake(2, 1))); } @@ -506,14 +509,16 @@ - (void)testDidOutputSampleBufferMustConnectVideoAfterSessionInterruption { didOutputSampleBuffer:FLTCreateTestSampleBufferWithTiming(CMTimeMake(1, 1), kCMTimeInvalid) fromConnection:connectionMock]; [cam captureOutput:nil - didOutputSampleBuffer:FLTCreateTestAudioSampleBufferWithTiming(CMTimeMake(1, 1), CMTimeMake(1, 1)) + didOutputSampleBuffer:FLTCreateTestAudioSampleBufferWithTiming(CMTimeMake(1, 1), + CMTimeMake(1, 1)) fromConnection:connectionMock]; [NSNotificationCenter.defaultCenter postNotificationName:AVCaptureSessionWasInterruptedNotification object:cam.audioCaptureSession]; appendedTime = kCMTimeInvalid; [cam captureOutput:nil - didOutputSampleBuffer:FLTCreateTestAudioSampleBufferWithTiming(CMTimeMake(11, 1), CMTimeMake(1, 1)) + didOutputSampleBuffer:FLTCreateTestAudioSampleBufferWithTiming(CMTimeMake(11, 1), + CMTimeMake(1, 1)) fromConnection:connectionMock]; XCTAssert(CMTIME_COMPARE_INLINE(appendedTime, ==, kCMTimeInvalid)); [cam captureOutput:cam.captureVideoOutput @@ -522,7 +527,8 @@ - (void)testDidOutputSampleBufferMustConnectVideoAfterSessionInterruption { XCTAssert(CMTIME_COMPARE_INLINE(appendedTime, ==, CMTimeMake(2, 1))); appendedTime = kCMTimeInvalid; [cam captureOutput:nil - didOutputSampleBuffer:FLTCreateTestAudioSampleBufferWithTiming(CMTimeMake(12, 1), CMTimeMake(1, 1)) + didOutputSampleBuffer:FLTCreateTestAudioSampleBufferWithTiming(CMTimeMake(12, 1), + CMTimeMake(1, 1)) fromConnection:connectionMock]; XCTAssert(CMTIME_COMPARE_INLINE(appendedTime, ==, CMTimeMake(2, 1))); } diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTCam.m b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTCam.m index 99680b0e0570..4751b55c643c 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTCam.m +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTCam.m @@ -312,7 +312,7 @@ - (instancetype)initWithMediaSettings:(FCPPlatformMediaSettings *)mediaSettings // Handle video and audio interruptions and errors. // https://github.com/flutter/flutter/issues/151253 - for (AVCaptureSession *session in @[_videoCaptureSession, _audioCaptureSession]) { + for (AVCaptureSession *session in @[ _videoCaptureSession, _audioCaptureSession ]) { [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(captureSessionWasInterrupted:) name:AVCaptureSessionWasInterruptedNotification @@ -332,7 +332,9 @@ - (void)captureSessionWasInterrupted:(NSNotification *)notification { } - (void)captureSessionRuntimeError:(NSNotification *)notification { - [self reportErrorMessage:[NSString stringWithFormat:@"%@", notification.userInfo[AVCaptureSessionErrorKey]]]; + [self reportErrorMessage:[NSString + stringWithFormat:@"%@", + notification.userInfo[AVCaptureSessionErrorKey]]]; } - (AVCaptureConnection *)createConnection:(NSError **)error { @@ -736,7 +738,8 @@ - (void)captureOutput:(AVCaptureOutput *)output }); } } - if (_isRecording && !_isRecordingPaused && _videoCaptureSession.running && _audioCaptureSession.running) { + if (_isRecording && !_isRecordingPaused && _videoCaptureSession.running && + _audioCaptureSession.running) { if (_videoWriter.status == AVAssetWriterStatusFailed) { [self reportErrorMessage:[NSString stringWithFormat:@"%@", _videoWriter.error]]; return; From 9b3b563cd7b87a5d744ad71dd49aa1e586813c70 Mon Sep 17 00:00:00 2001 From: misos1 <30872003+misos1@users.noreply.github.com> Date: Wed, 2 Apr 2025 20:19:11 +0200 Subject: [PATCH 3/6] fix format --- packages/camera/camera/CHANGELOG.md | 4 + packages/camera/camera/pubspec.yaml | 2 +- .../example/ios/RunnerTests/CameraTestUtils.h | 63 -- .../example/ios/RunnerTests/CameraTestUtils.m | 230 -------- .../ios/RunnerTests/CameraTestUtils.swift | 8 +- .../ios/RunnerTests/FLTCamSampleBufferTests.m | 536 ------------------ .../Mocks/MockCaptureSession.swift | 4 +- .../ios/RunnerTests/SampleBufferTests.swift | 40 +- .../Sources/camera_avfoundation/FLTCam.m | 4 +- .../camera_avfoundation/FLTCaptureSession.m | 2 +- 10 files changed, 36 insertions(+), 857 deletions(-) delete mode 100644 packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h delete mode 100644 packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m delete mode 100644 packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamSampleBufferTests.m diff --git a/packages/camera/camera/CHANGELOG.md b/packages/camera/camera/CHANGELOG.md index d0d58be6ca92..f37c6f60c5f2 100644 --- a/packages/camera/camera/CHANGELOG.md +++ b/packages/camera/camera/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.11.1+1 + +* Fixes delivering errors from onCameraError. + ## 0.11.1 * Adds API support query for image streaming. diff --git a/packages/camera/camera/pubspec.yaml b/packages/camera/camera/pubspec.yaml index 09899dfa5796..ccdd49602587 100644 --- a/packages/camera/camera/pubspec.yaml +++ b/packages/camera/camera/pubspec.yaml @@ -4,7 +4,7 @@ description: A Flutter plugin for controlling the camera. Supports previewing Dart. repository: https://github.com/flutter/packages/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.11.1 +version: 0.11.1+1 environment: sdk: ^3.6.0 diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h deleted file mode 100644 index 1d3ae18e4aa0..000000000000 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h +++ /dev/null @@ -1,63 +0,0 @@ -// 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_avfoundation; -#if __has_include() -@import camera_avfoundation.Test; -#endif - -NS_ASSUME_NONNULL_BEGIN - -/// Creates an `FLTCam` that runs its capture session operations on a given queue. -/// @param captureSessionQueue the capture session queue -/// @param mediaSettings media settings configuration parameters -/// @param mediaSettingsAVWrapper provider to perform media settings operations (for unit test -/// dependency injection). -/// @param captureDeviceFactory a callback to create capture device instances -/// @return an FLTCam object. -extern FLTCam *_Nullable FLTCreateCamWithCaptureSessionQueueAndMediaSettings( - dispatch_queue_t _Nullable captureSessionQueue, - FCPPlatformMediaSettings *_Nullable mediaSettings, - FLTCamMediaSettingsAVWrapper *_Nullable mediaSettingsAVWrapper, - CaptureDeviceFactory _Nullable captureDeviceFactory, - id _Nullable deviceOrientationProvider); - -extern FLTCam *FLTCreateCamWithCaptureSessionQueue(dispatch_queue_t captureSessionQueue); - -/// Creates an `FLTCam` with a given captureSession and resolutionPreset -/// @param captureSession AVCaptureSession for video -/// @param resolutionPreset preset for camera's captureSession resolution -/// @return an FLTCam object. -extern FLTCam *FLTCreateCamWithVideoCaptureSession(AVCaptureSession *captureSession, - FCPPlatformResolutionPreset resolutionPreset); - -/// Creates an `FLTCam` with a given captureSession and resolutionPreset. -/// Allows to inject a capture device and a block to compute the video dimensions. -/// @param captureSession AVCaptureSession for video -/// @param resolutionPreset preset for camera's captureSession resolution -/// @param captureDevice AVCaptureDevice to be used -/// @param videoDimensionsForFormat custom code to determine video dimensions -/// @return an FLTCam object. -extern FLTCam *FLTCreateCamWithVideoDimensionsForFormat( - AVCaptureSession *captureSession, FCPPlatformResolutionPreset resolutionPreset, - AVCaptureDevice *captureDevice, VideoDimensionsForFormat videoDimensionsForFormat); - -/// Creates a test sample buffer. -/// @return a test sample buffer. -extern CMSampleBufferRef FLTCreateTestSampleBuffer(void); - -/// Creates a test sample buffer with timing. -/// @return a test sample buffer. -extern CMSampleBufferRef FLTCreateTestSampleBufferWithTiming(CMTime timestamp, CMTime duration); - -/// Creates a test audio sample buffer. -/// @return a test audio sample buffer. -extern CMSampleBufferRef FLTCreateTestAudioSampleBuffer(void); - -/// Creates a test audio sample buffer with timing. -/// @return a test audio sample buffer. -extern CMSampleBufferRef FLTCreateTestAudioSampleBufferWithTiming(CMTime timestamp, - CMTime duration); - -NS_ASSUME_NONNULL_END diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m deleted file mode 100644 index f784bb0664c9..000000000000 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m +++ /dev/null @@ -1,230 +0,0 @@ -// 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 -@import AVFoundation; -@import camera_avfoundation; - -#import "MockDeviceOrientationProvider.h" - -static FCPPlatformMediaSettings *FCPGetDefaultMediaSettings( - FCPPlatformResolutionPreset resolutionPreset) { - return [FCPPlatformMediaSettings makeWithResolutionPreset:resolutionPreset - framesPerSecond:nil - videoBitrate:nil - audioBitrate:nil - enableAudio:YES]; -} - -FLTCam *FLTCreateCamWithCaptureSessionQueue(dispatch_queue_t captureSessionQueue) { - return FLTCreateCamWithCaptureSessionQueueAndMediaSettings(captureSessionQueue, nil, nil, nil, - nil); -} - -FLTCam *FLTCreateCamWithCaptureSessionQueueAndMediaSettings( - dispatch_queue_t captureSessionQueue, FCPPlatformMediaSettings *mediaSettings, - FLTCamMediaSettingsAVWrapper *mediaSettingsAVWrapper, CaptureDeviceFactory captureDeviceFactory, - id deviceOrientationProvider) { - if (!mediaSettings) { - mediaSettings = FCPGetDefaultMediaSettings(FCPPlatformResolutionPresetMedium); - } - - if (!mediaSettingsAVWrapper) { - mediaSettingsAVWrapper = [[FLTCamMediaSettingsAVWrapper alloc] init]; - } - - if (!deviceOrientationProvider) { - deviceOrientationProvider = [[MockDeviceOrientationProvider alloc] init]; - } - - id inputMock = OCMClassMock([AVCaptureDeviceInput class]); - OCMStub([inputMock deviceInputWithDevice:[OCMArg any] error:[OCMArg setTo:nil]]) - .andReturn(inputMock); - - id videoSessionMock = OCMClassMock([AVCaptureSession class]); - OCMStub([videoSessionMock beginConfiguration]) - .andDo(^(NSInvocation *invocation){ - }); - OCMStub([videoSessionMock commitConfiguration]) - .andDo(^(NSInvocation *invocation){ - }); - - OCMStub([videoSessionMock addInputWithNoConnections:[OCMArg any]]); - OCMStub([videoSessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES); - OCMStub([videoSessionMock isRunning]).andReturn(YES); - - id audioSessionMock = OCMClassMock([AVCaptureSession class]); - OCMStub([audioSessionMock addInputWithNoConnections:[OCMArg any]]); - OCMStub([audioSessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES); - OCMStub([audioSessionMock isRunning]).andReturn(YES); - - id frameRateRangeMock1 = OCMClassMock([AVFrameRateRange class]); - OCMStub([frameRateRangeMock1 minFrameRate]).andReturn(3); - OCMStub([frameRateRangeMock1 maxFrameRate]).andReturn(30); - id captureDeviceFormatMock1 = OCMClassMock([AVCaptureDeviceFormat class]); - OCMStub([captureDeviceFormatMock1 videoSupportedFrameRateRanges]).andReturn(@[ - frameRateRangeMock1 - ]); - - id frameRateRangeMock2 = OCMClassMock([AVFrameRateRange class]); - OCMStub([frameRateRangeMock2 minFrameRate]).andReturn(3); - OCMStub([frameRateRangeMock2 maxFrameRate]).andReturn(60); - id captureDeviceFormatMock2 = OCMClassMock([AVCaptureDeviceFormat class]); - OCMStub([captureDeviceFormatMock2 videoSupportedFrameRateRanges]).andReturn(@[ - frameRateRangeMock2 - ]); - - id captureDeviceMock = OCMClassMock([AVCaptureDevice class]); - OCMStub([captureDeviceMock lockForConfiguration:[OCMArg setTo:nil]]).andReturn(YES); - OCMStub([captureDeviceMock formats]).andReturn((@[ - captureDeviceFormatMock1, captureDeviceFormatMock2 - ])); - __block AVCaptureDeviceFormat *format = captureDeviceFormatMock1; - OCMStub([captureDeviceMock setActiveFormat:[OCMArg any]]).andDo(^(NSInvocation *invocation) { - [invocation retainArguments]; - [invocation getArgument:&format atIndex:2]; - }); - OCMStub([captureDeviceMock activeFormat]).andDo(^(NSInvocation *invocation) { - [invocation setReturnValue:&format]; - }); - - id fltCam = [[FLTCam alloc] initWithMediaSettings:mediaSettings - mediaSettingsAVWrapper:mediaSettingsAVWrapper - orientation:UIDeviceOrientationPortrait - videoCaptureSession:videoSessionMock - audioCaptureSession:audioSessionMock - captureSessionQueue:captureSessionQueue - captureDeviceFactory:captureDeviceFactory ?: ^id(void) { - return [[FLTDefaultCaptureDeviceController alloc] initWithDevice:captureDeviceMock]; - } - videoDimensionsForFormat:^CMVideoDimensions(AVCaptureDeviceFormat *format) { - return CMVideoFormatDescriptionGetDimensions(format.formatDescription); - } - deviceOrientationProvider:deviceOrientationProvider - error:nil]; - - id captureVideoDataOutputMock = [OCMockObject niceMockForClass:[AVCaptureVideoDataOutput class]]; - - OCMStub([captureVideoDataOutputMock new]).andReturn(captureVideoDataOutputMock); - - OCMStub([captureVideoDataOutputMock - recommendedVideoSettingsForAssetWriterWithOutputFileType:AVFileTypeMPEG4]) - .andReturn(@{}); - - OCMStub([captureVideoDataOutputMock sampleBufferCallbackQueue]).andReturn(captureSessionQueue); - - id videoMock = OCMClassMock([AVAssetWriterInputPixelBufferAdaptor class]); - OCMStub([videoMock assetWriterInputPixelBufferAdaptorWithAssetWriterInput:OCMOCK_ANY - sourcePixelBufferAttributes:OCMOCK_ANY]) - .andReturn(videoMock); - - id writerInputMock = [OCMockObject niceMockForClass:[AVAssetWriterInput class]]; - - OCMStub([writerInputMock assetWriterInputWithMediaType:AVMediaTypeAudio - outputSettings:[OCMArg any]]) - .andReturn(writerInputMock); - - OCMStub([writerInputMock assetWriterInputWithMediaType:AVMediaTypeVideo - outputSettings:[OCMArg any]]) - .andReturn(writerInputMock); - - return fltCam; -} - -FLTCam *FLTCreateCamWithVideoCaptureSession(AVCaptureSession *captureSession, - FCPPlatformResolutionPreset resolutionPreset) { - id inputMock = OCMClassMock([AVCaptureDeviceInput class]); - OCMStub([inputMock deviceInputWithDevice:[OCMArg any] error:[OCMArg setTo:nil]]) - .andReturn(inputMock); - - id audioSessionMock = OCMClassMock([AVCaptureSession class]); - OCMStub([audioSessionMock addInputWithNoConnections:[OCMArg any]]); - OCMStub([audioSessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES); - - return [[FLTCam alloc] initWithCameraName:@"camera" - mediaSettings:FCPGetDefaultMediaSettings(resolutionPreset) - mediaSettingsAVWrapper:[[FLTCamMediaSettingsAVWrapper alloc] init] - orientation:UIDeviceOrientationPortrait - videoCaptureSession:captureSession - audioCaptureSession:audioSessionMock - captureSessionQueue:dispatch_queue_create("capture_session_queue", NULL) - error:nil]; -} - -FLTCam *FLTCreateCamWithVideoDimensionsForFormat( - AVCaptureSession *captureSession, FCPPlatformResolutionPreset resolutionPreset, - AVCaptureDevice *captureDevice, VideoDimensionsForFormat videoDimensionsForFormat) { - id inputMock = OCMClassMock([AVCaptureDeviceInput class]); - OCMStub([inputMock deviceInputWithDevice:[OCMArg any] error:[OCMArg setTo:nil]]) - .andReturn(inputMock); - - id audioSessionMock = OCMClassMock([AVCaptureSession class]); - OCMStub([audioSessionMock addInputWithNoConnections:[OCMArg any]]); - OCMStub([audioSessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES); - - return [[FLTCam alloc] - initWithMediaSettings:FCPGetDefaultMediaSettings(resolutionPreset) - mediaSettingsAVWrapper:[[FLTCamMediaSettingsAVWrapper alloc] init] - orientation:UIDeviceOrientationPortrait - videoCaptureSession:captureSession - audioCaptureSession:audioSessionMock - captureSessionQueue:dispatch_queue_create("capture_session_queue", NULL) - captureDeviceFactory:^id(void) { - return [[FLTDefaultCaptureDeviceController alloc] initWithDevice:captureDevice]; - } - videoDimensionsForFormat:videoDimensionsForFormat - deviceOrientationProvider:[[MockDeviceOrientationProvider alloc] init] - error:nil]; -} - -CMSampleBufferRef FLTCreateTestSampleBufferWithTiming(CMTime timestamp, CMTime duration) { - CVPixelBufferRef pixelBuffer; - CVPixelBufferCreate(kCFAllocatorDefault, 100, 100, kCVPixelFormatType_32BGRA, NULL, &pixelBuffer); - - CMFormatDescriptionRef formatDescription; - CMVideoFormatDescriptionCreateForImageBuffer(kCFAllocatorDefault, pixelBuffer, - &formatDescription); - - CMSampleTimingInfo timingInfo = {duration, timestamp, kCMTimeInvalid}; - - CMSampleBufferRef sampleBuffer; - CMSampleBufferCreateReadyWithImageBuffer(kCFAllocatorDefault, pixelBuffer, formatDescription, - &timingInfo, &sampleBuffer); - - CFRelease(pixelBuffer); - CFRelease(formatDescription); - return sampleBuffer; -} - -CMSampleBufferRef FLTCreateTestSampleBuffer(void) { - return FLTCreateTestSampleBufferWithTiming(kCMTimeZero, CMTimeMake(1, 44100)); -} - -CMSampleBufferRef FLTCreateTestAudioSampleBufferWithTiming(CMTime timestamp, CMTime duration) { - CMBlockBufferRef blockBuffer; - CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault, NULL, duration.value, kCFAllocatorDefault, - NULL, 0, duration.value, kCMBlockBufferAssureMemoryNowFlag, - &blockBuffer); - - CMFormatDescriptionRef formatDescription; - AudioStreamBasicDescription basicDescription = { - duration.timescale, kAudioFormatLinearPCM, 0, 1, 1, 1, 1, 8}; - CMAudioFormatDescriptionCreate(kCFAllocatorDefault, &basicDescription, 0, NULL, 0, NULL, NULL, - &formatDescription); - - CMSampleBufferRef sampleBuffer; - CMAudioSampleBufferCreateReadyWithPacketDescriptions(kCFAllocatorDefault, blockBuffer, - formatDescription, duration.value, timestamp, - NULL, &sampleBuffer); - - CFRelease(blockBuffer); - CFRelease(formatDescription); - return sampleBuffer; -} - -CMSampleBufferRef FLTCreateTestAudioSampleBuffer(void) { - return FLTCreateTestAudioSampleBufferWithTiming(kCMTimeZero, CMTimeMake(1, 44100)); -} diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.swift index f332ab2b73a5..8e07d46dee42 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.swift @@ -80,7 +80,9 @@ enum CameraTestUtils { /// Creates a test sample buffer. /// @return a test sample buffer. - static func createTestSampleBuffer(timestamp: CMTime = CMTime.zero, duration: CMTime = CMTimeMake(value: 1, timescale: 44100)) -> CMSampleBuffer { + static func createTestSampleBuffer( + timestamp: CMTime = CMTime.zero, duration: CMTime = CMTimeMake(value: 1, timescale: 44100) + ) -> CMSampleBuffer { var pixelBuffer: CVPixelBuffer? CVPixelBufferCreate(kCFAllocatorDefault, 100, 100, kCVPixelFormatType_32BGRA, nil, &pixelBuffer) @@ -108,7 +110,9 @@ enum CameraTestUtils { /// Creates a test audio sample buffer. /// @return a test audio sample buffer. - static func createTestAudioSampleBuffer(timestamp: CMTime = .zero, duration: CMTime = CMTimeMake(value: 1, timescale: 44100)) -> CMSampleBuffer? { + static func createTestAudioSampleBuffer( + timestamp: CMTime = .zero, duration: CMTime = CMTimeMake(value: 1, timescale: 44100) + ) -> CMSampleBuffer? { var blockBuffer: CMBlockBuffer? CMBlockBufferCreateWithMemoryBlock( allocator: kCFAllocatorDefault, diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamSampleBufferTests.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamSampleBufferTests.m deleted file mode 100644 index 7de7c49070ec..000000000000 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamSampleBufferTests.m +++ /dev/null @@ -1,536 +0,0 @@ -// 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_avfoundation; -#if __has_include() -@import camera_avfoundation.Test; -#endif -@import AVFoundation; -@import XCTest; -#import -#import "CameraTestUtils.h" - -/// Includes test cases related to sample buffer handling for FLTCam class. -@interface FLTCamSampleBufferTests : XCTestCase - -@end - -@implementation FLTCamSampleBufferTests - -- (void)testSampleBufferCallbackQueueMustBeCaptureSessionQueue { - dispatch_queue_t captureSessionQueue = dispatch_queue_create("testing", NULL); - 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); -} - -- (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]; - }); - - // Pause then resume the recording. - [cam - startVideoRecordingWithCompletion:^(FlutterError *_Nullable error) { - } - messengerForStreaming:nil]; - [cam pauseVideoRecording]; - [cam resumeVideoRecording]; - - [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(); - CMSampleBufferRef audioSample = FLTCreateTestAudioSampleBuffer(); - - id connectionMock = OCMClassMock([AVCaptureConnection class]); - - 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]; - }); - - __block NSArray *writtenSamples = @[]; - - id adaptorMock = OCMClassMock([AVAssetWriterInputPixelBufferAdaptor class]); - OCMStub([adaptorMock assetWriterInputPixelBufferAdaptorWithAssetWriterInput:OCMOCK_ANY - sourcePixelBufferAttributes:OCMOCK_ANY]) - .andReturn(adaptorMock); - OCMStub([adaptorMock appendPixelBuffer:[OCMArg anyPointer] withPresentationTime:kCMTimeZero]) - .ignoringNonObjectArgs() - .andDo(^(NSInvocation *invocation) { - writtenSamples = [writtenSamples arrayByAddingObject:@"video"]; - }); - - id inputMock = OCMClassMock([AVAssetWriterInput class]); - OCMStub([inputMock assetWriterInputWithMediaType:OCMOCK_ANY outputSettings:OCMOCK_ANY]) - .andReturn(inputMock); - OCMStub([inputMock isReadyForMoreMediaData]).andReturn(YES); - OCMStub([inputMock appendSampleBuffer:[OCMArg anyPointer]]).andDo(^(NSInvocation *invocation) { - writtenSamples = [writtenSamples arrayByAddingObject:@"audio"]; - }); - - [cam - startVideoRecordingWithCompletion:^(FlutterError *_Nullable error) { - } - messengerForStreaming:nil]; - - [cam captureOutput:nil didOutputSampleBuffer:audioSample fromConnection:connectionMock]; - [cam captureOutput:nil didOutputSampleBuffer:audioSample fromConnection:connectionMock]; - [cam captureOutput:cam.captureVideoOutput - didOutputSampleBuffer:videoSample - fromConnection:connectionMock]; - [cam captureOutput:nil didOutputSampleBuffer:audioSample fromConnection:connectionMock]; - - NSArray *expectedSamples = @[ @"video", @"audio" ]; - XCTAssertEqualObjects(writtenSamples, expectedSamples, @"First appended sample must be video."); - - CFRelease(videoSample); - CFRelease(audioSample); -} - -- (void)testDidOutputSampleBufferSampleTimesMustBeNumericAfterPauseResume { - FLTCam *cam = FLTCreateCamWithCaptureSessionQueue(dispatch_queue_create("testing", NULL)); - CMSampleBufferRef videoSample = FLTCreateTestSampleBuffer(); - CMSampleBufferRef audioSample = FLTCreateTestAudioSampleBuffer(); - - id connectionMock = OCMClassMock([AVCaptureConnection class]); - - 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]; - }); - - __block BOOL videoAppended = NO; - id adaptorMock = OCMClassMock([AVAssetWriterInputPixelBufferAdaptor class]); - OCMStub([adaptorMock assetWriterInputPixelBufferAdaptorWithAssetWriterInput:OCMOCK_ANY - sourcePixelBufferAttributes:OCMOCK_ANY]) - .andReturn(adaptorMock); - OCMStub([adaptorMock appendPixelBuffer:[OCMArg anyPointer] withPresentationTime:kCMTimeZero]) - .ignoringNonObjectArgs() - .andDo(^(NSInvocation *invocation) { - CMTime presentationTime; - [invocation getArgument:&presentationTime atIndex:3]; - XCTAssert(CMTIME_IS_NUMERIC(presentationTime)); - videoAppended = YES; - }); - - __block BOOL audioAppended = NO; - id inputMock = OCMClassMock([AVAssetWriterInput class]); - OCMStub([inputMock assetWriterInputWithMediaType:OCMOCK_ANY outputSettings:OCMOCK_ANY]) - .andReturn(inputMock); - OCMStub([inputMock isReadyForMoreMediaData]).andReturn(YES); - OCMStub([inputMock appendSampleBuffer:[OCMArg anyPointer]]).andDo(^(NSInvocation *invocation) { - CMSampleBufferRef sampleBuffer; - [invocation getArgument:&sampleBuffer atIndex:2]; - CMTime sampleTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer); - XCTAssert(CMTIME_IS_NUMERIC(sampleTime)); - audioAppended = YES; - }); - - [cam - startVideoRecordingWithCompletion:^(FlutterError *_Nullable error) { - } - messengerForStreaming:nil]; - - [cam pauseVideoRecording]; - [cam resumeVideoRecording]; - - [cam captureOutput:cam.captureVideoOutput - didOutputSampleBuffer:videoSample - fromConnection:connectionMock]; - [cam captureOutput:nil didOutputSampleBuffer:audioSample fromConnection:connectionMock]; - [cam captureOutput:cam.captureVideoOutput - didOutputSampleBuffer:videoSample - fromConnection:connectionMock]; - [cam captureOutput:nil didOutputSampleBuffer:audioSample fromConnection:connectionMock]; - XCTAssert(videoAppended && audioAppended, @"Video or audio was not appended."); - - CFRelease(videoSample); - CFRelease(audioSample); -} - -- (void)testDidOutputSampleBufferMustNotAppendSampleWhenReadyForMoreMediaDataIsNo { - FLTCam *cam = FLTCreateCamWithCaptureSessionQueue(dispatch_queue_create("testing", NULL)); - CMSampleBufferRef videoSample = FLTCreateTestSampleBuffer(); - - id connectionMock = OCMClassMock([AVCaptureConnection class]); - - 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 BOOL sampleAppended = NO; - id adaptorMock = OCMClassMock([AVAssetWriterInputPixelBufferAdaptor class]); - OCMStub([adaptorMock assetWriterInputPixelBufferAdaptorWithAssetWriterInput:OCMOCK_ANY - sourcePixelBufferAttributes:OCMOCK_ANY]) - .andReturn(adaptorMock); - OCMStub([adaptorMock appendPixelBuffer:[OCMArg anyPointer] withPresentationTime:kCMTimeZero]) - .ignoringNonObjectArgs() - .andDo(^(NSInvocation *invocation) { - sampleAppended = YES; - }); - - __block BOOL readyForMoreMediaData = NO; - id inputMock = OCMClassMock([AVAssetWriterInput class]); - OCMStub([inputMock assetWriterInputWithMediaType:OCMOCK_ANY outputSettings:OCMOCK_ANY]) - .andReturn(inputMock); - OCMStub([inputMock isReadyForMoreMediaData]).andDo(^(NSInvocation *invocation) { - [invocation setReturnValue:&readyForMoreMediaData]; - }); - - [cam - startVideoRecordingWithCompletion:^(FlutterError *_Nullable error) { - } - messengerForStreaming:nil]; - - readyForMoreMediaData = YES; - sampleAppended = NO; - [cam captureOutput:cam.captureVideoOutput - didOutputSampleBuffer:videoSample - fromConnection:connectionMock]; - XCTAssertTrue(sampleAppended, @"Sample was not appended."); - - readyForMoreMediaData = NO; - sampleAppended = NO; - [cam captureOutput:cam.captureVideoOutput - didOutputSampleBuffer:videoSample - fromConnection:connectionMock]; - XCTAssertFalse(sampleAppended, @"Sample cannot be appended when readyForMoreMediaData is NO."); - - CFRelease(videoSample); -} - -- (void)testStopVideoRecordingWithCompletionMustCallCompletion { - FLTCam *cam = FLTCreateCamWithCaptureSessionQueue(dispatch_queue_create("testing", NULL)); - - 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]; - }); - - OCMStub([writerMock finishWritingWithCompletionHandler:[OCMArg checkWithBlock:^(id param) { - XCTAssert(status == AVAssetWriterStatusWriting, - @"Cannot call finishWritingWithCompletionHandler when status is " - @"not AVAssetWriterStatusWriting."); - void (^handler)(void) = param; - handler(); - return YES; - }]]); - - [cam - startVideoRecordingWithCompletion:^(FlutterError *_Nullable error) { - } - messengerForStreaming:nil]; - - __block BOOL completionCalled = NO; - [cam stopVideoRecordingWithCompletion:^(NSString *_Nullable path, FlutterError *_Nullable error) { - completionCalled = YES; - }]; - XCTAssert(completionCalled, @"Completion was not called."); -} - -- (void)testStartWritingShouldNotBeCalledBetweenSampleCreationAndAppending { - FLTCam *cam = FLTCreateCamWithCaptureSessionQueue(dispatch_queue_create("testing", NULL)); - CMSampleBufferRef videoSample = FLTCreateTestSampleBuffer(); - - id connectionMock = OCMClassMock([AVCaptureConnection class]); - - 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 BOOL startWritingCalled = NO; - OCMStub([writerMock startWriting]).andDo(^(NSInvocation *invocation) { - startWritingCalled = YES; - }); - - __block BOOL videoAppended = NO; - id adaptorMock = OCMClassMock([AVAssetWriterInputPixelBufferAdaptor class]); - OCMStub([adaptorMock assetWriterInputPixelBufferAdaptorWithAssetWriterInput:OCMOCK_ANY - sourcePixelBufferAttributes:OCMOCK_ANY]) - .andReturn(adaptorMock); - OCMStub([adaptorMock appendPixelBuffer:[OCMArg anyPointer] withPresentationTime:kCMTimeZero]) - .ignoringNonObjectArgs() - .andDo(^(NSInvocation *invocation) { - videoAppended = YES; - }); - - id inputMock = OCMClassMock([AVAssetWriterInput class]); - OCMStub([inputMock assetWriterInputWithMediaType:OCMOCK_ANY outputSettings:OCMOCK_ANY]) - .andReturn(inputMock); - OCMStub([inputMock isReadyForMoreMediaData]).andReturn(YES); - - [cam - startVideoRecordingWithCompletion:^(FlutterError *_Nullable error) { - } - messengerForStreaming:nil]; - - BOOL startWritingCalledBefore = startWritingCalled; - [cam captureOutput:cam.captureVideoOutput - didOutputSampleBuffer:videoSample - fromConnection:connectionMock]; - XCTAssert((startWritingCalledBefore && videoAppended) || (startWritingCalled && !videoAppended), - @"The startWriting was called between sample creation and appending."); - - [cam captureOutput:cam.captureVideoOutput - didOutputSampleBuffer:videoSample - fromConnection:connectionMock]; - XCTAssert(videoAppended, @"Video was not appended."); - - CFRelease(videoSample); -} - -- (void)testStartVideoRecordingWithCompletionShouldNotDisableMixWithOthers { - FLTCam *cam = FLTCreateCamWithCaptureSessionQueue(dispatch_queue_create("testing", NULL)); - - id writerMock = OCMClassMock([AVAssetWriter class]); - OCMStub([writerMock alloc]).andReturn(writerMock); - OCMStub([writerMock initWithURL:OCMOCK_ANY fileType:OCMOCK_ANY error:[OCMArg setTo:nil]]) - .andReturn(writerMock); - - [AVAudioSession.sharedInstance setCategory:AVAudioSessionCategoryPlayback - withOptions:AVAudioSessionCategoryOptionMixWithOthers - error:nil]; - - [cam - startVideoRecordingWithCompletion:^(FlutterError *_Nullable error) { - } - messengerForStreaming:nil]; - XCTAssert( - AVAudioSession.sharedInstance.categoryOptions & AVAudioSessionCategoryOptionMixWithOthers, - @"Flag MixWithOthers was removed."); - XCTAssert(AVAudioSession.sharedInstance.category == AVAudioSessionCategoryPlayAndRecord, - @"Category should be PlayAndRecord."); -} - -- (void)testDidOutputSampleBufferMustUseSingleOffsetForVideoAndAudio { - FLTCam *cam = FLTCreateCamWithCaptureSessionQueue(dispatch_queue_create("testing", NULL)); - - id connectionMock = OCMClassMock([AVCaptureConnection class]); - - 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]; - }); - - __block CMTime appendedTime; - - id adaptorMock = OCMClassMock([AVAssetWriterInputPixelBufferAdaptor class]); - OCMStub([adaptorMock assetWriterInputPixelBufferAdaptorWithAssetWriterInput:OCMOCK_ANY - sourcePixelBufferAttributes:OCMOCK_ANY]) - .andReturn(adaptorMock); - OCMStub([adaptorMock appendPixelBuffer:[OCMArg anyPointer] withPresentationTime:kCMTimeZero]) - .ignoringNonObjectArgs() - .andDo(^(NSInvocation *invocation) { - [invocation getArgument:&appendedTime atIndex:3]; - }); - - id inputMock = OCMClassMock([AVAssetWriterInput class]); - OCMStub([inputMock assetWriterInputWithMediaType:OCMOCK_ANY outputSettings:OCMOCK_ANY]) - .andReturn(inputMock); - OCMStub([inputMock isReadyForMoreMediaData]).andReturn(YES); - OCMStub([inputMock appendSampleBuffer:[OCMArg anyPointer]]).andDo(^(NSInvocation *invocation) { - CMSampleBufferRef sampleBuffer; - [invocation getArgument:&sampleBuffer atIndex:2]; - appendedTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer); - }); - - [cam - startVideoRecordingWithCompletion:^(FlutterError *_Nullable error) { - } - messengerForStreaming:nil]; - - appendedTime = kCMTimeInvalid; - [cam pauseVideoRecording]; - [cam resumeVideoRecording]; - [cam captureOutput:cam.captureVideoOutput - didOutputSampleBuffer:FLTCreateTestSampleBufferWithTiming(CMTimeMake(1, 1), kCMTimeInvalid) - fromConnection:connectionMock]; - XCTAssert(CMTIME_COMPARE_INLINE(appendedTime, ==, CMTimeMake(1, 1))); - - appendedTime = kCMTimeInvalid; - [cam pauseVideoRecording]; - [cam resumeVideoRecording]; - [cam captureOutput:cam.captureVideoOutput - didOutputSampleBuffer:FLTCreateTestSampleBufferWithTiming(CMTimeMake(11, 1), kCMTimeInvalid) - fromConnection:connectionMock]; - XCTAssert(CMTIME_COMPARE_INLINE(appendedTime, ==, kCMTimeInvalid)); - [cam captureOutput:cam.captureVideoOutput - didOutputSampleBuffer:FLTCreateTestSampleBufferWithTiming(CMTimeMake(12, 1), kCMTimeInvalid) - fromConnection:connectionMock]; - XCTAssert(CMTIME_COMPARE_INLINE(appendedTime, ==, CMTimeMake(2, 1))); - - appendedTime = kCMTimeInvalid; - [cam pauseVideoRecording]; - [cam resumeVideoRecording]; - [cam captureOutput:nil - didOutputSampleBuffer:FLTCreateTestAudioSampleBufferWithTiming(CMTimeMake(20, 1), - CMTimeMake(2, 1)) - fromConnection:connectionMock]; - XCTAssert(CMTIME_COMPARE_INLINE(appendedTime, ==, kCMTimeInvalid)); - [cam captureOutput:cam.captureVideoOutput - didOutputSampleBuffer:FLTCreateTestSampleBufferWithTiming(CMTimeMake(23, 1), kCMTimeInvalid) - fromConnection:connectionMock]; - XCTAssert(CMTIME_COMPARE_INLINE(appendedTime, ==, CMTimeMake(3, 1))); - - appendedTime = kCMTimeInvalid; - [cam pauseVideoRecording]; - [cam resumeVideoRecording]; - [cam captureOutput:cam.captureVideoOutput - didOutputSampleBuffer:FLTCreateTestSampleBufferWithTiming(CMTimeMake(28, 1), kCMTimeInvalid) - fromConnection:connectionMock]; - XCTAssert(CMTIME_COMPARE_INLINE(appendedTime, ==, kCMTimeInvalid)); - [cam captureOutput:nil - didOutputSampleBuffer:FLTCreateTestAudioSampleBufferWithTiming(CMTimeMake(30, 1), - CMTimeMake(2, 1)) - fromConnection:connectionMock]; - XCTAssert(CMTIME_COMPARE_INLINE(appendedTime, ==, kCMTimeInvalid)); - [cam captureOutput:cam.captureVideoOutput - didOutputSampleBuffer:FLTCreateTestSampleBufferWithTiming(CMTimeMake(33, 1), kCMTimeInvalid) - fromConnection:connectionMock]; - XCTAssert(CMTIME_COMPARE_INLINE(appendedTime, ==, kCMTimeInvalid)); - [cam captureOutput:nil - didOutputSampleBuffer:FLTCreateTestAudioSampleBufferWithTiming(CMTimeMake(32, 1), - CMTimeMake(2, 1)) - fromConnection:connectionMock]; - XCTAssert(CMTIME_COMPARE_INLINE(appendedTime, ==, CMTimeMake(2, 1))); -} - -- (void)testDidOutputSampleBufferMustConnectVideoAfterSessionInterruption { - FLTCam *cam = FLTCreateCamWithCaptureSessionQueue(dispatch_queue_create("testing", NULL)); - - id connectionMock = OCMClassMock([AVCaptureConnection class]); - - 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]; - }); - - __block CMTime appendedTime; - - id adaptorMock = OCMClassMock([AVAssetWriterInputPixelBufferAdaptor class]); - OCMStub([adaptorMock assetWriterInputPixelBufferAdaptorWithAssetWriterInput:OCMOCK_ANY - sourcePixelBufferAttributes:OCMOCK_ANY]) - .andReturn(adaptorMock); - OCMStub([adaptorMock appendPixelBuffer:[OCMArg anyPointer] withPresentationTime:kCMTimeZero]) - .ignoringNonObjectArgs() - .andDo(^(NSInvocation *invocation) { - [invocation getArgument:&appendedTime atIndex:3]; - }); - - id inputMock = OCMClassMock([AVAssetWriterInput class]); - OCMStub([inputMock assetWriterInputWithMediaType:OCMOCK_ANY outputSettings:OCMOCK_ANY]) - .andReturn(inputMock); - OCMStub([inputMock isReadyForMoreMediaData]).andReturn(YES); - OCMStub([inputMock appendSampleBuffer:[OCMArg anyPointer]]).andDo(^(NSInvocation *invocation) { - CMSampleBufferRef sampleBuffer; - [invocation getArgument:&sampleBuffer atIndex:2]; - appendedTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer); - }); - - [cam - startVideoRecordingWithCompletion:^(FlutterError *_Nullable error) { - } - messengerForStreaming:nil]; - - [cam captureOutput:cam.captureVideoOutput - didOutputSampleBuffer:FLTCreateTestSampleBufferWithTiming(CMTimeMake(1, 1), kCMTimeInvalid) - fromConnection:connectionMock]; - [cam captureOutput:nil - didOutputSampleBuffer:FLTCreateTestAudioSampleBufferWithTiming(CMTimeMake(1, 1), - CMTimeMake(1, 1)) - fromConnection:connectionMock]; - - [NSNotificationCenter.defaultCenter postNotificationName:AVCaptureSessionWasInterruptedNotification object:cam.audioCaptureSession]; - - appendedTime = kCMTimeInvalid; - [cam captureOutput:nil - didOutputSampleBuffer:FLTCreateTestAudioSampleBufferWithTiming(CMTimeMake(11, 1), - CMTimeMake(1, 1)) - fromConnection:connectionMock]; - XCTAssert(CMTIME_COMPARE_INLINE(appendedTime, ==, kCMTimeInvalid)); - [cam captureOutput:cam.captureVideoOutput - didOutputSampleBuffer:FLTCreateTestSampleBufferWithTiming(CMTimeMake(12, 1), kCMTimeInvalid) - fromConnection:connectionMock]; - XCTAssert(CMTIME_COMPARE_INLINE(appendedTime, ==, CMTimeMake(2, 1))); - appendedTime = kCMTimeInvalid; - [cam captureOutput:nil - didOutputSampleBuffer:FLTCreateTestAudioSampleBufferWithTiming(CMTimeMake(12, 1), - CMTimeMake(1, 1)) - fromConnection:connectionMock]; - XCTAssert(CMTIME_COMPARE_INLINE(appendedTime, ==, CMTimeMake(2, 1))); -} - -@end diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockCaptureSession.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockCaptureSession.swift index ffa5540d4c0a..0283f4f9de09 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockCaptureSession.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockCaptureSession.swift @@ -12,12 +12,12 @@ final class MockCaptureSession: NSObject, FLTCaptureSession { var stopRunningStub: (() -> Void)? var canSetSessionPresetStub: ((AVCaptureSession.Preset) -> Bool)? - var captureSession = AVCaptureSession() + var captureSession = AVCaptureSession() var _sessionPreset = AVCaptureSession.Preset.high var inputs = [AVCaptureInput]() var outputs = [AVCaptureOutput]() var automaticallyConfiguresApplicationAudioSession = false - var running = true + var running = true var sessionPreset: AVCaptureSession.Preset { get { diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/SampleBufferTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/SampleBufferTests.swift index 1513f50ded97..48780ed93506 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/SampleBufferTests.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/SampleBufferTests.swift @@ -365,38 +365,38 @@ final class CameraSampleBufferTests: XCTestCase { from: connectionMock) } - appendedTime = .invalid; + appendedTime = .invalid camera.pauseVideoRecording() camera.resumeVideoRecording() - appendVideoSample(1); + appendVideoSample(1) XCTAssertEqual(appendedTime, CMTimeMake(value: 1, timescale: 1)) - appendedTime = .invalid; + appendedTime = .invalid camera.pauseVideoRecording() camera.resumeVideoRecording() - appendVideoSample(11); + appendVideoSample(11) XCTAssertEqual(appendedTime, .invalid) - appendVideoSample(12); + appendVideoSample(12) XCTAssertEqual(appendedTime, CMTimeMake(value: 2, timescale: 1)) - appendedTime = .invalid; + appendedTime = .invalid camera.pauseVideoRecording() camera.resumeVideoRecording() - appendAudioSample(20); + appendAudioSample(20) XCTAssertEqual(appendedTime, .invalid) - appendVideoSample(23); + appendVideoSample(23) XCTAssertEqual(appendedTime, CMTimeMake(value: 3, timescale: 1)) - appendedTime = .invalid; + appendedTime = .invalid camera.pauseVideoRecording() camera.resumeVideoRecording() - appendVideoSample(28); + appendVideoSample(28) XCTAssertEqual(appendedTime, .invalid) - appendAudioSample(30); + appendAudioSample(30) XCTAssertEqual(appendedTime, .invalid) - appendVideoSample(33); + appendVideoSample(33) XCTAssertEqual(appendedTime, .invalid) - appendAudioSample(32); + appendAudioSample(32) XCTAssertEqual(appendedTime, CMTimeMake(value: 2, timescale: 1)) } @@ -445,18 +445,18 @@ final class CameraSampleBufferTests: XCTestCase { from: connectionMock) } - appendVideoSample(1); - appendAudioSample(1); + appendVideoSample(1) + appendAudioSample(1) NotificationCenter.default.post(name: AVCaptureSession.wasInterruptedNotification, object: camera.audioCaptureSession.captureSession) - appendedTime = .invalid; - appendAudioSample(11); + appendedTime = .invalid + appendAudioSample(11) XCTAssertEqual(appendedTime, .invalid) - appendVideoSample(12); + appendVideoSample(12) XCTAssertEqual(appendedTime, CMTimeMake(value: 2, timescale: 1)) - appendedTime = .invalid; - appendAudioSample(12); + appendedTime = .invalid + appendAudioSample(12) XCTAssertEqual(appendedTime, CMTimeMake(value: 2, timescale: 1)) } } diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTCam.m b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTCam.m index ff3672b00d4c..4035bed4cfae 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTCam.m +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTCam.m @@ -298,8 +298,8 @@ - (void)captureSessionWasInterrupted:(NSNotification *)notification { - (void)captureSessionRuntimeError:(NSNotification *)notification { [self reportErrorMessage:[NSString - stringWithFormat:@"%@", - notification.userInfo[AVCaptureSessionErrorKey]]]; + stringWithFormat:@"%@", + notification.userInfo[AVCaptureSessionErrorKey]]]; } - (AVCaptureConnection *)createConnection:(NSError **)error { diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTCaptureSession.m b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTCaptureSession.m index cc6cb1b52790..a8860f71aa93 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTCaptureSession.m +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTCaptureSession.m @@ -35,7 +35,7 @@ - (void)stopRunning { } - (BOOL)running { - return _captureSession.running; + return _captureSession.running; } - (BOOL)automaticallyConfiguresApplicationAudioSession { From dc8d5f931b426a89d8939e9a5f4bb56346c33fdf Mon Sep 17 00:00:00 2001 From: misos1 <30872003+misos1@users.noreply.github.com> Date: Wed, 2 Apr 2025 22:53:05 +0200 Subject: [PATCH 4/6] fix format --- .../example/ios/RunnerTests/SampleBufferTests.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/SampleBufferTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/SampleBufferTests.swift index 48780ed93506..3843670d3230 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/SampleBufferTests.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/SampleBufferTests.swift @@ -448,7 +448,9 @@ final class CameraSampleBufferTests: XCTestCase { appendVideoSample(1) appendAudioSample(1) - NotificationCenter.default.post(name: AVCaptureSession.wasInterruptedNotification, object: camera.audioCaptureSession.captureSession) + NotificationCenter.default.post( + name: AVCaptureSession.wasInterruptedNotification, + object: camera.audioCaptureSession.captureSession) appendedTime = .invalid appendAudioSample(11) From aa1cb5a037ab38e3d5af2702713448cfd57cfa1a Mon Sep 17 00:00:00 2001 From: misos1 <30872003+misos1@users.noreply.github.com> Date: Thu, 3 Apr 2025 16:23:39 +0200 Subject: [PATCH 5/6] add test --- packages/camera/camera/test/camera_test.dart | 13 ++++++++++++ .../ios/RunnerTests/CameraTestUtils.swift | 8 +++----- .../ios/RunnerTests/SampleBufferTests.swift | 20 +++++++++---------- 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/packages/camera/camera/test/camera_test.dart b/packages/camera/camera/test/camera_test.dart index 0c6a319397e0..1900a08edb8f 100644 --- a/packages/camera/camera/test/camera_test.dart +++ b/packages/camera/camera/test/camera_test.dart @@ -1441,6 +1441,19 @@ void main() { 'This is a test error message', ))); }); + + test('error from onCameraError is received', () async { + final CameraController cameraController = CameraController( + const CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + expect(cameraController.value.hasError, isTrue); + expect(cameraController.value.errorDescription, mockOnCameraErrorEvent.description); + }); }); } diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.swift index 8e07d46dee42..d6d43293aca9 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.swift @@ -95,7 +95,7 @@ enum CameraTestUtils { var timingInfo = CMSampleTimingInfo( duration: duration, presentationTimeStamp: timestamp, - decodeTimeStamp: CMTime.invalid) + decodeTimeStamp: .invalid) var sampleBuffer: CMSampleBuffer? CMSampleBufferCreateReadyWithImageBuffer( @@ -112,7 +112,7 @@ enum CameraTestUtils { /// @return a test audio sample buffer. static func createTestAudioSampleBuffer( timestamp: CMTime = .zero, duration: CMTime = CMTimeMake(value: 1, timescale: 44100) - ) -> CMSampleBuffer? { + ) -> CMSampleBuffer { var blockBuffer: CMBlockBuffer? CMBlockBufferCreateWithMemoryBlock( allocator: kCFAllocatorDefault, @@ -125,8 +125,6 @@ enum CameraTestUtils { flags: kCMBlockBufferAssureMemoryNowFlag, blockBufferOut: &blockBuffer) - guard let blockBuffer = blockBuffer else { return nil } - var formatDescription: CMFormatDescription? var basicDescription = AudioStreamBasicDescription( mSampleRate: Float64(duration.timescale), @@ -152,7 +150,7 @@ enum CameraTestUtils { var sampleBuffer: CMSampleBuffer? CMAudioSampleBufferCreateReadyWithPacketDescriptions( allocator: kCFAllocatorDefault, - dataBuffer: blockBuffer, + dataBuffer: blockBuffer!, formatDescription: formatDescription!, sampleCount: CMItemCount(duration.value), presentationTimeStamp: timestamp, diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/SampleBufferTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/SampleBufferTests.swift index 3843670d3230..3485104add32 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/SampleBufferTests.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/SampleBufferTests.swift @@ -356,12 +356,12 @@ final class CameraSampleBufferTests: XCTestCase { from: connectionMock) } - let appendAudioSample = { (time: Int64) in + let appendAudioSample = { (time: Int64, duration: Int64) in camera.captureOutput( nil, didOutputSampleBuffer: CameraTestUtils.createTestAudioSampleBuffer( timestamp: CMTimeMake(value: time, timescale: 1), - duration: CMTimeMake(value: 2, timescale: 1)), + duration: CMTimeMake(value: duration, timescale: 1)), from: connectionMock) } @@ -382,7 +382,7 @@ final class CameraSampleBufferTests: XCTestCase { appendedTime = .invalid camera.pauseVideoRecording() camera.resumeVideoRecording() - appendAudioSample(20) + appendAudioSample(20, 2) XCTAssertEqual(appendedTime, .invalid) appendVideoSample(23) XCTAssertEqual(appendedTime, CMTimeMake(value: 3, timescale: 1)) @@ -392,11 +392,11 @@ final class CameraSampleBufferTests: XCTestCase { camera.resumeVideoRecording() appendVideoSample(28) XCTAssertEqual(appendedTime, .invalid) - appendAudioSample(30) + appendAudioSample(30, 2) XCTAssertEqual(appendedTime, .invalid) appendVideoSample(33) XCTAssertEqual(appendedTime, .invalid) - appendAudioSample(32) + appendAudioSample(32, 2) XCTAssertEqual(appendedTime, CMTimeMake(value: 2, timescale: 1)) } @@ -436,29 +436,29 @@ final class CameraSampleBufferTests: XCTestCase { from: connectionMock) } - let appendAudioSample = { (time: Int64) in + let appendAudioSample = { (time: Int64, duration: Int64) in camera.captureOutput( nil, didOutputSampleBuffer: CameraTestUtils.createTestAudioSampleBuffer( timestamp: CMTimeMake(value: time, timescale: 1), - duration: CMTimeMake(value: 1, timescale: 1)), + duration: CMTimeMake(value: duration, timescale: 1)), from: connectionMock) } appendVideoSample(1) - appendAudioSample(1) + appendAudioSample(1, 1) NotificationCenter.default.post( name: AVCaptureSession.wasInterruptedNotification, object: camera.audioCaptureSession.captureSession) appendedTime = .invalid - appendAudioSample(11) + appendAudioSample(11, 1) XCTAssertEqual(appendedTime, .invalid) appendVideoSample(12) XCTAssertEqual(appendedTime, CMTimeMake(value: 2, timescale: 1)) appendedTime = .invalid - appendAudioSample(12) + appendAudioSample(12, 1) XCTAssertEqual(appendedTime, CMTimeMake(value: 2, timescale: 1)) } } From 46bf927fff68d3d6ffeb0a73fac156163d905e58 Mon Sep 17 00:00:00 2001 From: misos1 <30872003+misos1@users.noreply.github.com> Date: Thu, 3 Apr 2025 16:38:42 +0200 Subject: [PATCH 6/6] fix format --- packages/camera/camera/test/camera_test.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/camera/camera/test/camera_test.dart b/packages/camera/camera/test/camera_test.dart index 1900a08edb8f..0667247685b2 100644 --- a/packages/camera/camera/test/camera_test.dart +++ b/packages/camera/camera/test/camera_test.dart @@ -1452,7 +1452,8 @@ void main() { await cameraController.initialize(); expect(cameraController.value.hasError, isTrue); - expect(cameraController.value.errorDescription, mockOnCameraErrorEvent.description); + expect(cameraController.value.errorDescription, + mockOnCameraErrorEvent.description); }); }); }