如何在录制中断后恢复录制而不丢失数据?

解读

国内面试场景下,这道题既考察候选人对 Android 音视频框架的深度理解,也暗含“用户付费录制中途来电、切后台、被杀进程、存储空间不足”等真实痛点。面试官期望听到一条“从用户点击恢复到最终文件可播放”的完整技术链路,重点在于“数据不丢”与“体验无缝”。若只回答“用 MediaRecorder 重新 start”会被视为初级;必须体现对文件格式、缓存策略、容错机制、权限变更、国产 ROM 管控的系统性思考。

知识点

  1. 容器格式与 moov 原子:MP4 的 moov 写在文件尾,异常中断即无索引,文件无法播放。
  2. MPEG4Writer 的 setNextOutputFile()/setMaxFileSize():Android 10 以上官方支持的“分段 MP4”机制,可生成可独立播放的 sdcard/DCIM/.pending 片段。
  3. 录制引擎选型:MediaRecorder 状态机简单但不可控;MediaCodec+MediaMuxer 组合可手工 flush,实现“内存缓存 + 分段落盘”。
  4. 缓存策略:环形 FIFO 的 AudioRecord/VideoEncoder 缓冲区,在 Application 被强杀前通过 Service.onTaskRemoved() 把最后一帧写进临时文件;利用 FFmpeg 的 moov_rear 前置工具可在下次启动时修复。
  5. 国产 ROM 后台限制:华为/小米/opPO 的“后台录音”权限需引导用户手动加白;前台 Service + 媒体通知栏保活是标配。
  6. 存储与权限:Android 11 分区存储下,临时片段只能写在 Context.getFilesDir()/cache,恢复后通过 MediaStore.createWriteRequest() 一次性合并到公共目录,避免“权限掉落”导致文件不可见。
  7. 断电续录协议:自定义索引文件(json)记录每段起始 pts、文件路径、旋转角度,恢复时用 MediaMuxer.addTrack() 重新对齐时间戳,保证音视频同步。
  8. 性能与 GC:录制中断常因内存不足触发;使用 mmap 写文件、复用 MediaCodec 的 ByteBuffer,减少 Java 堆抖动。

答案

分三层实现“零丢失”恢复:

  1. 录制架构
    采用“MediaCodec 硬编 + 自研封装层”方案,放弃 MediaRecorder。音频线程与视频线程分别把编码后的数据送进一个“阻塞队列 + 环形缓存”,由 muxer 线程异步写盘。
    设置 MPEG4Writer.setNextOutputFile(),每 50 MB 或 30 s 自动切分一段可播 MP4,文件名带起始 pts。
    同时在应用私有目录生成 record.index,实时追加 JSON 行:{"seq":n,"path":"...","startPts":xxx,"rotation":0},每写成功一次 fsync 一次,确保断电不丢。

  2. 中断检测与现场保存
    在 Service 内注册 PhoneStateListener、Activity 生命周期回调及 onTrimMemory()。
    一旦收到来电、切后台或内存不足,立即向编码线程发送 EOS 信号,muxer 线程收到后停止当前段,调用 MediaMuxer.stop(),保证 moov 正常写入。
    若进程被强杀,利用 JobScheduler 设定 0 网络约束的“恢复任务”,系统将在 1–2 min 内拉起应用;拉起后读取 record.index 找到最后一段,继续录制。

  3. 恢复与合并
    用户点击“继续”后,新建下一段 MP4,沿用相同音频采样率、视频分辨率,时间戳从上一段 lastPts 开始累加。
    所有分段录制完成后,在后台线程用 MediaMuxer 二次合并:按 index 顺序 addTrack(),重新计算 pts,保持音视频同步;合并完删除临时分段,只保留最终文件。
    最后通过 MediaStore API 插入公共 Video 目录,发送系统扫描广播,确保相册即时可见。

通过以上流程,即使中途来电、杀进程、存储空间告警,用户再次打开 App 也能“一键继续”,最终文件可无缝播放,真正做到“数据零丢失”。

拓展思考

  1. 如果业务场景是“直播”而非“本地录制”,中断恢复需引入 RTMP/RTC 的“秒开重推 + GOP 缓存”策略,与本地分段落盘思路不同。
  2. 对于 Android 14 的“前台服务必须指定类型”新规,录制型 FGS 需声明 mediaProjection 类型,并在 6 小时内停止,否则系统抛 Fatal;面试时可主动提及,以体现对新版本适配的敏感度。
  3. 若目标设备为车载或 Wear,存储介质可能是只读闪存,需改用 SQLite 存储索引 + 低码率 TS 分段,避免频繁写放大;同时利用 Automotive OS 的 CarService 申请驾驶状态权限,防止行驶中弹窗干扰。