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

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

 技术背景

我们在对接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,或者摄像头数据。

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

image.gifandroid-unity推送-20240117-修正.jpg

先说打开摄像头:

publicIEnumeratorInitCameraCor()
    {
// 请求权限yieldreturnApplication.RequestUserAuthorization(UserAuthorization.WebCam);
if (Application.HasUserAuthorization(UserAuthorization.WebCam) &&WebCamTexture.devices.Length>0)
        {
// 创建相机贴图web_cam_texture_=newWebCamTexture(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();
        }
    }

image.gif

前后摄像头切换

privatevoidSwitchCamera()
    {
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_=newWebCamTexture(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();
    }

image.gif

启动|停止RTMP

privatevoidOnPusherBtnClicked()
    {
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        {
boolis_started=StartRtmpPusher();
if(is_started)
            {
btn_pusher_.GetComponentInChildren<Text>().text="停止RTMP";
if(!is_rtsp_publisher_running_)
                {
StartCaptureAvData();
coroutine_=StartCoroutine(OnPostVideo());
                }
            }
        }
    }

image.gif

推送RTMP实现如下:

publicboolStartRtmpPusher()
    {
if (is_pushing_rtmp_)
        {
Debug.Log("已推送..");   
returnfalse;
        }
//获取输入框的urlstringurl=input_url_.text.Trim();
if (!is_rtsp_publisher_running_)
        {
InitAndSetConfig();
        }
if (pusher_handle_==0) {
Debug.LogError("StartRtmpPusher, publisherHandle is null..");
returnfalse;
        }
NT_PB_U3D_SetPushUrl(pusher_handle_, rtmp_push_url_);
intis_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..");
returnfalse;
        }
returntrue;
    }

image.gif

对应的InitAndSetConfig()实现如下:

privatevoidInitAndSetConfig()
    {
if ( java_obj_cur_activity_==null )
        {
Debug.LogError("getApplicationContext is null");
return;
        }
intaudio_opt=1;
intvideo_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;
        }
intfps=30;
intgop=fps*2;
if(video_encoder_type_== (int)PB_VIDEO_ENCODER_TYPE.VIDEO_ENCODER_HARDWARE_AVC)
        {
inth264HWKbps=setHardwareEncoderKbps(true, video_width_, video_height_);
h264HWKbps=h264HWKbps*fps/25;
Debug.Log("h264HWKbps: "+h264HWKbps);
intisSupportH264HWEncoder=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:CBRNT_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 4NT_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!");
            }
        }
elseif(video_encoder_type_== (int)PB_VIDEO_ENCODER_TYPE.VIDEO_ENCODER_HARDWARE_HEVC)
        {
inthevcHWKbps=setHardwareEncoderKbps(false, video_width_, video_height_);
hevcHWKbps=hevcHWKbps*fps/25;
Debug.Log("hevcHWKbps: "+hevcHWKbps);
intisSupportHevcHWEncoder=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:CBRNT_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            {
intis_enable_vbr=1;
intvideo_quality=CalVideoQuality(video_width_, video_height_, true);
intvbr_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);
        }
    }

image.gif

数据投递

Color32[] cam_texture=web_cam_texture_.GetPixels32();
introwStride=web_cam_texture_.width*4;
intlength=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);

image.gif

停止RTMP推送

privatevoidStopRtmpPusher()
    {
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;
    }

image.gif

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

总结

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

相关文章
|
2月前
|
编解码 vr&ar 图形学
Unity下如何实现低延迟的全景RTMP|RTSP流渲染
随着虚拟现实技术的发展,全景视频成为新的媒体形式。本文详细介绍了如何在Unity中实现低延迟的全景RTMP或RTSP流渲染,包括环境准备、引入依赖、初始化客户端、解码与渲染、优化低延迟等步骤,并提供了具体的代码示例。适用于远程教育、虚拟旅游等实时交互场景。
77 5
|
27天前
|
Android开发 数据安全/隐私保护 虚拟化
安卓手机远程连接登录Windows服务器教程
安卓手机远程连接登录Windows服务器教程
56 4
|
1月前
|
编解码 vr&ar 图形学
Unity下如何实现低延迟的全景RTMP|RTSP流渲染
随着虚拟现实技术的发展,全景视频逐渐成为新的媒体形式。本文详细介绍了如何在Unity中实现低延迟的全景RTMP或RTSP流渲染,包括环境准备、引入依赖、初始化客户端、解码与渲染、优化低延迟等步骤,并提供了具体的代码示例。适用于远程教育、虚拟旅游等实时交互场景。
32 2
|
2月前
|
Ubuntu Linux Android开发
termux+anlinux+Rvnc viewer来使安卓手机(平板)变成linux服务器
本文介绍了如何在Android设备上安装Termux和AnLinux,并通过这些工具运行Ubuntu系统和桌面环境。
160 2
termux+anlinux+Rvnc viewer来使安卓手机(平板)变成linux服务器
|
3月前
|
Web App开发 网络协议 Android开发
Android平台一对一音视频通话方案大比拼:WebRTC VS RTMP VS RTSP,谁才是王者?
【9月更文挑战第4天】本文详细对比了在Android平台上实现一对一音视频通话时常用的WebRTC、RTMP及RTSP三种技术方案。从技术原理、性能表现与开发难度等方面进行了深入分析,并提供了示例代码。WebRTC适合追求低延迟和高质量的场景,但开发成本较高;RTMP和RTSP则在简化开发流程的同时仍能保持较好的传输效果,适用于不同需求的应用场景。
187 1
|
3月前
|
安全 API 开发工具
Android平台RTMP推送|轻量级RTSP服务如何实现麦克风|扬声器声音采集切换
Android平台扬声器播放声音的采集,在无纸化同屏等场景下,意义很大,早期低版本的Android设备,是没法直接采集扬声器audio的(从Android 10开始支持),所以,如果需要采集扬声器audio,需要先做系统版本判断,添加相应的权限。
|
3月前
|
编解码 开发工具 Android开发
Android平台实现屏幕录制(屏幕投影)|音频播放采集|麦克风采集并推送RTMP或轻量级RTSP服务
Android平台屏幕采集、音频播放声音采集、麦克风采集编码打包推送到RTMP和轻量级RTSP服务的相关技术实现,做成高稳定低延迟的同屏系统,还需要有配套好的RTMP、RTSP直播播放器
|
3月前
|
图形学 iOS开发 Android开发
从Unity开发到移动平台制胜攻略:全面解析iOS与Android应用发布流程,助你轻松掌握跨平台发布技巧,打造爆款手游不是梦——性能优化、广告集成与内购设置全包含
【8月更文挑战第31天】本书详细介绍了如何在Unity中设置项目以适应移动设备,涵盖性能优化、集成广告及内购功能等关键步骤。通过具体示例和代码片段,指导读者完成iOS和Android应用的打包与发布,确保应用顺利上线并获得成功。无论是性能调整还是平台特定的操作,本书均提供了全面的解决方案。
157 0
|
4月前
|
开发者 图形学 API
从零起步,深度揭秘:运用Unity引擎及网络编程技术,一步步搭建属于你的实时多人在线对战游戏平台——详尽指南与实战代码解析,带你轻松掌握网络化游戏开发的核心要领与最佳实践路径
【8月更文挑战第31天】构建实时多人对战平台是技术与创意的结合。本文使用成熟的Unity游戏开发引擎,从零开始指导读者搭建简单的实时对战平台。内容涵盖网络架构设计、Unity网络API应用及客户端与服务器通信。首先,创建新项目并选择适合多人游戏的模板,使用推荐的网络传输层。接着,定义基本玩法,如2D多人射击游戏,创建角色预制件并添加Rigidbody2D组件。然后,引入网络身份组件以同步对象状态。通过示例代码展示玩家控制逻辑,包括移动和发射子弹功能。最后,设置服务器端逻辑,处理客户端连接和断开。本文帮助读者掌握构建Unity多人对战平台的核心知识,为进一步开发打下基础。
153 0
|
4月前
|
图形学 C#
超实用!深度解析Unity引擎,手把手教你从零开始构建精美的2D平面冒险游戏,涵盖资源导入、角色控制与动画、碰撞检测等核心技巧,打造沉浸式游戏体验完全指南
【8月更文挑战第31天】本文是 Unity 2D 游戏开发的全面指南,手把手教你从零开始构建精美的平面冒险游戏。首先,通过 Unity Hub 创建 2D 项目并导入游戏资源。接着,编写 `PlayerController` 脚本来实现角色移动,并添加动画以增强视觉效果。最后,通过 Collider 2D 组件实现碰撞检测等游戏机制。每一步均展示 Unity 在 2D 游戏开发中的强大功能。
220 6

热门文章

最新文章