数据格式 的子部分

第1章

通用格式设计

1 序


二进制文件的格式一般采用结构 文件头 + 核心内容

  • 文件头:标识文件的基本信息,其设计方法基本一致。
  • 核心内容:这部分是真正的文件数据内容。

2 目录


通用格式设计 的子部分

1.1 文件头



描述了文件的整体信息,常见的字段有魔数、检验码、版本号、文件大小等。

顾名思义,即是将文件的整体信息通常放在文件的开头。

在少数情况下,也会将文件头放在文件的尾部,也就是‘文件尾’,但是一般还是叫做 文件头

1 魔数


魔数作为文件格式的标识,一般用于识别文件是否程序所能处理的文件。其值可以随意选取,主要看设计者自身的喜好。

例如 zip 格式的魔数就是 “PK\x03\x04”,其中 PK 就是设计者 Philip Katz 的名字首字母。

一般程序在分析文件时,会先判断魔数的值是否匹配,不匹配就表明文件格式不正确。

需要注意的是,假如魔数正确,文件格式并非一定能够读取正确,还需要进一步判断。

2 检验码


文件头通常还会有个检验码,用于检验文件是否完整并且没有经过修改的。这个检验码可以使用 crc, 可以使用 md5,也可以使用其它算法。

这也是检查文件格式的正确性中的重要一步,因为即便是魔数相同,校验码所采用的算法不同,和校验码所作的位置不同,所计算的校验码,基本上是不一的,所以进一步就,能区分出文件格式是否正确。

3 版本号


文件头通常还会包含版本号。

版本号不同,就表明文件格式发生变化,可能变化很小,也可能变化很大;可能是某些字段的值在解释上发生变化,也可能是直接添加了一些结构。所以导致文件的读取方式可能会有所不同。

版本号有时只是单独一个数字,不断往上递增。有时也会拆分成两个数字,为主版本号和次版本号。主版本号修改,通常表示文件格式发生大变动。而次版本号修改,通常只是表示添加了一些小功能。

4 字节顺序


字节顺序在文件格式设计中至关重要。

字节顺序分为大端字节序和小端字节序。不同的机器字节序有可能不同,设计文件格式时需要考虑文件用什么字节序保存数据的。

读取和存储的字节序不一致就会导致程序出错。

5 通用结构


通用结构定义如下:

struct FileHeader{
    uint8 mMagic[4];    //魔数
    uint8 mHash[16];    //检验码
    uint32 mEndian;     //字节序
    uint32 mVersion;    //文件版本
    ...
};

1.2 段节格式



分段(或叫分节,后续默认叫法为分段)的二进制文件格式是比较常见的,比如编译器 GCC 输出的目标文件一般为 ELF 的分段文件。

1 整体结构


这类文件一般采用 文件头 + 分段 的结构:

file header 
section 0
section 1
....
section N

文件头设计方法在 文件头 中介绍,本章节主要描述分段式存储数据的方法。

2 分段方法


一般有两种结构:

  • 分散式:在分区的头部的描述分段的大小等信息。
  • 段表式:存在统一的结构描述分段的大小、相对偏移等信息。

分散式

分散式分结构在分区的头部的描述分段的大小等信息,然后各个分段首尾相连的,分段写入。

分段的每一个段的结构通常是:

tag + length
section data

tag 和 length 合起来是分区头部,描述分段中的数据标识,后面紧跟着体数据。

tag 一般是一个整数,用来标识分区的类型。不同的分区用不同的 tag 值表示,不同的分区种类可以使用不同的读取方式。

整体结构如下:

file header 
section 0
            tag     //section 0 的标识
            length  //section 0 的数据长度
            data[]  //section 0 的数据
section 1
....
section N

段表式

段表式的结构是有一个描述分段信息的段表,后面存储各个分区的数据。

主要优点就是,可以通过段描述表(简称段表)快速的访问分段的内容,其格式如下。

section table
section 0
section 1
....
section N

段表一般描述分段的类型、数据长度、相对偏移等,结构如下。

kind
length
offset

C 语言定义如下:

struct SegTabItem{
    uint32 kind;    //类型
    uint32 length;  //数据长度
    uint32 offset;  //相对偏移
};

整体结构如下:

file header 
section table
                kind    //section 0 的类型
                length  //section 0 的数据长度
                offset  //section 0 的相对偏移
                kind    //section 1 的类型
                length  //section 1 的数据长度
                offset  //section 1 的相对偏移
                ...
                kind    //section N 的类型
                length  //section N 的数据长度
                offset  //section N 的相对偏移
section 0
section 1
....
section N

1.3 TLV格式



1 简介


TLV 是一种可变的格式,由三个域构成:标识域(Tag)+长度域(Length)+值域(Value),简称TLV格式。

其中:

  • T 可以理解为 Tag 或 Type ,用于标识标签或者编码格式信息;
  • L 定义数值的长度;
  • V 表示实际的数值。

T 和 L 的长度固定,一般是2或4个字节,V 的长度由 Length 指定。

  • T 和 L 一般都是整数值。
  • V 可以存储整数、浮点、字符串、字节串,其类型是由格式定义者根据 T 的不同值,指定不同的类型。

2 基本结构


data 0
    T = 1
    L = 1
    V = 1
data 1
    T = 1
    L = 1
    V = 2
...
data N
    T = 1
    L = 1
    V = n

当然V中的数据也是可以嵌套,至于嵌套几层看设计者的规定的。

结构如下:

data 0
    T = 1
    L = 3
    V =[
            data x
            T = 1
            L = 1
            V = 1
        ]
data 1
    T = 1
    L = 1
    V = 2
...
data N
    T = 1
    L = 1
    V = n

程序格式 的子部分

ELF 格式 的子部分

1.1 ELF32 格式


1.2 ELF64 格式


第3章

颜色格式

1 序


颜色常用颜色空间来表示。颜色空间是用一种数学方法形象化表示颜色,人们用它来指定和产生颜色。例如,

  • 对于人来说,我们可以通过色调、饱和度和明度来定义颜色;
  • 对于显示设备来说,人们使用红、绿和蓝磷光体的发光量来描述颜色;
  • 对于打印或者印刷设备来说,人们使用青色、品红色、黄色和黑色的反射和吸收来产生指定的颜色。

颜色空间有设备相关和设备无关之分。

设备相关的颜色空间是指颜色空间指定生成的颜色与生成颜色的设备有关。例如,RGB颜色空间是与显示系统相关的颜色空间,计算机显示器使用RGB来显示颜色,用像素值(例如,R=250,G=123,B=23)生成的颜色将随显示器的亮度和对比度的改变而改变。

设备无关的颜色空间是指颜色空间指定生成的颜色与生成颜色的设备无关,例如,CIE Lab*颜色空间就是设备无关的颜色空间,它构建在HSV(hue, saturation and value)颜色空间的基础上,用该空间指定的颜色无论在什么设备上生成的颜色都相同。

2 目录


颜色格式 的子部分

3.1 RGBA


RGB 的基本概念

RGB 代表红(Red)、绿(Green)、蓝(Blue)。这三种颜色被称为光的三原色。在 RGB 色彩模式下,通过不同强度比例的红、绿、蓝三种光的混合,可以产生出各种各样的颜色。

从物理学角度来看,光是一种电磁波,而人眼能够感知到的可见光波段内,红、绿、蓝这三种颜色的光具有独特的波长范围。红色光的波长大约在 620 - 750 纳米之间,绿色光波长约为 495 - 570 纳米,蓝色光波长则在 450 - 480 纳米左右。当这三种颜色的光以不同的能量强度同时作用于人眼时,大脑就会感知到不同的色彩。 例如,当红色光以最强的强度发出,而绿色光和蓝色光强度为零时,我们看到的就是纯粹的红色;同理,单独最强强度的绿色光呈现绿色,单独最强强度的蓝色光呈现蓝色。而当三种光强度相等时,就会产生白色光。

RGB的一些具体格式

其中 RGB、RGBA是比较常用的。

RGB24

RGB24 是一种 24 位的像素格式,它使用 8 位来表示红、绿和蓝的颜色分量。也被称作 RGB888

typedef struct
{
    unsigned char r;
    unsigned char g;
    unsigned char b;
}RGB,RGB24,RGB888;

#define RGB(r,g,b) ((unsigned int)(((unsigned char)(r)|(((unsigned char)(g))<<8))|(((unsigned char)(b))<<16)))

注:windows 平台下,一般使用BGR。颜色分量顺序为 BGR。

RGBA32

RGBA32 是一种 32 位的像素格式,它使用 8 位来表示红、绿、蓝和透明度的颜色分量。也被称作 RGB8888

typedef union
{
    unsigned int rgba;
    struct
    {
        unsigned char r;
        unsigned char g;
        unsigned char b;
        unsigned char a;
    };
}RGBA,RGBA32,RGBA8888;

#define RGBA(r,g,b,a) ((unsigned int)(((unsigned char)(r)|(((unsigned char)(g))<<8))|(((unsigned char)(b))<<16)|(((unsigned char)(a))<<24)))

RGB555

RGB555 是一种 16 位的像素格式,它使用 5 位来表示红、绿和蓝的颜色分量。其中的1位作为透明分量(可以不使用),共 16 位。

typedef struct
{
    unsigned short r : 5;
    unsigned short g : 5;
    unsigned short b : 5;
    unsigned short a : 1;
}RGBA555;

//各分量取值:
#define RGB555_MASK_RED 0x7C00
#define RGB555_MASK_GREEN 0x03E0
#define RGB555_MASK_BLUE 0x001F
#define RGB555_MASK_ALPHA 0x8000
R = (wPixel & RGB555_MASK_RED) >> 10; // 取值范围0-31
G = (wPixel & RGB555_MASK_GREEN) >> 5; // 取值范围0-31
B = wPixel & RGB555_MASK_BLUE; // 取值范围0-31
A = (wPixel & RGB555_MASK_ALPHA) >> 15;

#define RGB555(r,g,b,a) (unsigned short)( (r|0x08 << 10) | (g|0x08 << 5) | b|0x08 | (a << 15) )

RGB565

RGB565使用16位表示一个像素,这16位中的5位用于R,6位用于G,5位用于B。程序中通常使用一个字(WORD,一个字等于两个字节)来操作一个像素。当读出一个像素后,这个字的各个位意义如下:

typedef struct
{
    unsigned short r : 5;
    unsigned short g : 6;
    unsigned short b : 5;
}RGB565;

//各分量取值:
#define RGB565_MASK_RED 0xF800
#define RGB565_MASK_GREEN 0x07E0
#define RGB565_MASK_BLUE 0x001F
R = (wPixel & RGB565_MASK_RED) >> 11;
G = (wPixel & RGB565_MASK_GREEN) >> 5;
B = wPixel & RGB565_MASK_BLUE;

#define RGB(r,g,b) (unsigned int)( (r|0x08 << 11) | (g|0x08 << 6) | b|0x08 )

3.2 HSL/HSV


HSL是一种将RGB色彩模型中的点在圆柱坐标系中的表示法。这两种表示法试图做到比基于笛卡尔坐标系的几何结构RGB更加直观。是运用最广的颜色系统之一

HSL即色相、饱和度、亮度(英语:Hue, Saturation, Lightness)。 色相(H)是色彩的基本属性,就是平常所说的颜色名称,如红色、黄色等。 饱和度(S)是指色彩的纯度,越高色彩越纯,低则逐渐变灰,取0-100%的数值。 明度(V),亮度(L),取0-100%。

HSV即色相、饱和度、明度(英语:Hue, Saturation, Value),又称HSB,其中B即英语:Brightness。 HSL和HSV二者都把颜色描述在圆柱坐标系内的点,这个圆柱的中心轴取值为自底部的黑色到顶部的白色而在它们中间的是灰色,绕这个轴的角度对应于“色相”,到这个轴的距离对应于“饱和度”,而沿着这个轴的高度对应于“亮度”、“色调”或“明度”。 这两种表示在目的上类似,但在方法上有区别。二者在数学上都是圆柱,但HSV(色相、饱和度、明度)在概念上可以被认为是颜色的倒圆锥体(黑点在下顶点,白色在上底面圆心),HSL在概念上表示了一个双圆锥体和圆球体(白色在上顶点,黑色在下顶点,最大横切面的圆心是半程灰色)。注意尽管在HSL和HSV中“色相”指称相同的性质,它们的“饱和度”的定义是明显不同的。 因为HSL和HSV是设备依赖的RGB的简单变换,(h,s,l)或 (h,s,v)三元组定义的颜色依赖于所使用的特定RGB“加法原色”。每个独特的RGB设备都伴随着一个独特的HSL和HSV空间。但是 (h,s,l)或 (h,s,v)三元组在被约束于特定RGB空间比如sRGB的时候就更明确了。

HSL、HSV与RGB的转换

HSL和HSV在数学上定义为在RGB空间中的颜色的R,G和B的坐标的变换。

HSL与RGB的转换

// 定义结构体表示 HSL 颜色
typedef struct {
    float h; // 色调,范围 [0, 360]
    float s; // 饱和度,范围 [0, 1]
    float l; // 亮度,范围 [0, 1]
} HSL;

// 将 RGB 转换为 HSL
HSL rgb_to_hsl(int r, int g, int b) {
    // 归一化 RGB 值
    float rf = r / 255.0;
    float gf = g / 255.0;
    float bf = b / 255.0;

    // 计算最大值和最小值
    float max = fmaxf(fmaxf(rf, gf), bf);
    float min = fminf(fminf(rf, gf), bf);
    float delta = max - min;

    HSL hsl = {0, 0, 0};

    // 计算亮度 L
    hsl.l = (max + min) / 2.0;

    if (delta == 0) {
        hsl.h = 0; // 色调不确定,设为 0
        hsl.s = 0; // 饱和度为 0
    } else {
        // 计算饱和度 S
        if (hsl.l < 0.5) {
            hsl.s = delta / (max + min);
        } else {
            hsl.s = delta / (2.0 - max - min);
        }

        // 计算色调 H
        if (rf == max) {
            hsl.h = (gf - bf) / delta;
        } else if (gf == max) {
            hsl.h = 2 + (bf - rf) / delta;
        } else {
            hsl.h = 4 + (rf - gf) / delta;
        }

        hsl.h *= 60.0; // 转换为度数
        if (hsl.h < 0) {
            hsl.h += 360.0;
        }
    }

    return hsl;
}

// 将 HSL 转换为 RGB
RGB hsl_to_rgb(HSL hsl) {
    RGB rgb = {0, 0, 0};

    if (hsl.s == 0) {
        // 如果饱和度为 0,则是灰度颜色
        rgb.r = rgb.g = rgb.b = (int)(hsl.l * 255);
        return rgb;
    }

    float c = (1 - fabs(2 * hsl.l - 1)) * hsl.s;
    float x = c * (1 - fabs(fmod(hsl.h / 60.0, 2) - 1));
    float m = hsl.l - c / 2;

    float r, g, b;

    if (hsl.h >= 0 && hsl.h < 60) {
        r = c; g = x; b = 0;
    } else if (hsl.h >= 60 && hsl.h < 120) {
        r = x; g = c; b = 0;
    } else if (hsl.h >= 120 && hsl.h < 180) {
        r = 0; g = c; b = x;
    } else if (hsl.h >= 180 && hsl.h < 240) {
        r = 0; g = x; b = c;
    } else if (hsl.h >= 240 && hsl.h < 300) {
        r = x; g = 0; b = c;
    } else if (hsl.h >= 300 && hsl.h < 360) {
        r = c; g = 0; b = x;
    }

    rgb.r = (int)((r + m) * 255);
    rgb.g = (int)((g + m) * 255);
    rgb.b = (int)((b + m) * 255);

    return rgb;
}

代码解释:

  • 归一化:将输入的 RGB 值从 [0, 255] 范围归一化到 [0, 1] 范围。
  • 计算最大值和最小值:用于后续计算亮度和饱和度。
  • 计算亮度 L:根据最大值和最小值的平均值计算。
  • 计算饱和度 S:根据最大值和最小值的差值以及亮度 L 计算。
  • 计算色调 H:根据最大值对应的 RGB 分量计算,并转换为度数。

HSV与RGB的转换

// 定义结构体表示 RGB 颜色
typedef struct {
    int r; // 红色分量,范围 [0, 255]
    int g; // 绿色分量,范围 [0, 255]
    int b; // 蓝色分量,范围 [0, 255]
} RGB;

// 定义结构体表示 HSV 颜色
typedef struct {
    float h; // 色调,范围 [0, 360)
    float s; // 饱和度,范围 [0, 1]
    float v; // 明度,范围 [0, 1]
} HSV;

// 将 HSV 转换为 RGB
RGB hsv_to_rgb(HSV hsv) {
    RGB rgb = {0, 0, 0};
    float c = hsv.v * hsv.s;
    float x = c * (1 - fabs(fmod(hsv.h / 60.0, 2) - 1));
    float m = hsv.v - c;

    if (hsv.h >= 0 && hsv.h < 60) {
        rgb.r = (int)((c + m) * 255);
        rgb.g = (int)((x + m) * 255);
        rgb.b = (int)(m * 255);
    } else if (hsv.h >= 60 && hsv.h < 120) {
        rgb.r = (int)((x + m) * 255);
        rgb.g = (int)((c + m) * 255);
        rgb.b = (int)(m * 255);
    } else if (hsv.h >= 120 && hsv.h < 180) {
        rgb.r = (int)(m * 255);
        rgb.g = (int)((c + m) * 255);
        rgb.b = (int)((x + m) * 255);
    } else if (hsv.h >= 180 && hsv.h < 240) {
        rgb.r = (int)(m * 255);
        rgb.g = (int)((x + m) * 255);
        rgb.b = (int)((c + m) * 255);
    } else if (hsv.h >= 240 && hsv.h < 300) {
        rgb.r = (int)((x + m) * 255);
        rgb.g = (int)(m * 255);
        rgb.b = (int)((c + m) * 255);
    } else if (hsv.h >= 300 && hsv.h < 360) {
        rgb.r = (int)((c + m) * 255);
        rgb.g = (int)(m * 255);
        rgb.b = (int)((x + m) * 255);
    }

    return rgb;
}

// 将 HSV 转换为 RGB
RGB hsv_to_rgb(HSV hsv) {
    RGB rgb = {0, 0, 0};
    float c = hsv.v * hsv.s;
    float x = c * (1 - fabs(fmod(hsv.h / 60.0, 2) - 1));
    float m = hsv.v - c;

    if (hsv.h >= 0 && hsv.h < 60) {
        rgb.r = (int)((c + m) * 255);
        rgb.g = (int)((x + m) * 255);
        rgb.b = (int)(m * 255);
    } else if (hsv.h >= 60 && hsv.h < 120) {
        rgb.r = (int)((x + m) * 255);
        rgb.g = (int)((c + m) * 255);
        rgb.b = (int)(m * 255);
    } else if (hsv.h >= 120 && hsv.h < 180) {
        rgb.r = (int)(m * 255);
        rgb.g = (int)((c + m) * 255);
        rgb.b = (int)((x + m) * 255);
    } else if (hsv.h >= 180 && hsv.h < 240) {
        rgb.r = (int)(m * 255);
        rgb.g = (int)((x + m) * 255);
        rgb.b = (int)((c + m) * 255);
    } else if (hsv.h >= 240 && hsv.h < 300) {
        rgb.r = (int)((x + m) * 255);
        rgb.g = (int)(m * 255);
        rgb.b = (int)((c + m) * 255);
    } else if (hsv.h >= 300 && hsv.h < 360) {
        rgb.r = (int)((c + m) * 255);
        rgb.g = (int)(m * 255);
        rgb.b = (int)((x + m) * 255);
    }

    return rgb;
}

代码解释:

  • 定义结构体:RGB 和 HSV 结构体分别用于存储 RGB 和 HSV 颜色值。
  • 计算临时变量:
  • c:色度,计算公式为 V * S。
  • x:辅助变量,计算公式为 c * (1 - |(H / 60) % 2 - 1|)。
  • m:偏移量,计算公式为 V - c。
  • 根据色调 H 的范围选择相应的 RGB 值:通过条件判断,根据 H 的值选择合适的 RGB 分量。

3.3 YUV


简介

YUV,是一种颜色编码方法。常使用在各个视频处理组件中。 YUV在对照片或视频编码时,考虑到人类的感知能力,允许降低色度的带宽。 YUV是编译true-color颜色空间(color space)的种类,Y’UV, YUV, YCbCr,YPbPr等专有名词都可以称为YUV,彼此有重叠。“Y”表示明亮度(Luminance或Luma),也就是灰阶值,“U”和“V”表示的则是色度(Chrominance或Chroma),作用是描述影像色彩及饱和度,用于指定像素的颜色。

Y’代表明亮度(luma;brightness)而U与V存储色度(色讯;chrominance;color)部分;亮度(luminance)记作Y,而Y’的prime符号记作伽玛校正。 YUVFormats分成两个格式: 紧缩格式(packedformats):将Y、U、V值存储成MacroPixels数组,和RGB的存放方式类似。 平面格式(planarformats):将Y、U、V的三个分量分别存放在不同的矩阵中。 紧缩格式(packedformat)中的YUV是混合在一起的,对于YUV常见格式有AYUV格式(4:4:4采样、打包格式);YUY2、UYVY(采样、打包格式),有UYVY、YUYV等。平面格式(planarformats)是指每Y分量,U分量和V分量都是以独立的平面组织的,也就是说所有的U分量必须在Y分量后面,而V分量在所有的U分量后面,此一格式适用于采样(subsample)。平面格式(planarformat)有I420(4:2:0)、YV12、IYUV等。

常用的YUV格式

为节省带宽起见,大多数YUV格式平均使用的每像素位数都少于24位。主要的抽样(subsample)格式有YCbCr4:2:0、YCbCr4:2:2、YCbCr4:1:1和YCbCr4:4:4。YUV的表示法称为A:B:C表示法:

  • 4:4:4表示完全取样。
  • 4:2:2表示2:1的水平取样,垂直完全采样。
  • 4:2:0表示2:1的水平取样,垂直2:1采样。
  • 4:1:1表示4:1的水平取样,垂直完全采样。

最常用Y:UV记录的比重通常1:1或2:1,DVD-Video是以YUV4:2:0的方式记录,也就是我们俗称的I420,YUV4:2:0并不是说只有U(即Cb),V(即Cr)一定为0,而是指U:V互相援引,时见时隐,也就是说对于每一个行,只有一个U或者V分量,如果一行是4:2:0的话,下一行就是4:0:2,再下一行是4:2:0…以此类推。

至于其他常见的YUV格式有YUY2、YUYV、YVYU、UYVY、AYUV、Y41P、Y411、Y211、IF09、IYUV、YV12、YVU9、YUV411、YUV420等。

YUY2及常见表示方法

YUY2(和YUYV)格式为像素保留Y,而UV在水平空间上相隔二个像素采样一次(Y0U0Y1V0),(Y2U2Y3V2)…其中,(Y0U0Y1V0)就是一个macro-pixel(宏像素),它表示了2个像素,(Y2U2Y3V2)是另外的2个像素。以此类推,再如:Y41P(和Y411)格式为每个像素保留Y分量,而UV分量在水平方向上每4个像素采样一次。一个宏像素为12个字节,实际表示8个像素。

图像数据中YUV分量排列顺序如下:(U0Y0V0Y1U4Y2V4Y3Y4Y5Y6Y7)

YVYUUYVY

YVYU,UYVY格式跟YUY2类似,只是排列顺序有所不同。Y211格式是Y每2个像素采样一次,而UV每4个像素采样一次。AYUV格式则有一Alpha通道。

YV12

YV12格式与IYUV类似,每个像素都提取Y,在UV提取时,将图像2x2的矩阵,每个矩阵提取一个U和一个V。YV12格式和I420格式的不同处在V平面和U平面的位置不同。在YV12格式中,V平面紧跟在Y平面之后,然后才是U平面(即:YVU);但I420则是相反(即:YUV)。NV12与YV12类似,效果一样,YV12中U和V是连续排列的,而在NV12中,U和V就交错排列的。

3.4 XYZ


XYZ模型

CIE 1931 XYZ色彩空间(也叫做CIE 1931色彩空间)是其中一个最先采用数学方式来定义的色彩空间,它由国际照明委员会(CIE)于1931年创立。

XYZ 色彩空间作用

XYZ 色彩空间是为了解决更精确地定义色彩而提出来的, XYZ 三个分量中, XY代表的是色度, 其中Y分量既可以代表亮度也可以代表色度, 三个分量的单位都是 cd/m2 , (或者叫做nit)。我们无法用RGB来精确定义颜色, 因为,不同的设备显示的RGB都是不一样的,不同的设备, 显示同一个RGB, 在人眼看出来是千差万别的, 如果我们用XYZ定义一个设备的色彩空间, 这样就精确多了!

转换公式

XYZ 转 RGB

 R = 3.2406 * X + -1.5372 * Y + -0.4986 * Z
 G = -0.9689 * X + 1.8758 * Y + 0.0415 * Z
 B = 0.0557 * X + -0.2040 * Y + 1.0570 * Z

RGB 转 XYZ

X = 0.4124 * R + 0.3576 * G + 0.1805 * B
Y = 0.2126 * R + 0.7152 * G + 0.0722 * B
Z = 0.0193 * R + 0.1192 * G + 0.9505 * B

3.5 LAB


Lab色彩模型是由照度(L)和有关色彩的a, b三个要素组成。L表示照度(Luminosity),相当于亮度,a表示从红色至绿色的范围,b表示从蓝色至黄色的范围。L的值域由0到100,L=50时,就相当于50%的黑;a和b的值域都是由+120至-120,其中+120 a就是红色,渐渐过渡到-120 a的时候就变成绿色;同样原理,+120 b是黄色,-120 b是蓝色。所有的颜色就以这三个值交互变化所组成。

详情请在网络上搜索。

3.6 CMYK


CMYK代表印刷上用的四种颜色,C代表青色(Cyan),M代表洋红色或者品红(Magenta),Y代表黄色(Yellow),K代表黑色(Black)。因为在实际应用中,青色、洋红色和黄色很难叠加形成真正的黑色,最多不过是褐色而已。因此才引入了K——黑色。黑色的作用是强化暗调,加深暗部色彩。

详情请在网络上搜索。

第4章

压缩格式

1 序

2 目录


    第5章

    图片格式

    1 序

    2 目录


      视频格式 的子部分

      6.1 MP4文件格式重点全解析


      本文由 简悦 SimpRead 转码, 原文地址 zhuanlan.zhihu.com

      MP4 文件格式又被称为 MPEG-4 Part 14,出自 MPEG-4 标准第 14 部分 。它是一种多媒体格式容器,广泛用于包装视频和音频数据流、海报、字幕和元数据等。(顺便一提,目前流行的视频编码格式 AVC/H264 定义在 MPEG-4 Part 10)。MP4 文件格式基于 Apple 公司的 QuickTime 格式,因此,Introduction to QuickTime File Format Specification 也可以作为我们研究 MP4 的重要参考。

      作者:张武星

      审核:泰一

      01. Overview

      MP4 文件由 box 组成,每个 box 分为 Header 和 Data。其中 Header 部分包含了 box 的类型和大小,Data 包含了子 box 或者数据,box 可以嵌套子 box。

      下图是一个典型 MP4 文件的基本结构:

      图中看到 MP4 文件有几个主要组成部分:

      fytp

      File Type Box,一般在文件的开始位置,描述的文件的版本、兼容协议等。

      moov

      Movie Box,包含本文件中所有媒体数据的宏观描述信息以及每路媒体轨道的具体信息。一般位于 ftyp 之后,也有的视频放在文件末尾。注意,当改变 moov 位置时,内部一些值需要重新计算。

      mdat

      Media Data Box,存放具体的媒体数据。

      02. Moov Insider

      MP4 的媒体数据信息主要存放在 Moov Box 中,是我们需要分析的重点。moov 的主要组成部分如下:

      mvhd

      Movie Header Box,记录整个媒体文件的描述信息,如创建时间、修改时间、时间度量标尺、可播放时长等。

      下图示例中,可以获取文件信息如时长为 3.637 秒。

      udta

      User Data Box,自定义数据。

      track

      Track Box,记录媒体流信息,文件中可以存在一个或多个 track,它们之间是相互独立的。每个 track 包含以下几个组成部分:

      1. tkhd

      Track Header Box,包含关于媒体流的头信息。

      下图示例中,可以看到流信息如视频流宽度 720,长度 1280。

      2. mdia

      Media Box,这是一个包含 track 媒体数据信息的 container box。子 box 包括:

      • mdhd:Media Header Box,存放视频流创建时间,长度等信息。
      • hdlr:Handler Reference Box,媒体的播放过程信息。
      • minf:Media Information Box,解释 track 媒体数据的 handler-specific 信息。minf 同样是个 container box,其内部需要关注的内容是 stbl,这也是 moov 中最复杂的部分。

      stbl 包含了媒体流每一个 sample 在文件中的 offset,pts,duration 等信息。想要播放一个 MP4 文件,必须根据 stbl 正确找到每个 sample 并送给解码器。mdia 展开如下图所示:

      03. Stbl Insider

      Sample Table Box,上文提到 mdia 中最主要的部分是存放文件中每个 Sample 信息的 stbl。在解析 stbl 前,我们需要区分 Chunk 和 Sample 这两个概念。在 MP4 文件中,Sample 是一个媒体流的基本单元,例如视频流的一个 Sample 代表实际的 nal 数据。Chunk 是数据存储的基本单位,它是一系列 Sample 数据的集合,一个 Chunk 中可以包含一个或多的 Sample。

      stbl 用来描述每个 Sample 的信息,包含以下几个主要的子 box:

      stsd

      Sample Description Box,存放解码必须的描述信息。

      下图示例中,对于 h264 的视频流,其具体类型为 avc1,extensions 中存放有 sps,pps 等解码必要信息。

      stts

      Time-to-Sample Box,定义每个 Sample 时长。Time To Sample 的 table entry 布局如下:

      • Sample count:sample 个数
      • Sample duration:sample 持续时间

      持续时间相同的连续的 Sample 可以放到一个 entry 里面,以达到节省空间的目的。

      下图示例中,第 1 个 Sample 时间为 33362 微秒,第 2-11 个 Sample 时间为 33363 微秒:

      stss

      Sync Sample Box,同步 Sample 表,存放关键帧列表,关键帧是为了支持随机访问。stss 的 table entry 布局如下:

      下图示例中,该视频 track 只有一个关键帧即第 1 帧:

      stsc

      Sample-To-Chunk Box,Sample-Chunk 映射表。上文提到 MP4 通常把 Sample 封装到 Chunk 中,一个 Chunk 可能会包含一个或者几个 Sample。Sample-To-Chunk Atom 的 table entry 布局如下图所示:

      • First chunk:使用该表项的第一个 chunk 序号。
      • Samples per chunk:使用该表项的 chunk 中包含有几个 sample。
      • Sample description ID:使用该表项的 chunk 参考的 stsd 表项序号。

      下图示例中,可以看到该视频 track 一共有两个 stsc 表项,Chunk 序列 1-108,每个 Chunk 包含一个 sample,Chunk 序列 109 开始,每个 Chunk 包含两个 Sample。

      stsz

      Sample Size Box,指定了每个 Sample 的 size。Sample Size Atom 包含两 Sample 总数和一张包含了每个 Sample Size 的表。Sample Size 表的 entry 布局如下图:

      下图示例中,该视频流一共有 110 个 Sample,第 1 个 Sample 大小为 42072 字节,第 2 个 Sample 大小为 7354 个字节。

      stco

      Chunk Offset Box,指定了每个 Chunk 在文件中的位置,这个表是确定每个 Sample 在文件中位置的关键。该表包含了 Chunk 个数和一个包含每个 Chunk 在文件中偏移位置的表。每个表项的内存布局如下:

      需要注意,这里 stco 只是指定的每个 Chunk 在文件中的偏移位置,并没有给出每个 Sample 在文件中的偏移。想要获得每个 Sample 的偏移位置,需要结合 Sample Size box 和 Sample-To-Chunk 计算后取得。

      下图示例中,该视频流第 1 个 Chunk 在文件中的偏移为 4750,第 1 个 Chunk 在文件中的偏移为 47007。

      04. 如何计算 Sample 偏移位置

      上文提到通过 stco 并不能直接获取某个 Sample 的偏移位置,下面举例说明如何获取某一个 pts 对应的 Sample 在文件中的位置。大体需要以下步骤:

      1. 将 pts 转换到媒体对应的时间坐标系。

      2. 根据 stts 计算某个 pts 对应的 Sample 序号。

      3. 根据 stsc 计算 Sample 序号存放在哪个 Chunk 中。

      4. 根据 stco 获取对应 Chunk 在文件中的偏移位置。

      5. 根据 stsz 获取 Sample 在 Chunk 内的偏移位置并加上第 4 步获取的偏移,计算出 Sample 在文件中的偏移。

      例如,想要获取 3.64 秒视频 Sample 数据在文件中的位置:

      1. 根据 time scale 参数,将 3.64 秒转换为视频时间轴对应的 3640000。

      2. 遍历累加下表所示 stts 所有项目,计算得到 3640000 位于第 110 个 Sample。

      type    stts
      size    224
      flags   0
      version 0
      sample_counts   1,10,1,1,11,1,1,2,1,25,1,1,1,17,1,10,1,1,1,7,1,1,1,1,10,1
      sample_deltas   33362,33363,33362,33364,33363,33362,33364,33363,33362,33363,33362,33364,33362,33363,33362,33363,33362,33364,33362,33363,33362,33364,33363,33362,33363,0
      1. 查询下表所示 stsc 所有项目,计算得到第 110 个 Sample 位于第 109 个 Chunk,并且在该 Chunk 中位于第 2 个 Sample。
      type    stsc
      size    40
      flags   0
      version 0
      first_chunk 1,109
      samples_per_chunk   1,2
      sample_description_index    1,1
      1. 查询下表所示 stco 所有项目,得到第 109 个 Chunk 在文件中偏移位置为 1710064。
      Property name   Property value
      type    stco
      size    452
      flags   0
      version 0
      chunk_offsets   4750,47007,54865,61967,75519,88424,105222,117892,133730,149529,165568,182034,194595,210776,225470,240756,255358,270711,285459,300135,315217,330899,347372,363196,376409,394509,407767,424615,438037,455603,469784,487287,505197,519638,536714,553893,567187,584744,599907,615298,630669,645918,662605,678655,693510,708980,724061,738946,754170,771520,787233,800847,816997,832490,847814,862559,877929,898379,911054,925810,943883,956497,974403,991527,1009478,1025198,1041806,1062609,1078401,1091360,1105142,1118748,1132815,1145281,1156966,1171871,1186742,1202760,1218235,1236688,1249330,1263163,1280880,1297903,1313162,1332885,1345726,1359017,1376283,1391401,1405512,1419550,1433644,1452103,1475241,1492689,1511291,1522606,1535368,1559413,1575331,1588853,1609829,1626623,1642798,1658640,1674160,1693972,1710064
      1. 查询下表所示 stsz 所有项目,得到第 109 个 Sample 的 size 为 14808。计算得到 3.64 秒视频 Sample 数据在文件中:

      offset:1710064 + 14808 = 1724872

      size:17930

      type    stsz
      size    460
      flags   0
      version 0
      sample_sizes    42072,7354,6858,13110,12684,16416,12490,15497,15630,15865,16116,12387,15775,14519,14929,14433,15181,14390,14496,14717,15507,16101,15643,12843,17911,13070,16455,13221,17186,14002,17139,17737,14251,16708,16999,12911,17356,14801,15213,15016,15062,16505,15689,14657,15053,14907,14527,15048,17161,15308,13432,15777,15307,14971,14568,14987,20264,12494,14382,17873,12235,17718,16770,17766,15366,16420,20623,15403,12761,13394,13390,13714,12295,11505,14541,14689,15635,15291,18091,12458,13645,17346,16847,14902,19530,12446,13105,16872,14937,13944,13657,13908,18092,22959,17080,18421,11129,12400,23844,15564,13340,20603,16609,15984,15474,15339,19451,15719,14808,17930
      sample_size 0
      sample_count    110
      • 验证:用编辑器打开 MP4 文件,定位到文件偏移 offset = 1724872 的位置,前 4 字节值为 0x00004606。在 avcc 中一个 Sample 的前 4 个字节代表这个包的大小,转换为十进制是 17926,该值正好等于 size = 17930 减去表示长度的四个字节。

      参考资料

      在线 mp4 解析工具

      QuickTime File Format Specification

      你真的懂 MP4 格式吗?

      「视频云技术」公众号,每周阿里云一线专家分享,音视频领域最佳技术干货,欢迎关注。

      6.2 视频封装格式:MP4_格式详解


      本文由 简悦 SimpRead 转码, 原文地址 zhuanlan.zhihu.com

      1.MP4 格式概述

      1.1 简介

      MP4 或称 MPEG-4 第 14 部分(MPEG-4 Part 14)是一种标准的数字多媒体容器格式。扩展名为. mp4。虽然被官方标准定义的唯一扩展名是. mp4,但第三方通常会使用各种扩展名来指示文件的内容:

      • 同时拥有音频视频的 MPEG-4 文件通常使用标准扩展名. mp4;
      • 仅有音频的 MPEG-4 文件会使用. m4a 扩展名。

      大部分数据可以通过专用数据流嵌入到 MP4 文件中,因此 MP4 文件中包含了一个单独的用于存储流信息的轨道。目前得到广泛支持的编解码器或数据流格式有:

      • 视频格式:H.264/AVC、H.265/HEVC、VP8/9 等
      • 音频格式:AAC、MP3、Opus 等

      十年编程老舅:C/C++ 音视频开发学习路线 + 资料

      1.2 术语

      为了后面能比较规范的了解这种文件格式,这里需要了解下面几个概念和术语,这些概念和术语是理解好 MP4 媒体封装格式和其操作算法的关键。

      (1)Box
      这个概念起源于 QuickTime 中的 atom,也就是说 MP4 文件就是由一个个 Box 组成的,可以将其理解为一个数据块,它由 Header+Data 组成,Data 可以存储媒体元数据和实际的音视频码流数据。Box 里面可以直接存储数据块但是也可以包含其它类型的 Box,我们把这种 Box 又称为 container box

      (2)Sample
      简单理解为采样,对于视频可以理解为一帧数据,音频一帧数据就是一段固定时间的音频数据,可以由多个 Sample 数据组成,简而言之:存储媒体数据的单位是 sample。

      (3)Track
      表示一些 sample 的集合,对于媒体数据而言就是一个视频序列或者音频序列,我们常说的音频轨和视频轨可以对照到这个概念上。当然除了 Video Track 和 Audio Track 还可以有非媒体数据,比如 Hint Track,这种类型的 Track 就不包含媒体数据,可以包含一些将其他数据打包成媒体数据的指示信息或者字幕信息。简单来说:Track 就是电影中可以独立操作的媒体单位。

      (4)Chunk
      一个 track 的连续几个 sample 组成的单元被称为 chunk,每个 chunk 在文件中有一个偏移量,整个偏移量从文件头算起,在这个 chunk 内,sample 是连续存储的。
      这样就可以理解为 MP4 文件里面有多个 Track, 一个 Track 又是由多个 Chunk 组成,每个 Chunk 里面包含着一组连续的 Sample,正是因为定义了上述几个概念,MP4 这种封装格式才容易实现灵活、高效、开放的特性,所以要仔细理解。

      2.MP4 整体结构

      2.1 MP4 结构概览

      MP4 格式是一个 box 的格式,box 容器套 box 子容器,box 子容器再套 box 子容器。

      一个 box 由两部分组成:box header、box body。

      • box header:box 的元数据,比如 box type、box size。
      • box body:box 的数据部分,实际存储的内容跟 box 类型有关,比如 mdat 中 body 部分存储的媒体数据。
        box header 中,只有 type、size 是必选字段。当 size==1 时,存在 largesize 字段。如果 size==0,表示该 box 为文件的最后一个 box。在部分 box 中,还存在 version、flags 字段,这样的 box 叫做 Full Box。当 box body 中嵌套其他 box 时,这样的 box 叫做 container box。
        mp4box 图示如下:

      • 其中:
      • ftyp(file type box):文件头,记录一些兼容性信息
      • moov(movie box):记录媒体信息
      • mdat(media data):媒体负载

      完整的 Box 结构:

      每个 Box 承载的数据内容如下:

      2.2 Box 结构

      mp4 封装格式采用称为 box 的结构来组织数据。结构如下:

      +-+-+-+-+-+-+-+-+-+-+
        |  header  |  body  |
        +-+-+-+-+-+-+-+-+-+-+

      其它所有 box 都在语法上继承自此基本 box 结构。

      2.2.1 box header

      box 分为普通 box 和 fullbox。
      (1)普通 box header 结构如下:

      字段类型描述
      size4 Bytes包含 box header 的整个 box 的大小
      type4 Bytes4 个 ascii 值,如果是 "uuid",则表示此 box 为用户自定义,可忽略
      large size8 Bytessize=1 时才有的字段,用于扩展,例如 mdat box 会需要此字段

      (2)fullbox 在上面的基础上新增了 2 个字段:

      字段类型描述
      version1 Bytes版本号
      flags3 Bytes标识

      2.2.2 box body

      一个 box 可能会包含其它多个 box,此种 box 称为 container box。因此 box body 可能是一种具体 box 类型,也有可能是其它 box。
      虽然 Box 的类型非常多,大概有 70 多种,但是并不是都是必须的,一般的 MP4 文件都是含有必须的 Box 和个别非必须 Box,我用 MP4info 这种工具分析了一首 MP4 的文件,具体的 Box 显示如下:

      通过上述工具分析出来的结果,我们大概可以总结出 MP4 以下几个特点:

      1. MP4 文件就是由一个个 Box 组成,其中 Box 还可以相互嵌套,排列紧凑没有多的冗余数据;
      2. Box 类型并没有很多,主要是由必须的 ftyp、moov、mdat 组成,还有 free,udta 非必须 box 组成即去掉这两种 box 对于播放音视频也没有啥影响。
      3. Moov 一般存储媒体元数据,比较复杂嵌套层次比较深,后面会详细解释各个 box 的字段含义和组成。

      2.3 ftyp(File Type Box)

      ftyp 一般出现在文件的开头,用来指示该 mp4 文件使用的标准规范:

      字段类型描述
      major_brand4 bytes主版本
      minor_version4 bytes次版本
      compatible_brands[]4 bytes指定兼容的版本,注意此字段是一个 list,即可以包含多个 4 bytes 版本号

      一个示例如下:

      2.4 moov(Movie Box)

      1. moov 是一个 container box,一个文件只有一个,其包含的所有 box 用于描述媒体信息(metadata)。
      2. moov 的位置可以紧随着 ftyp 出现,也可以出现在文件末尾。
      3. 由于是一个 container box,所以除了 box header,其 box body 就是其它的 box。

      子 Box:

      • mvhd(moov header):用于简单描述一些所有媒体共享的信息。
      • trak:即 track,轨道,用于描述音频流或视频流信息,可以有多个轨道,如上出现了 2 次,分别表示一路音频和一路视频流。
      • udta(user data):用户自定义,可忽略。

      一个示例如下:
      (1)结构

      (2)数据

      (3)成分

      子 Box:mvhd
      用于简单描述一些所有媒体共享的信息。

      子 Box:trak
      track,轨道,用于描述音频流或视频流信息,可以有多个轨道,如上出现了 2 次,分别表示一路音频和一路视频流。

      2.5 mvhd(Movie Header Box)

      mvhd 作为媒体信息的 header 出现 (注意此 header 不是 box header,而是 moov 媒体信息的 header),用于描述一些所有媒体共享的基本信息。
      mvhd 语法继承自 fullbox,注意下述示例出现的 version 和 flags 字段属于 fullbox header。
      Box Body:

      2.6 trak(track)

      1. trak box 是一个 container box,其子 box 包含了该 track 的媒体信息。
      2. 一个 mp4 文件可以包含多个 track,track 之间是独立的,trak box 用于描述每一路媒体流。
      3. 一般情况下有两个 trak,分别对应音频流和视频流。

      十年编程老舅:C/C++ 音视频开发学习路线 + 资料

      一个示例如下:

      其中:

      • tkhd(track header box):用于简单描述该路媒体流的信息,如时长,宽度等。
      • mdia(media box):用于详细描述该路媒体流的信息
      • edts(edit Box):子 Box 为 elst(Edit List Box),它的作用是使某个 track 的时间戳产生偏移。

      2.7 tkhd(track header box)

      1. tkhd 作为媒体信息的 header 出现 (注意此 header 不是 box header,而是 track 媒体信息的 header),用于描述一些该 track 的基本信息。
      2. tkhd 语法继承自 fullbox,注意下述示例出现的 version 和 flags 字段属于 fullbox header。
        Box Body:

      2.8 edts(edit Box)

      它下边有一个 elst(Edit List Box),它的作用是使某个 track 的时间戳产生偏移。看一下一些字段:

      • segment_duration: 表示该 edit 段的时长,以 Movie Header Box(mvhd)中的 timescale 为单位, 即 segment_duration/timescale = 实际时长(单位 s)
      • media_time: 表示该 edit 段的起始时间,以 track 中 Media Header Box(mdhd)中的 timescale 为单位。如果值为 - 1(FFFFFF),表示是空 edit,一个 track 中最后一个 edit 不能为空。
      • media_rate: edit 段的速率为 0 的话,edit 段相当于一个”dwell”,即画面停止。画面会在 media_time 点上停止 segment_duration 时间。否则这个值始终为 1。
      • 需要注意的问题:

      为使 PTS 从 0 开始,media_time 字段一般设置为第一个 CTTS 的值,计算 PTS 和 DTS 的时候,他们分别都减去 media_time 字段的值就可以将 PTS 调整为从 0 开始的值。
      如果 media_time 是从一个比较大的值,则表示要求 PTS 值大于该值时画面才进行显示,这时应该将第一个大于或等于该值的 PTS 设置为 0,其他的 PTS 和 DTS 也相应做调整。
      2.9 mdia(media box)

      1. 定义了 track 媒体类型以及 sample 数据,描述 sample 信息。
      2. 它是一个 container box,它必须包含 mdhd,hdlr 和 minf。

      一个示例如下:

      其中:

      • mdhd(Media Header Box):用于简单描述该路媒体流的信息。
      • hdlr(Handler Reference Box):主要定义了 track 类型。
      • stbl(Media Information Box):用于描述该路媒体流的解码相关信息和音视频位置等信息。

      2.10 mdhd(Media Header Box)

      1. mdhd 作为媒体信息的 header 出现 (注意此 header 不是 box header,而是 media 媒体信息的 header),用于描述一些该 media 的基本信息。
      2. mdhd 和 tkhd ,内容大致都是一样的。不过 tkhd 通常是对指定的 track 设定相关属性和内容。而 mdhd 是针对于独立的 media 来设置的。
      3. mdhd 语法继承自 fullbox,注意下述示例出现的 version 和 flags 字段属于 fullbox header。
        Box Body:

      注:timescale 同 mvhd 中的 timescale,但是需要注意虽然意义相同,但是值有可能不同,下边 stts,ctts 等时间戳的计算都是以 mdhd 中的 timescale。

      2.11 hdlr(Handler Reference Box)

      1. 主要解释了媒体的播放过程信息。声明当前 track 的类型,以及对应的处理器(handler)。
      2. hdlr 语法继承自 fullbox,注意下述示例出现的 version 和 flags 字段属于 fullbox header。
        Box Body:

      2.12 minf(Media Information box)

      1. 解释 track 媒体数据的 handler-specific 信息,media handler 用这些信息将媒体时间映射到媒体数据并进行处理。minf 同样是个 container box,其内部需要关注的内容是 stbl,这也是 moov 中最复杂的部分。
      2. 一般情况下,“minf”包含一个 header box,一个 “dinf” 和一个 “stbl”,其中,header box 根据 track type(即 media handler type)分为“vmhd”、“smhd”、“hmhd” 和“nmhd”,“dinf”为 data information box,“stbl”为 sample table box。

      2.13 *mhd (Media Info Header Box)

      可分为 “vmhd”、“smhd”、“hmhd” 和“nmhd”,比如视频类型则为 vmhd,音频类型为 smhd。
      (1)vmhd

      • graphics mode:视频合成模式,为 0 时拷贝原始图像,否则与 opcolor 进行合成。
      • opcolor:一组 (red,green,blue),graphics modes 使用。
        (2)smhd
      • balance:立体声平衡,[8.8] 格式值,一般为 0 表示中间,-1.0 表示全部左声道,1.0 表示全部右声道。

      2.14 dinf(Data Information Box)

      1. 描述了如何定位媒体信息,是一个 container box。
      2. “dinf” 一般包含一个 “dref”(data reference box)
      3. “dref”下会包含若干个 “url” 或“urn”,这些 box 组成一个表,用来定位 track 数据。简单的说,track 可以被分成若干段,每一段都可以根据 “url” 或“urn”指向的地址来获取数据,sample 描述中会用这些片段的序号将这些片段组成一个完整的 track。一般情况下,当数据被完全包含在文件中时,“url”或 “urn” 中的定位字符串是空的。

      2.15 stbl(Sample Table Box)

      在介绍 stbl box 之前,需要先介绍一下 mp4 中定义的 sample 与 chunk:

      • sample:ISO/IEC 14496-12 中定义 samples 之间不能共享同一个时间戳,因此,在音视频 track 中,一个 sample 代表一个视频或音频帧。
      • chunk:多个 sample 的集合,实际上音视频 track 中,chunk 与 sample 一一对应。

      stbl box 是一个 container box,是整个 track 中最重要的一个 box,其子 box 描述了该路媒体流的解码相关信息、音视频位置信息、时间戳信息等。

      MP4 文件的媒体数据部分在 mdat box 里,而 stbl 则包含了这些媒体数据的索引以及时间信息。
      一个示例如下:

      其中:

      • stsd(sample description box):存储了编码类型和初始化解码器需要的信息,并与具体编解码器类型有关。
      • stts(time to sample box):存储了该 track 每个 sample 到 dts 的时间映射关系。
      • stss(sync sample box):针对视频 track,关键帧所属 sample 的序号。
      • ctts(composition time to sample box):存储了该 track 中,每个 sample 的 cts 与 dts 的时间差。
      • stsc/stz2(sample to chunk box):存储了该 track 中每个 sample 与 chunk 的映射关系。
      • stsz(sample size box):存储了该 track 中每个 sample 的字节大小。
      • stco/co64(chunk offset box):存储了该 track 中每个 chunk 在文件中的偏移。

      2.16 stsd(sample description box)

      主要存储了编码类型和初始化解码器需要的信息。这里以视频为例,包含子 box:avc1,表示是 H264 的视频。

      2.16.1 h264 stsd

      对于 h264 视频,典型结构如下:

      其上 (只列出 avc1 与 avcC,其余 box 可忽略):

      • avc1,是 avc/h264/mpeg-4 part 10 视频编解码格式的代称,是一个 container box,但是 box body 也携带自身的信息。
        Box Body:

      avcC(AVC Video Stream Definition Box),存储 sps && pps,即在 ISO/IEC 14496-15 中定义的 AVCDecoderConfigurationRecord 结构

      注:在 srs 中,解析 avcc/AVCDecoderConfigurationRecord 结构解析参见 srs/trunk/src/kernel/srs_kerner_codec.cpp::SrsFormat::avc_demux_sps_pps() 函数。

      2.16.2 aac stsd

      对于 aac 音频,典型结构如下:

      可以看到,aac stsd 结构比较复杂,box 众多。实际上,在 ISO/IEC 14496-3 中,定义了 AudioSpecificConfig 类型,aac stsd 结构主要信息就来自 AudioSpecificConfig。
      具体不做分析,可以参看 srs 中:

      • 解析 AudioSpecificConfig 结构的 srs/trunk/src/kernel/srs_kerner_codec.cpp::SrsFormat::audio_aac_sequence_header_demux() 函数
      • 封装 aac stsd 结构的 srs/trunk/src/kernel/srs_kernel_mp4.cpp::SrsMp4Encoder::flush() 函数

      2.17 stts(time to sample box)

      1. 存储了该 track 每个 sample 到 dts 的时间映射关系。
      2. 包含了一个压缩版本的表,通过这个表可以从解码时间映射到 sample 序号。表中的每一项是连续相同的编码时间增量 (Decode Delta) 的个数和编码时间增量。通过把时间增量累加就可以建立一个完整的 time to sample 表。

      这里为了节约条目的个数,采用了压缩存储的方式,即 sample_count 个连续的 sample 如果 sample_delta 时长一样,那么用一个条目就能表示了。
      一个音频 track 的示例如下:

      一个视频 track 的示例如下:

      2.18 ctts(composition time to sample box)

      1. 存储了该 track 中,每个 sample 的 pts 与 dts 时间差 (cts = pts - dts):
      2. 如果一个视频只有 I 帧和 P 帧,则 ctts 这个表就不需要了,因为解码顺序和显示顺序是一致的,但是如果视频中存在 B 帧,则需要 ctts。

      注意:

      • 此 box 在 dts 和 pts 不一样的情况下必须存在,如果一样,不用包含此 box。
      • 如果 box 的 version=0,意味着所有 sample 都满足 pts >= dts,因而差值用一个无符号的数字表示。只要存在一个 pts < dts,那么必须使用 version=1、有符号差值来表示。
      • 关于 ctts 的生成,可以参看 srs/trunk/src/kernel/srs_kernel_mp4.cpp::SrsMp4SampleManager::write_track() 函数 pts、dts、cts 满足公式:pts - dts = cts。

      2.19 stss(sync sample box)

      它包含 media 中的关键帧的 sample 表。关键帧是为了支持随机访问。如果此表不存在,说明每一个 sample 都是一个关键帧。

      一个视频示例如下:

      2.20 stsc/stz2(sample to chunk box)

      存储了该 track 中每个 sample 与 chunk 的映射关系。

      一个音频示例如下:

      • 第一组 chunk 的 first_chunk 序号为 1,每个 chunk 的 sample 个数为 1,因为第二组 chunk 的 first_chunk 序号为 2,可知第一组 chunk 中只有一个 chunk。
      • 第二组 chunk 的 first_chunk 序号为 2,每个 chunk 的 sample 个数为 2,因为第三组 chunk 的 first_chunk 序号为 24,可知第二组 chunk 中有 22 个 chunk,有 44 个 sample。
      • 这个并不是说,这个视频流只有 3 个 sample,也就是只有 3 帧,不可能的,而是第三,第四行省略了,也就是说,第三跟第四,等等,后面的 chunk 里面都只有 1 个 sample,跟第二个 chunk 一样。本视频流有 239 个 chunk。因为本视频流一共 240 帧,第一个 chunk 里面有 2 帧,后面的都是 1 帧,所以计算出来只有 239 个 chunk。

      2.21 stsz(sample size box)

      包含 sample 的数量和每个 sample 的字节大小,这个 box 相对来说体积比较大的。表明视频帧或者音频帧大小,FFmpeg 里面的 AVPacket 的 size 数据大小,就是从这个 box 中来的。

      2.22 stco/co64(chunk offset box)

      1. Chunk Offset 表存储了每个 chunk 在文件中的位置,这样就可以直接在文件中找到媒体数据,而不用解析 box。
      2. 需要注意的是一旦前面的 box 有了任何改变,这张表都要重新建立。

      stco 有两种形式,如果你的视频过大的话,就有可能造成 chunkoffset 超过 32bit 的限制。所以,这里针对大 Video 额外创建了一个 co64 的 Box。它的功效等价于 stco,也是用来表示 sample 在 mdat box 中的位置。只是,里面 chunk_offset 是 64bit 的。

      • 需要注意,这里 stco 只是指定的每个 Chunk 在文件中的偏移位置,并没有给出每个 Sample 在文件中的偏移。想要获得每个 Sample 的偏移位置,需要结合 Sample Size box 和 Sample-To-Chunk 计算后取得。

      2.23 udta(user data box)

      用户自定义数据。

      2.24 free(free space box)

      1. “free” 中的内容是无关紧要的,可以被忽略。该 box 被删除后,不会对播放产生任何影响。
      2. Ftyp 可以是 free 或 skip。

      2.25 mdat(media data box)

      1. mdat 就是具体的编码后的数据。
      2. mdat 也是一个 box,拥有 box header 和 box body。
      3. mdat 可以引用外部的数据,参见 moov –> udta –> meta,这里不讨论,只讨论数据存储在本文件中的形式。
      4. 对于 box body 部分,采用一个一个 samples 的形式进行存储,即一个一个音频帧或视频帧的形式进行存储。
      5. 码流组织方式采用 avcc 格式,即 AUD + slice size + slice 的形式。

      十年编程老舅:C/C++ 音视频开发学习路线 + 资料

      第7章

      音频格式

      1 序

      2 目录


        第8章

        模型3D

        1 序

        2 目录