Apache 在 HLS 与 DASH 场景里通常不是转码器,而是 HTTP 分发层。真正的分片生成交给 FFmpeg、Shaka Packager 或媒体打包服务完成,Apache 负责把 m3u8、mpd、ts、m4s 等文件稳定地推给播放器。配置不好时,常见表现是直播画面突然卡住、切换码率失败、网页播放器报跨域错误,或者 CDN 把旧清单缓存太久。要解决这些问题,需要同时关注 MIME 类型、缓存控制、清单过期策略、分片命名和 Range 请求支持。

HLS 分片在 Apache 中如何配置 MIME 与缓存
HLS 的核心是 M3U8 清单和媒体分片。传统 HLS 使用 TS 分片,文件扩展名通常是 ts,现代低延迟 HLS 也可能使用 CMAF 分片,扩展名常见为 m4s。Apache 默认不一定识别这些类型,若服务器返回 application/octet-stream,部分浏览器或播放器仍能播放,但移动端 Safari 和某些 CDN 对 MIME 更敏感,最好显式声明。清单文件 application/vnd.apple.mpegurl 是 HLS 标准类型,m4s 可用 video/mp4 或 video/iso.segment,具体取决于打包器输出和客户端兼容性。
缓存策略要按文件角色拆开。M3U8 在直播中会不断追加分片,如果缓存时间过长,播放器拿到旧清单,就会认为节目停止更新;如果完全不缓存,又会让源站承受大量清单轮询。一个比较稳妥的做法是给直播清单设置很短的 max-age,同时允许 CDN 稍长一点 s-maxage,并用 must-revalidate 或 stale-while-revalidate 控制过期行为。TS 或 m4s 分片一旦生成通常不再修改,只要文件名唯一,就可以设置很长的 Cache-Control 并标记 immutable,让边缘节点长期保留。
<Directory /var/www/hls>
Options FollowSymLinks
AllowOverride None
Require all granted
</Directory>
AddType application/vnd.apple.mpegurl .m3u8
AddType video/mp2t .ts
AddType video/mp4 .m4s
AddType video/mp4 .mp4
<FilesMatch "\.m3u8$">
Header set Cache-Control "no-cache, must-revalidate"
</FilesMatch>
<FilesMatch "\.(ts|m4s|mp4)$">
Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>
代码中 FilesMatch 与 Directory 是 Apache 配置指令,实际写入配置文件时直接书写即可。如果站点使用 .htaccess,可把 Header 与 Expires 指令放入目录文件,但生产环境更推荐在主配置中开启 mod_headers 与 mod_expires,减少每请求读取权限文件的开销。对于直播 HLS,还可以给清单单独设置 ETag 或 Last-Modified,但要注意如果清单内容频繁变化,ETag 命中价值有限,关键还是控制客户端刷新频率。
DASH MPD 与分片模板配置要点
DASH 的入口通常是 MPD 文件,它描述周期、适配集、表示、初始化段和媒体段。与 HLS 清单相比,MPD 更结构化,也更容易被错误缓存。直播 DASH 的 MPD 需要包含 availabilityStartTime、minimumUpdatePeriod、timeShiftBufferDepth 等字段,播放器根据这些值决定多久刷新一次清单、能回看多久。若 minimumUpdatePeriod 设置过大,画面会明显滞后;若设置过小,清单请求会暴增。Apache 作为分发层不生成 MPD,但必须保证 MPD 的响应头与文件类型正确。
分片模板是 DASH 配置的关键。SegmentTemplate 使用 $Number$ 或 $Time$ 生成媒体段地址,初始化段通常以 init.mp4 或 init.m4s 命名,媒体段以 segment 编号或时间戳命名。文件名必须避免覆盖,否则 CDN 节点可能把旧分片缓存住,播放器请求新时间段时拿到错误内容。若采用单一文件加字节范围的 SegmentBase 方案,Apache 必须支持 Range 请求,并且动态脚本不能吞掉 Accept-Ranges。静态文件分发通常由 mod_core 自动支持范围请求,但反向代理或 CGI 层需要特别检查。
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011"
type="dynamic"
availabilityStartTime="2026-01-01T00:00:00Z"
minimumUpdatePeriod="PT2S"
timeShiftBufferDepth="PT60S">
<Period>
<AdaptationSet mimeType="video/mp4" segmentAlignment="true">
<Representation id="v1" bandwidth="1200000" width="1280" height="720">
<SegmentTemplate media="segment_$Number$.m4s"
initialization="init_$RepresentationID$.m4s"
startNumber="1"
duration="2000"
timescale="1000" />
</Representation>
</AdaptationSet>
</Period>
</MPD>
Apache 对 DASH 常见扩展名也要配置。MPD 使用 application/dash+xml,初始化段和媒体段如果是 MP4 片段,可使用 video/mp4,音频片段可使用 audio/mp4。对于直播回看窗口内的分片,建议设置较短过期时间或让清理脚本删除旧分片,因为 DASH 客户端可能按时间请求历史片段。若使用长期缓存,需要确保不可变文件名和足够长的回看窗口,否则播放器请求已被删除的片段时,源站返回 404,而边缘节点可能把 404 缓存住,造成持续故障。
跨域、Range 与 CDN 缓存分层策略
网页播放器常从不同域名加载清单和分片,因此 CORS 是 Apache 流媒体配置绕不开的一环。若 MPD 与 m3u8 不在允许来源内,播放器会在清单请求阶段失败。简单做法是给清单和媒体分片都返回 Access-Control-Allow-Origin,但若前端需要携带 Cookie 或凭证,就不能使用通配符,必须精确回显 Origin,并设置 Access-Control-Allow-Credentials。预检请求也需要允许 Range 相关头部,例如 Accept-Ranges 与 Content-Range 的暴露,否则某些分片下载库无法正确判断字节范围。
<IfModule mod_headers.c>
<FilesMatch "\.(mpd|m3u8)$">
Header set Cache-Control "no-cache, must-revalidate"
Header set Access-Control-Allow-Origin "*"
</FilesMatch>
<FilesMatch "\.(mp4|m4s|ts|m4a)$">
Header set Cache-Control "public, max-age=31536000, immutable"
Header set Access-Control-Allow-Origin "*"
</FilesMatch>
</IfModule>
生产环境更推荐把缓存拆成源站与 CDN 两层。源站可以保留较短的清单缓存,避免频繁回源;CDN 对清单使用秒级 TTL,对分片使用长 TTL 和 immutable。这样既能保证播放器及时看到新片段,又能让热门分片在边缘节点命中。若使用多码率 HLS,master m3u8 与子清单的缓存策略也要区分:master 文件变化较少,可以比媒体清单缓存稍长;媒体清单变化频繁,需要更短。DASH 的 MPD 同理,静态点播 MPD 可以长缓存,动态直播 MPD 必须短缓存。
分片大小与延迟是一对矛盾。HLS 传统分片常取 4 到 6 秒,兼容性好,但端到端延迟较高;低延迟 HLS 或 LL-HLS 会把分片切到 1 到 2 秒,甚至使用 part 文件进一步缩短等待。DASH 也类似,过小的分片会增加清单和 HTTP 请求数量,过大的分片则让播放器等待首帧和切换码率。Apache 本身不关心分片时长,但清单和分片数量会直接影响 QPS 与缓存命中率,因此架构设计时要把分片时长、回看窗口、清单刷新间隔和 CDN TTL 一起计算,而不是单独调某一项。