首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >深入探讨后台摄像头|麦克风采集与轻量级RTSP服务|RTMP推流架构设计

深入探讨后台摄像头|麦克风采集与轻量级RTSP服务|RTMP推流架构设计

原创
作者头像
音视频牛哥
发布2025-12-18 17:02:17
发布2025-12-18 17:02:17
480
举报
文章被收录于专栏:RTSP服务器RTSP服务器RTMP推送

​在 Android 音视频开发中,“后台推流”是一个经典且棘手的需求。常见的场景包括:行车记录仪(熄屏录像)、智能安全帽(后台回传)、执法记录仪等。

传统的做法是将推流逻辑写在 Activity 中,但一旦用户按 Home 键或熄屏,Activity 及其持有的 Camera 资源极易被系统回收。为了解决这个问题,我们需要构建一个基于前台服务(Foreground Service)的独立采集架构

本文将结合大牛直播SDK(SmartMediakit)的 SmartPublisher 模块的源码,深度拆解如何实现这一系统。

一、 整体架构设计:UI 与业务的彻底解耦

为了保证推流服务的稳定性,我们采用 C/S(Client/Service)架构 模式:

  1. Service 层 (StreamMediaCameraService)
    • 作为后台驻留的核心载体。
    • 负责持有 Engine 实例。
    • 提升进程优先级(前台服务),防止 OOM Killer 查杀。
  2. Engine 层 (NTStreamMediaCameraEngineImpl)
    • 业务大脑。封装 Camera2 API、AudioRecord 以及 Native 推流库。
    • 管理数据采集(YUV/PCM)到编码推流的全流程。
  3. UI 层 (MainActivity)
    • 纯展示与控制。只负责显示预览画面(TextureView)和发送控制指令(开关相机、开始推流)。
    • 即使 Activity 销毁(如屏幕旋转、内存回收),Service 和 Engine 依然存活。
  4. 通信层 (NTStreamMediaBinder)
    • 通过 Binder 机制,让 Activity 获取 Service 中的 Engine 句柄。

二、 核心实现一:构建“杀不死”的前台服务

StreamMediaCameraService.java 中,最关键的逻辑是适配 Android 8.0 到 Android 14 的前台服务机制。

1. 为什么需要前台服务?

Android 系统对后台应用限制极严。如果不启动前台服务(在通知栏显示一条常驻通知),App 切后台后几分钟内网络和 CPU 就会被限制,导致推流中断。

2. 代码实现详解

我们需要根据 Android 版本动态设置 Notification Channel 和 ServiceType(特别是 Android 10+ 必须声明 Camera/Microphone 类型)。

代码语言:javascript
复制
// [文件: StreamMediaCameraService.java]

public void start_foreground_service() {
    try {
        String channelId = "nt_camera_service";
        String channelName = "Background Camera Service";
        
        // 1. 创建 NotificationManager
        NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        
        // 2. 适配 Android 8.0+ (Oreo): 必须创建 NotificationChannel
        if (Build.VERSION.SDK_INT >= 26) {
            NotificationChannel channel = new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_HIGH);
            manager.createNotificationChannel(channel);
        }

        // 3. 构建动态的 Notification 内容,实时显示当前状态
        StringBuilder sb = new StringBuilder(256);
        sb.append("流媒体采集服务正在运行;");
        if (engine_ != null) {
            if (engine_.is_opened_camera()) sb.append("摄像头已开启;");
            if (engine_.is_audio_record_running()) sb.append("麦克风已开启;");
            if (engine_.is_rtsp_stream_running()) sb.append("已输出RTSP流;");
        }

        Notification.Builder builder = new Notification.Builder(this)
                .setSmallIcon(R.mipmap.ic_launcher)
                .setContentTitle("摄像头采集前台服务")
                .setContentText(sb.toString());

        if (Build.VERSION.SDK_INT >= 26)
            builder.setChannelId(channelId);

        Notification notification = builder.build();

        // 4. 启动前台服务 (核心适配点)
        final int id = 111;
        if (Build.VERSION.SDK_INT < 29) {
            // Android 9.0 及以下
            startForeground(id, notification);
        } else {
            // Android 10.0+ (Q/R/S...): 必须显式声明服务类型
            int type = 0;
            // 动态检查权限,防止 Crash
            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);
        }
    } catch (Exception e) {
        Log.e(TAG, "start_foreground Exception:", e);
        stopSelf();
    }
}

注意:AndroidManifest.xml 中也必须对应声明:

代码语言:javascript
复制
<service android:name=".StreamMediaCameraService"
    android:foregroundServiceType="camera|microphone" />

三、 核心实现二:Activity 与 Service 的安全绑定

MainActivity 不直接 new Engine,而是通过 bindService 获取。这保证了 Engine 的生命周期跟随 Service 而非 Activity。

1. Binder 定义

代码语言:javascript
复制
// [文件: NTStreamMediaBinder.java]
public class NTStreamMediaBinder extends Binder {
    // 使用 AtomicReference 保证多线程安全
    private final AtomicReference<NTStreamMediaCameraEngine> engine_ = new AtomicReference<>();

    public NTStreamMediaCameraEngine get_stream_media_camera_engine() {
        return engine_.get();
    }
    // ... attach/detach 逻辑
}

2. Service 中的绑定逻辑

代码语言:javascript
复制
// [文件: StreamMediaCameraService.java]
@Override
public void onCreate() {
    super.onCreate();
    // Service 创建时,初始化 Engine,确保 Engine 和 Service 同生共死
    this.engine_ = new NTStreamMediaCameraEngineImpl(getApplication(), service_handler_, running_thread_, lib_publisher_);
}

@Override
public IBinder onBind(Intent intent) {
    // 当 Activity 绑定时,将 Engine 挂载到 Binder 上
    binder_.attach(engine_);
    return binder_; 
}

3. Activity 中的获取逻辑

代码语言:javascript
复制
// [文件: MainActivity.java]
private void onServiceConnected(NTStreamMediaBinder binder) {
    media_binder_ = binder;
    // 关键点:从 Binder 中拿到了活着的 Engine 实例
    media_engine_ = get_stream_media_engine(); 

    if (media_engine_ != null) {
        // 重新注册回调(因为 Activity 重建后回调对象变了)
        media_engine_callback_ = new NTStreamEngineCallbackImpl();
        media_engine_callback_.reset(MainActivity.this);
        media_engine_.register_callback(media_engine_callback_);

        // 恢复预览 View(如果有的话)
        if (is_preview_camera_ && preview_view_.isAvailable())
            media_engine_.set_camera_preview_view(preview_view_);
    }
    // ...
}

四、 核心实现三:Camera2 数据采集与 Native 推流

这是整个系统的数据引擎。NTStreamMediaCameraEngineImpl 负责协调 Camera2Helper 采集数据,并将数据投递给 JNI

1. 数据的产生 (Camera2 API)

Camera2Helper.java 中,我们通过 ImageReader 获取 YUV 数据(格式通常为 YUV_420_888)。

2. 数据的流转 (Java -> Native)

NTStreamMediaCameraEngineImpl 中,我们实现了 Camera2Listener。这是性能最敏感的部分,直接决定了推流的延迟和流畅度。

代码语言:javascript
复制
// [文件: NTStreamMediaCameraEngineImpl.java]

@Override
public void onCameraImageData(Image image) {
    if (null == image) return;
    
    // 只有当推流开启时才处理数据,节省 CPU
    if (!stream_publisher_.is_publishing()) return;

    // 校验格式,必须是 YUV_420_888
    if (image.getFormat() != ImageFormat.YUV_420_888) return;

    Image.Plane[] planes = image.getPlanes();
    // 获取 Y, U, V 三个平面的数据指针和步长
    ByteBuffer y_buffer = planes[0].getBuffer();
    ByteBuffer u_buffer = planes[1].getBuffer();
    ByteBuffer v_buffer = planes[2].getBuffer();

    int y_row_stride = planes[0].getRowStride();
    int u_row_stride = planes[1].getRowStride();
    int v_row_stride = planes[2].getRowStride();
    int uv_pixel_stride = planes[1].getPixelStride(); // 关键参数,决定是 I420 还是 NV21 等

    int width = image.getWidth();
    int height = image.getHeight();
    
    // 获取旋转角度(处理横竖屏推流的关键)
    int rotation_degree = cameraImageRotationDegree_.get();

    // 【核心调用】直接将 ByteBuffer 传递给 Native 层 (JNI)
    // 避免了在 Java 层进行 byte[] 拷贝,极大提升性能
    stream_publisher_.PostLayerImageYUV420888ByteBuffer(
            0, 0, 0,
            y_buffer, 0, y_row_stride,
            u_buffer, 0, u_row_stride,
            v_buffer, 0, v_row_stride, 
            uv_pixel_stride,
            width, height, 
            0, 0, 0, 0, // 翻转和缩放参数
            0, rotation_degree
    );
}

代码亮点:

  • 零拷贝 (Zero Copy): 使用 ByteBuffer 直接在 Java 和 C++ 之间传递内存地址,避免了 byte[] 数组的反复拷贝,这对于 1080P/30fps 的高清流至关重要。
  • 动态旋转: 通过 rotation_degree 参数,底层 SDK 自动处理图像旋转,确保观看端看到的画面方向正确。

五、 核心实现四:RTSP Server 与多路分发

系统不仅支持 RTMP 推流,还内置了一个轻量级的 RTSP Server。这意味着手机本身变成了一个 IPC(网络摄像机)。

代码语言:javascript
复制
// [文件: NTStreamMediaCameraEngineImpl.java]

private boolean start_rtsp_stream_internal(String stream_name) {
    // 1. 确保 RTSP Server 端口已监听
    long rtsp_server_handle = rtsp_server_.get_native();
    if(0 == rtsp_server_handle) return false;

    // 2. 创建 SDK 推流实例
    if (!test_and_create_sdk_instance()) return false;

    // 3. 设置流名称 (例如 rtsp://192.168.1.100:8554/stream1)
    stream_publisher_.SetRtspStreamName(stream_name);

    // 4. 将推流实例挂载到 Server 上
    stream_publisher_.ClearRtspStreamServer();
    stream_publisher_.AddRtspStreamServer(rtsp_server_handle);

    // 5. 启动流
    if (!stream_publisher_.StartRtspStream()) {
        return false;
    }
    
    // 6. 启动水印线程 (可选)
    start_video_layer_post_thread();

    return true;
}

六、 工业级细节:电池优化白名单

仅仅依靠前台服务,在某些深度定制的 ROM(如 MIUI、EMUI)或 Android 高版本 Doze 模式下仍可能被限制网络。在 MainActivity 中,我们必须请求用户豁免电池优化。

代码语言:javascript
复制
// [文件: MainActivity.java]

// 判断是否已在白名单中
private boolean isIgnoringBatteryOptimizations(){
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        String packageName = getPackageName();
        PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
        return pm.isIgnoringBatteryOptimizations(packageName);
    }
    return false;
}

// 跳转系统设置申请白名单
private void gotoSettingIgnoringBatteryOptimizations() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        try {
            Intent intent = new Intent();
            String packageName = getPackageName();
            intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
            intent.setData(Uri.parse("package:" + packageName));
            startActivityForResult(intent, REQUEST_IGNORE_BATTERY_CODE);
        } catch (Exception e) {
            Log.e(TAG, "gotoSettingIgnoringBatteryOptimizations Exception:", e);
        }
    }
}

七、 总结

通过上述代码的拆解,我们利用大牛直播SDK的轻量级RTSP服务模块,构建了一个完整的 Android 后台流媒体采集系统:

  1. 稳定性:利用 Notification + Service + ForegroundServiceType 适配,确保进程在后台不被查杀。
  2. 高性能:通过 Camera2 API + ByteBuffer + JNI 实现 YUV 数据的零拷贝传输。
  3. 灵活性Service 承载业务,Activity 仅作展示,实现了完美的解耦,支持断线重连和后台静默推流。
  4. 功能全:同时支持 RTMP 推流、RTSP 服务端、水印叠加、软硬编码自动切换。

这套架构不仅适用于 Demo 演示,更是开发行车记录仪、远程协助、无人机图传等商业级产品的坚实基础。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、 整体架构设计:UI 与业务的彻底解耦
  • 二、 核心实现一:构建“杀不死”的前台服务
    • 1. 为什么需要前台服务?
    • 2. 代码实现详解
  • 三、 核心实现二:Activity 与 Service 的安全绑定
    • 1. Binder 定义
    • 2. Service 中的绑定逻辑
    • 3. Activity 中的获取逻辑
  • 四、 核心实现三:Camera2 数据采集与 Native 推流
    • 1. 数据的产生 (Camera2 API)
    • 2. 数据的流转 (Java -> Native)
  • 五、 核心实现四:RTSP Server 与多路分发
  • 六、 工业级细节:电池优化白名单
  • 七、 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档