首页 > 其他分享 >Android平台Unity下如何通过WebCamTexture采集摄像头数据并推送至RTMP服务器或轻量级RTSP服务

Android平台Unity下如何通过WebCamTexture采集摄像头数据并推送至RTMP服务器或轻量级RTSP服务

时间:2024-01-17 20:31:26浏览次数:53  
标签:U3D handle pusher RTSP PB WebCamTexture cam NT 轻量级

技术背景

我们在对接Unity下推送模块的时候,遇到这样的技术诉求,开发者希望在Android的Unity场景下,获取到前后摄像头的数据,并投递到RTMP服务器,实现低延迟的数据采集处理。

在此之前,我们已经有了非常成熟的RTMP推送模块,也实现了Android平台Unity环境下的Camera场景采集,针对这个技术需求,有两种解决方案:

1. 通过针对原生android camera接口封装,打开摄像头,并回调NV12|NV21数据,在Unity环境下渲染即可;

2. 通过WebCamTexture组件,通过系统接口,拿到数据,直接编码推送。

对于第一种方案,涉及到camera接口的二次封装和数据回调,也可以实现,但是不如WebCamTexture组件方便,本文主要介绍下方案2。

WebCamTexture

WebCamTexture继承自Texture,下面是官方资料介绍。

描述

WebCam Texture 是实时视频输入渲染到的纹理。

静态变量

devices

返回可用设备列表。

变量

autoFocusPoint

通过此属性可以设置/获取摄像机的自动焦点。仅在 Android 和 iOS 设备上有效。

deviceName

设置此属性可指定要使用的设备的名称。

didUpdateThisFrame

视频缓冲区是否更新了此帧?

isDepth

如果纹理基于深度数据,则此属性为 true。

isPlaying

返回摄像机当前是否正在运行。

requestedFPS

设置摄像机设备的请求的帧率(以每秒帧数为单位)。

requestedHeight

设置摄像机设备的请求的高度。

requestedWidth

设置摄像机设备的请求的宽度。

videoRotationAngle

返回一个顺时针角度(以度为单位),可以使用此角度旋转多边形以使摄像机内容以正确的方向显示。

videoVerticallyMirrored

返回纹理图像是否垂直翻转。

构造函数

WebCamTexture

创建 WebCamTexture。

公共函数

GetPixel

返回坐标 (x, y) 上的像素颜色。

GetPixels

获取像素颜色块。

GetPixels32

返回原始格式的像素数据。

Pause

暂停摄像机。

Play

启动摄像机。

Stop

停止摄像机。

技术实现

本文以大牛直播SDK的Unity下WebCamTexture采集推送为例,audio的话,可以采集麦克风,或者通过audioclip采集unity场景的audio,video数据的话,可以采集unity场景的camera,或者摄像头数据。

除此之外,还可以设置常规的编码参数,比如软、硬编码,帧率码率关键帧等。

Android平台Unity下如何通过WebCamTexture采集摄像头数据并推送至RTMP服务器或轻量级RTSP服务_大牛直播SDK

先说打开摄像头:

public IEnumerator InitCameraCor()
    {
        // 请求权限
        yield return Application.RequestUserAuthorization(UserAuthorization.WebCam);

        if (Application.HasUserAuthorization(UserAuthorization.WebCam) && WebCamTexture.devices.Length > 0)
        {
            // 创建相机贴图
            web_cam_texture_ = new WebCamTexture(WebCamTexture.devices[web_cam_index_].name, web_cam_width_, web_cam_height_, fps_);
            web_cam_raw_image_.texture = web_cam_texture_;
            web_cam_texture_.Play();

        }
    }

前后摄像头切换

private void SwitchCamera()
    {
        if (WebCamTexture.devices.Length < 1)
            return;

        if (web_cam_texture_ != null && web_cam_texture_.isPlaying)
        {
            web_cam_raw_image_.enabled = false;
            web_cam_texture_.Stop();
            web_cam_texture_ = null;
        }

        web_cam_index_++;
        web_cam_index_ = web_cam_index_ % WebCamTexture.devices.Length;

        web_cam_texture_ = new WebCamTexture(WebCamTexture.devices[web_cam_index_].name, web_cam_width_, web_cam_height_, fps_);
        web_cam_raw_image_.texture = web_cam_texture_;
        web_cam_raw_image_.enabled = true;
        web_cam_texture_.Play();
    }

启动|停止RTMP

private void OnPusherBtnClicked()
    {
        if (is_pushing_rtmp_)
        {
            if(!is_rtsp_publisher_running_)
            {
                StopCaptureAvData();

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

            StopRtmpPusher();

            btn_pusher_.GetComponentInChildren<Text>().text = "推送RTMP";
        }
        else
        {
            bool is_started = StartRtmpPusher();

            if(is_started)
            {
                btn_pusher_.GetComponentInChildren<Text>().text = "停止RTMP";
            
                if(!is_rtsp_publisher_running_)
                {
                    StartCaptureAvData();
                    coroutine_ = StartCoroutine(OnPostVideo());
                }
            }
        }
    }

推送RTMP实现如下:

public bool StartRtmpPusher()
    {
        if (is_pushing_rtmp_)
        {
            Debug.Log("已推送..");   
            return false;
        }

        //获取输入框的url
        string url = input_url_.text.Trim();

        if (!is_rtsp_publisher_running_)
        {
            InitAndSetConfig();
        }

        if (pusher_handle_ == 0) {
             Debug.LogError("StartRtmpPusher, publisherHandle is null..");
            return false;
        }

        NT_PB_U3D_SetPushUrl(pusher_handle_, rtmp_push_url_);

        int is_suc = NT_PB_U3D_StartPublisher(pusher_handle_);

        if (is_suc  == DANIULIVE_RETURN_OK)
        {
            Debug.Log("StartPublisher success..");          
            is_pushing_rtmp_ = true;
        }
        else
        {
            Debug.LogError("StartPublisher failed..");
            return false;
        }

        return true;
    }

对应的InitAndSetConfig()实现如下:

private void InitAndSetConfig()
    {
        if ( java_obj_cur_activity_ == null )
        {
            Debug.LogError("[daniusdk.com]getApplicationContext is null");
            return;
        }

        int audio_opt = 1;
        int video_opt = 3;

        video_width_ = camera_.pixelWidth;
        video_height_ = camera_.pixelHeight;

        pusher_handle_ = NT_PB_U3D_Open(audio_opt, video_opt, video_width_, video_height_);

        if (pusher_handle_ != 0){
            Debug.Log("NT_PB_U3D_Open success");
            NT_PB_U3D_Set_Game_Object(pusher_handle_, game_object_);
        }
        else
        {
            Debug.LogError("NT_PB_U3D_Open failed!");
            return;
        }

        int fps = 30;
        int gop = fps * 2;

        if(video_encoder_type_ == (int)PB_VIDEO_ENCODER_TYPE.VIDEO_ENCODER_HARDWARE_AVC)
        {
            int h264HWKbps = setHardwareEncoderKbps(true, video_width_, video_height_);
            h264HWKbps = h264HWKbps * fps / 25;

            Debug.Log("h264HWKbps: " + h264HWKbps);

            int isSupportH264HWEncoder = NT_PB_U3D_SetVideoHWEncoder(pusher_handle_, h264HWKbps);

            if (isSupportH264HWEncoder == 0) {
                NT_PB_U3D_SetNativeMediaNDK(pusher_handle_, 0);
                NT_PB_U3D_SetVideoHWEncoderBitrateMode(pusher_handle_, 1); // 0:CQ, 1:VBR, 2:CBR
                NT_PB_U3D_SetVideoHWEncoderQuality(pusher_handle_, 39);
                NT_PB_U3D_SetAVCHWEncoderProfile(pusher_handle_, 0x08); // 0x01: Baseline, 0x02: Main, 0x08: High

                // NT_PB_U3D_SetAVCHWEncoderLevel(pusher_handle_, 0x200); // Level 3.1
                // NT_PB_U3D_SetAVCHWEncoderLevel(pusher_handle_, 0x400); // Level 3.2
                // NT_PB_U3D_SetAVCHWEncoderLevel(pusher_handle_, 0x800); // Level 4
                NT_PB_U3D_SetAVCHWEncoderLevel(pusher_handle_, 0x1000); // Level 4.1 多数情况下,这个够用了
                //NT_PB_U3D_SetAVCHWEncoderLevel(pusher_handle_, 0x2000); // Level 4.2

                // NT_PB_U3D_SetVideoHWEncoderMaxBitrate(pusher_handle_, ((long)h264HWKbps)*1300);

                Debug.Log("Great, it supports h.264 hardware encoder!");
            }
        }
        else if(video_encoder_type_ == (int)PB_VIDEO_ENCODER_TYPE.VIDEO_ENCODER_HARDWARE_HEVC)
        {
            int hevcHWKbps = setHardwareEncoderKbps(false, video_width_, video_height_);
            hevcHWKbps = hevcHWKbps*fps/25;

            Debug.Log("hevcHWKbps: " + hevcHWKbps);

            int isSupportHevcHWEncoder = NT_PB_U3D_SetVideoHevcHWEncoder(pusher_handle_, hevcHWKbps);

            if (isSupportHevcHWEncoder == 0) {
                NT_PB_U3D_SetNativeMediaNDK(pusher_handle_, 0);
                NT_PB_U3D_SetVideoHWEncoderBitrateMode(pusher_handle_, 0); // 0:CQ, 1:VBR, 2:CBR
                NT_PB_U3D_SetVideoHWEncoderQuality(pusher_handle_, 39);

                // NT_PB_U3D_SetVideoHWEncoderMaxBitrate(pusher_handle_, ((long)hevcHWKbps)*1200);

                Debug.Log("Great, it supports hevc hardware encoder!");
            }
        }
        else 
        {
            if (is_sw_vbr_mode_) //H.264 software encoder
            {
                int is_enable_vbr = 1;
                int video_quality = CalVideoQuality(video_width_, video_height_, true);
                int vbr_max_bitrate = CalVbrMaxKBitRate(video_width_, video_height_);
                vbr_max_bitrate = vbr_max_bitrate * fps / 25;

                NT_PB_U3D_SetSwVBRMode(pusher_handle_, is_enable_vbr, video_quality, vbr_max_bitrate);
                //NT_PB_U3D_SetSWVideoEncoderSpeed(pusher_handle_, 2);
            }
        }

        NT_PB_U3D_SetAudioCodecType(pusher_handle_, 1);

        NT_PB_U3D_SetFPS(pusher_handle_, fps);

        NT_PB_U3D_SetGopInterval(pusher_handle_, gop);

        if (audio_push_type_ == (int)PB_AUDIO_OPTION.AUDIO_OPTION_MIC_EXTERNAL_PCM_MIXER
            || audio_push_type_ == (int)PB_AUDIO_OPTION.AUDIO_OPTION_TWO_EXTERNAL_PCM_MIXER)
        {
            NT_PB_U3D_SetAudioMix(pusher_handle_, 1);
        }
        else
        {
            NT_PB_U3D_SetAudioMix(pusher_handle_, 0);
        }
    }

数据投递

Color32[] cam_texture = web_cam_texture_.GetPixels32();

        int rowStride = web_cam_texture_.width * 4;
        int length = rowStride * web_cam_texture_.height;

        NT_PB_U3D_OnCaptureVideoRGBA32Data(pusher_handle_, (long)Color32ArrayToIntptr(cam_texture), length, rowStride, web_cam_texture_.width, web_cam_texture_.height,
                                   1, 0, 0, 0, 0);

停止RTMP推送

private void StopRtmpPusher()
    {
        if(!is_pushing_rtmp_)
            return;

        NT_PB_U3D_StopPublisher(pusher_handle_);

        if(!is_rtsp_publisher_running_)
        {
            NT_PB_U3D_Close(pusher_handle_);
            pusher_handle_ = 0;

            NT_PB_U3D_UnInit();
        }

        is_pushing_rtmp_ = false;
    }

轻量级RTSP服务的接口封装,之前blog已多次提到,这里不再赘述。

总结

Unity场景下采集摄像头数据并编码打包推送到RTMP服务器或轻量级RTSP服务,采集获取数据不麻烦,主要难点在于需要控制投递到原生模块的帧率,比如设置30帧,实际采集到的数据是50帧,需要均匀的处理数据投递,达到既流畅延迟又低。配合SmartPlayer播放测试,无论是RTMP推送还是轻量级RTSP服务出来的数据,整体都在毫秒级延迟,感兴趣的开发者,可以跟我沟通交流测试。



标签:U3D,handle,pusher,RTSP,PB,WebCamTexture,cam,NT,轻量级
From: https://blog.51cto.com/daniusdk/9294321

相关文章

  • RTSP/Onvif安防视频云平台EasyNVR迁移盘符后启动异常的问题排查与解决
    EasyNVR安防视频云平台可支持设备通过RTSP/Onvif协议接入,并进行视频流的处理及分发,在视频监控场景中可实现视频实时监控直播、云端录像、云存储、录像检索与回看、告警、级联等,平台能将拉取过来的音视频流转化成适合全平台播放的RTMP、RTSP、hTTP-FLV、Websocket-FLV、HLS、WebRTC......
  • rtsp视频网页播放
    注意:目前都在windows上使用,服务器安装部署多多少少有些问题。1、WebRtcStreamergithub:https://github.com/mpromonet/webrtc-streamer/releases但是经常打不开,如果有需要私信我,因为太忙了没时间放网盘,见谅里面有windows版也有linux版的在本地使用,进入exe目录 启动,默认......
  • Gstreamer Rtspsrc连接大华摄像头失败原因及解决
    先说解决办法sudoapt-getremovegstreamer1.0-plugins-ugly分析过程和原因输入命令gst-launch-1.0rtspsrclocation="rtsp/url"!fakesink终端输出如下SettingpipelinetoPAUSED...PipelineisliveanddoesnotneedPREROLL...Progress:(open)OpeningStre......
  • TinyGPT-V:2.8B参数引领轻量级多模态AI
    前言在当前多模态大型语言模型(MLLM)快速发展的背景下,TinyGPT-V的出现标志着一个重要的技术突破。这款轻量级模型以其2.8B参数的设计,在AI领域引起广泛关注,成为GPT-4V等模型的高效替代方案。Huggingface模型下载:https://huggingface.co/Tyrannosaurus/TinyGPT-VAI快站模型免费加速下载......
  • Android平台RTMP推送|轻量级RTSP服务|GB28181设备接入模块之实时快照保存JPG还是PNG?
    JPG还是PNG?JPG和PNG是两种常见的图片文件格式,在压缩方式、图像质量、透明效果和可编辑性等方面存在显著差异。压缩方式:JPG是一种有损压缩格式,通过丢弃图像数据来减小文件大小,因此可能会损失一些图像细节和质量。而PNG使用的是无损压缩格式,它不会丢失任何原始图像数据,从而保持了图像......
  • 从Bitcask存储模型谈超轻量级KV系统设计与实现
    Bitcask介绍Bitcask是一种“基于日志结构的哈希表”(ALog-StructuredHashTableforFastKey/ValueData)Bitcask最初作为分布式数据库Riak的后端出现,Riak中的每个节点都运行一个Bitcask实例,各自存储其负责的数据。抛开论文,我们先通过一篇博客#Bitcask—alog-struc......
  • 界面组件DevExpress WPF v23.2 - 更轻量级的主题支持
    DevExpressWPFSubscription拥有120+个控件和库,将帮助您交付满足甚至超出企业需求的高性能业务应用程序。通过DevExpressWPF能创建有着强大互动功能的XAML基础应用程序,这些应用程序专注于当代客户的需求和构建未来新一代支持触摸的解决方案。DevExpressWPF控件日前正式发布了......
  • LiveNVR监控流媒体Onvif/RTSP常见问题-如何配置快照目录快照存储默认目录目录如何配置
    LiveNVR监控流媒体Onvif/RTSP常见问题-如何配置快照目录快照存储默认目录目录如何配置?1、快照目录2、指定快照目录3、RTSP/HLS/FLV/RTMP拉流Onvif流媒体服务1、快照目录部署LiveNVR后,配置通道上线后,会在LiveNVR部署的服务器里面存储对应的最新快照,默认的快照目录是LiveNVR解......
  • 简洁、轻量级的 Go API 框架
    本次分享的框架是「gin-api-mono」介绍gin-api-mono前先了解go-gin-apigo-gin-api这是一个基于Gin的API框架,它提供了WEB界面一键安装的方式,让你可以快速启动一个开箱即用的Go项目。无论你是否有项目经验,这个框架都适合作为练手项目使用(新手入门必备)。该框架采用了......
  • GB28181视频监控平台LiteCVR调用rtsp地址返回的IP不正确原因排查
    RTSP(Real-TimeStreamingProtocol)是一种用于控制实时流媒体传输的应用层协议。它被设计用于建立和管理客户端与媒体服务器之间的连接,以便实现实时音频、视频或其他交互式媒体内容的传输。RTSP允许客户端通过发送命令来控制流媒体服务器的播放、暂停、快进、倒带等操作。RTSP支持多......