Unity下如何实现RTMP或RTSP流播放和录制

news/2024/5/21 0:35:32/文章来源:https://blog.csdn.net/renhui1112/article/details/128425609

技术背景

在探讨Unity平台RTMP或RTSP直播流数据播放和录制之前,我们先简单回顾下RTSP或RTMP直播流数据在Unity平台的播放流程:

  1. 通过Native RTSP或RTSP直播播放SDK回调RGB/YUV420/NV12等其中的一种未压缩的图像格式;
  2. Unity下创建相应的RGB/YUV420等Shader;
  3. Unity从各个平台获取图像数据来填充纹理即可。

Unity3D环境下,我们之前已覆盖以下模块:

  • Windows平台RTMP直播推送模块(采集Unity窗体、摄像头或屏幕);
  • Windows平台RTMP|RTSP直播播放模块;
  • Linux平台RTMP直播推送模块(采集Unity窗体、Unity声音);
  • Linux平台RTMP|RTSP直播播放模块;
  • Android平台RTMP直播推送模块(采集Unity窗体、麦克风或Unity声音);
  • Android平台RTMP|RTSP直播播放模块;
  • iOS平台RTMP|RTSP直播播放模块。

技术实现

本文主要介绍流数据录制,实际上,录制相对直播播放来说,更简单一些,毕竟不需要解码绘制。本文以Windows平台下Unity为例,介绍下流数据的录制流程,无图无真相:

 

开始录像

我们可指定文件录制规则,如是否需要录制纯音频或纯视频、单个录制文件大小限制、文件录制目录等参数设定,并设置录像回调事件:

/** SmartPlayerWinMono.cs** WebSite: http://daniusdk.com* Created by DaniuSDK on 2018/05/10.*/
private void StartRecorder(int sel)
{Debug.Log("StartRecorder++, sel: " + sel);if (videoctrl[sel].is_recording_){Debug.Log("StartRecorder, already started.. sel: " + sel);return;}if (!videoctrl[sel].is_playing_){if (!OpenPlayerHandle(sel)){Debug.LogError("call OpenPlayerHandle failed..");return;}}bool is_rec_video = true;bool is_rec_audio = true;NTSmartPlayerSDK.NT_SP_SetRecorderVideo(videoctrl[sel].player_handle_, is_rec_video ? 1 : 0);NTSmartPlayerSDK.NT_SP_SetRecorderAudio(videoctrl[sel].player_handle_, is_rec_audio ? 1 : 0);String rec_dir = "D:\\Rec";     //录像目录可自行指定String rec_name_file_prefix_= "daniu" + sel.ToString();UInt32 max_file_size = 200 * 1024; // 单位是KByte, 默认200MBbool is_append_date = true;bool is_append_time = true;bool is_audio_transcode_aac = true;UInt32 ret = NTSmartPlayerSDK.NT_SP_SetRecorderDirectory(videoctrl[sel].player_handle_, rec_dir);if (NT.NTBaseCodeDefine.NT_ERC_OK != ret){Debug.LogError("设置录像目录失败,请确保目录存在且是英文目录");return;}NTSmartPlayerSDK.NT_SP_SetRecorderFileMaxSize(videoctrl[sel].player_handle_, max_file_size);NT_SP_RecorderFileNameRuler rec_name_ruler = new NT_SP_RecorderFileNameRuler();rec_name_ruler.type_ = 0;rec_name_ruler.file_name_prefix_ = rec_name_file_prefix_;rec_name_ruler.append_date_ = is_append_date ? 1 : 0;rec_name_ruler.append_time_ = is_append_time ? 1 : 0;NTSmartPlayerSDK.NT_SP_SetRecorderFileNameRuler(videoctrl[sel].player_handle_, ref rec_name_ruler);NTSmartPlayerSDK.NT_SP_SetRecorderAudioTranscodeAAC(videoctrl[sel].player_handle_, is_audio_transcode_aac ? 1 : 0);videoctrl[sel].record_call_back_ = new SP_SDKRecorderCallBack(NT_SP_SDKRecorderCallBack);NTSmartPlayerSDK.NT_SP_SetRecorderCallBack(videoctrl[sel].player_handle_, IntPtr.Zero, videoctrl[sel].record_call_back_);videoctrl[sel].set_record_call_back_ = new VideoControl.SetRecordCallBack(RecordCallBack);if (NT.NTBaseCodeDefine.NT_ERC_OK != NTSmartPlayerSDK.NT_SP_StartRecorder(videoctrl[sel].player_handle_)){Debug.LogError("call NT_SP_StartRecorder failed..");return;}videoctrl[sel].is_recording_ = true;
}

这里需要注意的是,OpenPlayerHandle()针对播放端的设定,同一个实例,播放和录像仅需设置一次,具体实现如下:

private bool OpenPlayerHandle(int sel)
{if (videoctrl[sel].player_handle_ != IntPtr.Zero)return true;window_handle_ = IntPtr.Zero;if (videoctrl[sel].player_handle_ == IntPtr.Zero){videoctrl[sel].player_handle_ = new IntPtr();UInt32 ret_open = NTSmartPlayerSDK.NT_SP_Open(out videoctrl[sel].player_handle_, window_handle_, 0, IntPtr.Zero);if (ret_open != 0){videoctrl[sel].player_handle_ = IntPtr.Zero;Debug.LogError("call NT_SP_Open failed, sel: " + sel);return false;}}videoctrl[sel].event_call_back_ = new SP_SDKEventCallBack(NT_SP_SDKEventCallBack);NTSmartPlayerSDK.NT_SP_SetEventCallBack(videoctrl[sel].player_handle_, window_handle_, videoctrl[sel].event_call_back_);videoctrl[sel].sdk_event_call_back_ = new VideoControl.SetEventCallBack(SDKEventCallBack);if (IntPtr.Zero == videoctrl[sel].player_handle_)return false;/* ++ 播放前参数配置可加在此处 ++ */int play_buffer_time_ = 100;NTSmartPlayerSDK.NT_SP_SetBuffer(videoctrl[sel].player_handle_, play_buffer_time_);                 //设置buffer timeint is_using_tcp = 0;        //TCP模式NTSmartPlayerSDK.NT_SP_SetRTSPTcpMode(videoctrl[sel].player_handle_, is_using_tcp);int timeout = 10;NTSmartPlayerSDK.NT_SP_SetRtspTimeout(videoctrl[sel].player_handle_, timeout);int is_auto_switch_tcp_udp = 1;NTSmartPlayerSDK.NT_SP_SetRtspAutoSwitchTcpUdp(videoctrl[sel].player_handle_, is_auto_switch_tcp_udp);Boolean is_mute_ = false;NTSmartPlayerSDK.NT_SP_SetMute(videoctrl[sel].player_handle_, is_mute_ ? 1 : 0);                    //是否启动播放的时候静音int is_fast_startup = 1;NTSmartPlayerSDK.NT_SP_SetFastStartup(videoctrl[sel].player_handle_, is_fast_startup);              //设置快速启动模式Boolean is_low_latency_ = false;NTSmartPlayerSDK.NT_SP_SetLowLatencyMode(videoctrl[sel].player_handle_, is_low_latency_ ? 1 : 0);    //设置是否启用低延迟模式//设置旋转角度(设置0, 90, 180, 270度有效,其他值无效)int rotate_degrees = 0;NTSmartPlayerSDK.NT_SP_SetRotation(videoctrl[sel].player_handle_, rotate_degrees);int volume = 100;NTSmartPlayerSDK.NT_SP_SetAudioVolume(videoctrl[sel].player_handle_, volume);  //设置播放音量, 范围是[0, 100], 0是静音,100是最大音量, 默认是100// 设置上传下载报速度int is_report = 0;int report_interval = 2;NTSmartPlayerSDK.NT_SP_SetReportDownloadSpeed(videoctrl[sel].player_handle_, is_report, report_interval);//设置播放URLNTSmartPlayerSDK.NT_SP_SetURL(videoctrl[sel].player_handle_, videoctrl[sel].playback_url_);/* -- 播放前参数配置可加在此处 -- */return true;
}

停止录像

停止录制的时候,如果当前流数据没有在播放,可直接调用Close接口,释放实例并把player_handle置null。

private void StopRecorder(int sel)
{Debug.Log("StopRecorder++, sel: " + sel);if (videoctrl[sel].player_handle_ == IntPtr.Zero){return;}NTSmartPlayerSDK.NT_SP_StopRecorder(videoctrl[sel].player_handle_);videoctrl[sel].is_recording_ = false;if (!videoctrl[sel].is_playing_){NTSmartPlayerSDK.NT_SP_Close(videoctrl[sel].player_handle_);videoctrl[sel].player_handle_ = IntPtr.Zero;}
}

开始播放

接下来,我们回顾下播放流程,播放端我们以回调YUV数据为例:

public void StartPlayer(int sel)
{Debug.Log("StartPlayer++, sel: " + sel);if (videoctrl[sel].is_playing_){Debug.Log("StartPlayer, already started.. sel: " + sel);return;}lock (videoctrl[sel].frame_lock_){videoctrl[sel].cur_video_frame_ = null;}if (!videoctrl[sel].is_recording_){if (!OpenPlayerHandle(sel)){Debug.LogError("call OpenPlayerHandle failed, sel:" + sel);return;}}if (is_enable_hardware_decoder_){NTSmartPlayerSDK.NT_SP_SetH264HardwareDecoder(videoctrl[sel].player_handle_, is_support_h264_hardware_decoder_ ? 1 : 0, 0);NTSmartPlayerSDK.NT_SP_SetH265HardwareDecoder(videoctrl[sel].player_handle_, is_support_h265_hardware_decoder_ ? 1 : 0, 0);}else{NTSmartPlayerSDK.NT_SP_SetH264HardwareDecoder(videoctrl[sel].player_handle_, 0, 0);NTSmartPlayerSDK.NT_SP_SetH265HardwareDecoder(videoctrl[sel].player_handle_, 0, 0);}//video frame callback (YUV/RGB)videoctrl[sel].sdk_video_frame_call_back_ = new VideoControl.SetVideoFrameCallBack(SDKVideoFrameCallBack);videoctrl[sel].video_frame_call_back_ = new SP_SDKVideoFrameCallBack(NT_SP_SetVideoFrameCallBack);NTSmartPlayerSDK.NT_SP_SetVideoFrameCallBack(videoctrl[sel].player_handle_, (Int32)NT.NTSmartPlayerDefine.NT_SP_E_VIDEO_FRAME_FORMAT.NT_SP_E_VIDEO_FRAME_FROMAT_I420, window_handle_, videoctrl[sel].video_frame_call_back_);UInt32 flag = NTSmartPlayerSDK.NT_SP_StartPlay(videoctrl[sel].player_handle_);if (flag == DANIULIVE_RETURN_OK){videoctrl[sel].is_need_get_frame_ = true;Debug.Log("NT_SP_StartPlay succeed, sel:" + sel);}else{videoctrl[sel].is_need_get_frame_ = false;Debug.LogError("NT_SP_StartPlay failed, sel:" + sel);}videoctrl[sel].is_playing_ = true;
}

停止播放

停止播放的时候和录像一样,如果当前流数据没有在录制状态,可直接调用Close接口,释放实例并把player_handle置null。

private void StopPlayer(int sel)
{Debug.Log("StopPlayer++, sel: " + sel);videoctrl[sel].is_need_get_frame_ = false;videoctrl[sel].is_need_init_texture_ = false;if (videoctrl[sel].player_handle_ == IntPtr.Zero){return;}UInt32 flag = NTSmartPlayerSDK.NT_SP_StopPlay(videoctrl[sel].player_handle_);if (flag == DANIULIVE_RETURN_OK){Debug.Log("call NT_SP_StopPlay succeed, sel: " + sel);}else{Debug.LogError("call NT_SP_StopPlay failed, sel: " + sel);}if (!videoctrl[sel].is_recording_){NTSmartPlayerSDK.NT_SP_Close(videoctrl[sel].player_handle_);videoctrl[sel].player_handle_ = IntPtr.Zero;}videoctrl[sel].is_playing_ = false;
}

总结

Unity下实现RTMP或RTSP流录像,相对来说技术难度不大,甚至不需要操作UI即可完成。需要注意的是,Windows平台录制特别是多路录制受限于磁盘IO写入等,如果录制多路,可能会存在硬件性能瓶颈。此外,录像和播放,共用一个player实例即可,播放和录像,设计需要考虑逻辑完全分离,比如可只播放或只录制,亦或播放的过程中随时录制,或者录像的过程中随时播放。

 

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

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

相关文章

c# winform 重启自己 简单实现

1.情景 有些时候,系统会出问题,问题原因很难排除,但是重启问题就能修正,这时候我们就需要在一个检测到问题的时机,让系统进行一次重启。 2.代码 using System; using System.Windows.Forms;namespace 程序重启自己 …

IDEA创建kotlin项目

今天新建了一个kotlin项目,竟然不能导入jar包,原因是新建项目的时候,选择了kotlin作为Gradle的开发语音,kotlin语音里面,下面这行配置识别不了: implementation fileTree(dir: libs, include: [*.jar])所以…

Selenium 常用函数总结

Seleninum作为自动化测试的工具,自然是提供了很多自动化操作的函数, 下面列举下个人觉得比较常用的函数,更多可见官方文档: 官方API文档: http://seleniumhq.github.io/selenium/docs/api/py/api.html 1) 定位元素 f…

Fragment

Fragment简单认识 1.简介 在大屏幕设备上支持更加动态和灵活的UI设计就是一种卡片的设计思路一个Activity可以有多个Fragment,一个Fragment可以被多个Activity使用可以进行动态的添加,替换和删除Fragment有着自己的生命周期,同时受到Activity…

Shiro之授权

授权 1、角色认证 在controller层创建接口 使用shiro中的注解RequiresRoles指定能访问的角色名称 /*** 登录认证角色*/ RequiresRoles("admin") GetMapping("/userLoginRoles") ResponseBody public String userLoginRoles(){System.out.println("…

微信键盘终于正式发布,张小龙说:其目的并不是为了抢夺输入法市场

自从2021年1月份,张小龙在微信公开课透露:微信将上线属于自己的专属输入法,到现在已经快2年过了。 今天终于正式发布了,下面我们一起来体验下。 1、安装 打开App Store,输入“微信键盘”,点击获取就可以…

基于Springboot+Mybatis+mysql+element-vue高校就业管理系统

基于SpringbootMybatismysqlelement-vue高校就业管理系统一、系统介绍二、功能展示1.用户登陆注册2.个人信息(学生端)3.查看企业岗位信息(学生端)4.我的应聘(学生端)5.学生信息管理(辅导员)6.三方协议书审核(辅导员&am…

一文读懂Linux内核处理器架构中的栈

栈是什么?栈有什么作用? 首先,栈 (stack) 是一种串列形式的 数据结构。这种数据结构的特点是 后入先出 (LIFO, Last In First Out),数据只能在串列的一端 (称为:栈顶 top) 进行 推入 (push) 和 弹出 (pop) 操作。根据…

自学编程和计算机科班出身的差别在哪里

前不久逛知乎的时候看到一个问题:自学编程和计算机科班出身的差别在哪里? 自己回答了一下,获得了比较多的点赞和评论,在这里也分享给大家。 985 通信专业学长,转行程序员,聊一聊我的看法:说一千…

YOLOV3论文学习

YOLOv3论文链接:https://pjreddie.com/media/files/papers/YOLOv3.pdf 综述 一、摘要 1、320*320的YOLOv3推理时间22ms,准确率28.2mAP,达到了SSD的精确度,推理速度却快了三倍。 2、基于.5mAp Iou 的YOLOv3的检测效果还比较不错&a…

Doo Prime 为泰国 SOS 儿童村送温暖,公益有起点爱心无疆界

一年一度的圣诞节即将来临,在这欢乐的时刻, Doo Prime 荣幸地宣布 ,向泰国 SOS 儿童村捐赠了 35 万泰铢 ( 约合 1.23 万美元 ),作为泰国南部城市合艾府 SOS 儿童村的房屋翻修费用。 Doo Prime 希望 SOS 儿童村的孩子们都能在温馨…

Android入门第55天-在Android里使用OKHttp组件访问网络资源

简介 今天的课程开始进入高级课程类了,我们要开始接触网络协议、设备等领域编程了。在今天的课程里我们会使用OKHttp组件来访问网络资源而不是使用Android自带的URLConnection。一个是OKHttp组件更方便二个是OKHttp组件本身就带有异步回调功能。 下面就进入课程。…

(Java)车厢重组

车厢重组一、题目描述二、输入格式三、输出格式四、样例(1)样例输入(2)样例输出五、正确代码六、思路一、题目描述 在一个旧式的火车站旁边有一座桥,其桥面可以绕河中心的桥墩水平旋转。一个车站的职工发现桥的长度最…

Fragment案例

Fragment案例 1.案例要求 框架布局项目难点:1 导航栏的实现,显示导航按钮、切换Fragment 2 每个Fragment的创建、显示 3 Fragment的跳转(从新闻列表到新闻详情,再返回) 涉及的技术:用RadioGroup及RadioButt…

【源码共读】Vite 项目自动添加 eslint 和 prettier

vite-pretty-lint库是一个为Vite创建的Vue或React项目初始化eslint和prettier的库。 该库的目的是为了让开发者在创建项目时,不需要手动配置eslint和prettier,而是通过vite-pretty-lint库来自动配置。 源码地址: vite-pretty-lintgithub1s…

3ds Max:标准几何体

三维软件中一般有许多非常复杂的命令,能够完成非常复杂的图形运算,但其实许多绚丽的图形也是由最基本的几何体构成,许多复杂的命令也是基本的运算程序的集合,就像是砖块,构成了复杂的大厦。任何一个几何体,…

【Linux】缓冲区/磁盘inode/动静态库制作

目录 一、缓冲区 1、缓冲区的概念 2、缓冲区的意义 3、缓冲区刷新策略 4、同一份代码,打印结果不同 5、仿写FILE 5.1myFILE.h 5.2myFILE.c 5.3main.c 6、内核缓冲区 二、了解磁盘 1、磁盘的物理结构 2、磁盘的存储结构 2.1磁盘的定位 3、磁盘的抽象…

Sentinel系列——概述与安装1-1

Sentinel系列——概述与安装1-1概述服务雪崩解决方法基本概念资源规则Sentinel 是如何工作的安装Sentinel下载地址启动修改sentinel启动参数设置启动端口设置用户名密码概述 随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式、多语言…

java开发的环保网站垃圾分类系统源码

本项目是基于springboot开发的小区垃圾分类的监管系统。为了更好的督促小区业主更好的进行垃圾分类和垃圾投放,本系统设计了一套积分奖罚机制,如果业主此次投放垃圾符合分类要求则加10积分,不符合则扣除200积分,积分不够需要进行扫…

Zookeeper 4 Zookeeper JavaAPI 操作 4.1 Curator 介绍 4.2 Curator API 常用操作【建立连接】

Zookeeper 【黑马程序员Zookeeper视频教程,快速入门zookeeper技术】 文章目录Zookeeper4 Zookeeper JavaAPI 操作4.1 Curator 介绍4.1.1 Curator 介绍4.2 Curator API 常用操作4.2.1 Curator API 常用操作4.2.2 建立连接4 Zookeeper JavaAPI 操作 4.1 Curator 介绍…