/*
* 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 System.Collections;
namespace Tizen.Multimedia
{
static internal class CameraLog
{
internal const string Tag = "Tizen.Multimedia.Camera";
internal const string Enter = "[Enter]";
internal const string Leave = "[Leave]";
}
///
/// The camera class provides methods to capture photos and support setting up notifications
/// for state changes of capturing, previewing, focusing, information about resolution and binary format
/// and functions for picture manipulations like sepia negative and many more.
/// It also notifies you when a significant picture parameter changes e.g. focus.
///
///
/// http://tizen.org/privilege/camera
///
public class Camera : IDisposable
{
private IntPtr _handle = IntPtr.Zero;
private bool _disposed = false;
private CameraState _state = CameraState.None;
private static Dictionary _callbackIdInfo = new Dictionary();
///
/// Initializes a new instance of the Class.
///
/// The camera device to access
///
/// http://tizen.org/privilege/camera
///
public Camera(CameraDevice device)
{
CameraErrorFactory.ThrowIfError(Interop.Camera.Create((int)device, out _handle),
"Failed to create camera instance");
Feature = new CameraFeatures(this);
Setting = new CameraSettings(this);
Display = new CameraDisplay(this);
RegisterCallbacks();
SetState(CameraState.Created);
}
///
/// Destructor of the camera class.
///
~Camera()
{
Dispose(false);
}
internal IntPtr GetHandle()
{
ValidateNotDisposed();
return _handle;
}
#region Dispose support
///
/// Release any unmanaged resources used by this object.
///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
// to be used if there are any other disposable objects
}
if (_handle != IntPtr.Zero)
{
Interop.Camera.Destroy(_handle);
_handle = IntPtr.Zero;
}
_disposed = true;
}
}
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
///
/// Event that occurs when an camera is interrupted by policy.
///
public event EventHandler Interrupted;
private Interop.Camera.InterruptedCallback _interruptedCallback;
///
/// Event that occurs when there is an asynchronous error.
///
public event EventHandler ErrorOccurred;
private Interop.Camera.ErrorCallback _errorCallback;
///
/// Event that occurs when the auto focus state is changed.
///
public event EventHandler FocusStateChanged;
private Interop.Camera.FocusStateChangedCallback _focusStateChangedCallback;
///
/// Event that occurs when a face is detected in preview frame.
///
public event EventHandler FaceDetected;
private Interop.Camera.FaceDetectedCallback _faceDetectedCallback;
///
/// Event that occurs during capture of image.
///
public event EventHandler Capturing;
private Interop.Camera.CapturingCallback _capturingCallback;
///
/// Event that occurs after the capture of the image.
///
public event EventHandler CaptureCompleted;
private Interop.Camera.CaptureCompletedCallback _captureCompletedCallback;
///
/// Event that occurs when there is change in HDR capture progress.
/// Check whether HdrCapture feature is supported or not before add this EventHandler.
///
public event EventHandler HdrCaptureProgress;
private Interop.Camera.HdrCaptureProgressCallback _hdrCaptureProgressCallback;
///
/// Event that occurs when camera state is changed.
///
public event EventHandler StateChanged;
private Interop.Camera.StateChangedCallback _stateChangedCallback;
#region DeviceStateChanged callback
internal static Interop.Camera.DeviceStateChangedCallback _deviceStateChangedCallback;
public static event EventHandler _deviceStateChanged;
public static object _deviceStateChangedEventLock = new object();
///
/// Set the DeviceStateChanged Callback.
/// User doesn't need to create camera instance.
/// This static EventHandler calls platform function every time because each callback function have to remain its own callbackId.
///
/// Callback of type .
/// The Id of registered callback.
/// 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)
{
int callbackId = 0;
_deviceStateChangedCallback = (CameraDevice device, CameraDeviceState state, IntPtr userData) =>
{
_deviceStateChanged?.Invoke(null, new CameraDeviceStateChangedEventArgs(device, state));
};
CameraErrorFactory.ThrowIfError(Interop.Camera.SetDeviceStateChangedCallback(_deviceStateChangedCallback, IntPtr.Zero, out callbackId),
"Failed to set interrupt callback");
// Keep current callbackId and EventHandler pair to remove EventHandler later.
_callbackIdInfo.Add(value, callbackId);
Log.Info(CameraLog.Tag, "add callbackId " + callbackId.ToString());
_deviceStateChanged += value;
}
}
remove
{
lock (_deviceStateChangedEventLock)
{
_deviceStateChanged -= value;
int callbackId = 0;
_callbackIdInfo.TryGetValue(value, out callbackId);
Log.Info(CameraLog.Tag, "remove callbackId " + callbackId.ToString());
CameraErrorFactory.ThrowIfError(Interop.Camera.UnsetDeviceStateChangedCallback(callbackId),
"Unsetting media packet preview callback failed");
_callbackIdInfo.Remove(value);
if (_deviceStateChanged == null)
{
_deviceStateChangedCallback = null;
}
}
}
}
#endregion DeviceStateChanged callback
#region Preview EventHandler
private Interop.Camera.PreviewCallback _previewCallback;
private event EventHandler _preview;
private object _previewEventLock = new object();
///
/// Event that occurs once per frame when previewing.
/// Preview callback is registered when user add callback explicitly to avoid useless P/Invoke.
///
public event EventHandler Preview
{
add
{
lock (_previewEventLock)
{
if (_preview == null)
{
RegisterPreviewCallback();
}
_preview += value;
}
}
remove
{
lock (_previewEventLock)
{
_preview -= value;
if (_preview == null)
{
CameraErrorFactory.ThrowIfError(Interop.Camera.UnsetPreviewCallback(_handle),
"Unsetting preview callback failed");
_previewCallback = null;
}
}
}
}
#endregion Preview EventHandler
#region MediaPacketPreview EventHandler
private Interop.Camera.MediaPacketPreviewCallback _mediaPacketPreviewCallback;
private EventHandler _mediaPacketPreview;
private object _mediaPacketPreviewEventLock = new object();
///
/// Event that occurs once per frame when previewing.
/// Preview callback is registered when user add callback explicitly to avoid useless P/Invoke.
///
public event EventHandler MediaPacketPreview
{
add
{
lock (_mediaPacketPreviewEventLock)
{
if (_mediaPacketPreview == null)
{
RegisterMediaPacketPreviewCallback();
}
_mediaPacketPreview += value;
}
}
remove
{
lock (_mediaPacketPreviewEventLock)
{
_mediaPacketPreview -= value;
if (_mediaPacketPreview == null)
{
CameraErrorFactory.ThrowIfError(Interop.Camera.UnsetMediaPacketPreviewCallback(_handle),
"Unsetting media packet preview callback failed");
_mediaPacketPreviewCallback = null;
}
}
}
}
#endregion MediaPacketPreview EventHandler
#endregion EventHandlers
#region Properties
///
/// Get/Set the various camera settings.
///
public CameraSettings Setting { get; }
///
/// Gets the various camera features.
///
public CameraFeatures Feature { get; }
///
/// Get/set various camera display properties.
///
public CameraDisplay Display { get; }
///
/// Gets the state of the camera.
///
/// None, Created, Preview, Capturing, Captured
/// The camera already has been disposed.
public CameraState State
{
get
{
ValidateNotDisposed();
CameraState val = CameraState.None;
CameraErrorFactory.ThrowIfError(Interop.Camera.GetState(_handle, out val),
"Failed to get camera state");
return val;
}
}
///
/// The hint for display reuse.
/// If the hint is set to true, the display will be reused when the camera device is changed with
/// ChangeDevice method.
///
/// In case of invalid parameters.
/// Invalid state.
/// The camera already has been disposed.
public bool DisplayReuseHint
{
get
{
ValidateNotDisposed();
bool val = false;
CameraErrorFactory.ThrowIfError(Interop.Camera.GetDisplayReuseHint(_handle, out val),
"Failed to get camera display reuse hint");
return val;
}
set
{
ValidateState(CameraState.Preview);
CameraErrorFactory.ThrowIfError(Interop.Camera.SetDisplayReuseHint(_handle, value),
"Failed to set display reuse hint.");
}
}
///
/// Gets the facing direction of camera module.
///
/// A that specifies the facing direction of camera device.
/// The camera already has been disposed.
public CameraFacingDirection Direction
{
get
{
ValidateNotDisposed();
CameraFacingDirection val = 0;
CameraErrorFactory.ThrowIfError(Interop.Camera.GetFacingDirection(_handle, out val),
"Failed to get camera direction");
return val;
}
}
///
/// Gets the camera device count.
///
/// 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.
public int CameraCount
{
get
{
ValidateNotDisposed();
int val = 0;
CameraErrorFactory.ThrowIfError(Interop.Camera.GetDeviceCount(_handle, out val),
"Failed to get camera device count");
return val;
}
}
#endregion Properties
#region Methods
///
/// Changes the camera device.
///
/// The hardware camera to access.
///
/// http://tizen.org/privilege/camera
///
///
/// 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 or state.
///
/// In case of invalid parameters.
/// In case of any invalid operations.
/// In case of ChangeDevice feature is not supported.
/// The camera already has been disposed.
public void ChangeDevice(CameraDevice device)
{
ValidateState(CameraState.Created, CameraState.Preview);
CameraErrorFactory.ThrowIfError(Interop.Camera.ChangeDevice(_handle, (int)device),
"Failed to change the camera device");
}
///
/// Gets the device state.
///
///
/// http://tizen.org/privilege/camera
///
/// The device to get state.
/// Returns the state of camera device
/// In case of invalid parameters.
/// In case of any invalid operations.
/// In case of this feature is not supported.
public CameraDeviceState GetDeviceState(CameraDevice device)
{
int val = 0;
CameraErrorFactory.ThrowIfError(Interop.Camera.GetDeviceState(device, out val),
"Failed to get the camera device state.");
return (CameraDeviceState)val;
}
///
/// Gets the flash state.
///
///
/// http://tizen.org/privilege/camera
///
/// The device to get state.
/// Returns the flash state of 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)
{
CameraFlashState val = CameraFlashState.NotUsed;
CameraErrorFactory.ThrowIfError(Interop.Camera.GetFlashState(device, out val),
"Failed to get camera flash state");
return val;
}
///
/// Starts capturing and drawing preview frames on the screen.
/// The display handle 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 state.
///
///
/// 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.
/// In case of access to the resources cannot be granted.
public void StartPreview()
{
ValidateState(CameraState.Created, CameraState.Captured);
CameraErrorFactory.ThrowIfError(Interop.Camera.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.
///
///
/// 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.
/// In case of access to the resources cannot be granted.
public void StopPreview()
{
ValidateState(CameraState.Preview);
CameraErrorFactory.ThrowIfError(Interop.Camera.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.
///
///
/// 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 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.
/// In case of access to the resources cannot be granted.
public void StartCapture()
{
ValidateState(CameraState.Preview);
CameraErrorFactory.ThrowIfError(Interop.Camera.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.
///
///
/// 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 event.
/// The preview should be restarted by calling 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.
/// 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(Interop.Camera.StopContinuousCapture(_handle),
"Failed to cancel the continuous capture");
SetState(CameraState.Captured);
});
}
CameraErrorFactory.ThrowIfError(Interop.Camera.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 state.
///
/// 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.
/// In case of access to the resources cannot be granted.
public void StartFocusing(bool continuous)
{
ValidateState(CameraState.Preview, CameraState.Captured);
CameraErrorFactory.ThrowIfError(Interop.Camera.StartFocusing(_handle, continuous),
"Failed to cancel the camera focus.");
}
///
/// Stops camera auto focusing.
/// The camera must be in the or state.
///
///
/// 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.
/// In case of access to the resources cannot be granted.
public void StopFocusing()
{
ValidateState(CameraState.Preview, CameraState.Captured);
CameraErrorFactory.ThrowIfError(Interop.Camera.CancelFocusing(_handle),
"Failed to cancel the camera focus.");
}
///
/// Starts face detection.
/// The camera must be in the state.
///
///
/// http://tizen.org/privilege/camera
///
///
/// This should be called after is started.
/// The Eventhandler set using invoked when the face is detected in preview frame.
/// Internally it starts continuous 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.
/// 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(Interop.Camera.StartFaceDetection(_handle, _faceDetectedCallback, IntPtr.Zero),
"Failed to start face detection");
}
///
/// Stops face detection.
///
///
/// 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.
/// 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(Interop.Camera.StopFaceDetection(_handle),
"Failed to stop the face detection.");
_faceDetectedCallback = null;
}
#endregion Methods
#region Callback registrations
private void RegisterCallbacks()
{
RegisterErrorCallback();
RegisterFocusStateChanged();
RegisterHdrCaptureProgress();
RegisterInterruptedCallback();
RegisterStateChangedCallback();
//Define capturing callback
_capturingCallback = (IntPtr image, IntPtr postview, IntPtr thumbnail, IntPtr userData) =>
{
Capturing?.Invoke(this, new CameraCapturingEventArgs(new ImageData(image),
postview == IntPtr.Zero ? null : new ImageData(postview),
thumbnail == IntPtr.Zero ? null : new ImageData(thumbnail)));
};
//Define captureCompleted callback
_captureCompletedCallback = _ =>
{
SetState(CameraState.Captured);
CaptureCompleted?.Invoke(this, EventArgs.Empty);
};
}
private void RegisterInterruptedCallback()
{
_interruptedCallback = (CameraPolicy policy, CameraState previous, CameraState current, IntPtr userData) =>
{
Interrupted?.Invoke(this, new CameraInterruptedEventArgs(policy, previous, current));
};
CameraErrorFactory.ThrowIfError(Interop.Camera.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(Interop.Camera.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(Interop.Camera.SetStateChangedCallback(_handle, _stateChangedCallback, IntPtr.Zero),
"Setting state changed callback failed");
}
private void RegisterFocusStateChanged()
{
_focusStateChangedCallback = (CameraFocusState state, IntPtr userData) =>
{
FocusStateChanged?.Invoke(this, new CameraFocusStateChangedEventArgs(state));
};
CameraErrorFactory.ThrowIfError(Interop.Camera.SetFocusStateChangedCallback(_handle, _focusStateChangedCallback, IntPtr.Zero),
"Setting focus changed callback failed");
}
private void RegisterHdrCaptureProgress()
{
//Hdr Capture can not be supported.
if (Feature.IsHdrCaptureSupported)
{
_hdrCaptureProgressCallback = (int percent, IntPtr userData) =>
{
HdrCaptureProgress?.Invoke(this, new HdrCaptureProgressEventArgs(percent));
};
CameraErrorFactory.ThrowIfError(Interop.Camera.SetHdrCaptureProgressCallback(_handle, _hdrCaptureProgressCallback, IntPtr.Zero),
"Setting Hdr capture progress callback failed");
}
}
private void RegisterPreviewCallback()
{
_previewCallback = (IntPtr frame, IntPtr userData) =>
{
_preview?.Invoke(this, new PreviewEventArgs(new PreviewData(frame)));
};
CameraErrorFactory.ThrowIfError(Interop.Camera.SetPreviewCallback(_handle, _previewCallback, IntPtr.Zero),
"Setting preview callback failed");
}
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(Interop.Camera.SetMediaPacketPreviewCallback(_handle, _mediaPacketPreviewCallback, IntPtr.Zero),
"Setting media packet preview callback failed");
}
#endregion Callback registrations
}
}