简介:SMPTE 274M-2008是电视行业针对1920×1080高清图像格式的正式标准,面向广播工程、视频设备研发与信号处理相关技术人员,解决多帧率下图像样本结构、数字表示及定时参考序列的规范化问题。PDF共35页,单文件509KB,内容包含标准的完整正文及附录,涵盖图像结构、系统色度、栅格结构、数字图像表示、SAV/EAV数字定时参考序列、模拟同步与接口规范,并延伸至渐进分段帧接口、辅助数据、位并行接口及前后滤波特性等关键技术细节。目前已有110人学习下载,适合需要查阅原始规范、确保设备互操作性或开展高清视频系统设计的工程师作为案头参考。通过这份原始英文标准,可准确核对1080i/1080p等格式的采样结构、色彩参数和同步信号定义,避免二手资料的不确定性。
1. SMPTE 274M-2008 到底在定什么:从 1920x1080 到“能交换的比特流”
一台采集卡标称支持 1080p60,但把裸 YUV 流送进 FPGA 或软件栈时,第一件事不是算像素,而是找行消隐在哪结束、SAV 在哪出现、黑电平码字是 64 还是 16。1920x1080 只是 Image Sample Structure 里“有效样本”这一项,SMPTE 274M-2008 真正回答的是三件事:样本在行场结构里怎么摆放(active line / blanking)、每个样本在 8bit 和 10bit 下怎么编码(Digital Representation)、以及每行从哪里开始和结束(Digital Timing Reference Sequences)。这份标准是 1125 行系统里 1080 有效行格式的基准,HDMI 的 video timing、SDI 的 payload、甚至部分屏幕驱动里的 timing generator 都能回溯到它。适合做视频采集、显示驱动、FPGA 监视器和编解码测试的工程师,也适合刚接触视频格式、想搞懂 1080p 时序表为什么长成那样的人。
2. 1920x1080 的样本结构与色度采样:有效区、消隐区和 4:2:2 怎么对齐
2.1 先看 1125 行里的有效区与消隐区
SMPTE 274M 定义的系统不是 1080 行,而是 1125 行一帧,其中 1080 行是有效视频,其余 45 行用于垂直消隐。有效行编号在逐行和隔行模式下略有差异,但常规做法是把第 1 行到第 41 行左右作为垂直消隐区间,之后进入有效区,直到帧末。你拿到的 1080p 信号如果显示出来画面上下有黑边,经常就是这 45 行被错误地当成有效像素处理了。
水平方向上,每行不是 1920 个样本到头。标准按不同帧率给每行分配了不同的总样本数,有效样本固定 1920,剩下的部分就是水平消隐。水平消隐里除了承载 SAV/EAV 定时参考码,还可能有辅助数据(ANC),这部分在驱动开发和采集测试里最容易出问题:把消隐样本灌进帧缓冲,画面会出现右侧垂直条纹;把消隐直接裁掉,又可能把 ANC 里的 HDR 或音频元数据丢掉。
2.2 4:2:2 色度采样:亮度和色度的位置关系
样本结构不只是亮度。SMPTE 274M 支持 4:2:2 和 4:4:4 两种色度采样,电视制作链路里默认是 4:2:2。4:2:2 的含义是每两个亮度样本对应一个 Cb 一个 Cr,所以一条 1920 样本宽的亮度行,对应的是 960 个 Cb 和 960 个 Cr。放到内存布局里,10bit 4:2:2 常见的排布是 Cb Y Cr Y 交错,也就是每个像素至少有一个 Y,但色度是每两像素共享一次。
表格列出不同帧率对应的行结构,这是做 timing generator 时直接要查的参数:
| 帧率族 | 每行总样本 | 有效样本 | 采样率 (MHz) | 行频 (kHz) |
|---|---|---|---|---|
| 1080i/60, 1080p/30 | 2200 | 1920 | 74.25 | 33.75 |
| 1080i/59.94, 1080p/29.97 | 2200 | 1920 | 74.25 / 1.001 | 33.75 / 1.001 |
| 1080p/60 | 2200 | 1920 | 148.5 | 67.5 |
| 1080p/50 | 2640 | 1920 | 148.5 | 56.25 |
| 1080i/50, 1080p/25 | 2640 | 1920 | 74.25 | 28.125 |
| 1080p/24 | 2750 | 1920 | 74.25 | 27 |
| 1080p/23.98 | 2750 | 1920 | 74.25 / 1.001 | 27 / 1.001 |
看到 2200、2640、2750 这三个数不要背错。它们的来源是:采样率固定后,行频由帧率和 1125 总行数决定,再用采样率除以行频得到每行总样本。1080p/60 的采样率是 148.5 MHz,行频 67.5 kHz,所以每行 2200 个样本;1080p/24 采样率 74.25 MHz,行频 27 kHz,每行就是 2750 个样本。行频和总样本数会直接影响你对流做行同步时的步进长度。
2.3 用 ffprobe 核对容器里的有效区元数据
拿到一个视频文件,先用 ffprobe 看它的解析度和色度信息是否和 274M 的约定一致:
ffprobe -v error -select_streams v:0 \ -show_entries stream=width,height,pix_fmt,color_range,color_space,field_order \ -of default=noprint_wrappers=1 input.mov输出里重点看两项:color_range 为 tv 或 pc,以及 field_order。274M 电视应用默认是 tv range(有限范围),也就是前面表格里说的黑电平不是 0 而是 16/64。如果 color_range 显示 pc,说明文件可能已经做过了 range 转换,后面做 YCbCr 转 RGB 时不能直接套电视标准系数。
这个命令只验证“文件头声称的格式”,不验证流内容是否真的符合 274M 的时序。真正的行结构校验,要靠解析 SAV/EAV 完成,这在第 4 章会展开。先把格式参数固定下来,后面所有转换和时序判断才有基准。
3. 数字表示:码字范围、黑电平与防过冲的量化边界
3.1 8bit 和 10bit 的码字映射
SMPTE 274M 的 Digital Representation 规定样本值不是 0~255 或 0~1023 全范围使用的。以亮度 Y 为例,8bit 下黑电平是 16,参考白是 235;10bit 下对应的是 64 和 940。色度 Cb/Cr 以消隐电平为中心,8bit 中心值是 128,10bit 是 512。表格里这几个数字值得记牢:
| 信号分量 | 8bit 范围 | 10bit 范围 | 参考电平 |
|---|---|---|---|
| Y 黑电平 | 16 | 64 | 有效范围下限 |
| Y 参考白 | 235 | 940 | 有效范围上限 |
| Cb/Cr 中心 | 128 | 512 | 色差零值 |
| 预留保护区 | 0~15, 236~255 | 0~63, 941~1023 | 同步和过冲预留 |
上下两端留出的保护区不是浪费。0 和 255(8bit)在 SDI 和并行接口里要留给定时参考序列的同步字使用,如果视频内容把像素填到全范围,同步字就可能会被误判成图像数据。另外模拟前端过冲、滤波振铃都可能让像素值越过正常边界,预留区给这部分失真留了缓冲。
3.2 为什么坚持用有限范围而不是全范围
消费级视频设备经常把 8bit 用满 0~255,这叫 full range,也叫 pc range。274M 面向电视链路,默认是 limited range。两者的转换关系不是简单的线性缩放那么简单,因为 black level 和 white level 的坐标都变了。做 10bit 到 8bit 转换时,常规做法是右移两位:v8 = (v10 + 2) >> 2,注意要先加 2 再移位,相当于做四舍五入而不是直接截断。反过来 8bit 升 10bit 是左移两位,低位补零即可,不需要加数值。
很多显示驱动里看到的“画面发灰”问题,根因就是往 RGB 面板送数据时,把 limited range 的 YCbCr 直接用 full range 系数做了矩阵运算,黑电平被抬到了 16/255 的位置,对比度一下就丢了。先确认 source 的 range,再决定转换矩阵,比调亮度和对比度参数有效得多。
3.3 YCbCr 转 RGB 的一个可靠实现
下面这段代码是 10bit limited range 到 8bit RGB 的完整转换,系数组来自 BT.709 的常规数字实现。输入 y、cb、cr 是 10bit 值,输出是 0~255 的 R、G、B。
def ycbcr10_to_rgb8(y, cb, cr): # 10bit limited range -> 归一化 y = (y - 64) / (940 - 64) cb = (cb - 512) / (960 - 64) # 10bit 色度标称范围 64~960 cr = (cr - 512) / (960 - 64) # BT.709 系数矩阵,y 范围已归一到 0~1 r = y + 1.5748 * cr g = y - 0.1873 * cb - 0.4681 * cr b = y + 1.8556 * cb # 量化到 8bit 并钳位 r = max(0, min(255, round(r * 255))) g = max(0, min(255, round(g * 255))) b = max(0, min(255, round(b * 255))) return r, g, b参数说明里要注意两点。第一,色度的归一化范围用了 64~960 而不是 512 为中心再对称展开,这是按 10bit 色度信号的标称范围做的,跟亮度用 64~940 略有区别,避免色差分量增益偏大。第二,矩阵系数 1.5748、-0.1873、-0.4681、1.8556 是 BT.709 有限范围到 RGB 的常见常量,你自己写硬件时这些系数可以固化成定点数,注意符号位和精度够不够 10bit 输入。
这段代码只解决“数值翻译”问题。它不处理 gamma 编码,因为视频链路里的 YCbCr 本身是经过光电转换曲线编码的,直接矩阵运算后得到的是非线性 RGB,通常还要再接一条 EOTF 才能显示。显示器驱动里有很多“颜色不对”的 bug 就是少了这一步,扯远了,先记住:先 range,再矩阵,最后 gamma。
4. 数字定时参考序列:SAV/EAV、P0-P3 保护位与多帧率换算
4.1 定时参考码的四字结构
每一行图像数据在传输时由三个段组成:消隐前的 EAV(End of Active Video)、消隐后的 SAV(Start of Active Video)、中间的有效样本区。EAV 和 SAV 都是四个字长的定时参考序列。8bit 模式下是 FF 00 00 XY 四个字节,10bit 模式是 3FF 000 000 XY 四个字。前三个字是固定同步字,最后一个 XY 字携带行状态信息。
XY 字的位定义是:bit7 固定为 1;bit6 是 F,隔行模式下标识奇偶场,逐行模式恒为 0;bit5 是 V,表示当前是否处于垂直消隐,V=1 在消隐区;bit4 是 H,EAV 里 H=1,SAV 里 H=0。bit3 到 bit0 是保护位 P3~P0,用来做误码检测。
4.2 保护位 P0-P3 的生成和校验
P0~P3 不是冗余添加,它们由 F、V、H 的异或组合生成。公式如下:
P0 = V XOR H P1 = F XOR H P2 = F XOR V P3 = F XOR V XOR H生成 XY 字的 Python 实现:
def build_xy(f, v, h): p0 = v ^ h p1 = f ^ h p2 = f ^ v p3 = f ^ v ^ h return 0x80 | (f << 6) | (v << 5) | (h << 4) | (p3 << 3) | (p2 << 2) | (p1 << 1) | p0以逐行有效行为例,F=0、V=0、H=0 时,SAV 的 XY 是 0x80。EAV 在同样条件下 F=0、V=0、H=1,代入公式算出 P0=1、P1=1、P2=0、P3=1,得到 0x9B。很多人把 EAV 记成 0x9D,那是垂直消隐区 SAV 的值——F=0、V=1、H=0 时 P0=1、P1=0、P2=1、P3=1,才得到 0x9D。解析时不要背固定值,直接用公式校验前四位和 XY 的后四位是否一致,这样对误码的容忍度更高。
4.3 多帧率下总样本数和行频的换算逻辑
回到第 2 章的表格,2200、2640、2750 这三个总样本数不是并列的三种格式族。它们对应的采样率族分别是:60/59.94 系的 33.75 kHz 行频 + 2200 样本,25/50 系的 28.125 kHz 行频 + 2640 样本,24 系的 27 kHz 行频 + 2750 样本。从 SAV 到下一个 SAV 的距离,就是每行总样本数。换成字节数,还要乘以每个样本的字节数——4:2:2 10bit 每 4 个样本打包成 5 字节,这个在硬件位流里常见,软件里则多按 32bit 对齐处理。
做软件解析时有个人人都踩的坑:按帧率直接假设行长为 1920 的倍数。正确做法是先找 EAV,数两个 SAV 之间的样本数,用这个实测值反推行频和帧率。下面这段代码在裸 YUV 流里定位 SAV/EAV,并统计每行样本数:
import sys def find_timing(data, bit_depth=8): sync_size = 4 if bit_depth == 8 else 4 # 这里按 8bit 流处理 # 8bit: FF 00 00 XY results = [] i = 0 while i < len(data) - 4: if data[i] == 0xFF and data[i+1] == 0x00 and data[i+2] == 0x00: xy = data[i+3] f = (xy >> 6) & 0x01 v = (xy >> 5) & 0x01 h = (xy >> 4) & 0x01 p3 = (xy >> 3) & 0x01 p2 = (xy >> 2) & 0x01 p1 = (xy >> 1) & 0x01 p0 = xy & 0x01 if (p0 == (v ^ h) and p1 == (f ^ h) and p2 == (f ^ v) and p3 == (f ^ v ^ h)): results.append((i, f, v, h)) i += 4 else: i += 1 return results with open(sys.argv[1], 'rb') as fh: buf = fh.read() res = find_timing(buf) print(f"找到 {len(res)} 个定时参考码")逻辑说明:同步字是 FF 00 00 三个字节,找到后取第四个字节做 XY 校验。校验通过才记录,否则跳过。这样能滤掉视频内容里恰好出现的 FF 00 00。输出结果里 F、V、H 的值组合起来,就能画出每一行的消隐状态。实际做行同步时,更稳的办法是找 EAV(H=1)作为行边界锚点,再数固定偏移到 SAV。这个脚本的问题是不处理 10bit,10bit 流里同步字是 3FF 000 000,字节序跟 8bit 不同,需要按 16bit 对齐扫描。
4.4 多帧率混合场景下的判定优先级
同一个采集卡可能收到 1080i/59.94 和 1080p/29.97 两种流,它们行频近似、帧率相同,区别只在 F bit 是否在每行切换。判定的优先级是这样的:先统计 V=1 的行数,判断垂直消隐长度;再看 F bit 是否随行号交替变化,判断隔行还是逐行;最后用两个 SAV 之间的距离推断总样本数,反查帧率。不要把帧率当成第一判断依据,时序结构比帧率更稳定,因为锁定结构只需要几行数据,锁帧率则需要跨越多帧统计。
5. 用一份小脚本对 YUV 裸流做 274M 合规性自检
最后给一个能直接跑的合规性检查脚本,它不做完整解码,只验证时序结构是否满足 SMPTE 274M 的基本要求。输入是一段 8bit 4:2:2 的 YUV 裸流,输出每行的 SAV/EAV 位置、垂直消隐行数和推断出的帧率族。
#!/usr/bin/env python3 import sys def parse_xy(xy): f = (xy >> 6) & 1 v = (xy >> 5) & 1 h = (xy >> 4) & 1 p = [(xy >> 0) & 1, (xy >> 1) & 1, (xy >> 2) & 1, (xy >> 3) & 1] return f, v, h, p def check_protect(f, v, h, p): return (p[0] == (v ^ h) and p[1] == (f ^ h) and p[2] == (f ^ v) and p[3] == (f ^ v ^ h)) def main(): if len(sys.argv) < 2: print("usage: check_274m.py input.yuv") return with open(sys.argv[1], 'rb') as fh: data = fh.read() markers = [] i = 0 while i < len(data) - 4: if data[i] == 0xFF and data[i+1] == 0x00 and data[i+2] == 0x00: f, v, h, p = parse_xy(data[i+3]) if check_protect(f, v, h, p): markers.append((i, f, v, h)) i += 4 else: i += 1 eav_pos = [m[0] for m in markers if m[3] == 1] if len(eav_pos) < 2: print("未找到足够多的 EAV,无法判定格式") return line_lens = [eav_pos[k+1] - eav_pos[k] for k in range(len(eav_pos)-1)] from collections import Counter common_len = Counter(line_lens).most_common(1)[0][0] total_samples = common_len # 8bit 4:2:2 下字节数即样本数 vbi_lines = sum(1 for m in markers if m[3] == 1 and m[2] == 1) total_lines = len(eav_pos) family = {2200: "60/59.94 系", 2640: "50/25 系", 2750: "24 系"}.get(total_samples, "未知") print(f"总 EAV 数: {total_lines}") print(f"垂直消隐行数: {vbi_lines}") print(f"每行总样本: {total_samples}, 属于 {family}") print(f"SAV/EAV 交替正常: {all(markers[k][3] != markers[k+1][3] for k in range(len(markers)-1))}") if __name__ == "__main__": main()运行方式:
python3 check_274m.py capture_1080p.yuv看输出时有个判断技巧:总样本数落在 2200、2640、2750 三档之一,基本可以认定行结构符合 274M;如果出现 1920 或者是 2200 附近但不等于 2200 的数,不要怀疑算法,先查是不是把 10bit 流当成 8bit 读了,或者源流本身经过了裁剪。垂直消隐行数如果跟预期帧率对不上,再用 ffprobe 或其它工具确认场序,很多采集驱动会在隔行转逐行时把 V 信号改掉,标准合规的流不会出现这种情况。
把这段脚本固化成一个通用工具函数,以后每次拿到新格式的裸流都先跑一遍,能省掉大量手动数行的时间。遇到奇偶场切换,再加一个判断,F bit 在隔行模式下行号奇偶变化是否同步,就能顺带识别出隔行还是逐行。到此,SMPTE 274M-2008 的三个核心部分——样本结构、数字表示、定时参考序列已经全部落到可执行代码上了。
本文还有配套的精品资源,点击获取