回 Jason 主站
HARDWARE_DECODER

H.264 硬件解码器

基于 Apple VideoToolbox 的逐帧 NAL/SPS/PPS 解析与 PNG 帧导出 · Swift 课程实现。

架构

解码 pipeline:从 NAL 单元提取参数集,建立硬件解码会话,输出 CVPixelBuffer 后转 PNG。

flowchart LR
    A[读取 video.h264] --> B[检查硬解能力]
    B --> C[扫描 NAL 单元]
    C --> D[提取 SPS / PPS]
    D --> E[创建 VTDecompressionSession]
    E --> F[逐帧 decode]
    F --> G[CVPixelBuffer]
    G --> H[导出 PNG]
      

代码亮点

SPS / PPS 提取

// 扫描 NAL 单元 type 7 (SPS) 与 type 8 (PPS)
let nalType = data[startCodeLen] & 0x1F
if nalType == 7 { spsData = data.subdata(in: ...) }
else if nalType == 8 { ppsData = data.subdata(in: ...) }

解码会话创建

VTDecompressionSessionCreate(
    allocator: kCFAllocatorDefault,
    formatDescription: formatDesc,
    decoderSpecification: nil,
    imageBufferAttributes: pixelBufferAttrs,
    outputCallback: &callbackRecord,
    decompressionSessionOut: &session)

CVPixelBuffer → PNG

let ciImage = CIImage(cvPixelBuffer: pixelBuffer)
let context = CIContext()
let cgImage = context.createCGImage(ciImage, from: ciImage.extent)
// 写入 PNG 到 frames/ 目录

课程笔记

NAL 结构 · H.264 码流以 0x00000001 起始码分隔 NAL 单元,每个 NAL 单元含 1 字节头(forbidden_zero_bit + nal_ref_idc + nal_unit_type)。
硬解 vs 软解 · VideoToolbox 在 Apple Silicon / Intel 都走 GPU 解码;遇到 H.265 10bit 等不支持的 profile 必须 fallback 到 FFmpeg 软解。
踩坑:startCode 长度 · NAL 起始码可能是 3 字节(0x000001)或 4 字节(0x00000001),扫描器要兼容两种。

本项目依赖本地 video.h264 文件 + Xcode 编译,无在线 demo。