初始代码
编辑器模式或Android环境和实机测试都没有问题。
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
public class WebCamTest : MonoBehaviour
{
/// <summary>
/// 图片组件
/// </summary>
public RawImage rawImage;
/// <summary>
/// 当前相机索引
/// </summary>
private int index = 0;
/// <summary>
/// 当前运行的相机
/// </summary>
private WebCamTexture currentWebCam;
void Start()
{
StartCoroutine(Call());
}
/// <summary>
/// 开启摄像机
/// </summary>
public IEnumerator Call()
{
yield return Application.RequestUserAuthorization(UserAuthorization.WebCam);//请求权限
if (Application.HasUserAuthorization(UserAuthorization.WebCam) && WebCamTexture.devices.Length > 0)//权限成功,且存在摄像机
{
//创建相机贴图
currentWebCam = new WebCamTexture(WebCamTexture.devices[index].name, Screen.width, Screen.height, 60);
rawImage.texture = currentWebCam;
currentWebCam.Play();//运行下一个
//前置后置摄像头需要旋转一定角度,否则画面是不正确的,必须置于Play()函数后
rawImage.rectTransform.localEulerAngles = new Vector3(0, 0, -currentWebCam.videoRotationAngle);
}
}
/// <summary>
/// 切换前后摄像头
/// </summary>
public void SwitchCamera()
{
if (WebCamTexture.devices.Length < 1)//只有一个摄像机
return;
if (currentWebCam != null)
currentWebCam.Stop();//暂停之前的
//索引下一个
index++;
index = index % WebCamTexture.devices.Length;
//创建相机贴图
currentWebCam = new WebCamTexture(WebCamTexture.devices[index].name, Screen.width, Screen.height, 60);
rawImage.texture = currentWebCam;
currentWebCam.Play();
//前置后置摄像头需要旋转一定角度,否则画面是不正确的,必须置于Play()函数后
rawImage.rectTransform.localEulerAngles = new Vector3(0, 0, -currentWebCam.videoRotationAngle);
}
}
后续功能&代码更新
2022.10.21更新:先上代码,可直接复制使用,对照代码再对其中几个比较重要的点进行记录解释。
using System;
using System.Collections;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Serialization;
using UnityEngine.UI;
using static my_code.ShowWebCam;
namespace my_code
{
[RequireComponent(typeof(RawImage))]
public class WebCam : MonoBehaviour
{
public ShowWebCam WebCamObject { get; set; }
[FormerlySerializedAs("onChange")]
[SerializeField]
private ChangeWebCamEvent m_onChange = new ChangeWebCamEvent();
protected void Awake()
{
WebCamObject = new ShowWebCam(GetComponent<RawImage>())
{
OnChange = m_onChange
};
}
//private IEnumerator Start()
//{
// yield return m_webCam.RequestWebCam();
//}
private void OnDestroy()
{
if (null != WebCamObject)
WebCamObject.Shutdown();
}
}
/// <summary> RawImage播放网络摄像机 </summary>
public class ShowWebCam
{
public ShowWebCam(RawImage image)
{
Show = image;
RequestedFPS = 60;
{//正方形效果更好
RequestedWidth = 720;//1280;
RequestedHeight = 720;
}
OnChange = new ChangeWebCamEvent();
m_showRTF = Show.transform as RectTransform;
m_root = (RectTransform)m_showRTF.GetComponentInParent<Canvas>().transform;
}
private WebCamTexture[] m_webCamTextures;
// 画布对象,画布的根对象
private readonly RectTransform m_showRTF = null, m_root = null;
/// <summary> 画布尺寸 </summary>
private Vector2 m_showSizeDelta = new Vector2() { x = 0, y = 0 };
/// <summary> 相机映射显示区域 </summary>
public RawImage Show { get; set; }
/// <summary> 当前相机索引 </summary>
public virtual int CurrentIndex { get; protected set; }
/// <summary> 当前的相机映射纹理 </summary>
public WebCamTexture CurrentWebCam { get; protected set; }
/// <summary> Web端是否有相机权限 </summary>
public bool HaveAuthorization { get; protected set; }
private float m_requestedFPS;
public float RequestedFPS
{
get => m_requestedFPS;
set
{
m_requestedFPS = value;
for (int i = 0; null != m_webCamTextures && i < WebCamTexture.devices.Length; i++)
{
if (null != m_webCamTextures[i])
{
m_webCamTextures[i].requestedFPS = value;
}
}
}
}
private int m_requestedWidth;
public int RequestedWidth
{
get => m_requestedWidth;
set
{
m_requestedWidth = value;
for (int i = 0; null != m_webCamTextures && i < WebCamTexture.devices.Length; i++)
{
if (null != m_webCamTextures[i])
{
m_webCamTextures[i].requestedWidth = value;
}
}
}
}
public int m_requestedHeight;
public int RequestedHeight
{
get => m_requestedHeight;
set
{
m_requestedHeight = value;
for (int i = 0; null != m_webCamTextures && i < WebCamTexture.devices.Length; i++)
{
if (null != m_webCamTextures[i])
{
m_webCamTextures[i].requestedHeight = value;
}
}
}
}
[Serializable]
public class ChangeWebCamEvent : UnityEvent<WebCamTexture> { }
public ChangeWebCamEvent OnChange { get; set; }
/// <summary> 请求权限创建对象,并屏幕适配 </summary>
/// <returns> 协程 </returns>
public IEnumerator RequestWebCam()
{
Vector3 eul = new Vector3(0, 0, 0);
switch (Application.platform)
{//平台(移动端)屏幕适配
case RuntimePlatform.Android://安卓
if (0 < WebCamTexture.devices.Length)
{//是否存在摄像机硬件
m_webCamTextures = new WebCamTexture[WebCamTexture.devices.Length];
}
goto SetCanvas;//安卓自动请求权限
case RuntimePlatform.IPhonePlayer://IPhone
yield return Application.RequestUserAuthorization(UserAuthorization.WebCam);//iOS需要手动请求权限
eul.y = 180;//竖直翻转180度旋转
SetCanvas:
{
eul.z = -90;//向右90度旋转
m_showSizeDelta.x = m_root.sizeDelta.y;//计算画布的大小
}
break;
case RuntimePlatform.WebGLPlayer://Web端权限确认
if (!(HaveAuthorization = Application.HasUserAuthorization(UserAuthorization.WebCam)))
{
Debug.LogError("Failed to get webcam permissions in the Web Player!");
}
break;
case RuntimePlatform.OSXEditor://Mac编辑器
eul.y = 180;//竖直翻转180度旋转
m_showSizeDelta.y = m_root.sizeDelta.y;
break;
case RuntimePlatform.WindowsEditor://Win编辑器
default:
Show.color = Color.black;
m_showSizeDelta = m_root.sizeDelta;//设置画布的大小
//Debug.Log(rectTransform.sizeDelta + "+" + root.localScale + "+" + root.sizeDelta);
break;
}
m_showRTF.localEulerAngles = eul;
m_showRTF.sizeDelta = m_showSizeDelta;//设置画布的大小
//OpenWebCam();//0:手机后置摄像机
}
/// <summary> 打开摄像头 </summary>
/// <param name="index"> 摄像头的下标索引。0:手机后置摄像机 </param>
/// <returns> 当前运行的相机 </returns>
public WebCamTexture OpenWebCam(int index = 0)
{
if (0 < WebCamTexture.devices.Length)
{//是否存在摄像机硬件
if (null == m_webCamTextures)
{//权限成功后
m_webCamTextures = new WebCamTexture[WebCamTexture.devices.Length];
}
CurrentIndex = index %= WebCamTexture.devices.Length;//计数循环
//Debug.Log("Actual:" + index);
if (null == m_webCamTextures[index])
{//创建实例化一个摄像机显示区域
m_webCamTextures[index] = new WebCamTexture(WebCamTexture.devices[index].name)//创建相机贴图
{
requestedWidth = RequestedWidth,
requestedHeight = RequestedHeight,
requestedFPS = RequestedFPS
};
}
Show.texture = CurrentWebCam = m_webCamTextures[index];//显示的图片信息
switch (Application.platform)
{//平台(移动端)屏幕适配
case RuntimePlatform.Android | RuntimePlatform.IPhonePlayer:
m_showSizeDelta.y = m_root.sizeDelta.y * ((float)CurrentWebCam.width / (float)CurrentWebCam.height);//计算画布的大小
break;
case RuntimePlatform.OSXEditor://Mac编辑器
m_showSizeDelta.x = m_root.sizeDelta.y * ((float)RequestedWidth / (float)RequestedHeight);//计算画布的大小
break;
default: break;
}
m_showRTF.sizeDelta = m_showSizeDelta;//设置画布的大小
OnChange.Invoke(CurrentWebCam);
CurrentWebCam.Play();//打开摄像机运行识别
return CurrentWebCam;
}
else
{
Debug.Log("WebCam permissions were obtained successfully, but no webcam was detected!");
return null;
}
}
/// <summary> 切换前后摄像头 </summary>
/// <returns> 当前运行的相机 </returns>
public WebCamTexture SwitchWebCam()
{
CloseWebCam();
return OpenWebCam(CurrentIndex + 1);//索引下一个
}
/// <summary> 关闭摄像机 </summary>
public void CloseWebCam()
{
if (CurrentWebCam != null)
{//暂停之前的
Show.texture = null;
CurrentWebCam.Stop();
}
}
/// <summary> 清理内存 </summary>
public void Shutdown()
{
UnityEngine.Object.Destroy(CurrentWebCam);
if (null != m_webCamTextures) Array.Clear(m_webCamTextures, 0, m_webCamTextures.Length);
}
}
}
注意事项
- 脚本中有两个类,
ShowWebCam
类与WebCam
类:ShowWebCam
类所有的逻辑都在其中,创建时需要传入RawImage
组件,用于画面的播放;WebCam
是Mono类,其实就是将ShowWebCam
类在外面包了一层,可以直接拖拽到物体上当组件使用; WebCam
类的IEnumerator Start()
方法被我注释掉了,是因为项目中我是要手动调用里面的m_webCam.RequestWebCam();
,且经过我亲自测试m_webCam.RequestWebCam();
不能在Awake()
调用,会导致协程中yield return
之后的代码无法运行;- 协程
RequestWebCam()
需要提前调用,来获取权限。其中比较坑的是各个平台的权限获取方式是不一样的,我之前没有认真看API以为所有平台都是一样的,这个问题就卡了很久,协程RequestWebCam()
中的是经过本人安卓、苹果、Mac实机测试后的结果,如有遗漏欢迎指出:(权限获取在第一次安装程序时才会弹窗)- Android:
WebCamTexture.devices.Length
,第一次调用弹窗同意之后,需要在调用一次才可使用,不然会出现黑屏情况,所以我在OpenWebCam
函数中的开始又调用了一次; - IPhonePlayer:
Application.RequestUserAuthorization(UserAuthorization.WebCam);
- Android:
- 获取画面之后的,画布屏幕适配也是一个小坑。这边我不知道是什么逻辑,不管
WebCamTexture.requestedWidth
和WebCamTexture.requestedHeight
我如何设置,最后我获取到的画面永远都是一个宽(WebCamTexture.width
)高(WebCamTexture.height
)等长的正方形画面,所以我在构造函数中专门注释了:正方形效果更好。
在此逻辑前提下,我做了画面的屏幕适配(就是画面正常无拉伸),因为我的项目基本都是移动端竖屏全屏显示,故我只需要将RawImage
画布,以屏幕的高设置为长宽等高的正方形即可(当然我项目中写的比较复杂,只是为了记录方法,可自行更改忽略)。 - 安卓和IOS的画面还需要各自做个旋转逻辑才能正常,在代码中挺简单的,就不详细说明了。