#define ENABLE_LOG /* * Copyright (c) 2014 - 2022 t_saki@serenegiant.com */ using AOT; using System; using System.Collections; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Threading; using UnityEngine; #if UNITY_ANDROID && UNITY_2018_3_OR_NEWER using UnityEngine.Android; #endif namespace Serenegiant.UVC { [RequireComponent(typeof(AndroidUtils))] public class UVCManager : MonoBehaviour { private const string TAG = "UVCManager#"; private const string FQCN_DETECTOR = "com.serenegiant.usb.DeviceDetectorFragment"; private const int FRAME_TYPE_MJPEG = 0x000007; private const int FRAME_TYPE_H264 = 0x000014; private const int FRAME_TYPE_H264_FRAME = 0x030011; /** * 未设置IUVCSElector时 * 或IUVCSElector在选择分辨率时为空 * 返回时的默认分辨率(宽度) */ public Int32 DefaultWidth = 1280; /** * 未设置IUVCSElector时 * 或IUVCSElector在选择分辨率时为空 * 返回时的默认分辨率(高度) */ public Int32 DefaultHeight = 720; /** * *与UVC设备协商时 * H.264是否优先协商 * 仅安卓实机有效 * true: H.264 > MJPEG > YUV * false: MJPEG > H.264 > YUV */ public bool PreferH264 = false; /** * 是否在场景渲染之前请求对UVC设备视频纹理进行渲染 */ public bool RenderBeforeSceneRendering = false; /** * UVC关系的处理程序 */ [SerializeField, ComponentRestriction(typeof(IUVCDrawer))] public Component[] UVCDrawers; /** * 保持正在使用的相机信息的保持架类 */ public class CameraInfo { internal readonly UVCDevice device; internal Texture previewTexture; internal int frameType; internal Int32 activeId; private Int32 currentWidth; private Int32 currentHeight; private bool isRenderBeforeSceneRendering; private bool isRendering; //PC测试用 internal CameraInfo(Texture texture) { this.device = null; activeId = 1; previewTexture = texture; SetSize(texture.width, texture.height); } internal CameraInfo(UVCDevice device) { this.device = device; } /** * 機器idを取得 */ public Int32 Id{ get { return device.id; } } /** * 機器名を取得 */ public string DeviceName { get { return device.name; } } /** * 获取供应商标识 */ public int Vid { get { return device.vid; } } /** * 获取产品标识 */ public int Pid { get { return device.pid; } } /** * 映像取得中かどうか */ public bool IsPreviewing { get { return (activeId != 0) && (previewTexture != null); } } /** * 現在の解像度(幅) * プレビュー中でなければ0 */ public Int32 CurrentWidth { get { return currentWidth; } } /** * 現在の解像度(高さ) * プレビュー中でなければ0 */ public Int32 CurrentHeight { get { return currentHeight; } } /** * 2024/01/19 返回一个size */ public Vector2 Size => new Vector2(currentWidth, currentHeight); /** * 現在の解像度を変更 * @param width * @param height */ internal void SetSize(Int32 width, Int32 height) { currentWidth = width; currentHeight = height; } public Vector2Int IndexToCoord(int i) { var y = i / currentWidth; var x = i % currentWidth; return new Vector2Int(x, y); } public int CoordToIndex(int x, int y) { return y * currentWidth + x; } public override string ToString() { return $"{base.ToString()}({currentWidth}x{currentHeight},id={Id},activeId={activeId},IsPreviewing={IsPreviewing})"; } /** * 开始从UVC设备渲染视频 * @param manager */ internal Coroutine StartRender(UVCManager manager, bool renderBeforeSceneRendering) { StopRender(manager); isRenderBeforeSceneRendering = renderBeforeSceneRendering; isRendering = true; if (renderBeforeSceneRendering) { return manager.StartCoroutine(OnRenderBeforeSceneRendering()); } else { return manager.StartCoroutine(OnRender()); } } /** * 完成UVC设备的视频渲染 * @param manager */ internal void StopRender(UVCManager manager) { if (isRendering) { isRendering = false; if (isRenderBeforeSceneRendering) { manager.StopCoroutine(OnRenderBeforeSceneRendering()); } else { manager.StopCoroutine(OnRender()); } } } /** * 用于渲染事件处理 * 作为代码例程执行 * 在场景渲染之前,请求将UVC设备的视频渲染到纹理 */ private IEnumerator OnRenderBeforeSceneRendering() { var renderEventFunc = GetRenderEventFunc(); for (; activeId != 0;) { yield return null; GL.IssuePluginEvent(renderEventFunc, activeId); } yield break; } /** * 用于渲染事件处理 * 作为代码例程执行 * 渲染后请求将UVC设备的视频渲染到纹理 */ private IEnumerator OnRender() { var renderEventFunc = GetRenderEventFunc(); for (; activeId != 0;) { yield return new WaitForEndOfFrame(); GL.IssuePluginEvent(renderEventFunc, activeId); } yield break; } } /** * 用于在主线程上运行的同步上下文实例 */ private SynchronizationContext mainContext; /** * 当连接到终端的UVC设备的状态发生变化时,接受事件回调的分配器 */ private OnDeviceChangedCallbackManager.OnDeviceChangedFunc callback; /** * 连接到终端的UVC设备列表 */ private List attachedDevices = new List(); /** * 正在获取视频的UVC设备的地图 * 保留设备标识的id-CameraInfo对 */ private Dictionary cameraInfos = new Dictionary(); //-------------------------------------------------------------------------------- // 从UnityEngine调用 //-------------------------------------------------------------------------------- // Start is called before the first frame update IEnumerator Start() { #if (!NDEBUG && DEBUG && ENABLE_LOG) Console.WriteLine($"{TAG}Start:"); #endif mainContext = SynchronizationContext.Current; callback = OnDeviceChangedCallbackManager.Add(this); yield return Initialize(); } #if (!NDEBUG && DEBUG && ENABLE_LOG) void OnApplicationFocus() { Console.WriteLine($"{TAG}OnApplicationFocus:"); } #endif #if (!NDEBUG && DEBUG && ENABLE_LOG) void OnApplicationPause(bool pauseStatus) { Console.WriteLine($"{TAG}OnApplicationPause:{pauseStatus}"); } #endif #if (!NDEBUG && DEBUG && ENABLE_LOG) void OnApplicationQuits() { Console.WriteLine($"{TAG}OnApplicationQuits:"); } #endif void OnDestroy() { #if (!NDEBUG && DEBUG && ENABLE_LOG) Console.WriteLine($"{TAG}OnDestroy:"); #endif StopAll(); OnDeviceChangedCallbackManager.Remove(this); } //-------------------------------------------------------------------------------- // UVC设备连接状态变化时插件的回调函数 //-------------------------------------------------------------------------------- public void OnDeviceChanged(IntPtr devicePtr, bool attached) { var id = UVCDevice.GetId(devicePtr); #if (!NDEBUG && DEBUG && ENABLE_LOG) Console.WriteLine($"{TAG}OnDeviceChangedInternal:id={id},attached={attached}"); #endif if (attached) { UVCDevice device = new UVCDevice(devicePtr); #if (!NDEBUG && DEBUG && ENABLE_LOG) Console.WriteLine($"{TAG}OnDeviceChangedInternal:device={device.ToString()}"); #endif if (HandleOnAttachEvent(device)) { attachedDevices.Add(device); StartPreview(device); } } else { var found = attachedDevices.Find(item => { return item != null && item.id == id; }); if (found != null) { HandleOnDetachEvent(found); StopPreview(found); attachedDevices.Remove(found); } } } //================================================================================ /** * 接続中のUVC機器一覧を取得 * @return 接続中のUVC機器一覧List */ public List GetAttachedDevices() { var result = new List(cameraInfos.Count); foreach (var info in cameraInfos.Values) { result.Add(info); } return result; } // /** // * 対応解像度を取得 // * @param camera 対応解像度を取得するUVC機器を指定 // * @return 対応解像度 既にカメラが取り外されている/closeしているのであればnull // */ // public SupportedFormats GetSupportedVideoSize(CameraInfo camera) // { // var info = (camera != null) ? Get(camera.DeviceName) : null; // if ((info != null) && info.IsOpen) // { // return GetSupportedVideoSize(info.DeviceName); // } // else // { // return null; // } // } // /** // * 解像度を変更 // * @param 解像度を変更するUVC機器を指定 // * @param 変更する解像度を指定, nullならデフォルトに戻す // * @param 解像度が変更されたかどうか // */ // public bool SetVideoSize(CameraInfo camera, SupportedFormats.Size size) // { // var info = (camera != null) ? Get(camera.DeviceName) : null; // var width = size != null ? size.Width : DefaultWidth; // var height = size != null ? size.Height : DefaultHeight; // if ((info != null) && info.IsPreviewing) // { // if ((width != info.CurrentWidth) || (height != info.CurrentHeight)) // { // 解像度が変更になるとき // StopPreview(info.DeviceName); // StartPreview(info.DeviceName, width, height); // return true; // } // } // return false; // } private void StartPreview(UVCDevice device) { var info = CreateIfNotExist(device); if ((info != null) && !info.IsPreviewing) { int width = DefaultWidth; int height = DefaultHeight; // var supportedVideoSize = GetSupportedVideoSize(deviceName); // if (supportedVideoSize == null) // { // throw new ArgumentException("fauled to get supported video size"); // } // // 解像度の選択処理 // if ((UVCDrawers != null) && (UVCDrawers.Length > 0)) // { // foreach (var drawer in UVCDrawers) // { // if ((drawer is IUVCDrawer) && ((drawer as IUVCDrawer).CanDraw(this, info.device))) // { // var size = (drawer as IUVCDrawer).OnUVCSelectSize(this, info.device, supportedVideoSize); //#if (!NDEBUG && DEBUG && ENABLE_LOG) // Console.WriteLine($"{TAG}StartPreview:selected={size}"); //#endif // if (size != null) // { // 一番最初に見つかった描画可能なIUVCDrawersがnull以外を返せばそれを使う // width = size.Width; // height = size.Height; // break; // } // } // } // } // FIXME 対応解像度の確認処理 #if (!NDEBUG && DEBUG && ENABLE_LOG) Console.WriteLine($"{TAG}StartPreview:({width}x{height}),id={device.id}"); #endif int[] frameTypes = { PreferH264 ? FRAME_TYPE_H264 : FRAME_TYPE_MJPEG, PreferH264 ? FRAME_TYPE_MJPEG : FRAME_TYPE_H264, }; foreach (var frameType in frameTypes) { if (Resize(device.id, frameType, width, height) == 0) { info.frameType = frameType; break; } } info.SetSize(width, height); info.activeId = device.id; mainContext.Post(__ => { // 必须在主线程上生成纹理 #if (!NDEBUG && DEBUG && ENABLE_LOG) Console.WriteLine($"{TAG}影像接收用纹理生成:({width}x{height})"); #endif Texture2D tex = new Texture2D( width, height, TextureFormat.ARGB32, false, /* mipmap */ true /* linear */); tex.filterMode = FilterMode.Point; tex.Apply(); info.previewTexture = tex; var nativeTexPtr = info.previewTexture.GetNativeTexturePtr(); Start(device.id, nativeTexPtr.ToInt32()); HandleOnStartPreviewEvent(info); info.StartRender(this, RenderBeforeSceneRendering); }, null); } } private void StopPreview(UVCDevice device) { var info = Get(device); if ((info != null) && info.IsPreviewing) { mainContext.Post(__ => { HandleOnStopPreviewEvent(info); Stop(device.id); info.StopRender(this); info.SetSize(0, 0); info.activeId = 0; }, null); } } private void StopAll() { List values = new List(cameraInfos.Values); foreach (var info in values) { StopPreview(info.device); } } //-------------------------------------------------------------------------------- /** * 连接UVC设备时的处理实体 * @param info * @return true: 使用连接的UVC设备, false: 不使用连接的UVC设备 */ private bool HandleOnAttachEvent(UVCDevice device/*NonNull*/) { if ((UVCDrawers == null) || (UVCDrawers.Length == 0)) { // IUVCDrawerが割り当てられていないときはtrue(接続されたUVC機器を使用する)を返す return true; } else { bool hasDrawer = false; foreach (var drawer in UVCDrawers) { if (drawer is IUVCDrawer) { hasDrawer = true; if ((drawer as IUVCDrawer).OnUVCAttachEvent(this, device)) { // どれか1つのIUVCDrawerがtrueを返せばtrue(接続されたUVC機器を使用する)を返す return true; } } } // IUVCDrawerが割り当てられていないときはtrue(接続されたUVC機器を使用する)を返す return !hasDrawer; } } /** * UVC设备被拆除时的处理实体 * @param info */ private void HandleOnDetachEvent(UVCDevice device/*NonNull*/) { if ((UVCDrawers != null) && (UVCDrawers.Length > 0)) { foreach (var drawer in UVCDrawers) { if (drawer is IUVCDrawer) { (drawer as IUVCDrawer).OnUVCDetachEvent(this, device); } } } } /** * 从UVC机器的影像取得结束 * @param args UVC機器の識別文字列 */ void HandleOnStartPreviewEvent(CameraInfo info) { #if (!NDEBUG && DEBUG && ENABLE_LOG) Console.WriteLine($"{TAG}HandleOnStartPreviewEvent:({info})"); #endif if ((info != null) && info.IsPreviewing && (UVCDrawers != null)) { foreach (var drawer in UVCDrawers) { if ((drawer is IUVCDrawer) && (drawer as IUVCDrawer).CanDraw(this, info.device)) { (drawer as IUVCDrawer).OnUVCStartEvent(this, info.device, info.previewTexture); } } } else { #if (!NDEBUG && DEBUG && ENABLE_LOG) Console.WriteLine($"{TAG}HandleOnStartPreviewEvent:No UVCDrawers"); #endif } } /** * UVC機器からの映像取得を終了した * @param args UVC機器の識別文字列 */ void HandleOnStopPreviewEvent(CameraInfo info) { #if (!NDEBUG && DEBUG && ENABLE_LOG) Console.WriteLine($"{TAG}HandleOnStopPreviewEvent:({info})"); #endif if (UVCDrawers != null) { foreach (var drawer in UVCDrawers) { if ((drawer is IUVCDrawer) && (drawer as IUVCDrawer).CanDraw(this, info.device)) { (drawer as IUVCDrawer).OnUVCStopEvent(this, info.device); } } } } //-------------------------------------------------------------------------------- /** * 获取与指定UVC标识字符串相对应的CameraInfo * 如果尚未注册,则新建 * @param deviceName UVC機器識別文字列 * @param CameraInfoを返す */ /*NonNull*/ private CameraInfo CreateIfNotExist(UVCDevice device) { if (!cameraInfos.ContainsKey(device.id)) { cameraInfos[device.id] = new CameraInfo(device); } return cameraInfos[device.id]; } /** * 获取与指定UVC标识字符串相对应的CameraInfo * @param deviceName UVC机器识别文字列 * @param 注册时返回CameraInfo,未注册时为空 */ /*Nullable*/ private CameraInfo Get(UVCDevice device) { return cameraInfos.ContainsKey(device.id) ? cameraInfos[device.id] : null; } //-------------------------------------------------------------------------------- /** *初始化插件 *如果确认并获取权限,则调用实际的插件初始化处理#InitPlugin */ private IEnumerator Initialize() { #if (!NDEBUG && DEBUG && ENABLE_LOG) Console.WriteLine($"{TAG}Initialize:"); #endif if (AndroidUtils.CheckAndroidVersion(28)) { yield return AndroidUtils.GrantCameraPermission((string permission, AndroidUtils.PermissionGrantResult result) => { #if (!NDEBUG && DEBUG && ENABLE_LOG) Console.WriteLine($"{TAG}OnPermission:{permission}={result}"); #endif switch (result) { case AndroidUtils.PermissionGrantResult.PERMISSION_GRANT: InitPlugin(); break; case AndroidUtils.PermissionGrantResult.PERMISSION_DENY: if (AndroidUtils.ShouldShowRequestPermissionRationale(AndroidUtils.PERMISSION_CAMERA)) { //无法获得权限 //必须显示FIXME说明用的对话框等 } break; case AndroidUtils.PermissionGrantResult.PERMISSION_DENY_AND_NEVER_ASK_AGAIN: break; } }); } else { InitPlugin(); } yield break; } /** * 初始化插件 * 对uvc-plugin-unity的处理请求 */ private void InitPlugin() { #if (!NDEBUG && DEBUG && ENABLE_LOG) Console.WriteLine($"{TAG}InitPlugin:"); #endif // 检查是否分配了IUVCdrawers var hasDrawer = false; if ((UVCDrawers != null) && (UVCDrawers.Length > 0)) { foreach (var drawer in UVCDrawers) { if (drawer is IUVCDrawer) { hasDrawer = true; break; } } } if (!hasDrawer) { //在检查器中未设置IUVCdrawer时 //尝试从带有此脚本的游戏对象获取 #if (!NDEBUG && DEBUG && ENABLE_LOG) Console.WriteLine($"{TAG}InitPlugin:has no IUVCDrawer, try to get from gameObject"); #endif var drawers = GetComponents(typeof(IUVCDrawer)); if ((drawers != null) && (drawers.Length > 0)) { UVCDrawers = new Component[drawers.Length]; int i = 0; foreach (var drawer in drawers) { UVCDrawers[i++] = drawer; } } } #if (!NDEBUG && DEBUG && ENABLE_LOG) Console.WriteLine($"{TAG}InitPlugin:num drawers={UVCDrawers.Length}"); #endif // 请求读取aandusb的DeviceDetector using (AndroidJavaClass clazz = new AndroidJavaClass(FQCN_DETECTOR)) { clazz.CallStatic("initUVCDeviceDetector", AndroidUtils.GetCurrentActivity()); } } //-------------------------------------------------------------------------------- // 定义和声明本机插件关系 //-------------------------------------------------------------------------------- /** * 插件中的渲染事件获取native(c/c++)函数 */ [DllImport("unityuvcplugin")] private static extern IntPtr GetRenderEventFunc(); /** * 初期設定 */ [DllImport("unityuvcplugin", EntryPoint = "Config")] private static extern Int32 Config(Int32 deviceId, Int32 enabled, Int32 useFirstConfig); /** * 映像取得開始 */ [DllImport("unityuvcplugin", EntryPoint ="Start")] private static extern Int32 Start(Int32 deviceId, Int32 tex); /** * 映像取得終了 */ [DllImport("unityuvcplugin", EntryPoint ="Stop")] private static extern Int32 Stop(Int32 deviceId); /** * 影像尺寸设定 */ [DllImport("unityuvcplugin")] private static extern Int32 Resize(Int32 deviceId, Int32 frameType, Int32 width, Int32 height); } // UVCManager /** *如果是IL2Cpp的话,就不能对用于从c/c++回调的引导者进行跟踪 *必须使用static类函数进行处理。 *但如果是这样,则无法调用调用对象的函数,因此创建管理器类 *首先只接受UVCmanager,所以不作为接口 */ public static class OnDeviceChangedCallbackManager { //コールバック関数の型を宣言 [UnmanagedFunctionPointer(CallingConvention.StdCall)] public delegate void OnDeviceChangedFunc(Int32 id, IntPtr devicePtr, bool attached); /** * プラグインのnative側登録関数 */ [DllImport("unityuvcplugin")] private static extern IntPtr Register(Int32 id, OnDeviceChangedFunc callback); /** * プラグインのnative側登録解除関数 */ [DllImport("unityuvcplugin")] private static extern IntPtr Unregister(Int32 id); private static Dictionary sManagers = new Dictionary(); /** * 指定したUVCManagerを接続機器変化コールバックに追加 */ public static OnDeviceChangedFunc Add(UVCManager manager) { Int32 id = manager.GetHashCode(); OnDeviceChangedFunc callback = new OnDeviceChangedFunc(OnDeviceChanged); sManagers.Add(id, manager); Register(id, callback); return callback; } /** * 指定したUVCManagerを接続機器変化コールバックから削除 */ public static void Remove(UVCManager manager) { Int32 id = manager.GetHashCode(); Unregister(id); sManagers.Remove(id); } [MonoPInvokeCallback(typeof(OnDeviceChangedFunc))] public static void OnDeviceChanged(Int32 id, IntPtr devicePtr, bool attached) { var manager = sManagers.ContainsKey(id) ? sManagers[id] : null; if (manager != null) { manager.OnDeviceChanged(devicePtr, attached); } } } // OnDeviceChangedCallbackManager } // namespace Serenegiant.UVC