Skip to main content

Chapter 114: Container, Codec, Frame, Timestamp, Synchronization

一段手机视频在系统里先表现为文件,再表现为轨道、样本、压缩数据、解码帧和播放时钟。用户看到的是“能播放、能拖动、声音对得上画面、方向正确、色彩正常”。系统需要完成的工作更具体:识别容器,拆出音频和视频 elementary stream,配置解码器,按时间戳调度输出,并把元数据传给渲染层、音频层和媒体库。

本章用一个贯穿材料展开:travel_clip.mp4 是手机相机录制的视频文件,容器为 MP4,视频轨使用 HEVC,音频轨使用 AAC,文件里带有旋转信息、色彩信息、时长、封面和若干时间表。这个文件被播放器打开后,系统要回答四个问题:文件里有哪些轨道,轨道里的压缩数据由哪个 codec 处理,解码后的帧何时显示或播放,元数据如何影响最终体验。

读完本章后,读者应能追踪一个媒体文件从 open 到画面输出的最小路径:container 负责组织轨道、样本、索引和元数据;codec 负责把 compressed stream 转成 raw frame 或 PCM;frame 和 packet 负责承载数据阶段;timestamp 和 clock 负责决定顺序;metadata 负责影响显示方向、颜色、封面和隐私暴露面。

公开材料给出了这条路径的稳定边界。ISO Base Media File Format 把 MP4 类文件描述为面向 time-based media 的结构化文件;MDN 的 media container 说明 也把 container 定位为封装一个或多个 media stream 与 metadata 的文件格式。Android 侧的 MediaExtractor 暴露 track、sample 和 sample time;MediaCodec 暴露 codec 输入输出 buffer 与 presentationTimeUs;Apple 侧的 Core Media 使用 CMTimeCMSampleBufferCMFormatDescription 表达时间、样本与格式描述。正文只使用这些公开概念建立系统模型,Apple 私有播放器内部调度细节保持边界说明。

114.1 Media Container 与 Elementary Stream 的分离

Media Container 是保存媒体轨道、时间表、索引、元数据和压缩数据位置的文件结构。它解决的问题是“多个媒体流如何放在同一个文件里,并能被播放器按时间读出”。MP4、MOV、WebM、Matroska 这类格式都属于 container 层;它们可以保存音频轨、视频轨、字幕轨、章节、封面、方向、色彩信息和时间索引。

Elementary Stream 是单一媒体类型的编码流。视频 elementary stream 可以是一串 H.264、HEVC 或 AV1 编码访问单元;音频 elementary stream 可以是一串 AAC、Opus 或其他音频帧。它解决的问题是“codec 能消费的压缩数据长什么样”。container 负责把这些流放入文件并提供读取表,codec 负责理解压缩语法。

travel_clip.mp4 的第一层判断应从 container 开始。播放器先读取文件类型和顶层结构,识别这是 MP4 系列文件,再读取 movie、track、sample table 等结构。视频轨会给出 codec identifier、尺寸、时长、时间尺度、关键帧索引和每个 sample 的位置。音频轨会给出采样率、声道、codec specific data 和 sample 表。到了这一步,系统仍然没有解码画面;它只是知道了“有什么流、在哪里、什么时间播放、需要什么 decoder”。

Elementary stream 从 container 中拆出后才进入 codec 边界。对于 HEVC 视频,container 中的 sample 可能保存一个访问单元,访问单元内部由多个 NAL unit 组成。对于 AAC 音频,sample 可能对应一个压缩音频帧或一组音频采样的编码表示。这个边界使播放器可以在同一个 demux 层处理 MP4 和 MOV,再把具体压缩格式交给不同 codec。

Android 的 MediaExtractor 就体现了这个分离。它先读取 data source,遍历 track,返回每条 track 的 MediaFormat,再通过 readSampleDatagetSampleTime 读出当前 sample 的压缩字节和展示时间。应用看到的是 sample 流;真正理解 H.264、HEVC、AAC 或 Opus 的部分在 MediaCodec 或底层 hardware codec。Apple 的 AVAsset、track、sample buffer 和 format description 也采用类似分层:asset 表示媒体资源,track 表示其中一条媒体流,sample buffer 承载可调度的数据和时间。

这个分离还决定失败定位顺序。文件打不开时,先看 container 识别;能识别文件但没有画面时,看 track 和 codec 支持;能解码但无法拖动时,看 keyframe 索引和 sample table;能播放但方向或色彩异常时,看 metadata 是否被读取并传递到显示路径。

114.2 Codec 作为压缩、解压与硬件加速边界

Codec 是编码器和解码器的统称。编码器把 raw frame 或 PCM 压缩成 bitstream;解码器把 bitstream 还原成视频帧或音频 PCM。移动系统把 codec 设计成明确边界,是因为压缩格式、硬件能力、功耗、专利许可、DRM 和格式兼容都集中在这一层。

Codec 名称只说明压缩语法的大类。H.264、HEVC、AV1、AAC、Opus 都是 codec 名称;profile、level、bit depth、chroma subsampling、resolution、frame rate、sample rate 和 channel count 才决定某台设备是否能用硬件稳定处理。一个手机可能支持 HEVC Main profile 8-bit 4:2:0,却在 HEVC Main10 HDR 或高分辨率高帧率输入上退回软件路径、拒绝配置或降低性能。

Android 的 MediaCodec 文档把 profile 选择、codec-specific data 和 buffer 时间戳放在 codec 配置路径中。尤其是编码时,profile 缺省选择可能由设备决定;对于 HDR、10-bit、4:2:2 或 4:4:4 输入,应用需要配置适配的 profile。这个规则说明 codec 边界承担两类判断:压缩语法能否被识别,当前硬件配置能否承载该语法的具体参数。

travel_clip.mp4 的 HEVC 视频轨进入解码器之前,播放器要把 track format 交给 codec。format 里至少需要 MIME 类型、宽高、codec-specific data 和可能的色彩信息。codec-specific data 可能包含 VPS、SPS、PPS 等初始化信息,它们描述后续 bitstream 的解码上下文。缺少这些数据时,decoder 即使收到 video sample,也缺少稳定解释压缩流的前提。

硬件加速在移动系统中具有系统策略属性。硬解能降低 CPU 消耗,减少发热,并让视频帧直接输出到 Surface、layer 或 pixel buffer。代价是输入格式、输出像素格式、protected content、color metadata 和 buffer ownership 都要满足硬件路径要求。系统播放器通常会优先选择硬件解码;当 profile、resolution、DRM 或 vendor codec 行为冲突时,再切换失败路径或报错。

Apple 平台公开暴露的 VideoToolbox、Core Media、AVFoundation 类型也把 codec 边界拆成 format description、sample buffer、decompression session 和 pixel buffer。公开文档能确认这些类型的职责,具体硬件调度、daemon 分工和私有优化策略属于平台内部实现。写跨平台媒体路径时,应把“公开 API 边界”和“私有调度推断”分开表述。

114.3 Frame、Packet、Sample、NAL Unit 的数据层级

媒体系统里同一个文件会被拆成多个数据层级。Packet、sample、frame、NAL unit 这些词经常交叉出现,稳定理解方式是先看它们所处阶段:container 读取阶段、codec 输入阶段、codec 输出阶段、渲染阶段。

在 container 和 demux 语境中,sample 通常指一段带时间戳和 flags 的媒体数据。Android MediaExtractor 读出的 sample 有 track index、sample time、sample flags 和 sample data。这里的 sample 仍然是压缩数据。对于视频轨,它可能对应一个 access unit;对于音频轨,它可能对应一段压缩音频帧。

Packet 是许多媒体库对“从 demuxer 输出的一包压缩数据”的称呼。它通常携带 byte buffer、stream index、PTS、DTS、duration、flags 和 side data。不同框架命名有差异:Android 更常说 sample 和 buffer,FFmpeg 更常说 packet,Apple Core Media 更常说 sample buffer。它们共同解决的问题是把压缩字节和时间信息绑定起来,交给 codec 或上层调度。

Frame 在视频里有两层含义。编码语境中的 frame 可能表示一个编码访问单元,例如 I frame、P frame、B frame;解码输出语境中的 frame 表示 raw image,例如 YUV 或 RGB 图像帧。音频里也有 frame,例如 AAC frame,但音频播放最终更关心 PCM sample count 与音频时钟。读媒体文档时,先看 frame 处在“codec input”还是“codec output”,可以减少概念混淆。

NAL Unit 是 H.264、HEVC 等视频 codec 内部的语法单元。一个 access unit 可以由多个 NAL unit 组成。参数集 NAL unit 提供解码上下文,slice NAL unit 承载图像数据,SEI 可能携带辅助信息。container 可以按长度前缀保存 NAL unit,也可能需要转换成 Annex B start code 形式再送入某些 decoder。这个转换发生在 container binding 和 codec input 之间。

下表把 travel_clip.mp4 中最常见的数据对象放到同一条路径中:

层级典型对象所属边界关键字段失败表现
Filetravel_clip.mp4Containerbrand、track、metadata文件无法识别、时长异常
Trackvideo track / audio trackDemuxMIME、timebase、sample table无轨道、格式未知
Sample / Packetcompressed access unitDemux 到 Codecbyte range、PTS、DTS、flags解码失败、seek 失败
NAL Unit / Audio Framecodec syntax unitCodec inputSPS/PPS/VPS、slice、AAC framecodec config 错误、花屏
Raw Frame / PCMdecoder outputRender / Audiopixel format、planes、sample count黑屏、色彩错、爆音
Layer / Audio Buffersystem outputComposer / Audio HALsurface timestamp、audio clock掉帧、音画漂移

这个层级表的作用是定位责任。container 负责找 sample,codec 负责解释 sample 内部语法,render 和 audio output 负责按时输出 raw data。把 NAL unit 当成文件层概念,会把问题推错边界;把 decoded frame 当成 sample,也会误判 copy cost、buffer ownership 和 timestamp 语义。

114.4 Timestamp:PTS、DTS、Duration、Timebase

Timestamp 是媒体播放顺序的核心数据。PTS 表示 presentation timestamp,即样本应被展示或播放的媒体时间;DTS 表示 decoding timestamp,即样本应进入解码器的时间;duration 表示样本占据的媒体时间长度;timebase 表示时间戳整数转换成秒的比例。

视频出现 B frame 后,解码顺序和显示顺序可能分离。一个视频帧可能依赖未来的参考帧,因此 decoder 需要先收到后显示的帧,再输出当前应显示的帧。DTS 服务 codec 输入顺序,PTS 服务渲染输出顺序。对于没有重排序的音频流,PTS 和 DTS 往往接近;对于带帧重排序的视频流,两者差异决定了播放管线要保留多少 reorder buffer。

Timebase 解决“时间戳整数如何解释”的问题。MP4 track 可以有自己的 timescale;WebM/Matroska 使用 TimecodeScale;Android API 常用 microseconds 表示 sample time 和 presentationTimeUs;Apple 的 CMTime 使用 value 加 timescale 的有理数表达时间。跨平台媒体代码经常出错的地方在这里:同一个数值在 milliseconds、microseconds、nanoseconds 或 track timescale 中含义不同。

travel_clip.mp4 的视频轨如果使用 timescale 90000,那么 sample PTS 为 180000 对应 2 秒;音频轨如果使用 48000 作为时间尺度,那么 96000 对应 2 秒。播放器不能直接比较两个整数大小,必须先归一到共同时间轴。Android MediaExtractor.getSampleTime() 直接返回 microseconds,减少了应用层换算;Apple CMTime 保留 timescale,使框架可以精确表达 1/30、1/1001 这类媒体时间。

Duration 影响帧保持时间和 seek 结果。固定帧率视频里,相邻 PTS 差值通常稳定;可变帧率视频里,每个 frame duration 可能不同。音频 sample 的 duration 可以由编码帧采样数和 sample rate 推导。字幕、chapter、thumbnail 这类 timed metadata 也依赖 duration,否则会出现字幕提前消失、章节定位不准或封面时间点异常。

时间戳还承担错误恢复边界。seek 时播放器通常跳到目标时间附近的 sync sample 或 keyframe,再丢弃目标时间之前的解码输出。网络播放发生 stall 后,播放器要用 timeline 重新对齐 audio queue 和 video queue。录制文件出现非单调 PTS 时,muxer 可能拒绝写入,播放器可能丢包或重排失败。WebM container guidelines 对 timecode 单调性和 cues 有明确约束,说明 container 写入阶段已经在影响后续播放调度。

114.5 Audio / Video Synchronization 与 Clock Source

Audio / Video Synchronization 解决的问题是“音频和视频如何在同一条播放时间线上输出”。解码器只负责产出数据;播放器还要选择 clock source,把 audio buffer、video frame、subtitle cue 和 UI seek 状态放到共同时间线上。

移动播放器常把 audio output clock 作为主时钟。原因是音频硬件持续消耗 PCM buffer,人耳对音频断裂和变速更敏感。视频帧可以在接近展示时间时送到 Surface、layer 或 display queue;如果视频落后,系统可以丢弃迟到帧;如果视频稍快,系统可以延后提交或重复上一帧。音频 drift 更常通过轻微重采样、缓冲调整或播放速率控制处理。

同步管线通常包含三个队列:compressed packet queue、decoded audio queue、decoded video frame queue。demuxer 按 track 和 timestamp 读样本,decoder 产生 output,scheduler 对比 frame PTS 和 playback clock。video frame 的 PTS 小于当前 clock 太多时,继续显示会造成画面追不上声音;video frame 的 PTS 大于当前 clock 时,scheduler 等待合适的 VSync 或 layer deadline。

下面的图把 travel_clip.mp4 的播放路径压缩成一个可检查模型。图中每个节点都对应一个责任边界,方便定位音画不同步问题发生在哪一段。

这条路径说明,音画同步依赖的对象分散在 container、codec、audio output 和 display scheduler 中。container 给出时间戳和 duration;codec 保留或转换时间戳;audio output 提供可观测 playback position;video scheduler 把 frame PTS 映射到 display timeline。任何一段丢失或误换算时间戳,用户看到的结果都是“嘴型对不上声音”。

失败场景可以按时钟关系排查。若拖动后短时间不同步,重点看 decoder flush、keyframe seek 和队列清空。若播放越久越偏,重点看 audio sample rate、timebase 换算、非单调 PTS 和设备 clock drift。若只在蓝牙耳机上延迟明显,重点看 audio route latency 和系统补偿。若只在高帧率或 HDR 视频上掉帧,重点看 video decoder 吞吐、Surface timestamp 和 display deadline。

114.6 Metadata、Rotation、Color Space、HDR 信息

Metadata 是媒体文件中辅助解释内容的数据。它可以描述文件标题、创建时间、地理位置、封面、方向、色彩空间、HDR 参数、语言、章节和作者。播放器和媒体库使用 metadata 决定 UI 展示、排序、旋转、色彩转换、缩略图和隐私处理。

Rotation 是移动视频最常见的 metadata 之一。手机竖拍时,sensor 和编码帧的存储方向可能保持横向,container 或 track metadata 记录显示旋转角度。正确的播放器会把旋转信息传给渲染层,让用户看到竖直画面。忽略 rotation 时,用户看到横倒视频;把 rotation 直接写进像素时,转码会增加计算和画质损失。

尺寸信息也分多层。Coded size 是 codec 解码出的像素尺寸,display size 是最终显示尺寸,pixel aspect ratio 会影响横纵比例。移动系统在预览、相册、分享和投屏路径中都要处理这些差异。一个视频的 bitstream 可能解码为 1920×1080,但 metadata 指示旋转 90 度后显示为 1080×1920。播放器 UI 计算 layout 时应使用显示尺寸,decoder 配置仍要尊重 coded size。

Color Space 决定像素值如何解释。视频 format 可能携带 color primaries、transfer function、matrix coefficients 和 range。Android 的 MediaFormat 从 API level 24 起包含 KEY_COLOR_STANDARDKEY_COLOR_TRANSFERKEY_COLOR_RANGE 等键,用于描述颜色标准、传递函数和范围。HDR 路径还可能需要 mastering display metadata、content light level、10-bit pixel format 和系统显示能力。

HDR 失败通常表现为过曝、灰暗、颜色偏移或被错误当作 SDR 显示。稳定判断顺序是:先看 container 或 sample 是否携带 HDR metadata,再看 decoder output format 是否保留这些字段,再看 render path 是否连接到 HDR-capable surface、layer 和 display。若中间层丢失 metadata,即使解码器成功输出像素,最终画面也会偏离创作者意图。

地理位置、创建时间和封面属于用户可见与隐私相关 metadata。相册会用它们排序、聚合和生成缩略图;分享路径可能移除位置或保留拍摄时间。移动 OS 在媒体库、分享面板和云同步之间处理 metadata 时,既要保持体验一致,又要执行隐私策略。这里的系统边界在媒体库服务、文件访问授权、照片选择器和分享目标之间,而非 codec 内部。

114.7 Container、Codec、Frame、Timestamp 的系统协作关系

把前面的对象合起来,媒体播放的系统协作关系可以归纳成一条责任链:container 给出轨道和时间表,demuxer 产出 sample,codec 产出 raw frame 或 PCM,scheduler 使用 timestamp 和 clock 决定输出时间,render 和 audio system 负责最终设备输出,metadata 影响展示方式和用户可见 UI。

travel_clip.mp4 被打开后,第一步是 container 解析。系统从文件结构中读取 track 数量、MIME、duration、sample table、sync sample、rotation、color metadata 和 codec-specific data。这里得出的结论是“这个文件理论上需要 HEVC decoder、AAC decoder、一个视频输出 Surface 和一个音频输出路径”。

第二步是 codec 配置。播放器把视频 format 交给 video decoder,把音频 format 交给 audio decoder。视频 decoder 需要知道 codec type、profile、level、width、height、codec-specific data、可能的 color metadata 和 output surface。音频 decoder 需要知道 codec type、sample rate、channel count 和 codec-specific data。配置失败说明当前设备或当前安全路径无法承载该格式。

第三步是 sample 读取和解码。demuxer 按 track 读 compressed sample,把 PTS、DTS、duration、flags 和 byte buffer 一起送入对应 decoder。视频 sample 可能包含关键帧、预测帧或带重排序的访问单元;音频 sample 输出 PCM 后进入音频队列。decoder 输出时必须保留或映射 presentation time,否则 scheduler 无法知道 frame 何时进入显示路径。

第四步是同步和渲染。audio output 建立播放 clock,video scheduler 根据 frame PTS 选择提交、等待、丢弃或重复。显示层还要应用 rotation、scaling、color conversion 和 HDR metadata。最终用户看到的“横竖方向正确、色彩正确、声音对得上画面”,是 container、codec、timestamp 和 metadata 同时有效的结果。

第五步是失败归因。一个媒体失败现象应按照责任链向前定位:文件无法识别看 container;能识别但 decoder 配置失败看 codec capability;能解码但拖动慢看 keyframe 和 index;能播放但音画偏移看 timestamp、timebase 和 clock;能播放但方向或颜色异常看 metadata 传递;在某些设备上失败看硬件 codec profile、vendor 实现和系统版本边界。

移动 OS 的特殊性在于这条链路经常跨进程、跨硬件和跨策略。Android 路径可能穿过 app、media framework、media server 或 codec service、hardware codec、SurfaceFlinger 和 AudioFlinger;Apple 路径可能穿过 AVFoundation、Core Media、VideoToolbox、Core Video、Core Animation 和音频系统。两类平台的公开 API 名称不同,但共同系统问题一致:把文件结构、压缩语法、时间轴和设备输出连接成稳定体验。

最小自检任务

给定一个现象:用户在手机相册中打开一个 clip.mp4,文件能播放,但画面横倒,拖动到中间后前两秒音画不同步,HDR 画面偏灰。已知文件有视频轨和音频轨,视频轨为 HEVC Main10,音频轨为 AAC,container 中带有 rotation 和 HDR metadata。请写出从 container 到最终输出的责任链,并分别定位横倒、拖动后短暂不同步、HDR 偏灰最可能发生在哪些边界。

答案要点

责任链应从 container 解析开始:播放器读取 MP4 track、sample table、codec-specific data、rotation、color 和 HDR metadata;demuxer 按 sample time 输出视频和音频 sample;video decoder 根据 HEVC Main10 配置硬件或软件解码;audio decoder 输出 PCM;scheduler 以 playback clock 对齐 video frame PTS;render path 应使用 rotation、display size、color transfer 和 HDR metadata 输出到 layer 与 display。

横倒问题优先定位在 metadata 到渲染层的传递边界。container 中已有 rotation,decoder 输出像素成功,说明压缩流可解码;最终显示方向错误,重点看播放器是否读取 track transform,layout 是否使用 display size,render path 是否应用旋转矩阵。

拖动后前两秒音画不同步优先定位在 seek、keyframe、decoder flush 和队列重建边界。seek 后系统要跳到 sync sample 附近,清空旧的 audio/video queue,再用新的 PTS 对齐 audio clock。短暂不同步说明文件整体 timestamp 未必损坏,更可能是 seek 后队列状态、reorder buffer 或 clock 重建存在偏差。

HDR 偏灰优先定位在 color metadata、decoder output format 和 display render path。HEVC Main10 只说明压缩格式和 bit depth,最终 HDR 体验还依赖 transfer function、color primaries、range、mastering display metadata、HDR-capable surface 和屏幕能力。metadata 在 container 中存在,但中间层未传递或 display path 按 SDR 处理,都会造成偏灰。

本章知识点总结

  • Container:Container 组织多个媒体轨、样本表、索引、时间线和元数据,使音频、视频、字幕和封面能作为一个文件被读取。
  • Elementary Stream:Elementary stream 是单一媒体类型的编码流,codec 直接消费它的压缩语法。
  • Demux 边界:Demuxer 从 container 中拆出 track format 和 compressed sample,把文件结构问题转换成 sample 流问题。
  • Codec 边界:Codec 负责压缩和解压,profile、level、bit depth、分辨率和硬件能力共同决定能否稳定处理。
  • Codec Data:VPS、SPS、PPS 或音频初始化数据为 decoder 提供解释后续 bitstream 的必要上下文。
  • Sample 层级:Sample 或 packet 绑定压缩字节、track index、timestamp、duration 和 flags,是 demux 到 codec 的交接单位。
  • Frame 语境:Frame 在编码输入和解码输出中含义不同,判断时要先定位它属于 codec input 还是 render output。
  • NAL Unit:NAL unit 是 H.264、HEVC 等视频 codec 内部语法单元,多个 NAL unit 可以组成一个访问单元。
  • PTS:PTS 决定样本应展示或播放的媒体时间,是渲染调度和音画同步的核心字段。
  • DTS:DTS 决定样本进入 decoder 的顺序,带 B frame 的视频会让解码顺序和显示顺序分离。
  • Timebase:Timebase 把时间戳整数转换成秒,跨轨道比较前必须归一到共同时间轴。
  • Audio Clock:移动播放器常以 audio output clock 为主时钟,再让视频帧按 PTS 追随播放时间线。
  • Metadata:Rotation、display size、color space、HDR、location 和 cover art 会直接影响相册、播放器和分享路径。
  • HDR 链路:HDR 正确显示依赖 metadata、decoder output format、render surface 和屏幕能力共同成立。
  • 排查顺序:媒体失败应按 container、track、codec、timestamp、clock、metadata 和设备输出边界逐层定位。