☰
ISO/IEC 23008-12 与 HEIF/HEIC 容器:结构、属性与互操作实践
2026/9/30 11:33:55 网站建设 项目流程

简介:ISO/IEC 23008-12:2017 定义了高效图像文件格式(HEIF/HEIC)的国际标准体系,覆盖图像编码、文件封装、元数据、安全性要求,主要面向开发人员、测试人员以及视频和图像编码研究人员。标准聚焦于异构环境下多媒体内容的压缩与分发,阐述了基于 HEVC 的图像压缩方式、单图与图像序列的储存规则、派生图像和元数据的组织方式,并规定了编解码器兼容性和互操作性测试方法。该标准为2017年12月发布的第1版,资源为1个正式英文PDF文件,压缩包约906KB,文件内含完整目录、术语、范围及规范性引用等章节,方便按条款查阅。已有258人学习下载,适合需要深入理解 HEIF/HEIC 原理、设计图像处理管线或进行标准符合性验证的读者。掌握该标准可清晰把握文件层级结构、编码参数与数据保护机制,为实际项目选型和工程落地提供可靠依据,也能减少格式兼容性排查成本。

1. 打开 ISO/IEC 23008-12:2017:HEIF/HEIC 图像容器不只是换后缀

我们见过太多问题了:一张 .heic 照片传到 Windows 上只有文件图标没有缩略图,服务端把 HEIF/HEIC 批量转 JPEG 时方向偶发不对、颜色发灰,安卓端收到的 HEIC 在部分机型上直接黑屏。这些问题的根源大多不在 HEVC 编解码,而在容器层。ISO/IEC 23008-12:2017 定义了 HEIF(High Efficiency Image File Format)的完整容器规则:box 结构、图像项索引、属性绑定、派生图像、序列轨道和元数据组织。它和 HEVC 编码标准相互独立,但实际 HEIF/HEIC 文件绝大多数装载的就是 HEVC 码流。把这份标准吃透,等于给图像处理链路补上最后一块容易忽略的拼图。这篇笔记从标准条文出发,落到我拆解、生成和测试 HEIF/HEIC 的工程经验,适合后台和客户端的图像处理开发者。

2. 容器解剖:ISO/IEC 23008-12 的 Box 体系与关键属性

2.1 从 ISO 基础媒体文件格式继承,但用 meta 讲图像故事

打开任何一个 HEIF/HEIC 文件,你面对的前缀结构几乎和 MP4 一样。因为在标准体系里,HEIF 本来就是 ISO/IEC 14496-12(ISO Base Media File Format,简称 ISOBMFF)的一个图像扩展。文件还是那套 box 语言:ftyp 声明品牌,meta 承载与图像相关的全部目录信息,mdat 存放编码后的比特流,moov 负责轨道级索引,主要用于图像序列场景。

排查 HEIF 文件时的第一件事,不是把它丢给解码器,而是先看一眼文件头。ftyp box 的布局很直白:前四个字节是 box 大小,然后是 box 类型和 major_brand。mif1是通用 HEIF 品牌,heic表示图像项用 HEVC 编码,heix是 HEVC 序列或扩展场景。不同品牌组合决定了解读预期,很多“打不开的 HEIF”,其实只是兼容品牌列表里缺少目标播放器认识的标识,播放器先行过滤,根本不往下解析。

$ xxd -l 64 sample.heic 00000000: 00000024 66747970 68656963 00000000 68656963 ...

这段输出里,00000024是 box 大小,66747970是 ASCII 的 ftyp,后面的68656963对应 heic 品牌。我一般会再往下扫 meta box 的位置,方便后续手工解析定位起点。标准第 5.1 节同时要求文件满足 ISOBMFF 的一般性约束,meta 和 mdat 在文件里的前后位置并不强制,可以在 mdat 之前也可以在之后。解析时不要假设 meta 永远在文件头,按 box 类型逐个扫描才靠谱。我们在服务端转码时遇到过文件开头拼了一个超大的 free box,meta 被推到很后面,用固定偏移解析的旧代码直接读错位置,这是典型的把 ISO BMFF 结构想得太简单的教训。

2.2 pitm、iinf、iloc:一张 HEIF 的目录与数据索引

HEIF 将一张张图像抽象为 image item。每个 item 有一个整数 ID,数据本体可能是 HEVC 编码码流,也可能是派生规则描述,还可能只是辅助数据。meta box 里的三个 box 一起构成目录体系:

  • pitm(primary item box):指明默认显示的那个 item ID;
  • iinf(item information box):枚举所有 item,记录类型、名称,并区分它是编码图像、派生图像,还是普通元数据项;
  • iloc(item location box):记录每个 item 的字节偏移、长度,以及数据引用方式。

解复用流程并不复杂:先解析 meta,取 pitm 的 item ID,再查 iinf 确认类型和属性绑定情况,最后从 iloc 拿到数据的 offset 和 size,按需抽取字节交给解码器。标准允许一个 item 由多个 extent(分片)组成,所以读取时不能假设数据是连续一份,要按 extent 逐段读取并拼接。我早期照着标准写最小解析器时,就因为在 iloc 里没处理多 extent,结果拿到半段码流,解码器一直报数据不完整。多 extent 什么时候出现?编码器为了内存拷贝效率,有时会把编码数据和附加数据(比如分块 alpha)拆开放。服务端做后处理时,最好实现完整的 extent 拼接逻辑,而不是用“取前 N 字节”的捷径。

iloc里的 construction_method 字段决定数据是内嵌还是外部引用。HEIF 里常用的是 0(内嵌)和 1(外部文件引用)。标准允许外部引用,但很多商业播放器在碰到 construction_method 非 0 时会静默跳过。稳妥的写端策略是强制把图像项数据内嵌,引用方式虽然合法,互操作性却很差,不值得为省几个字节去赌对方实现。

2.3 属性绑定:ispe、irot、imir、colr 怎么跟着图像项走

图像项光有数据还不够,它显示多大、方向如何、颜色怎么解释,全由属性框决定。标准第 6.5 节列出的常见属性有:ispe(图像空间范围,即宽和高)、pixi(像素通道和位深)、colr(色彩信息,由 colour primaries、transfer characteristics、matrix coefficients 组成)、pasp(像素宽高比)、irot(旋转,只允许 90 度的整数倍)、imir(镜像)、clap(干净孔径,用于精确裁剪)、auxC(辅助图类型声明)等。

属性本身不散落在图像项里,而是统一放在 iprp(item properties box)的 ipco 容器中,再用 ipma(item property association box)把属性绑定到具体 item ID。这个“定义与使用分离”的结构带来一个好处:多个图像项可以共享同一份属性。比如缩略图和主图的色彩信息一致,只需要在主图那条属性关联之外,再给缩略图关联同一个属性索引即可,不需要复制一遍 colr 的字节。

但属性是有语义顺序的。标准对几何变换的处理顺序有明确约束,例如先镜像还是先旋转、裁剪发生在哪个阶段,读取端不能按自己的美术直觉乱排。irot 和 imir 的先后关系,很多实现都翻过车。我自己的做法是画一条属性处理管线:解码像素 → 按 ispe 确定基准显示尺寸 → 应用 clap 裁剪 → 应用 irot/imir 几何变换 → 按 colr 转换色彩空间,每个环节对照标准条款核对。在写端,属性顺序问题就变成 ipma 里的关联顺序。有的解析器直接按 ipma 中条目出现的先后顺序应用属性,如果写入顺序和标准隐含语义不一致,同一文件在不同读取端就会呈现不同方向。所以写端不但要把属性写全,还要把关联顺序写对。

3. 图像角色与序列:静态图集合和动态 HEIF 的区别

3.1 图像角色的职责:cover、thumbnail、auxiliary、hidden

一个 HEIF/HEIC 文件里装多张图是常态,不是异常。标准第 6.4 节定义了若干角色:cover image(封面图,pitm 指向的就是它)、thumbnail image(缩略图)、auxiliary image(辅助图,常见的是 alpha 通道或深度图)、hidden image(默认不显示的隐藏项)、master image(被辅助图引用的主图)。角色不直接写成一个字符串,而是通过属性、辅助图类型以及 item 之间的引用关系表达。

苹果的 HEIC 就是多图像项的典型。从 iPhone 导出的文件,除了主图,还经常带缩略图或其他辅助数据。如果拿最朴素的处理方式——直接解第一个 image item——很容易把缩略图当主图,或者把隐藏的辅助图当成独立照片渲染出来。所以读端两个动作必须做稳:pitm 定位主图,iinf 遍历全部 item 并过滤角色。写端的对应坑是:如果把 alpha 图直接写成一个普通编码 item,而没挂 auxC 属性声明它是辅助图,播放器会把它当成另一个独立图片显示,透明合成效果直接失效。写辅助数据必须同时写入 auxC 属性并绑定到该 item,还要在字段里声明它辅助的是哪一个主图,读取端才能正确建立“主图 + 辅助图”的合成关系。

标准里还允许存在“隐藏图像项”,这类 item 参与派生计算或元数据引用,但默认不展示。部分实现会在解码时忽略隐藏项,在需要读取深度图、HDR 合成源或灰度掩膜时就会抓瞎。如果业务确实依赖隐藏数据,写端要确认目标读取端支持对隐藏项的访问,不要默认所有播放器都会暴露这个入口。

3.2 图像序列:为什么编码约束 box 很重要

HEIF 不止放静态图,它也能表达带时间维度的图像序列。在文件格式上,序列呈现为一个常规视频轨道,handler 类型约定为 pict,图像帧就是轨道的样本。标准第 7 章专门讲了序列轨道的要求,其中最容易被忽略的是 coding constraints box。这个 box 声明序列内部所有帧的编码一致性约束:所有帧都应该使用相同的 profile、level、分辨率,以及可预测的参数集结构。

为什么强制一致性?因为播放器在序列播放时需要随机访问。如果中途某帧换了参数集,解码器就得重新创建会话,首帧延迟和跳帧都会恶化。HEIF 序列的编码策略不能像单张图片那样每张自由配置参数,而是要像视频编码一样维持端到端稳定。处理实况照片和动态壁纸项目时,我把序列当“低帧率视频”来约束:分辨率固定,帧内不切换 profile,参数集只放一份。正是这些约束保证了手机上点开实况照片时能快速出第一帧,那些首帧要等半秒以上的体验,多半是写端没有遵循序列一致性导致的。

序列轨道还有一个细节:标准定义了 direct reference samples list 这类样本组,用来声明帧与帧之间的参考关系。做缩略图序列或辅助序列时,这种样本组能告诉读取端哪些帧可以独立解码、哪些帧依赖其他帧。如果写端漏掉参考关系声明,播放器做 seek 时可能随机花屏,又回到“看起来能放、实际上不能跳”的兼容泥潭。

3.3 派生图像项:不存储像素的生成规则

标准最特别的地方是派生图像项(derived image item)。一个派生项没有任何编码数据,而是携带一个操作方法描述,运行时由读取端执行操作、从输入图像项生成输出像素。标准定义了若干派生操作类型:grid 把多个图块拼接成一个大图;iovl(image overlay)把一个或多个图像按坐标叠放;idit 或 identity 直接透传源图。

派生项的存储优势很明显:拼接全景图时不用重新编码一张超大的位图,只需要记录各图块的位置和顺序,读取端解码后现场拼。这在分发多分辨率瓦片和全景漫游图层时能把文件体积压得很低。但工程上必须接受现实:派生项的支持是生态短板。我实测过,grid 派生 HEIF 在一部分 Android 设备上能正常出图,在另一台型号上直接报 unsupported image item。如果产品要大规模分发,派生项适合作为客户端“高级特性”使用,服务端持久化时最好预渲染成普通编码图像项,避免依赖端上运行时推导能力。标准给出了语法,但语法正确不等于生态接受。

4. 动手落地:检查、读取、转换与写入 HEIF/HEIC

4.1 先检查:heif-info 与 box 结构验证

libheif 是应用最广的 HEIF 开源实现之一,命令行工具 heif-info 能列出文件里的图像项和属性:

$ heif-info sample.heic file contains 2 images image[0]: 4032x3024, codec hevc, properties: ispe, colr, irot image[1]: 176x144, codec hevc, properties: ispe, colr

输出里 image[0] 是主图,4032x3024 是 ispe 描述的目标显示尺寸,不是解码器必须吐出的原始尺寸;codec hevc 表示码流是 HEVC;属性列表里的 irot 说明主图带旋转。如果这个文件没有输出 irot,而原始照片是竖拍的,多半是写端把方向做进了像素而不是属性,后续做缩放时容易二次变形。手工查看 box 层级时,xxd 够用:

$ xxd -l 128 sample.heic

观察 ftyp、meta、mdat 的顺序和偏移。遇到 meta 出现在 mdat 之后不要诧异,按 box 链依次解析,不要硬按固定偏移取数。解析 meta 内部层级时,我习惯把 meta 整体读进内存再逐层剥,因为内部 box 有嵌套和 length 前缀,直接看十六进制容易花眼。

4.2 转码与批量转换:heif-convert 与 ffmpeg

heif-convert 是 libheif 自带的简单转换器,默认只处理主图:

$ heif-convert sample.heic output.jpg

它不会挑缩略图,更不会处理多图像项里的辅助图。要做批量转码、缩放、质量控制的,直接上 ffmpeg:

$ ffmpeg -i sample.heic -vf "scale=1920:-1" -q:v 2 output.jpg

这条命令有三个关键参数:-i指定输入 HEIC,ffmpeg 通过 libheif 封装层自动解出主图;-vf "scale=1920:-1"是视频滤镜,宽度固定 1920,高度按原图比例自动计算,-1表示保持宽高比,避免人为拉伸;-q:v 2是 JPEG 编码质量参数,数值越小质量越高,2 属于视觉无损区间,存档场景建议显式指定,否则 ffmpeg 默认的 JPEG 质量可能偏保守。

需要提醒的是,ffmpeg 转 HEIC 到 JPEG 时,默认会把 irot 属性应用到像素上,同时可能保留源文件的 EXIF Orientation。这两个“方向信息”如果叠加,就会得到旋转两次的结果。安全操作是在输出时重置 EXIF 方向字段,具体做法我在下一章避坑实录里展开。

4.3 Python 批量处理:读取属性与 EXIF

服务端更常遇到 Python 批量处理场景。pillow-heif 把 libheif 封装成 Pillow 插件,注册 opener 后直接使用:

from PIL import Image import pillow_heif pillow_heif.register_heif_opener() img = Image.open("sample.heic") print("size:", img.size) print("mode:", img.mode) print("info keys:", list(img.info.keys())) exif_raw = img.info.get("exif")

这里的img.size不等于 ispe 里的宽高,因为 Pillow 在打开时已经应用了 irot 等显示属性,竖构图照片的 size 会直接变成 3024x4032。img.info里能拿到 exif 原始块,但它是字节流,格式化解析还需要 piexif 或 exifread。这个库和 libheif 版本同步更新,生产环境建议锁定版本号,避免底层 HEVC 库行为变化影响输出稳定性。如果程序需要读取辅助图或某个特定 item,Pillow 插件并不直接暴露 item ID 的细粒度控制。这时候要么回到 libheif 的 C API,要么先用 heif-info 确认 item 布局,再决定解码策略。批量缩略图只取主图即可,不必深入 item 级。

4.4 写入 HEIF:heif-enc 与编码配置

写出 HEIF 使用 heif-enc:

$ heif-enc -q 80 -o output.heic input.png

-q 80是质量系数 0-100,映射到 HEVC 编码器的量化参数,值越高保留细节越多、文件越大。面向移动端兼容时,把编码约束在 main profile、8bit、4:2:0 更安全,不要随手开 10bit。不同 libheif 发行版的命令行选项名有差异,动手前先执行heif-enc --help确认当前版本支持哪些参数。用属性表达旋转的写法类似这样:

$ heif-enc --rotation=90 -o rotated.heic input.png

指定 rotation 时,工具写入 irot 属性而不是重采样像素,输出文件还能保持原始像素矩阵。这个细节很实用:做无损旋转、方向修正时,改属性比转像素更安全,也更快。但注意 irot 只接受 90 度的整数倍,任意角度旋转只能走像素重采样,别指望用属性描述 45 度。写入时的常见陷阱是色彩信息:从 PNG 转 HEIF 时,源文件色彩描述会带到 colr,PNG 通常没有完整的三段色彩参数,转出来的 HEIF 在部分播放器上颜色可能偏淡或偏灰。写端最好是显式指定色彩参数,或先转成已知色彩空间再编码。

5. 避坑与常见问题:兼容性、旋转、颜色与多帧处理

5.1 同一 HEIC 在苹果生态正常、Android 部分机型黑屏

现象:用 heif-enc 默认参数产出的 HEIC 在 iPhone、Mac 上一切正常,推到部分 Android 机型上相册直接黑屏或提示不支持。

原因:查看编码信息后发现,内部 HEVC 码流是 main10 profile 或 10bit 4:2:0,目标设备硬件解码器只支持 8bit main profile,软件解码路径又没兜底,于是画面完全解不出来。

解决:面向多端的 HEIC,编码统一约束为 main profile、8bit、4:2:0。在 heif-enc 里显式指定参数,并准备一份 Android 真机兼容清单做回归。如果某些场景必须 10bit HDR,就做成专用文件并配套检测逻辑,不要在通用链路里混用。这里的本质是:HEIF 容器标准放开了编码档次,但终端设备没有放开,写端要替用户做兼容性收缩。

5.2 竖拍 HEIC 在相册里显示成横图

现象:同一张竖构图图片,系统相册看着正常,web 上传组件处理后变成横图。

原因:读取端解码后忽略 irot 属性,直接把解码器输出的 4032x3024 原始像素交给上屏逻辑。写端的文件是对的,错在读取端不认属性。这个问题在自研播放器、老旧 SDK 里反复出现。

解决:写端尽量兼容这种“弱读取端”,在写入 irot 属性的同时,把 EXIF Orientation 也写对。很多平台读取 EXIF 的优先级高于解析 irot,能靠 EXIF 把方向救回来。渲染端做缩略图时,服务端必须先合并 irot/imir 再输出像素,不能直接把解码结果落成 JPEG。两台设备之间传文件方向错乱,百分之八十是这里没对齐。

5.3 ffmpeg 转码后图片方向被转了两次

现象:HEIC 转 JPEG 后,在浏览器里方向正常,在 Windows 照片查看器里变成旋转 90 度的横图。

原因:ffmpeg 解码时已经应用了 irot 属性,把像素旋转到位,但生成的 JPEG 里残留的 EXIF Orientation 标签没有被清理,Windows 照片查看器又会按 EXIF 再转一次,等于转了两遍。

解决:转码命令里显式处理元数据。常见做法是用-map_metadata过滤方向相关标签,或者转完后用 exiftool 执行exiftool -Orientation=1 output.jpg。如果后端转码链路里有多步处理,每次落盘都校验一遍方向信息,确保只有一层应用。这条坑在缩略图服务里特别常见,因为上游和下游各自做了一次方向修正,叠加起来就出问题。

5.4 实况照片 HEIC 序列首帧要等一秒多

现象:手机相册打开实况照片时,画面先短暂黑场再出第一帧,体验明显劣于普通视频。

原因:序列轨道内部每个样本都内嵌了各自的参数集,播放器无法在打开容器时确认解码环境,只能等解析到首帧参数集后初始化解码器;加上首帧是关键帧,解码负载集中在开头,延迟被放大。

解决:序列编码时统一参数集,VPS/SPS/PPS 在样本描述里只写一份,编码中禁止切换 profile/level。对关键业务,把首帧位置和参数集信息在 moov 中提前暴露,让播放器在 seek 到具体样本之前就完成解析。这里对应标准第 7 章 coding constraints box 的落地:序列不是多张独立图片的简单堆叠,它需要视频级的编码纪律。

5.5 色彩空间描述缺失导致整体偏色

现象:同一张 HEIC 在 macOS 上看颜色正常,在某个 Linux 看图软件上明显偏灰,肤色发绿。

原因:colr box 缺失或只写了部分字段,读取端不知道应该按 sRGB 还是 BT.709 来解释 YUV,只能按自己的默认值猜,猜测和显示环境不匹配,色彩就偏了。

解决:写端强制写全 colr 三个字段:colour primaries、transfer characteristics、matrix coefficients。转换流程里如果源格式没有完整色彩信息,先按行业默认 sRGB 归一化再编码。读端遇到缺失 colr 时按 sRGB 处理,并在日志里告警,而不是静默猜测。颜色问题比方向问题更隐蔽,因为它在单个平台上“看起来正常”,只有跨设备对比时才露馅。

6. 把标准条款变成自动化验证用例:HEIF/HEIC 互操作性测试习惯

标准文本写得再清楚,也只有变成可重复的验证清单才有工程价值。我维护着一套 HEIF 互操作性测试包,里面固定放几类样本:8bit main profile 主图、10bit 高动态范围图、带 irot/imir 几何属性的多方向图、带 alpha 辅助图的多 item 文件、带 grid 派生项的图集,以及两段 HEIF 图像序列。每次修改编码器参数或升级 libheif 版本,我就在全平台跑一遍这套样本,用统一检查脚本把结果汇总成报告。

验证的核心项不多,但每项都有明确判定方式:

验证点检查方式通过标准
主图选择heif-info 对比 pitm默认显示的 item ID 符合预期
旋转方向解码后像素对比原图方向正确且 EXIF Orientation 一致
镜像行为解码后水平翻转比对翻转轴正确
色彩描述解析 colr 三段字段primaries/transfer/matrix 均非空
辅助图引用解析 auxC 与引用关系能定位到辅助的主图
序列首帧目标设备实测计时首帧延迟低于业务阈值

有一次我调整了 HDR 图样的写入逻辑,原本以为只影响 10bit 文件,结果回归时发现普通 8bit 文件的 colr 也变了,颜色整体偏冷。如果没这套自动化用例,这种低概率回归很可能被当作用户端显示差异直接带病上线。那以后我给自己立了个规矩:容器相关代码每次改动,不管自认为多小,都强制跑一遍上述验证清单,输出报告后才允许合入。标准不会告诉你每个坑在哪,但它把每个字段的语义写清楚了,照着核对总能定位到具体 box。希望这份拆解能帮你在 HEIF/HEIC 这条路上少走几步弯路。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询