米家监控录像 MP4 格式说明与转换指南
0. 一句话结论
这类文件没有加密,也没有损坏。
它们是小米 / 米家摄像机写出的一种分片 MP4(fragmented MP4,简称 fMP4),视频用 HEVC(H.265)、音频用 Opus,并且整个文件按固定 128 MiB 预分配,末尾留有零填充。
播放器和剪辑软件出现"打不开、时长显示 0、拖不动进度条、没有声音",原因是容器的组织方式和音频编码,不是文件坏了。
观看用potplayer,转换mp4需要花点心思
1. 先判断到底是不是加密 / 损坏
1.1 加密与否,看有没有这些 box 名
用十六进制编辑器打开文件开头,或在文件里搜索下面这些四字节标识:
| 标识 | 含义 |
|---|---|
encv / enca | 加密的视频 / 音频采样条目 |
sinf | 保护方案信息容器 |
tenc | 轨道加密参数(密钥信息) |
senc | 采样加密信息 |
pssh | 保护系统头 |
这类录像里一个都没有。 相反,mdat 区域可以直接读到明文的视频码流(HEVC 的 NAL 单元,帧头形如 26 xx、40 01、42 01、44 01 等)。所以是明文存储。
1.2 是否损坏,看这几个特征
| 现象 | 实际原因 | 是否算损坏 |
|---|---|---|
| 时长显示 0:00,进度条不能拖 | moov 中样本表为空(分片 MP4 的正常写法) | 否 |
| 有图像但完全没声音 | 音轨是 Opus,很多软件不认 MP4 里的 Opus | 否 |
| 每个文件大小完全一致 | 录像按固定大小预分配写入 | 否 |
| 文件末尾有大段全零 | 预分配但未使用的空间 | 否 |
十六进制开头是可读的 ftyp isom… | 容器头是明文,结构完整 | 否 |
真正的损坏通常是:moof / mdat 的 box 长度字段自相矛盾、box 链中途断掉、中间出现大片连续全零而没有对应 box 头。这些文件都不属于这种情况。
2. 容器结构(分片 MP4)
2.1 整体布局
ftyp 24 字节 文件类型声明
free 136 字节 占位(对齐用)
moov 1142 字节 轨道与编码声明(初始化段)
sidx 345600 字节 全文件分片索引(含大量预留空白)
┌─ 以下单元重复出现 ────────────────────────┐
│ uuid 56 字节 私有索引标记 │
│ moof 约 2.3 KB 分片头(含样本表) │
│ mdat 约 210–390 KB 实际媒体数据 │
└──────────────────────────────────────────┘实测样本:单个文件内约有 150 ~ 760 组 moof + mdat,每组约 6 秒,合计时长与录像机记录的时长一致。
2.2 关键点:moov 里没有样本表
对 moov 逐层解析后得到:
| 位置 | 内容 |
|---|---|
moov/mvhd | 整体时间信息 |
moov/mvex/trex × 2 | 分片扩展头(两条轨道) |
moov/trak[0]/mdia/minf/stbl | stsd 里是 hvc1(HEVC 视频) |
moov/trak[1]/mdia/minf/stbl | stsd 里是 Opus(音频) |
而两条轨道的样本表全部是空的:
stts entryCount = 0
stsz sampleCount = 0
stsc entryCount = 0
stco entryCount = 0
stss entryCount = 0这就是"文件看起来很奇怪"的根本原因。 普通 MP4 会把所有样本的时长、大小、偏移写在 moov 里;而分片 MP4 把这些信息拆散,放到每个 moof 里,真正的总索引放在 sidx。只读 moov 的软件自然读出"时长 0、没有样本"。
这种写法本身是合规的,是 DASH / CMAF 流媒体常用的组织形式,只是监控设备把整段录像按这种方式落盘了。
2.3 sidx 索引
| 字段 | 实测值 |
|---|---|
| 声明大小 | 345600 字节 |
| 版本 | 0 |
| timescale | 90000 |
| 条目大小 | 12 字节 |
| 条目数 | 每文件 150 ~ 760 条(按实际分片数) |
| 条目容量 | 28797 条(剩余部分为零填充,实测约 339 KB) |
每条索引记录"某个分片占多少字节、持续多少时间",且与物理 moof + mdat 的字节数逐条对得上(误差为 0)。
2.4 私有 uuid 标记
每隔约 2.5 MB 出现一个 56 字节的 uuid box,其 user-type 前 10 字节是 ASCII 的 mike_index,后跟 8 字节零。这是厂商私有的索引检查点,标准播放器会直接忽略,不影响播放。
3. 编码参数
| 项目 | 视频 | 音频 |
|---|---|---|
| 编码 | HEVC / H.265(hvc1) | Opus |
| 分辨率 | 3840 × 2160 | — |
| 帧率 | 可变帧率,平均约 20 fps | — |
| 采样率 | — | 48000 Hz |
| 声道 | — | 1(单声道) |
| 总码率 | 约 330–370 kbps(含音频) | — |
两个兼容性雷区:
- Opus 装进 MP4。Opus 通常出现在 WebM / Matroska 里,MP4 封装支持得晚,不少剪辑软件和系统解码器直接不认。这是"没声音"的元凶。
- HEVC 4K。需要软件自带 H.265 解码能力;系统还需要安装 HEVC 扩展。这是"画面打不开"的元凶之一。
4. 固定 128 MiB 与零填充
4.1 预分配
每个录像文件都是 134,217,728 字节(= 128 MiB = 2²⁷),一个字节不差。而各段录像的时长并不相同。说明写入方式是:先按固定大小占位,再往里写数据,录制结束就收尾,未用到的部分留在文件里。
4.2 零区分布
对整文件做逐字节扫描后,只有两处连续零区:
| 位置 | 长度 | 性质 |
|---|---|---|
约 7.8 KB 处(sidx 尾部) | 约 339 KB | sidx 的预留条目容量,未用满 |
| 文件末尾 | 约 7.2 – 7.5 MB | 预分配未使用的空间 |
其余部分(前 125 MB 左右)数据密度稳定在 93% – 95% 非零,中间没有空洞。
此外,分片内部还存在周期性的小段零填充(每个分片末尾约 4 KB),属于封装对齐,正常现象。
4.3 怎么确认内容完整(而不是被截断)
不要用"文件末尾有零"来判断损坏。正确做法是核对索引:
- 读
sidx,把所有条目的 duration 相加,除以 timescale(90000),得到索引总时长。 - 与录像机记录的实际时长对照。
实测结果:两者一致(例如某一文件的索引总时长 3219.3 秒,实际 3220 秒,覆盖率 100.0%)。同时 sidx 里所有分片字节数之和,恰好等于"索引结束后到文件末尾"的长度,说明末尾零区也被索引覆盖,不存在"数据写到一半断掉"的情况。
结论:内容是完整的,末尾零填充只是预留空间。
5. 目录里的 .index 文件是什么
同目录下通常还有一个以 .index 结尾的二进制文件(体积约十几 MB)。它不是视频:
开头既不是
ftyp,也不匹配任何媒体容器。按固定 144 字节一条记录排列。
记录内容包含明文的录像文件名以及对应的元信息(时间、偏移等)。
有效记录只占前半部分,其余是预留的空白区域。
它是摄像机自己的目录索引,用来登记每段录像的存储位置。同样没有加密。
6. 转换方案
6.0 前提:先明确你要干什么
| 目的 | 是否需要转换 |
|---|---|
| 只是观看 | 不需要。VLC / PotPlayer / mpv 都能直接播放 |
| 用系统自带播放器看 | 需要(自带播放器不认分片 MP4,也缺 HEVC 解码) |
| 剪映 / Premiere 剪辑 | 需要(主要是 Opus 音轨的问题) |
| 长期归档 | 不需要,保持原样即可 |
无论采用哪种方案,都请保留原始文件作为母版,不要原地覆盖。
6.1 方案 A:只换音频,视频直通(推荐)
视频流原样拷贝(画质零损失),只把 Opus 重编码为 AAC:
ffmpeg -hide_banner -y -i 输入.mp4 -map 0 -c:v copy -c:a aac -b:a 128k -movflags +faststart 输出.mp4优点:速度快(4K 素材实测单个文件 13–26 秒),画质不变,解决"没声音"和"时长 0"。缺点:输出仍是 HEVC,很老的剪辑软件可能还不认。
6.2 方案 B:完整重编码为 H.264
兼容性最好,但费时且有损(会二次压缩):
ffmpeg -hide_banner -y -i 输入.mp4 -c:v libx264 -crf 20 -preset medium -c:a aac -b:a 128k -movflags +faststart 输出.mp44K 素材单个文件可能需要数十分钟到一两小时。适合交付给兼容性要求高的场合。
6.3 方案 C:纯重封装(只修容器)
ffmpeg -hide_banner -y -i 输入.mp4 -map 0 -c copy -movflags +faststart 输出.mp4秒级完成、零损失,能修好 moov 为空导致的"时长 0 / 不能拖进度条 / 打不开"。但音轨仍然是 Opus,剪辑软件可能依旧没有声音。只有在确认目标软件能处理 Opus 时才够用。
6.4 三方案对照
| 视频画质 | 音频 | 速度 | 兼容性 | |
|---|---|---|---|---|
| A 只换音频 | 零损失 | AAC | 快 | 高 |
| B 完整重编码 | 有损 | AAC | 很慢 | 最高 |
| C 纯重封装 | 零损失 | 仍为 Opus | 极快 | 中 |
7. 批量处理脚本
把下面的内容保存为 .ps1 文件,按需修改两个路径后运行。脚本使用方案 A。
param(
[Parameter(Mandatory=$true)][string]$InputDir,
[Parameter(Mandatory=$true)][string]$OutputDir,
[string]$Ffmpeg = 'ffmpeg'
)
New-Item -ItemType Directory -Force -Path $OutputDir | Out-Null
$files = Get-ChildItem -LiteralPath $InputDir -Filter *.mp4 -File | Sort-Object Name
Write-Host "待处理 $($files.Count) 个文件"
$sw = [Diagnostics.Stopwatch]::StartNew()
foreach ($f in $files) {
$dst = Join-Path $OutputDir $f.Name
if (Test-Path -LiteralPath $dst) { Write-Host "跳过(已存在): $($f.Name)"; continue }
$t = [Diagnostics.Stopwatch]::StartNew()
& $Ffmpeg -hide_banner -loglevel warning -y -i $f.FullName `
-map 0 -c:v copy -c:a aac -b:a 128k -movflags +faststart $dst
$t.Stop()
$size = (Get-Item -LiteralPath $dst).Length
"{0,-48} {1,14:N0} B {2,6:N1}s" -f $f.Name, $size, $t.Elapsed.TotalSeconds
}
$sw.Stop()
"总耗时 {0:N1}s" -f $sw.Elapsed.TotalSeconds运行示例(-Ffmpeg 填 ffmpeg 可执行文件的实际位置):
.\convert.ps1 -InputDir '输入目录' -OutputDir '输入目录\output' -Ffmpeg 'D:\Program\ffmpeg-9.0.2-essentials_build\bin\ffmpeg.exe'8. 转换结果如何验证
8.1 看流信息和时长
ffprobe -v error -show_entries format=duration,bit_rate -show_entries stream=codec_name,codec_tag_string,width,height,nb_frames -of default=noprint_wrappers=1 输出.mp4对照检查:
视频 codec 应为
hevc(方案 A/C)或h264(方案 B)音频 codec 应为
aac,codec_tag_string为mp4aduration与源文件一致(误差在 0.01 秒量级)
8.2 抽帧解码测试
分别抽头部、中段、尾部各解一帧,退出码为 0 即正常:
ffmpeg -v error -i 输出.mp4 -frames:v 1 -f null -
ffmpeg -v error -ss 1600 -i 输出.mp4 -frames:v 1 -f null -
ffmpeg -v error -sseof -3 -i 输出.mp4 -frames:v 1 -f null -8.3 看 box 结构
转换成功的成品应该长这样(moov 在前,且带有真实的样本表):
ftyp 28 字节
moov 约 2.5 MB <- 包含完整的 stts / stsz / stsc / stco
free 8 字节
mdat 约 137 MB <- 媒体数据对比原文件:moov 只有 1142 字节、样本表全空、后面跟着几百组 moof + mdat。这个差别就是"修好了"的直接证据。
9. 转码时的常见警告怎么读
方案 A 转码时,很可能看到成片的这两条:
[aac] Queue input is backward in time
[aost#0:1/aac] Non-monotonic DTS; previous: ..., current: ...; changing to ...含义:原始 Opus 音轨的时间戳自身存在抖动(每段会往回跳几十毫秒,最多约 17 ms),ffmpeg 为避免输出非单调时间戳,自动做了 +1 的微调。
这不是错误,也不代表转换失败。 对监控画面没有实际影响。转换后校验时长一致即可放心。
10. 常见问题
Q:能直接改扩展名让它变成普通 MP4 吗?不能。问题是容器内部结构,改名字没有任何作用。
Q:能不能手动把末尾的零删掉,让文件变小?不要这么做。末尾零区被 sidx 的最后一条索引和最后一个 mdat 的长度字段引用着,直接截断会让 box 长度与实际不符,部分解析器会直接判定文件损坏。想瘦身请用方案 A 重新封装。
Q:为什么有的文件是 4K 但码率只有 300 多 kbps?监控场景画面变化少,且采用可变帧率(平均约 20 fps),编码器在静态画面下会大幅省码率。夜间静态画面的码率尤其低,属于正常现象。
Q:为什么转换后文件反而变大了?常见。Opus 单声道码率低(约 60–70 kbps),换成 128 kbps 的 AAC 后音轨体积会明显增长。若要控体积,把 -b:a 调到 64k 即可,单声道足够。
Q:原始文件可以删吗?建议保留。转换属于有损或半有损操作(方案 A 虽视频无损,但音频已重编码),原文件是唯一母版。
Q:为什么不同录像的文件大小一模一样?见第 4 节,是固定大小预分配的结果,与内容多少无关。
11. 速查表
| 想做的事 | 命令要点 |
|---|---|
| 只想看 | 装 VLC / PotPlayer / mpv,直接播 |
| 修复容器、不要重编码 | -c copy -movflags +faststart |
| 剪辑用(推荐) | -c:v copy -c:a aac -b:a 128k -movflags +faststart |
| 最大兼容性 | -c:v libx264 -crf 20 -preset medium -c:a aac |
| 查时长与编码 | ffprobe -show_entries format=duration -show_entries stream=codec_name ... |
| 查是否加密 | 搜索 encv enca sinf tenc senc pssh 是否存在 |
| 查是否有空洞 | 逐块统计非零字节比例,正常应在 90% 以上 |
