提示

返回博客列表

电脑能播、手机播不了:视频兼容性排查与 profile、level、像素格式的那些事

有次交付一批视频给客户,对方反馈"手机上看不了"。我在电脑上用 VLC、PotPlayer、浏览器各试了一遍——全部正常。这让排查变得很困难,因为"在我这儿是好的"。

后来我把 ffprobe 的所有输出都打印出来,一项项去对照目标设备的解码能力,才找到原因:我们用了 10bit HEVC(yuv420p10le)。电脑上的软件解码器全支持,但客户那边几款老安卓手机和一台智能电视的硬件解码器只支持 8bit HEVC,播出来是黑屏或者干脆报错。

从那以后我做视频交付前会过一遍兼容性检查。这篇写完整的排查流程:profile、level、像素格式、codec tag、moov 位置、音频格式,每一项都有设备"不支持就翻脸"的例子,最后给一份最保守的参数模板(以及什么时候该放弃保守)。

TL;DR:排查顺序是 编码格式 → profile/level → 像素格式(8bit/10bit) → 分辨率帧率 → 容器与 codec tag → 音频格式 → moov 位置。最常见的三个杀手:10bit(老硬件解码器不支持)、HEVC/H.265(支持度碎片化,iOS 需要 hvc1 tag)、profile/level 超出设备能力。最保守的通用配置:H.264 + high profile + level 4.0/4.1 + yuv420p(8bit) + AAC-LC 48kHz + -movflags +faststart。真正管用的验证方法是在目标设备上真机播放,而不是在电脑上看。

目录

一、先看一个"播不了"的完整排查表

这是我现在的排查清单(按顺序,从最常见到最少见):

排查项 怎么看 不支持的表现
编码格式 codec_name 黑屏 / 只有声音 / 直接报错
profile profile 部分设备拒绝解码
level level 同上(分辨率/帧率/码率超限)
像素格式 pix_fmt 黑屏(10bit 最常见)
分辨率/帧率 width/height/avg_frame_rate 卡顿 / 播不了
容器 tag codec_tag_string iOS/QuickTime 打不开
moov 位置 ffprobe 能否秒开 播放前要下载完整个文件
音频编码 codec_name(audio) 没声音 / 整体播放失败
采样率 sample_rate 少数设备无声

二、编码格式:H.264 仍然是最大公约数

编码 支持度 说明
H.264 / AVC 几乎 100% 过去十五年所有设备都支持,包括很老的
H.265 / HEVC 约 60~80% 2015 年前的手机/电视基本不支持硬件解码;iOS/macOS 支持好但要求特定 tag
VP9 浏览器好,硬件差 YouTube 大量使用;安卓支持好,iOS Safari 从 14 开始支持(部分)
AV1 新设备 2020 年后的硬件,老设备只能软解(慢、发热)

我的默认选择:

  • 要发给别人看的成片 → H.264(除非确认对方设备都支持 H.265);
  • 自己归档/内部使用 → H.265(省空间);
  • 网页播放 → H.264 为主,可以额外提供 VP9/AV1 档(多码率 ABR)。

一个真实的教训:我曾经给客户全量转成了 H.265(省了 40% 空间,客户很高兴),结果他们把视频发给终端用户,一部分用户打不开。最后又重新出了一份 H.264 版本。省下的存储成本,抵不过一次返工和客户的信任损失。

判断对方能不能播 H.265,最实用的办法是问清楚他们的最低端设备是什么。如果答案里有"老安卓盒子""几年前的电视""某些客户的旧手机",就老老实实用 H.264。

三、profile 与 level:设备的"能力上限声明"

profile 是"用了哪些编码工具",level 是"多大规模"(分辨率 × 帧率 × 码率的组合上限)。

H.264 的 profile

profile 特性 支持度
baseline 无 B 帧、无 CABAC 最老的设备(视频会议、老手机)
main 有 B 帧、CABAC 绝大部分设备
high 加 8×8 变换、自定义量化 2009 年后的设备基本都支持
high10 / high422 / high444 10bit / 4:2:2 / 4:4:4 硬件解码支持很差

结论:

  • 最大兼容 → main(甚至 baseline,如果你要给非常老的设备);
  • 常规使用 → high(压缩率比 main 好 10~15%,兼容性依然很好);
  • 千万别用 high10/high422 做交付(除非确认目标支持)。
-profile:v high

H.264 的 level

level 限制了"解码器要处理多大的数据"。常见的对应关系(以官方规格书为准,这里给实用的近似):

level 典型支持 最大码率(High profile)
3.0 720×480@30 10 Mbps
3.1 1280×720@30 14 Mbps
4.0 1920×1080@30 20 Mbps
4.1 1920×1080@30 / 1280×720@60 50 Mbps
4.2 1920×1080@60 50 Mbps
5.0 2560×1920@30 135 Mbps
5.1 3840×2160@30 240 Mbps
5.2 3840×2160@60 240 Mbps

注意 level 是"能力要求":一个标了 level 5.1 的视频,只支持 4.1 的设备会直接拒绝播放(即使分辨率只有 720p)。

所以我们有个反直觉的优化:如果你把 720p 视频编码时让它用了 level 5.1(ffmpeg 默认会根据分辨率/帧率/码率自动选,有时偏保守地选高),老设备就播不了。显式指定 level 可以避免这个问题:

-level:v 4.1

常见错误:4K 视频忘了指定 level,ffmpeg 自动用了 5.1,结果某些只支持 5.0 的设备播不了。实际上 4K@30 用 5.0 也够(5.0 支持 3840×2160@?)。这种情况我一般会显式指定并实测。

HEVC 的 level 体系类似,但多了 tier(Main tier / High tier),High tier 允许更高码率。老设备的 HEVC 硬件解码通常只支持 Main tier 到 level 4.1(也就是 1080p@30 左右)。

四、像素格式:10bit 是头号杀手

这是"电脑能播、手机不能播"最常见的原因。

pix_fmt 位深 色度采样 兼容性
yuv420p 8bit 4:2:0 最好,几乎所有设备
yuv420p10le 10bit 4:2:0 电脑软件解码 OK;老硬件解码器不支持
yuv422p 8bit 4:2:2 硬件解码支持很差
yuv444p 8bit 4:4:4 同上,更差

10bit 的收益(前面讲过):减少色带(banding),渐变天空这类场景明显,同样码率下 VMAF 略高。

10bit 的代价:

  • 老安卓、老电视的硬件解码器不支持 → 黑屏或者只有声音;
  • 部分浏览器的 HEVC 硬解不支持 10bit;
  • 编码耗时 +15% 左右。

我的规则:

母版 / 归档 → 10bit(画质优先,自己用)
对外交付 / 移动端播放 → 8bit(兼容优先)

怎么检查:

ffprobe -v error -select_streams v:0 -show_entries stream=pix_fmt -of csv=p=0 out.mp4
# yuv420p10le  ← 如果要发给移动端,这就是问题

转成 8bit:

ffmpeg -i in.mp4 -c:v libx264 -crf 20 -pix_fmt yuv420p -c:a copy out_8bit.mp4

注意:从 10bit 转 8bit 是有损的(会重新编码),所以母版留 10bit,交付时再转,别一开始就用 8bit 编码(否则想出 10bit 版就得重来)。

五、分辨率与帧率的上限

设备的解码能力有硬上限,超过就播不了(或者极卡):

设备类型 典型上限
老安卓手机(2015 前) 1080p@30(H.264 high 都勉强)
中端安卓(2018 后) 4K@30 H.264 / 1080p@60 HEVC
iPhone(2017 后) 4K@60 HEVC
智能电视/盒子 差异巨大,便宜的只到 1080p@30
微信内置播放器 有分辨率和码率限制(且会二次转码)

两个常见坑:

1. 高帧率(60fps)比高分辨率更容易出问题。很多设备能播 4K@30 但播不了 1080p@60(因为后者对解码吞吐要求更高)。给不确定设备的内容,30fps 更安全。

2. 奇数分辨率或非常规尺寸。有些硬件解码器要求宽高是偶数(甚至 16 的倍数)。用 -vf scale=1280:-2 保证偶数:

-vf "scale=1280:-2"     # -2 表示自动计算且保证是偶数

-2 而不是 -1:-1 只保证比例,-2 保证偶数。遇到 "width not divisible by 2" 的报错就用这个。

六、容器、codec tag 与 moov 位置

codec tag:iOS 的隐形门槛

MP4 文件里,H.265 有两种 tag:

  • hev1:标准写法;
  • hvc1:Apple 要求的写法。

iOS/macOS 的 QuickTime 和 Safari 只认 hvc1。ffmpeg 默认写 hev1,导致 HEVC 视频在 iPhone 上打不开(在安卓和电脑上正常——又是这个剧情)。

# 转成 iOS 能认的 tag(不重新编码,秒完成)
ffmpeg -i in.mp4 -c copy -tag:v hvc1 out_ios.mp4

H.264 也有类似情况(avc1 是标准,一般没问题)。

moov 位置:影响"能否边下边播"

MP4 的 moov box 是索引。如果它在文件末尾(ffmpeg 默认编码后就在末尾),播放器必须先下载完整个文件才能开始播放——对本地文件无所谓,对网络播放是致命的(用户要等几秒甚至几分钟)。

# 把 moov 挪到开头
ffmpeg -i in.mp4 -c copy -movflags +faststart out.mp4

这个参数我每次交付都加,成本为零(只重写容器,不重新编码),收益是网页/移动端能秒开。

检查:

# 看 moov 在哪(用 python 读 box 顺序,或者简单判断:能否快速起播)
ffprobe -v error -show_entries format=start_time,duration -of csv=p=0 out.mp4

更直接的办法:用浏览器打开,看是不是要等很久才开始播。

容器选择

容器 兼容 说明
MP4 最好 万能选择
MKV 差(移动端) 电脑播放器很好,电视/手机经常不认
WebM 浏览器 VP9/AV1 用
TS 流媒体 HLS 用

交付一律用 MP4(除非是 HLS 分发)。MKV 虽然功能强(多音轨、内挂字体、章节),但移动端支持度差太多。

七、音频:被忽略的那一路

视频播不了,有时候是音频的锅。

音频格式 兼容性 说明
AAC-LC 最好 万能选择
HE-AAC (AAC+) 中等 部分老设备不支持,且音质一般
MP3 很好 MP4 容器里也能放
Opus 差(MP4 里) WebM 里很好,MP4 里支持有限
AC-3 / E-AC-3 中等 电视/影院好,手机一般
FLAC / PCM 差 无损,但很多播放器不支持
# 明确指定 AAC-LC(不要用 HE-AAC)
-c:a aac -profile:a aac_low -ar 48000 -b:a 128k

-profile:a aac_low 很重要:ffmpeg 的 aac 编码器默认是 LC,但如果你用了某些参数或者编码器版本不同,可能输出 HE-AAC(-profile:a aac_he),老设备播不了或者音质怪。

采样率:44100 和 48000 都安全。有些设备对 32000 以下的低采样率或者 96000 的高采样率有问题。统一用 48000(视频的标准采样率)。

声道:立体声(2.0)最安全。5.1 在某些设备上会被降混或者无声。

八、诊断流程:拿着 ffprobe 输出对照

我现在的标准动作(拿到一个"播不了"的文件):

ffprobe -v error -show_format -show_streams -of json out.mp4 | jq '.streams[] | {
  codec_type, codec_name, codec_tag_string, profile, level,
  width, height, pix_fmt, avg_frame_rate, sample_rate, channels
}'

输出示例(有问题的那份):

{
  "codec_type": "video",
  "codec_name": "hevc",
  "codec_tag_string": "hev1",       ← 问题 1:iOS 需要 hvc1
  "profile": "Main 10",              ← 问题 2:10bit
  "level": 120,                      ← level 4.0(120 = 4.0×30)
  "width": 1920, "height": 1080,
  "pix_fmt": "yuv420p10le",          ← 问题 3:10bit 实锤
  "avg_frame_rate": "30/1"
}

三个问题叠加,难怪老设备播不了。修复:

ffmpeg -i in.mp4 \
  -c:v libx264 -profile:v high -level 4.1 -crf 20 -preset slow \
  -pix_fmt yuv420p -vf "scale=1920:-2" \
  -c:a aac -profile:a aac_low -ar 48000 -b:a 128k \
  -movflags +faststart \
  -tag:v avc1 \
  out_compatible.mp4

(H.264 的场景下 tag 一般不用管,avc1 是默认的。)

level 的数字含义:ffprobe 输出的是编码值(比如 120),H.264 的 level 换算为 值 / 30(120 / 30 = 4.0)。HEVC 是 值 / 30(比如 120 = 4.0)。这个换算我每次都要查,干脆记在这里。

九、参数模板:保守 vs 现代

模板 A:最保守(要发给未知设备)

ffmpeg -i input.mp4 \
  -c:v libx264 -profile:v main -level 3.1 \
  -crf 23 -preset slow \
  -pix_fmt yuv420p -vf "scale=1280:-2,fps=30" \
  -g 60 -keyint_min 60 \
  -c:a aac -profile:a aac_low -ar 44100 -b:a 128k -ac 2 \
  -movflags +faststart \
  output_safe.mp4

适用:给客户/终端用户的交付、要在微信里传播、不明确目标设备。

代价:720p@30 + main profile,画质和体积都不是最优,但几乎不可能播不了。

模板 B:常规(1080p,现代设备)

ffmpeg -i input.mp4 \
  -c:v libx264 -profile:v high -level 4.1 \
  -crf 20 -preset slow \
  -pix_fmt yuv420p -vf "scale=1920:-2" \
  -c:a aac -profile:a aac_low -ar 48000 -b:a 128k \
  -movflags +faststart \
  output_1080p.mp4

适用:大多数场景(网页播放、手机播放、近几年设备)。

模板 C:HEVC(内部归档/已知支持)

ffmpeg -i input.mp4 \
  -c:v libx265 -profile:v main -level 4.1 \
  -crf 26 -preset slow -pix_fmt yuv420p \
  -tag:v hvc1 \
  -c:a aac -profile:a aac_low -ar 48000 -b:a 128k \
  -movflags +faststart \
  output_hevc.mp4

-tag:v hvc1 保证 iOS 能播;8bit + main profile 保证老硬件也能硬解。

模板 D:10bit 母版(自己用,不对外)

ffmpeg -i input.mp4 \
  -c:v libx265 -crf 19 -preset slow \
  -pix_fmt yuv420p10le \
  -x265-params "psy-rd=2.0:aq-mode=3" \
  -c:a copy \
  master_10bit.mp4

决策表:

场景 编码 profile/level pix_fmt 容器/tag
未知设备交付 H.264 main / 3.1 yuv420p MP4 + faststart
常规网页/手机 H.264 high / 4.1 yuv420p MP4 + faststart
已知支持 H.265 H.265 main / 4.1 yuv420p MP4 + hvc1
内部母版 H.265 main10 yuv420p10le MKV/MP4
HLS 分发 H.264 多档 high / 4.1 yuv420p TS + master.m3u8

十、坑清单

  1. 用 10bit 交付给移动端 → 黑屏。对外交付一律 8bit。
  2. HEVC 用默认 tag → iOS 打不开。加 -tag:v hvc1。
  3. 没加 +faststart → 网页播放要下载完整文件才能开始。
  4. 用 high10/high422 profile → 硬件解码不支持。
  5. 不指定 level,让 ffmpeg 自动选到 5.1 → 只支持 4.1 的老设备拒绝播放。
  6. HE-AAC 音频 → 部分设备无声或音质差。用 -profile:a aac_low。
  7. 采样率奇怪(22050、96000)→ 少数设备不支持。统一 44100 或 48000。
  8. 奇数分辨率 → 编码器报错或播放异常。用 scale=W:-2。
  9. 60fps 发给老设备 → 播不了或者极卡。不确定就降到 30fps。
  10. MKV 交付 → 移动端/电视经常不认。用 MP4。
  11. 只在电脑上验证 → 电脑上软件解码器什么都支持。必须在目标设备真机测。
  12. 用 VLC 能播就以为没问题 → VLC 自带解码器,不代表系统播放器能播。要用系统原生播放器测。
  13. 微信里播不了 → 微信有自己的转码和限制(分辨率、码率、时长)。要测就在微信里真发一次。
  14. 忽略音频 → 视频没问题但没声音,用户也认为"播不了"。
  15. 多音轨/多字幕 → 部分播放器只认第一条,或者干脆不显示。交付时只保留一条默认音轨。
  16. 颜色和亮度看着不对 → 色彩空间/范围(tv vs pc range)问题,可以看项目里色彩空间那篇。

最后说说"验证"这件事。

我现在的规矩是:交付前,在至少两个真实的目标设备上播放(优先选客户群里最低端的那个设备)。电脑上的播放器(VLC、PotPlayer)自带全套软件解码器,它们能播完全不能说明任何问题——VLC 能播 10bit HEVC 4:4:4 的怪东西,但用户的手机不行。

用系统原生播放器测:iOS 用"照片"或者 Safari,安卓用系统相册/自带播放器,电视就用电视自己的播放器。这才是真实体验。

还有一条经验:问客户要"最低端设备型号",然后按它来设计参数。听起来很土,但这比任何"兼容性最佳实践"都可靠。因为兼容性问题不是"平均水平"的问题,是短板的问题——你按中端设备做,那 5% 的老设备用户就会来投诉,而他们往往是声音最大的那一群。

我们现在的交付文档里会写一行"本视频编码参数:H.264 high@4.1 / yuv420p 8bit / AAC-LC 48kHz / 已开启 faststart,最低支持设备:iPhone 6 及以上、Android 5.0 及以上"。这行字很值钱——它既是承诺,也是出问题时界定责任的依据。

想亲手试试?用 VidDown 一键解析下载

粘贴视频链接即可解析,多平台支持、网页端即用;下载桌面客户端解锁海外平台本地解析,开通会员更享不限次下载。

顶部