Android平台RTMP推流实战全解:从初始化到流畅上传的技术闭环
🧭 一、RTMP推流,移动端的刚需方案
在直播/监控/采集类移动应用中,将采集到的音视频内容实时推送至服务器是最基础但最关键的一环。而 RTMP(Real-Time Messaging Protocol)凭借其稳定性、部署成熟度、低延迟等优势,依然是移动端推流主力协议。
但仅仅“推起来”远远不够——移动端推流,尤其是 Android 平台,面临更多挑战:
编解码兼容复杂(软编、硬编适配各厂商 SoC)
推流稳定性高要求(频繁前后台切换、摄像头切换)
网络环境不可控(Wi-Fi、4G频繁抖动)
推流配置灵活性(码率、帧率、GOP、Profile 组合设置)
功耗与资源控制
大牛直播SDK 在 Android 平台上推出的 RTMP 推送模块,正是基于上述痛点,围绕“可靠性、兼容性、低延迟、易接入”设计的一套可商用化技术方案。
🏗️ 二、架构设计:模块组件拆解
Android平台采集屏幕和扬声器推送RTMP整体延迟测试
大牛直播SDK Android 推流模块核心由三部分构成:
-
Native 编解码内核(SmartPublisherJniV2.java)
-
实现音视频采集、编码、RTMP打包与传输;
-
支持软编、硬编(MediaCodec);
-
可设置参数覆盖大部分直播场景;
-
-
状态封装控制层(LibPublisherWrapper.java)
-
封装 native 层句柄/状态管理;
-
提供线程安全的发布、关闭、录制控制接口;
-
管理推送标志状态(Event状态回调);
-
-
业务调度层(MainActivity.java)
-
管理 UI、事件响应、权限申请;
-
实现相机切换、分辨率配置、水印设置;
-
发起推流、设置推流 URL、初始化编码参数;
-
🎬 三、推流过程详解:从打开到关闭的调用链路

以下是简化后的核心调用流程示例,帮助开发者快速理解 RTMP 推送的实际应用结构:
/*
* MainActivity.java
* Created by daniusdk.com
*/
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;
}
private void InitAndSetConfig() {
if (null == libPublisher)
return;
if (!stream_publisher_.empty())
return;
Log.i(TAG, "InitAndSetConfig video width: " + video_width_ + ", height" + video_height_ + " imageRotationDegree:" + cameraImageRotationDegree_);
int audio_opt = 1;
long handle = libPublisher.SmartPublisherOpen(context_, audio_opt, 3, video_width_, video_height_);
if (0==handle) {
Log.e(TAG, "sdk open failed!");
return;
}
Log.i(TAG, "publisherHandle=" + handle);
int fps = 25;
int gop = fps * 3;
initialize_publisher(libPublisher, handle, video_width_, video_height_, fps, gop);
stream_publisher_.set(libPublisher, handle);
}
🔍 实际调用流程参考(MainActivity.java 摘录)
在 ButtonStartPushListener 的按钮监听器中,整合了初始化、配置和推送逻辑:
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 base_url = "rtmp://player.daniulive.com:1935/hls/stream";
String rtmp_pusher_url = base_url + (int)(System.currentTimeMillis() % 1000000);
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("停止推送 ");
}
}
🔧 四、系统特性与工程化设计亮点
🛠️ 参数灵活性
-
支持软硬编码动态切换;
-
支持自定义码率、GOP、帧率、Profile、硬编码级别;
-
支持纯音频、纯视频、音视频三种推流模式;
🎨 水印与镜像支持
watermarkSelctor = (Spinner) findViewById(R.id.watermarkSelctor);
final String[] watermarks = new String[]{"图片水印", "全部水印", "文字水印", "不加水印"};
ArrayAdapter<String> adapterWatermark = new ArrayAdapter<String>(this,
android.R.layout.simple_spinner_item, watermarks);
adapterWatermark.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
watermarkSelctor.setAdapter(adapterWatermark);
watermarkSelctor.setSelection(3,true);
watemarkType = 3; //默认不加水印
watermarkSelctor.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view,
int position, long id) {
watemarkType = position;
Log.i(TAG, "[水印类型]Currently choosing: " + watermarks[position] + ", watemarkType: " + watemarkType);
if (layer_post_thread_ != null) {
layer_post_thread_.enableText(isHasTextWatermark());
layer_post_thread_.enablePicture(isHasPictureWatermark());
layer_post_thread_.update_layers();
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
支持动态文字水印、PNG水印、画面水平/垂直镜像等视觉处理。
🔊 音频处理
-
实时音量调节:
SetInputAudioVolume() -
静音控制:
SetMute() -
自动增益与降噪:
SmartPublisherSetAGC()、SetNoiseSuppression()
📸 快照与录像能力
支持推流过程中实时快照、录像,并支持:
-
设置录像文件目录:
SetRecorderDirectory() -
单文件最大大小:
SetRecorderFileMaxSize() -
录像过程暂停/恢复控制
void ConfigRecorderParam() {
if (null == libPublisher)
return;
if (null == recDir || recDir.isEmpty())
return;
int ret = libPublisher.SmartPublisherCreateFileDirectory(recDir);
if (ret != 0) {
Log.e(TAG, "Create record dir failed, path:" + recDir);
return;
}
if (!stream_publisher_.SetRecorderDirectory(recDir)) {
Log.e(TAG, "Set record dir failed , path:" + recDir);
return;
}
// 更细粒度控制录像的, 一般情况无需调用
//libPublisher.SmartPublisherSetRecorderAudio(publisherHandle, 0);
//libPublisher.SmartPublisherSetRecorderVideo(publisherHandle, 0);
if (!stream_publisher_.SetRecorderFileMaxSize(200)) {
Log.e(TAG, "SmartPublisherSetRecorderFileMaxSize failed.");
return;
}
}
class ButtonStartRecorderListener implements View.OnClickListener {
public void onClick(View v) {
if (layer_post_thread_ != null)
layer_post_thread_.update_layers();
if (stream_publisher_.is_recording()) {
stopRecorder();
if (stream_publisher_.empty())
ConfigControlEnable(true);
btnStartRecorder.setText("实时录像");
btnPauseRecorder.setText("暂停录像");
btnPauseRecorder.setEnabled(false);
isPauseRecording = true;
return;
}
Log.i(TAG, "onClick start recorder..");
InitAndSetConfig();
ConfigRecorderParam();
boolean start_ret = stream_publisher_.StartRecorder();
if (!start_ret) {
stream_publisher_.try_release();
Log.e(TAG, "Failed to start recorder.");
return;
}
startAudioRecorder();
ConfigControlEnable(false);
startLayerPostThread();
btnStartRecorder.setText("停止录像");
btnPauseRecorder.setEnabled(true);
isPauseRecording = true;
}
}
class ButtonPauseRecorderListener implements View.OnClickListener {
public void onClick(View v) {
if (stream_publisher_.is_recording()) {
if (isPauseRecording) {
boolean ret = stream_publisher_.PauseRecorder(true);
if (ret) {
isPauseRecording = false;
btnPauseRecorder.setText("恢复录像");
} else {
Log.e(TAG, "Pause recorder failed..");
}
} else {
boolean ret = stream_publisher_.PauseRecorder(false);
if (ret) {
isPauseRecording = true;
btnPauseRecorder.setText("暂停录像");
} else {
Log.e(TAG, "Resume recorder failed..");
}
}
}
}
}
📦 五、集成优势:不只是Demo,而是系统级模块
大牛直播SDK 推流模块优势不仅是“功能多”,而是专为工程系统适配:
| 能力点 | 说明 |
|---|---|
| 跨平台兼容 | Android/iOS/Windows/Linux 四平台统一接口 |
| 视频低延迟 | 实测平均延迟 300~500ms,网络波动可自适应调整 |
| 轻量嵌入 | 支持运行在 Android 5.0+ 设备上,资源占用极小 |
| 外部编码接入 | 支持 YUV、PCM、已编码H.264/AAC 数据对接 |
| 线程安全 | LibPublisherWrapper 使用读写锁封装,支持多线程调度 |
| 可组合 | 可与录像模块、RTSP服务模块、转发模块无缝组合 |
🚀 六、适配场景与行业实践
-
⚙️ 工业手持终端推流:如 Android 工业平板、巡检手持;
-
👮 执法记录仪实时上传:边采集边推送至平台,也可用GB28181设备接入模块;
-
🚁 无人机回传画面:低延迟视频推流;
-
🏠 智慧社区/安防推送:兼容H.265节省带宽;
-
🖥️ 企业培训录播系统:可选录像 + 推流组合形式;
📌 七、总结:RTMP在Android端,依然主力,关键是做好“工程交付”
RTMP 推流并没有过时,特别是在 Android 端,其低门槛、广泛兼容、高稳定性,仍然是无可替代的选择。而大牛直播SDK 不仅提供了 API,更提供了“工程交付逻辑”:从初始化到采集,从编码到推送,从可调参数到可组合模块,真正跑得起来、用得久。
📚 官方文档:大牛直播SDK
📖 更多实战文章:音视频牛哥-CSDN博客

浙公网安备 33010602011771号