news 2026/5/1 9:46:58

Android 端构建高性能 RTSP 转 RTMP|轻量级RTSP服务 网关:透传与二次编码深度实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Android 端构建高性能 RTSP 转 RTMP|轻量级RTSP服务 网关:透传与二次编码深度实践

在移动端音视频应用中,将外部 RTSP 摄像头流(如海康、大华 IPC)接入手机,并将其转发至 RTMP 服务器(如 Nginx、SRS)或在本地开启 RTSP 服务供其他设备拉流,是一个非常典型的“单兵作战”或“移动网关”场景。

本文结合SmartPlayer的核心代码,深入拆解基于大牛直播 SDK 实现的两种核心转发模式:数据透传(Pass-through)二次编码(Transcoding),并探讨如何利用内置的轻量级 RTSP 服务模块实现本地分发。

核心架构:Player 与 Publisher 的联动

SmartPlayer.java中,我们维护了两个核心对象:

  1. Player (拉流端):LibPlayerWrapper mPlayerWrapper,负责拉取 RTSP 流,根据模式决定是只输出编码数据还是解码出 YUV 数据。

  2. Publisher (推流端):LibPublisherWrapper mStreamPublisher,负责接收来自 Player 的数据,并将其推送到 RTMP 服务器或发布为本地 RTSP 流。

1. 模式切换的“总开关”

我们在 UI 上通过Spinner控件控制转发模式,这直接决定了底层数据流的走向。

// SmartPlayer.java 中 setupSpinners 方法片段 mSpTransferMode.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { boolean newRelayMode = (position == 0); // 0: 透传模式, 1: 二次编码 if (isRelayMode != newRelayMode) { // ... 状态重置逻辑 isRelayMode = newRelayMode; Log.i(TAG, "传输模式切换为: " + (isRelayMode ? "透传" : "二次编码")); } if (isRelayMode) { // 透传模式:直接转发编码数据 (mAudioOpt=2, mVideoOpt=2 表示编码后数据) mAudioOpt = 2; mVideoOpt = 2; } else { // 二次编码模式:需要重新采集或处理数据 (mVideoOpt=3 表示层叠加模式) mAudioOpt = 0; mVideoOpt = 3; } } // ... });

模式一:极速转发——透传模式 (Pass-through)

核心逻辑:透传模式下,Player 不进行解码,Publisher 不进行编码。Player 将拉取到的 H.264/H.265 和 AAC 数据包原封不动地通过回调抛出,Publisher 接收后直接打包发送。这种模式 CPU 占用极低,延迟最小。

数据流向:

RTSP Source->Player Demuxer->Callback (Encoded Data)->Publisher Muxer->RTMP/RTSP Dest

代码实现:

SmartPlayer.java中,当isRelayMode为 true 时,我们不设置ExternalRender(因为不需要 YUV 数据),而是注册PlayerVideoDataCallbackPlayerAudioDataCallback

视频数据回调实现:

// SmartPlayer.java 内部类 PlayerVideoDataCallback class PlayerVideoDataCallback implements NTVideoDataCallback { private WeakReference<LibPublisherWrapper> publisher_; private int video_buffer_size = 0; private ByteBuffer video_buffer_ = null; public PlayerVideoDataCallback(LibPublisherWrapper publisher) { if (publisher != null) publisher_ = new WeakReference<>(publisher); } @Override public ByteBuffer getVideoByteBuffer(int size) { // 动态分配复用 Buffer,减少 GC if (size < 1) return null; if (size <= video_buffer_size && video_buffer_ != null) { return video_buffer_; } video_buffer_size = size + 1024; video_buffer_size = (video_buffer_size + 0xf) & (~0xf); video_buffer_ = ByteBuffer.allocateDirect(video_buffer_size); return video_buffer_; } @Override public void onVideoDataCallback(int ret, int video_codec_id, int sample_size, int is_key_frame, long timestamp, int width, int height, long presentation_timestamp) { if (video_buffer_ == null) return; LibPublisherWrapper publisher = publisher_.get(); if (null == publisher) return; if (!publisher.is_publishing()) return; video_buffer_.rewind(); // 核心:直接投递编码后的数据给 Publisher publisher.PostVideoEncodedData(video_codec_id, video_buffer_, sample_size, is_key_frame, timestamp, presentation_timestamp); } }

同理,音频也通过PlayerAudioDataCallback直接透传PostAudioEncodedData


模式二:灵活处理——二次编码模式 (Transcoding)

核心逻辑:二次编码模式下,Player 将视频解码为 YUV (I420) 数据。我们在中间环节可以对 YUV 数据进行处理(如添加水印、文字、裁剪等),然后交给 Publisher 重新编码发送。

数据流向:

RTSP Source->Player Decoder->YUV Callback->LayerPostThread(加水印)->Publisher Encoder->RTMP/RTSP Dest

代码实现:

1. 启动播放时的配置:startPlayLogic中,如果不是透传模式,我们需要创建一个I420ExternalRender来接收解码后的数据。

// SmartPlayer.java private boolean startPlayLogic() { if (isPlaying) return false; // ... 初始化校验 NTExternalRender externalRender = null; if (!isRelayMode) { Log.i(TAG, "二次编码模式: 设置 ExternalRender"); // 创建 I420 渲染器,关联 Publisher 列表 externalRender = new I420ExternalRender(mPublisherArray); } // 启动播放器,传入 externalRender boolean ret = mPlayerWrapper.StartPlayer( mSurfaceView, null, externalRender, 1, // render_scale_mode true, // is_fast_startup isHardwareDecoder, true // is_low_latency ); // ... }

2. 数据的桥接与渲染:I420ExternalRender实现了NTExternalRender接口,它从 Player 获取解码后的 I420 数据,并调用 Publisher 的PostLayerImageI420ByteBuffer方法。这里配合LayerPostThread实现了强大的图层叠加功能。

// SmartPlayer.java 内部类 I420ExternalRender private static class I420ExternalRender implements NTExternalRender { // ... 成员变量省略 @Override public void onNTRenderFrame(int width, int height, long timestamp) { if (y_buffer_ == null) return; y_buffer_.rewind(); u_buffer_.rewind(); v_buffer_.rewind(); if (publisher_list_ != null) { for (WeakReference<LibPublisherWrapper> ref : publisher_list_) { LibPublisherWrapper p = ref.get(); if (p != null && !p.empty()) { // 将解码后的 YUV 数据投递到视频层 (Layer 0) // 后续 LayerPostThread 会在此基础上叠加水印层 p.PostLayerImageI420ByteBuffer(0, 0, 0, y_buffer_, 0, y_row_bytes_, u_buffer_, 0, u_row_bytes_, v_buffer_, 0, v_row_bytes_, width_, height_, 0, 0, 0, 0, 0, 0); } } } } // ... 内存分配逻辑省略 }

3. 水印与图层合成:在二次编码模式下,我们启动了LayerPostThread,它负责定期更新动态水印(如时间戳、图片 Logo)。

// SmartPlayer.java private boolean startPushRtmpLogic() { initAndSetConfig(); // ... SetURL logic if (!isRelayMode) { // 非透传模式下,需要启动音频录制(因为不透传音频流)和图层处理线程 startAudioRecorder(); startLayerPostThread(); } return true; }

进阶功能:内置轻量级 RTSP 服务

除了推送到 RTMP 服务器,SmartPlayer还能利用SmartPublisherJniV2开启一个本地的 RTSP Server。这意味着手机不仅是转发器,还是一个标准的 RTSP 流媒体服务器。

1. 开启 RTSP 服务

首先需要初始化并启动 RTSP 服务监听端口(如 18554)。

// SmartPlayer.java private void handleRtspService() { if (isRTSPServiceRunning) { // ... 停止服务逻辑 } else { mRtspServerHandle = libPublisher.OpenRtspServer(0); if (mRtspServerHandle == 0) return; libPublisher.SetRtspServerPort(mRtspServerHandle, 18554); if (libPublisher.StartRtspServer(mRtspServerHandle, 0) == 0) { isRTSPServiceRunning = true; // ... UI 更新 } // ... Error handling } }

2. 发布流到 RTSP 服务

启动服务后,我们需要将 Publisher 当前的流(无论是透传来的还是二次编码的)“挂载”到 RTSP 服务上。

// SmartPlayer.java private void handleRtspPublish() { if (mStreamPublisher.is_rtsp_publishing()) { // ... 停止发布逻辑 } else { initAndSetConfig(); // 设置 RTSP 流名称,例如 stream1 // 最终地址将是 rtsp://ip:18554/stream1 mStreamPublisher.SetRtspStreamName("stream1"); mStreamPublisher.ClearRtspStreamServer(); // 将流关联到之前创建的 Server Handle mStreamPublisher.AddRtspStreamServer(mRtspServerHandle); if (mStreamPublisher.StartRtspStream()) { if (!isRelayMode) { // 二次编码模式需采集 startAudioRecorder(); startLayerPostThread(); } // ... UI 更新 } } }

总结

通过SmartPlayer的实现,我们清晰地看到了大牛直播 SDK 在处理音视频转发时的灵活性:

  • 透传模式是性能首选,适合纯粹的网关应用,CPU 占用极低,画质无损。

  • 二次编码模式是功能首选,虽然牺牲了一定的 CPU 资源,但换来了对视频内容的完全控制权(水印、AI 处理接口等)。

  • RTSP 服务模块则让 Android 设备具备了服务端的各个能力,极大地扩展了应用场景。

开发者可以根据实际的业务需求(是对延迟敏感,还是需要添加品牌 Logo),在代码中灵活切换isRelayMode,构建最适合自己的移动端音视频应用。

📎 CSDN官方博客:音视频牛哥-CSDN博客

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/1 1:49:00

SSH密钥登录PyTorch容器:增强安全性与便捷性

SSH密钥登录PyTorch容器&#xff1a;增强安全性与便捷性 在深度学习项目日益复杂、团队协作频繁的今天&#xff0c;如何快速搭建一个既安全又高效的开发环境&#xff0c;成为每个AI工程师必须面对的问题。尤其是在使用GPU资源进行模型训练时&#xff0c;既要保证计算性能的充分…

作者头像 李华
网站建设 2026/5/1 4:05:03

综合布线品牌排名哪家技术强

综合布线品牌排名哪家技术强 在当今数字化时代&#xff0c;综合布线系统作为网络基础设施的重要组成部分&#xff0c;其技术水平直接影响着网络的性能和稳定性。众多综合布线品牌在市场上竞争激烈&#xff0c;究竟哪家技术更强呢&#xff1f;让我们来深入分析。 大唐风暴&…

作者头像 李华
网站建设 2026/5/1 4:47:24

Markdown TOC自动生成PyTorch文档目录

Markdown TOC 自动生成 PyTorch 文档目录 在现代 AI 工程实践中&#xff0c;一个常见的挑战是&#xff1a;如何在快速迭代的模型开发中&#xff0c;同时保证环境的一致性和文档的专业性&#xff1f;我们经常遇到这样的场景——团队成员各自配置本地环境&#xff0c;结果“在我机…

作者头像 李华
网站建设 2026/4/30 8:33:36

PHP开源订水平台源码系统,支持手动派单或自动分配

温馨提示&#xff1a;文末有资源获取方式在本地生活服务全面线上化的今天&#xff0c;送水行业也需借助数字工具提升竞争力。选择一套合适的在线订水系统&#xff0c;是业务升级的第一步。本文将深入解析一款备受推崇的PHP开源订水平台源码&#xff0c;看它如何为您的送水业务注…

作者头像 李华
网站建设 2026/4/30 15:13:30

vue2大文件上传组件的源码解析与扩展开发

要求&#xff1a;免费,开源,技术支持 技术&#xff1a;百度webuploader&#xff0c;分块&#xff0c;切片&#xff0c;断点续传&#xff0c;秒传&#xff0c;MD5验证&#xff0c;纯JS实现&#xff0c;支持第三方软件集成 前端&#xff1a;vue2,vue3,vue-cli,html5,webuploader …

作者头像 李华
网站建设 2026/4/30 10:12:15

Jupyter Notebook分栏显示PyTorch代码与输出

Jupyter Notebook 分栏显示 PyTorch 代码与输出 在高分辨率显示器普及的今天&#xff0c;开发者却仍在为“写一行代码、滚三屏看结果”而烦恼。尤其是在深度学习实验中&#xff0c;一个训练循环的日志动辄上百行&#xff0c;图像生成结果藏在文档底部&#xff0c;调试时来回翻…

作者头像 李华