米家监控录像MP4格式与转换说明

实用技术 算法
43 编辑于
kinotern

米家监控录像 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/stblstsd 里是 hvc1(HEVC 视频)
moov/trak[1]/mdia/minf/stblstsd 里是 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
timescale90000
条目大小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(含音频)—

两个兼容性雷区:

  1. Opus 装进 MP4。Opus 通常出现在 WebM / Matroska 里,MP4 封装支持得晚,不少剪辑软件和系统解码器直接不认。这是"没声音"的元凶。
  2. HEVC 4K。需要软件自带 H.265 解码能力;系统还需要安装 HEVC 扩展。这是"画面打不开"的元凶之一。

4. 固定 128 MiB 与零填充

4.1 预分配

每个录像文件都是 134,217,728 字节(= 128 MiB = 2²⁷),一个字节不差。而各段录像的时长并不相同。说明写入方式是:先按固定大小占位,再往里写数据,录制结束就收尾,未用到的部分留在文件里。

4.2 零区分布

对整文件做逐字节扫描后,只有两处连续零区:

位置长度性质
约 7.8 KB 处(sidx 尾部)约 339 KBsidx 的预留条目容量,未用满
文件末尾约 7.2 – 7.5 MB预分配未使用的空间

其余部分(前 125 MB 左右)数据密度稳定在 93% – 95% 非零,中间没有空洞。

此外,分片内部还存在周期性的小段零填充(每个分片末尾约 4 KB),属于封装对齐,正常现象。

4.3 怎么确认内容完整(而不是被截断)

不要用"文件末尾有零"来判断损坏。正确做法是核对索引:

  1. 读 sidx,把所有条目的 duration 相加,除以 timescale(90000),得到索引总时长。
  2. 与录像机记录的实际时长对照。

实测结果:两者一致(例如某一文件的索引总时长 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 输出.mp4

4K 素材单个文件可能需要数十分钟到一两小时。适合交付给兼容性要求高的场合。

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。

powershell
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 可执行文件的实际位置):

powershell
.\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 为 mp4a

  • duration 与源文件一致(误差在 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% 以上

本文版权遵循 CC BY-NC 协议 和 本站版权政策

(。>︿<。) 已经一滴回复都不剩了哦~