【Unity】全てのカメラを完全制御するアプリを作ってみる

Android
https://amzn.to/3bd8XOX

 これまで、Unity のAndroidアプリにおけるカメラ制御を脱初心者とばかりにとことん調査してきました。今回はその集大成としてAndroidの実機についている全てのカメラを解像度を変更して正しく表示させるアプリを作ります。
 一度これを作っておけば、どの機種でもすぐにカメラスペックを確認できますので、がんばって作っていきます。

Goal

カメラデバイス、解像度を選択して正しくプレビュー表示させるアプリを作る!




はじめに

 こちらの記事で進めてきたAndroidライブラリとUnityアプリをベースに進めていきます。

 このサンプルに対して以下を実装していきます。

  • カメラデバイス一覧をDropDownリストから選択する
  • カメラデバイスに応じた解像度リストをDropDownに表示する
  • 一部を全画面で表示するのか、全体を表示するのかの選択
  • カメラの表示切替ボタン

UIの配置決め

 UIは以下のように配置します。表示エリアは正方形でサイズ固定、このエリア内で映像を表示させます。

UI実装

 各パーツの処理を実装していきます。カメラのインスタンスは表示のObjectにアタッチして、ここに情報を集約させておきます。

初期化処理

 初期化ではカメラの情報取得とデフォルト設定でのカメラ動作開始を行います。Androidライブラリの初期化はAwake()で、カメラの表示はStart()で行います。

using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.UI;
using System.Drawing;
using System.Xml;

public class CameraPrev : MonoBehaviour
{
    public RawImage RawImage;
    public WebCamTexture webCam;

    private AndroidJavaObject _javaClass = null;
    private List<Vector2> _prevSizeList = new List<Vector2>();
    private List<List<Vector2>> _deviceList = new List<List<Vector2>>();
    private List<string> _devidList = new List<string>();
    private int[] _Orientation;
    private int _nowCamIDIndex;
    private int _nowCamSizeIndex;
    private Vector2 defaulRawImageSize;
    private WebCamDevice[] _devices;

    void Awake()
    {
        // Nativeライブラリの初期化
        _javaClass = new AndroidJavaObject("com.example.getcam.GetCamParameter");

        // デバイスIDの取得
        string[] idstmp = _javaClass.Call<string[]>("GetCamIDList");
        foreach (string idtmp in idstmp) {
            _devidList.Add(idtmp);
        }

        // プレビューサイズの取得
        string sizelist;
        sizelist = _javaClass.Call<string>("GetPreviewSize");

        // XMLをリストに変換
        XmlDocument xmlDoc = new XmlDocument();
        xmlDoc.Load(new StringReader(sizelist));
        XmlNode root = xmlDoc.FirstChild;
        XmlNodeList talkList = xmlDoc.GetElementsByTagName("device");
        Vector2 tmpsize;
        foreach (XmlNode devtmp in talkList) {
            _prevSizeList = new List<Vector2>();
            _prevSizeList.Clear();
            XmlNodeList nodelist = devtmp.ChildNodes;
            foreach (XmlNode s in nodelist) {
                tmpsize.x = float.Parse(s.Attributes["Width"].Value);
                tmpsize.y = float.Parse(s.Attributes["Height"].Value);
                _prevSizeList.Add(tmpsize);
            }
            _deviceList.Add(_prevSizeList);
        }

        // カメラの取り付け向き取得
        _Orientation = _javaClass.Call<int[]>("GetOrientation");

    }
    // Start is called before the first frame update
    void Start()
    {
        // UnityのWebCamTextureでカメラリスト取得
        _devices = WebCamTexture.devices;
        // デフォルト表示エリア保存
        defaulRawImageSize = RawImage.GetComponent<RectTransform>().sizeDelta;
        // カメラ0を設定
        _nowCamIDIndex = 0;
        // プレビューサイズインデックス0設定
        _nowCamSizeIndex = 0;
        // カメラ起動
        StartCamera();
    }

    public List<Vector2> GetCameraSize()
    {
        return _deviceList[_nowCamIDIndex];
    }

    public void SetCameraSize(int index)
    {
        _nowCamSizeIndex = index;
    }

    public List<string> GetDeviceIDList()
    {
        return _devidList;
    }

    public void SetDeviceID(int devindex)
    {
        _nowCamIDIndex = devindex;
    }

    public void RsetDevice()
    {
        //表示エリアを初期化
        RawImage.GetComponent<RectTransform>().eulerAngles = Vector3.zero;
        RawImage.GetComponent<RectTransform>().localScale = new Vector3(1f,1f,1f);
        RawImage.GetComponent<RectTransform>().sizeDelta = defaulRawImageSize;

        // 起動中のカメラ停止
        webCam.Stop();
        webCam = null;

        // カメラ起動
        StartCamera();
    }

    public void StartCamera()
    {
        // 指定カメラを起動させる
        webCam = new WebCamTexture(_devices[_nowCamIDIndex].name);
        // RawImageのテクスチャにWebCamTextureのインスタンスを設定
        RawImage.texture = webCam;
        // 縦横のサイズを要求(_deviceListからorientationを考慮して決定する。ここでは直値)
        webCam.requestedWidth = (int)_deviceList[_nowCamIDIndex][_nowCamSizeIndex].x;
        webCam.requestedHeight = (int)_deviceList[_nowCamIDIndex][_nowCamSizeIndex].y;
        // カメラ起動
        webCam.Play();
        // 起動させて初めてvideoRotationAngle、width、heightに値が入り、
        // アスペクト比、何度回転させれば正しく表示されるかがわかる
        if ((webCam.videoRotationAngle == 90) || (webCam.videoRotationAngle == 270)) {
            // 表示するRawImageを回転させる
            Vector3 angles = RawImage.GetComponent<RectTransform>().eulerAngles;
            angles.z = -webCam.videoRotationAngle;
            RawImage.GetComponent<RectTransform>().eulerAngles = angles;
        }
        // 回転済みなので、widthはx、heightはyでそのままサイズ調整
        // 全体を表示させるように、大きい方のサイズを表示枠に合わせる
        float scaler;
        Vector2 sizetmp = RawImage.GetComponent<RectTransform>().sizeDelta;
        if (webCam.width > webCam.height) {
            scaler = sizetmp.x / webCam.width;
        } else {
            scaler = sizetmp.y / webCam.height;
        }
        // サイズ調整
        sizetmp.x = webCam.width * scaler;
        sizetmp.y = webCam.height * scaler;
        if (_devices[_nowCamIDIndex].isFrontFacing && !webCam.videoVerticallyMirrored) {
            //インカメラで反転していない場合は反転させる
            Vector3 scaletmp = RawImage.GetComponent<RectTransform>().localScale;
            if ((webCam.videoRotationAngle == 90) || (webCam.videoRotationAngle == 270)) {
                scaletmp.y = -1;
            } else {
                scaletmp.x = -1;
            }
            RawImage.GetComponent<RectTransform>().localScale = scaletmp;
        }
        // 表示領域サイズ設定
        RawImage.GetComponent<RectTransform>().sizeDelta = sizetmp;
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

 初期化処理だけで、デフォルトの画が出ることを確認しておきます。

デバイスリストの設定

 デバイスリストを取得します。Start()の段階でデバイスIDの取得は完了しているので、前述のスクリプトから取得します。プレビューサイズはデバイスに紐づいているので、デバイスが変更されたタイミングでプレビューサイズのリストを更新します。同時にデバイスの変更をカメラ表示するスクリプトに通知します。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class DeviceDropDown : MonoBehaviour
{
    public GameObject CameraPrev;
    public GameObject CameraSizeDropDown;
    private List<string> _deviceidlist;
    private List<Vector2> _devicesizelist;
    private Dropdown _devicedd;

    // Start is called before the first frame update
    void Start()
    {
        // デバイスIDリストの取得
        _deviceidlist = CameraPrev.GetComponent<CameraPrev>().GetDeviceIDList();
        // DropDownコンポーネント取得
        _devicedd = GetComponent<Dropdown>();
        // Optionクリア
        _devicedd.ClearOptions();
        // デバイスリストを設定
        _devicedd.AddOptions(_deviceidlist);
        // プレビューサイズリストの取得
        _devicesizelist = CameraPrev.GetComponent<CameraPrev>().GetCameraSize();
        // サイズリスト更新
        CameraSizeDropDown.GetComponent<SizeDropDown>().SetDeviceSizeList(_devicesizelist);
    }

    public void OnSelected()
    {
        // デバイスID変更を通知
        CameraPrev.GetComponent<CameraPrev>().SetDeviceID(_devicedd.value);
        // プレビューサイズリストの取得
        _devicesizelist = CameraPrev.GetComponent<CameraPrev>().GetCameraSize();
        // サイズリスト更新
        CameraSizeDropDown.GetComponent<SizeDropDown>().SetDeviceSizeList(_devicesizelist);
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

プレビューサイズの設定

 デバイスリスト同様にプレビューサイズをDropDownに設定します。プレビューサイズはデバイスリストの初期化及び変更時に更新されるます。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class SizeDropDown : MonoBehaviour
{
    public GameObject CameraPrev;
    private List<Vector2> _devicesizelist;

    // Start is called before the first frame update
    void Start()
    {

    }

    public void SetDeviceSizeList(List<Vector2> sizelist)
    {
        // sizeリストを文字列変換
        List<string> sizeliststr = setSizeList(sizelist);
        // Optionクリア
        GetComponent<Dropdown>().ClearOptions();
        // デバイスリストを設定
        GetComponent<Dropdown>().AddOptions(sizeliststr);
        // 初期化
        GetComponent<Dropdown>().value = 0;
        // デバイスサイズ変更を通知
        CameraPrev.GetComponent<CameraPrev>().SetCameraSize(0);
    }

    private List<string> setSizeList(List<Vector2> sizelist)
    {
        List<string> tmp = new List<string>();
        foreach(Vector2 size in sizelist) {
            tmp.Add(size.x.ToString() + " X " + size.y.ToString());
        }
        return tmp;
    }
    public void OnSelected()
    {
        // デバイスサイズ変更を通知
        CameraPrev.GetComponent<CameraPrev>().SetCameraSize(GetComponent<Dropdown>().value);
    }

    // Update is called once per frame
    void Update()
    {

    }
}

カメラの切り替え処理

 カメラの切り替えはChangeボタンで行います。Changeが押されたタイミングで設定済みのパラメータでカメラの再起動を行います。カメラの再起動はCameraPrevのRsetDeviceで行います。OnClickでRsetDevice()をコールするだけなので、コードは省略します。

動かしてみる

 Xperia1で動かしてみましたが、なぜかカメラが5個ありました。全ての解像度で適切に表示されることが確認できました。

 インカメラは反転せずに画が来ていたので、反転する判定を入れてあります。こちらも反転して、いい感じで表示されました。

まとめ

 かなり苦労しましたが、UnityのWebCamTextureとAndroidのカメラの関係を深く理解することができました。アプリをもう少し改良して、横画面や基準の向きについても情報出力すると、さらに理解が深まりそうです。
 Unityカメラについては本記事+以下の3記事の四部作で基礎は理解できると思います。ご参考になれば幸いです。

https://amzn.to/3bd8XOX

コメント