Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Unity【Live Capture】- 关于人脸捕捉的解决方案

Unity【Live Capture】- 关于人脸捕捉的解决方案

作者头像
CoderZ
发布于 2022-08-29 08:48:09
发布于 2022-08-29 08:48:09
1K02
代码可运行
举报
运行总次数:2
代码可运行

最近项目里有人脸捕捉的需求,刚开始时参考的下面这篇文章,使用官方发布的Facial AR Remote,需要我们自己构建IOS客户端,因此需要准备包括MacOS操作系统、Xcode等开发环境,在Unity构建出Xcode工程后,还要考虑开发许可证等问题,而且在尝试时,我使用的Xcode13版本,在编译上还有一些问题,比较麻烦。

https://www.163.com/dy/article/E70U8CLT0526E124.html

随后发现了另一个解决方案,即Live Capture,IOS客户端已经发布于App Store中,名称Unity Face Capture:

Live Capture在Package Manager中通过git url的方式进行添加,地址:

http://com.unity.live-capture

Live Capture官方手册地址:

https://docs.unity.cn/Packages/com.unity.live-capture@1.0/manual/index.html

PDF文档下载地址:

https://link.csdn.net/?target=https%3A%2F%2Fforum.unity.com%2Fattachments%2Flive-capture-apps-startup-guide-pdf.961348%2F

文档很详细,也可以参考下面的步骤:

1.新建空物体,添加Take Recoder组件,它依赖Playable Director组件,添加Take Recorder时自动添加Playable Director:

2.将制作好的包含人脸的模型拖入场景,挂载ARKit Face Actor组件,然后将其做成Prefab预制体。

3.在Take Recoder组件下方点击+按钮,添加ARKit Face Device,并将其Actor设为步骤2中挂载了ARKit Face Actor组件的人脸模型:

4.在Project窗口右键,Create / Live Capture / ARKit Face Capture / Mapper,创建一个Head Mapper资产,将其Rig Prefab设为步骤2中生成的Prefab,并设置人脸模型中对应的的Left Eye、RightEye、Head骨骼节点:

5.点击Head Mapper资产中的Add Renderer按钮,绑定BlendShape,名称如果与ARKit中要求一致则会自动绑定好,否则需要一一对应设置:

6.将编辑好的Head Mapper资产赋值给步骤2挂载的ARKit Face Actor组件中的Mapper:

7.Window / Live Capture / Connections 打开Connections窗口,创建服务器,点击Start即可启动:

8.启动后打开IOS客户端,点击Connect进行连接,若连接不上,检查一下手机和电脑是否处于同一网段,检查电脑防火墙,最好都关掉。

另外值得注意的是,服务端的启动是通过在Connections编辑器窗口中点击Start开启的,因此它的使用环境是在Unity编辑器环境中,如果想打包后运行时使用,需要将其从Package Manager中迁移到工程Assets目录下,并创建脚本编写启动方法:

创建LiveCaptureServer类,继承Server:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
using System;
using System.IO;
using System.Linq;
using System.Text;
using UnityEngine;
using System.Collections.Generic;
using Unity.LiveCapture.Networking;
using Unity.LiveCapture.Networking.Discovery;

namespace Unity.LiveCapture.CompanionApp
{
    [CreateAssetMenu(menuName = "Live Capture/Server")]
    public class LiveCaptureServer : Server
    {
        const int k_DefaultPort = 9000;

        /// <summary>
        /// The server executes this event when a client has connected.
        /// </summary>
        public static event Action<ICompanionAppClient> ClientConnected = delegate { };

        /// <summary>
        /// The server executes this event when a client has disconnected.
        /// </summary>
        public static event Action<ICompanionAppClient> ClientDisconnected = delegate { };

        struct ConnectHandler
        {
            public string Name;
            public DateTime Time;
            public Func<ICompanionAppClient, bool> Handler;
        }

        readonly Dictionary<string, Type> s_TypeToClientType = new Dictionary<string, Type>();
        static readonly List<ConnectHandler> s_ClientConnectHandlers = new List<ConnectHandler>();

        /// <summary>
        /// Adds a callback used to take ownership of a client that has connected.
        /// </summary>
        /// <param name="handler">The callback function. It must return true if it takes ownership of a client.</param>
        /// <param name="name">The name of the client to prefer. If set, this handler has priority over clients that have the given name.</param>
        /// <param name="time">The time used to determine the priority of handlers when many are listening for the same
        /// client <paramref name="name"/>. More recent values have higher priority.</param>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="handler"/> is null.</exception>
        public static void RegisterClientConnectHandler(Func<ICompanionAppClient, bool> handler, string name, DateTime time)
        {
            if (handler == null)
                throw new ArgumentNullException(nameof(handler));

            DeregisterClientConnectHandler(handler);

            s_ClientConnectHandlers.Add(new ConnectHandler
            {
                Name = name,
                Time = time,
                Handler = handler,
            });
        }

        /// <summary>
        /// Removes a client connection callback.
        /// </summary>
        /// <param name="handler">The callback to remove.</param>>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="handler"/> is null.</exception>
        public static void DeregisterClientConnectHandler(Func<ICompanionAppClient, bool> handler)
        {
            if (handler == null)
                throw new ArgumentNullException(nameof(handler));

            for (var i = 0; i < s_ClientConnectHandlers.Count; i++)
            {
                if (s_ClientConnectHandlers[i].Handler == handler)
                {
                    s_ClientConnectHandlers.RemoveAt(i);
                }
            }
        }

        public void Init()
        {
            foreach (var (type, attributes) in AttributeUtility.GetAllTypes<ClientAttribute>())
            {
                if (!typeof(CompanionAppClient).IsAssignableFrom(type))
                {
                    Debug.LogError($"{type.FullName} must be assignable from {nameof(CompanionAppClient)} to use the {nameof(ClientAttribute)} attribute.");
                    continue;
                }

                foreach (var attribute in attributes)
                {
                    s_TypeToClientType[attribute.Type] = type;
                }
            }
        }

        [SerializeField, Tooltip("The TCP port on which the server will listen for incoming connections. Changes to the port only take effect after restarting the server.")]
        int m_Port = k_DefaultPort;
        [SerializeField, Tooltip("Start the server automatically after entering play mode.")]
        bool m_AutoStartOnPlay = true;

        readonly DiscoveryServer m_Discovery = new DiscoveryServer();
        readonly NetworkServer m_Server = new NetworkServer();
        readonly Dictionary<Remote, ICompanionAppClient> m_RemoteToClient = new Dictionary<Remote, ICompanionAppClient>();

        /// <summary>
        /// The TCP port on which the server will listen for incoming connections.
        /// </summary>
        /// <remarks>
        /// Changes to the port only take effect after restarting the server.
        /// </remarks>
        public int Port
        {
            get => m_Port;
            set
            {
                if (m_Port != value)
                {
                    m_Port = value;
                    OnServerChanged(true);
                }
            }
        }

        /// <summary>
        /// Start the server automatically after entering play mode.
        /// </summary>
        public bool AutoStartOnPlay
        {
            get => m_AutoStartOnPlay;
            set
            {
                if (m_AutoStartOnPlay != value)
                {
                    m_AutoStartOnPlay = value;
                    OnServerChanged(true);
                }
            }
        }

        /// <summary>
        /// Are clients able to connect to the server.
        /// </summary>
        public bool IsRunning => m_Server.IsRunning;

        /// <summary>
        /// The number of clients currently connected to the server.
        /// </summary>
        public int ClientCount => m_RemoteToClient.Count;

        /// <inheritdoc/>
        protected override void OnEnable()
        {
            base.OnEnable();

            m_Server.RemoteConnected += OnClientConnected;
            m_Server.RemoteDisconnected += OnClientDisconnected;
        }

        /// <inheritdoc/>
        protected override void OnDisable()
        {
            base.OnDisable();

            m_Discovery.Stop();
            m_Server.Stop();

            m_Server.RemoteConnected -= OnClientConnected;
            m_Server.RemoteDisconnected -= OnClientDisconnected;
        }

        /// <summary>
        /// Gets the currently connected clients.
        /// </summary>
        /// <returns>A new collection containing the client handles.</returns>
        public IEnumerable<ICompanionAppClient> GetClients()
        {
            return m_RemoteToClient.Values;
        }

        /// <inheritdoc />
        public override string GetName() => "Companion App Server";

        /// <summary>
        /// Start listening for clients connections.
        /// </summary>
        public void StartServer()
        {
            if (!NetworkUtilities.IsPortAvailable(m_Port))
            {
                Debug.LogError($"Unable to start server: Port {m_Port} is in use by another program! Close the other program, or assign a free port using the Live Capture Window.");
                return;
            }

            if (m_Server.StartServer(m_Port))
            {
                // start server discovery
                var config = new ServerData(
                    "Live Capture",
                    Environment.MachineName,
                    m_Server.ID,
                    PackageUtility.GetVersion(LiveCaptureInfo.Version)
                );
                var endPoints = m_Server.EndPoints.ToArray();

                m_Discovery.Start(config, endPoints);
            }

            OnServerChanged(false);
        }

        /// <summary>
        /// Disconnects all clients and stop listening for new connections.
        /// </summary>
        public void StopServer()
        {
            m_Server.Stop();
            m_Discovery.Stop();

            OnServerChanged(false);
        }

        /// <inheritdoc/>
        public override void OnUpdate()
        {
            m_Server.Update();
            m_Discovery.Update();
        }

        void OnClientConnected(Remote remote)
        {
            m_Server.RegisterMessageHandler(remote, InitializeClient, false);
        }

        void OnClientDisconnected(Remote remote, DisconnectStatus status)
        {
            if (m_RemoteToClient.TryGetValue(remote, out var client))
            {
                try
                {
                    ClientDisconnected.Invoke(client);
                }
                catch (Exception e)
                {
                    Debug.LogError(e);
                }

                m_RemoteToClient.Remove(remote);
                OnServerChanged(false);
            }
        }

        void InitializeClient(Message message)
        {
            try
            {
                if (message.ChannelType != ChannelType.ReliableOrdered)
                {
                    return;
                }

                var streamReader = new StreamReader(message.Data, Encoding.UTF8);
                var json = streamReader.ReadToEnd();
                var data = default(ClientInitialization);

                try
                {
                    data = JsonUtility.FromJson<ClientInitialization>(json);
                }
                catch (Exception)
                {
                    Debug.LogError($"{nameof(CompanionAppServer)} failed to initialize client connection! Could not parse JSON: {json}");
                    return;
                }

                if (!s_TypeToClientType.TryGetValue(data.Type, out var clientType))
                {
                    Debug.LogError($"Unknown client type \"{data.Type}\" connected to {nameof(CompanionAppServer)}!");
                    return;
                }

                var remote = message.Remote;
                var client = Activator.CreateInstance(clientType, m_Server, remote, data) as CompanionAppClient;
                client.SendProtocol();

                m_RemoteToClient.Add(remote, client);

                AssignOwner(client);

                ClientConnected.Invoke(client);
                OnServerChanged(false);
            }
            catch (Exception e)
            {
                Debug.LogException(e);
            }
            finally
            {
                message.Dispose();
            }
        }

        void AssignOwner(ICompanionAppClient client)
        {
            // connect to the registered handler that was most recently used with this client if possible
            foreach (var handler in s_ClientConnectHandlers.OrderByDescending(h => h.Time.Ticks))
            {
                try
                {
                    if (handler.Name == client.Name)
                    {
                        if (handler.Handler(client))
                            return;
                    }
                }
                catch (Exception e)
                {
                    Debug.LogException(e);
                }
            }

            // fall back to the first free device that is compatible with the client
            foreach (var handler in s_ClientConnectHandlers)
            {
                try
                {
                    if (handler.Handler(client))
                        return;
                }
                catch (Exception e)
                {
                    Debug.LogException(e);
                }
            }
        }
    }
}

更改CompanionAppDevice类:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
using System;
using UnityEngine;

namespace Unity.LiveCapture.CompanionApp
{
    /// <summary>
    /// A type of <see cref="LiveCaptureDevice"/> that uses a <see cref="ICompanionAppClient"/> for communication.
    /// </summary>
    interface ICompanionAppDevice
    {
        /// <summary>
        /// Clears the client assigned to this device.
        /// </summary>
        void ClearClient();
    }

    /// <summary>
    /// A type of <see cref="LiveCaptureDevice"/> that uses a <see cref="ICompanionAppClient"/> for communication.
    /// </summary>
    /// <typeparam name="TClient">The type of client this device communicates with.</typeparam>
    public abstract class CompanionAppDevice<TClient> : LiveCaptureDevice, ICompanionAppDevice where TClient : class, ICompanionAppClient
    {
        bool m_ClientRegistered;
        bool m_Recording;
        TClient m_Client;
        readonly SlateChangeTracker m_SlateChangeTracker = new SlateChangeTracker();
        readonly TakeNameFormatter m_TakeNameFormatter = new TakeNameFormatter();
        string m_LastAssetName;

        bool TryGetInternalClient(out ICompanionAppClientInternal client)
{
            client = m_Client as ICompanionAppClientInternal;

            return client != null;
        }

        /// <summary>
        /// This function is called when the object becomes enabled and active.
        /// </summary>
        protected virtual void OnEnable()
{
            CompanionAppServer.ClientDisconnected += OnClientDisconnected;
            LiveCaptureServer.ClientDisconnected += OnClientDisconnected;
            RegisterClient();
        }

        /// <summary>
        /// This function is called when the behaviour becomes disabled.
        /// </summary>
        /// <remaks>
        /// This is also called when the object is destroyed and can be used for any cleanup code.
        ///  When scripts are reloaded after compilation has finished, OnDisable will be called, followed by an OnEnable after the script has been loaded.
        /// </remaks>
        protected virtual void OnDisable()
{
            CompanionAppServer.ClientDisconnected -= OnClientDisconnected;
            CompanionAppServer.DeregisterClientConnectHandler(OnClientConnected);
            LiveCaptureServer.ClientConnected -= OnClientDisconnected;
            LiveCaptureServer.DeregisterClientConnectHandler(OnClientConnected);

            StopRecording();
            UnregisterClient();
        }

        /// <summary>
        /// This function is called when the behaviour gets destroyed.
        /// </summary>
        protected override void OnDestroy()
{
            base.OnDestroy();

            ClearClient();
        }

        /// <inheritdoc/>
        public override bool IsReady()
{
            return m_Client != null;
        }

        /// <inheritdoc/>
        public override bool IsRecording()
{
            return m_Recording;
        }

        /// <inheritdoc/>
        public override void StartRecording()
{
            if (!m_Recording)
            {
                m_Recording = true;

                OnRecordingChanged();
                SendRecordingState();
            }
        }

        /// <inheritdoc/>
        public override void StopRecording()
{
            if (m_Recording)
            {
                m_Recording = false;

                OnRecordingChanged();
                SendRecordingState();
            }
        }

        /// <summary>
        /// Gets the client currently assigned to this device.
        /// </summary>
        /// <returns>The assigned client, or null if none is assigned.</returns>
        public TClient GetClient()
{
            return m_Client;
        }

        /// <summary>
        /// Assigns a client to this device.
        /// </summary>
        /// <param name="client">The client to assign, or null to clear the assigned client.</param>
        /// <param name="rememberAssignment">Try to auto-assign the client to this device when it reconnects in the future.</param>
        public void SetClient(TClient client, bool rememberAssignment)
{
            if (m_Client != client)
            {
                UnregisterClient();

                if (m_Client != null)
                {
                    ClientMappingDatabase.DeregisterClientAssociation(this, m_Client, rememberAssignment);
                }

                m_Client = client;

                if (m_Client != null)
                {
                    // if any device is also using this client, we must clear the client from the previous device.
                    if (ClientMappingDatabase.TryGetDevice(client, out var previousDevice))
                    {
                        previousDevice.ClearClient();
                    }

                    ClientMappingDatabase.RegisterClientAssociation(this, m_Client, rememberAssignment);
                }

                RegisterClient();
            }
        }

        void RegisterClient()
{
            if (!isActiveAndEnabled ||  m_ClientRegistered)
            {
                return;
            }

            LiveCaptureServer.DeregisterClientConnectHandler(OnClientConnected);
            CompanionAppServer.DeregisterClientConnectHandler(OnClientConnected);

            m_SlateChangeTracker.Reset();

            if (TryGetInternalClient(out var client))
            {
                client.SetDeviceMode += ClientSetDeviceMode;
                client.StartRecording += ClientStartRecording;
                client.StopRecording += ClientStopRecording;
                client.StartPlayer += ClientStartPlayer;
                client.StopPlayer += ClientStopPlayer;
                client.PausePlayer += ClientPausePlayer;
                client.SetPlayerTime += ClientSetPlayerTime;
                client.SetSelectedTake += ClientSetSelectedTake;
                client.SetTakeData += ClientSetTakeData;
                client.DeleteTake += ClientDeleteTake;
                client.SetIterationBase += ClientSetIterationBase;
                client.ClearIterationBase += ClientClearIterationBase;
                client.TexturePreviewRequested += OnTexturePreviewRequested;

                OnClientAssigned();

                client.SendInitialize();

                UpdateClient();

                 m_ClientRegistered = true;
            }
            else
            {
                ClientMappingDatabase.TryGetClientAssignment(this, out var clientName, out var time);
                LiveCaptureServer.RegisterClientConnectHandler(OnClientConnected, clientName, time);
                CompanionAppServer.RegisterClientConnectHandler(OnClientConnected, clientName, time);
            }
        }

        void UnregisterClient()
{
            if (!m_ClientRegistered)
            {
                return;
            }

            if (TryGetInternalClient(out var client))
            {
                OnClientUnassigned();

                client.SendEndSession();
                client.SetDeviceMode -= ClientSetDeviceMode;
                client.StartRecording -= ClientStartRecording;
                client.StopRecording -= ClientStopRecording;
                client.StartPlayer -= ClientStartPlayer;
                client.StopPlayer -= ClientStopPlayer;
                client.PausePlayer -= ClientPausePlayer;
                client.SetPlayerTime -= ClientSetPlayerTime;
                client.SetSelectedTake -= ClientSetSelectedTake;
                client.SetTakeData -= ClientSetTakeData;
                client.DeleteTake -= ClientDeleteTake;
                client.SetIterationBase -= ClientSetIterationBase;
                client.ClearIterationBase -= ClientClearIterationBase;
                client.TexturePreviewRequested -= OnTexturePreviewRequested;

                m_ClientRegistered = false;
            }
        }

        /// <inheritdoc />
        public void ClearClient()
{
            SetClient(null, true);
        }

        /// <summary>
        /// Called to send the device state to the client.
        /// </summary>
        public virtual void UpdateClient()
{
            var takeRecorder = GetTakeRecorder();

            if (takeRecorder != null)
            {
                SendDeviceState(takeRecorder.IsLive());

                var slate = takeRecorder.GetActiveSlate();
                var hasSlate = slate != null;
                var slateChanged = m_SlateChangeTracker.Changed(slate);
                var take = hasSlate ? slate.Take : null;

                var assetName = GetAssetName();
                var assetNameChanged = assetName != m_LastAssetName;
                m_LastAssetName = assetName;

                if (TryGetInternalClient(out var client))
                {
                    client.SendFrameRate(takeRecorder.IsLive() || take == null ? takeRecorder.FrameRate : take.FrameRate);
                    client.SendHasSlate(hasSlate);
                    client.SendSlateDuration(hasSlate ? slate.Duration : 0d);
                    client.SendSlateIsPreviewing(takeRecorder.IsPreviewPlaying());
                    client.SendSlatePreviewTime(takeRecorder.GetPreviewTime());

                    if (slateChanged || assetNameChanged)
                    {
                        if (hasSlate)
                            m_TakeNameFormatter.ConfigureTake(slate.SceneNumber, slate.ShotName, slate.TakeNumber);
                        else
                            m_TakeNameFormatter.ConfigureTake(0, "Shot", 0);

                        client.SendNextTakeName(m_TakeNameFormatter.GetTakeName());
                        client.SendNextAssetName(m_TakeNameFormatter.GetAssetName());
                    }
                }

                if (slateChanged)
                {
                    SendSlateDescriptor(slate);
                }
            }

            SendRecordingState();
        }

        /// <summary>
        /// Gets the name used for the take asset name.
        /// </summary>
        /// <returns>The name of the asset.</returns>
        protected virtual string GetAssetName() { return name; }

        /// <summary>
        /// The device calls this method when a new client is assigned.
        /// </summary>
        protected virtual void OnClientAssigned() {}

        /// <summary>
        /// The device calls this method when the client is unassigned.
        /// </summary>
        protected virtual void OnClientUnassigned() {}

        /// <summary>
        /// The device calls this method when the recording state has changed.
        /// </summary>
        protected virtual void OnRecordingChanged() {}

        /// <summary>
        /// The device calls this method when the slate has changed.
        /// </summary>
        protected virtual void OnSlateChanged(ISlate slate) {}

        void ClientStartRecording()
{
            var takeRecorder = GetTakeRecorder();

            if (takeRecorder != null)
            {
                takeRecorder.StartRecording();
            }

            Refresh();
        }

        void ClientStopRecording()
{
            var takeRecorder = GetTakeRecorder();

            if (takeRecorder != null)
            {
                takeRecorder.StopRecording();
            }

            Refresh();
        }

        void ClientSetDeviceMode(DeviceMode deviceMode)
{
            var takeRecorder = GetTakeRecorder();

            if (takeRecorder != null)
            {
                takeRecorder.SetLive(deviceMode == DeviceMode.LiveStream);

                SendDeviceState(takeRecorder.IsLive());
            }
        }

        void ClientStartPlayer()
{
            var takeRecorder = GetTakeRecorder();

            if (takeRecorder != null)
            {
                takeRecorder.PlayPreview();
            }

            Refresh();
        }

        void ClientStopPlayer()
{
            var takeRecorder = GetTakeRecorder();

            if (takeRecorder != null)
            {
                takeRecorder.PausePreview();
                takeRecorder.SetPreviewTime(0d);
            }

            Refresh();
        }

        void ClientPausePlayer()
{
            var takeRecorder = GetTakeRecorder();

            if (takeRecorder != null)
            {
                takeRecorder.PausePreview();
            }

            Refresh();
        }

        void ClientSetPlayerTime(double time)
{
            var takeRecorder = GetTakeRecorder();

            if (takeRecorder != null)
            {
                takeRecorder.SetPreviewTime(time);
            }

            Refresh();
        }

        void SendDeviceState()
{
            var takeRecorder = GetTakeRecorder();

            if (takeRecorder != null)
            {
                SendDeviceState(takeRecorder.IsLive());
            }
        }

        void SendDeviceState(bool isLive)
{
            if (TryGetInternalClient(out var client))
            {
                client.SendDeviceMode(isLive ? DeviceMode.LiveStream : DeviceMode.Playback);
            }
        }

        void SendRecordingState()
{
            if (TryGetInternalClient(out var client))
            {
                client.SendRecordingState(IsRecording());
            }
        }

        void SendSlateDescriptor()
{
            var takeRecorder = GetTakeRecorder();

            if (takeRecorder != null)
            {
                SendSlateDescriptor(takeRecorder.GetActiveSlate());
            }
        }

        void SendSlateDescriptor(ISlate slate)
{
            if (TryGetInternalClient(out var client))
            {
                client.SendSlateDescriptor(SlateDescriptor.Create(slate));
            }

            OnSlateChanged(slate);
        }

        void ClientSetSelectedTake(Guid guid)
{
            var takeRecorder = GetTakeRecorder();

            if (takeRecorder != null)
            {
                TakeManager.Default.SelectTake(takeRecorder.GetActiveSlate(), guid);

                SendSlateDescriptor();
                Refresh();
            }
        }

        void ClientSetTakeData(TakeDescriptor descriptor)
{
            TakeManager.Default.SetTakeData(descriptor);

            SendSlateDescriptor();
            Refresh();
        }

        void ClientDeleteTake(Guid guid)
{
            TakeManager.Default.DeleteTake(guid);

            SendSlateDescriptor();
            Refresh();
        }

        void ClientSetIterationBase(Guid guid)
{
            var takeRecorder = GetTakeRecorder();

            if (takeRecorder != null)
            {
                var slate = takeRecorder.GetActiveSlate();

                TakeManager.Default.SetIterationBase(slate, guid);

                SendSlateDescriptor(slate);
                Refresh();
            }
        }

        void ClientClearIterationBase()
{
            var takeRecorder = GetTakeRecorder();

            if (takeRecorder != null)
            {
                var slate = takeRecorder.GetActiveSlate();

                TakeManager.Default.ClearIterationBase(slate);

                SendSlateDescriptor(slate);
                Refresh();
            }
        }

        void OnTexturePreviewRequested(Guid guid)
{
            var texture = TakeManager.Default.GetAssetPreview<Texture2D>(guid);

            if (texture != null && TryGetInternalClient(out var client))
            {
                client.SendTexturePreview(guid, texture);
            }
        }

        bool OnClientConnected(ICompanionAppClient client)
{
            if (m_Client == null && client is TClient c && (!ClientMappingDatabase.TryGetClientAssignment(this, out var clientName, out _) || c.Name == clientName))
            {
                SetClient(c, false);
                return true;
            }
            return false;
        }

        void OnClientDisconnected(ICompanionAppClient client)
{
            if (m_Client == client)
            {
                SetClient(null, false);
            }
        }
    }
}

本人打包后成功运行,测试脚本:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
using UnityEngine;
using Unity.LiveCapture.CompanionApp;

public class LiveCaptureExample : MonoBehaviour
{
    LiveCaptureServer server;
    private void Awake()
    {
        server = Resources.Load<LiveCaptureServer>("Live Capture Server");
        server.Init();
        server.StartServer();
    }
    private void Update()
    {
        server.OnUpdate();
    }
    private void OnDestroy()
    {
        server.StopServer();
    }
}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-03-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 当代野生程序猿 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
golang源码阅读:livego直播系统
在分析源码之前,先搭建一个直播系统: 直播服务器 https://github.com/gwuhaolin/livego 播放站点 https://github.com/Bilibili/flv.js/ 推流 https://github.com/obsproject/obs-studio 首先启动直播服务器 ./livego --flv_dir=./data --level=debug 1,在启动livego服务后默认会监听以下端口: 8090端口:用于控制台,通过HTTP请求可查看与控制直播房间的推拉
golangLeetcode
2022/08/03
3.1K1
golang源码阅读:livego直播系统
h5-capture 使用和明细
这里要用到一个 h5 的属性capture 由于h5项目中使用到了文件上传的功能,这里来写一下 html5的一个属性 capture 的使用。 直接使用 type= "file" 加上 accept="image/*"就会默认调起系统的图片文件。 当你需要直接唤起相机的时候 capture="camera" 需要支持多选的时候 multiple="multiple" <input type="file" id="camera" multiple="multiple" capture="camera" acc
西南_张家辉
2021/02/02
1.4K0
Unity3D-网络(一)高级开发-网络解决方案
Unity5.1为开发者发布全新的多玩家在线工具、技术和服务。该技术的内部项目名称为 UNET,全称为 Unity Networking。 第一阶段是多玩家在线技术基础。 第二阶段基于第一阶段,借助模拟服务器引入服务器权威游戏 (server authoritative gaming) 概念。 第三阶段是最后阶段,我们想通过主模拟服务器赋予协调多个模拟服务器的能力。
孙寅
2020/06/02
3.4K0
Hangfire Redis 实现秒级定时任务、使用 CQRS 实现动态执行代码
本文示例项目仓库:https://github.com/whuanle/HangfireDemo
痴者工良
2025/04/19
1730
Hangfire Redis 实现秒级定时任务、使用 CQRS 实现动态执行代码
Unity【Face Cap】- 关于人脸捕捉的解决方案(二)
在官网文档里只看到了支持Unity的说明,但是并没有找到相关的开发工具包的下载地址,博主是在下面的链接里下载的,没有C币的可以联系我发一份。
CoderZ
2022/08/29
1.3K0
Unity【Face Cap】- 关于人脸捕捉的解决方案(二)
WinForm企业应用框架设计【五】系统登录以及身份验证+源码
WinForm企业应用框架设计【一】界限划分与动态创建WCF服务(no svc!no serviceActivations!)
liulun
2022/05/09
7550
WinForm企业应用框架设计【五】系统登录以及身份验证+源码
.NET Core 3.0之深入源码理解HttpClientFactory(二)
上一篇文章讨论了通过在ConfigureServices中调用services.AddHttpClient()方法,并基于此进一步探讨了DefaultHttpClientFactory是如何创建HttpClient实例和HttpMessageHandler实例的,并了解了DefaultHttpClientFactory内部维护者一个定时器和两个HttpMessageHandler对象集合,以定期清理无效的 HttpMessageHandler对象,详细的内容可以点击链接跳转,接下来我会接着前一篇文章继续展开相关讨论。
AI.NET 极客圈
2019/07/30
9070
Unity 接入百度AI - 人像动漫化
运用对抗生成网络技术,结合人脸检测、头发分割、人像分割等技术,为用户量身定制千人千面的二次元动漫形象,并支持通过参数设置,生成二次元动漫人像。
CoderZ
2022/08/29
8070
Unity 接入百度AI - 人像动漫化
C# 通过ServiceStack 操作Redis——String类型的使用及示例
我这里就用别人已经封装好的Reids操作类来和大家一起参考了下,看看怎么使用ServiceStack.Redis 操作Redis数据
明志德道
2023/10/21
4210
C# 通过ServiceStack 操作Redis——String类型的使用及示例
Cordova插件cordova-plugin-media-capture实现短视频的录制上传和播放
1、网上的教程大部分都是虎头蛇尾的不全的。互相抄来抄去真的感觉就没有一个是真正自己去写一写的,不然这里面这么多的坑就没有一个人出来说说的?下面就写写我实现功能过程中的一些问题吧,代码绝对完整并且按照步骤来一定可以成功!
用户6493868
2022/03/05
2.1K0
unity 的Cinemachine组件运用
通过Package Manager 安装CineMachine 1) 最简单的方法使用freeLook虚拟相机
全栈程序员站长
2022/08/27
2K0
unity 的Cinemachine组件运用
Unity【Multiplayer 多人在线】服务端、客户端通用架构的使用指南
•Package包中包含Server服务端内容以及protogen工具,将其解压到工程外;
CoderZ
2022/12/26
1.4K0
Unity【Multiplayer 多人在线】服务端、客户端通用架构的使用指南
【Unity游戏开发】你真的了解UGUI中的IPointerClickHandler吗?
  马三在最近的开发工作中遇到了一个比较有意思的bug:“TableViewCell上面的某些自定义UI组件不能响应点击事件,并且它的父容器TableView也不能响应点击事件,但是TableViewCell上面的Button等组件却可以接受点击事件,并且如果单独把自定义UI控件放在一个UI上面也可以接受点击事件”。最后马三通过仔细地分析,发现是某些自定义的UI组件实现方法的问题。通常情况下,如果想要一个UI响应点击事件的话,我们只需要实现IPointerClickHandler这个接口就可以了,但是在我们项目中的TableView继承自MonoBehavior,并且实现了IPointerClickHandler, IPointerDownHandler, IPointerUpHandler,IDragHandler等UI接口,此时如果我们的自定义UI组件只实现了IPointerClickHandler接口,而没有实现 IPointerDownHandler 接口,然后又作为TableViewCell里面的一个Child的话,就会出现TableViewCell接收不到点击事件,TableView也接收不到点击事件。点击事件被诡异地“吞没了”!下面我们简单地设计三个不同情况下的模拟测试来复现一下这个bug。
马三小伙儿
2019/04/09
3.4K0
【Unity游戏开发】你真的了解UGUI中的IPointerClickHandler吗?
Unity Metaverse(八)、RTC Engine 基于Agora声网SDK实现音视频通话
本文介绍如何在Unity中接入声网SDK,它可以应用的场景有许多,例如直播、电商、游戏、社交等,音视频通话是其实时互动的基础能力。
CoderZ
2023/08/23
8230
Unity Metaverse(八)、RTC Engine 基于Agora声网SDK实现音视频通话
Unity 接入百度AI - Logo商标识别
该请求用于检测和识别图片中的品牌LOGO信息。即对于输入的一张图片(可正常解码,且长宽比适宜),输出图片中LOGO的名称、位置和置信度。当效果欠佳时,可以建立子库(在百度开发者中心控制台创建应用并申请建库)并通过调用logo入口接口完成自定义logo入库,提高识别效果。
CoderZ
2022/08/29
4000
Unity 接入百度AI - Logo商标识别
VR开发--Cardboard制作VR播放器
首先,今天有了一个想法。所以我决定制作一个VR播放器。纯手工打造,24K金不敢说,100%真心。
孙寅
2020/06/02
2.3K0
Flutter下实现低延迟的跨平台RTSP/RTMP播放
Flutter是谷歌的移动UI框架,可以快速在iOS和Android上构建高质量的原生用户界面。 Flutter可以与现有的代码一起工作。在全世界,Flutter正在被越来越多的开发者和组织使用,并且Flutter是完全免费、开源的。
音视频牛哥
2019/09/17
5K0
Flutter下实现低延迟的跨平台RTSP/RTMP播放
旧的Spring Security OAuth已停止维护,全面拥抱新解决方案Spring SAS
可用于方法或类上,将基于注解的权限code,针对性处理方法或当前类的所有接口进行权限拦截。
JEECG
2024/03/06
3690
dotnet 读 WPF 源代码笔记 从 WM_POINTER 消息到 Touch 事件
本文记录我读 WPF 源代码的笔记,在 WPF 底层是如何从 Win32 的消息循环获取到的 WM_POINTER 消息处理转换作为 Touch 事件的参数
林德熙
2024/09/09
4040
Unity3D下如何实现跨平台(Windows/Linux/Android/iOS)低延迟的RTMP、RTSP播放
好多开发者,希望我们能探讨下Unity平台RTMP或RTSP直播流数据播放和录制相关的模块,实际上,这块流程我们已经聊过多次,无非就是通过原生的RTMP或者RTSP模块,先从协议层拉取到数据,并解包解码,回调YUV或RGB数据,然后,在Unity创建响应的shader,获取图像数据填充纹理即可,说起来流程很简单,但是每个环节,如果做到极致体验,都非常难。简单来说,多一次拷贝,都会增大性能瓶颈或延迟。
音视频牛哥
2023/05/24
9950
Unity3D下如何实现跨平台(Windows/Linux/Android/iOS)低延迟的RTMP、RTSP播放
推荐阅读
相关推荐
golang源码阅读:livego直播系统
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验