/* * Copyright (c) 2016 Samsung Electronics Co., Ltd All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the License); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an AS IS BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; using System.Threading; using static Interop; using Native = Interop.Camera; namespace Tizen.Multimedia { static internal class CameraLog { internal const string Tag = "Tizen.Multimedia.Camera"; internal const string Enter = "[Enter]"; internal const string Leave = "[Leave]"; } /// /// This camera class provides methods to capture photos and supports setting up notifications /// for state changes of capturing, previewing, focusing, and informing about the resolution and the binary format, /// and functions for picture manipulations like sepia, negative, and many more. /// It also notifies you when a significant picture parameter changes, (For example, focus). /// /// 3 public class Camera : IDisposable, IDisplayable { private IntPtr _handle = IntPtr.Zero; private bool _disposed = false; private CameraState _state = CameraState.None; /// /// Initializes a new instance of the class. /// /// http://tizen.org/feature/camera /// 3 /// The camera device to access. public Camera(CameraDevice device) { if (!Features.IsSupported(CameraFeatures.Camera)) { throw new NotSupportedException("Camera feature is not supported."); } CameraErrorFactory.ThrowIfError(Native.Create(device, out _handle), "Failed to create camera instance"); Capabilities = new CameraCapabilities(this); Settings = new CameraSettings(this); DisplaySettings = new CameraDisplaySettings(this); RegisterCallbacks(); SetState(CameraState.Created); } /// /// Finalizes an instance of the Camera class. /// ~Camera() { Dispose(false); } /// /// Gets the native handle of the camera. /// /// 3 public IntPtr Handle => GetHandle(); internal IntPtr GetHandle() { ValidateNotDisposed(); return _handle; } #region Dispose support /// /// Releases the unmanaged resources used by the camera. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. /// 3 protected virtual void Dispose(bool disposing) { if (!_disposed) { if (disposing) { // to be used if there are any other disposable objects } if (_handle != IntPtr.Zero) { Native.Destroy(_handle); _handle = IntPtr.Zero; } _disposed = true; } } /// /// Releases all resources used by the camera. /// /// 3 public void Dispose() { ReplaceDisplay(null); Dispose(true); GC.SuppressFinalize(this); } internal void ValidateNotDisposed() { if (_disposed) { Log.Error(CameraLog.Tag, "Camera handle is disposed."); throw new ObjectDisposedException(nameof(Camera)); } } #endregion Dispose support #region Check camera state internal void ValidateState(params CameraState[] required) { ValidateNotDisposed(); Debug.Assert(required.Length > 0); var curState = _state; if (!required.Contains(curState)) { throw new InvalidOperationException($"The camera is not in a valid state. " + $"Current State : { curState }, Valid State : { string.Join(", ", required) }."); } } internal void SetState(CameraState state) { _state = state; } #endregion Check camera state #region EventHandlers /// /// An event that occurs when the camera interrupt is started by the policy. /// /// 4 public event EventHandler InterruptStarted; private Native.InterruptStartedCallback _interruptStartedCallback; /// /// An event that occurs when an camera is interrupted by the policy. /// /// 3 public event EventHandler Interrupted; private Native.InterruptedCallback _interruptedCallback; /// /// An event that occurs when there is an asynchronous error. /// /// 3 public event EventHandler ErrorOccurred; private Native.ErrorCallback _errorCallback; /// /// An event that occurs when the auto focus state is changed. /// /// 3 public event EventHandler FocusStateChanged; private Native.FocusStateChangedCallback _focusStateChangedCallback; /// /// An event that occurs when a face is detected in the preview frame. /// /// 3 public event EventHandler FaceDetected; private Native.FaceDetectedCallback _faceDetectedCallback; /// /// An event that occurs during capture of an image. /// /// 3 public event EventHandler Capturing; private Native.CapturingCallback _capturingCallback; /// /// An event that occurs after the capture of the image. /// /// 3 public event EventHandler CaptureCompleted; private Native.CaptureCompletedCallback _captureCompletedCallback; private Native.HdrCaptureProgressCallback _hdrCaptureProgressCallback; private event EventHandler _hdrCaptureProgress; private object _hdrCaptureProgressEventLock = new object(); /// /// An event that occurs when there is a change in the HDR capture progress. /// Checks whether the is supported or not before adding this EventHandler. /// /// 3 /// In case of HDR feature is not supported. public event EventHandler HdrCaptureProgress { add { lock (_hdrCaptureProgressEventLock) { if (_hdrCaptureProgress == null) { RegisterHdrCaptureProgress(); } _hdrCaptureProgress += value; } } remove { lock (_hdrCaptureProgressEventLock) { _hdrCaptureProgress -= value; if (_hdrCaptureProgress == null) { UnregisterHdrCaptureProgress(); } } } } /// /// An event that occurs when the camera state is changed. /// /// 3 public event EventHandler StateChanged; private Native.StateChangedCallback _stateChangedCallback; private static Native.DeviceStateChangedCallback _deviceStateChangedCallback; private static event EventHandler _deviceStateChanged; private static object _deviceStateChangedEventLock = new object(); private static int _deviceStateCallbackId; /// /// An event that occurs after the is changed. /// /// 3 /// In case of any invalid operations. /// In case of this feature is not supported. /// In case of invalid parameters. public static event EventHandler DeviceStateChanged { add { lock (_deviceStateChangedEventLock) { if (_deviceStateChanged == null) { RegisterDeviceStateChangedCallback(); } _deviceStateChanged += value; } } remove { lock (_deviceStateChangedEventLock) { _deviceStateChanged -= value; if (_deviceStateChanged == null) { UnregisterDeviceStateChangedCallback(); } } } } private Native.PreviewCallback _previewCallback; private event EventHandler _preview; private object _previewEventLock = new object(); /// /// An event that occurs once per frame when previewing. /// Preview callback is registered when an user adds a callback explicitly to avoid useless P/Invoke. /// /// 3 public event EventHandler Preview { add { lock (_previewEventLock) { if (_preview == null) { RegisterPreviewCallback(); } _preview += value; } } remove { lock (_previewEventLock) { _preview -= value; if (_preview == null) { UnregisterPreviewCallback(); } } } } private Native.MediaPacketPreviewCallback _mediaPacketPreviewCallback; private EventHandler _mediaPacketPreview; private object _mediaPacketPreviewEventLock = new object(); /// /// An event that occurs once per frame when previewing. /// Preview callback is registered when an user adds a callback explicitly to avoid useless P/Invoke. /// /// 3 public event EventHandler MediaPacketPreview { add { lock (_mediaPacketPreviewEventLock) { if (_mediaPacketPreview == null) { RegisterMediaPacketPreviewCallback(); } _mediaPacketPreview += value; } } remove { lock (_mediaPacketPreviewEventLock) { _mediaPacketPreview -= value; if (_mediaPacketPreview == null) { UnregisterMediaPacketPreviewCallback(); } } } } #endregion EventHandlers #region Properties /// /// Gets or sets the various camera settings. /// /// 4 public CameraSettings Settings { get; } /// /// Gets the various camera capabilities. /// /// 4 public CameraCapabilities Capabilities { get; } /// /// Get/set various camera display properties. /// /// 3 public CameraDisplaySettings DisplaySettings { get; } private Display _display; private CameraError SetDisplay(Display display) { if (display == null) { return CameraDisplay.SetTarget(GetHandle(), DisplayType.None, IntPtr.Zero); } return display.ApplyTo(this); } private void ReplaceDisplay(Display newDisplay) { _display?.SetOwner(null); _display = newDisplay; _display?.SetOwner(this); } /// /// Sets or gets the display type and handle to show preview images. /// The camera must be in the state. /// /// 3 /// /// This must be set before the StartPreview() method. /// In custom ROI display mode, DisplayRoiArea property must be set before calling this method. /// /// In case of any invalid operations. /// In case of this feature is not supported. /// The camera already has been disposed of. /// In case of access to the resources cannot be granted. public Display Display { get { return _display; } set { ValidateState(CameraState.Created); if (value?.Owner != null) { if (ReferenceEquals(this, value.Owner)) { return; } throw new ArgumentException("The display has already been assigned to another."); } CameraErrorFactory.ThrowIfError(SetDisplay(value), "Failed to set the camera display"); ReplaceDisplay(value); } } CameraError IDisplayable.ApplyEvasDisplay(DisplayType type, ElmSharp.EvasObject evasObject) { Debug.Assert(_disposed == false); ValidationUtil.ValidateEnum(typeof(DisplayType), type, nameof(type)); return CameraDisplay.SetTarget(GetHandle(), type, evasObject); } CameraError IDisplayable.ApplyEcoreWindow(IntPtr windowHandle) { throw new NotSupportedException("Camera does not support NUI.Window display."); } /// /// Gets the state of the camera. /// /// 3 /// None, Created, Preview, Capturing, Captured. /// The camera already has been disposed of. public CameraState State { get { ValidateNotDisposed(); CameraState val = CameraState.None; CameraErrorFactory.ThrowIfError(Native.GetState(_handle, out val), "Failed to get camera state"); return val; } } /// /// The hint for the display reuse. /// If the hint is set to true, the display will be reused when the camera device is changed with /// the ChangeDevice method. /// /// 3 /// In case of invalid parameters. /// An invalid state. /// The camera already has been disposed of. public bool DisplayReuseHint { get { ValidateNotDisposed(); CameraErrorFactory.ThrowIfError(Native.GetDisplayReuseHint(_handle, out bool val), "Failed to get camera display reuse hint"); return val; } set { ValidateState(CameraState.Preview); CameraErrorFactory.ThrowIfError(Native.SetDisplayReuseHint(_handle, value), "Failed to set display reuse hint."); } } /// /// Gets the facing direction of the camera module. /// /// 3 /// A that specifies the facing direction of the camera device. /// The camera already has been disposed of. public CameraFacingDirection Direction { get { ValidateNotDisposed(); CameraErrorFactory.ThrowIfError(Native.GetFacingDirection(_handle, out var val), "Failed to get camera direction"); return val; } } /// /// Gets the camera device count. /// /// 3 /// This returns 2, if the device supports primary and secondary cameras. /// Otherwise 1, if the device only supports primary camera. /// The camera already has been disposed of. public int CameraCount { get { ValidateNotDisposed(); CameraErrorFactory.ThrowIfError(Native.GetDeviceCount(_handle, out int val), "Failed to get camera device count"); return val; } } #endregion Properties #region Methods /// /// Changes the camera device. /// /// 3 /// The hardware camera to access. /// /// If display reuse is set using /// before stopping the preview, the display will be reused and last frame on the display /// can be kept even though camera device is changed. /// The camera must be in the . /// /// In case of invalid parameters. /// In case of any invalid operations. /// In case of the ChangeDevice feature is not supported. /// The camera already has been disposed of. public void ChangeDevice(CameraDevice device) { ValidateState(CameraState.Created); ValidationUtil.ValidateEnum(typeof(CameraDevice), device, nameof(device)); CameraErrorFactory.ThrowIfError(Native.ChangeDevice(_handle, device), "Failed to change the camera device"); } /// /// Gets the device state. /// /// 4 /// The device to get the state. /// Returns the state of the camera device. /// In case of invalid parameters. /// In case of any invalid operations. /// In case of this feature is not supported. public static CameraDeviceState GetDeviceState(CameraDevice device) { ValidationUtil.ValidateEnum(typeof(CameraDevice), device, nameof(device)); CameraErrorFactory.ThrowIfError(Native.GetDeviceState(device, out var val), "Failed to get the camera device state."); return val; } /// /// Gets the flash state. /// /// 3 /// The device to get the state. /// Returns the flash state of the camera device. /// In case of invalid parameters. /// In case of any invalid operations. /// In case of this feature is not supported. public static CameraFlashState GetFlashState(CameraDevice device) { ValidationUtil.ValidateEnum(typeof(CameraDevice), device, nameof(device)); CameraErrorFactory.ThrowIfError(Native.GetFlashState(device, out var val), "Failed to get camera flash state"); return val; } /// /// Starts capturing and drawing preview frames on the screen. /// The display property must be set using before using this method. /// If needed set fps , preview resolution /// , or preview format /// before using this method. /// The camera must be in the or the state. /// /// 3 /// /// http://tizen.org/privilege/camera /// /// In case of any invalid operations. /// In case of this feature is not supported. /// The camera already has been disposed of. /// In case of access to the resources cannot be granted. public void StartPreview() { ValidateState(CameraState.Created, CameraState.Captured); CameraErrorFactory.ThrowIfError(Native.StartPreview(_handle), "Failed to start the camera preview."); // Update by StateChangedCallback can be delayed for dozens of milliseconds. SetState(CameraState.Preview); } /// /// Stops capturing and drawing preview frames on the screen. /// The camera must be in the state. /// /// 3 /// /// http://tizen.org/privilege/camera /// /// In case of any invalid operations. /// In case of this feature is not supported. /// The camera already has been disposed of. /// In case of access to the resources cannot be granted. public void StopPreview() { ValidateState(CameraState.Preview); CameraErrorFactory.ThrowIfError(Native.StopPreview(_handle), "Failed to stop the camera preview."); SetState(CameraState.Created); } /// /// Starts capturing of still images. /// EventHandler must be set for capturing using /// and for completed using before calling this method. /// The camera must be in the state. /// /// 3 /// /// http://tizen.org/privilege/camera /// /// /// This function causes the transition of the camera state from capturing to captured /// automatically and the corresponding EventHandlers will be invoked. /// The preview should be restarted by calling the method after capture is completed. /// /// In case of any invalid operations. /// In case of this feature is not supported. /// The camera already has been disposed of. /// In case of access to the resources cannot be granted. public void StartCapture() { ValidateState(CameraState.Preview); CameraErrorFactory.ThrowIfError(Native.StartCapture(_handle, _capturingCallback, _captureCompletedCallback, IntPtr.Zero), "Failed to start the camera capture."); SetState(CameraState.Capturing); } /// /// Starts continuously capturing still images. /// EventHandler must be set for capturing using /// and for completed using before calling this method. /// The camera must be in the state. /// /// 3 /// /// http://tizen.org/privilege/camera /// /// The number of still images. /// The interval of the capture(milliseconds). /// The cancellation token to cancel capturing. /// /// /// If this is not supported, zero shutter lag occurs. The capture resolution could be /// changed to the preview resolution. This function causes the transition of the camera state /// from capturing to captured automatically and the corresponding Eventhandlers will be invoked. /// Each captured image will be delivered through Eventhandler set using the event. /// The preview should be restarted by calling the method after capture is completed. /// /// In case of invalid parameters. /// In case of any invalid operations. /// In case of this feature is not supported. /// The camera already has been disposed of. /// In case of access to the resources cannot be granted. public void StartCapture(int count, int interval, CancellationToken cancellationToken) { ValidateState(CameraState.Preview); if (count < 2) { throw new ArgumentOutOfRangeException(nameof(count), count, $"{nameof(count)} should be greater than one."); } if (interval < 0) { throw new ArgumentOutOfRangeException(nameof(interval), interval, $"{nameof(interval)} should be greater than or equal to zero."); } //Handle CancellationToken if (cancellationToken != CancellationToken.None) { cancellationToken.Register(() => { CameraErrorFactory.ThrowIfError(Native.StopContinuousCapture(_handle), "Failed to cancel the continuous capture"); SetState(CameraState.Captured); }); } CameraErrorFactory.ThrowIfError(Native.StartContinuousCapture(_handle, count, interval, _capturingCallback, _captureCompletedCallback, IntPtr.Zero), "Failed to start the continuous capture."); SetState(CameraState.Capturing); } /// /// Starts camera auto-focusing, asynchronously. /// The camera must be in the or the state. /// /// 3 /// Continuous auto focus. /// /// http://tizen.org/privilege/camera /// /// /// If continuous status is true, the camera continuously tries to focus. /// /// In case of invalid parameters. /// In case of any invalid operations. /// In case of this feature is not supported. /// The camera already has been disposed of. /// In case of access to the resources cannot be granted. public void StartFocusing(bool continuous) { ValidateState(CameraState.Preview, CameraState.Captured); CameraErrorFactory.ThrowIfError(Native.StartFocusing(_handle, continuous), "Failed to cancel the camera focus."); } /// /// Stops camera auto focusing. /// The camera must be in the or the state. /// /// 3 /// /// http://tizen.org/privilege/camera /// /// In case of any invalid operations. /// In case of this feature is not supported. /// The camera already has been disposed of. /// In case of access to the resources cannot be granted. public void StopFocusing() { ValidateState(CameraState.Preview, CameraState.Captured); CameraErrorFactory.ThrowIfError(Native.CancelFocusing(_handle), "Failed to cancel the camera focus."); } /// /// Starts face detection. /// The camera must be in the state. /// /// 3 /// /// http://tizen.org/privilege/camera /// /// /// This should be called after is started. /// The Eventhandler set using is invoked when the face is detected in the preview frame. /// Internally, it starts continuously focus and focusing on the detected face. /// /// In case of any invalid operations. /// In case of this feature is not supported. /// The camera already has been disposed of. /// In case of access to the resources cannot be granted. public void StartFaceDetection() { ValidateState(CameraState.Preview); _faceDetectedCallback = (IntPtr faces, int count, IntPtr userData) => { var result = new List(); IntPtr current = faces; for (int i = 0; i < count; i++) { result.Add(new FaceDetectionData(current)); current = IntPtr.Add(current, Marshal.SizeOf()); } FaceDetected?.Invoke(this, new FaceDetectedEventArgs(result)); }; CameraErrorFactory.ThrowIfError(Native.StartFaceDetection(_handle, _faceDetectedCallback, IntPtr.Zero), "Failed to start face detection"); } /// /// Stops face detection. /// /// 3 /// /// http://tizen.org/privilege/camera /// /// In case of any invalid operations. /// In case of this feature is not supported. /// The camera already has been disposed of. /// In case of access to the resources cannot be granted. public void StopFaceDetection() { if (_faceDetectedCallback == null) { throw new InvalidOperationException("The face detection is not started."); } CameraErrorFactory.ThrowIfError(Native.StopFaceDetection(_handle), "Failed to stop the face detection."); _faceDetectedCallback = null; } #endregion Methods #region Callback registrations private void RegisterCallbacks() { RegisterErrorCallback(); RegisterFocusStateChanged(); RegisterInterruptStartedCallback(); RegisterInterruptedCallback(); RegisterStateChangedCallback(); //Define capturing callback _capturingCallback = (IntPtr main, IntPtr postview, IntPtr thumbnail, IntPtr userData) => { Capturing?.Invoke(this, new CameraCapturingEventArgs(new StillImage(main), postview == IntPtr.Zero ? null : new StillImage(postview), thumbnail == IntPtr.Zero ? null : new StillImage(thumbnail))); }; //Define captureCompleted callback _captureCompletedCallback = _ => { SetState(CameraState.Captured); CaptureCompleted?.Invoke(this, EventArgs.Empty); }; } private void RegisterInterruptStartedCallback() { _interruptStartedCallback = (CameraPolicy policy, CameraState state, IntPtr userData) => { InterruptStarted?.Invoke(this, new CameraInterruptStartedEventArgs(policy, state)); }; CameraErrorFactory.ThrowIfError(Native.SetInterruptStartedCallback(_handle, _interruptStartedCallback, IntPtr.Zero), "Failed to set interrupt callback"); } private void RegisterInterruptedCallback() { _interruptedCallback = (CameraPolicy policy, CameraState previous, CameraState current, IntPtr userData) => { Interrupted?.Invoke(this, new CameraInterruptedEventArgs(policy, previous, current)); }; CameraErrorFactory.ThrowIfError(Native.SetInterruptedCallback(_handle, _interruptedCallback, IntPtr.Zero), "Failed to set interrupt callback"); } private void RegisterErrorCallback() { _errorCallback = (CameraErrorCode error, CameraState current, IntPtr userData) => { ErrorOccurred?.Invoke(this, new CameraErrorOccurredEventArgs(error, current)); }; CameraErrorFactory.ThrowIfError(Native.SetErrorCallback(_handle, _errorCallback, IntPtr.Zero), "Setting error callback failed"); } private void RegisterStateChangedCallback() { _stateChangedCallback = (CameraState previous, CameraState current, bool byPolicy, IntPtr _) => { SetState(current); Log.Info(CameraLog.Tag, "Camera state changed " + previous.ToString() + " -> " + current.ToString()); StateChanged?.Invoke(this, new CameraStateChangedEventArgs(previous, current, byPolicy)); }; CameraErrorFactory.ThrowIfError(Native.SetStateChangedCallback(_handle, _stateChangedCallback, IntPtr.Zero), "Setting state changed callback failed"); } private static void RegisterDeviceStateChangedCallback() { _deviceStateChangedCallback = (CameraDevice device, CameraDeviceState state, IntPtr userData) => { _deviceStateChanged?.Invoke(null, new CameraDeviceStateChangedEventArgs(device, state)); }; CameraErrorFactory.ThrowIfError(Native.SetDeviceStateChangedCallback(_deviceStateChangedCallback, IntPtr.Zero, out _deviceStateCallbackId), "Failed to set device state changed callback"); Log.Info(CameraLog.Tag, "add callbackId " + _deviceStateCallbackId.ToString()); } private static void UnregisterDeviceStateChangedCallback() { CameraErrorFactory.ThrowIfError(Native.UnsetDeviceStateChangedCallback(_deviceStateCallbackId), "Unsetting device state changed callback failed"); _deviceStateChangedCallback = null; _deviceStateCallbackId = 0; } private void RegisterFocusStateChanged() { _focusStateChangedCallback = (CameraFocusState state, IntPtr userData) => { FocusStateChanged?.Invoke(this, new CameraFocusStateChangedEventArgs(state)); }; CameraErrorFactory.ThrowIfError(Native.SetFocusStateChangedCallback(_handle, _focusStateChangedCallback, IntPtr.Zero), "Setting focus changed callback failed"); } private void RegisterHdrCaptureProgress() { _hdrCaptureProgressCallback = (int percent, IntPtr userData) => { _hdrCaptureProgress?.Invoke(this, new HdrCaptureProgressEventArgs(percent)); }; CameraErrorFactory.ThrowIfError(Native.SetHdrCaptureProgressCallback(_handle, _hdrCaptureProgressCallback, IntPtr.Zero), "Setting Hdr capture progress callback failed"); } private void UnregisterHdrCaptureProgress() { CameraErrorFactory.ThrowIfError(Native.UnsetHdrCaptureProgressCallback(_handle), "Unsetting hdr capture progress is failed"); _hdrCaptureProgressCallback = null; } private void RegisterPreviewCallback() { _previewCallback = (IntPtr frame, IntPtr userData) => { _preview?.Invoke(this, new PreviewEventArgs(new PreviewFrame(frame))); }; CameraErrorFactory.ThrowIfError(Native.SetPreviewCallback(_handle, _previewCallback, IntPtr.Zero), "Setting preview callback failed"); } private void UnregisterPreviewCallback() { CameraErrorFactory.ThrowIfError(Native.UnsetPreviewCallback(_handle), "Unsetting preview callback failed"); _previewCallback = null; } private void RegisterMediaPacketPreviewCallback() { _mediaPacketPreviewCallback = (IntPtr mediaPacket, IntPtr userData) => { MediaPacket packet = MediaPacket.From(mediaPacket); var eventHandler = _mediaPacketPreview; if (eventHandler != null) { eventHandler.Invoke(this, new MediaPacketPreviewEventArgs(packet)); } else { packet.Dispose(); } }; CameraErrorFactory.ThrowIfError(Native.SetMediaPacketPreviewCallback(_handle, _mediaPacketPreviewCallback, IntPtr.Zero), "Setting media packet preview callback failed"); } private void UnregisterMediaPacketPreviewCallback() { CameraErrorFactory.ThrowIfError(Native.UnsetMediaPacketPreviewCallback(_handle), "Unsetting media packet preview callback failed"); _mediaPacketPreviewCallback = null; } #endregion Callback registrations } }