内网RTSP直连 + 公网RTMP上云:基于SmartMediakit的 Android双引擎架构设计

引言:打破局域网围墙,构建"云边一体"视界

在工业安防、应急指挥或无人机图传等专业场景中,视频传输往往面临着一对矛盾的硬性需求:

  • 现场指挥(边缘侧) :决策者就在现场,需要毫秒级的内网低延迟画面(如 RTSP 直连),任何卡顿都可能延误战机。

  • 远程调度(云端侧):指挥中心远在千里之外,需要将画面稳定推送到公网服务器(RTMP 推流),哪怕网络波动也要保证画面不丢失。

传统的解决方案通常臃肿且昂贵,往往需要配合专用的采集卡和推流网关。但现在,基于 SmartMediakit (大牛直播 SDK) ,我们完全可以利用一台普通的 Android 设备,打破这一硬件壁垒。通过精巧的代码架构,同时实现 "内网轻量级 RTSP 服务""公网 RTMP 推流" 的并行输出。

今天,我们将深度剖析如何构建这套"双引擎"流媒体网关,打造一台无需外部服务器、支持 H.265 硬编码且具备断网自恢复能力的工业级移动终端。


一、 架构深解:从"单点推流"到"云边双模"的异构分发

构建这套系统的最大挑战,在于如何在有限的移动端算力下,同时满足"内网低延迟"与"外网高可靠"的异构需求。如果简单粗暴地开启两个推流实例,CPU 和内存消耗将成倍增加,导致设备发热甚至丢帧。

因此,我们在 LibPublisherWrapper 的设计中,确立了 "一次编码,异构分发 (One Encoding, Heterogeneous Distribution)" 的核心架构原则。通过 SDK 内部的缓冲队列复用机制,实现了数据流的高效流转:

我们将整个数据链路拆解为紧密协作的三层:

1. 全源采集层 (Unified Source)

这是数据的起点。我们利用 Camera2Helper 获取原始的 YUV_420_888 视频帧,配合 NTAudioRecord 采集 PCM 音频数据。

  • 关键点:无论后续分发多少路流,采集动作仅执行一次,确保源头数据的纯净与低功耗。

2. 硬核编码中枢 (Native Engine)

这是系统的"心脏"。原始数据传入 SmartPublisherJniV2 后,直接调用底层的 H.265 (HEVC) 硬编码器

  • 技术突破 :区别于传统方案,这里的编码器生成的 H.265 码流(NAL Units)会被 SDK 内部的 分发器 (Dispatcher) 截获并复制一份,而不是直接发送。这意味着我们在不增加编解码负荷的前提下,获得了两份完全一致的压缩数据。

3. 双轨分发层 (Dual-Track Output)

编码后的数据在此处"分道扬镳",进入两个完全隔离的传输通道:

  • 轨道 A:边缘计算位面 (Edge / RTSP)

    • 机制 :启动内置的轻量级 RTSP Server,绑定本地端口(如 8554)。

    • 作用 :数据不经过任何中间服务器,直接响应内网 NVR 或 VLC 的拉流请求。这不仅实现了毫秒级延迟,更确保了在公网断开的情况下,本地监控业务不受丝毫影响。

  • 轨道 B:云端传输位面 (Cloud / RTMP)

    • 机制:通过 TCP 长连接,主动将数据推送到远端 CDN 或指挥中心(如 Nginx-RTMP/SRS)。

    • 作用:负责数据的远程备份与全网分发。SDK 内部独立的发送线程确保了即使网络抖动,也不会阻塞内网 RTSP 的正常服务。

架构总结 :这种 "采集与编码耦合,传输与协议解耦" 的设计,不仅将 CPU 负载压到了最低,更在物理层面实现了业务的高可用性------内网断不了,外网连得上


二、 核心实战:构建工业级双模引擎

在代码层面,我们将重点攻克三个关键阵地:构建高可用的 RTSP 服务端、实现断网自愈的 RTMP 客户端,以及充分利用硬件性能的 H.264/H.265 编码策略。

1. 引擎一:内网 RTSP 服务端的微服务化

这是边缘节点的核心能力。在 SmartMediakit 中,RTSP Server 被设计为一个独立的"微服务"模块。这意味着它的生命周期可以独立于推流实例存在。

MainActivity.java 中,我们通过两步操作完成"服务启动"与"数据挂载":

java 复制代码
//启动/停止RTSP服务
class ButtonRtspServiceListener implements View.OnClickListener {
	public void onClick(View v) {
		if (!rtsp_server_.empty()) {
			rtsp_server_.reset();
			btnRtspService.setText("启动RTSP服务");
			btnRtspPublisher.setEnabled(false);
			return;
		}

		Log.i(TAG, "onClick start rtsp service..");

		int port = 8554;
		String user_name = null;
		String password = null;
		LibPublisherWrapper.RTSPServer.Handle server_handle = LibPublisherWrapper.RTSPServer.create_and_start_server(libPublisher,
				port, user_name, password);

		if (null == server_handle) {
			Log.e(TAG, "启动rtsp server失败! 请检查设置的端口是否被占用!");
			return;
		}

		rtsp_server_.reset(server_handle);

		btnRtspService.setText("停止RTSP服务");
		btnRtspPublisher.setEnabled(true);
	}
}

//发布/停止RTSP流
class ButtonRtspPublisherListener implements View.OnClickListener {
	public void onClick(View v) {
		if (stream_publisher_.is_rtsp_publishing()) {
			stopRtspPublisher();

			btnRtspPublisher.setText("发布RTSP流");
			btnGetRtspSessionNumbers.setEnabled(false);
			btnRtspService.setEnabled(true);
			return;
		}

		Log.i(TAG, "onClick start rtsp publisher..");

		InitAndSetConfig();

		String rtsp_stream_name = "stream1";
		stream_publisher_.SetRtspStreamName(rtsp_stream_name);
		stream_publisher_.ClearRtspStreamServer();

		stream_publisher_.AddRtspStreamServer(rtsp_server_.get_native());

		if (!stream_publisher_.StartRtspStream()) {
			stream_publisher_.try_release();
			Log.e(TAG, "调用发布rtsp流接口失败!");
			return;
		}

		startAudioRecorder();
		startLayerPostThread();

		btnRtspPublisher.setText("停止RTSP流");
		btnGetRtspSessionNumbers.setEnabled(true);
		btnRtspService.setEnabled(false);
	}
}

//获取RTSP会话数
class ButtonGetRtspSessionNumbersListener implements View.OnClickListener {
	public void onClick(View v) {
		if (rtsp_server_.is_running()) {
			int session_numbers = rtsp_server_.get_client_session_number();
			Log.i(TAG, "GetRtspSessionNumbers: " + session_numbers);

			PopRtspSessionNumberDialog(session_numbers);
		}
	}
}

技术洞察:这种"服务"与"流"分离的设计非常巧妙。你可以在不重启 APP 的情况下,动态地向 RTSP Server 增加或移除不同的流(如切换前后摄像头流),极大地提升了系统的灵活性。

安卓轻量级RTSP服务采集摄像头和麦克风实现IPC功能

2. 引擎二:RTMP 远程推流的鲁棒性设计

公网环境复杂多变,因此 RTMP 推流端必须具备极强的鲁棒性。代码中不仅要处理简单的连接,更要通过 SDK 内置的重连机制应对 4G/5G 的弱网抖动。

java 复制代码
class ButtonStartPushListener implements View.OnClickListener {
	public void onClick(View v) {
		if (stream_publisher_.is_rtmp_publishing()) {
			stopPush();

			btnRTMPPusher.setText("推送RTMP");
			return;
		}

		Log.i(TAG, "onClick start push rtmp..");
		InitAndSetConfig();

		String rtmp_pusher_url = "rtmp://192.168.0.102:1935/hls/stream1";

		PopPusherUrlDialog(rtmp_pusher_url);

		Log.i(TAG, rtmp_pusher_url);



		if (!stream_publisher_.SetURL(rtmp_pusher_url))
			Log.e(TAG, "Failed to set publish stream URL..");

		boolean start_ret = stream_publisher_.StartPublisher();
		if (!start_ret) {
			stream_publisher_.try_release();
			Log.e(TAG, "Failed to start push stream..");
			return;
		}

		startAudioRecorder();
		startLayerPostThread();

		btnRTMPPusher.setText("停止推送 ");

	}
}

安卓采集摄像头和麦克风实现低延迟RTMP推流

3. 性能压舱石:H.264/H.265 硬编码

如何在同时开启 RTSP 和 RTMP 的情况下,保证手机不发烫?答案是:除软编码外,全面拥抱 H.264/H.265 (HEVC)硬编。

代码中实现了一套智能的"硬编码协商"机制,能够根据当前分辨率动态估算最佳码率,并优先启用 HEVC 编码器。

java 复制代码
private boolean initialize_publisher(SmartPublisherJniV2 lib_publisher, long handle, int width, int height, int fps, int gop) {
	if (null == lib_publisher) {
		Log.e(TAG, "initialize_publisher lib_publisher is null");
		return false;
	}

	if (0 == handle) {
		Log.e(TAG, "initialize_publisher handle is 0");
		return false;
	}

	if (videoEncodeType == 1) {
		int kbps = LibPublisherWrapper.estimate_video_hardware_kbps(width, height, fps, true);
		Log.i(TAG, "h264HWKbps: " + kbps);
		int isSupportH264HWEncoder = lib_publisher.SetSmartPublisherVideoHWEncoder(handle, kbps);
		if (isSupportH264HWEncoder == 0) {
			lib_publisher.SetNativeMediaNDK(handle, 0);
			lib_publisher.SetVideoHWEncoderBitrateMode(handle, 1); // 0:CQ, 1:VBR, 2:CBR
			lib_publisher.SetVideoHWEncoderQuality(handle, 39);
			lib_publisher.SetAVCHWEncoderProfile(handle, 0x08); // 0x01: Baseline, 0x02: Main, 0x08: High

			// lib_publisher.SetAVCHWEncoderLevel(handle, 0x200); // Level 3.1
			// lib_publisher.SetAVCHWEncoderLevel(handle, 0x400); // Level 3.2
			// lib_publisher.SetAVCHWEncoderLevel(handle, 0x800); // Level 4
			lib_publisher.SetAVCHWEncoderLevel(handle, 0x1000); // Level 4.1 多数情况下,这个够用了
			//lib_publisher.SetAVCHWEncoderLevel(handle, 0x2000); // Level 4.2

			// lib_publisher.SetVideoHWEncoderMaxBitrate(handle, ((long)h264HWKbps)*1300);

			Log.i(TAG, "Great, it supports h.264 hardware encoder!");
		}
	} else if (videoEncodeType == 2) {
		int kbps = LibPublisherWrapper.estimate_video_hardware_kbps(width, height, fps, false);
		Log.i(TAG, "hevcHWKbps: " + kbps);
		int isSupportHevcHWEncoder = lib_publisher.SetSmartPublisherVideoHevcHWEncoder(handle, kbps);
		if (isSupportHevcHWEncoder == 0) {
			lib_publisher.SetNativeMediaNDK(handle, 0);
			lib_publisher.SetVideoHWEncoderBitrateMode(handle, 1); // 0:CQ, 1:VBR, 2:CBR
			lib_publisher.SetVideoHWEncoderQuality(handle, 39);

			// libPublisher.SetVideoHWEncoderMaxBitrate(handle, ((long)hevcHWKbps)*1200);

			Log.i(TAG, "Great, it supports hevc hardware encoder!");
		}
	}

	boolean is_sw_vbr_mode = true;
	//H.264 software encoder
	if (is_sw_vbr_mode) {
		int is_enable_vbr = 1;
		int video_quality = LibPublisherWrapper.estimate_video_software_quality(width, height, true);
		int vbr_max_kbps = LibPublisherWrapper.estimate_video_vbr_max_kbps(width, height, fps);
		lib_publisher.SmartPublisherSetSwVBRMode(handle, is_enable_vbr, video_quality, vbr_max_kbps);
	}

	if (is_pcma_) {
		lib_publisher.SmartPublisherSetAudioCodecType(handle, 3);
	} else {
		lib_publisher.SmartPublisherSetAudioCodecType(handle, 1);
	}

	lib_publisher.SetSmartPublisherEventCallbackV2(handle, new EventHandlerPublisherV2().set(handler_, record_executor_));

	lib_publisher.SmartPublisherSetSWVideoEncoderProfile(handle, 3);

	lib_publisher.SmartPublisherSetSWVideoEncoderSpeed(handle, 2);

	lib_publisher.SmartPublisherSetGopInterval(handle, gop);

	lib_publisher.SmartPublisherSetFPS(handle, fps);

	// lib_publisher.SmartPublisherSetSWVideoBitRate(handle, 600, 1200);

	boolean is_noise_suppression = true;
	lib_publisher.SmartPublisherSetNoiseSuppression(handle, is_noise_suppression ? 1 : 0);

	boolean is_agc = false;
	lib_publisher.SmartPublisherSetAGC(handle, is_agc ? 1 : 0);

	int echo_cancel_delay = 0;
	lib_publisher.SmartPublisherSetEchoCancellation(handle, 1, echo_cancel_delay);

	return true;
}

三、 进阶:如何保证"双模"的高可靠性?

在工业级应用中,我们不仅要"能用",还要"稳"。

1. 独立的状态管理

LibPublisherWrapper.java 中,我们维护了独立的状态位,确保 RTSP 和 RTMP 的生命周期互不干扰,以RTMP的为例,我们的封装如下:

java 复制代码
public boolean StartPublisher() {
	if (!check_native_handle())
		return false;

	if (is_rtmp_publishing()) {
		Log.e(TAG, "already publishing rtmp, native_handle:" + get());
		return false;
	}

	int ret = lib_publisher_.SmartPublisherStartPublisher(get());
	if (ret != OK) {
		Log.e(TAG, "call SmartPublisherStartPublisher failed, native_handle:" + get() + ", ret:" + ret);
		return false;
	}

	write_lock_.lock();
	try {
		this.is_rtmp_publishing_ = true;
	} finally {
		write_lock_.unlock();
	}

	Log.i(TAG, "call SmartPublisherStartPublisher OK, native_handle:" + get());
	return true;
}

public boolean StopPublisher() {
	if (!check_native_handle())
		return false;

	if (!is_rtmp_publishing()) {
		Log.w(TAG, "it's not publishing rtmp, native_handle:" + get());
		return false;
	}

	boolean is_need_call = false;
	write_lock_.lock();
	try {
		if (this.is_rtmp_publishing_) {
			this.is_rtmp_publishing_ = false;
			is_need_call = true;
		}
	} finally {
		write_lock_.unlock();
	}

	if (is_need_call)
		lib_publisher_.SmartPublisherStopPublisher(get());

	return true;
}

2. 智能重连与网络感知

对于 RTMP 推流,SDK 内部实现了基于状态机的重连机制。当 4G 信号抖动时,SDK 会通过 Event 回调通知上层,并尝试自动重连,无需上层业务代码干预。


四、 全景复盘:构建"三位一体"的立体监控网

让我们跳出代码,将视角投射到真实的工业现场。当这套系统运行在无人巡检车或应急指挥终端上时,它实际上构建了一张"三位一体"的立体监控网:

  1. 内网操作层(操作员视角 - RTSP)

    • 场景 :中控室操作员通过内网 PC(VLC 或专用客户端)拉取 rtsp://IP:8554/stream1

    • 体验 :得益于 <200ms 的极致低延迟,操作员看到的画面几乎与现场同步。这意味着在远程操控机械臂或驾驶无人车时,能实现"眼到手到"的精准反馈,彻底消除了传统推流方案中"操作滞后"的安全隐患。

  2. 公网监管层(管理者视角 - RTMP)

    • 场景:远在北京的指挥官打开微信小程序或 Web 后台,拉取云端分发的 RTMP/HLS 流。

    • 体验:彻底打破了传统 RTMP 直播 3-5 秒延迟的瓶颈。得益于 SmartMediakit 播放端极致的追帧策略与低延迟缓冲算法,系统在 4G 网络下不仅能通过 H.265 硬解维持 1080P 高清画质,更将端到端同步时延稳定控制在 200ms 以内。这让管理者实现了真正的"所见即所得",跨地域调度指挥不再受时间滞后的干扰。

  3. 本地兜底层(黑匣子视角 - MP4)

    • 场景:现场网络突然遭受强干扰,RTSP 和 RTMP 同时中断。

    • 体验 :系统的本地录像模块依然在静默运行,将关键影像完整写入 SD 卡。这道"最后防线"确保了核心数据零丢失,为后续的事故回溯与证据留存提供了坚实的物理保障。

总结 :这不仅仅是一次分身,而是**"实时操控"、"远程监管"、"数据留存"**三种业务形态在一台普通 Android 设备上的完美融合。


五、 结语:定义移动端边缘流媒体的新标准

通过将 RTSP 内网服务RTMP 云端推流 有机融合,我们实际上是在普通的 Android 终端上,完成了一次从"采集设备"到 "微型边缘计算网关" 的质变。

这套方案代表了一种去中心化的流媒体架构新思路:

  • 双模分发能力 (Local + Cloud) :既能满足现场指挥的毫秒级直连 ,又能保障云端调度的全网覆盖

  • 极致算力释放 (HEVC/H.265) :通过底层硬编码技术,在移动芯片上压榨出服务器级的压缩效率

  • 高可用异构架构 (Decoupled Services) :服务与推流的解耦,确保了在极端网络环境下,系统依然具备单点生存能力

对于开发者而言,掌握这套架构,意味着你手中握有了一把通往智慧工地远程医疗移动单兵执法 等高端场景的钥匙。代码即基石,连接即未来,愿这套方案能成为你构建下一代工业级音视频应用的坚实起点。

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

相关推荐
二等饼干~za8986682 小时前
碰一碰发视频系统源码开发搭建--技术分享
java·运维·服务器·重构·django·php·音视频
EasyCVR3 小时前
视频汇聚平台EasyCVR筑牢消防领域可视化监控防线
运维·人工智能·音视频
专业开发者4 小时前
2020 年国际消费电子展(CES 2020):真无线耳机强势席卷音频品类
物联网·音视频
Hui Baby4 小时前
视频字幕自动生成探秘
音视频
free-elcmacom4 小时前
深度学习<2>从“看单帧”到“懂故事”:视频模型的帧链推理,藏着机器读懂时间的秘密
人工智能·python·深度学习·音视频
TESmart碲视5 小时前
深入解析:DisplayLink 是如何把“视频”变成 USB 数据再还原成显示信号的?
计算机外设·音视频·tesmart
山海青风5 小时前
藏文TTS介绍:4 神经网络 TTS 的随机性与自然度
人工智能·python·神经网络·音视频
KOYUELEC光与电子请努力拼搏~13 小时前
ESS艾西斯音频解码芯片ESS9039PRO特点
音视频
音视频牛哥18 小时前
【深度选型】RTSP超低延迟播放器:自研陷阱与成熟模块的效益分析
音视频·rtsp播放器·低延迟rtsp播放器·linux rtsp播放器·windows rtsp播放器·安卓rtsp播放器·ios rtsp播放器