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,或者摄像头数据。

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

先说打开摄像头:

    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;}//获取输入框的urlstring 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("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: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!");}}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: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{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服务出来的数据,整体都在毫秒级延迟,感兴趣的开发者,可以跟我沟通交流测试。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/629854.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

大模型学习之书生·浦语大模型5——基于LMDeploy大模型量化部署实践

目录 大模型部署背景 LMDeploy部署 量化 TurboMind API server 动手实践环节

LCR 173. 点名(二分)

一、题目描述 LCR 173. 点名 某班级 n 位同学的学号为 0 ~ n-1。点名结果记录于升序数组 records。假定仅有一位同学缺席&#xff0c;请返回他的学号。 示例 1: 输入: records [0,1,2,3,5] 输出: 4示例 2: 输入: records [0, 1, 2, 3, 4, 5, 6, 8] 输出: 7 二、题目解析…

flink1.14.5使用CDH6.3.2的yarn提交作业

使用CDH6.3.2安装了hadoop集群&#xff0c;但是CDH不支持flink的安装&#xff0c;网上有CDH集成flink的文章&#xff0c;大都比较麻烦&#xff1b;但其实我们只需要把flink的作业提交到yarn集群即可&#xff0c;接下来以CDH yarn为基础&#xff0c;flink on yarn模式的配置步骤…

2024-01-16 创业日记-关于用户需求分析-思考

摘要: 创业公司一个非常大的问&#xff0c;就是制造的产品没有市场。创始人所设想的需求&#xff0c;不是真正的市场上所缺失的&#xff0c;导致开发出的产品不被市场认可。 那么需求是如何确定的呢&#xff1f;需求是怎么来的呢&#xff1f;自己所设想的用户需求&#xff0c…

时间函数 localtime localtime_r

时间函数 localtime localtime_r localtime 和 localtime_r 的函数功能&#xff1a; converts the calendar time timep to broken-time representation 在调用 localtime 和 localtime_t 函数时&#xff0c;需特别注意&#xff1a; localtime 是不可重入函数&#xff0c;非线…

Resemble Enhance音频失真损坏修复AI工具:一个开源语音超分辨率AI模型

Resemble Enhance是一款强大的音频处理工具&#xff0c;可以将嘈杂的录音转化为清晰而有力的声音&#xff0c;为用户提供更优质的听觉体验。这个工具不仅可以有效去除录音中的各种噪声和杂音&#xff0c;还能够恢复音频失真并扩展音频带宽&#xff0c;使原本的声音听起来更加清…

高级分布式系统-第10讲 分布式控制系统

高级分布式系统汇总&#xff1a;高级分布式系统目录汇总-CSDN博客 自动化是关于一切人造系统自动、智能、自主、高效和安全运行的科学与技术 计算机控制技术是实现自动化的主要方法和手段 分布式控制技术是伴随着机器大工业生产而诞生的特殊计算机控制技术 计算机控制系统 …

echarts业务中常用属性设置记录

1.legend计算占比&#xff08;解决占比总和不等于100%的问题&#xff09; //在data中定义两个字段 total:0, znum:0 //计算上面两个值 this.data.forEach(val > this.total parseInt(val.value)); for (let i 0; i < nv.length; i) {if (i ! nv.length - 1) {this.znum…

Qt/QML编程之路:使用camera摄像头(35)

汽车应用中,camera起到了越来越多的作用,数字化的作用,这点无可争议,而作为GUI设计工具,如何让Camera类的应用能更好的发挥作用呢? You can use Camera to capture images and movies from a camera, and manipulate the capture and processing settings that get appl…

网站防御爬虫攻击有哪些方式

很多网站都深受爬虫困扰&#xff0c;网站在被爬虫大量抓取的的时候经常容易被爬虫把服务器资源抓崩了&#xff0c;有的时候&#xff0c;同行也会来爬取我们网站进行数据采集&#xff0c;影响我们站点的原创性&#xff0c;那么如何进行相对应的防护还是非常重要的&#xff01; …

mysql新增用户密码控制局域网访问权限

方法一、通过navicat中sql语句新增 CREATE USER usernamelocalhost IDENTIFIED BY password; GRANT ALL PRIVILEGES ON *.* TO usernamelocalhost WITH GRANT OPTION; FLUSH PRIVILEGES;把其中的username和password改成自己的即可 如果将上面的localhost改成%&#xff0c;则这…

从CISC到RISC-V:揭开指令集的面纱

对于大多数同学来说&#xff0c;计算机或智能手机的运行似乎就像魔法一样神奇。你可能知道它们内部都是一些复杂的电子组件&#xff0c;比如CPU、内存等等&#xff0c;但这些组件是如何协同工作&#xff0c;让我们可以在电脑上打字&#xff0c;或者在手机上看视频呢&#xff1f…

1.环境部署

1.虚拟机安装redhat8系统 这个其实很简单&#xff0c;但是有一点小细节需要注意。 因为我的电脑是 16核心的&#xff0c;所以选择内核16&#xff0c;可以最大发挥虚拟机的性能 磁盘选择SATA&#xff0c;便于后期学习 将一些没用的设备移除 选择安装redhat 8 时间选择上海 选择…

Python 语法糖

一、基本概念 语法糖&#xff0c;可以理解为&#xff1a;“甜蜜” 的便捷语法。 它是编程语言为程序提供的更简洁、更易读的语法实现的语法结构&#xff0c;它并不影响语言的功能&#xff0c;仅仅是一种更便捷的书写方式。 这就像你制作蛋糕时&#xff0c;使用现代烤箱而不是…

无法解析的外部符号ShellExecuteExW

问题情况 在QT使用&#xff1a;ShellExecuteEx时遇上这么一个错误&#xff1a;遇上这么一个错误&#xff1a; error: LNK2019: 无法解析的外部符号 __imp_ShellExecuteExW *ReportService.obj&#x1f44e; error: LNK2019: 无法解析的外部符号 __imp_ShellExecuteExW&#x…

源码部署Milvus(三)成功解决vscode调试milvus源码时间超时问题!

作者有话说 继上周成功调试后出现时间超时问题&#xff0c;且想定位create_index等如何触发milvus底层实现文件&#xff0c;总是报错Timeout&#xff0c;这周碎片化抽空解决此问题&#xff0c;猜测可能是vscode配置的调试环境有问题&#xff0c;果不其然&#xff01; 报错 g…

React16源码: React中调度之scheduleWork的源码实现

scheduleWork 1 &#xff09; 概述 在 ReactDOM.render, setState, forceUpdate 这几个方法最终都调用了 scheduleWork 这个方法 在 scheduleWork 当中&#xff0c;它需要去找到更新对应的 FiberRoot 节点 在使用 ReactDOM.render 的时候&#xff0c;传给 scheduleWork 的就是…

RocketMQ源码阅读-Producer消息发送

RocketMQ源码阅读-Producer消息发送 1. 从单元测试入手2. 启动过程3. 同步消息发送过程4. 异步消息发送过程5. 小结 Producer是消息的生产者。 Producer和Consummer对Rocket来说都是Client&#xff0c;Server是Broker。 客户端在源码中是一个单独的Model&#xff0c;目录为rock…

ASP.NET Core 的 Web Api 实现限流 中间件

Microsoft.AspNetCore.RateLimiting 中间件提供速率限制&#xff08;限流&#xff09;中间件。 它是.NET 7 以上版本才支持的中间件&#xff0c;刚看了一下&#xff0c;确实挺好用&#xff0c;下面给大家简单介绍一下&#xff1a; RateLimiterOptionsExtensions 类提供下列用…

本地浏览器查看--服务器上运行的可视化,如tensorboard

特别提醒&#xff1a;注意看本地端和服务器端 一、服务器终端运行 tensorboard --logdirlogs --port6008二、本地终端运行--以建立端口映射 ssh -L 6008:localhost:6008 -p 443 root服务器IP解释&#xff1a;将服务器的6008端口映射到本地的6008端口&#xff0c;-p 443 …