Android平台后台采集摄像头实现RTMP推送和轻量级RTSP服务技术实践
技术背景
在安防监控、智慧零售、远程巡检、执法记录仪、智慧教育等越来越多的行业场景中,一个核心诉求是:让Android设备在后台持续采集摄像头画面和麦克风音频,并通过RTMP推送到远端服务器,或在设备本地启动一个轻量级RTSP服务供内网其他终端直接拉流观看。
这两种推送模式各有侧重:
- RTMP推送:适合公网环境,数据上行到CDN或流媒体服务器,由多个客户端拉流观看,适合直播、远程监控等场景。
- 轻量级RTSP服务:适合内网或局域网环境,设备端直接作为RTSP Server,无需额外部署流媒体服务器,播放端通过RTSP URL直连拉流,延迟可做到毫秒级,适合无纸化会议、电子教室、工业内网监控等场景。
传统做法是将这两个能力分别集成、分别维护,不仅工作量大,而且音视频同步、编码器管理、前台服务维护等问题容易顾此失彼。本文基于大牛直播SDK(SmartMediaKit)的Android平台Camera2Demo,结合完整的源码实现,系统地讲解如何在一套架构中同时支持后台Camera2摄像头采集、RTMP推送和轻量级RTSP服务。
整体架构设计
整个方案的架构可以用下图概括:
┌────────────────────────────────────────────────────────────┐
│ MainActivity │
│ (UI控制层:预览、开关摄像头、麦克风、推送、RTSP服务) │
│ ↕ ServiceConnection (bind/unbind) │
├────────────────────────────────────────────────────────────┤
│ StreamMediaCameraService │
│ (前台服务:持有Engine实例、管理生命周期、保活) │
│ ↕ NTStreamMediaBinder │
├────────────────────────────────────────────────────────────┤
│ NTStreamMediaCameraEngineImpl │
│ (核心引擎:Camera2采集、音频采集、编码、推送、RTSP服务) │
│ ┌──────────┐ ┌──────────────┐ ┌─────────────────┐ │
│ │Camera2 │ │NTAudioRecord │ │LibPublisher │ │
│ │Helper │ │V2 (麦克风) │ │Wrapper(编码推送) │ │
│ └──────────┘ └──────────────┘ └─────────────────┘ │
│ ↕ │
│ SmartPublisherJniV2 │
│ (JNI Native层) │
└────────────────────────────────────────────────────────────┘
这套架构有几个关键设计点:
服务化运行:将摄像头采集和推送逻辑全部放在StreamMediaCameraService前台服务中,Activity通过ServiceConnection绑定服务获取Engine引用,即使Activity被销毁,采集和推送也不会中断。
引擎与UI解耦:NTStreamMediaCameraEngine定义了纯粹的接口契约,NTStreamMediaCameraEngineImpl实现全部业务逻辑,UI层仅通过接口操作,职责清晰。
线程安全设计:Engine内部通过HandlerThread维护独立的工作线程,所有关键操作通过post_or_execute()投递执行,避免多线程竞争。
核心模块解析

1. 前台服务:StreamMediaCameraService
Android 8.0及以上系统对后台采集摄像头有严格限制,必须通过前台服务(Foreground Service)配合对应的服务类型声明才能合法运行。StreamMediaCameraService承担了这一角色。
在onCreate()中,服务完成SDK初始化、创建工作线程并实例化核心引擎:
@Override
public void onCreate() {
super.onCreate();
this.running_thread_ = new HandlerThread("NTCameraServiceRT");
this.running_thread_.start();
LibPublisherWrapper.RTSPServer.initialize_sdk(
this.lib_publisher_, getApplication().getApplicationContext());
while(-1 == this.running_thread_.getThreadId())
Thread.yield();
this.engine_ = new NTStreamMediaCameraEngineImpl(
getApplication(), service_handler_, running_thread_, lib_publisher_);
}
前台服务的通知构建中,通过ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA和FOREGROUND_SERVICE_TYPE_MICROPHONE声明服务类型,这是Android 10+后台使用摄像头和麦克风的硬性要求:
int type = 0;
if (Build.VERSION.SDK_INT >= 30 && check_camera_permission())
type |= ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA;
if (Build.VERSION.SDK_INT >= 30 && check_record_audio_permission())
type |= ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE;
startForeground(id, notification, type);
服务销毁时的关键设计是同步等待Engine关闭。由于Engine内部的Camera2操作是异步的,直接销毁可能导致资源泄漏。代码通过AtomicBoolean信号量配合超时机制确保安全关闭:
AtomicBoolean is_done = new AtomicBoolean(false);
if(engine_impl.post(close_runnable)) {
long wait_begin_t = SystemClock.elapsedRealtime();
while(!is_done.get()) {
Thread.yield();
if (SystemClock.elapsedRealtime() - wait_begin_t > 3000)
break;
}
} else
close_runnable.run();
2. Camera2摄像头采集:Camera2Helper
Camera2Helper是整个视频采集链路的起点,封装了Android Camera2 API的完整生命周期管理。采用Builder模式构建,支持指定摄像头ID、预览View、镜像显示、屏幕方向等参数。
打开摄像头的核心流程:
public synchronized void start(int width, int height) {
if (mCameraDevice.get() != null) return;
if (!is_opening_.compareAndSet(false, true)) return;
startBackgroundThread();
if (mTextureView != null) {
if (mTextureView.isAvailable())
openCamera(mVideoWidth, mVideoHeight);
else
mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
} else
openCamera(mVideoWidth, mVideoHeight);
创建CaptureSession时的双输出配置
这里使用TEMPLATE_RECORD而非TEMPLATE_PREVIEW作为CaptureRequest模板,这是一个很重要的细节——RECORD模板会优化视频录制的帧率稳定性和编码质量,比PREVIEW模板更适合推流场景。
帧率控制是保证推流质量的另一关键。代码通过Camera2Enumerator查询设备支持的FPS范围,然后选择最接近目标帧率的范围:
Range<Integer> bestFps = getBestFpsRange(camera_characteristics);
if (bestFps != null) {
mPreviewRequestBuilder.set(
CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, bestFps);
}
此外,Camera2Helper还做了防抖策略选择:优先使用光学防抖(OIS),不可用时回退到电子防抖(EIS),以及持续视频对焦模式(CONTROL_AF_MODE_CONTINUOUS_VIDEO),都是直播推流场景的最佳实践。
3. 核心引擎:NTStreamMediaCameraEngineImpl
NTStreamMediaCameraEngineImpl是整个方案的中枢,它同时实现了NTStreamMediaCameraEngine接口和Camera2Listener回调接口,将摄像头数据采集与编码推送无缝衔接。
视频数据回调处理是最核心的数据通路。当Camera2Helper通过onCameraImageData(Image image)回调YUV数据时,Engine直接将数据投递给SDK编码层
@Override
public void onCameraImageData(Image image) {
if (image.getFormat() != ImageFormat.YUV_420_888) return;
....
stream_publisher_.PostLayerImageYUV420888ByteBuffer(0, 0, 0,
y_buffer, y_offset, y_row_stride,
u_buffer, u_offset, u_row_stride,
v_buffer, v_offset, v_row_stride, uv_pixel_stride,
crop_width, crop_height, 0, 0, 0, 0,
0, rotation_degree);
}
这里有几个技术要点值得关注:
- 零拷贝传递:直接将Image的ByteBuffer引用传给Native层,避免了Java层的数据拷贝开销。
- Crop Rect处理:Camera2在某些设备上会返回带裁剪区域的图像,代码正确处理了偏移计算。
- 旋转角度传递:将摄像头的Sensor方向与设备物理方向结合计算出的rotation_degree传给编码器,由编码器负责旋转,这比在Java层做像素旋转高效得多。
- 层叠加模式(video_opt=3):使用
PostLayerImageYUV420888ByteBuffer接口以Layer方式投递视频帧,便于后续叠加水印、时间戳等OSD信息。
音频采集通过NTAudioRecordV2完成,回调PCM数据后直接投递给SDK:
@Override
public void onNTAudioRecordV2Frame(ByteBuffer data, int size,
int sampleRate, int channel, int per_channel_sample_number) {
LibPublisherWrapper sdk_wrapper = get_publisher_wrapper();
if (sdk_wrapper != null)
sdk_wrapper.OnPCMData(data, size, sampleRate, channel, per_channel_sample_number);
}
4. RTMP推送实现
启动RTMP推送的流程清晰直观,所有操作通过工作线程串行化执行:
private boolean start_rtmp_stream_internal(String url) {
// 1. 创建SDK实例(如果尚未创建)
if (!test_and_create_sdk_instance()) return false;
// 2. 设置推送URL
if (!stream_publisher_.SetURL(url)) {
stream_publisher_.try_release();
return false;
}
// 3. 启动推送
if (!stream_publisher_.StartPublisher()) {
stream_publisher_.try_release();
return false;
}
// 4. 启动水印线程(可选)
start_video_layer_post_thread();
return true;
}
SDK实例创建时的编码器配置非常丰富,支持软编码和硬编码切换:
private long create_sdk_instance(SmartPublisherJniV2 publisher_sdk,
int width, int height, int fps, int gop) {
long handle = publisher_sdk.SmartPublisherOpen(
get_application_context(), audio_opt_.get(), video_opt_.get(), width, height);
// H.264硬编码配置
if (1 == video_encoder_type_) {
int hw_kbps = LibPublisherWrapper.estimate_video_hardware_kbps(width, height, fps, true);
int isSupportH264HWEncoder = publisher_sdk.SetSmartPublisherVideoHWEncoder(handle, hw_kbps);
if (isSupportH264HWEncoder == 0) {
publisher_sdk.SetVideoHWEncoderBitrateMode(handle, 1); // VBR
publisher_sdk.SetAVCHWEncoderProfile(handle, 0x08); // High Profile
publisher_sdk.SetAVCHWEncoderLevel(handle, 0x1000); // Level 4.1
}
}
// H.265硬编码配置
else if (2 == video_encoder_type_) {
int hw_kbps = LibPublisherWrapper.estimate_video_hardware_kbps(width, height, fps, false);
publisher_sdk.SetSmartPublisherVideoHevcHWEncoder(handle, hw_kbps);
}
// 软编码VBR配置
publisher_sdk.SmartPublisherSetSwVBRMode(handle, 1, video_quality, vbr_max_bitrate);
// 音频编码配置(AAC)
publisher_sdk.SmartPublisherSetAudioCodecType(handle, 1);
publisher_sdk.SmartPublisherSetAudioBitRate(handle, 96);
return handle;
}
安卓采集摄像头和麦克风实现低延迟RTMP推流
5. 轻量级RTSP服务实现
轻量级RTSP服务是这套方案的另一大亮点。设备端直接作为RTSP Server,无需额外部署流媒体服务器。实现分三步走:
第一步:启动RTSP Server
@Override
public boolean start_rtsp_server(int port, String user_name, String password) {
LibPublisherWrapper.RTSPServer.Handle handle =
LibPublisherWrapper.RTSPServer.create_and_start_server(
lib_publisher_, port, user_name, password);
rtsp_server_.reset(handle);
return true;
}
服务启动后,设备开始监听指定端口(默认8554),支持配置用户名和密码进行访问控制。
第二步:发布RTSP流
private boolean start_rtsp_stream_internal(String stream_name) {
if (!test_and_create_sdk_instance()) return false;
// 设置流名称
stream_publisher_.SetRtspStreamName(stream_name);
// 关联RTSP Server
stream_publisher_.ClearRtspStreamServer();
stream_publisher_.AddRtspStreamServer(rtsp_server_handle);
// 开始推流
stream_publisher_.StartRtspStream();
start_video_layer_post_thread();
return true;
}
发布成功后,SDK通过事件回调返回可供拉流的RTSP URL:
case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_RTSP_URL:
if (!is_null_or_empty(param3)) {
rtsp_url_.getAndSet(param3);
// RTSP URL格式如:rtsp://192.168.1.100:8554/stream1
}
break;
RTMP和RTSP可以同时运行,因为它们共用同一个SDK实例和同一份编码数据,只是输出通道不同。停止时互不影响:
public boolean is_stream_running() {
return stream_publisher_.is_publishing();
}
// is_publishing()内部判断:is_rtmp_publishing_ || is_rtsp_publishing_ || ...
6. LibPublisherWrapper:线程安全的SDK封装
LibPublisherWrapper使用ReadWriteLock保护native handle的访问,确保在多线程环境下SDK调用的安全性:
private final ReadWriteLock rw_lock_ = new ReentrantReadWriteLock(true);
private volatile long native_handle_;
public void release() {
long handle;
write_lock_.lock();
try {
handle = this.native_handle_;
this.native_handle_ = 0;
} finally {
write_lock_.unlock();
}
if (lib_publisher_ != null && handle != 0)
lib_publisher_.SmartPublisherClose(handle);
}
try_release()方法的设计也很巧妙——只有在所有流都停止后才真正释放资源,避免了RTMP停止后RTSP还在使用时误释放的问题。
UI交互与控制流
MainActivity作为控制面板,通过ServiceConnection绑定服务获取Engine引用后,所有操作都通过NTStreamMediaCameraEngine接口完成:
// 绑定服务
Intent service_intent = new Intent(this, StreamMediaCameraService.class);
bindService(service_intent, service_connection_, Context.BIND_AUTO_CREATE);
// 获取引擎引用
public void onServiceConnected(ComponentName name, IBinder service) {
NTStreamMediaBinder binder = (NTStreamMediaBinder) service;
media_engine_ = binder.get_stream_media_camera_engine();
media_engine_.register_callback(media_engine_callback_);
}
典型的使用流程如下:
- 启动服务 →
startForegroundService(intent) - 配置参数 →
engine.commit_base_config(1280, 720, 25, encoder_type) - 打开摄像头 →
engine.open_camera() - 打开麦克风 →
engine.start_audio_record(44100, 1) - 启动RTSP服务 →
engine.start_rtsp_server(8554, "", "") - 发布RTSP流 →
engine.start_rtsp_stream("stream1") - (可选)启动RTMP推送 →
engine.start_rtmp_stream("rtmp://server/live/stream")
所有操作均支持独立控制,可以只开RTSP不开RTMP,也可以两者同时运行。
安卓轻量级RTSP服务采集摄像头和麦克风实现IPC功能
关键技术细节
Camera2图像旋转处理
移动设备的摄像头Sensor安装方向与屏幕方向通常不一致(大多数手机后置Sensor旋转了90度),需要正确计算旋转角度传给编码器。Camera2Helper中的计算逻辑:
public int getCameraImageRotationDegree(int device_degree) {
if (CAMERA_ID_BACK.equals(camera_id)) {
degree = (mSensorOrientation + device_degree + 360) % 360;
} else {
degree = (mSensorOrientation - device_degree + 360) % 360;
}
return degree;
}
前后置摄像头的计算公式不同,前置摄像头由于存在镜像关系,需要用减法。
预览View的动态绑定与解绑
后台服务模式下,Activity可能被销毁后又重新创建,预览View需要支持动态绑定:
@Override
public boolean set_camera_preview_view(TextureView view) {
// 尝试重建CaptureSession加入新的TextureView
if (!helper.recreate_preview_session(camera_preview_view_)) {
// 重建失败则重启摄像头
helper.stop();
helper.set_texture_view(camera_preview_view_);
helper.start(video_width_, video_height_);
}
}
这里的recreate_preview_session()先尝试只重建Session(代价小),失败时才完全重启摄像头(代价大),是一个性能友好的渐进策略。
事件回调机制
SDK的事件回调通过NTSmartEventCallbackV2接口传递,Engine通过WeakReference避免内存泄漏,并将回调切换到工作线程处理:
private static class SDKEventCallbackImpl implements NTSmartEventCallbackV2 {
private final WeakReference<NTStreamMediaCameraEngineImpl> engine_;
@Override
public void onNTSmartEventCallbackV2(long handle, int id, ...) {
NTStreamMediaCameraEngineImpl engine = engine_.get();
if (engine != null) {
engine.post(() -> engine.on_publisher_event(handle, id, ...));
}
}
}
适用场景
基于本文方案,可以快速落地以下场景:
安防与远程监控:将Android设备(手机、平板、工控机)部署在现场,后台持续采集摄像头画面,通过RTMP推送到云端,管理人员随时随地查看。内网环境下也可通过轻量级RTSP服务直连拉流,延迟更低。
智慧零售:在门店部署Android设备采集客流画面,RTMP推送到总部分析系统,RTSP服务供本地门店管理屏实时预览。
远程教育与无纸化会议:教师端Android设备采集教学画面,通过轻量级RTSP服务在内网分发给学生终端,无需部署服务器,延迟做到100~200毫秒级别。
执法记录与车载终端:设备在复杂网络环境下运行,RTMP负责上传到指挥中心,RTSP服务供同车或同组人员本地查看,双通道互为备份。
工业巡检:工人佩戴的Android智能终端采集现场画面,RTMP上传到远程专家系统,RTSP供现场调度屏显示,实现远程指导。
总结
本文以大牛直播SDK(SmartMediaKit)的Android平台Camera2Demo为基础,详细解析了一套完整的后台摄像头采集 + RTMP推送 + 轻量级RTSP服务的技术方案。整套方案的核心优势在于:
- 服务化架构:前台服务保活,Activity无关性,适配Android高版本后台限制。
- Camera2深度适配:支持无预览后台采集、帧率精确控制、防抖和对焦优化、旋转角度自动计算。
- 双通道输出:RTMP和RTSP可同时运行、共享编码资源、独立控制。
- 软硬编灵活切换:支持H.264/H.265硬编码和软编码,VBR/CBR/CQ多种码率模式。
- 线程安全:工作线程串行化、ReadWriteLock保护native资源、AtomicReference/AtomicBoolean保证状态一致性。
📎 CSDN官方博客:音视频牛哥-CSDN博客
更多推荐
所有评论(0)