
簡介ISO/IEC 23008-12:2017 是高效編碼與異構(gòu)環(huán)境媒體交付系列標(biāo)準(zhǔn)的第 12 部分正式定義 HEIF/HEIC 圖像文件格式面向多媒體開發(fā)人員、移動端圖像處理工程師、流媒體系統(tǒng)設(shè)計者以及關(guān)注圖像格式演進的技術(shù)人員。資源為官方發(fā)布的 PDF 原版標(biāo)準(zhǔn)僅 1 個文件壓縮包約 906KB正文內(nèi)容覆蓋范圍、規(guī)范性引用文件、術(shù)語與定義、縮略語、總覽和通用要求等章節(jié)便于按章節(jié)查閱和引用標(biāo)準(zhǔn)原文。該格式基于 HEVC 高效視頻編碼不僅支持單張圖像與圖像序列還能封裝元數(shù)據(jù)與派生圖像為不同設(shè)備、軟件平臺之間的互操作性及格式轉(zhuǎn)換提供統(tǒng)一規(guī)范。已有 259 人學(xué)習(xí)下載適合希望深入理解 HEIF/HEIC 容器結(jié)構(gòu)、自行實現(xiàn)編碼器或解碼器、排查媒體播放兼容性問題的技術(shù)工程師作為標(biāo)準(zhǔn)依據(jù)參考。1. HEIC 的真相它贏在文件組織方式而不是壓縮算法同樣一張 1024×1024 的照片HEIC 文件里可能根本沒有保存一張完整的主圖而是存了 4 塊 512×512 的瓦片外加一條把它們拼成 2×2 網(wǎng)格的表達式。真正決定畫面如何呈現(xiàn)的不是某個像素壓縮算法而是一套盒子Box組織規(guī)則。這就是 ISO/IEC 23008-12:2017HEIF/HEIC 背后的規(guī)范與 JPEG 最本質(zhì)的區(qū)別它不定義圖像內(nèi)容的編碼方式只定義圖像如何被組織、如何被引用、如何被派生出新圖像。搞懂這套組織規(guī)則你才能解釋為什么 HEIC 能被裁剪、旋轉(zhuǎn)、疊加輔助圖卻幾乎不損失畫質(zhì)也才能理解為什么同樣一個 HEIC 文件在某些解碼器上正常、在另一些上直接翻車。這篇從文件骨架拆到條目、屬性、派生與序列再落到常見排查方法。2. 從 ISOBMFF 派生為什么是盒子樹而不是新容器標(biāo)準(zhǔn)正文 6.2 和 7.2 都寫著一句話Derivation from the ISO base media file format從 ISO 基礎(chǔ)媒體文件格式派生。這句話是整個 HEIF 架構(gòu)的根它沒有發(fā)明新容器而是直接復(fù)用了 MP4 家族那套經(jīng)過二十年驗證的盒子模型。理解這一點你就理解了一半的設(shè)計動機——圖像格式的容錯性、擴展性、流式友好性都來自這套樹狀結(jié)構(gòu)而不是來自某個更聰明的壓縮算法。2.1 盒子Box的自我描述size type 構(gòu)成的一切HEIF 文件就是一串首尾相接的盒子。每個盒子的前 8 個字節(jié)是固定的前 4 字節(jié)是 big-endian 的長度含這 8 字節(jié)頭部后 4 字節(jié)是 ASCII 類型標(biāo)識。一個盒子可以包含子盒子于是整份文件天然變成一棵樹。下面這個遞歸解析邏輯是所有 HEIF 解碼器地基def walk_boxes(data, offset0, depth0): while offset 8 len(data): size int.from_bytes(data[offset:offset4], big) btype data[offset4:offset8].decode(latin1) if size 1: # 32 位長度不夠用擴展為 64 位長度 size int.from_bytes(data[offset8:offset16], big) header 16 elif size 0: # 0 表示盒子一直延伸到文件末尾 size len(data) - offset header 8 else: header 8 payload data[offsetheader:offsetsize] print( * depth f{btype} {offset}: {size} bytes) if btype meta: # meta 盒內(nèi)部通常嵌套完整子盒序列 walk_boxes(payload, 0, depth 1) offset size這段代碼的關(guān)鍵在于解析器可以不認識某個盒子只靠 size 字段就能跳到下一個盒子繼續(xù)讀。所以未來新增任何屬性盒舊解碼器都能安全跳過絕不會因為一個未知類型就整體崩潰——這就是向前兼容在文件格式層面的具體落地。三種長度語義也很講究size0 只允許出現(xiàn)在文件最后一個盒子用來兜底size1 是給大文件準(zhǔn)備的 64 位擴展普通手機照片用不上但專業(yè)相機和長圖像序列必須靠它。2.2 ftyp / meta / mdat三個盒子的分工與邊界HEIF 文件骨架可以簡化成三種角色文件頭、元數(shù)據(jù)島、原始數(shù)據(jù)池。典型的頂層結(jié)構(gòu)是 ftyp、meta、mdat 三個盒子各司其職。ftyp 在最前面聲明這個文件屬于哪個品牌brand以及兼容哪些品牌。解碼器先看它決定能否繼續(xù)。meta 盒是所有描述信息的家條目清單、屬性定義、條目與屬性的映射、圖像角色全在這里。mdat 盒則是完全不解釋內(nèi)容的字節(jié)池——主圖像、縮略圖、輔助圖像、元數(shù)據(jù)的原始字節(jié)都平鋪在里面靠偏移量和長度被 meta 里的條目引用。這種元數(shù)據(jù)與像素數(shù)據(jù)分離的設(shè)計對整個生態(tài)是決定性的。JPEG 的做法是文件頭擠著一堆分段、文件尾再掛 EXIF數(shù)據(jù)和管理信息耦合在同一個流里而 HEIF 的做法更像文件系統(tǒng)meta 是目錄項mdat 是數(shù)據(jù)塊。目錄項壞了最多損失索引數(shù)據(jù)塊還能搶救回來反過來想替換一個條目里的圖像內(nèi)容只需要改 mdat 中對應(yīng)區(qū)段的指針和長度不需要重寫整個文件。2.3 兼容紅利為什么不多發(fā)明一個容器從設(shè)計取舍看從 ISOBMFF 派生帶來的最大紅利是解析器現(xiàn)成。MP4 家族的容器解析已經(jīng)在播放器、瀏覽器、嵌入式設(shè)備里被驗證過無數(shù)遍隨機訪問、字節(jié)序、嵌套深度限制、損壞恢復(fù)策略這些都是血淚經(jīng)驗換來的。HEIF 直接繼承這些經(jīng)驗而不是像某些格式那樣從頭定義一套二進制結(jié)構(gòu)再花五年踩坑。我一般這么理解這個決策標(biāo)準(zhǔn)委員會把賭注壓在圖像未來一定會越來越像媒體上——靜態(tài)圖像、圖像序列、分層編碼、派生關(guān)系本質(zhì)上都是多個數(shù)據(jù)對象 它們之間的引用關(guān)系。用一套為多媒體設(shè)計的容器來裝圖像比讓圖像格式守著單幀單文件的舊模型更符合演進方向。所以標(biāo)準(zhǔn)第 9 章才敢放心地擴展 ItemInfoEntry 和 Item Properties Box因為容器骨架沒動只是在 meta 這個目錄區(qū)里加字段舊解析器依舊能跳過。3. 圖像條目與屬性把像素和對像素的解釋徹底拆開HEIF 對傳統(tǒng)圖像格式最狠的一次重構(gòu)是把一幅圖像從單一像素塊拆成了兩個概念圖像條目Image Item和圖像屬性Image Property。條目是一段數(shù)據(jù)屬性是這段數(shù)據(jù)如何被解釋。同一個條目可以掛多個屬性同一個屬性也可以被多個條目共享。這一層設(shè)計直接決定了 HEIC 文件能同時承載主圖、縮略圖、深度圖、旋轉(zhuǎn)信息、色彩信息而不互相干擾。3.1 ItemInfoEntry圖像條目的身份證每個圖像條目都有一個 infeItemInfoEntry作為身份證。它記錄條目 ID、條目類型和條目名稱。標(biāo)準(zhǔn)第 9.2 節(jié)專門為它定義了擴展語法原因是舊版 ISOBMFF 里條目信息分散在不同盒中2017 版把它們統(tǒng)一進一個可擴展結(jié)構(gòu)def parse_infe(payload): version payload[0] flags int.from_bytes(payload[1:4], big) p 4 if version 2: item_ID int.from_bytes(payload[p:p2], big); p 2 item_protection_index int.from_bytes(payload[p:p2], big); p 2 item_type payload[p:p4].decode(latin1); p 4 end payload.index(b\x00, p) item_name payload[p:end].decode(utf-8) return { item_ID: item_ID, item_type: item_type, item_name: item_name, protection_index: item_protection_index, }item_type 是四個字符的代碼它決定了解碼器把這條數(shù)據(jù)當(dāng)什么比如 hvc1 表示 HEVC 編碼的主圖像auxl 表示輔助圖像具體輔助用途由屬性進一步說明grid 表示瓦片拼接圖像iovl 表示疊加圖像。item_ID 在文件內(nèi)唯一所有引用關(guān)系——無論是屬性映射、派生源引用還是角色指派——都靠這個數(shù)字串起來。注意 parse 結(jié)束后item_name 只是給人看的名字解碼器不應(yīng)該依賴它做邏輯判斷否則換個名字就會翻車。3.2 屬性池與映射ipco / ipma 的雙層結(jié)構(gòu)圖像屬性不是直接寫死在條目里的而是集中在 iprpItem Properties Box中內(nèi)部再分 ipco屬性定義池和 ipma條目到屬性的映射表。這種雙層結(jié)構(gòu)很像數(shù)據(jù)庫的表設(shè)計屬性定義只存一份多個條目通過 ipma 映射到同一條定義省空間也省一致性維護成本。ipma 里每條關(guān)聯(lián)還帶一個 essential 標(biāo)志——標(biāo)記這個屬性是不是解碼必不可少的如果解碼器不認識一個 essential 屬性必須拒絕解碼而不是忽略它。屬性 Box設(shè)計作用關(guān)鍵取值ispe圖像空間范圍寬、高解碼前必須知道irot90° 倍數(shù)的旋轉(zhuǎn)0/90/180/270imir水平或垂直鏡像軸方向clap清潔孔徑從解碼畫面裁剪出有效區(qū)colr色彩信息色彩原色、傳輸特性、矩陣系數(shù)pixi像素信息每分量位深、通道數(shù)pasp像素寬高比非正方形像素的修正rloc相對位置條目在畫布上的偏移auxC輔助圖像類型深度圖、alpha、色度圖等解碼器要正確顯示一張 HEIC不是解完像素就行而是要按順序把這一串屬性應(yīng)用上去先用 ispe 確定畫布按 irot/imir 做幾何變換用 clap 決定最終顯示窗口用 colr 把像素轉(zhuǎn)換到目標(biāo)色彩空間。任何一個屬性被遺漏輕則方向不對重則顏色整個偏掉。這就是為什么 HEIF 解碼比 JPEG 解碼更依賴完整實現(xiàn)而不是能出圖就行。3.3 派生圖像不存像素的 grid / iovl / iden最反直覺的部分在這里一個圖像條目可以完全沒有自己的像素數(shù)據(jù)它的 content 是對其他條目的引用。標(biāo)準(zhǔn) 6.6 節(jié)定義了派生圖像Derived Images常見類型包括 grid網(wǎng)格拼接、iovl疊加合成和 iden恒等變換。grid 的思想很直白把一張大圖切成若干瓦片每個瓦片是獨立編碼的 hvc1 條目再建立一個 grid 條目聲明按 2×3 拼接即可。解碼時不需要同時解出整張大圖而是按需解出可視區(qū)域的瓦片——這對超大分辨率的全景圖、衛(wèi)星圖意義重大。iovl 則是把多個條目按坐標(biāo)疊加前景圖、背景圖、alpha 圖分開存需要合成時才疊加。派生關(guān)系可以鏈?zhǔn)角短滓粋€ iovl 的輸入本身可以是一個 grid 條目。解碼器像做深度優(yōu)先遍歷一樣遞歸求值所以標(biāo)準(zhǔn)要求解碼器必須能檢測循環(huán)引用否則一條A 派生自 BB 又派生自 A的惡意文件就能讓解碼線程死循環(huán)。3.4 圖像角色隱藏圖、封面圖、縮略圖、輔助圖如何分工條目類型說了數(shù)據(jù)是什么圖像角色再說這張圖在文件里干什么。標(biāo)準(zhǔn) 6.4 節(jié)定義了隱藏圖、封面圖、縮略圖、輔助圖和主圖主圖是默認展示的封面圖用于某些交互場景優(yōu)先顯示縮略圖用小尺寸快速預(yù)覽輔助圖不直接顯示它給主圖提供深度、alpha、色彩校正所需的數(shù)據(jù)隱藏圖存在但不參與任何正常呈現(xiàn)。這五個角色都通過關(guān)聯(lián)屬性標(biāo)記而不是靠文件內(nèi)的順序推斷。我見過某項目用第一個條目就是主圖的假設(shè)去寫解碼器結(jié)果遇到輔助圖排在前面就直接把深度圖當(dāng)照片顯示。正確的做法永遠是先解析 meta 里的角色映射再決定哪些條目進渲染管線、哪些只做后臺附屬。4. 圖像序列一段沒有聲音的視頻軌道是怎么組織的HEIF 不只是單張圖片的容器標(biāo)準(zhǔn)第 7 章完整定義了圖像序列Image Sequence。一個序列被組織成一條軌道track軌道里的每一幀是一個樣本sample。從容器角度看它幾乎就是一段沒有音頻、沒有 B 幀復(fù)雜度約束的視頻軌道。連拍、動態(tài)照片、延時攝影、分層的 HDR 靜態(tài)圖像都可以靠這條軌道在同一個文件里表達。4.1 軌道的皮膚Track Header 里的 handler 與尺寸軌道在容器層必須有 Track Header 盒聲明基本參數(shù)還要用 handler 盒聲明軌道類型。HEIF 圖像序列的 handler 類型被定義為 pict這樣播放器一看就能把它歸類為圖像軌道而非普通視頻軌從而用圖像解碼管線而不是完整視頻播放管線處理它。這個區(qū)分很重要圖像序列強調(diào)逐幀可編輯、可替換而不是像視頻那樣強調(diào)連續(xù)回放。標(biāo)準(zhǔn) 7.2.3 還定義了一個專門的 Coding Constraints 盒ccst用來聲明這軌圖像的解碼約束。它的位域設(shè)計很緊湊def parse_ccst(payload): # payload 前 4 字節(jié)是 version flags第 5 字節(jié)是約束位域 flags_byte payload[4] all_ref_pics_intra (flags_byte 7) 1 # 所有幀是否都為幀內(nèi)編碼 intra_pred_used (flags_byte 6) 1 # 幀內(nèi)預(yù)測是否被啟用 max_ref_per_pic flags_byte 0x3F # 每幀最多引用幾個參考幀 return { all_ref_pics_intra: all_ref_pics_intra, intra_pred_used: intra_pred_used, max_ref_per_pic: max_ref_per_pic, }這三個字段的價值在于讓解碼器在解碼前就拿到策略而不是逐幀試探。如果 all_ref_pics_intra 為 1說明每一幀都不依賴其他幀解碼器就可以做完全隨機訪問編輯時可以放心插入、刪除任何一幀而不用重新編碼后續(xù)幀。max_ref_per_pic 則告訴解碼器內(nèi)存窗口要開多大。很多 HEIF 解碼翻車就是因為實現(xiàn)方無視 ccst默認按普通視頻 B 幀處理結(jié)果遇到全幀內(nèi)序列時白白多做了一個完整的幀重排流程。4.2 refs 樣本組記錄幀之間的直接引用關(guān)系即便有 ccst 聲明HEVC 編碼的圖像序列還是可能出現(xiàn)幀間引用比如某些幀參考前一幀做預(yù)測編碼。問題在于解碼器想隨機訪問第 n 幀時怎么知道該回溯到哪一幀標(biāo)準(zhǔn) 7.4.1 給出了答案直接引用樣本列表Direct Reference Samples List用 refs 樣本組記錄每個樣本直接引用了哪些樣本。引用關(guān)系清晰后隨機訪問點推導(dǎo)就變成一個簡單的圖遍歷# 根據(jù) refs 樣本組推導(dǎo)每個樣本是否可獨立解碼 def find_random_access_points(refs_group, total_samples): independent set() for sample_id in range(1, total_samples 1): refs refs_group.get(sample_id, []) if not refs: independent.add(sample_id) # 不引用任何幀天然隨機訪問點 elif all(r in independent for r in refs): independent.add(sample_id) # 依賴的都是獨立幀也隨機可達 return sorted(independent)這里的關(guān)鍵洞察是直接引用和間接引用的區(qū)分。一個樣本可能間接依賴前面十九幀但 refs 只記錄直接依賴的那幾幀。解碼器靠這個映射回溯時不需要把整段歷史都解碼出來而是可以沿著依賴鏈只解必要幀代價是多次跳轉(zhuǎn)。誤用 refs 的典型錯誤是拿關(guān)鍵幀類型去猜比如認為所有不包含 IDR 的幀都依賴前面最近的一個 IDR——這在參考關(guān)系不連續(xù)時是錯的解碼結(jié)果就是花屏或者綠屏。4.3 縮略圖軌道、輔助軌道與 eqiv 實體組序列不止一條。標(biāo)準(zhǔn) 7.5 節(jié)規(guī)定了縮略圖序列軌道和輔助圖像序列軌道縮略圖軌道通過 thmb 軌道引用關(guān)聯(lián)到主軌道解碼器需要快速預(yù)覽時直接讀縮略軌道不必解碼全分辨率主序列輔助軌道保存深度、alpha、HDR 信息它們和主序列通過引用關(guān)系一一對應(yīng)但又獨立編碼、獨立可替換。6.8 節(jié)的 eqiv 機制打通了靜態(tài)圖和動態(tài)序列的邊界。一個未定時的圖像條目比如封面圖通過 eqiv 實體組關(guān)聯(lián)到某條軌道的某個時間段或者說某個 sample group 把 timed sequence 里的一段樣本與 untimed item 等價起來。實際價值很直接相冊封面既可以是縮略圖條目也可以指到視頻軌道里的某一幀播放器在展示時就不用關(guān)心數(shù)據(jù)的物理存放形態(tài)只看邏輯等價關(guān)系。5. HEIF/HEIC 排查指南常見問題、踩坑與驗證方法容器設(shè)計再優(yōu)雅實現(xiàn)時該踩的坑一個都不會少。下面這五類問題是我在實際項目中見過最多的 HEIF/HEIC 解碼故障每條都按現(xiàn)象、原因、解決拆開?,F(xiàn)象不支持該條目類型——解碼器直接拒絕打開或者只顯示黑屏。原因很多實現(xiàn)只認得 hvc1 主圖像遇到 grid、iovl 派生條目或者 auxl 輔助條目要么直接報錯要么把輔助圖像當(dāng)主圖渲染。更深層的原因是這類解碼器沒有實現(xiàn) ipma 里的 essential 標(biāo)志邏輯不知道哪些條目必須支持、哪些可以安全跳過。解決先解析 meta 里的所有 infe打印 item_type 清單確認文件里到底有哪些類型的條目。遇到派生條目如果實現(xiàn)不支持就沿派生樹的引用關(guān)系找到最底層的 hvc1 真實像素條目。遇到輔助條目按角色映射過濾掉不要進渲染管線。現(xiàn)象圖像顯示方向不對但 EXIF 里明明寫著正確的旋轉(zhuǎn)角度。原因HEIF 的方向不依賴 EXIF而是靠 irot / imir 屬性。很多移植的解碼器沿用了 JPEG 時代讀 EXIF Orientation的習(xí)慣完全無視這兩個屬性盒于是畫面沒有被旋轉(zhuǎn)或鏡像。更隱蔽的是 irot 只支持 90° 的整數(shù)倍如果原圖是任意角度就必須靠 clap 先裁剪再旋轉(zhuǎn)處理順序錯了也會偏。解決解碼完像素后先查 ipma 里有沒有掛 irot 和 imir再查 clap 是否定義了裁剪區(qū)。應(yīng)用順序固定為先裁 clap再鏡像 imir再旋轉(zhuǎn) irot最后才是色彩轉(zhuǎn)換。順序顛倒就會得到既偏又花的畫面?,F(xiàn)象封面圖不是想要的畫面顯示成了白場或深度圖。原因封面條目的選擇邏輯寫死了用第一個條目。前面說過條目在文件里的物理順序與角色無關(guān)輔助條目、隱藏條目可能排在主圖前面。有的文件里封面由 cover 角色顯式指定有的靠 primary item 指定兩者優(yōu)先級還不同。解決先查 eqiv 實體組或樣本組看封面是否關(guān)聯(lián)到某個時間點的序列幀如果沒有再查角色映射里 cover 標(biāo)記者都查不到才回退到 primary item。不要信任第 0 個 item這種假設(shè)。現(xiàn)象動態(tài)照片/連拍序列只有第一幀能顯示后續(xù)幀花屏或綠屏。原因解碼器沒有讀 refs 樣本組把 HEVC 幀間預(yù)測幀當(dāng)獨立幀直接解碼或者解析了 refs 但是沒有按依賴鏈回溯解碼。還有一類原因是 ccst 里 max_ref_per_pic 聲明了較大窗口但解碼器內(nèi)部參考幀緩存開小了。解決逐幀打印 refs 映射確認當(dāng)前幀的直接引用樣本列表在解碼順序上強制按依賴拓撲排序保證每一幀解碼前它的所有引用都已解碼。同時把參考幀緩存大小對齊到 ccst 的 max_ref_per_pic。現(xiàn)象文件完整性校驗失敗但圖像能正常打開。原因標(biāo)準(zhǔn) 8.4 節(jié)定義的完整性檢查是對條目數(shù)據(jù)做摘要校驗值存在 meta 中。如果某個工具在保存時重寫了 mdat 里的字節(jié)比如歸一化、重壓縮卻沒有同步更新摘要校驗必然失敗。反過來有的流式寫入器在文件未完全落盤時提前計算摘要也會得到不匹配的結(jié)果。解決拿到文件先確認完整性校驗的覆蓋范圍——是覆蓋整個 mdat還是覆蓋單個條目數(shù)據(jù)。校驗失敗時不要直接丟棄文件打印摘要算法標(biāo)識和預(yù)期值對比原始數(shù)據(jù)區(qū)有沒有被改動。很多官方工具打不開但自家?guī)炷荛_的疑難雜癥最后都歸因到這里。6. 設(shè)計取舍為什么標(biāo)準(zhǔn)押注在組織規(guī)則而不是編碼器上ISO/IEC 23008-12:2017 通篇沒有規(guī)定圖像必須用某種編碼它只規(guī)定條目怎么聲明、屬性怎么掛、派生怎么引用、序列怎么組織。真正的壓縮效率來自 HEVC未來換成更激進的編碼器容器依舊成立。這個取舍的背后是一種判斷編碼技術(shù)的迭代周期是五年一代而文件格式一旦鋪開生命周期是二十年起步。把如何組織和如何編碼解耦才能讓格式活得比任何一代編碼器都長。代價同樣清晰。解碼器的復(fù)雜度從讀像素流升級為先解析盒子樹再沿著條目、屬性、引用關(guān)系求值軟件棧整體變重。一個小設(shè)備上的 HEIF 解碼器光處理 grid 瓦片和 iovl 疊加就要維護一個遞歸求值器服務(wù)端生成 HEIC 時還要維護 ipco 屬性池的去重與 ipma 映射的一致性。這些成本 JPEG 時代是完全沒有的。所以標(biāo)準(zhǔn)選了多層抽象屬性可擴展essential 標(biāo)志讓不認識新屬性的解碼器顯式失敗而非默默出錯refs 樣本組讓隨機訪問從猜幀類型變成查依賴圖。這些設(shè)計本質(zhì)上都是在給組織規(guī)則兜底——用更精確的元數(shù)據(jù)換取更寬容的擴展空間。從那以后我每次拿到一個新的圖像容器或打包格式都會先強制自己畫一遍盒子樹列出所有條目、所有屬性、所有引用關(guān)系再談解碼??床欢藐P(guān)系的解碼器寫出來也是脆的。希望幫到你。本文還有配套的精品資源點擊獲取