using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.Events; using UnityEngine.Scripting; namespace TensorFlowLite { /// /// An wrapper for WebCamTexture that corrects texture rotation /// [System.Obsolete("Use TextureSource.VirtualTextureSource instead")] public sealed class WebCamInput : MonoBehaviour { [System.Serializable] public class TextureUpdateEvent : UnityEvent { } [SerializeField, WebCamName] private string editorCameraName; [SerializeField] private WebCamKind preferKind = WebCamKind.WideAngle; [SerializeField] private bool isFrontFacing = false; [SerializeField] private Vector2Int requestSize = new Vector2Int(1280, 720); [SerializeField] private int requestFps = 60; public TextureUpdateEvent OnTextureUpdate = new TextureUpdateEvent(); private TextureResizer resizer; private WebCamTexture webCamTexture; private WebCamDevice[] devices; private int deviceIndex; public Vector2Int RequestSize { get => requestSize; set => requestSize = value; } public int RequestFps { get => requestFps; set => requestFps = value; } public string RequestCameraByDeviceName { get => editorCameraName; set => editorCameraName = value; } public string PreferKind { get => preferKind.ToString(); set => System.Enum.TryParse(value, out preferKind); } public bool IsFrontFacing { get => isFrontFacing; set => isFrontFacing = value; } private static List deviceIndexesOpened; private void OnEnable() { resizer = new TextureResizer(); devices = WebCamTexture.devices; string cameraName = Application.isEditor || !string.IsNullOrEmpty(editorCameraName) ? editorCameraName : WebCamUtil.FindName(preferKind, isFrontFacing); WebCamDevice device = default; for (int i = 0; i < devices.Length; i++) { if (devices[i].name == cameraName) { device = devices[i]; deviceIndex = i; break; } } if (deviceIndexesOpened == null) deviceIndexesOpened = new List(); // trying to open a busy camera if (deviceIndexesOpened.Contains(deviceIndex)) { // select next available camera var ordered = deviceIndexesOpened.OrderByDescending(x => x); deviceIndex = ordered.First(); deviceIndex++; if (deviceIndex >= devices.Length) { deviceIndex = 0; while (deviceIndexesOpened.Contains(deviceIndex) && deviceIndex < devices.Length - 1) { deviceIndex++; } } device = devices[deviceIndex]; } deviceIndexesOpened.Add(deviceIndex); editorCameraName = devices[deviceIndex].name; StartCamera(device); } private void OnDisable() { deviceIndexesOpened.Remove(deviceIndex); PauseCamera(); } private void OnDestroy() { deviceIndexesOpened.Remove(deviceIndex); StopCamera(); resizer?.Dispose(); } private void Update() { if (!webCamTexture.didUpdateThisFrame) return; var tex = NormalizeWebcam(webCamTexture, requestSize.x, requestSize.y, isFrontFacing); OnTextureUpdate.Invoke(tex); } // Invoked by Unity Event [Preserve] public void ToggleCamera() { deviceIndex = (deviceIndex + 1) % devices.Length; StartCamera(devices[deviceIndex]); } private void StartCamera(WebCamDevice device) { StopCamera(); isFrontFacing = device.isFrontFacing; webCamTexture = new WebCamTexture(device.name, requestSize.x, requestSize.y, requestFps); try { webCamTexture.Play(); } catch (System.Exception e) { Debug.Log(e.Message); webCamTexture = null; } } private void PauseCamera() { if (webCamTexture == null) { return; } webCamTexture.Stop(); } private void StopCamera() { if (webCamTexture == null) { return; } webCamTexture.Stop(); Destroy(webCamTexture); } private RenderTexture NormalizeWebcam(WebCamTexture texture, int width, int height, bool isFrontFacing) { int cameraWidth = texture.width; int cameraHeight = texture.height; bool isPortrait = IsPortrait(texture); if (isPortrait) { (cameraWidth, cameraHeight) = (cameraHeight, cameraWidth); // swap } float cameraAspect = (float)cameraWidth / cameraHeight; float targetAspect = (float)width / height; int w, h; if (cameraAspect > targetAspect) { w = RoundToEven(cameraHeight * targetAspect); h = cameraHeight; } else { w = cameraWidth; h = RoundToEven(cameraWidth / targetAspect); } Matrix4x4 mtx; Vector4 uvRect; int rotation = texture.videoRotationAngle; // Seems to be bug in the android. might be fixed in the future. if (Application.platform == RuntimePlatform.Android) { rotation = -rotation; } if (isPortrait) { mtx = TextureResizer.GetVertTransform(rotation, texture.videoVerticallyMirrored, isFrontFacing); uvRect = TextureResizer.GetTextureST(targetAspect, cameraAspect, AspectMode.Fill); } else { mtx = TextureResizer.GetVertTransform(rotation, isFrontFacing, texture.videoVerticallyMirrored); uvRect = TextureResizer.GetTextureST(cameraAspect, targetAspect, AspectMode.Fill); } // Debug.Log($"camera: rotation:{texture.videoRotationAngle} flip:{texture.videoVerticallyMirrored}"); return resizer.Resize(texture, w, h, false, mtx, uvRect); } private static bool IsPortrait(WebCamTexture texture) { return texture.videoRotationAngle == 90 || texture.videoRotationAngle == 270; } private static int RoundToEven(float n) { return Mathf.RoundToInt(n / 2) * 2; } } }