
简介这是一份面向Android开发者的远程视频监控程序源码包完整演示了在移动端实现实时监控所需的网络通信、音视频编解码、多媒体数据渲染与后台线程处理等关键技术。压缩包内共68个文件以java源码、xml布局与配置、class编译产物为主同时附带apk安装包、PC端exe程序及工程配置文件便于直接安装调试并对照源码理解Android端与PC端的配合逻辑。已有353人学习下载适合正在研究Android网络编程、流媒体传输或想快速搭建监控Demo的开发者。通过这份源码可以重点学习Socket/HTTP通信、MediaCodec硬解码、SurfaceView视频展示、权限声明与多线程异步处理等实践细节也能看到Android项目从源码到构建产物classes.dex、resources.ap_等的完整结构对理解APK打包流程也有帮助。1. 拿到远程视频监控源码包先别急着解压“远程视频监控”这六个字在 Android 源码里被压缩成一件容易误判的事很多人以为难点在 Camera 调用打开项目才发现真正决定能否“远程”的是视频编码怎么选、推流走什么协议、信令怎么握手、弱网下怎么保活。这个题目适合两类人一是做安防、看护、车载类应用的工程师想绕过从零造轮子的阶段二是刚接触流媒体方向的 Android 开发者想找一个能跑通的工程范本,理解 Camera2、MediaCodec、RTSP 这三者是怎么串成一条链路的。需要说明的是市面上这类 ZIP 源码包质量参差不齐名字叫“完整版”打开后缺 native 库、缺 Gradle 配置、甚至缺 Manifest 权限的都常见。这篇文章不会假装你手里那份源码是完美的而是按一个可落地的远程视频监控工程应该具备的结构把采集、编码、推流、信令、优化这五段拆开讲并附上可以直接上手用的关键代码和参数表。2. 工程结构先行从 ZIP 解压到 AS 编译通过拿到一份 Android 远程视频监控源码包第一件要做的事不是读业务代码而是先确认工程能不能构建。这个环节卡住的人数远超想象原因集中在三类Gradle 版本与 Android Studio 不匹配、NDK 与 so 库架构缺失、Manifest 中权限或 Service 声明不全。下面按步骤来排查。2.1 先看工程骨架识别源码包属于哪类项目结构解压后先观察根目录。一个标准的 Android 工程至少要具备目录/文件作用缺失时的表现settings.gradle声明模块Android Studio 无法识别项目结构build.gradle根级插件版本与依赖仓库Gradle Sync 直接报错app/模块主工程代码无app时需检查是否 multi-moduleapp/src/main/jniLibs/so 动态库目录UnsatisfiedLinkErrorAndroidManifest.xml权限与组件声明运行即崩溃提示权限异常如果settings.gradle里有多个模块例如:app与:library说明源码包采用了组件化拆分监控逻辑可能封装在 library 里。此时不要只导入app目录应该在整个根目录下执行 Open 操作。2.2 Gradle 与 SDK 版本对齐常见做法是先用 Android Studio 打开根目录等 Gradle Sync 完成再根据报错逐项调整。远程监控工程通常依赖 Camera2 API对minSdkVersion的要求至少是 21若要使用 CameraX 扩展则需要 21 以上并启用了 Java 8 支持。我一般会把app/build.gradle中的关键部分改成这样android { compileSdkVersion 33 defaultConfig { applicationId com.example.remote.monitor minSdkVersion 21 targetSdkVersion 33 versionCode 1 versionName 1.0 // 使用 C 时需配置 NDK 版本建议统一到 21.4.7075529 ndk { abiFilters armeabi-v7a, arm64-v8a } } }这里的关键参数是abiFilters。远程视频监控的 so 库如 FFmpeg、x264 或内部封装好的编码器在不同 CPU 架构下不可混用如果源码包只提供arm64-v8a的 so而测试机是 32 位模拟器就必须在abiFilters里去掉armeabi-v7a否则安装后加载 so 库直接崩溃。2.3 Manifest 中容易被源码包裁剪掉的声明远程监控 App 至少需要以下权限uses-permission android:nameandroid.permission.CAMERA / uses-permission android:nameandroid.permission.INTERNET / uses-permission android:nameandroid.permission.ACCESS_NETWORK_STATE / uses-permission android:nameandroid.permission.WAKE_LOCK /WAKE_LOCK是这类源码中最容易被遗漏的一项。视频采集与编码需要 CPU 持续工作屏幕熄灭后如果没有 WakeLock系统会进入休眠导致后置摄像头回调停止、编码器断流。拿到源码后应先在 Manifest 里检查这几项权限再考虑业务逻辑。2.4 编译期最常碰到的三个错误处理Gradle Sync 时报Could not find com.android.tools.build:gradle:x.x.x说明插件版本不存在或仓库未配置可在根级build.gradle中加上google()与mavenCentral()仓库。编译时报Invoke-customs are only supported starting with Android 0说明app/build.gradle中缺少compileOptions配置补上 Java 8 支持即可compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 }安装后启动崩溃并提示java.lang.UnsatisfiedLinkError优先检查jniLibs目录下是否存在libavcodec.so等文件且文件名是否与System.loadLibrary()中的名称对应。提示拿到源码后先备份一份原始 ZIP避免修改 Gradle 文件后无法回退到初始状态。3. Camera2 采集与 MediaCodec 硬编码把摄像头数据变成可传输的 H.264 流远程监控的核心链路是摄像头帧 → 编码 → 网络传输。Android 上 95% 的源码工程走的都是同一套路Camera2 负责取帧MediaCodec 负责硬编码输出 H.264RTP/RTSP 负责封装传输。这里最容易翻车的不是 API 调用而是 Buffer 格式、色彩空间和时间戳的处理。3.1 Camera2 的输出目标ImageReader 还是 Surface?Camera2 的采集链路有两种选择直接把MediaCodec支持的Surface作为相机输出目标帧数据从 Camera 到编码器零拷贝。先用ImageReader拿到Image再通过getInputBuffer()喂给编码器多一次拷贝但便于做图像处理比如画框、加水印。远程监控场景下我倾向于推荐第二种思路原因是源码工程往往需要在视频上叠加时间戳或设备编号Surface直连模式虽然性能好却无法在编码前修改画面。如果源码包默认采用ImageReader需要确认ImageFormat是YUV_420_888这是 MediaCodec 输入侧兼容性最好的格式。ImageReader imageReader ImageReader.newInstance( width, height, ImageFormat.YUV_420_888, 4 ); imageReader.setOnImageAvailableListener(reader - { Image image reader.acquireLatestImage(); if (image null) { return; } // 将 YUV_420_888 转成 byte[]喂给 MediaCodec byte[] data yuvImageToByteArray(image); feedEncoder(data); image.close(); }, backgroundHandler);这段代码里ImageReader.newInstance的第四个参数表示最大图片数量建议不要小于 3。如果设为 1相机回调频繁时会出现丢帧表现为视频卡顿但 CPU 占用不高。acquireLatestImage与acquireNextImage的区别在于前者会跳过积压的旧帧监控场景下应优先考虑前者以此保证推流端拿到的始终是最新画面。3.2 MediaCodec 编码器的参数设置照着这张表调编码器配置是整个工程中与“画面质量”和“延迟”关系最直接的部分参数推荐值说明KEY_COLOR_FORMATCOLOR_FormatYUV420Flexible兼容性最好避免部分设备不支持COLOR_FormatYUV420SemiPlanarKEY_BIT_RATE1~4 Mbps720p 建议 2 Mbps1080p 建议 4 Mbps根据上行带宽调节KEY_FRAME_RATE15~30监控场景 15fps 可减少编码压力动态画面多时拉到 30KEY_I_FRAME_INTERVAL1~3每隔多少秒插入一个关键帧值越小花屏恢复越快但码率占用越高KEY_PROFILEAVCProfileBaselineBaseline 兼容性最强Web 端播放不易出现黑屏关键帧间隔是远程监控最需要关心的参数。播放端如果出现花屏通常是因为 I 帧间隔过长弱网下 P 帧丢失后就无法恢复画面。把KEY_I_FRAME_INTERVAL设为 1 或 2虽然每秒多占用一部分码率但换来回放端快速出图这笔账是划算的。编码器的配置代码一般长这样MediaFormat format MediaFormat.createVideoFormat( MediaFormat.MIMETYPE_VIDEO_AVC, width, height ); format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible); format.setInteger(MediaFormat.KEY_BIT_RATE, 2_000_000); format.setInteger(MediaFormat.KEY_FRAME_RATE, 15); format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); format.setInteger(MediaFormat.KEY_BITRATE_MODE, MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR); codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);BITRATE_MODE_VBR适合网络波动场景画面复杂时自动提升码率静止时降低。但如果你的推流协议是 RTSP 且传输层是 TCP建议改成BITRATE_MODE_CBR恒定码率对带宽预测更友好避免出现瞬间数据量过大导致 socket 写阻塞。3.3 时间戳远程监控画面跳动的隐形元凶编码器输出的 buffer 必须携带正确的presentationTimeUs。这个时间戳是解码端决定画面何时显示的依据如果使用系统System.currentTimeMillis()会因为挂起和唤醒产生跳动正确做法是使用System.nanoTime()换算成微秒long presentationTimeUs System.nanoTime() / 1000;从dequeueOutputBuffer拿到的BufferInfo中flags包含BUFFER_FLAG_CODEC_CONFIG时需要单独存储这份 SPS/PPS 数据它用于推流时的sps-pps参数拼接或 RTSP 的 AVCDecoderConfigurationRecord。很多源码包把这一步省略了结果播放端显示黑屏日志中只有解码失败。4. RTSP 推流与信令通道让画面真正实现“远程”编码器产出的 H.264 裸流只是半成品要让它能从手机端传到远端的播放器上还需要完成传输协议封装和信令交互。远程视频监控最常用的传输协议是 RTSP市面上开源播放器 VLC、FFplay 都内置 RTSP 客户端测试方便也符合“拿源码改一改就能用”的预期。4.1 自建 RTSP 服务端还是推流到媒体服务器?这里有一个经常被忽略的架构选择方案适用场景缺点手机内置轻量 RTSP Server一对一监控手机直接充当流媒体服务端耗电高App 被杀后流中断手机作为客户端推流到 NVR 或媒体服务器多设备统一管理服务端持久化需要额外部署服务端如 SRS、ZLMediaKit源码包里如果自带了一个RtspServer类说明采用方案一如果只有推流器推流端则需要自己准备服务端。我建议后者因为远程监控通常意味着设备端 IP 不固定NAT 环境下手机无法被外部直接访问主动推流到服务器更可靠。以 SRS 为例服务端启动后默认监听1935端口用于 RTMP如果走 RTSP 需要开启1935与554端口。一条最小可用的推流链路命令如下# 服务端启动 SRS 并开启 RTSP 转发 ./objs/srs -c conf/rtsp.conf # 测试拉流FFplay 直接从远端读取 ffplay -rtsp_transport tcp rtsp://your-server-ip:554/live/camera1这个命令能跑通的前提是推流端把流推到rtsp://your-server-ip:554/live/camera1这个路径。Android 端可用 MediaCodec 输出 H.264 后自行实现 RTP 打包并发送到该地址。如果需要快速验证编码流是否合法也可以用adb shell下的命令行工具来抓取裸流文件再做本地解码adb exec-out screencap -p /tmp/screen.png # 或者抓取编码后的 H264: adb pull /sdcard/test.h264 . ffprobe test.h264这段命令里的ffprobe可以快速检查h264文件是否包含可解的 sps/pps如果你在代码里没有发送 SPS/PPSffprobe会直接提示Could not find codec parameters这是远程监控源码包里最常见的联调错误。4.2 RTP 打包与 H.264 分片H.264 的 NAL 单元可能大于 MTU通常 1500 字节RTP 打包时需要将大 NAL 分片使用 FU-A 分片方式小于 MTU 时采用 Single NAL Unit 方式。代码实现了这些逻辑。伪代码结构如下private void sendH264Nal(byte[] nal, int rtpPort, String host) { if (nal.length MAX_PACKET_SIZE) { // 单包发送 sendRtpPacket(nal, /* marker */ true); } else { // FU-A 分片分包发送 int start 0; while (start nal.length) { int len Math.min(nal.length - start, MAX_PACKET_SIZE - 2); // 组装 FU indicator FU header 数据 sendRtpPacket(fuPacket, /* marker */ start len nal.length); start len; } } }分片逻辑中FU indicator的第一个字节由原始 NAL header 的高 5 位组成FU header的低 5 位由分片序号组成。如果这里没处理好播放端会出现马赛克或丢帧花屏。注意RTSP 拉流时除 RTP 包外还需发送 RTCP SR 包用于时间戳同步否则 VLC 的播放进度会一直跳动。4.3 信令通道控制命令与视频流分离远程视频监控除了画面还需要远程操作的能力比如旋转摄像头、抓拍、开关夜视。常见做法是建立一条独立的 WebSocket 或 TCP 长连接信令只传 JSON不传视频数据。{cmd: capture, deviceId: camera_001, ts: 1699999999} {cmd: ptz, direction: left, speed: 30}信令与视频流分离的好处是网络拥塞时视频可以降帧率或丢帧但控制指令仍能及时到达不至于连停止转动这种基础操作都做不了。源码包中如果只在MainActivity里处理了SurfaceView的显示却没有独立信令 Service建议自行补充一个MonitorCommandService该 Service 只维护一个 Socket 连接并在后台保活。5. 流畅度与弱网适配远程监控的体验瓶颈在传输侧当视频已经开始远程传输问题就从“怎么推到服务器”转移到“网络波动下还能不能保持流畅”。大量源码包在这一层是裸奔的它们只是死循环里不断send()网络一抖就出现卡顿、花屏乃至 TCP 缓冲区堆积。这章讨论的是如何把一条“能跑”的推流链路改成一条“在弱网下不那么容易崩”的推流链路。5.1 帧率与码率的动态调节策略弱网表现不理想时第一反应是降低码率。MediaCodec 在编码过程中可以通过Bundle参数动态调整目标码率Bundle bitrate new Bundle(); bitrate.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, 800_000); codec.setParameters(bitrate);这个接口没有返回值部分设备上并不生效实际测试时需要观察一秒钟实际编码输出的字节数计算真实码率。若设备不支持动态调码就要在采集侧做文章——降低帧率或分辨率。有一个参数表格值得参考网络条件RTT/丢包率目标分辨率帧率码率延迟 30ms丢包 0%1080p304 Mbps延迟 30~100ms丢包 1%720p202 Mbps延迟 100ms丢包 3%480p12800 Kbps监控场景中我通常不会把这种调整放在应用层写死而是让信令服务端根据 TCP 丢包率推送新的编码配置。这么做避免客户端频繁重配 MediaCodec 带来的编码秒级黑屏。5.2 关键时刻的缓冲策略播放端的缓冲策略也会影响观感。作为推流端要关注的是发送缓冲区。TCP 默认写缓冲在大延迟下容易堆积导致“新帧发不出去旧帧卡在路上”。一个有效的做法是发送线程每次写数据前检查当前socket.sendQueueSize超限时丢弃非关键帧if (sendQueueSize MAX_BUFFER_SIZE) { // 当前帧为 P 帧直接丢弃保留 I 帧 continue; }这种“丢帧不丢关键帧”的策略比降低码率更直接能保证画面即使掉帧也不至于长时间花屏。5.3 前后台切换与生命周期处理远程监控源码如果没有处理好 App 退到后台的场景很容易出现“屏幕关了推流也断了”。监控类的 App 必须使用前台 Service且通过startForeground()传一个常驻通知才能在 Android 8.0 以上保证后台存活。Notification notification new Notification.Builder(this, CHANNEL_ID) .setContentTitle(远程监控运行中) .setContentText(正在推送视频流) .setSmallIcon(R.drawable.ic_monitor) .build(); startForeground(1, notification);在低端机上Camera2 和 MediaCodec 同时运行时 CPU 占用率很高如果整机温度上升部分手机会自动降频表现为从 30fps 跌到 20fps 以下。要排查这类问题可以边推流边执行adb shell dumpsys cpuinfo | grep monitor观察monitor相关进程的 CPU 占比和线程优先级若发现编码线程被抢占可在代码中设置Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO);THREAD_PRIORITY_URGENT_AUDIO是 Android 中可用的较高调度优先级虽然听起来像音频相关的但在 Android 的调度器里持续的视频推流线程也可以借用这一档优先级来与低优先级线程竞争 CPU。5.4 编码结束时如何避免 B 帧问题远程视频监控对实时性要求高通常需要设置KEY_LATENCY或采用AVCProfileBaseline来避免 B 帧。B 帧会引入多帧延迟对低延迟监控不利。在部分 MediaCodec 实现里即使没有显式设置也可能默认开启 B 帧表现为端到端延迟达数百毫秒。可以在配置后通过codec.getCodecInfo().getCapabilitiesForType(mediaType).videoCapabilities查看编码器是否支持 B 帧再决定是否调整 profile。6. 把源码跑成一款可交付的监控应用验证清单与设备适配技巧源码能编译通过、能推流这只是起跑线。在实际环境里不同 SoC 和 Android 版本对 Camera2 行为的差异会直接暴露出来。这里给出一份可以从工程化层面对齐的内部验证模板和应对方案。6.1 用 adb 命令压测摄像头与编码器的温度上限远程监控设备经常挂在户外或固定位置长时间运行后是否有稳定性风险需要先跑一轮压力验证# 循环播放并对焦切换到前置/后置摄像头持续运行 30 分钟 adb shell am start -n com.example.monitor/.MainActivity adb shell settings put system screen_off_timeout 1800000 # 同时开启 media.codec 的调试日志 adb shell setprop debug.media.codec 2 adb logcat -s MediaCodec:V这套命令的目的是验证长时间编码后是否出现MediaCodec.CodecException: Error 0x80000001这类错误在部分海思、联发科平台上非常常见出现后通常需要重启编码器此时需要实现分层重试先stop()再start()不用释放实例还不行才执行release()并重新创建。如果源码包里并没有多设备适配分支建议主动加一层CodecRetryManager。6.2 验证推流的 5 个自查点在交付前按照以下顺序检查能淘汰 80% 的隐藏问题用ffprobe -v trace查看拉流地址的 SPS/PPS 是否在第一帧就到达。将手机切换为飞行模式 5 秒再恢复确认推流端能在 3 秒内恢复出图。用另一台设备从 4G 网络拉流观察卡顿收敛时间标准是连续 5 秒无花屏。adb shell top -n 1检查 App 是否占用超过 60% CPU超出时确认分辨率与帧率档位是否合理。检查唤醒锁是否在锁屏后依然持锁可通过以下命令确认adb shell dumpsys power | grep PARTIAL_WAKE_LOCK若发现没有持锁看护场景下屏幕一关推流就中止如果持锁时间过长则需要注意温度控制与耗电之间的取舍比如让用户在设置页里选择“连续模式”和“节能模式”。6.3 一个被低估的功能码流加密与鉴权很多源码包的 RTSP 地址是明文公开的任何知道 IP 的设备都能拉流。远程监控场景下即使不做完整加密至少要在信令通道加一个 token 鉴权。常见做法是信令服务端鉴权成功后下发一个动态随机端口推流端只把视频流推到这个临时端口拉流端必须先通过信令获取端口才能访问视频数据。这套机制能挡住大部分通过端口扫描直接拉流的场景而且实现成本很低只需在建立 Socket 连接时多传一个token字段{cmd: auth, token: 8f2a9c3d, timestamp: 1699999999}若源码包里已经有一个RtspServer但没有任何鉴权优先补充以上逻辑整个改造不需要动编码器核心思路就是把“流媒体服务”和“控制鉴权”彻底拆开。这部分完成后整个远程视频监控工程才真正达到可以部署的状态而能被记住的监控产品恰恰是从这种“不起眼的细节”开始积累信心的。本文还有配套的精品资源点击获取