Android平台RTMP推流实战全解:从初始化到流畅上传的技术闭环

🧭 一、RTMP推流,移动端的刚需方案

在直播/监控/采集类移动应用中,将采集到的音视频内容实时推送至服务器是最基础但最关键的一环。而 RTMP(Real-Time Messaging Protocol)凭借其稳定性、部署成熟度、低延迟等优势,依然是移动端推流主力协议。

但仅仅“推起来”远远不够——移动端推流,尤其是 Android 平台,面临更多挑战:

  • 编解码兼容复杂(软编、硬编适配各厂商 SoC)

  • 推流稳定性高要求(频繁前后台切换、摄像头切换)

  • 网络环境不可控(Wi-Fi、4G频繁抖动)

  • 推流配置灵活性(码率、帧率、GOP、Profile 组合设置)

  • 功耗与资源控制

大牛直播SDK 在 Android 平台上推出的 RTMP 推送模块,正是基于上述痛点,围绕“可靠性、兼容性、低延迟、易接入”设计的一套可商用化技术方案。


🏗️ 二、架构设计:模块组件拆解

Android平台采集屏幕和扬声器推送RTMP整体延迟测试

大牛直播SDK Android 推流模块核心由三部分构成:

  1. Native 编解码内核(SmartPublisherJniV2.java)

    • 实现音视频采集、编码、RTMP打包与传输;

    • 支持软编、硬编(MediaCodec);

    • 可设置参数覆盖大部分直播场景;

  2. 状态封装控制层(LibPublisherWrapper.java)

    • 封装 native 层句柄/状态管理;

    • 提供线程安全的发布、关闭、录制控制接口;

    • 管理推送标志状态(Event状态回调);

  3. 业务调度层(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博客

posted @ 2025-05-07 11:38  音视频牛哥  阅读(19)  评论(0)    收藏  举报  来源