Unity拍照及录屏开发探索

一、拍照

可以使用Unity的RenderTexture和Texture2D类来实现,需要一个摄像机来拍摄图片,然后,通过创建一个RenderTexture,将摄像机的渲染结果保存到Texture中。最后将这个Texture转换为一个常规的2D纹理,将其保存为图像文件。

public class PhotoShootHandler : MonoBehaviour
{
    [Tooltip("Camera used to render the scene. If not set, the main camera will be  used.")]
    public Camera foregroundCamera;
    [Tooltip("Array of sprite transforms that will be used for displaying the  countdown until image shot.")]
    public Transform[] countdown;
    [Tooltip("UI-Text used to display information messages.")]
    public UnityEngine.UI.Text infoText;
    private IEnumerator currentRoutine = null,currentPhotoRoutine = null;
    /// <summary>
    /// 检查是否在拍照
    /// </summary>
    public bool IsInProgress()
    {
        return (currentRoutine != null);
    }
    /// <summary>
    /// 开始拍照
    /// </summary>
    public void CountdownAndMakePhoto()
    {
        if (currentRoutine != null)
            return;
        currentRoutine = CoCountdownAndMakePhoto();
        StartCoroutine(currentRoutine);
    }
    private IEnumerator CoCountdownAndMakePhoto()
    {
        if (countdown != null && countdown.Length > 0)
        {
            for (int i = 0; i < countdown.Length; i++)
            {
                if (countdown[i])
                    countdown[i].gameObject.SetActive(true);
                yield return new WaitForSeconds(1f);
                if (countdown[i])
                    countdown[i].gameObject.SetActive(false);
            }
        }
        MakePhoto();
        yield return null;
        currentRoutine = null;
    }
    public void MakePhoto()
    {
        MakePhoto(true);
    }
    public string MakePhoto(bool openIt)
    {
        int resWidth = Screen.width;
        int resHeight = Screen.height;
        Texture2D screenShot = new Texture2D(resWidth, resHeight, TextureFormat.RGB24,  false);
        RenderTexture rt = new RenderTexture(resWidth, resHeight, 24);
        if (infoText)
        {
            infoText.text = string.Empty;
        }
        if (!foregroundCamera)
        {
            foregroundCamera = Camera.main;
        }
        // 渲染
        if (foregroundCamera && foregroundCamera.enabled)
        {
            foregroundCamera.targetTexture = rt;
            foregroundCamera.Render();
            foregroundCamera.targetTexture = null;
        }
        //获取图片,RenderTexture.active当前处于活动状态的渲染纹理
        RenderTexture prevActiveTex = RenderTexture.active;
        RenderTexture.active = rt;
        screenShot.ReadPixels(new Rect(0, 0, resWidth, resHeight), 0, 0);
        //还原
        RenderTexture.active = prevActiveTex;
        Destroy(rt);
        //byte[] btScreenShot = screenShot.EncodeToJPG();
        byte[] btScreenShot = screenShot.EncodeToPNG();
        Destroy(screenShot);
        // 保存
        string sDirName = Application.persistentDataPath + "/Screenshots";
        if (!Directory.Exists(sDirName))
            Directory.CreateDirectory(sDirName);
        string fileName = string.Format("{0:F0}", Time.realtimeSinceStartup * 10f) +  ".png";
        string sFileName = sDirName + "/" + fileName;
        File.WriteAllBytes(sFileName, btScreenShot);
        Debug.Log("Photo saved to: " + sFileName);
        if (infoText)
        {
            infoText.text = "Saved to: " + sFileName;
        }
        // 打开i文件进行展示
        /*if (openIt)
        {
            System.Diagnostics.Process.Start(sFileName);
        }*/
        //上传照片
        UploadRecordPhoto(fileName);
        return sFileName;
    }
    /// <summary>
    /// 上传录屏图片
    /// </summary>
    /// <returns></returns>
    private void UploadRecordPhoto(string fileName)
    {
        if (currentPhotoRoutine != null)
            return;
        currentPhotoRoutine = DoUploadRecordVideo(fileName);
        StartCoroutine(currentPhotoRoutine);
    }
    private IEnumerator DoUploadRecordVideo(string fileName)
    {
        yield return new WaitForSeconds(1.0f);
        string filePath = Application.persistentDataPath + "/Screenshots" + "/" +  fileName;
        if (!IsOccupied(filePath, "上传照片"))
        {
            print("压入照片文件");
            HttpUploadHandler.Instance.PushImageInQueue(fileName);
            currentPhotoRoutine = null;
        }
        else
        {
            currentPhotoRoutine = null;
            UploadRecordPhoto(fileName);
        }
    }
    #region 工具
    /// <summary>
    /// 判断文件是否被占用
    /// </summary>
    /// <param name="filePath"></param>
    /// <returns></returns>
    public bool IsOccupied(string filePath, string fileFrom)
    {
        FileStream stream = null;
        try
        {
            stream = new FileStream(filePath, FileMode.Open, FileAccess.Read,  FileShare.None);
            return false;
        }
        catch
        {
            print(fileFrom + "文件被占用");
            return true;
        }
        finally
        {
            if (stream != null)
            {
                stream.Close();
            }
        }
    }
    #endregion
}

二、录制当前界面视频(适用于PC及移动端)

1、概述:

Unity录制视频可以 利用Unity Recorder来录制,但是仅能用于编辑器中,可以在运行模式下将Unity场景及动画、 Timeline录制成动画或视频。
而对于非编辑器条件下,目前探索使用ffmpeg来实现,使用正常。

2、 ffmpeg参数说明

     * -f :格式
     *     gdigrab :ffmpeg内置的用于抓取Windows桌面的方法,支持抓取指定名称的窗口
     *     dshow :依赖于第三方软件Screen Capture Recorder(后面简称SCR)
     * -i :输入源
     *     title :要录制的窗口的名称,仅用于GDIGRAB方式
     *     video :视频播放硬件名称或者"screen-capture-recorder",后者依赖SCR
     *     audio :音频播放硬件名称或者"virtual-audio-capturer",后者依赖SCR
     * -preset ultrafast :以最快的速度进行编码,生成的视频文件大
     * -c:v :视频编码方式
     * -c:a :音频编码方式
     * -b:v :视频比特率
     * -r :视频帧率
     * -s :视频分辨率
     * -y :输出文件覆盖已有文件不提示
     *
     * FFMPEG官方文档: ffmpeg Documentation
     * Screen Capture Recorder主页: https://github.com/rdp/screen-capture-recorder-to-video-windows-free

3、实现代码

using Newtonsoft.Json.Serialization;
using System;
using System.Collections;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using UnityEngine;

[ExecuteInEditMode]
public class VideoRecoderHandler : MonoBehaviour
{
    #region 模拟控制台信号需要使用的DLL
    [DllImport("kernel32.dll")]
    static extern bool GenerateConsoleCtrlEvent(int dwCtrlEvent, int dwProcessGroupId);
    [DllImport("kernel32.dll")]
    static extern bool SetConsoleCtrlHandler(IntPtr handlerRoutine, bool add);
    [DllImport("kernel32.dll")]
    static extern bool AttachConsole(int dwProcessId);
    [DllImport("kernel32.dll")]
    static extern bool FreeConsole();
    #endregion
    #region 设置菜单
    public enum RecordType
    {
        GDIGRAB,
        DSHOW
    }
    public enum Bitrate
    {
        _1000k,
        _1500k,
        _2000k,
        _2500k,
        _3000k,
        _3500k,
        _4000k,
        _5000k,
        _8000k
    }
    public enum Framerate
    {
        _14,
        _24,
        _30,
        _45,
        _60
    }
    public enum Resolution
    {
        _1280x720,
        _1920x1080,
        _1080x1920,
        _Auto
    }
    public enum OutputPath
    {
        Desktop,
        StreamingAsset,
        DataPath,
        PersistentDataPath,
        Custom
    }
    #endregion
    #region 成员
    [Tooltip("启用Debug则显示CMD窗口,否则不显示。")]
    [SerializeField]
    private bool _debug = false;
    [Tooltip("DSHOW - 录制全屏 \nGUIGRAB - 录制游戏窗口(仅用于发布版)")]
    public RecordType recordType = RecordType.DSHOW;
    public Resolution resolution = Resolution._1280x720;
    public Framerate framerate = Framerate._24;
    public Bitrate bitrate = Bitrate._1500k;
    public OutputPath outputPath = OutputPath.Desktop;
    public string customOutputPath = @"D:/Records";
    public bool IsRecording { get { return _isRecording; } }
    // 参数:窗口名称 -b比特率 -r帧率 -s分辨率 文件路径 文件名
    private const string FFARGS_GDIGRAB = "-f gdigrab -i title={0} -f dshow -i audio=\"virtual-audio-capturer\" -y -preset ultrafast -rtbufsize 3500k -pix_fmt yuv420p -b:v {1} -r {2} -s {3} {4}/{5}.mp4";
    //private const string FFARGS_GDIGRAB = "-f gdigrab -i title={0} -f dshow -i audio=\"virtual-audio-capturer\" -f dshow -i video=\"screen-capture-recorder\" -y -preset ultrafast -rtbufsize 3500k -pix_fmt yuv420p -b:v {1} -r {2} -s {3} {4}/{5}.mp4";
    // 参数:-b比特率 -r帧率 -s分辨率 文件路径 文件名
    private const string FFARGS_DSHOW = "-f dshow -i video=\"screen-capture-recorder\" -f dshow -i audio=\"virtual-audio-capturer\" -y -preset ultrafast -rtbufsize 3500k -b:v {0} -r {1} -s {2} {3}/{4}.mp4";
    private string _ffpath;
    private string _ffargs;
    private int _pid;
    private bool _isRecording = false;
    private string curVideoName;
    private IEnumerator currentVideoRoutine = null;


    #endregion
#if !UNITY_EDITOR && !DEVELOPMENT_BUILD
    private void Start()
    {
        _debug = false;
    }
#endif
#if UNITY_EDITOR || DEVELOPMENT_BUILD
    private void OnGUI()
    {
        /*if (GUILayout.Button("Start")) StartRecording();
        if (GUILayout.Button("Stop")) StopRecording(() => { UnityEngine.Debug.Log("结束录制。"); });*/
    }
#endif
#if UNITY_EDITOR
    private void OnValidate()
    {
        if (_debug) UnityEngine.Debug.Log("FFRecorder - CMD窗口已启用。");
        else UnityEngine.Debug.Log("FFRecorder - CMD窗口已禁用。");
        if (recordType == RecordType.GDIGRAB)
        {
            UnityEngine.Debug.Log("FFRecorder - 使用【GDIGRAB】模式录制当前窗口。");
            UnityEngine.Debug.LogError("FFRecorder - 【GDIGRAB】模式在编辑器中不可用。");
        }
        else if (recordType == RecordType.DSHOW)
        {
            UnityEngine.Debug.Log("FFRecorder - 使用【DSHOW】模式录制全屏。");
        }
    }
#endif
    public void StartRecording()
    {
        if (_isRecording)
        {
            UnityEngine.Debug.LogError("FFRecorder::StartRecording - 当前已有录制进程。");
            return;
        }
        // 杀死已有的ffmpeg进程,不要加.exe后缀
        Process[] goDie = Process.GetProcessesByName("ffmpeg");
        foreach (Process p in goDie) p.Kill();
        // 解析设置,如果设置正确,则开始录制
        bool validSettings = ParseSettings();
        if (validSettings)
        {
            UnityEngine.Debug.Log("FFRecorder::StartRecording - 执行命令:" + _ffpath + " " + _ffargs);
            StartCoroutine(IERecording());
        }
        else
        {
            UnityEngine.Debug.LogError("FFRecorder::StartRecording - 设置不当,录制取消,请检查控制台输出。");
        }
    }
    public void StopRecording(Action _OnStopRecording)
    {
        if (!_isRecording)
        {
            UnityEngine.Debug.LogError("FFRecorder::StopRecording - 当前没有录制进程,已取消操作。");
            return;
        }
        StartCoroutine(IEExitCmd(_OnStopRecording));
    }


    public void FocusStopRecording(Action _OnStopRecording)
    {
        if (_isRecording)
        {
            try
            {
                UnityEngine.Debug.LogError("强制取消——录制进程非正常结束,输出文件可能无法播放。");
                Process.GetProcessById(_pid).Kill();


                _isRecording = false;
            }
            catch (Exception e)
            {
                UnityEngine.Debug.LogError("FFRecorder::OnDestroy - " + e.Message);
            }
        }
        if (_OnStopRecording != null)
        {
            _OnStopRecording();
        }
    }


    private bool ParseSettings()
    {
        _ffpath = Application.streamingAssetsPath + @"/ffmpeg/ffmpeg.exe";
        string name = Application.productName + "_" + DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss");
        curVideoName = name + ".mp4";
        // 分辨率
        string s;
        if (resolution == Resolution._1280x720)
        {
            int w = 1280;
            int h = 720;
            if (Screen.width < w)
            {
                w = Screen.width;
                UnityEngine.Debug.LogWarning(string.Format("录制水平分辨率大于屏幕水平分辨率,已自动缩小为{0}。", w));
            }
            if (Screen.height < h)
            {
                h = Screen.height;
                UnityEngine.Debug.LogWarning(string.Format("录制垂直分辨率大于屏幕垂直分辨率,已自动缩小为{0}。", h));
            }
            s = w.ToString() + "x" + h.ToString();
        }
        else if (resolution == Resolution._1920x1080)
        {
            int w = 1920;
            int h = 1080;
            if (Screen.width < w)
            {
                w = Screen.width;
                UnityEngine.Debug.LogWarning(string.Format("录制水平分辨率大于屏幕水平分辨率,已自动缩小为{0}。", w));
            }
            if (Screen.height < h)
            {
                h = Screen.height;
                UnityEngine.Debug.LogWarning(string.Format("录制垂直分辨率大于屏幕垂直分辨率,已自动缩小为{0}。", h));
            }
            s = w.ToString() + "x" + h.ToString();
        }
        else  /*(resolution == Resolution._Auto)*/
        {
            s = Screen.width.ToString() + "x" + Screen.height.ToString();
        }
        // 帧率
        string r = framerate.ToString().Remove(0, 1);
        // 比特率
        string b = bitrate.ToString().Remove(0, 1);
        // 输出位置
        string output;
        if (outputPath == OutputPath.Desktop) output = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) + "/" + Application.productName + "_Records";
        else if (outputPath == OutputPath.DataPath) output = Application.dataPath + "/" + Application.productName + "_Records";
        else if (outputPath == OutputPath.StreamingAsset) output = Application.streamingAssetsPath + "/" + Application.productName + "_Records";
        else if (outputPath == OutputPath.PersistentDataPath) output = Application.persistentDataPath + "/" + Application.productName + "_Records";
        else /*(outputPath == OutputPath.Custom)*/ output = customOutputPath;


        // 命令行参数
        if (recordType == RecordType.GDIGRAB)
        {
            _ffargs = string.Format(FFARGS_GDIGRAB, Application.productName, b, r, s, output, name);
        }
        else /*(recordType == RecordType.DSHOW)*/
        {
            _ffargs = string.Format(FFARGS_DSHOW, b, r, s, output, name);
        }
        // 创建输出文件夹
        if (!System.IO.Directory.Exists(output))
        {
            try
            {
                System.IO.Directory.CreateDirectory(output);
            }
            catch (Exception e)
            {
                UnityEngine.Debug.LogError("FFRecorder::ParseSettings - " + e.Message);
                return false;
            }
        }
        return true;
    }
    // 不一定要用协程
    private IEnumerator IERecording()
    {
        yield return null;
        Process ffp = new Process();
        ffp.StartInfo.FileName = _ffpath;                   // 进程可执行文件位置
        ffp.StartInfo.Arguments = _ffargs;                  // 传给可执行文件的命令行参数
        ffp.StartInfo.CreateNoWindow = !_debug;             // 是否显示控制台窗口
        ffp.StartInfo.UseShellExecute = _debug;             // 是否使用操作系统Shell程序启动进程
        ffp.Start();                                        // 开始进程
        _pid = ffp.Id;
        _isRecording = true;
    }
    private IEnumerator IEExitCmd(Action _OnStopRecording)
    {
        // 将当前进程附加到pid进程的控制台
        AttachConsole(_pid);
        // 将控制台事件的处理句柄设为Zero,即当前进程不响应控制台事件
        // 避免在向控制台发送【Ctrl C】指令时连带当前进程一起结束
        SetConsoleCtrlHandler(IntPtr.Zero, true);
        // 向控制台发送 【Ctrl C】结束指令
        // ffmpeg会收到该指令停止录制
        GenerateConsoleCtrlEvent(0, 0);
        // ffmpeg不能立即停止,等待一会,否则视频无法播放
        yield return new WaitForSeconds(3.0f);
        // 卸载控制台事件的处理句柄,不然之后的ffmpeg调用无法正常停止
        SetConsoleCtrlHandler(IntPtr.Zero, false);
        // 剥离已附加的控制台
        FreeConsole();
        _isRecording = false;
        if (_OnStopRecording != null)
        {
            _OnStopRecording();
        }
        UploadRecordVideo();
    }


    private IEnumerator FocusIEExitCmd(Action _OnStopRecording)
    {
        // 将当前进程附加到pid进程的控制台
        AttachConsole(_pid);
        // 将控制台事件的处理句柄设为Zero,即当前进程不响应控制台事件
        // 避免在向控制台发送【Ctrl C】指令时连带当前进程一起结束
        SetConsoleCtrlHandler(IntPtr.Zero, true);
        // 向控制台发送 【Ctrl C】结束指令
        // ffmpeg会收到该指令停止录制
        GenerateConsoleCtrlEvent(0, 0);
        // ffmpeg不能立即停止,等待一会,否则视频无法播放
        yield return new WaitForSeconds(3.0f);
        // 卸载控制台事件的处理句柄,不然之后的ffmpeg调用无法正常停止
        SetConsoleCtrlHandler(IntPtr.Zero, false);
        // 剥离已附加的控制台
        FreeConsole();
        _isRecording = false;
        if (_OnStopRecording != null)
        {
            _OnStopRecording();
        }


        string filePath = Application.persistentDataPath + "/" + Application.productName + "_Records" + "/" + curVideoName;
        StartCoroutine(DeleteVideoFile(filePath));
    }


    /// <summary>
    /// 上传录屏视频
    /// </summary>
    /// <returns></returns>
    private void UploadRecordVideo()
    {
        if (currentVideoRoutine != null)
            return;
        currentVideoRoutine = DoUploadRecordVideo();
        StartCoroutine(currentVideoRoutine);
    }


    private IEnumerator DoUploadRecordVideo()
    {
        yield return new WaitForSeconds(1.0f);
        string filePath = Application.persistentDataPath + "/" + Application.productName + "_Records" + "/" + curVideoName;


        if (!IsOccupied(filePath, "上传视频"))
        {
            print("压入视频文件");
            currentVideoRoutine = null;
        }
        else
        {
            currentVideoRoutine = null;
            UploadRecordVideo();
        }
    }


    // 程序结束时要杀掉后台的录制进程,但这样会导致输出文件无法播放
    public void OnDestroy()
    {
        if (_isRecording)
        {
            try
            {
                UnityEngine.Debug.LogError("FFRecorder::OnDestroy - 录制进程非正常结束,输出文件可能无法播放。");
                Process.GetProcessById(_pid).Kill();


                _isRecording = false;
            }
            catch (Exception e)
            {
                UnityEngine.Debug.LogError("FFRecorder::OnDestroy - " + e.Message);
            }
        }
    }


    /// <summary>
    /// 删除视频文件
    /// </summary>
    /// <param name="filePath"></param>
    /// <returns></returns>
    private IEnumerator DeleteVideoFile(string filePath)
    {
        yield return new WaitForSeconds(1.0f);
        if (!IsOccupied(filePath, "删除视频"))
        {
            File.Delete(filePath);
            UnityEngine.Debug.Log("清理异常视频完成!");
        }
        else
        {
            UnityEngine.Debug.Log("删除异常视频失败");
            //StartCoroutine(DeleteVideoFile(filePath));
        }
    }


    #region 工具
    /// <summary>
    /// 判断文件是否被占用
    /// </summary>
    /// <param name="filePath"></param>
    /// <returns></returns>
    public bool IsOccupied(string filePath, string fileFrom)
    {
        FileStream stream = null;
        try
        {
            stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.None);
            return false;
        }
        catch
        {
            print(fileFrom + "文件被占用");
            return true;
        }
        finally
        {
            if (stream != null)
            {
                stream.Close();
            }
        }
    }
    #endregion
}

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值