/****************************************************************************
* Copyright 2019 Nreal Techonology Limited. All rights reserved.
*
* This file is part of NRSDK.
*
* https://www.nreal.ai/
*
*****************************************************************************/
namespace NRKernal.Record
{
using UnityEngine;
using NRKernal;
using System;
using System.Collections.Generic;
///
/// Records a video from the MR images directly to disk. MR images comes from rgb camera or rgb
/// camera image and virtual image blending. The final video recording will be stored on the file
/// system in the MP4 format.
public class NRVideoCapture : IDisposable
{
/// Default constructor.
public NRVideoCapture()
{
IsRecording = false;
}
/// Finalizer.
~NRVideoCapture()
{
}
/// A list of all the supported device resolutions for recording videos.
/// The supported resolutions.
public static IEnumerable SupportedResolutions
{
get
{
NativeResolution rgbResolution = new NativeResolution(1280, 720);
if (NRDevice.Subsystem.IsFeatureSupported(NRSupportedFeature.NR_FEATURE_RGB_CAMERA))
rgbResolution = NRFrame.GetDeviceResolution(NativeDevice.RGB_CAMERA);
Resolution stand_resolution = new Resolution()
{
width = rgbResolution.width,
height = rgbResolution.height,
refreshRate = NativeConstants.RECORD_FPS_DEFAULT,
};
yield return stand_resolution;
Resolution low_resolution = new Resolution()
{
width = stand_resolution.width / 2,
height = stand_resolution.height / 2,
refreshRate = NativeConstants.RECORD_FPS_DEFAULT,
};
yield return low_resolution;
Resolution high_resolution = new Resolution()
{
width = stand_resolution.width * 3 / 2,
height = stand_resolution.height * 3 / 2,
refreshRate = NativeConstants.RECORD_FPS_DEFAULT,
};
yield return high_resolution;
}
}
///
/// Indicates whether or not the VideoCapture instance is currently recording video.
/// True if this object is recording, false if not.
public bool IsRecording { get; private set; }
/// Context for the capture.
private FrameCaptureContext m_CaptureContext;
/// Gets the context.
/// The context.
public FrameCaptureContext GetContext()
{
return m_CaptureContext;
}
/// Gets the preview texture.
/// The preview texture.
public Texture PreviewTexture
{
get
{
return m_CaptureContext?.PreviewTexture;
}
}
/// Creates an asynchronous.
/// True to show, false to hide the holograms.
/// The on created callback.
public static void CreateAsync(bool showHolograms, OnVideoCaptureResourceCreatedCallback onCreatedCallback)
{
NRVideoCapture capture = new NRVideoCapture();
capture.m_CaptureContext = FrameCaptureContextFactory.Create();
onCreatedCallback?.Invoke(capture);
}
///
/// Returns the supported frame rates at which a video can be recorded given a resolution.
/// A recording resolution.
/// The frame rates at which the video can be recorded.
public static IEnumerable GetSupportedFrameRatesForResolution(Resolution resolution)
{
yield return NativeConstants.RECORD_FPS_DEFAULT;
}
/// Dispose must be called to shutdown the PhotoCapture instance.
public void Dispose()
{
if (m_CaptureContext != null)
{
m_CaptureContext.Release();
m_CaptureContext = null;
}
}
/// Starts recording asynchronous.
/// Filename of the file.
/// The on started recording video callback.
public void StartRecordingAsync(string filename, OnStartedRecordingVideoCallback onStartedRecordingVideoCallback)
{
float volumeFactorMic = 1.0f;
float volumeFactorApp = 1.0f;
if (NRDevice.Subsystem.GetDeviceType() == NRDeviceType.NrealLight)
{
volumeFactorMic = NativeConstants.RECORD_VOLUME_MIC;
volumeFactorApp = NativeConstants.RECORD_VOLUME_APP;
}
StartRecordingAsync(filename, onStartedRecordingVideoCallback, volumeFactorMic, volumeFactorApp);
}
/// Starts recording asynchronous.
/// Filename of the file.
/// The on started recording video callback.
/// The volume factor of mic.
/// The volume factor of application.
public void StartRecordingAsync(string filename, OnStartedRecordingVideoCallback onStartedRecordingVideoCallback, float volumeFactorMic, float volumeFactorApp)
{
NRDebugger.Info("[VideoCapture] StartRecordingAsync: IsRecording={0}, volFactorMic={1}, volFactorApp={2}", IsRecording, volumeFactorMic, volumeFactorApp);
var result = new VideoCaptureResult();
if (IsRecording)
{
result.resultType = CaptureResultType.UnknownError;
onStartedRecordingVideoCallback?.Invoke(result);
}
else
{
try
{
VideoEncoder encoder = m_CaptureContext.GetEncoder() as VideoEncoder;
if (encoder != null)
{
encoder.AdjustVolume(RecorderIndex.REC_MIC, volumeFactorMic);
encoder.AdjustVolume(RecorderIndex.REC_APP, volumeFactorApp);
}
var behaviour = m_CaptureContext.GetBehaviour();
((NRRecordBehaviour)behaviour).SetOutPutPath(filename);
m_CaptureContext.StartCapture();
IsRecording = true;
result.resultType = CaptureResultType.Success;
onStartedRecordingVideoCallback?.Invoke(result);
}
catch (Exception ex)
{
NRDebugger.Warning("[VideoCapture] StartRecordingAsync: {0}\n{1}", ex.Message, ex.StackTrace);
result.resultType = CaptureResultType.UnknownError;
onStartedRecordingVideoCallback?.Invoke(result);
throw;
}
}
}
/// Starts video mode asynchronous.
/// Options for controlling the setup.
/// The on video mode started callback.
/// Auto adaption for BlendMode based on supported feature on current device.
public void StartVideoModeAsync(CameraParameters setupParams, OnVideoModeStartedCallback onVideoModeStartedCallback, bool autoAdaptBlendMode = false)
{
if (autoAdaptBlendMode)
{
var blendMode = m_CaptureContext.AutoAdaptBlendMode(setupParams.blendMode);
if (blendMode != setupParams.blendMode)
{
NRDebugger.Warning("[VideoCapture] AutoAdaptBlendMode : {0} => {1}", setupParams.blendMode, blendMode);
setupParams.blendMode = blendMode;
}
}
if (Application.isEditor || Application.platform != RuntimePlatform.Android)
{
StartVideoMode(setupParams, onVideoModeStartedCallback);
return;
}
NRDebugger.Info("[VideoCapture] StartVideoModeAsync: audioState={0}, blendMode={1}", setupParams.audioState, setupParams.blendMode);
bool recordMic = setupParams.CaptureAudioMic;
bool recordApp = setupParams.CaptureAudioApplication;
if (recordApp)
{
NRAndroidPermissionsManager.GetInstance().RequestAndroidPermission("android.permission.RECORD_AUDIO").ThenAction((requestResult) =>
{
if (requestResult.IsAllGranted)
{
NRAndroidPermissionsManager.GetInstance().RequestScreenCapture().ThenAction((AndroidJavaObject mediaProjection) =>
{
if (mediaProjection != null)
{
setupParams.mediaProjection = mediaProjection;
StartVideoMode(setupParams, onVideoModeStartedCallback);
}
else
{
NRDebugger.Error("[VideoCapture] Screen capture is denied by user.");
var result = new VideoCaptureResult();
result.resultType = CaptureResultType.UnknownError;
onVideoModeStartedCallback?.Invoke(result);
NRSessionManager.Instance.OprateInitException(new NRPermissionDenyError(NativeConstants.ScreenCaptureDenyErrorTip));
}
});
}
else {
NRDebugger.Error("[VideoCapture] Record audio need the permission of 'android.permission.RECORD_AUDIO'.");
var result = new VideoCaptureResult();
result.resultType = CaptureResultType.UnknownError;
onVideoModeStartedCallback?.Invoke(result);
NRSessionManager.Instance.OprateInitException(new NRPermissionDenyError(NativeConstants.AudioPermissionDenyErrorTip));
}
});
}
else if (recordMic)
{
NRAndroidPermissionsManager.GetInstance().RequestAndroidPermission("android.permission.RECORD_AUDIO").ThenAction((requestResult) =>
{
if (requestResult.IsAllGranted)
{
StartVideoMode(setupParams, onVideoModeStartedCallback);
}
else {
NRDebugger.Error("[VideoCapture] Record audio need the permission of 'android.permission.RECORD_AUDIO'.");
var result = new VideoCaptureResult();
result.resultType = CaptureResultType.UnknownError;
onVideoModeStartedCallback?.Invoke(result);
NRSessionManager.Instance.OprateInitException(new NRPermissionDenyError(NativeConstants.AudioPermissionDenyErrorTip));
}
});
}
else
{
StartVideoMode(setupParams, onVideoModeStartedCallback);
}
}
private void StartVideoMode(CameraParameters setupParams, OnVideoModeStartedCallback onVideoModeStartedCallback, bool autoAdaptBlendMode = false)
{
setupParams.camMode = CamMode.VideoMode;
setupParams.hologramOpacity = 1;
if (autoAdaptBlendMode)
{
var blendMode = m_CaptureContext.AutoAdaptBlendMode(setupParams.blendMode);
if (blendMode != setupParams.blendMode)
{
NRDebugger.Warning("[VideoCapture] AutoAdaptBlendMode : {0} => {1}", setupParams.blendMode, blendMode);
setupParams.blendMode = blendMode;
}
}
if (setupParams.frameRate <= 0)
NRDebugger.Warning("[PhotoCapture] frameRate need to be bigger than zero");
m_CaptureContext.StartCaptureMode(setupParams);
var result = new VideoCaptureResult();
result.resultType = CaptureResultType.Success;
onVideoModeStartedCallback?.Invoke(result);
}
public void StartVideoModeAsync(CameraParameters setupParams, AudioState audioState, OnVideoModeStartedCallback onVideoModeStartedCallback, bool autoAdaptBlendMode = false)
{
setupParams.audioState = audioState;
StartVideoModeAsync(setupParams, onVideoModeStartedCallback, autoAdaptBlendMode);
}
/// Stops recording asynchronous.
/// The on stopped recording video callback.
public void StopRecordingAsync(OnStoppedRecordingVideoCallback onStoppedRecordingVideoCallback)
{
var result = new VideoCaptureResult();
if (!IsRecording)
{
result.resultType = CaptureResultType.UnknownError;
onStoppedRecordingVideoCallback?.Invoke(result);
}
else
{
try
{
m_CaptureContext.StopCapture();
IsRecording = false;
result.resultType = CaptureResultType.Success;
onStoppedRecordingVideoCallback?.Invoke(result);
}
catch (Exception)
{
result.resultType = CaptureResultType.UnknownError;
onStoppedRecordingVideoCallback?.Invoke(result);
throw;
}
}
}
/// Stops video mode asynchronous.
/// The on video mode stopped callback.
public void StopVideoModeAsync(OnVideoModeStoppedCallback onVideoModeStoppedCallback)
{
m_CaptureContext.StopCaptureMode();
var result = new VideoCaptureResult();
result.resultType = CaptureResultType.Success;
onVideoModeStoppedCallback?.Invoke(result);
}
/// Contains the result of the capture request.
public enum CaptureResultType
{
///
/// Specifies that the desired operation was successful.
///
Success = 0,
///
/// Specifies that an unknown error occurred.
///
UnknownError = 1
}
///
/// Specifies what audio sources should be recorded while recording the video.
///
public enum AudioState
{
///
/// Only include the mic audio in the video recording.
///
MicAudio = 0,
///
/// Only include the application audio in the video recording.
///
ApplicationAudio = 1,
/////
///// Include both the application audio as well as the mic audio in the video recording.
/////
ApplicationAndMicAudio = 2,
///
/// Do not include any audio in the video recording.
///
None = 3
}
///
/// A data container that contains the result information of a video recording operation.
public struct VideoCaptureResult
{
///
/// A generic result that indicates whether or not the VideoCapture operation succeeded.
public CaptureResultType resultType;
/// The specific HResult value.
public long hResult;
/// Indicates whether or not the operation was successful.
/// True if success, false if not.
public bool success
{
get
{
return resultType == CaptureResultType.Success;
}
}
}
/// Called when the web camera begins recording the video.
/// Indicates whether or not video recording started successfully.
public delegate void OnStartedRecordingVideoCallback(VideoCaptureResult result);
/// Called when a VideoCapture resource has been created.
/// The VideoCapture instance.
public delegate void OnVideoCaptureResourceCreatedCallback(NRVideoCapture captureObject);
/// Called when video mode has been started.
/// Indicates whether or not video mode was successfully activated.
public delegate void OnVideoModeStartedCallback(VideoCaptureResult result);
/// Called when video mode has been stopped.
/// Indicates whether or not video mode was successfully deactivated.
public delegate void OnVideoModeStoppedCallback(VideoCaptureResult result);
/// Called when the video recording has been saved to the file system.
/// Indicates whether or not video recording was saved successfully to the
/// file system.
public delegate void OnStoppedRecordingVideoCallback(VideoCaptureResult result);
}
}