[Camera] Support variable buffer size (#3934)
[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         PinnedPreviewBuffer<byte> _previewBuffer;
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             CreateCameraDevice(device);
64
65             Initialize();
66         }
67
68         /// <summary>
69         /// Initializes a new instance of the <see cref="Camera"/> class.
70         /// </summary>
71         /// <remarks>CameraDevice and Type will be selected internally by CameraDeviceManager.</remarks>
72         /// <exception cref="InvalidOperationException">In case of any invalid operations.</exception>
73         /// <exception cref="NotSupportedException">The camera feature is not supported.</exception>
74         /// <since_tizen> 9 </since_tizen>
75         /// <feature> http://tizen.org/feature/camera </feature>
76         [EditorBrowsable(EditorBrowsableState.Never)]
77         public Camera() : this(CameraDevice.Default)
78         {
79         }
80
81         private void Initialize()
82         {
83             Capabilities = new CameraCapabilities(this);
84             Settings = new CameraSettings(this);
85             DisplaySettings = new CameraDisplaySettings(this);
86
87             RegisterCallbacks();
88
89             SetState(CameraState.Created);
90         }
91
92         private void CreateCameraDevice(CameraDevice device)
93         {
94             CameraDeviceType cameraDeviceType = CameraDeviceType.BuiltIn;
95             CameraDevice cameraDevice = device;
96
97             if (CameraDeviceManager.IsSupported || device == CameraDevice.Default)
98             {
99                 var deviceInfo = GetDeviceInformation();
100                 if (!deviceInfo.Any())
101                 {
102                     throw new InvalidOperationException("CDM is supported but, there's no available camera device.");
103                 }
104
105                 cameraDeviceType = deviceInfo.First().Type;
106                 cameraDevice = deviceInfo.First().Device;
107                 Log.Debug(CameraLog.Tag, $"Type:[{cameraDeviceType}], Device:[{cameraDevice}]");
108             }
109
110             CreateNativeCameraDevice(cameraDeviceType, cameraDevice);
111         }
112
113         private IEnumerable<CameraDeviceInformation> GetDeviceInformation()
114         {
115             using (var cameraDeviceManager = new CameraDeviceManager())
116             {
117                 return cameraDeviceManager.GetDeviceInformation();
118             }
119         }
120
121         private void CreateNativeCameraDevice(CameraDeviceType type, CameraDevice device)
122         {
123             if (type == CameraDeviceType.BuiltIn || type == CameraDeviceType.Usb)
124             {
125                 Native.Create(device, out _handle).
126                     ThrowIfFailed($"Failed to create {type} camera");
127             }
128             else
129             {
130                 Native.CreateNetworkCamera(device, out _handle).
131                     ThrowIfFailed($"Failed to create {type} camera");
132             }
133         }
134
135         /// <summary>
136         /// Finalizes an instance of the Camera class.
137         /// </summary>
138         ~Camera()
139         {
140             Dispose(false);
141         }
142
143         /// <summary>
144         /// Gets the native handle of the camera.
145         /// </summary>
146         /// <since_tizen> 3 </since_tizen>
147         /// <feature> http://tizen.org/feature/camera </feature>
148         public IntPtr Handle => GetHandle();
149
150         internal IntPtr GetHandle()
151         {
152             ValidateNotDisposed();
153             return _handle;
154         }
155
156         #region Dispose support
157         /// <summary>
158         /// Releases the unmanaged resources used by the camera.
159         /// </summary>
160         /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
161         /// <since_tizen> 3 </since_tizen>
162         protected virtual void Dispose(bool disposing)
163         {
164             if (!_disposed)
165             {
166                 if (disposing)
167                 {
168                     // to be used if there are any other disposable objects
169                     _previewBuffer?.Dispose();
170                 }
171
172                 if (_handle != IntPtr.Zero)
173                 {
174                     Native.Destroy(_handle);
175                     _handle = IntPtr.Zero;
176                 }
177
178                 _disposed = true;
179             }
180         }
181
182         /// <summary>
183         /// Releases all resources used by the camera.
184         /// </summary>
185         /// <since_tizen> 3 </since_tizen>
186         /// <feature> http://tizen.org/feature/camera </feature>
187         public void Dispose()
188         {
189             ReplaceDisplay(null);
190             Dispose(true);
191             GC.SuppressFinalize(this);
192         }
193
194         internal void ValidateNotDisposed()
195         {
196             if (_disposed)
197             {
198                 Log.Error(CameraLog.Tag, "Camera handle is disposed.");
199                 throw new ObjectDisposedException(nameof(Camera));
200             }
201         }
202         #endregion Dispose support
203
204         internal void ValidateState(params CameraState[] required)
205         {
206             ValidateNotDisposed();
207
208             Debug.Assert(required.Length > 0);
209
210             var curState = _state;
211             if (!required.Contains(curState))
212             {
213                 throw new InvalidOperationException($"The camera is not in a valid state. " +
214                     $"Current State : { curState }, Valid State : { string.Join(", ", required) }.");
215             }
216         }
217
218         internal void SetState(CameraState state)
219         {
220             _state = state;
221         }
222
223         /// <summary>
224         /// Changes the camera device.
225         /// </summary>
226         /// <param name="device">The hardware camera to access.</param>
227         /// <since_tizen> 3 </since_tizen>
228         /// <feature> http://tizen.org/feature/camera </feature>
229         /// <remarks>
230         /// If display reuse is set using <see cref="DisplayReuseHint"/>
231         /// before stopping the preview, the display will be reused and last frame on the display
232         /// can be kept even though camera device is changed.
233         /// The camera must be in the <see cref="CameraState.Created"/>.
234         /// </remarks>
235         /// <exception cref="ArgumentException">In case of invalid parameters.</exception>
236         /// <exception cref="InvalidOperationException">In case of any invalid operations.</exception>
237         /// <exception cref="NotSupportedException">In case of the ChangeDevice feature is not supported.</exception>
238         /// <exception cref="ObjectDisposedException">The camera already has been disposed of.</exception>
239         public void ChangeDevice(CameraDevice device)
240         {
241             ValidateState(CameraState.Created);
242             ValidationUtil.ValidateEnum(typeof(CameraDevice), device, nameof(device));
243
244             Native.ChangeDevice(_handle, device).ThrowIfFailed("Failed to change the camera device");
245         }
246
247         /// <summary>
248         /// Gets the device state.
249         /// </summary>
250         /// <param name="device">The device to get the state.</param>
251         /// <returns>Returns the state of the camera device.</returns>
252         /// <since_tizen> 4 </since_tizen>
253         /// <feature> http://tizen.org/feature/camera </feature>
254         /// <exception cref="ArgumentException">In case of invalid parameters.</exception>
255         /// <exception cref="InvalidOperationException">In case of any invalid operations.</exception>
256         /// <exception cref="NotSupportedException">In case of this feature is not supported.</exception>
257         public static CameraDeviceState GetDeviceState(CameraDevice device)
258         {
259             ValidationUtil.ValidateEnum(typeof(CameraDevice), device, nameof(device));
260
261             Native.GetDeviceState(device, out var val).ThrowIfFailed("Failed to get the camera device state.");
262
263             return val;
264         }
265
266         /// <summary>
267         /// Gets the flash state.
268         /// </summary>
269         /// <param name="device">The device to get the state.</param>
270         /// <returns>Returns the flash state of the camera device.</returns>
271         /// <since_tizen> 3 </since_tizen>
272         /// <feature> http://tizen.org/feature/camera </feature>
273         /// <exception cref="ArgumentException">In case of invalid parameters.</exception>
274         /// <exception cref="InvalidOperationException">In case of any invalid operations.</exception>
275         /// <exception cref="NotSupportedException">In case of this feature is not supported.</exception>
276         public static CameraFlashState GetFlashState(CameraDevice device)
277         {
278             ValidationUtil.ValidateEnum(typeof(CameraDevice), device, nameof(device));
279
280             Native.GetFlashState(device, out var val).ThrowIfFailed("Failed to get camera flash state");
281
282             return val;
283         }
284
285         /// <summary>
286         /// Starts capturing and drawing preview frames on the screen.
287         /// The display property must be set using <see cref="Display"/> before using this method.
288         /// If needed set fps <see cref="CameraSettings.PreviewFps"/>, preview resolution
289         /// <see cref="CameraSettings.PreviewResolution"/>, or preview format <see cref="CameraSettings.PreviewPixelFormat"/>
290         /// before using this method.
291         /// The camera must be in the <see cref="CameraState.Created"/> or the <see cref="CameraState.Captured"/> state.
292         /// </summary>
293         /// <since_tizen> 3 </since_tizen>
294         /// <privilege> http://tizen.org/privilege/camera </privilege>
295         /// <feature> http://tizen.org/feature/camera </feature>
296         /// <exception cref="InvalidOperationException">In case of any invalid operations.</exception>
297         /// <exception cref="NotSupportedException">In case of this feature is not supported.</exception>
298         /// <exception cref="ObjectDisposedException">The camera already has been disposed of.</exception>
299         /// <exception cref="UnauthorizedAccessException">In case of access to the resources cannot be granted.</exception>
300         public void StartPreview()
301         {
302             ValidateState(CameraState.Created, CameraState.Captured);
303
304             Native.StartPreview(_handle).ThrowIfFailed("Failed to start the camera preview.");
305
306             // Update by StateChangedCallback can be delayed for dozens of milliseconds.
307             SetState(CameraState.Preview);
308         }
309
310         /// <summary>
311         /// Stops capturing and drawing preview frames on the screen.
312         /// The camera must be in the <see cref="CameraState.Preview"/> state.
313         /// </summary>
314         /// <since_tizen> 3 </since_tizen>
315         /// <privilege> http://tizen.org/privilege/camera </privilege>
316         /// <feature> http://tizen.org/feature/camera </feature>
317         /// <exception cref="InvalidOperationException">In case of any invalid operations.</exception>
318         /// <exception cref="NotSupportedException">In case of this feature is not supported.</exception>
319         /// <exception cref="ObjectDisposedException">The camera already has been disposed of.</exception>
320         /// <exception cref="UnauthorizedAccessException">In case of access to the resources cannot be granted.</exception>
321         public void StopPreview()
322         {
323             ValidateState(CameraState.Preview);
324
325             Native.StopPreview(_handle).ThrowIfFailed("Failed to stop the camera preview.");
326
327             SetState(CameraState.Created);
328         }
329
330         /// <summary>
331         /// Starts capturing of still images.
332         /// EventHandler must be set for capturing using <see cref="Capturing"/>
333         /// and for completed using <see cref="CaptureCompleted"/> before calling this method.
334         /// The camera must be in the <see cref="CameraState.Preview"/> state.
335         /// </summary>
336         /// <since_tizen> 3 </since_tizen>
337         /// <privilege> http://tizen.org/privilege/camera </privilege>
338         /// <feature> http://tizen.org/feature/camera </feature>
339         /// <remarks>
340         /// This function causes the transition of the camera state from capturing to captured
341         /// automatically and the corresponding EventHandlers will be invoked.
342         /// The preview should be restarted by calling the <see cref="StartPreview"/> method after capture is completed.
343         /// </remarks>
344         /// <exception cref="InvalidOperationException">In case of any invalid operations.</exception>
345         /// <exception cref="NotSupportedException">In case of this feature is not supported.</exception>
346         /// <exception cref="ObjectDisposedException">The camera already has been disposed of.</exception>
347         /// <exception cref="UnauthorizedAccessException">In case of access to the resources cannot be granted.</exception>
348         public void StartCapture()
349         {
350             ValidateState(CameraState.Preview);
351
352             Native.StartCapture(_handle, _capturingCallback, _captureCompletedCallback).
353                 ThrowIfFailed("Failed to start the camera capture.");
354
355             SetState(CameraState.Capturing);
356         }
357
358         /// <summary>
359         /// Starts continuously capturing still images.
360         /// EventHandler must be set for capturing using <see cref="Capturing"/>
361         /// and for completed using <see cref="CaptureCompleted"/> before calling this method.
362         /// The camera must be in the <see cref="CameraState.Preview"/> state.
363         /// </summary>
364         /// <param name="count">The number of still images.</param>
365         /// <param name="interval">The interval of the capture(milliseconds).</param>
366         /// <param name="cancellationToken">The cancellation token to cancel capturing.</param>
367         /// <seealso cref="CancellationToken"/>
368         /// <since_tizen> 3 </since_tizen>
369         /// <privilege> http://tizen.org/privilege/camera </privilege>
370         /// <feature> http://tizen.org/feature/camera </feature>
371         /// <remarks>
372         /// If this is not supported, zero shutter lag occurs. The capture resolution could be
373         /// changed to the preview resolution. This function causes the transition of the camera state
374         /// from capturing to captured automatically and the corresponding Eventhandlers will be invoked.
375         /// Each captured image will be delivered through Eventhandler set using the <see cref="Capturing"/> event.
376         /// The preview should be restarted by calling the <see cref="StartPreview"/> method after capture is completed.
377         /// </remarks>
378         /// <exception cref="ArgumentOutOfRangeException">In case of invalid parameters.</exception>
379         /// <exception cref="InvalidOperationException">In case of any invalid operations.</exception>
380         /// <exception cref="NotSupportedException">In case of this feature is not supported.</exception>
381         /// <exception cref="ObjectDisposedException">The camera already has been disposed of.</exception>
382         /// <exception cref="UnauthorizedAccessException">In case of access to the resources cannot be granted.</exception>
383         public void StartCapture(int count, int interval, CancellationToken cancellationToken)
384         {
385             ValidateState(CameraState.Preview);
386
387             if (count < 2)
388             {
389                 throw new ArgumentOutOfRangeException(nameof(count), count, $"{nameof(count)} should be greater than one.");
390             }
391
392             if (interval < 0)
393             {
394                 throw new ArgumentOutOfRangeException(nameof(interval), interval, $"{nameof(interval)} should be greater than or equal to zero.");
395             }
396
397             //Handle CancellationToken
398             if (cancellationToken != CancellationToken.None)
399             {
400                 cancellationToken.Register(() =>
401                 {
402                     Native.StopContinuousCapture(_handle).ThrowIfFailed("Failed to cancel the continuous capture");
403                     SetState(CameraState.Captured);
404                 });
405             }
406
407             Native.StartContinuousCapture(_handle, count, interval, _capturingCallback, _captureCompletedCallback).
408                 ThrowIfFailed("Failed to start the continuous capture.");
409
410             SetState(CameraState.Capturing);
411         }
412
413         /// <summary>
414         /// Starts camera auto-focusing, asynchronously.
415         /// The camera must be in the <see cref="CameraState.Preview"/> or the <see cref="CameraState.Captured"/> state.
416         /// </summary>
417         /// <param name="continuous">Continuous auto focus.</param>
418         /// <since_tizen> 3 </since_tizen>
419         /// <privilege> http://tizen.org/privilege/camera </privilege>
420         /// <feature> http://tizen.org/feature/camera </feature>
421         /// <remarks>
422         /// If continuous status is true, the camera continuously tries to focus.
423         /// </remarks>
424         /// <exception cref="ArgumentException">In case of invalid parameters.</exception>
425         /// <exception cref="InvalidOperationException">In case of any invalid operations.</exception>
426         /// <exception cref="NotSupportedException">In case of this feature is not supported.</exception>
427         /// <exception cref="ObjectDisposedException">The camera already has been disposed of.</exception>
428         /// <exception cref="UnauthorizedAccessException">In case of access to the resources cannot be granted.</exception>
429         public void StartFocusing(bool continuous)
430         {
431             ValidateState(CameraState.Preview, CameraState.Captured);
432
433             Native.StartFocusing(_handle, continuous).ThrowIfFailed("Failed to cancel the camera focus.");
434         }
435
436         /// <summary>
437         /// Stops camera auto focusing.
438         /// The camera must be in the <see cref="CameraState.Preview"/> or the <see cref="CameraState.Captured"/> state.
439         /// </summary>
440         /// <since_tizen> 3 </since_tizen>
441         /// <privilege> http://tizen.org/privilege/camera </privilege>
442         /// <feature> http://tizen.org/feature/camera </feature>
443         /// <exception cref="InvalidOperationException">In case of any invalid operations.</exception>
444         /// <exception cref="NotSupportedException">In case of this feature is not supported.</exception>
445         /// <exception cref="ObjectDisposedException">The camera already has been disposed of.</exception>
446         /// <exception cref="UnauthorizedAccessException">In case of access to the resources cannot be granted.</exception>
447         public void StopFocusing()
448         {
449             ValidateState(CameraState.Preview, CameraState.Captured);
450
451             Native.CancelFocusing(_handle).ThrowIfFailed("Failed to cancel the camera focus.");
452         }
453
454         /// <summary>
455         /// Starts face detection.
456         /// The camera must be in the <see cref="CameraState.Preview"/> state.
457         /// </summary>
458         /// <since_tizen> 3 </since_tizen>
459         /// <privilege> http://tizen.org/privilege/camera </privilege>
460         /// <feature> http://tizen.org/feature/camera </feature>
461         /// <remarks>
462         /// This should be called after <see cref="StartPreview"/> is started.
463         /// The Eventhandler set using <see cref="FaceDetected"/> is invoked when the face is detected in the preview frame.
464         /// Internally, it starts continuously focus and focusing on the detected face.
465         /// </remarks>
466         /// <exception cref="InvalidOperationException">In case of any invalid operations.</exception>
467         /// <exception cref="NotSupportedException">In case of this feature is not supported.</exception>
468         /// <exception cref="ObjectDisposedException">The camera already has been disposed of.</exception>
469         /// <exception cref="UnauthorizedAccessException">In case of access to the resources cannot be granted.</exception>
470         public void StartFaceDetection()
471         {
472             ValidateState(CameraState.Preview);
473
474             _faceDetectedCallback = (IntPtr faces, int count, IntPtr userData) =>
475             {
476                 var result = new List<FaceDetectionData>();
477                 IntPtr current = faces;
478
479                 for (int i = 0; i < count; i++)
480                 {
481                     result.Add(new FaceDetectionData(current));
482                     current = IntPtr.Add(current, Marshal.SizeOf<Native.DetectedFaceStruct>());
483                 }
484
485                 FaceDetected?.Invoke(this, new FaceDetectedEventArgs(result));
486             };
487
488             Native.StartFaceDetection(_handle, _faceDetectedCallback).
489                 ThrowIfFailed("Failed to start face detection");
490         }
491
492         /// <summary>
493         /// Stops face detection.
494         /// </summary>
495         /// <since_tizen> 3 </since_tizen>
496         /// <privilege> http://tizen.org/privilege/camera </privilege>
497         /// <feature> http://tizen.org/feature/camera </feature>
498         /// <exception cref="InvalidOperationException">In case of any invalid operations.</exception>
499         /// <exception cref="NotSupportedException">In case of this feature is not supported.</exception>
500         /// <exception cref="ObjectDisposedException">The camera already has been disposed of.</exception>
501         /// <exception cref="UnauthorizedAccessException">In case of access to the resources cannot be granted.</exception>
502         public void StopFaceDetection()
503         {
504             ValidateNotDisposed();
505
506             if (_faceDetectedCallback == null)
507             {
508                 throw new InvalidOperationException("The face detection is not started.");
509             }
510
511             Native.StopFaceDetection(_handle).ThrowIfFailed("Failed to stop the face detection.");
512
513             _faceDetectedCallback = null;
514         }
515     }
516 }