广告运营常有这样一句抱怨:他们在 DSP 控制台打开素材库,点开一条刚上传的视频广告想预览,播放器却一直在转——两秒、三秒、五秒——画面才终于出来。第一反应是”网不稳”,刷新一下,照样卡住。
一次真实抓包(已脱敏)。上半部分:6 条素材里 4 个缩略图卡在空白占位上。下半部分:DevTools 把根因直接摆到你面前——每个 .mp4 请求都是 206 Partial Content,每个恰好返回 16.7 kB(播放器嗅探前 16 KB 找 moov),但同样大小的请求耗时从 63 ms 到 6.93 s 不等,还有两个干脆是 (pending)。这就是 moov 待在文件末尾时在网络层的特征签名。
到了真实的媒体侧流量上,同一条素材面对的是一个严酷得多的世界——4G 信号边缘的用户、家用路由器 NAT 后面的 CTV、低端设备上的移动网页,外面还裹着一层如今已被 IAB 弃用的 VPAID 垫片。启动延迟没有消失,反而被放大到了用户根本不愿意再等的地步。
用户实际看到的是一块黑屏:播放器从 VAST 响应里拿到 <MediaFile> URL,开始拉取 MP4,屏幕一直黑着(或卡在占位加载图上),直到整个文件下载完毕、第一帧能被解码出来为止。在 pre-roll / mid-roll 这种本就停留时间很短的广告位上,一条黑屏五秒以上的素材会同时拖垮完播率和可见率。
同一现象在端上的样子:App 的 pre-roll 广告位一直转圈,直到”Skip Ad”按钮可点,用户一秒后就划走了,第一帧从未出现。计费规则不会认这次曝光——但 DSP 的成本已经实打实付出去了。
这个症状背后最常见的单一根因只有一个——这条 MP4 的 moov box 被写在了文件末尾。下文是一份工程师视角的拆解:MP4 的物理结构以及它带来的协议约束、编码器为什么默认把 moov 写在末尾、HTTP 流式传输为什么把这变成数秒级延迟、这几秒在 MRC 可见性和 vCPM 上让你付出多少代价、-movflags +faststart 在容器层面到底做了什么、应该在素材入库流水线的哪一步处理它、几个常见的误判,以及一段可以直接搬进生产的拒收检查脚本。
范围说明:本篇聚焦 progressive MP4 在程序化视频投放里的启动延迟——也就是我在 App 广告 SDK 那篇里说过”素材渲染 / 可见性测量另文展开”的那一块。HLS / DASH 的流式分发、VAST 的完整解析、OMID SDK 的实现各有专文方向,这里只在与 faststart 直接相关处点到。
TL;DR
- 一句话病根:MP4 默认把
moov元数据写在文件末尾,而播放器必须先拿到moov才能渲染第一帧——它装着全部解码元数据,这是 ISO BMFF 协议层面的硬约束。 - 为什么默认在末尾:
stco偏移表要等mdat全部写完才能算出来,是单遍顺序编码绕不开的结果。对本地播放没影响;可一旦走 HTTP 流式传输,播放器别无选择,只能等整个文件下完。 - 代价有多大:一条 15 秒 / 2.5 MB 的移动端 pre-roll,在 5 Mbps 链路上到第一帧约 5 秒(30 秒广告拉到 8 秒以上)。这几秒里 OMID 的
start事件从不触发,CTV 播放器甚至直接超时、降级为ad-error。 - 钱当场就没了:在 vCPM 计费下,这几乎肯定被算作不可见——MRC 的 2 秒可见性计时器只能在第一帧之后才开始。
- 修复只要一行:
ffmpeg -c copy -movflags +faststart,外加一段 9 行的入库拒收检查。IAB Tech Lab 把”MOOV atom 必须在文件头部”写进了规范本身——这是合规要求,不是性能调优。
Table of contents
Open Table of contents
1. MP4 的物理结构:ftyp / moov / mdat
MP4 是 ISO/IEC 14496-14 定义的容器,它本身又建立在 ISO Base Media File Format(BMFF,ISO/IEC 14496-12)“盒中盒”结构之上。任何合法的 MP4 至少有这三个顶层 box:
| Box | 角色 | 装的是什么 |
|---|---|---|
ftyp | 文件类型 box | major brand(如 isom / mp42 / avc1)和兼容性列表 |
moov | 元数据 box | 每条 track 的描述、解码器配置(avcC / hvcC)、采样表(stsd / stsc / stco / stts) |
mdat | 媒体数据 box | 真正的视频 / 音频帧字节流 |
关键约束:没有 moov,mdat 里的字节就无法解码。 mdat 里每一帧的字节位置都由 moov 的采样表索引;没有 moov,整块 mdat 对播放器来说只是一堆无法寻址的二进制。
换句话说:播放器拿到一条 MP4 字节流后做的第一件事,就是定位并解析 moov。这是每个合规播放器实现都遵守的协议级约束。
2. 编码器为什么默认把 moov 写在末尾
这不是编码器偷工减料——而是单遍顺序写入的物理必然。看看 ffmpeg 编码 H.264 时做了什么:
- 写
ftyp——内容事先就知道,可以立刻写出来; - 一边编码一边把 NAL units 追加进
mdat——这时候总帧数、总字节数、关键帧分布、平均码率都还不知道; - 编码结束,统计数据这下齐了:采样数、每个 chunk 的偏移、关键帧位置(
stss)、码率分布; - 组装
moov,并追加到文件末尾。
核心矛盾在于:moov 的内容依赖对整块 mdat 的聚合统计——尤其是 stco(chunk 偏移表)需要文件里每个 chunk 的绝对字节偏移。mdat 没写完,stco 就建不出来;stco 建不出来,moov 就不完整。
对绝大多数本地编码 / 本地播放的场景,这个顺序没问题:文件躺在磁盘上,播放器随时可以 lseek(SEEK_END) 跳到末尾的 moov,再跳回去解码 mdat,用户毫无感觉。
这个假设对每一个非流式场景都成立——直到这条 MP4 被丢进 HTTP 拉取链路。
3. 走 HTTP 流式传输,moov 在末尾就是一场延迟灾难
3a. 单向字节流约束
HTTP 是单向字节流,到达顺序由发送方决定,接收方无法重排。播放器只能按字节顺序解析:读完 ftyp 后,去找下一个 box。如果下一个是 moov,第一帧立刻可解;如果是 mdat,播放器必须等整个文件下完、moov 出现在末尾才能解码任何东西——它已经收到的那几 MB mdat 全是废的,因为根本解不了。
3b. 代入数字:2.5 MB / 5 Mbps / 5 s
用今天移动网页 / 社交 pre-roll 最常见的规格:
| 参数 | 值 | 说明 |
|---|---|---|
| 时长 | 15 s | 移动网页和 TikTok / Reels / IG / YouTube Shorts pre-roll 的主流规格;30 s 在老牌桌面 / CTV 上更常见 |
| 编码 | H.264 High @ 720p | DSP 接入清单里最常见的组合 |
| 码率 | 1.3 Mbps | 720p 的中位数 |
| 文件大小 | ≈ 2.5 MB | 上面三行的乘积 |
| 用户下行带宽 | 5 Mbps | 全球分布里一个乐观的点——成熟市场宽带 / 良好 4G |
| 完整下载耗时 | ≈ 5 s | 理想路径 4 s + 20–50% 用于 TCP 慢启动和丢包重传;30 秒的广告会把它推过 8 s |
这 5 秒里屏幕是黑的或者卡在占位图上。DSP 确实收到了 VAST 的 impression 事件,但 IAB Open Measurement(OMID)的 loaded / start 事件没触发——任何以视频开播为前提的计费规则或可见性指标,都不会把它算作一次有效曝光。
更糟的是,对一条 15 秒的广告,熟悉的”Skip Ad in 5”按钮通常在曝光验证完成之前就已经可点——也就是说,可见性的 2 秒计时器还没开始(第一帧可能都没渲染),用户就已经合法地按下了跳过。这次曝光既不可见也没看完,归因窗口也从未打开过。短素材不是更安全,而是更危险: 5 秒延迟占 15 秒广告的 33%,而 8 秒只占 30 秒广告的 27%。按比例算,素材越短,被拖得越狠。
3c. 全球投放的两个放大器:带宽长尾与冷缓存
上面的 5 秒是”乐观”档。真正决定全球投放损失规模的,是另外两个放大器。
放大器一:带宽长尾。 全球投放没有一个可以拿来规划的单一”中位带宽”;同一条 moov 在末尾的素材,启动延迟在不同地区相差一个数量级——
| 地区档位 | 典型带宽 | 2.5 MB 启动延迟 |
|---|---|---|
| 成熟市场宽带 / 光纤(韩国、北欧) | 50+ Mbps | < 1 s |
| 良好 4G(上面的主例) | 5 Mbps | ≈ 5 s |
| 拥塞移动网,p90 长尾(东南亚、拉美、印度) | 1.5 Mbps | ≈ 13 s |
杀死可见性的不是中位数,而是这个 p90 长尾:成熟市场用户可能永远察觉不到问题,而大片低带宽流量在 moov 在末尾的情况下系统性地无法登记一次可见曝光。全球投放意味着你的损益表一次性暴露在整个分布之下,而长尾的权重比直觉以为的要大。
放大器二:冷缓存 + 长尾素材。 如果素材放在 S3 origin 上,由 CloudFront 全球分发,要注意 CloudFront 有 600+ 个 PoP,而程序化素材绝大多数是低频长尾——某个边缘节点的第一次请求是 cache MISS,得回 S3 origin(比如 us-east-1)取。一个远离 origin、又命中冷边缘的用户,延迟会叠加:origin RTT + S3 首字节 + 整对象传输(因为 moov 在末尾,播放器必须读到尾部)。faststart 消不掉缓存未命中,但它能让播放器即便在 miss 时也在约 20 KB 后就渲染第一帧,而不是被迫拉完整个对象。换句话说,全球长尾投放让冷缓存成为常态,而冷缓存恰恰是 faststart 收益最大的地方。
3d. CTV 的失败模式:从 ad-impression 到 ad-error
在 CTV 上失败模式更激进。各 OS 厂商对”未开播缓冲”的容忍度差异很大:
- Roku 的
BrightScript Video节点通常在 5–8 秒内对 progressive-MP4 启动超时,触限时抛ErrorReturned; - Fire TV(Android TV 内核)更宽容些,但 ExoPlayer 的
DefaultLoadControl默认bufferForPlaybackMs = 2500ms,超时抛ExoPlaybackException; - Samsung Tizen / LG webOS 原生播放器行为随版本波动很大,但规律一致:长时间没有第一帧 → 触发错误 → 上报 ad-error。
在这每一种容器里,DSP 收到的都不是 ad-impression,而是 ad-error。钱花了,曝光从未被记录。
3e. 回到网络层证据
回到开头的场景:运营预览素材的环境通常是延迟的”最佳下界”——内部 CDN、带宽充足、设备好。端上用户面对的是同一机制的退化版本:带宽更窄、设备更弱、耐心更短。运营预览里的卡顿,是端上黑屏的先行指标。
这同一件事在本文开头的抓包里有完整的网络层签名:每个 .mp4 请求都是 206 Partial Content,每个恰好返回 16.7 kB——播放器在前 16 KB 里嗅探 moov;没找到,同样大小的请求随后耗时从 63 ms 到 6.93 s(110× 的跨度),还有两个干脆是 (pending)。它们一个都没拿到 moov,于是缩略图解码器没有第一帧可解。症状是 6 个里 4 个空白预览;根因是同样那一行 -movflags +faststart 从来没跑过。
4. MRC 可见性:为什么 5 秒启动意味着钱没了
MRC(Media Rating Council)在其 Viewable Ad Impression Measurement Guidelines 里给出了至今仍在沿用的可见视频曝光定义:
当至少 50% 的播放器像素在屏幕上连续可见至少 2 秒、且音频开启时,视频曝光被判定为可见。
这条规则有一个显而易见却至关重要的隐含前提:2 秒计时器只能在第一帧出现之后才开始。一条第一帧 5 秒才出来(30 秒广告则是 8 秒)的视频,在本就停留时间有限的广告位(CTV 通常 5–15 s、移动网页 3–8 s)里,根本攒不够 2 秒可见。
摊到时间轴上:
场景 A 与 B 的时序对比:moov 在末尾时,OMID
start 在约 5 s(15 秒广告,本文主例)触发,30 秒广告则拉到约 8 s;moov 在头部(已应用 faststart)时是约 200 ms。可见性的 2 秒计时器只能在第一帧之后才开始。
场景 A 的第 6 步和场景 B 的第 9 步相差约 4.8 秒(15 秒广告;30 秒广告的差距拉到约 7.8 s)。The Trade Desk、DV360、Amazon DSP 都已经把 vCPM(viewable CPM)默认或推为视频库存的计费基准。在 vCPM 下,一条没做 faststart 的素材意味着实测损失常常落在两位数百分比——具体数字取决于你流量的设备构成、网络构成和停留时间长尾;没有一个可以照抄的通用”X%”。
把这个百分比翻译成损益:假设一家中型 DSP 月度视频投放 $1 M,其中 60% 的库存走 vCPM 合约——
| 可见性损失 | 月度沉没成本 | 年化 |
|---|---|---|
| 5% | $30 K | $360 K |
| 10% | $60 K | $720 K |
| 20% | $120 K | $1.44 M |
这张表是量级示意,不是实测基准:可见性损失率高度依赖你流量的设备 / 网络 / 停留时间构成,5%–20% 只是给你一个”该上心”的数量级。真要拿数字,按下面”10 分钟自查”在你自己的报表里测。
而修复它的成本:一个工程师能在单个 sprint 内独立交付的入库拒收检查。 这个 ROI 在 AdTech 工程 backlog 上别处很难匹敌。
真正的代价是被误诊成”库存质量”问题
上面那张表只算了直接损失。更贵的代价是误诊。
当可见性下滑时,所有人第一眼看到的是第三方测量报告(IAS / DoubleVerify / MOAT)里的一个 topline 数字。trader 的本能是”这流量质量差”——于是把出问题的版位 / SSP 拉黑、把预算改道。但 faststart 缺陷在你自己的素材里,每个版位上都一模一样。结果是双重损失:你既在坏素材上持续流失可见曝光,又基于一个错误信号把好库存拉黑、缩小了自己的可采买供给。
一句话:faststart 是一个素材侧 bug,但它在报表里的特征签名几乎和供给侧质量问题一模一样——这正是它能在生产里活几个月都没人发现的原因。
10 分钟自查:你现在是不是有这个问题?
你不用碰一个 MP4,从报表层就能确认——
- 把可见性 / 视频开播率 / 完播率按
creative_id拆,而不是按版位拆。 一条 faststart 坏掉的素材会表现为一个低可见性离群点,而且在所有版位 / SSP 上都一致地低——这种”放哪儿都差”的一致性是素材侧缺陷的指纹;真正的库存质量问题会随版位变化。 - 把每条素材的可见性和文件大小 / 时长做相关。 负相关(“更大 / 更长 → 可见性更低”)基本就是 faststart 的签名。
- 挖 VAST / 播放器错误报告。 集中在特定素材上的首四分位掉落和 media-load 超时,对应本文开头抓包里那些
(pending)行。
只要命中任何一条,去第 8 节的检查脚本,对那条素材跑 has_faststart 把它钉死。
5. faststart 到底做了什么
ffmpeg 提供 -movflags +faststart 来修这个:
ffmpeg -i input.mp4 -c copy -movflags +faststart output.mp4
一张图说尽一切:
MP4 字节布局前后对比:moov(红 → 绿)从文件末尾挪到 ftyp 之后,stco 表里每个 chunk 偏移都平移 sizeof(moov) 字节。2.5 MB 的 mdat 主体不动。
-c copy 是关键——它告诉 ffmpeg 不要重新编码,只重排容器里的 box。步骤是:
- 把原文件走一遍,把
moov的字节范围和内容读进内存; - 按
ftyp→moov→mdat的顺序写一个新文件; - 重写
moov的stco里每一个 chunk 偏移——因为mdat在文件里的位置变了,每个 chunk 的绝对字节偏移都得增加moov自身的长度。
第 3 步是 faststart 不可能是简单字节交换的根本原因:stco 是一张绝对偏移表,所以 moov 一移动,表里每个数字都得做 += sizeof(moov)。ffmpeg 内部正是这么干的,这也是为什么对大文件来说 faststart 的代价是 O(file_size) 的额外 I/O——一次完整读加一次完整写。
注意: 64 位大文件(> 4 GiB)需要
stco的 64 位变体co64。当源文件已经用co64时 ffmpeg 处理得很正确;当源用stco、但 faststart 后偏移可能超出 32 位范围时,它会自动升级到co64。
6. 在素材流水线的哪一步加它
先把为什么它是强制的钉死。IAB Tech Lab 在 Digital Video Ad Format Guidelines(2015)里写得很清楚:
Placing the MOOV Atom
Digital media may contain a number of different data objects, called atoms, in their files. The movie atom (MOOV atom) contains data necessary for video execution and should be placed at the front of the media file in order to be executed correctly. In some cases, the video won’t even play if the MOOV atom isn’t placed at the front of the file. Video encoding software usually places the MOOV atom correctly if you select options that optimize the video for web, but you should check with your encoding software to find out how to manually check for MOOV atom placement.
要点是:MOOV atom 必须在文件头部;某些情况下视频根本不会播(对应第 3 节里 CTV 的超时失败模式);而”为 web 优化”的编码选项底下就是 faststart。
换句话说,这不是性能优化,而是 IAB 在协议层面对每一条视频广告素材下的硬性要求。落到工程上,它必须出现在素材流水线的某一步——你不能、也不该指望广告主自己在上传时跑 -movflags +faststart。三个常见的落点:
a. 入库 API 里同步处理(适合小文件)
广告主上传 → API 接收 → 立刻跑 ffmpeg -c copy -movflags +faststart → 写进 S3 origin(由 CloudFront 分发)。简单直接。对 ≤ 10 MB 的文件,新增延迟 1–2 s,可以接受。在 AWS 上这一步可以由 S3 PutObject 事件触发一个 Lambda。
b. 入库时入队 + 异步 worker(适合大文件 / CTV)
CTV 素材常有 30–50 MB,同步处理会卡住上传请求。拆成两步:上传时把原文件落进 S3 → 往队列(SQS / Kafka 等)推一个事件 → 一个 worker(Fargate / Lambda)处理后覆写回 S3 origin → 素材状态从 processing 流转到 ready_to_serve,此后 CloudFront 只会分发修好的对象。
c. CDN 边缘函数兜底
在 S3 + CloudFront 架构里,用 Lambda@Edge 的 origin-response 钩子抽样前 100 字节、检查 moov 位置(CloudFront Functions 太轻量,读不了响应体),对任何抓到 moov 在末尾的素材告警。不要在边缘做实时修复——重写 stco 需要 O(file_size) 的双倍 I/O,边缘资源吃不消——把这个当成最后一道防线,监控漏网的。
S3 + CloudFront 的两个配置陷阱: ① 别对
.mp4开 CloudFront 自动压缩,并确保Accept-Ranges: bytes没被破坏——否则206range 行为失效,会回退到整对象下载,等于雪上加霜;② 当长尾流量很多 PoP 都在回源时,打开 CloudFront Origin Shield 加一层共享缓存,削减冗余回源。
做完上面任意一种后,验证标准很简单:让运营再预览一次同一条素材——第一帧应该在 200–500 ms 内出现。 如果还卡,瓶颈就不是 faststart;去查 CDN 命中率、origin 延迟、CORS、解码白名单,以及预览前端是不是又给它套了别的东西。这一步把运营预览变成了端上行为最便宜的回归测试。
7. 几个常见的坑
| 情形 | 需要 faststart? | 为什么 | 该怎么办 |
|---|---|---|---|
| fragmented MP4(fMP4 / CMAF) | ❌ 强加反而变差 | moov 此时只装编码器配置、且已在头部;真正的索引在每对 moof / mdat 里 | 若 ffprobe 显示有 moof box,就是 fMP4,别动它 |
HLS(.ts 或 .m4s 分片) | ❌ 不在本文范围 | HLS / DASH 在 manifest 层解决启动延迟 | 程序化视频素材几乎都是 progressive MP4——因为 VAST 的 <MediaFile> 携带的是单个 URL,不是 manifest |
| 批量入库 | ✅ 但要异步 | 双遍是 2× I/O | 推到 worker 队列;加一个 transcoded → ready_to_serve 状态,别阻塞编码完成回调 |
| MP4 里封 HEVC / VP9 / AV1 | ✅ 与编码无关 | moov 是容器级 box,不在乎编码 | 同一规则;WebM 是 Matroska 容器、不在 VAST <MediaFile> 兼容列表里,无需考虑 |
| VPAID 包装 | ⚠️ 救不了你 | VPAID 已被 IAB 于 2019 年弃用,由 SIMID + OMID 接替;包装层的 AdLoaded ≠ 第一帧渲染 | 仍走 a/b/c 入库选项之一处理;OMID start 是计费层唯一认可的”已播放”信号 |
8. 把它写进入库拒收规则
一条进入 DSP 素材入库流水线的视频素材,至少应该跑三项硬检查:
| # | 检查 | 推荐阈值 / 标准 |
|---|---|---|
| 1 | 编码在白名单内 | H.264 Constrained Baseline / Main / High(默认接受)、HEVC Main / Main10(取决于下游 DSP 和 CTV 接入清单) |
| 2 | moov box 位置 == “头部” | 等价于已应用 faststart |
| 3 | 码率在合理区间 | 720p H.264 ≤ 2 Mbps、1080p H.264 ≤ 4 Mbps、CTV 4K HEVC ≤ 12 Mbps |
本文只展开检查 #2。两个等价实现——按你入库 worker 已经有什么来挑一个:
Python(仅标准库)
def has_faststart(filepath: str) -> bool:
"""检查一条 MP4 的 moov box 是否在 mdat 之前。
仅对 <= 4 GiB 的常规文件有效;不处理 64 位 `largesize` 分支。
"""
with open(filepath, "rb") as f:
# ftyp box 头:4 字节 box size + 4 字节 box type
ftyp_size = int.from_bytes(f.read(4), "big")
assert f.read(4) == b"ftyp", "not a valid MP4 file"
# 跳过整个 ftyp box,落到下一个顶层 box 的开头
f.seek(ftyp_size)
f.read(4) # 跳过下一个 box 的 size 字段
return f.read(4) == b"moov"has_faststart.py
Shell(仅 ffprobe)
#!/usr/bin/env bash
# 用 ffprobe -v trace 导出 box 解析日志,
# 取前两个顶层 box 类型;moov 在前就说明 faststart 已开。
set -euo pipefail
order=$(
ffprobe -v trace -i "$1" 2>&1 \
| grep -oE "type:'(moov|mdat)'" \
| head -n 2 \
| tr '\n' ' '
)
case "$order" in
"type:'moov' type:'mdat' ") echo "OK: faststart ENABLED"; exit 0 ;;
"type:'mdat' type:'moov' ") echo "FAIL: moov at end"; exit 1 ;;
*) echo "UNKNOWN: $order"; exit 2 ;;
esaccheck-faststart.sh
用法:
$ ./check-faststart.sh hero_15s.mp4
OK: faststart ENABLED
$ ./check-faststart.sh broken_15s.mp4
FAIL: moov at end
关于其他工具的依赖:
mp4dump(来自 Bento4)和MP4Box -info(来自 GPAC)也能显示 box 顺序,但二者在入库容器里都不是默认安装的。ffprobe随 ffmpeg 一起发布、在大多数转码 worker 镜像里已经存在,所以上面的 shell 版本兼容性最好。
两条不能妥协的原则
不管你选 Python 还是 shell,钉死两条原则:
- 检查必须在素材离开入库前完成——在它落进存储前就拒收,而不是事后审计;
- 失败必须硬失败——别”先转码、再异步检查、再悄悄修复”;修得越晚,坏掉的曝光攒得越多。
速查表
-movflags +faststart是合规要求,不是性能优化。 IAB Tech Lab 的 DVAFG 说得很明确:MOOV atom 必须在文件头部,否则某些播放器根本不会播。- 在入库入口拒收,而不是在事后审计里。 一段 9 行的 Python 或
ffprobe检查,放在素材流水线的最前端做硬失败——别想着修复。 - 永远别在 CDN 边缘做实时修复。 重写
stco需要O(file_size)的双倍 I/O,边缘节点扛不住。边缘的活儿是抽样监控漏网的,不是转码。 - 运营预览里的卡顿,是端上黑屏的先行指标。 把运营变成你最便宜的回归测试:零成本、覆盖广,每一次复现都是一次免费告警。
- VPAID / SIMID 包装替代不了它。 OMID
start测的是真实的第一帧渲染;包装层的AdLoaded在合约和可见性测量里一文不值。
一个 flag、九行检查、几十 KB 的 stco 重写——它挡住的是你 vCPM 合约里两位数百分比的可见性沉没成本。把这条规则放进素材流水线的入口检查,而不是某个以后再说的 backlog。
延伸阅读
先看同系列里和本篇直接相邻的几篇:
- 程序化广告生态全景:先看全局,再定位”视频素材”在投放链路与可见性 / 测量里的位置。
- App 广告 SDK 深挖:素材渲染、VAST、可见性测量在端上的承载——本篇正是那篇里”另文展开”的一块。
- 媒体 Ad Server 深挖:vCPM / 可见性最终落到哪一层结算。
- Header Bidding:Prebid.js vs Prebid Server:素材进入投放之前的那段竞价链路。
再是规范与一手资料:
- IAB Tech Lab — Digital Video Ad Format Guidelines(2015):第 6 节引文出处;MOOV atom 须在文件头部的规范级要求。
- ISO/IEC 14496-12: ISO Base Media File Format:底层 MP4 容器规范(box 结构、
moov/mdat定义)。 - MRC — Viewable Ad Impression Measurement Guidelines for Video:最初的 2 秒可见性规则。
- IAB Tech Lab — Open Measurement Interface Definition(OMID):究竟是哪些事件驱动可见性计时。
- IAB Tech Lab — VAST 4.3 spec:
<MediaFile>字段约定(progressive vs streaming)。 - IAB Tech Lab — VPAID 2.0 deprecation notice:VPAID 由 SIMID + OMID 接替的官方声明。
- ffmpeg docs — Muxer Options for MP4:各
-movflagsflag 的语义(不止faststart,还有frag_keyframe、empty_moov、separate_moof等)。
附录:术语表
按出场顺序排列,便于速查。
协议 / 容器
- MP4:ISO/IEC 14496-14 定义的视频容器格式,建立在 ISO BMFF 之上。
- ISO BMFF(ISO Base Media File Format,ISO/IEC 14496-12):MP4、3GP、HEIF 等格式底层的”盒中盒”容器规范。
- box / atom:BMFF 容器的基本单元——4 字节 size + 4 字节 type + 内容;“atom”是 QuickTime 时代的旧称,与 box 同义。
ftyp:文件类型 box;第一个 box,声明 major brand 和兼容性。moov:movie box;装着全部必需的解码元数据(track 描述、解码器配置、采样表)。mdat:媒体数据 box;装着真正的视频 / 音频帧字节流。stco/co64:采样表里的 chunk 偏移表,记录每个 chunk 的绝对字节位置;co64是 64 位大文件变体。- fMP4 / CMAF:fragmented MP4;把
mdat拆成多对moof/mdat,每个分片自带索引;HLS-fMP4 和 DASH 在用。 - HLS / DASH:分别是 Apple 和 MPEG 的流式协议,在 manifest 层处理分片;不在本文 progressive-MP4 问题的范围内。
计费 / 测量
- vCPM(viewable CPM):只对可见曝光计费的 CPM 模型,按 MRC 可见性定义。
- MRC(Media Rating Council):制定数字广告测量标准的机构;“50% 像素 × 2s × 音频开启”的可见性规则出自其 guidelines。
- OMID(Open Measurement Interface Definition):IAB Tech Lab 的可见性测量 SDK 接口;
loaded/start/firstQuartile等事件是计费层认可的”已播放”信号。 - VAST(Video Ad Serving Template):IAB 的视频广告响应 XML 规范;
<MediaFile>字段携带 progressive-MP4 URL。 - VPAID(Video Player Ad Interface Definition):视频广告的旧 JS 包装规范,2019 年被 IAB 弃用,由 SIMID + OMID 接替。
- SIMID(Secure Interactive Media Interface Definition):VPAID 的合规替代,把交互层隔离进 iframe。
- measurement vendor(IAS / DoubleVerify / MOAT):第三方可见性 / 反作弊测量公司,产出广告主的 topline 可见性、完播等报告;faststart 缺陷最先在它们的报告里表现为”可见性下滑”。
流水线角色
- DSP(Demand-Side Platform):广告主侧的程序化采买平台,发出 bid request、管理素材。
- SSP(Supply-Side Platform):媒体侧的程序化售卖平台,聚合库存、接受出价。
- OpenRTB:IAB 的实时竞价协议,DSP 与 SSP / ad exchange 之间的标准接口。
- CDN:内容分发网络;视频素材通常通过 CDN 边缘节点交付给播放器。
- S3(Amazon Simple Storage Service):AWS 对象存储,常作为视频素材的 origin。
- CloudFront:AWS 的 CDN,从 S3 origin 取数、在全球 600+ 个 PoP 上缓存 / 分发;支持 Range 请求。
- Lambda@Edge:跑在 CloudFront 边缘的函数;
origin-response钩子能读响应体的头几个字节,适合抽样检查moov位置。 - Origin Shield:CloudFront 里的共享回源缓存层,削减各 PoP 对同一对象的冗余回源。
- creative ingestion(素材入库):广告素材从广告主上传进入 DSP / SSP 系统、被校验存储、待投放的完整流程。
- CTV(Connected TV):联网电视设备(Roku / Fire TV / Tizen / webOS),在播放器缓冲、超时、错误上报上与网页 / 移动端差异显著。
- pre-roll / mid-roll / post-roll:视频广告插入位置——内容之前 / 之中 / 之后。
工具 / 标准文档