技术背景

在安防监控、智慧零售、远程巡检、执法记录仪、智慧教育等越来越多的行业场景中,一个核心诉求是:让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_CAMERAFOREGROUND_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_);
}

典型的使用流程如下:

  1. 启动服务startForegroundService(intent)
  2. 配置参数engine.commit_base_config(1280, 720, 25, encoder_type)
  3. 打开摄像头engine.open_camera()
  4. 打开麦克风engine.start_audio_record(44100, 1)
  5. 启动RTSP服务engine.start_rtsp_server(8554, "", "")
  6. 发布RTSP流engine.start_rtsp_stream("stream1")
  7. (可选)启动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博客 

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐