首页 > 系统相关 >Windows平台Unity下实现camera场景推送RTMP|轻量级RTSP服务|实时录像

Windows平台Unity下实现camera场景推送RTMP|轻量级RTSP服务|实时录像

时间:2023-11-22 23:31:36浏览次数:33  
标签:publisher Windows type wrapper _. Unity btn NT 轻量级


技术背景

我们在对接Unity平台camera场景采集的时候,除了常规的RTMP推送、录像外,还有一些开发者,需要能实现轻量级RTSP服务,对外提供个拉流的RTSP URL。

目前我们在Windows平台Unity下数据源可采集到以下部分:

  • 采集Unity camera场景;
  • 采集摄像头;
  • 采集屏幕;
  • 采集Unity声音;
  • 采集麦克风;
  • 采集扬声器;
  • Unity PCM混音;

对外提供的技术能力有:

  • RTMP直播推送;
  • 轻量级RTSP服务;
  • 实时录像、暂停|恢复录像;
  • 实时预览。

技术实现

实际上,在实现Unity平台音视频能力之前,我们原生模块已经有非常成熟的技术积累,Unity下还是调用的原生的推送模块,不同的是,数据源需要采集Unity的audio、video,然后高效的投递到底层模块,底层模块负责编码打包,并投递到RTMP或RTSP服务。

Windows平台Unity下实现camera场景推送RTMP|轻量级RTSP服务|实时录像_Unity3D RTSP

先说支持的音视频类型:

public void SelVideoPushType(int type)
    {
        switch (type)
        {
            case 0:
                video_push_type_ = (uint)NTSmartPublisherDefine.NT_PB_E_VIDEO_OPTION.NT_PB_E_VIDEO_OPTION_LAYER;    //采集Unity窗体
                break;
            case 1:
                video_push_type_ = (uint)NTSmartPublisherDefine.NT_PB_E_VIDEO_OPTION.NT_PB_E_VIDEO_OPTION_CAMERA;   //采集摄像头
                break;
            case 2:
                video_push_type_ = (uint)NTSmartPublisherDefine.NT_PB_E_VIDEO_OPTION.NT_PB_E_VIDEO_OPTION_SCREEN;   //采集屏幕
                break;
            case 3:
                video_push_type_ = (uint)NTSmartPublisherDefine.NT_PB_E_VIDEO_OPTION.NT_PB_E_VIDEO_OPTION_NO_VIDEO; //不采集视频
                break;
        }

        Debug.Log("SelVideoPushType type: " + type + " video_push_type: " + video_push_type_);
    }

    public void SelAudioPushType(int type)
    {
        switch (type)
        {
            case 0:
                audio_push_type_ = (uint)NTSmartPublisherDefine.NT_PB_E_AUDIO_OPTION.NT_PB_E_AUDIO_OPTION_EXTERNAL_PCM_DATA;    //采集Unity声音
                break;
            case 1:
                audio_push_type_ = (uint)NTSmartPublisherDefine.NT_PB_E_AUDIO_OPTION.NT_PB_E_AUDIO_OPTION_CAPTURE_MIC;  //采集麦克风
                break;
            case 2:
                audio_push_type_ = (uint)NTSmartPublisherDefine.NT_PB_E_AUDIO_OPTION.NT_PB_E_AUDIO_OPTION_CAPTURE_SPEAKER;  //采集扬声器
                break;
            case 3:
                audio_push_type_ = (uint)NTSmartPublisherDefine.NT_PB_E_AUDIO_OPTION.NT_PB_E_AUDIO_OPTION_TWO_EXTERNAL_PCM_MIXER;  //两路Unity AudioClip混音测试
                break;
            case 4:
                audio_push_type_ = (uint)NTSmartPublisherDefine.NT_PB_E_AUDIO_OPTION.NT_PB_E_AUDIO_OPTION_NO_AUDIO;   //不采集音频
                break;
        }

        Debug.Log("SelAudioPushType type: " + type + " audio_push_type: " + audio_push_type_);
    }

采集音视频数据:

private void StartCaptureAvData()
    {
        if (audio_push_type_ == (uint)NTSmartPublisherDefine.NT_PB_E_AUDIO_OPTION.NT_PB_E_AUDIO_OPTION_EXTERNAL_PCM_DATA
            || audio_push_type_ == (uint)NTSmartPublisherDefine.NT_PB_E_AUDIO_OPTION.NT_PB_E_AUDIO_OPTION_TWO_EXTERNAL_PCM_MIXER)
        {
            PostUnityAudioClipData();
        }

        textures_poll_ = new TexturesPool();
        post_image_worker_ = new PostImageWorker(textures_poll_, publisher_wrapper_);
        post_worker_thread_ = new Thread(post_image_worker_.run);
        post_worker_thread_.Start();
    }

    private void StopCaptureAvData()
    {
        if (post_image_worker_ != null)
        {
            post_image_worker_.requestStop();
            post_image_worker_ = null;
        }

        if (post_worker_thread_ != null)
        {
            post_worker_thread_.Join();
            post_worker_thread_ = null;
        }

        if (textures_poll_ != null)
        {
            textures_poll_.clear();
            textures_poll_ = null;
        }

        StopAudioSource();
    }

RTMP推送:

public void btn_start_rtmp_pusher_Click()
    {
        if (publisher_wrapper_.IsPushingRtmp())
        {
            StopPushRTMP();
            btn_rtmp_pusher_.GetComponentInChildren<Text>().text = "推送RTMP";

            return;
        }

        String url = rtmp_pusher_url_.text;

        if (url.Length < 8)
        {
            publisher_wrapper_.Close();

            Debug.LogError("请输入RTMP推送地址");
            return;
        }

        if (!publisher_wrapper_.IsPreviewing() && !publisher_wrapper_.IsRtspPublisherRunning() && !publisher_wrapper_.IsRecording())
        {
            publisher_wrapper_.SetVideoPushType(video_push_type_);
            publisher_wrapper_.SetAudioPushType(audio_push_type_);
        }

        if (!publisher_wrapper_.StartRtmpPusher(url))
        {
            Debug.LogError("调用StartPublisher失败..");
            return;
        }

        btn_rtmp_pusher_.GetComponentInChildren<Text>().text = "停止推送";

        if (!publisher_wrapper_.IsPreviewing() && !publisher_wrapper_.IsRtspPublisherRunning() && !publisher_wrapper_.IsRecording())
        {
            StartCaptureAvData();
            coroutine_ = StartCoroutine(OnPostVideo());
        }
    }

轻量级RTSP服务相关调用:

public void btn_rtsp_service_Click()
    {
        if (publisher_wrapper_.IsRtspServiceRunning())
        {
            publisher_wrapper_.StopRtspService();
            btn_rtsp_service_.GetComponentInChildren<Text>().text = "启动RTSP服务";

            btn_rtsp_publisher_.interactable = false;
            return;
        }

        if (!publisher_wrapper_.StartRtspService())
        {
            Debug.LogError("调用StartRtspService失败..");
            return;
        }

        btn_rtsp_publisher_.interactable = true;

        btn_rtsp_service_.GetComponentInChildren<Text>().text = "停止RTSP服务";
    }

    public void btn_rtsp_publisher_Click()
    {
        if (publisher_wrapper_.IsRtspPublisherRunning())
        {
            publisher_wrapper_.StopRtspStream();

            if (!publisher_wrapper_.IsPreviewing() && !publisher_wrapper_.IsPushingRtmp() && !publisher_wrapper_.IsRecording())
            {
                StopCaptureAvData();

                if (coroutine_ != null)
                {
                    StopCoroutine(coroutine_);
                    coroutine_ = null;
                }
            }

            btn_rtsp_service_.interactable = true;

            btn_rtsp_publisher_.GetComponentInChildren<Text>().text = "发布RTSP";
        }
        else
        {
            if (!publisher_wrapper_.IsRtspServiceRunning())
            {
                Debug.LogError("RTSP service is not running..");
                return;
            }


            if (!publisher_wrapper_.IsPreviewing() && !publisher_wrapper_.IsPushingRtmp() && !publisher_wrapper_.IsRecording())
            {
                publisher_wrapper_.SetVideoPushType(video_push_type_);
                publisher_wrapper_.SetAudioPushType(audio_push_type_);
            }

            publisher_wrapper_.StartRtspStream();

            if (!publisher_wrapper_.IsPreviewing() && !publisher_wrapper_.IsPushingRtmp() && !publisher_wrapper_.IsRecording())
            {
                StartCaptureAvData();
                coroutine_ = StartCoroutine(OnPostVideo());
            }

            btn_rtsp_publisher_.GetComponentInChildren<Text>().text = "停止RTSP";

            btn_rtsp_service_.interactable = false;
        }
    }

    public void btn_get_rtsp_session_numbers_Click()
    {
        if (publisher_wrapper_.IsRtspServiceRunning())
        {
            btn_get_rtsp_session_numbers_.GetComponentInChildren<Text>().text = "RTSP会话数:" + publisher_wrapper_.GetRtspSessionNumbers();
        }
    }

实时录像调用:

public void btn_record_Click()
    {
        if (publisher_wrapper_.IsRecording())
        {
            StopRecord();
            btn_record_.GetComponentInChildren<Text>().text = "开始录像";

            return;
        }

        if (!publisher_wrapper_.IsPreviewing() && !publisher_wrapper_.IsRtspPublisherRunning() && !publisher_wrapper_.IsPushingRtmp())
        {
            publisher_wrapper_.SetVideoPushType(video_push_type_);
            publisher_wrapper_.SetAudioPushType(audio_push_type_);
        }

        if (!publisher_wrapper_.StartRecorder())
        {
            Debug.LogError("调用StartRecorder失败..");
            return;
        }

        btn_record_.GetComponentInChildren<Text>().text = "停止录像";

        if (!publisher_wrapper_.IsPreviewing() && !publisher_wrapper_.IsRtspPublisherRunning() && !publisher_wrapper_.IsPushingRtmp())
        {
            StartCaptureAvData();
            coroutine_ = StartCoroutine(OnPostVideo());
        }
    }

    public void StopRecord()
    {
        if (!publisher_wrapper_.IsPreviewing() && !publisher_wrapper_.IsRtspPublisherRunning() && !publisher_wrapper_.IsPushingRtmp())
        {
            StopCaptureAvData();

            if (coroutine_ != null)
            {
                StopCoroutine(coroutine_);
                coroutine_ = null;
            }
        }

        publisher_wrapper_.StopRecorder();
    }

    private void btn_pause_record_Click()
    {
        if (!publisher_wrapper_.IsPublisherHandleAvailable())
        {
            return;
        }

        String btn_pause_rec_text = btn_pause_record_.GetComponentInChildren<Text>().text;

        if ("暂停录像" == btn_pause_rec_text)
        {
            UInt32 ret = publisher_wrapper_.PauseRecorder(true);

            if ((UInt32)NT.NTSmartPublisherDefine.NT_PB_E_ERROR_CODE.NT_ERC_PB_NEED_RETRY == ret)
            {
                Debug.LogError("暂停录像失败, 请重新尝试!");
                return;
            }
            else if (NTBaseCodeDefine.NT_ERC_OK == ret)
            {
                btn_pause_record_.GetComponentInChildren<Text>().text = "恢复录像";
            }
        }
        else
        {
            UInt32 ret = publisher_wrapper_.PauseRecorder(false);
            if ((UInt32)NT.NTSmartPublisherDefine.NT_PB_E_ERROR_CODE.NT_ERC_PB_NEED_RETRY == ret)
            {
                Debug.LogError("恢复录像失败, 请重新尝试!");
                return;
            }
            else if (NTBaseCodeDefine.NT_ERC_OK == ret)
            {
                btn_pause_record_.GetComponentInChildren<Text>().text = "暂停录像";
            }
        }
    }

实时预览相关:

public void btn_preview_Click()
    {
        if (btn_preview_.GetComponentInChildren<Text>().text.Equals("本地预览"))
        {
            if (!publisher_wrapper_.IsPushingRtmp() && !publisher_wrapper_.IsRtspPublisherRunning() && !publisher_wrapper_.IsRecording())
            {
                publisher_wrapper_.SetVideoPushType(video_push_type_);
            }

            if (publisher_wrapper_.StartPreview())
            {
                btn_preview_.GetComponentInChildren<Text>().text = "停止预览";
            }

            if (!publisher_wrapper_.IsPushingRtmp() && !publisher_wrapper_.IsRtspPublisherRunning() && !publisher_wrapper_.IsRecording())
            {
                StartCaptureAvData();
                coroutine_ = StartCoroutine(OnPostVideo());
            }
        }
        else
        {
            if (!publisher_wrapper_.IsPushingRtmp() && !publisher_wrapper_.IsRtspPublisherRunning() && !publisher_wrapper_.IsRecording())
            {
                StopCaptureAvData();

                if (coroutine_ != null)
                {
                    StopCoroutine(coroutine_);
                    coroutine_ = null;
                }
            }

            publisher_wrapper_.StopPreview();
            btn_preview_.GetComponentInChildren<Text>().text = "本地预览";
        }
    }

总结

Unity平台下RTMP推送、录像、轻量级RTSP服务,在虚拟仿真、医疗、教育等场景下,应用非常广泛。要实现低延迟,除了需要高效率的音视频数据采集,编码和数据投递外,还需要好的直播播放器支持。配合我们的SmartPlayer,可轻松实现毫秒级体验,满足绝大多数应用场景技术诉求。


标签:publisher,Windows,type,wrapper,_.,Unity,btn,NT,轻量级
From: https://blog.51cto.com/daniusdk/8518123

相关文章

  • Win11 SQL Server 安装程序无法通过 Windows Update 服务搜索更新。
    SQLServer安装提示安装程序无法通过windowsupdate服务搜索更新SQLServer安装提示安装程序无法通过windowsupdate服务搜索更新_sqlserver安装程序无法通过windowsupdate-CSDN博客解决方法:手动创建DefaultSetup.ini放置到安装程序文件夹里的x64或者x86目录中,如果De......
  • Windows下mDNS查询API—DnsStartMulticastQuery/DnsStopMulticastQuery的使用
    背景及问题:目前很多局域网设备通过mNDS协议实现互联,IP地址为自动IP段-169.254.x.x,有时候设备厂家提供的API需要通过知晓局域网中的IP地址/设备名,才能连接该设备。这样要求每个软件必须配置设备名或者启动时遍历所有IP(6w+),不是很方便,这时候可以通过mDNS查询,自动拿到设备名,再进行连......
  • windows 文件授权问题
    跨平台可执行权限介绍在类Unix系统(如Mac,Linux)中,执行权限是通过文件的权限位来控制的。而在Windows系统中,执行权限通常取决于文件扩展名和关联的执行程序,所以,当我们在跨平台的开发环境中,可能会遇到这样一个问题:在Windows系统上创建的脚本文件缺乏类Unix系统上的执行权......
  • Windows7下ELectron应用的Input输入框在拼音输入中的失焦问题?
    Windows7下ELectron应用的Input输入框在拼音输入中的失焦问题主要是为了解决虚拟键盘(使用的simple-keyboard)的拼音输入问题(Windows自带的虚拟键盘因为不够方便和美观,所以没有采用;simple-keyboard其实也支持拼音输入,但是更不好用),最后决定通过Koffi(ffi,ffi-napi)调用user32.dll,模拟......
  • C/C++ 实现Windows注册表操作
    Windows注册表(Registry)是Windows操作系统中用于存储系统配置信息、用户设置和应用程序数据的一个集中式数据库。它是一个层次结构的数据库,由键(Key)和值(Value)组成,这些键和值被用于存储各种系统和应用程序的配置信息。以下是注册表的一些基本概念:键(Key):注册表中的数据结构,类似于文......
  • windows安装QT时出现“无法下载存档……”解决办法
    参考windows、Ubuntu安装QT时经常出现“无法下载存档……”解决办法-CSDN博客使用国内源安装:清华大学:https://mirrors.tuna.tsinghua.edu.cn/qt/北京理工大学:http://mirror.bit.edu.cn/qtproject/中国互联网络信息中心:http://mirror.bit.edu.cn/qtproject/步骤:在qt的exe......
  • 更改Windows的远程桌面端口
    摘自:https://cloud.tencent.com/developer/article/1557774方法一:工具下载地址:https://pan.baidu.com/s/1Rt3ZFXY0sOD5okeb9VI3_A方法二:命令1.执行以下命令(将以下所有的36970更为新的要更改的端口后再执行):regadd"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control......
  • Unity 低功耗玉石
    前言曾使用过UE5的substrate系统基于BSDF实现过玉石材质,效果雀氏nice但消耗太高了!因此本篇基于Unity介绍如何模拟透射来实现一个低功耗的玉石材质效果如下本篇同步发布于http://chenglixue.top/index.php/unity/90/总体框架模拟透射光思路因为透射现象是一种光打在物......
  • 【Unity】在游戏中实现虚拟摇杆功能
    使用场景在手机游戏开发中,使用虚拟摇杆控制角色进行移动。虚拟摇杆预制体制作在UI界面添加虚拟摇杆外圈图片在外圈下添加内圈图片将位置置于外圈中心位置添加脚本usingTools;usingUnityEngine;///<summary>///虚拟摇杆管理器///</summary>......
  • 【Unity】伪随机算法之PRD
    概念在游戏制作中通常会有暴击等概率性事件,有两种方法实现,一种就是正常使用随机算法实现,真随机受人品影响,对游戏体验极不友好,所以就提出了伪随机概念,常见的就是PRD算法。P(N)=C*NP为最终概率C为概率增量N为次数随着攻击次数增加概率增加,当暴击时将N重置为1,没有......