目标平台:Android(API 21+)
支持协议:RTSP、RTMP
目录
- 概述
- 环境要求
- 工程文件说明
- 快速集成步骤
- PlayerConfig 配置说明
- 核心 API 说明
- 事件回调说明
- 录像功能
- 视频渲染原理
1. 概述
本文档描述如何在 Android Unity3D 工程中集成大牛直播 SDK,实现 RTSP / RTMP 直播流的拉取、渲染与本地录像功能。
架构总览
帧渲染模型(PULL)
SDK 不主动推送帧数据,而是由 Unity 主线程每帧主动调用GetVideoFrame()拉取,避免跨线程写纹理的问题:
2. 环境要求
| 项目 | 要求 |
|---|---|
| Unity | 2019.4.13f1 或更高 |
| Android API | 最低 21(Android 5.0),推荐 26+ |
| 构建工具 | IL2CPP 或 Mono 均可 |
| 权限 | INTERNET、WRITE_EXTERNAL_STORAGE(录像)、READ_EXTERNAL_STORAGE |
AndroidManifest.xml 权限
<uses-permissionandroid:name="android.permission.INTERNET"/><uses-permissionandroid:name="android.permission.WRITE_EXTERNAL_STORAGE"android:maxSdkVersion="28"/><uses-permissionandroid:name="android.permission.READ_EXTERNAL_STORAGE"android:maxSdkVersion="32"/>Android 10+ 录像路径建议使用
Application.persistentDataPath,无需存储权限。
3. 工程文件说明
各文件职责
| 文件 | 职责 | 是否需要修改 |
|---|---|---|
NTSmartPlayerAPI.cs | JNI 接口定义,1:1 对应 Java 层 | ❌ 通常不改 |
NTPlayerEvent.cs | 事件 ID 常量,对应NTSmartEventID.java | ❌ 不改 |
NTPlayerWrapper.cs | 句柄管理、状态机、配置下发 | ⚠️ 如需扩展参数可改 |
PlayerConfig.cs | 序列化配置字段,Inspector 可调 | ✅ 按需扩展 |
PlayerInstance.cs | 事件处理、YUV 渲染、状态文本 | ✅ 按需扩展 |
PlayerManager.cs | 场景入口,SDK 初始化 | ⚠️ 一般不改 |
UIController.cs | 业务 UI 逻辑 | ✅ 按需修改 |
4. 快速集成步骤
Step 1 — 拷贝 SDK 文件
将大牛直播提供的以下文件放入工程对应目录:
Step 2 — 拷贝 C# 脚本
将本工程Assets/目录下的所有.cs文件拷贝到目标工程。
Step 3 — 配置 Player Settings
Edit → Project Settings → Player → Android:
| 设置项 | 推荐值 |
|---|---|
| Minimum API Level | Android 5.0 (API 21) |
| Scripting Backend | IL2CPP(发布)/ Mono(调试) |
| Target Architecture | ARM64(必选)、ARMv7(可选) |
| Internet Access | Required |
| Write Permission | External (SDCard)(如需 sdcard 录像) |
Step 4 — 搭建场景 | 接线 Inspector
Step 5 — 授权 SDK
在PlayerManager.Awake()初始化后调用:
NTSmartPlayerAPI.NT_U3D_SetSDKClientKey("your_cid","your_key",0);或在PlayerConfig扩展字段统一管理。
Step 6. PlayerConfig 配置说明
PlayerManagerGameObject 上通过 Inspector 配置,也可在代码中访问PlayerManager.Instance.config。
[Serializable]publicclassPlayerConfig{// ── 解码 ──────────────────────────────────────────────────────────────publicboolenableHardwareDecoder=false;// false = 软解(兼容性好);true = 硬解(性能好,需设备支持)// 运行中不可切换,需停止播放后修改再重新开始// ── 缓冲 ──────────────────────────────────────────────────────────────[Range(0,8000)]publicintbufferTimeMs=0;// 0 = 最低延迟模式;建议直播设 0,点播/不稳定网络设 500~2000// ── RTSP 传输 ──────────────────────────────────────────────────────────publicboolrtspUseTcp=false;// false = 优先 UDP,丢包时考虑改 true[Range(1,30)]publicintrtspTimeoutSec=10;// 连接超时(秒)publicboolrtspAutoSwitchTcpUdp=true;// UDP 失败自动切 TCP// ── 音频 ──────────────────────────────────────────────────────────────publicboolmute=false;[Range(0,100)]publicintvolume=100;publicbooluseAudioTrack=true;// true = AudioTrack 模式(推荐)// ── 播放行为 ──────────────────────────────────────────────────────────publicboolfastStartup=true;// 减少起播黑屏时间publicboollowLatencyMode=false;// 超低延迟,会增加丢帧概率[Range(0,270)]publicintrotateDegrees=0;// 顺时针旋转:0/90/180/270// ── 录像 ──────────────────────────────────────────────────────────────publicintrecMaxFileSizeMB=500;// 单个录像文件最大体积(MB),超过后自动切片}6. 核心 API 说明
6.1 播放控制(通过 UIController 或直接调用)
// 创建播放器实例(内部调用 NT_U3D_Open + ApplyConfig)PlayerInstanceplayer=PlayerManager.Instance.CreatePlayer("rtsp://...");// 绑定渲染目标(必须在 StartPlay 前调用)player.SetRenderTarget(rawImage,matI420,matNV21,matNV12);// 开始播放player.StartPlay();// 停止播放player.StopPlay();// 销毁播放器(Close 句柄 + 释放纹理)PlayerManager.Instance.DestroyPlayer();6.2 实时控制(播放中随时可调)
// 静音 / 取消静音player.SetMuteRealtime(true);// 调节音量(0–100)player.SetVolumeRealtime(80);6.3 录像控制
// 开始录像(目录路径,SDK 自动创建)player.StartRecorder("/sdcard/daniulive/Record");// 或使用 persistentDataPath(Android 10+ 推荐,无需权限)player.StartRecorder(Application.persistentDataPath+"/Record");// 停止录像player.StopRecorder();// 查询状态boolisRecording=player.is_recording;6.4 状态查询
boolisPlaying=player.is_playing;boolisRecording=player.is_recording;longhandle=player.handle;// 0 = 未初始化intwidth=player.VideoWidth;intheight=player.VideoHeight;stringstatus=player.StatusText;// 格式化状态文本,直接显示给用户7. 事件回调说明
事件由 SDK 通过UnitySendMessage发到PlayerManager.onNTSmartEvent,再分发到PlayerInstance.HandleEvent()。
消息格式(逗号分隔):
handle,code,param1,param2,param3,param4所有事件 ID 定义在NTPlayerEvent.cs,与 Android SDK 的NTSmartEventID.java完全对应。
事件列表
| 常量 | 值 | 说明 | 参数 |
|---|---|---|---|
STARTED | 0x1000001 | SDK 内部状态机已启动 | — |
CONNECTING | 0x1000002 | 连接中 | — |
CONNECTION_FAILED | 0x1000003 | 连接失败 | — |
CONNECTED | 0x1000004 | 已连接,即将收到视频 | — |
DISCONNECTED | 0x1000005 | 连接断开 | — |
STOP | 0x1000006 | SDK 内部停止(如断流) | — |
RESOLUTION_INFO | 0x1000007 | 视频分辨率 | p1=width, p2=height |
NO_MEDIADATA | 0x1000008 | 长时间收不到媒体数据 | — |
SWITCH_URL | 0x1000009 | 正在切换 URL | — |
CAPTURE_IMAGE | 0x100000A | 快照结果 | p1=0 成功,非0 失败 |
RTSP_STATUS_CODE | 0x100000B | RTSP 状态码上报 | p1=状态码(如 401) |
RECORDER_NEW_FILE | 0x1000021 | 录像开始写入新文件 | p3=完整文件路径 |
RECORDER_FILE_DONE | 0x1000022 | 一个录像文件写完 | p3=完整文件路径 |
START_BUFFERING | 0x1000081 | 开始缓冲 | — |
BUFFERING | 0x1000082 | 缓冲中 | p1=进度百分比(0–100) |
STOP_BUFFERING | 0x1000083 | 缓冲结束,恢复播放 | — |
DOWNLOAD_SPEED | 0x1000091 | 下载速度上报 | 见下方说明 |
DOWNLOAD_SPEED 参数解析
p1 = 下载速度(Byte/s) p2 = 丢包率编码(long): bit31=1 → 高 15 位为前向丢包率 FLR(Q8.8 定点,÷256 得比例) bit15=1 → 低 15 位为综合丢包率 LR(Q8.8 定点,÷256 得比例)示例解析(已在PlayerInstance.HandleEvent中实现):
// 转换为百分比floatflr=((loss>>16)&0x7FFF)/256.0f*100f;// 前向丢包率 %floatlr=(loss&0x7FFF)/256.0f*100f;// 综合丢包率 %8. 录像功能
录像流程
StartRecorder(dir) ├─ NT_U3D_CreateFileDirectory(dir) ← 创建目录 ├─ NT_U3D_SetRecorderDirectory(handle, dir) ├─ NT_U3D_SetRecorderFileMaxSize(handle, MB) ├─ NT_U3D_SetRecorderAudioTranscodeAAC(handle, 1) ← 音频转 AAC(重要) └─ NT_U3D_StartRecorder(handle) └─ 触发 RECORDER_NEW_FILE 事件(p3 = 文件路径)录像与播放的关系
- 录像和播放共享同一个 SDK 句柄,可以同时进行
- 单独录像(不播放):理论上支持,但建议同时启动播放保证视频数据正常拉取
- 停止播放时,若录像仍在进行,录像不会中断
文件切片
recMaxFileSizeMB(默认 500MB)达到后 SDK 自动切片:
- 触发
RECORDER_FILE_DONE(旧文件写完) - 触发
RECORDER_NEW_FILE(新文件开始)
录像路径建议
// Android 10+ 推荐,无需存储权限stringdir=Path.Combine(Application.persistentDataPath,"Record");// 对应路径:/data/data/<包名>/files/Record 或// /sdcard/Android/data/<包名>/files/Record(外部存储)// 旧版 Android(≤9)可用 sdcard 路径,需权限stringdir="/sdcard/daniulive/Record";9. 视频渲染原理
帧格式
| 常量 | 值 | 格式 | 使用场景 |
|---|---|---|---|
FORMAT_I420 | 3 | YUV 4:2:0 Planar | 软解默认输出 |
FORMAT_NV21 | 4 | YUV 4:2:0 Semi-Planar(VU 交错) | 硬解常见 |
FORMAT_NV12 | 5 | YUV 4:2:0 Semi-Planar(UV 交错) | 硬解常见 |
纹理结构
I420(三平面):
yTex_: Texture2D(stride0, height, Alpha8) ← Y 分量 uTex_: Texture2D(stride1, height/2, Alpha8) ← U 分量 vTex_: Texture2D(stride2, height/2, Alpha8) ← V 分量NV21 / NV12(双平面):
yTex_: Texture2D(stride0, height, Alpha8) ← Y 分量 uTex_: Texture2D(width/2, height/2, RG16) ← UV 交错分量Shader 属性
Shader 通过以下属性名接收纹理:
_NT_SDK_Y ← Y 纹理 _NT_SDK_U ← U(或 UV)纹理 _NT_SDK_V ← V 纹理(仅 I420 格式使用)注意:Shader 和 Material 由大牛直播 SDK 提供,不要修改。
附录 A:事件回调格式参考
SDK 通过UnitySendMessage发出的原始消息格式:
"handle,code,param1,param2,param3,param4"在PlayerManager.onNTSmartEvent()中解析:
string[]parts=msg.Split(',');// parts[0] = handle(long,多播放器时用于路由)// parts[1] = code(int,事件 ID)// parts[2] = param1// parts[3] = param2// parts[4] = param3(录像事件中为文件路径)// parts[5] = param4(目前未使用)附录 B:调用时序图
播放时序
UIController.OnPlayClicked() → PlayerManager.CreatePlayer(url) → PlayerInstance.Open(url, config, goName) → NTPlayerWrapper.Open() → NT_U3D_Open() ← 获取句柄 → NT_U3D_Set_Game_Object() ← 注册事件接收对象 → ApplyConfig() ← 一次性下发所有参数 → PlayerInstance.SetRenderTarget(...) ← 绑定渲染目标 → PlayerInstance.StartPlay() → NTPlayerWrapper.StartPlay() → NT_U3D_StartPlay() ← 开始拉流 每帧: PlayerManager.Update() → PlayerInstance.UpdateFrame() → NT_U3D_GetVideoFrame() ← 拉帧 → UploadTextures() ← 上传 YUV 纹理 事件: SDK → UnitySendMessage("PlayerManager", "onNTSmartEvent", msg) → PlayerInstance.HandleEvent(code, p1, p2, p3)停止时序
UIController.OnPlayClicked()(再次点击) → PlayerInstance.StopPlay() → NTPlayerWrapper.StopPlay() → NT_U3D_StopPlay() → UIController.ClearRenderView() → 若 !is_recording → PlayerManager.DestroyPlayer() → PlayerInstance.Close() → NTPlayerWrapper.Close() → NT_U3D_StopPlay() (幂等,已停则跳过) → NT_U3D_StopRecorder() (幂等) → NT_U3D_Close() → DisposeTextures()📎 CSDN官方博客:音视频牛哥-CSDN博客