news 2026/4/16 14:06:05

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/4/16 1:35:19

DiskInfo定时任务自动化巡检脚本

DiskInfo定时任务自动化巡检脚本 在现代AI研发环境中&#xff0c;一次长达数天的模型训练任务可能因为一个看似微不足道的原因而功亏一篑——磁盘空间不足。你有没有遇到过这样的场景&#xff1a;BERT大模型正在收敛&#xff0c;突然进程被终止&#xff0c;日志里只留下一行冰冷…

作者头像 李华
网站建设 2026/4/16 5:20:21

解锁数字化培训新系统,全平台无缝对接的企业学习考试解决方案

温馨提示&#xff1a;文末有资源获取方式传统面授与纸质考试的企业培训模式&#xff0c;正日益凸显其效率低下、组织繁琐、成本高昂且难以追踪效果的短板。企业迫切需要一款能够打破时空限制、实现标准化管理与科学评估的智能化工具。一套功能全面、架构先进的无纸化企业培训考…

作者头像 李华
网站建设 2026/4/16 11:58:03

Anaconda环境隔离避免PyTorch版本冲突

Anaconda环境隔离避免PyTorch版本冲突 在深度学习项目日益复杂的今天&#xff0c;你是否曾遇到这样的场景&#xff1a;刚跑通一个基于 PyTorch 1.12 的旧模型&#xff0c;准备启动新实验时却发现最新版算法依赖 PyTorch 2.7&#xff1f;结果一升级&#xff0c;老项目直接报错—…

作者头像 李华
网站建设 2026/4/15 7:16:25

PyTorch学习率调度器Scheduler实战

PyTorch学习率调度器Scheduler实战 在深度学习的实际训练中&#xff0c;一个常见的困扰是&#xff1a;模型刚开始收敛得很慢&#xff0c;调高学习率又容易后期震荡、无法收敛到最优解。这种“两难”局面几乎每个从业者都经历过——你是否也曾在训练日志里反复调整那个固定的学习…

作者头像 李华
网站建设 2026/4/16 12:00:06

2025年AI写产品的那些事

在整个2025年&#xff0c;主流的AI编程工具几乎用个遍&#xff0c;每次大版本的密集更新&#xff0c;都需要挨个重新尝试&#xff0c;选择最趁手的工具。 没有一款工具完全不能用&#xff0c;也没有哪款工具一直最好用。 像主流的Cursor&#xff0c;Claude Code&#xff0c;Qod…

作者头像 李华
网站建设 2026/4/16 12:05:31

Anaconda多用户共享PyTorch环境配置

Anaconda多用户共享PyTorch环境配置 在高校实验室或AI创业公司中&#xff0c;常常能看到这样的场景&#xff1a;新来的研究生花了整整两天才把PyTorch和CUDA配好&#xff0c;结果跑通代码后发现版本不兼容&#xff1b;团队成员之间因为环境差异导致“在我机器上能跑”的尴尬局面…

作者头像 李华