-
Notifications
You must be signed in to change notification settings - Fork 3.5k
[camera_avfoundation] Set highest available resolution for ResolutionPreset.Max #5245
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
01ad812
5f5cabc
f9f302f
e5a33f6
d3ce411
5813f25
60e043a
9830404
87b0c59
0c29019
ae0210f
0c8b21a
37ffaa7
70deac4
0fe1ac7
c314755
ec1b896
5e6f1f5
decce1e
832cc7b
ddfe778
5c01c8c
0c40901
42e9cfa
8e019b3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| // 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; | ||
| @import camera_avfoundation.Test; | ||
|
|
||
| @import AVFoundation; | ||
| @import XCTest; | ||
| #import <OCMock/OCMock.h> | ||
| #import "CameraTestUtils.h" | ||
|
|
||
| /// Includes test cases related to resolution presets setting operations for FLTCam class. | ||
| @interface FLTCamSessionPresetsTest : XCTestCase | ||
| @end | ||
|
|
||
| @implementation FLTCamSessionPresetsTest | ||
|
|
||
| - (void)testResolutionPresetWithBestFormat_mustUpdateCaptureSessionPreset { | ||
| NSString *expectedPreset = AVCaptureSessionPresetInputPriority; | ||
|
|
||
| id videoSessionMock = OCMClassMock([AVCaptureSession class]); | ||
| OCMStub([videoSessionMock addInputWithNoConnections:[OCMArg any]]); | ||
|
|
||
| id captureFormatMock = OCMClassMock([AVCaptureDeviceFormat class]); | ||
| id captureDeviceMock = OCMClassMock([AVCaptureDevice class]); | ||
| OCMStub([captureDeviceMock formats]).andReturn(@[ captureFormatMock ]); | ||
|
|
||
| OCMExpect([captureDeviceMock activeFormat]).andReturn(captureFormatMock); | ||
| OCMExpect([captureDeviceMock lockForConfiguration:NULL]).andReturn(YES); | ||
| OCMExpect([videoSessionMock setSessionPreset:expectedPreset]); | ||
|
|
||
| FLTCreateCamWithVideoDimensionsForFormat(videoSessionMock, @"max", captureDeviceMock, | ||
| ^CMVideoDimensions(AVCaptureDeviceFormat *format) { | ||
| CMVideoDimensions videoDimensions; | ||
| videoDimensions.width = 1; | ||
| videoDimensions.height = 1; | ||
| return videoDimensions; | ||
| }); | ||
|
|
||
| OCMVerifyAll(captureDeviceMock); | ||
| OCMVerifyAll(videoSessionMock); | ||
| } | ||
|
|
||
| - (void)testResolutionPresetWithCanSetSessionPresetMax_mustUpdateCaptureSessionPreset { | ||
| NSString *expectedPreset = AVCaptureSessionPreset3840x2160; | ||
|
|
||
| id videoSessionMock = OCMClassMock([AVCaptureSession class]); | ||
| OCMStub([videoSessionMock addInputWithNoConnections:[OCMArg any]]); | ||
|
|
||
| // Make sure that setting resolution preset for session always succeeds. | ||
| OCMStub([videoSessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES); | ||
|
|
||
| OCMExpect([videoSessionMock setSessionPreset:expectedPreset]); | ||
|
|
||
| FLTCreateCamWithVideoCaptureSession(videoSessionMock, @"max"); | ||
|
|
||
| OCMVerifyAll(videoSessionMock); | ||
| } | ||
|
|
||
| - (void)testResolutionPresetWithCanSetSessionPresetUltraHigh_mustUpdateCaptureSessionPreset { | ||
| NSString *expectedPreset = AVCaptureSessionPreset3840x2160; | ||
|
|
||
| id videoSessionMock = OCMClassMock([AVCaptureSession class]); | ||
| OCMStub([videoSessionMock addInputWithNoConnections:[OCMArg any]]); | ||
|
|
||
| // Make sure that setting resolution preset for session always succeeds. | ||
| OCMStub([videoSessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES); | ||
|
|
||
| // Expect that setting "ultraHigh" resolutionPreset correctly updates videoCaptureSession. | ||
| OCMExpect([videoSessionMock setSessionPreset:expectedPreset]); | ||
|
|
||
| FLTCreateCamWithVideoCaptureSession(videoSessionMock, @"ultraHigh"); | ||
|
|
||
| OCMVerifyAll(videoSessionMock); | ||
| } | ||
|
|
||
| @end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -86,6 +86,11 @@ @interface FLTCam () <AVCaptureVideoDataOutputSampleBufferDelegate, | |
| /// Videos are written to disk by `videoAdaptor` on an internal queue managed by AVFoundation. | ||
| @property(strong, nonatomic) dispatch_queue_t photoIOQueue; | ||
| @property(assign, nonatomic) UIDeviceOrientation deviceOrientation; | ||
| /// A wrapper for CMVideoFormatDescriptionGetDimensions. | ||
| /// Allows for alternate implementations in tests. | ||
| @property(nonatomic, copy) VideoDimensionsForFormat videoDimensionsForFormat; | ||
| /// A wrapper for AVCaptureDevice creation to allow for dependency injection in tests. | ||
| @property(nonatomic, copy) CaptureDeviceFactory captureDeviceFactory; | ||
| @end | ||
|
|
||
| @implementation FLTCam | ||
|
|
@@ -116,6 +121,30 @@ - (instancetype)initWithCameraName:(NSString *)cameraName | |
| audioCaptureSession:(AVCaptureSession *)audioCaptureSession | ||
| captureSessionQueue:(dispatch_queue_t)captureSessionQueue | ||
| error:(NSError **)error { | ||
| return [self initWithResolutionPreset:resolutionPreset | ||
| enableAudio:enableAudio | ||
| orientation:orientation | ||
| videoCaptureSession:videoCaptureSession | ||
| audioCaptureSession:videoCaptureSession | ||
| captureSessionQueue:captureSessionQueue | ||
| captureDeviceFactory:^AVCaptureDevice *(void) { | ||
| return [AVCaptureDevice deviceWithUniqueID:cameraName]; | ||
| } | ||
| videoDimensionsForFormat:^CMVideoDimensions(AVCaptureDeviceFormat *format) { | ||
| return CMVideoFormatDescriptionGetDimensions(format.formatDescription); | ||
| } | ||
| error:error]; | ||
| } | ||
|
|
||
| - (instancetype)initWithResolutionPreset:(NSString *)resolutionPreset | ||
| enableAudio:(BOOL)enableAudio | ||
| orientation:(UIDeviceOrientation)orientation | ||
| videoCaptureSession:(AVCaptureSession *)videoCaptureSession | ||
| audioCaptureSession:(AVCaptureSession *)audioCaptureSession | ||
| captureSessionQueue:(dispatch_queue_t)captureSessionQueue | ||
| captureDeviceFactory:(CaptureDeviceFactory)captureDeviceFactory | ||
| videoDimensionsForFormat:(VideoDimensionsForFormat)videoDimensionsForFormat | ||
| error:(NSError **)error { | ||
| self = [super init]; | ||
| NSAssert(self, @"super init cannot be nil"); | ||
| _resolutionPreset = FLTGetFLTResolutionPresetForString(resolutionPreset); | ||
|
|
@@ -136,7 +165,9 @@ - (instancetype)initWithCameraName:(NSString *)cameraName | |
| _photoIOQueue = dispatch_queue_create("io.flutter.camera.photoIOQueue", NULL); | ||
| _videoCaptureSession = videoCaptureSession; | ||
| _audioCaptureSession = audioCaptureSession; | ||
| _captureDevice = [AVCaptureDevice deviceWithUniqueID:cameraName]; | ||
| _captureDeviceFactory = captureDeviceFactory; | ||
| _captureDevice = captureDeviceFactory(); | ||
| _videoDimensionsForFormat = videoDimensionsForFormat; | ||
| _flashMode = _captureDevice.hasFlash ? FLTFlashModeAuto : FLTFlashModeOff; | ||
| _exposureMode = FLTExposureModeAuto; | ||
| _focusMode = FLTFocusModeAuto; | ||
|
|
@@ -366,7 +397,24 @@ - (NSString *)getTemporaryFilePathWithExtension:(NSString *)extension | |
|
|
||
| - (BOOL)setCaptureSessionPreset:(FLTResolutionPreset)resolutionPreset withError:(NSError **)error { | ||
| switch (resolutionPreset) { | ||
| case FLTResolutionPresetMax: | ||
| case FLTResolutionPresetMax: { | ||
| AVCaptureDeviceFormat *bestFormat = | ||
| [self highestResolutionFormatForCaptureDevice:_captureDevice]; | ||
| if (bestFormat) { | ||
| _videoCaptureSession.sessionPreset = AVCaptureSessionPresetInputPriority; | ||
| if ([_captureDevice lockForConfiguration:NULL]) { | ||
| // Set the best device format found and finish the device configuration. | ||
| _captureDevice.activeFormat = bestFormat; | ||
| [_captureDevice unlockForConfiguration]; | ||
|
|
||
| // Set the preview size based on values from the current capture device. | ||
| _previewSize = | ||
| CGSizeMake(_captureDevice.activeFormat.highResolutionStillImageDimensions.width, | ||
| _captureDevice.activeFormat.highResolutionStillImageDimensions.height); | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| case FLTResolutionPresetUltraHigh: | ||
| if ([_videoCaptureSession canSetSessionPreset:AVCaptureSessionPreset3840x2160]) { | ||
| _videoCaptureSession.sessionPreset = AVCaptureSessionPreset3840x2160; | ||
|
|
@@ -422,6 +470,24 @@ - (BOOL)setCaptureSessionPreset:(FLTResolutionPreset)resolutionPreset withError: | |
| return YES; | ||
| } | ||
|
|
||
| /// Finds the highest available resolution in terms of pixel count for the given device. | ||
| - (AVCaptureDeviceFormat *)highestResolutionFormatForCaptureDevice: | ||
| (AVCaptureDevice *)captureDevice { | ||
| AVCaptureDeviceFormat *bestFormat = nil; | ||
| NSUInteger maxPixelCount = 0; | ||
| for (AVCaptureDeviceFormat *format in _captureDevice.formats) { | ||
| CMVideoDimensions res = self.videoDimensionsForFormat(format); | ||
| NSUInteger height = res.height; | ||
| NSUInteger width = res.width; | ||
| NSUInteger pixelCount = height * width; | ||
| if (pixelCount > maxPixelCount) { | ||
| maxPixelCount = pixelCount; | ||
| bestFormat = format; | ||
| } | ||
| } | ||
| return bestFormat; | ||
| } | ||
|
|
||
| - (void)captureOutput:(AVCaptureOutput *)output | ||
| didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer | ||
| fromConnection:(AVCaptureConnection *)connection { | ||
|
|
@@ -935,7 +1001,7 @@ - (void)setDescriptionWhileRecording:(NSString *)cameraName | |
| return; | ||
| } | ||
|
|
||
| _captureDevice = [AVCaptureDevice deviceWithUniqueID:cameraName]; | ||
| _captureDevice = self.captureDeviceFactory(); | ||
|
|
||
|
Comment on lines
-938
to
1005
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @hellohuanlin you are probably right, this line is causing camera not updating. Would you consider just reverting this pr altogether?
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It shouldn't be reverted, the |
||
| AVCaptureConnection *oldConnection = | ||
| [_captureVideoOutput connectionWithMediaType:AVMediaTypeVideo]; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: This should be after the code below, enclosing the entire case implementation, otherwise the indentation is very confusing.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed everything
In this case I decided to just remove everything related to AVCaptureSessionPreset3840x2160, since it was just a repetition of the next case (FLTResolutionPresetUltraHigh). Now it's more in line with other cases
Out of curiosity - what's your definition of nit in this context? I thought these are not compulsory
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I generally use it to indicate that something is a very small change. I wasn't aware there was a doc that equated it with being optional; I'll stop using it given that.