你的抽屉里是不是也躺着几部被时代淘汰的旧 Android 手机?屏幕可能碎了,电池也许不耐用了,但这台设备的“心脏”——性能尚可的 CPU 和素质不错的摄像头——依然宝刀未老。与其让它们在抽屉里吃灰,不如通过代码让它们焕发第二春。

今天,我们将基于 SmartMediakit (大牛直播 SDK),手把手教你开发一款功能完备的 Android 监控 App。这不仅是一个简单的 Demo,更是一个工业级的解决方案:它支持高效的 H.265 硬编码RTMP 远程推流,内置了无需中间件的 轻量级 RTSP 服务端,甚至还支持专业的 动态 OSD 时间水印

1. 核心架构设计

在开始编码之前,我们先梳理一下这个 Demo 的核心架构。为了保证代码的整洁和高内聚,项目被划分为了几个关键模块:

  • 数据采集层 (Camera2Helper.java):基于 Android Camera2 API,负责处理预览、分辨率切换、画面旋转以及 YUV 数据回调。

  • 业务逻辑层 (LibPublisherWrapper.java):这是对底层 JNI (SmartPublisherJniV2) 的高级封装。它管理着 RTSP Server 的生命周期、推流状态以及录像功能。

  • 特效层 (LayerPostThread.java):专门用于处理动态水印(时间戳、文字、图片),让监控画面具备“专业感”。

  • UI 控制层 (MainActivity.java):负责权限管理、界面交互以及各模块的调度。


2. 搞定“眼睛”:Camera2 数据采集

监控的第一步是“看”。在 Camera2Helper.java 中,我们需要处理复杂的 Camera2 API。为了适配监控场景,我们重点关注 YUV 数据的获取。

我们使用 ImageReader 来接收预览数据,格式设为 YUV_420_888,这是图像处理中最通用的格式。

// 引用自 Camera2Helper.java,配置 Camera 参数的核心逻辑
private boolean configCameraParams(CameraManager manager, String cameraId, int width, int height) throws CameraAccessException {
    CameraCharacteristics characteristics
            = manager.getCameraCharacteristics(cameraId);

    StreamConfigurationMap map = characteristics.get(
            CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
    if (map == null) {
        return false;
    }

    Log.i(TAG, "configCameraParams width: " + width + " height: " + height);

    // 设置预览尺寸
    mPreviewSize = new Size(width, height);

    // 配置 ImageReader,使用 YUV_420_888 格式接收数据,用于后续编码推流
    mImageReader = ImageReader.newInstance(width, height, ImageFormat.YUV_420_888, 3);

    mImageReader.setOnImageAvailableListener(
            new OnImageAvailableListenerImpl(), mBackgroundHandler);

    mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);

    Log.i(TAG, "configCameraParams: " +mSensorOrientation);

    mCameraId = cameraId;
    return true;
}

OnImageAvailableListenerImpl 中,我们将采集到的数据通过接口回调给主界面。这一步是整个视频流的源头。


3. 构建“大脑”:内置 RTSP 服务端

这是本项目的杀手锏功能。普通的推流 App 只能把流推送到服务器,而我们的旧手机通过 LibPublisherWrapper 内的 RTSPServer 类,直接变身成为一台 RTSP 服务器。这意味着你可以用电脑上的 VLC 播放器直接连接手机 IP 进行观看,延迟极低。

MainActivity.java 中,我们通过以下代码启动 RTSP 服务:

// 引用自 MainActivity.java,启动 RTSP 服务的监听逻辑
class ButtonRtspServiceListener implements View.OnClickListener {
    public void onClick(View v) {
        if (!rtsp_server_.empty()) {
            rtsp_server_.reset();
            btnRtspService.setText("启动RTSP服务");
            btnRtspPublisher.setEnabled(false);
            return;
        }

        Log.i(TAG, "onClick start rtsp service..");

        int port = 8554; // RTSP 服务端口
        String user_name = null; // 可配置鉴权用户名
        String password = null;  // 可配置鉴权密码
        
        // 调用 Wrapper 中的静态方法创建并启动 Server
        LibPublisherWrapper.RTSPServer.Handle server_handle = LibPublisherWrapper.RTSPServer.create_and_start_server(libPublisher,
                port, user_name, password);

        if (null == server_handle) {
            Log.e(TAG, "启动rtsp server失败! 请检查设置的端口是否被占用!");
            return;
        }

        rtsp_server_.reset(server_handle);

        btnRtspService.setText("停止RTSP服务");
        btnRtspPublisher.setEnabled(true);
    }
}

启动服务只是第一步,我们还需要将摄像头的视频流“发布”到这个服务上。点击“发布 RTSP 流”按钮时,代码会将当前的 Publisher 实例挂载到 RTSP Server 上:

// 引用自 MainActivity.java,发布流的逻辑
class ButtonRtspPublisherListener implements View.OnClickListener {
    public void onClick(View v) {
        if (stream_publisher_.is_rtsp_publishing()) {
            stopRtspPublisher();

            btnRtspPublisher.setText("发布RTSP流");
            btnGetRtspSessionNumbers.setEnabled(false);
            btnRtspService.setEnabled(true);
            return;
        }

        Log.i(TAG, "onClick start rtsp publisher..");

        InitAndSetConfig(); // 初始化 SDK 配置

        String rtsp_stream_name = "stream1";
        stream_publisher_.SetRtspStreamName(rtsp_stream_name);
        stream_publisher_.ClearRtspStreamServer();

        // 核心:将流关联到之前启动的 Server 句柄
        stream_publisher_.AddRtspStreamServer(rtsp_server_.get_native());

        if (!stream_publisher_.StartRtspStream()) {
            stream_publisher_.try_release();
            Log.e(TAG, "调用发布rtsp流接口失败!");
            return;
        }

        startAudioRecorder();
        startLayerPostThread(); // 启动水印线程

        btnRtspPublisher.setText("停止RTSP流");
        btnGetRtspSessionNumbers.setEnabled(true);
        btnRtspService.setEnabled(false);
    }
}

此时,你就可以通过 rtsp://[手机IP]:8554/stream1 在局域网内无缝预览画面了。


4. 动态 OSD 水印:让监控更专业

普通的监控只有画面,但专业的监控必须有时间戳。在 LayerPostThread.java 中,我们实现了一个独立的线程,用于绘制动态图层。

这个线程每隔 400ms 更新一次,利用 Canvas 绘制当前时间,并生成 Bitmap 投递给 SDK。

// 引用自 LayerPostThread.java,处理时间戳图层的核心方法
private int post_timestamp_layer(List<LibPublisherWrapper> publisher_list, boolean is_use_cache, int index, int left, int top, int video_w, int video_h) {
    /*Bitmap text_bitmap = makeTextBitmap(makeTimestampString(), getFontSize(),
            Color.argb(255, 255, 255, 255), true, Color.argb(255, 0, 0, 0),true);
     */

    // 生成包含当前时间的 Bitmap,带描边效果,防止在白色背景下看不清
    Bitmap text_bitmap = makeTextBitmap(makeTimestampString(), getFontSize(video_w),
            Color.argb(255, 0, 0, 0), true, Color.argb(255, 255, 255, 255),true);

    if (null == text_bitmap)
        return 0;

    int scale_w = 0, scale_h = 0, scale_filter_mode = 0;

    // 将 Bitmap 投递到底层 SDK 的指定图层(index)
    for (LibPublisherWrapper i : publisher_list)
        i.PostLayerBitmap(index, left, top, text_bitmap, 0, 0, 0, 0,
                0, 0, scale_w, scale_h, scale_filter_mode, 0);

    int ret = scale_h > 0? scale_h : text_bitmap.getHeight();

    text_bitmap.recycle(); // 记得回收 Bitmap 内存

    return ret;
}

run() 方法的主循环中,它会不断调用 on_update_layers,从而实现秒级跳动的实时水印效果。

安卓轻量级RTSP服务采集摄像头,PC端到安卓拉取RTSP流


5. H.265 硬编码:性能与画质的平衡

旧手机的 CPU 性能有限,软编码(CPU 编码)容易导致发热和卡顿。我们在 MainActivity.java 中通过检测硬件能力,优先开启 H.265 (HEVC) 硬编码,这能极大地降低带宽占用。

// 引用自 MainActivity.java,初始化 Publisher 并配置编码参数
private boolean initialize_publisher(SmartPublisherJniV2 lib_publisher, long handle, int width, int height, int fps, int gop) {
    // ... 省略部分参数检查

    if (videoEncodeType == 1) {
        // H.264 硬编码配置
        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); // 1:VBR 可变码率
            lib_publisher.SetVideoHWEncoderQuality(handle, 39);
            lib_publisher.SetAVCHWEncoderProfile(handle, 0x08); // High Profile
            lib_publisher.SetAVCHWEncoderLevel(handle, 0x1000); // Level 4.1
            Log.i(TAG, "Great, it supports h.264 hardware encoder!");
        }
    } else if (videoEncodeType == 2) {
        // H.265 (HEVC) 硬编码配置,更高效的压缩
        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); 
            lib_publisher.SetVideoHWEncoderQuality(handle, 39);
            Log.i(TAG, "Great, it supports hevc hardware encoder!");
        }
    }
    
    // ... 配置音频和去噪等
    return true;
}

这是一份内容更丰富、技术总结更深刻的“总结”部分重写建议。这段内容不仅概括了各个类的功能,还升华了代码设计的逻辑,突出了“大牛直播 SDK”的核心价值,更符合技术博客的调性。


6. 总结与展望:从小 Demo 到工业级应用

通过对这几千行代码的拆解,我们不仅仅是写了一个 Demo,而是构建了一个微型的、但功能完备的移动端安防监控系统。整个项目的成功,得益于清晰的模块化设计和强大的底层 SDK 支持:

  • 全能采集源 (Camera2Helper): 它不仅是摄像头的搬运工,更是处理 Android 碎片化的利器。它封装了复杂的 Camera2 API,通过 ImageReader 高效输出 YUV_420_888 数据,并利用 OrientationDetector 完美解决了监控场景中最棘手的画面旋转与角度矫正问题,确保无论是横屏还是竖屏安装,输出画面始终“立得住”。

  • 高性能流媒体中枢 (LibPublisherWrapper): 这是整个应用的“心脏”。作为 JNI 层的封装者,它赋予了 Java 层直接调用底层 C++ 核心库的能力。它实现了 RTSP 服务端RTMP 推流客户端 的双工运行,同时还能并行处理 H.265 硬编码本地录像。这种“多线程、多协议、硬编码”的架构,是保证旧手机在大负载下依然流畅不发烫的关键。

  • 专业级视觉叠加 (LayerPostThread): 为了让监控画面具备取证能力,我们设计了这个独立的渲染线程。不同于简单的静态图片贴图,它通过独立的 Canvas 绘制机制,实现了毫秒级精度的 OSD (On Screen Display) 动态时间戳文字水印。这种图层叠加技术,让一台普通的手机瞬间拥有了专业 IPC(网络摄像机)的既视感。

  • 灵活的业务调度 (MainActivity): 作为总指挥,它将权限管理、UI 交互、生命周期管理与底层服务无缝串联。无论是切换前后摄像头、调整分辨率,还是通过 AudioManager 进行静音控制,所有复杂的业务逻辑在这里被简化为一个个清晰的按钮事件,极大地降低了二次开发的门槛。

下一步,交给你了:

现在的智能手机更新迭代极快,与其让旧手机在抽屉里通过贬值变成“电子垃圾”,不如花一个下午的时间,将这份源码编译进去。你获得的不仅是一个随时可用的家庭安防摄像头,更是一次深入理解 Android 音视频开发RTSP/RTMP 协议 以及 JNI 跨平台调用 的绝佳实战机会。

📎 CSDN官方博客:音视频牛哥-CSDN博客

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐