[Camera] Change the dispose timing of CameraDeviceManager (#3270)
[platform/core/csapi/tizenfx.git] / src / Tizen.Multimedia.Camera / Camera / Camera.cs
1 /*
2  * Copyright (c) 2016 Samsung Electronics Co., Ltd All Rights Reserved
3  *
4  * Licensed under the Apache License, Version 2.0 (the License);
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an AS IS BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 using System;
18 using System.Collections.Generic;
19 using System.ComponentModel;
20 using System.Diagnostics;
21 using System.Linq;
22 using System.Runtime.InteropServices;
23 using System.Threading;
24 using Native = Interop.Camera;
25
26 namespace Tizen.Multimedia
27 {
28     static internal class CameraLog
29     {
30         internal const string Tag = "Tizen.Multimedia.Camera";
31         internal const string Enter = "[Enter]";
32         internal const string Leave = "[Leave]";
33     }
34
35     /// <summary>
36     /// This camera class provides methods to capture photos and supports setting up notifications
37     /// for state changes of capturing, previewing, focusing, and informing about the resolution and the binary format,
38     /// and functions for picture manipulations like sepia, negative, and many more.
39     /// It also notifies you when a significant picture parameter changes, (For example, focus).
40     /// </summary>
41     /// <since_tizen> 3 </since_tizen>
42     /// <feature> http://tizen.org/feature/camera </feature>
43     public partial class Camera : IDisposable, IDisplayable<CameraError>
44     {
45         private IntPtr _handle = IntPtr.Zero;
46         private bool _disposed = false;
47         private CameraState _state = CameraState.None;
48         private CameraDeviceManager _cameraDeviceManager;
49
50         /// <summary>
51         /// Initializes a new instance of the <see cref="Camera"/> class.
52         /// </summary>
53         /// <param name="device">The camera device to access.</param>
54         /// <exception cref="ArgumentException">Invalid CameraDevice type.</exception>
55         /// <exception cref="InvalidOperationException">In case of any invalid operations.</exception>
56         /// <exception cref="NotSupportedException">The camera feature is not supported.</exception>
57         /// <since_tizen> 3 </since_tizen>
58         /// <feature> http://tizen.org/feature/camera </feature>
59         public Camera(CameraDevice device)
60         {
61             ValidationUtil.ValidateEnum(typeof(CameraDevice), device, nameof(device));
62
63             Create(device);
64
65             Capabilities = new CameraCapabilities(this);
66             Settings = new CameraSettings(this);
67             DisplaySettings = new CameraDisplaySettings(this);
68
69             RegisterCallbacks();
70
71             SetState(CameraState.Created);
72         }
73
74         private void Create(CameraDevice device)
75         {
76             CameraDeviceType cameraDeviceType = CameraDeviceType.BuiltIn;
77
78             try
79             {
80                 _cameraDeviceManager = new CameraDeviceManager();
81                 var deviceInfo = _cameraDeviceManager.GetDeviceInformation();
82                 Log.Info(CameraLog.Tag, deviceInfo.ToString());
83
84                 cameraDeviceType = deviceInfo.First().Type;
85             }
86             catch (NotSupportedException e)
87             {
88                 Tizen.Log.Info(CameraLog.Tag,
89                     $"CameraDeviceManager is not supported. {e.Message}. Not error.");
90             }
91
92             if (cameraDeviceType == CameraDeviceType.BuiltIn ||
93                 cameraDeviceType == CameraDeviceType.Usb)
94             {
95                 Native.Create(device, out _handle).
96                     ThrowIfFailed($"Failed to create {cameraDeviceType.ToString()} camera");
97             }
98             else
99             {
100                 Native.CreateNetworkCamera(device, out _handle).
101                     ThrowIfFailed($"Failed to create {cameraDeviceType.ToString()} camera");
102             }
103         }
104
105         /// <summary>
106         /// Finalizes an instance of the Camera class.
107         /// </summary>
108         ~Camera()
109         {
110             Dispose(false);
111         }
112
113         /// <summary>
114         /// Gets the native handle of the camera.
115         /// </summary>
116         /// <since_tizen> 3 </since_tizen>
117         /// <feature> http://tizen.org/feature/camera </feature>
118         public IntPtr Handle => GetHandle();
119
120         internal IntPtr GetHandle()
121         {
122             ValidateNotDisposed();
123             return _handle;
124         }
125
126         #region Dispose support
127         /// <summary>
128         /// Releases the unmanaged resources used by the camera.
129         /// </summary>
130         /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
131         /// <since_tizen> 3 </since_tizen>
132         protected virtual void Dispose(bool disposing)
133         {
134             if (!_disposed)
135             {
136                 if (disposing)
137                 {
138                     // to be used if there are any other disposable objects
139                 }
140
141                 if (_handle != IntPtr.Zero)
142                 {
143                     Native.Destroy(_handle);
144                     _handle = IntPtr.Zero;
145                 }
146
147                 _disposed = true;
148             }
149         }
150
151         /// <summary>
152         /// Releases all resources used by the camera.
153         /// </summary>
154         /// <since_tizen> 3 </since_tizen>
155         /// <feature> http://tizen.org/feature/camera </feature>
156         public void Dispose()
157         {
158             ReplaceDisplay(null);
159             Dispose(true);
160             GC.SuppressFinalize(this);
161         }
162
163         internal void ValidateNotDisposed()
164         {
165             if (_disposed)
166             {
167                 Log.Error(CameraLog.Tag, "Camera handle is disposed.");
168                 throw new ObjectDisposedException(nameof(Camera));
169             }
170         }
171         #endregion Dispose support
172
173         internal void ValidateState(params CameraState[] required)
174         {
175             ValidateNotDisposed();
176
177             Debug.Assert(required.Length > 0);
178
179             var curState = _state;
180             if (!required.Contains(curState))
181             {
182                 throw new InvalidOperationException($"The camera is not in a valid state. " +
183                     $"Current State : { curState }, Valid State : { string.Join(", ", required) }.");
184             }
185         }
186
187         internal void SetState(CameraState state)
188         {
189             _state = state;
190         }
191
192         /// <summary>
193         /// Changes the camera device.
194         /// </summary>
195         /// <param name="device">The hardware camera to access.</param>
196         /// <since_tizen> 3 </since_tizen>
197         /// <feature> http://tizen.org/feature/camera </feature>
198         /// <remarks>
199         /// If display reuse is set using <see cref="DisplayReuseHint"/>
200         /// before stopping the preview, the display will be reused and last frame on the display
201         /// can be kept even though camera device is changed.
202         /// The camera must be in the <see cref="CameraState.Created"/>.
203         /// </remarks>
204         /// <exception cref="ArgumentException">In case of invalid parameters.</exception>
205         /// <exception cref="InvalidOperationException">In case of any invalid operations.</exception>
206         /// <exception cref="NotSupportedException">In case of the ChangeDevice feature is not supported.</exception>
207         /// <exception cref="ObjectDisposedException">The camera already has been disposed of.</exception>
208         public void ChangeDevice(CameraDevice device)
209         {
210             ValidateState(CameraState.Created);
211             ValidationUtil.ValidateEnum(typeof(CameraDevice), device, nameof(device));
212
213             Native.ChangeDevice(_handle, device).ThrowIfFailed("Failed to change the camera device");
214         }
215
216         /// <summary>
217         /// Gets the device state.
218         /// </summary>
219         /// <param name="device">The device to get the state.</param>
220         /// <returns>Returns the state of the camera device.</returns>
221         /// <since_tizen> 4 </since_tizen>
222         /// <feature> http://tizen.org/feature/camera </feature>
223         /// <exception cref="ArgumentException">In case of invalid parameters.</exception>
224         /// <exception cref="InvalidOperationException">In case of any invalid operations.</exception>
225         /// <exception cref="NotSupportedException">In case of this feature is not supported.</exception>
226         public static CameraDeviceState GetDeviceState(CameraDevice device)
227         {
228             ValidationUtil.ValidateEnum(typeof(CameraDevice), device, nameof(device));
229
230             Native.GetDeviceState(device, out var val).ThrowIfFailed("Failed to get the camera device state.");
231
232             return val;
233         }
234
235         /// <summary>
236         /// Gets the flash state.
237         /// </summary>
238         /// <param name="device">The device to get the state.</param>
239         /// <returns>Returns the flash state of the camera device.</returns>
240         /// <since_tizen> 3 </since_tizen>
241         /// <feature> http://tizen.org/feature/camera </feature>
242         /// <exception cref="ArgumentException">In case of invalid parameters.</exception>
243         /// <exception cref="InvalidOperationException">In case of any invalid operations.</exception>
244         /// <exception cref="NotSupportedException">In case of this feature is not supported.</exception>
245         public static CameraFlashState GetFlashState(CameraDevice device)
246         {
247             ValidationUtil.ValidateEnum(typeof(CameraDevice), device, nameof(device));
248
249             Native.GetFlashState(device, out var val).ThrowIfFailed("Failed to get camera flash state");
250
251             return val;
252         }
253
254         /// <summary>
255         /// Starts capturing and drawing preview frames on the screen.
256         /// The display property must be set using <see cref="Display"/> before using this method.
257         /// If needed set fps <see cref="CameraSettings.PreviewFps"/>, preview resolution
258         /// <see cref="CameraSettings.PreviewResolution"/>, or preview format <see cref="CameraSettings.PreviewPixelFormat"/>
259         /// before using this method.
260         /// The camera must be in the <see cref="CameraState.Created"/> or the <see cref="CameraState.Captured"/> state.
261         /// </summary>
262         /// <since_tizen> 3 </since_tizen>
263         /// <privilege> http://tizen.org/privilege/camera </privilege>
264         /// <feature> http://tizen.org/feature/camera </feature>
265         /// <exception cref="InvalidOperationException">In case of any invalid operations.</exception>
266         /// <exception cref="NotSupportedException">In case of this feature is not supported.</exception>
267         /// <exception cref="ObjectDisposedException">The camera already has been disposed of.</exception>
268         /// <exception cref="UnauthorizedAccessException">In case of access to the resources cannot be granted.</exception>
269         public void StartPreview()
270         {
271             ValidateState(CameraState.Created, CameraState.Captured);
272
273             Native.StartPreview(_handle).ThrowIfFailed("Failed to start the camera preview.");
274
275             // Update by StateChangedCallback can be delayed for dozens of milliseconds.
276             SetState(CameraState.Preview);
277         }
278
279         /// <summary>
280         /// Stops capturing and drawing preview frames on the screen.
281         /// The camera must be in the <see cref="CameraState.Preview"/> state.
282         /// </summary>
283         /// <since_tizen> 3 </since_tizen>
284         /// <privilege> http://tizen.org/privilege/camera </privilege>
285         /// <feature> http://tizen.org/feature/camera </feature>
286         /// <exception cref="InvalidOperationException">In case of any invalid operations.</exception>
287         /// <exception cref="NotSupportedException">In case of this feature is not supported.</exception>
288         /// <exception cref="ObjectDisposedException">The camera already has been disposed of.</exception>
289         /// <exception cref="UnauthorizedAccessException">In case of access to the resources cannot be granted.</exception>
290         public void StopPreview()
291         {
292             ValidateState(CameraState.Preview);
293
294             Native.StopPreview(_handle).ThrowIfFailed("Failed to stop the camera preview.");
295
296             SetState(CameraState.Created);
297         }
298
299         /// <summary>
300         /// Starts capturing of still images.
301         /// EventHandler must be set for capturing using <see cref="Capturing"/>
302         /// and for completed using <see cref="CaptureCompleted"/> before calling this method.
303         /// The camera must be in the <see cref="CameraState.Preview"/> state.
304         /// </summary>
305         /// <since_tizen> 3 </since_tizen>
306         /// <privilege> http://tizen.org/privilege/camera </privilege>
307         /// <feature> http://tizen.org/feature/camera </feature>
308         /// <remarks>
309         /// This function causes the transition of the camera state from capturing to captured
310         /// automatically and the corresponding EventHandlers will be invoked.
311         /// The preview should be restarted by calling the <see cref="StartPreview"/> method after capture is completed.
312         /// </remarks>
313         /// <exception cref="InvalidOperationException">In case of any invalid operations.</exception>
314         /// <exception cref="NotSupportedException">In case of this feature is not supported.</exception>
315         /// <exception cref="ObjectDisposedException">The camera already has been disposed of.</exception>
316         /// <exception cref="UnauthorizedAccessException">In case of access to the resources cannot be granted.</exception>
317         public void StartCapture()
318         {
319             ValidateState(CameraState.Preview);
320
321             Native.StartCapture(_handle, _capturingCallback, _captureCompletedCallback).
322                 ThrowIfFailed("Failed to start the camera capture.");
323
324             SetState(CameraState.Capturing);
325         }
326
327         /// <summary>
328         /// Starts continuously capturing still images.
329         /// EventHandler must be set for capturing using <see cref="Capturing"/>
330         /// and for completed using <see cref="CaptureCompleted"/> before calling this method.
331         /// The camera must be in the <see cref="CameraState.Preview"/> state.
332         /// </summary>
333         /// <param name="count">The number of still images.</param>
334         /// <param name="interval">The interval of the capture(milliseconds).</param>
335         /// <param name="cancellationToken">The cancellation token to cancel capturing.</param>
336         /// <seealso cref="CancellationToken"/>
337         /// <since_tizen> 3 </since_tizen>
338         /// <privilege> http://tizen.org/privilege/camera </privilege>
339         /// <feature> http://tizen.org/feature/camera </feature>
340         /// <remarks>
341         /// If this is not supported, zero shutter lag occurs. The capture resolution could be
342         /// changed to the preview resolution. This function causes the transition of the camera state
343         /// from capturing to captured automatically and the corresponding Eventhandlers will be invoked.
344         /// Each captured image will be delivered through Eventhandler set using the <see cref="Capturing"/> event.
345         /// The preview should be restarted by calling the <see cref="StartPreview"/> method after capture is completed.
346         /// </remarks>
347         /// <exception cref="ArgumentOutOfRangeException">In case of invalid parameters.</exception>
348         /// <exception cref="InvalidOperationException">In case of any invalid operations.</exception>
349         /// <exception cref="NotSupportedException">In case of this feature is not supported.</exception>
350         /// <exception cref="ObjectDisposedException">The camera already has been disposed of.</exception>
351         /// <exception cref="UnauthorizedAccessException">In case of access to the resources cannot be granted.</exception>
352         public void StartCapture(int count, int interval, CancellationToken cancellationToken)
353         {
354             ValidateState(CameraState.Preview);
355
356             if (count < 2)
357             {
358                 throw new ArgumentOutOfRangeException(nameof(count), count, $"{nameof(count)} should be greater than one.");
359             }
360
361             if (interval < 0)
362             {
363                 throw new ArgumentOutOfRangeException(nameof(interval), interval, $"{nameof(interval)} should be greater than or equal to zero.");
364             }
365
366             //Handle CancellationToken
367             if (cancellationToken != CancellationToken.None)
368             {
369                 cancellationToken.Register(() =>
370                 {
371                     Native.StopContinuousCapture(_handle).ThrowIfFailed("Failed to cancel the continuous capture");
372                     SetState(CameraState.Captured);
373                 });
374             }
375
376             Native.StartContinuousCapture(_handle, count, interval, _capturingCallback, _captureCompletedCallback).
377                 ThrowIfFailed("Failed to start the continuous capture.");
378
379             SetState(CameraState.Capturing);
380         }
381
382         /// <summary>
383         /// Starts camera auto-focusing, asynchronously.
384         /// The camera must be in the <see cref="CameraState.Preview"/> or the <see cref="CameraState.Captured"/> state.
385         /// </summary>
386         /// <param name="continuous">Continuous auto focus.</param>
387         /// <since_tizen> 3 </since_tizen>
388         /// <privilege> http://tizen.org/privilege/camera </privilege>
389         /// <feature> http://tizen.org/feature/camera </feature>
390         /// <remarks>
391         /// If continuous status is true, the camera continuously tries to focus.
392         /// </remarks>
393         /// <exception cref="ArgumentException">In case of invalid parameters.</exception>
394         /// <exception cref="InvalidOperationException">In case of any invalid operations.</exception>
395         /// <exception cref="NotSupportedException">In case of this feature is not supported.</exception>
396         /// <exception cref="ObjectDisposedException">The camera already has been disposed of.</exception>
397         /// <exception cref="UnauthorizedAccessException">In case of access to the resources cannot be granted.</exception>
398         public void StartFocusing(bool continuous)
399         {
400             ValidateState(CameraState.Preview, CameraState.Captured);
401
402             Native.StartFocusing(_handle, continuous).ThrowIfFailed("Failed to cancel the camera focus.");
403         }
404
405         /// <summary>
406         /// Stops camera auto focusing.
407         /// The camera must be in the <see cref="CameraState.Preview"/> or the <see cref="CameraState.Captured"/> state.
408         /// </summary>
409         /// <since_tizen> 3 </since_tizen>
410         /// <privilege> http://tizen.org/privilege/camera </privilege>
411         /// <feature> http://tizen.org/feature/camera </feature>
412         /// <exception cref="InvalidOperationException">In case of any invalid operations.</exception>
413         /// <exception cref="NotSupportedException">In case of this feature is not supported.</exception>
414         /// <exception cref="ObjectDisposedException">The camera already has been disposed of.</exception>
415         /// <exception cref="UnauthorizedAccessException">In case of access to the resources cannot be granted.</exception>
416         public void StopFocusing()
417         {
418             ValidateState(CameraState.Preview, CameraState.Captured);
419
420             Native.CancelFocusing(_handle).ThrowIfFailed("Failed to cancel the camera focus.");
421         }
422
423         /// <summary>
424         /// Starts face detection.
425         /// The camera must be in the <see cref="CameraState.Preview"/> state.
426         /// </summary>
427         /// <since_tizen> 3 </since_tizen>
428         /// <privilege> http://tizen.org/privilege/camera </privilege>
429         /// <feature> http://tizen.org/feature/camera </feature>
430         /// <remarks>
431         /// This should be called after <see cref="StartPreview"/> is started.
432         /// The Eventhandler set using <see cref="FaceDetected"/> is invoked when the face is detected in the preview frame.
433         /// Internally, it starts continuously focus and focusing on the detected face.
434         /// </remarks>
435         /// <exception cref="InvalidOperationException">In case of any invalid operations.</exception>
436         /// <exception cref="NotSupportedException">In case of this feature is not supported.</exception>
437         /// <exception cref="ObjectDisposedException">The camera already has been disposed of.</exception>
438         /// <exception cref="UnauthorizedAccessException">In case of access to the resources cannot be granted.</exception>
439         public void StartFaceDetection()
440         {
441             ValidateState(CameraState.Preview);
442
443             _faceDetectedCallback = (IntPtr faces, int count, IntPtr userData) =>
444             {
445                 var result = new List<FaceDetectionData>();
446                 IntPtr current = faces;
447
448                 for (int i = 0; i < count; i++)
449                 {
450                     result.Add(new FaceDetectionData(current));
451                     current = IntPtr.Add(current, Marshal.SizeOf<Native.DetectedFaceStruct>());
452                 }
453
454                 FaceDetected?.Invoke(this, new FaceDetectedEventArgs(result));
455             };
456
457             Native.StartFaceDetection(_handle, _faceDetectedCallback).
458                 ThrowIfFailed("Failed to start face detection");
459         }
460
461         /// <summary>
462         /// Stops face detection.
463         /// </summary>
464         /// <since_tizen> 3 </since_tizen>
465         /// <privilege> http://tizen.org/privilege/camera </privilege>
466         /// <feature> http://tizen.org/feature/camera </feature>
467         /// <exception cref="InvalidOperationException">In case of any invalid operations.</exception>
468         /// <exception cref="NotSupportedException">In case of this feature is not supported.</exception>
469         /// <exception cref="ObjectDisposedException">The camera already has been disposed of.</exception>
470         /// <exception cref="UnauthorizedAccessException">In case of access to the resources cannot be granted.</exception>
471         public void StopFaceDetection()
472         {
473             ValidateNotDisposed();
474
475             if (_faceDetectedCallback == null)
476             {
477                 throw new InvalidOperationException("The face detection is not started.");
478             }
479
480             Native.StopFaceDetection(_handle).ThrowIfFailed("Failed to stop the face detection.");
481
482             _faceDetectedCallback = null;
483         }
484     }
485 }