/**************************************************************************** * 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); } }