做流媒体业务的同学大概都遇到过这样一个问题:为了让iOS用户和Android用户都能正常观看,同一份视频内容往往要准备两套封装——HLS用TS切片,DASH用fMP4切片。转码集群跑两遍,存储也要存两份,成本直接翻倍。CMAF(Common Media Application Format,通用媒体应用格式)就是为了解决这个问题而生的,它让HLS和DASH共享同一套分片文件,从源头上砍掉重复存储和重复分发。这篇文章就来聊聊CMAF到底是什么,以及它在CDN侧为什么能省钱。

CMAF格式到底是什么
CMAF的核心思想可以一句话概括:定义一种统一的、基于fMP4(fragmented MP4)的分片格式,让不同流媒体协议都能直接消费它。它由ISO/IEC 23000-19标准定义,包含两部分内容:一是CMAF Track,也就是一个可以独立解码的媒体轨道,内部由一连串CMAF Chunk组成;二是CMAF Segment,由一个或多个Chunk组成,是网络传输的基本单位。
在CMAF出现之前,HLS的传统实现使用MPEG-TS切片,DASH使用fMP4或WebM切片,两种格式互不兼容。CMAF统一采用fMP4作为容器,并对其内部结构做了严格约束:每个Track必须以ftyp和moov盒子开头,初始化段必须自描述,视频Track必须是每帧一个fragment的moof-mdat结构。正是这些约束保证了同一个CMAF Segment可以被HLS客户端和DASH客户端同时解析。
具体来看,HLS这边只需在m3u8播放列表里把切片声明为FORMAT="fMP4"的EXT-X-MAP加.ts(实际上是mp4后缀)切片,DASH那边在MPD里用SegmentTemplate指向同一批文件即可。下面是一个简化后的对照示例:
<!-- HLS 播放列表 master.m3u8 -->
#EXTM3U
#EXT-X-STREAM-INF:BANDWIDTH=2000000,RESOLUTION=1280x720
chunklist.m3u8
#EXTINF:4.000,
media-720p-001.m4s
#EXTINF:4.000,
media-720p-002.m4s
#EXT-X-ENDLIST
<!-- DASH 清单 manifest.mpd(指向同一批 m4s 分片)-->
<MPD>
<Period>
<AdaptationSet mimeType="video/mp4">
<SegmentTemplate
media="$RepresentationID$/media-$Number$.m4s"
initialization="$RepresentationID$/init.mp4"
duration="4" startNumber="1"/>
</AdaptationSet>
</Period>
</MPD>可以看到,两份清单指向的媒体分片完全相同,服务端只需要生成一次。这正是CMAF降低存储成本的第一层逻辑:存储一份文件,服务两种协议。
CMAF如何降低CDN存储与带宽成本
第一层收益刚才已经说了,是消除协议级的重复存储。假如你的业务有2000小时点播内容、8档码率,用传统方式HLS和DASH各存一份,那就是32000个分片集合;切换到CMAF之后直接减半。对于UGC或长视频平台,这个数字乘上CDN边缘节点的副本数,节省的存储容量是相当可观的。
第二层收益体现在缓存命中率上。CDN边缘节点是按URL做缓存的,HLS和DASH如果使用不同分片文件,即使内容完全一样,也会在边缘节点各占一个缓存槽位。统一成CMAF之后,iOS和Android用户请求的是同一个URL,缓存空间里一份内容就能命中两类流量,缓存命中率上升,回源带宽自然下降。回源减少意味着源站出口带宽压力降低,这部分费用在流媒体账单里往往占比不小。
第三层收益是转码资源的节省。转码是CPU密集型任务,同样的内容如果要分别输出HLS的TS流和DASH的fMP4流,相当于做了两次完整的编码封装流程。CMAF让一次编码产出一套fMP4分片,转码集群的机器数量可以按比例缩减。举个数字化的例子:某平台原本100台转码机满负荷运转,迁移到CMAF统一输出后,理论上可以省下接近一半的封装开销,编码本身如果ABR阶梯一致甚至可以完全复用。
低延迟直播与CMAF Chunked传输
CMAF还有一个常被忽视但很关键的能力:低延迟直播。传统HLS的延迟主要来自切片长度,如果每个分片4秒、播放器缓冲3个分片,端到端延迟至少10秒以上。CMAF允许把一个Segment再切成多个更小的Chunk(比如每帧或每半秒一个),编码器一边编码一边往外推,CDN收到一个Chunk就立即转发,播放器收到Segment完整数据之前就开始解码。这种模式叫CMAF Chunked Delivery,配合HLS的EXT-X-PREFETCH或LL-HLS的blocking playlist reload,可以把直播延迟压到3秒以内。
对CDN来说,Chunked传输的挑战在于代理链路必须支持流式透传。有些老旧的代理服务器会等整个HTTP响应体到齐再转发,这会直接抵消掉Chunked带来的延迟优势。Nginx默认开启proxy_buffering,需要显式关闭:
# Nginx 反向代理配置,关闭缓冲以支持 CMAF Chunked 低延迟转发
location /live/ {
proxy_pass http://encoder_origin;
proxy_buffering off; # 关键:逐块转发,不等完整分片
proxy_request_buffering off; # 客户端请求体也流式处理
proxy_http_version 1.1;
add_header Cache-Control no-cache;
}需要注意的是,低延迟模式下的部分缓存策略要做取舍。未完成的Segment不能按普通文件缓存,CDN通常只对已完成的Segment做缓存,对正在传输中的Chunk走透传。因此低延迟直播和点播场景的CDN配置要分开设计,别指望一套配置通吃。
实际部署中的注意事项与常见坑
第一个坑是加密方式的兼容性。HLS常用AES-128对TS切片加密,而CMAF场景下更推荐使用CENC(Common Encryption),它允许同一份加密内容被HLS和DASH同时解密。如果你在CMAF上仍坚持用AES-128的SAMPLE-AES方案,务必确认HLS侧声明了EXT-X-KEY的KEYFORMAT,且DASH侧的DRM系统能解析同样的密钥,否则就会出现某端能播另一端黑屏的问题。
第二个坑是编码参数的一致性。CMAF要求同一内容的多码率版本在关键帧对齐、GOP结构上一致,否则切换码率时播放器需要重新下载初始化段,造成卡顿。编码侧建议固定GOP、固定关键帧间隔(比如2秒),并在所有码率上保持一致的分片边界。
第三个坑是CDN回源与预热的配合。虽然CMAF减少了文件数量,但清单文件(m3u8和MPD)仍然是两份,预热任务要同时覆盖两种清单及其引用的分片。另外,部分老旧CDN节点对.m4s扩展名的MIME类型默认返回application/octet-stream,会导致某些播放器下载而不是流式播放,部署前记得确认节点返回video/iso.segment或至少video/mp4。
总体来说,CMAF是一项投入产出比很高的技术改造:一次编码、一份存储、一套缓存,同时服务HLS和DASH两套生态,还能顺带解锁低延迟直播能力。如果你的业务仍在为两套协议重复付存储和带宽费用,CMAF值得认真评估。