@@ -13,7 +13,7 @@ final class DefaultCamera: FLTCam, Camera {
13
13
var dartAPI : FCPCameraEventApi ?
14
14
var onFrameAvailable : ( ( ) -> Void ) ?
15
15
16
- override var videoFormat : FourCharCode {
16
+ var videoFormat : FourCharCode = kCVPixelFormatType_32BGRA {
17
17
didSet {
18
18
captureVideoOutput. videoSettings = [
19
19
kCVPixelBufferPixelFormatTypeKey as String : videoFormat
@@ -47,11 +47,33 @@ final class DefaultCamera: FLTCam, Camera {
47
47
/// Videos are written to disk by `videoAdaptor` on an internal queue managed by AVFoundation.
48
48
private let photoIOQueue = DispatchQueue ( label: " io.flutter.camera.photoIOQueue " )
49
49
50
+ /// All DefaultCamera's state access and capture session related operations should be run on this queue.
51
+ private let captureSessionQueue : DispatchQueue
52
+
53
+ private let mediaSettings : FCPPlatformMediaSettings
54
+ private let mediaSettingsAVWrapper : FLTCamMediaSettingsAVWrapper
55
+
56
+ /// A wrapper for AVCaptureDevice creation to allow for dependency injection in tests.
57
+ private let captureDeviceFactory : CaptureDeviceFactory
58
+ private let audioCaptureDeviceFactory : AudioCaptureDeviceFactory
59
+ private let captureDeviceInputFactory : FLTCaptureDeviceInputFactory
60
+ private let assetWriterFactory : AssetWriterFactory
61
+ private let inputPixelBufferAdaptorFactory : InputPixelBufferAdaptorFactory
62
+
63
+ private let deviceOrientationProvider : FLTDeviceOrientationProviding
64
+
50
65
private var videoWriter : FLTAssetWriter ?
51
66
private var videoWriterInput : FLTAssetWriterInput ?
52
67
private var audioWriterInput : FLTAssetWriterInput ?
53
68
private var videoAdaptor : FLTAssetWriterInputPixelBufferAdaptor ?
54
69
70
+ /// A dictionary to retain all in-progress FLTSavePhotoDelegates. The key of the dictionary is the
71
+ /// AVCapturePhotoSettings's uniqueID for each photo capture operation, and the value is the
72
+ /// FLTSavePhotoDelegate that handles the result of each photo capture operation. Note that photo
73
+ /// capture operations may overlap, so FLTCam has to keep track of multiple delegates in progress,
74
+ /// instead of just a single delegate reference.
75
+ private( set) var inProgressSavePhotoDelegates = [ Int64: FLTSavePhotoDelegate] ( )
76
+
55
77
private var imageStreamHandler : FLTImageStreamHandler ?
56
78
57
79
/// Tracks the latest pixel buffer sent from AVFoundation's sample buffer delegate callback.
@@ -69,6 +91,9 @@ final class DefaultCamera: FLTCam, Camera {
69
91
private var videoTimeOffset = CMTime . zero
70
92
private var audioTimeOffset = CMTime . zero
71
93
94
+ /// True when images from the camera are being streamed.
95
+ private( set) var isStreamingImages = false
96
+
72
97
/// Number of frames currently pending processing.
73
98
private var streamingPendingFramesCount = 0
74
99
@@ -80,6 +105,7 @@ final class DefaultCamera: FLTCam, Camera {
80
105
81
106
private var exposureMode = FCPPlatformExposureMode . auto
82
107
private var focusMode = FCPPlatformFocusMode . auto
108
+ private var flashMode : FCPPlatformFlashMode
83
109
84
110
private static func flutterErrorFromNSError( _ error: NSError ) -> FlutterError {
85
111
return FlutterError (
@@ -116,6 +142,95 @@ final class DefaultCamera: FLTCam, Camera {
116
142
return ( captureVideoInput, captureVideoOutput, connection)
117
143
}
118
144
145
+ init ( configuration: FLTCamConfiguration ) throws {
146
+ captureSessionQueue = configuration. captureSessionQueue
147
+ mediaSettings = configuration. mediaSettings
148
+ mediaSettingsAVWrapper = configuration. mediaSettingsWrapper
149
+ captureDeviceFactory = configuration. captureDeviceFactory
150
+ audioCaptureDeviceFactory = configuration. audioCaptureDeviceFactory
151
+ captureDeviceInputFactory = configuration. captureDeviceInputFactory
152
+ assetWriterFactory = configuration. assetWriterFactory
153
+ inputPixelBufferAdaptorFactory = configuration. inputPixelBufferAdaptorFactory
154
+ deviceOrientationProvider = configuration. deviceOrientationProvider
155
+
156
+ let captureDevice = captureDeviceFactory ( configuration. initialCameraName)
157
+ flashMode = captureDevice. hasFlash ? . auto : . off
158
+
159
+ super. init ( )
160
+
161
+ videoCaptureSession = configuration. videoCaptureSession
162
+ audioCaptureSession = configuration. audioCaptureSession
163
+ videoDimensionsForFormat = configuration. videoDimensionsForFormat
164
+
165
+ self . captureDevice = captureDevice
166
+
167
+ capturePhotoOutput = FLTDefaultCapturePhotoOutput ( photoOutput: AVCapturePhotoOutput ( ) )
168
+ capturePhotoOutput. highResolutionCaptureEnabled = true
169
+
170
+ videoCaptureSession. automaticallyConfiguresApplicationAudioSession = false
171
+ audioCaptureSession. automaticallyConfiguresApplicationAudioSession = false
172
+
173
+ deviceOrientation = configuration. orientation
174
+
175
+ let connection : AVCaptureConnection
176
+ ( captureVideoInput, captureVideoOutput, connection) = try DefaultCamera . createConnection (
177
+ captureDevice: captureDevice,
178
+ videoFormat: videoFormat,
179
+ captureDeviceInputFactory: configuration. captureDeviceInputFactory)
180
+
181
+ captureVideoOutput. setSampleBufferDelegate ( self , queue: captureSessionQueue)
182
+
183
+ videoCaptureSession. addInputWithNoConnections ( captureVideoInput)
184
+ videoCaptureSession. addOutputWithNoConnections ( captureVideoOutput. avOutput)
185
+ videoCaptureSession. addConnection ( connection)
186
+
187
+ videoCaptureSession. addOutput ( capturePhotoOutput. avOutput)
188
+
189
+ motionManager. startAccelerometerUpdates ( )
190
+
191
+ if mediaSettings. framesPerSecond != nil {
192
+ // The frame rate can be changed only on a locked for configuration device.
193
+ try mediaSettingsAVWrapper. lockDevice ( captureDevice)
194
+ mediaSettingsAVWrapper. beginConfiguration ( for: videoCaptureSession)
195
+
196
+ // Possible values for presets are hard-coded in FLT interface having
197
+ // corresponding AVCaptureSessionPreset counterparts.
198
+ // If _resolutionPreset is not supported by camera there is
199
+ // fallback to lower resolution presets.
200
+ // If none can be selected there is error condition.
201
+ do {
202
+ try setCaptureSessionPreset ( mediaSettings. resolutionPreset)
203
+ } catch {
204
+ videoCaptureSession. commitConfiguration ( )
205
+ captureDevice. unlockForConfiguration ( )
206
+ throw error
207
+ }
208
+
209
+ FLTSelectBestFormatForRequestedFrameRate (
210
+ captureDevice,
211
+ mediaSettings,
212
+ videoDimensionsForFormat)
213
+
214
+ if let framesPerSecond = mediaSettings. framesPerSecond {
215
+ // Set frame rate with 1/10 precision allowing non-integral values.
216
+ let fpsNominator = floor ( framesPerSecond. doubleValue * 10.0 )
217
+ let duration = CMTimeMake ( value: 10 , timescale: Int32 ( fpsNominator) )
218
+
219
+ mediaSettingsAVWrapper. setMinFrameDuration ( duration, on: captureDevice)
220
+ mediaSettingsAVWrapper. setMaxFrameDuration ( duration, on: captureDevice)
221
+ }
222
+
223
+ mediaSettingsAVWrapper. commitConfiguration ( for: videoCaptureSession)
224
+ mediaSettingsAVWrapper. unlockDevice ( captureDevice)
225
+ } else {
226
+ // If the frame rate is not important fall to a less restrictive
227
+ // behavior (no configuration locking).
228
+ try setCaptureSessionPreset ( mediaSettings. resolutionPreset)
229
+ }
230
+
231
+ updateOrientation ( )
232
+ }
233
+
119
234
func setUpCaptureSessionForAudioIfNeeded( ) {
120
235
// Don't setup audio twice or we will lose the audio.
121
236
guard !mediaSettings. enableAudio || !isAudioSetup else { return }
@@ -467,8 +582,9 @@ final class DefaultCamera: FLTCam, Camera {
467
582
guard let strongSelf = self else { return }
468
583
469
584
strongSelf. captureSessionQueue. async { [ weak self] in
470
- self ? . inProgressSavePhotoDelegates. removeObject (
471
- forKey: settings. uniqueID)
585
+ self ? . inProgressSavePhotoDelegates. removeValue (
586
+ forKey:
587
+ settings. uniqueID)
472
588
}
473
589
474
590
if let error = error {
0 commit comments