/*
* 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.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
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
/// http://tizen.org/feature/camera
public partial class Camera : IDisposable, IDisplayable
{
private IntPtr _handle = IntPtr.Zero;
private bool _disposed = false;
private CameraState _state = CameraState.None;
PinnedPreviewBuffer _previewBuffer;
///
/// Initializes a new instance of the class.
///
/// The camera device to access.
/// Invalid CameraDevice type.
/// In case of any invalid operations.
/// The camera feature is not supported.
/// 3
/// http://tizen.org/feature/camera
public Camera(CameraDevice device)
{
ValidationUtil.ValidateEnum(typeof(CameraDevice), device, nameof(device));
CreateCameraDevice(device);
Initialize();
}
///
/// Initializes a new instance of the class.
///
/// CameraDevice and Type will be selected internally by CameraDeviceManager.
/// In case of any invalid operations.
/// The camera feature is not supported.
/// 9
/// http://tizen.org/feature/camera
[EditorBrowsable(EditorBrowsableState.Never)]
public Camera() : this(CameraDevice.Default)
{
}
private void Initialize()
{
Capabilities = new CameraCapabilities(this);
Settings = new CameraSettings(this);
DisplaySettings = new CameraDisplaySettings(this);
RegisterCallbacks();
SetState(CameraState.Created);
}
private void CreateCameraDevice(CameraDevice device)
{
CameraDeviceType cameraDeviceType = CameraDeviceType.BuiltIn;
CameraDevice cameraDevice = device;
if (CameraDeviceManager.IsSupported || device == CameraDevice.Default)
{
var deviceInfo = GetDeviceInformation();
if (!deviceInfo.Any())
{
throw new InvalidOperationException("CDM is supported but, there's no available camera device.");
}
cameraDeviceType = deviceInfo.First().Type;
cameraDevice = deviceInfo.First().Device;
Log.Debug(CameraLog.Tag, $"Type:[{cameraDeviceType}], Device:[{cameraDevice}]");
}
CreateNativeCameraDevice(cameraDeviceType, cameraDevice);
}
private IEnumerable GetDeviceInformation()
{
using (var cameraDeviceManager = new CameraDeviceManager())
{
return cameraDeviceManager.GetDeviceInformation();
}
}
private void CreateNativeCameraDevice(CameraDeviceType type, CameraDevice device)
{
if (type == CameraDeviceType.BuiltIn || type == CameraDeviceType.Usb)
{
Native.Create(device, out _handle).
ThrowIfFailed($"Failed to create {type} camera");
}
else
{
Native.CreateNetworkCamera(device, out _handle).
ThrowIfFailed($"Failed to create {type} camera");
}
}
///
/// Finalizes an instance of the Camera class.
///
~Camera()
{
Dispose(false);
}
///
/// Gets the native handle of the camera.
///
/// 3
/// http://tizen.org/feature/camera
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
_previewBuffer?.Dispose();
}
if (_handle != IntPtr.Zero)
{
Native.Destroy(_handle);
_handle = IntPtr.Zero;
}
_disposed = true;
}
}
///
/// Releases all resources used by the camera.
///
/// 3
/// http://tizen.org/feature/camera
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
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;
}
///
/// Changes the camera device.
///
/// The hardware camera to access.
/// 3
/// http://tizen.org/feature/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 .
///
/// 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));
Native.ChangeDevice(_handle, device).ThrowIfFailed("Failed to change the camera device");
}
///
/// Gets the device state.
///
/// The device to get the state.
/// Returns the state of the camera device.
/// 4
/// http://tizen.org/feature/camera
/// 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));
Native.GetDeviceState(device, out var val).ThrowIfFailed("Failed to get the camera device state.");
return val;
}
///
/// Gets the flash state.
///
/// The device to get the state.
/// Returns the flash state of the camera device.
/// 3
/// http://tizen.org/feature/camera
/// 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));
Native.GetFlashState(device, out var val).ThrowIfFailed("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
/// http://tizen.org/feature/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);
Native.StartPreview(_handle).ThrowIfFailed("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
/// http://tizen.org/feature/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);
Native.StopPreview(_handle).ThrowIfFailed("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
/// http://tizen.org/feature/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);
Native.StartCapture(_handle, _capturingCallback, _captureCompletedCallback).
ThrowIfFailed("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.
///
/// The number of still images.
/// The interval of the capture(milliseconds).
/// The cancellation token to cancel capturing.
///
/// 3
/// http://tizen.org/privilege/camera
/// http://tizen.org/feature/camera
///
/// 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(() =>
{
Native.StopContinuousCapture(_handle).ThrowIfFailed("Failed to cancel the continuous capture");
SetState(CameraState.Captured);
});
}
Native.StartContinuousCapture(_handle, count, interval, _capturingCallback, _captureCompletedCallback).
ThrowIfFailed("Failed to start the continuous capture.");
SetState(CameraState.Capturing);
}
///
/// Starts camera auto-focusing, asynchronously.
/// The camera must be in the or the state.
///
/// Continuous auto focus.
/// 3
/// http://tizen.org/privilege/camera
/// http://tizen.org/feature/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);
Native.StartFocusing(_handle, continuous).ThrowIfFailed("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
/// http://tizen.org/feature/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);
Native.CancelFocusing(_handle).ThrowIfFailed("Failed to cancel the camera focus.");
}
///
/// Starts face detection.
/// The camera must be in the state.
///
/// 3
/// http://tizen.org/privilege/camera
/// http://tizen.org/feature/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));
};
Native.StartFaceDetection(_handle, _faceDetectedCallback).
ThrowIfFailed("Failed to start face detection");
}
///
/// Stops face detection.
///
/// 3
/// http://tizen.org/privilege/camera
/// http://tizen.org/feature/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()
{
ValidateNotDisposed();
if (_faceDetectedCallback == null)
{
throw new InvalidOperationException("The face detection is not started.");
}
Native.StopFaceDetection(_handle).ThrowIfFailed("Failed to stop the face detection.");
_faceDetectedCallback = null;
}
}
}