<commit_before>[![license](https://img.shields.io/badge/license-BSD--3--Clause-blue.svg)](https://img.shields.io/badge/license-BSD--3--Clause-blue.svg)

# 介绍

这是一份关于视频技术的介绍，内容循序渐进。尽管它面向的是软件开发人员，但我们希望对任何人而言，这份文档都能简单易学。这个点子产生于一个[视频技术新手小型研讨会](https://docs.google.com/presentation/d/17Z31kEkl_NGJ0M66reqr9_uTG6tI5EDDVXpdPKVuIrs/edit#slide=id.p)期间。

本文档旨在尽可能使用**浅显的词语，丰富的图像和实例**介绍数字视频概念，使这些知识能适用于各种场合。你可以随时反馈意见或建议，以改进这篇文档。

“**自己动手**”需要安装 docker，并将这个 repo clone 到你的计算机。

```bash
git clone https://github.com/leandromoreira/digital_video_introduction.git
cd digital_video_introduction
./setup.sh
```

> **注意**：当你看到 `./s/ffmpeg` 或 `./s/mediainfo` 命令时，说明我们运行的是 docker 容器中的版本，容器已经包含了程序所需的依赖。

所有的**“自己动手”应从本 repo 的根目录运行**。**jupyter** 的示例应使用 `./s/start_jupyter.sh` 启动服务器，然后复制 URL 到你的浏览器中使用。

# 目录

- [介绍](#介绍)
- [目录](#目录)
- [基本术语](#基本术语)
  * [编码彩色图像的其它方法](#编码彩色图像的其它方法)
  * [自己动手：玩转图像和颜色](#自己动手玩转图像和颜色)
  * [DVD 的 DAR 是 4:3](#dvd-的-dar-是-43)
  * [自动动手：检查视频属性](#自动动手检查视频属性)
- [消除冗余](#消除冗余)
  * [颜色，亮度和我们的眼睛](#颜色亮度和我们的眼睛)
    + [颜色模型](#颜色模型)
    + [YCbCr 和 RGB 之间的转换](#ycbcr-和-rgb-之间的转换)
    + [色度子采样](#色度子采样)
    + [自己动手：检查 YCbCr 直方图](#自己动手检查-ycbcr-直方图)
  * [帧类型](#帧类型)
    + [I 帧（内部，关键帧）](#i-帧内部关键帧)
    + [P 帧（预测）](#p-帧预测)
      - [自己动手：具有单个 I 帧的视频](#自己动手具有单个-i-帧的视频)
    + [B 帧（双向预测）](#b-帧双向预测)
      - [自己动手：使用 B 帧比较视频](#自己动手使用-b-帧比较视频)
    + [小结](#小结)
  * [时间冗余（帧间预测）](#时间冗余帧间预测)
      - [自己动手：查看运动向量](#自己动手查看运动向量)
  * [空间冗余（帧内预测）](#空间冗余帧内预测)
      - [自己动手：查看帧间预测](#自己动手查看帧间预测)
- [视频编解码器是如何工作的？](#视频编解码器是如何工作的)
  * [是什么？为什么？如何工作？](#是什么为什么如何工作)
  * [历史](#历史)
    + [AV1 的诞生](#av1-的诞生)
  * [通用编解码器](#通用编解码器)
  * [第一步 - 图片分区](#第一步---图片分区)
    + [自己动手：查看分区](#自己动手查看分区)
  * [第二步 - 预测](#第二步---预测)
  * [第三步 - 转换](#第三步---转换)
    + [自己动手：丢弃不同的系数](#自己动手丢弃不同的系数)
  * [第四步 - 量化](#第四步---量化)
    + [自己动手：量化](#自己动手量化)
  * [第五步 - 熵编码](#第五步---熵编码)
    + [VLC 编码](#vlc-编码)
    + [算术编码](#算术编码)
    + [自己动手：CABAC vs CAVLC](#自己动手cabac-vs-cavlc)
  * [第六步 - 比特流格式](#第六步---比特流格式)
    + [H.264 比特流](#h264-比特流)
    + [自己动手：检查 H.264 比特流](#自己动手检查-h264-比特流)
  * [回顾](#回顾)
  * [H.265 如何实现比 H.264 更好的压缩率?](#h265-如何实现比-h264-更好的压缩率)
- [在线流媒体](#在线流媒体)
  * [通用架构](#通用架构)
  * [逐行下载和自适应流](#逐行下载和自适应流)
  * [内容保护](#内容保护)
- [如何使用 jupyter](#如何使用-jupyter)
- [会议](#会议)
- [参考](#参考)

# 基本术语

一个**图像**可以想象为一个**二维矩阵**。如果我们考虑到**色彩**，我们可以推断出这个想法，将这个图像看成一个使用**额外尺寸**提供**颜色数据**的**三维矩阵**。

如果我们选择原色（红，绿和蓝）代表这些颜色，我们定义三个平面：第一个是红色，第二个是绿色，最后一个是蓝色。

![an image is a 3d matrix RGB](/i/image_3d_matrix_rgb.png "An image is a 3D matrix")

我们把这个矩阵里的每一个点称为**像素**（图像元素）。一个像素代表一个给定颜色的**强度**（通常是一个数字）。例如，一个**红色像素**的意思是0个绿色，0个蓝色和最大值的红色。**粉色像素**可以表示成是三种颜色的组合。使用数字范围从0到255来表示，粉色像素定义为**红色255，绿色192和蓝色203**.

> ### 编码彩色图像的其它方法
>
> 有很多其它可能的模型能用来表示由颜色组成的一张图像。例如，我们可以使用一个索引调色板，我们可以只需一个单字节来表示每个像素，而不是使用 RGB 模型时需要的 3 个。在这样一个模型里我们可以用一个二维矩阵来代替3维矩阵去表示我们的颜色，这将节省内存，但会产生较少的颜色选项。
>
> ![NES palette](/i/nes-color-palette.png "NES palette")

例如，看看下面这几张图片。第一张脸完全着色。其它的是红，绿和蓝色平面（显示为灰色调）。

![RGB channels intensity](/i/rgb_channels_intensity.png "RGB channels intensity")

我们可以看到对于最终的颜色，**红色**那张**贡献更多**（第二张脸里最亮的部分），**蓝色**的贡献大多**只能在马里奥的眼睛**（最后一张脸）和他衣服的一部分上看到，看看**所有平面对马里奥的胡子贡献较少**（最黑暗的部分）。

并且每个颜色强度需要一定量的比特，这个数量被称为比特深度。让我们说我们花费了每个颜色（平面）8比特（可以使用的值是从0到255），因此我们的一个颜色深度是24（8*3）比特，我们还可以推断我们可以使用2的24次方个不同的颜色。

> 这是非常好的学习[如何将一张图片从真实世界捕捉成比特](http://www.cambridgeincolour.com/tutorials/camera-sensors.htm)。

图片的另一个属性是**分辨率**，一个单位里像素的数量。通常表示成宽*高，例如，下面这张 **4x4** 的图片。

![image resolution](/i/resolution.png "image resolution")

> ### 自己动手：玩转图像和颜色
>
> 你可以使用 [jupyter](#如何使用-jupyter)（python, numpy, matplotlib 等等）[玩转图像](/image_as_3d_array.ipynb)。
>
> 你也可以学习[图像过滤器（边缘检测，磨皮，模糊。。。）的原理](/filters_are_easy.ipynb)。

使用图像或视频时我们可以看到的另一个属性是宽高比，它简单的描述了图像或像素的宽度和高度之间的比例关系。

当人们说这个电影或照片是 16x9 时，通常指的是显示宽高比（DAR），然而我们也可以有不同形状的单个像素，我们叫这为像素宽高比（PAR）。

![display aspect ratio](/i/DAR.png "display aspect ratio")

![pixel aspect ratio](/i/PAR.png "pixel aspect ratio")

> ## DVD 的 DAR 是 4:3
>
> 虽然 DVD 的实际分辨率是 704x480，但它依然保持 4:3 的宽高比，因为它有一个 10:11（704x10／480x11）的 PAR。

终于，我们可以将**视频**定义为基于**时间**的**连续的 n 帧**，这可以被看作另一个维度，n 是帧率或帧每秒（FPS）。

![video](/i/video.png "video")

显示一段视频每秒所需的比特数量就是它的**比特率**。
> 比特率 = 宽 * 高 * 比特深度 * 帧每秒

例如，一段每秒30帧，每像素24比特，分辨率是480x240的视频，如果我们不做任何压缩，它将需要 **82,944,000 比特每秒**或 82.944 Mbps (30x480x240x24)。

当**比特率**几乎恒定时称为恒定比特率（**CBR**），但它也可以变化，然后称为可变比特率（**VBR**）。

> 这个图形显示了一个受限的 VBR，当帧为黑色时不会花费太多的比特。
>
> ![constrained vbr](/i/vbr.png "constrained vbr")

在早期，工程师们想出了一项技术用于将视频显示的感知帧率加倍而**没有消耗额外带宽**。这项技术被称为**隔行视频**；它基本上是在 1 “帧”里发送屏幕的一半，在下一“帧”里发送另一半。

今天的屏幕渲染大多使用**逐行扫描技术**。逐行是显示，存储或传输运动图像的方法，其中每帧的所有行被依次绘制。

![interlaced vs progressive](/i/interlaced_vs_progressive.png "interlaced vs progressive")

现在我们知道了**图像**被数字化的原理，它的**颜色**的排列方式，在展示一个视频时我们要花费**每秒多少比特**，它是恒定的（CBR）还是可变的（VBR），给定的**帧率**使用给定的**分辨率**和很多其它项如隔行视频，PAR和其它。

> ## 自动动手：检查视频属性
> 你可以[使用 ffmpeg 或 mediainfo 检查大多数属性的解释](https://github.com/leandromoreira/introduction_video_technology/blob/master/encoding_pratical_examples.md#inspect-stream)。

# 消除冗余
<commit_msg>Chinese translation: copyedit basic terminology<commit_after>[![license](https://img.shields.io/badge/license-BSD--3--Clause-blue.svg)](https://img.shields.io/badge/license-BSD--3--Clause-blue.svg)

# 介绍

这是一份关于视频技术的介绍，内容循序渐进。尽管它面向的是软件开发人员，但我们希望对任何人而言，这份文档都能简单易学。这个点子产生于一个[视频技术新手小型研讨会](https://docs.google.com/presentation/d/17Z31kEkl_NGJ0M66reqr9_uTG6tI5EDDVXpdPKVuIrs/edit#slide=id.p)期间。

本文档旨在尽可能使用**浅显的词语，丰富的图像和实例**介绍数字视频概念，使这些知识能适用于各种场合。你可以随时反馈意见或建议，以改进这篇文档。

“**自己动手**”需要安装 docker，并将这个 repo clone 到你的计算机。

```bash
git clone https://github.com/leandromoreira/digital_video_introduction.git
cd digital_video_introduction
./setup.sh
```

> **注意**：当你看到 `./s/ffmpeg` 或 `./s/mediainfo` 命令时，说明我们运行的是 docker 容器中的版本，容器已经包含了程序所需的依赖。

所有的 **“自己动手”应从本 repo 的根目录运行**。**jupyter** 的示例应使用 `./s/start_jupyter.sh` 启动服务器，然后复制 URL 到你的浏览器中使用。

# 目录

- [介绍](#介绍)
- [目录](#目录)
- [基本术语](#基本术语)
  * [编码彩色图像的其它方法](#编码彩色图像的其它方法)
  * [自己动手：玩转图像和颜色](#自己动手玩转图像和颜色)
  * [DVD 的 DAR 是 4:3](#dvd-的-dar-是-43)
  * [自动动手：检查视频属性](#自动动手检查视频属性)
- [消除冗余](#消除冗余)
  * [颜色，亮度和我们的眼睛](#颜色亮度和我们的眼睛)
    + [颜色模型](#颜色模型)
    + [YCbCr 和 RGB 之间的转换](#ycbcr-和-rgb-之间的转换)
    + [色度子采样](#色度子采样)
    + [自己动手：检查 YCbCr 直方图](#自己动手检查-ycbcr-直方图)
  * [帧类型](#帧类型)
    + [I 帧（内部，关键帧）](#i-帧内部关键帧)
    + [P 帧（预测）](#p-帧预测)
      - [自己动手：具有单个 I 帧的视频](#自己动手具有单个-i-帧的视频)
    + [B 帧（双向预测）](#b-帧双向预测)
      - [自己动手：使用 B 帧比较视频](#自己动手使用-b-帧比较视频)
    + [小结](#小结)
  * [时间冗余（帧间预测）](#时间冗余帧间预测)
      - [自己动手：查看运动向量](#自己动手查看运动向量)
  * [空间冗余（帧内预测）](#空间冗余帧内预测)
      - [自己动手：查看帧间预测](#自己动手查看帧间预测)
- [视频编解码器是如何工作的？](#视频编解码器是如何工作的)
  * [是什么？为什么？如何工作？](#是什么为什么如何工作)
  * [历史](#历史)
    + [AV1 的诞生](#av1-的诞生)
  * [通用编解码器](#通用编解码器)
  * [第一步 - 图片分区](#第一步---图片分区)
    + [自己动手：查看分区](#自己动手查看分区)
  * [第二步 - 预测](#第二步---预测)
  * [第三步 - 转换](#第三步---转换)
    + [自己动手：丢弃不同的系数](#自己动手丢弃不同的系数)
  * [第四步 - 量化](#第四步---量化)
    + [自己动手：量化](#自己动手量化)
  * [第五步 - 熵编码](#第五步---熵编码)
    + [VLC 编码](#vlc-编码)
    + [算术编码](#算术编码)
    + [自己动手：CABAC vs CAVLC](#自己动手cabac-vs-cavlc)
  * [第六步 - 比特流格式](#第六步---比特流格式)
    + [H.264 比特流](#h264-比特流)
    + [自己动手：检查 H.264 比特流](#自己动手检查-h264-比特流)
  * [回顾](#回顾)
  * [H.265 如何实现比 H.264 更好的压缩率?](#h265-如何实现比-h264-更好的压缩率)
- [在线流媒体](#在线流媒体)
  * [通用架构](#通用架构)
  * [逐行下载和自适应流](#逐行下载和自适应流)
  * [内容保护](#内容保护)
- [如何使用 jupyter](#如何使用-jupyter)
- [会议](#会议)
- [参考](#参考)

# 基本术语

一个**图像**可以视作一个**二维矩阵**。如果将**色彩**考虑进来，我们可以做出推广：将这个图像视作一个**三维矩阵**——多出来的维度用于储存色彩信息。

如果我们选择三原色（红、绿、蓝）代表这些色彩，这就定义了三个平面：第一个是红色平面，第二个是绿色平面，最后一个是蓝色平面。

![an image is a 3d matrix RGB](/i/image_3d_matrix_rgb.png "An image is a 3D matrix")

我们把这个矩阵里的每一个点称为**像素**（图像元素）。像素的色彩由三原色的**强度**（通常用数值表示）表示。例如，一个**红色像素**的指强度为 0 的绿色，强度为 0 的蓝色和强度最大的红色。**粉色像素**可以通过三种颜色的组合表示。如果规定强度的取值范围是 0 到 255，**红色 255、绿色 192、蓝色 203** 则表示粉色。

> ### 编码彩色图像的其它方法
>
> 除 RGB 模型外，许多模型也可以用来表示色彩，进而组成图像。如下图，我们给每种颜色都标上了序号，这样每个像素仅需一个字节就可以表示出来，而不是 RGB 模型通常所需要的 3 个。在这样一个模型里我们可以用一个二维矩阵来代替三维矩阵去表示我们的色彩，这将节省存储空间，但色彩的数量将会受限。
>
> ![NES palette](/i/nes-color-palette.png "NES palette")

例如以下几张图片。第一张包含所有颜色平面。剩下的分别是红、绿、蓝色平面（显示为灰色调）（译注：颜色强度高的地方显示为亮色，强度低为暗色）。

![RGB channels intensity](/i/rgb_channels_intensity.png "RGB channels intensity")

我们可以看到，对于最终的成像，红色平面对强度的贡献更多（三个平面最亮的是红色平面），蓝色平面（最后一张图片）的贡献大多只在马里奥的眼睛和他衣服的一部分上。所有颜色平面对马里奥的胡子（最暗的部分）均贡献较少。

存储颜色的强度，需要占用一定大小的数据空间，这个大小被称为颜色深度。假如每个颜色（平面）的强度使用 8 bit 定义（取值范围为 0 到 255），那么颜色深度就是 24（8*3）bit，我们还可以推导出我们可以使用 2 的 24 次方个不同的颜色。

> 很棒的文章：[现实世界的照片是如何拍摄成 0 和 1 的](http://www.cambridgeincolour.com/tutorials/camera-sensors.htm)。

图片的另一个属性是**分辨率**，即一个平面内像素的数量。通常表示成宽*高，例如下面这张 **4x4** 的图片。

![image resolution](/i/resolution.png "image resolution")

> ### 自己动手：玩转图像和颜色
>
> 你可以使用 [jupyter](#如何使用-jupyter)（python, numpy, matplotlib 等等）[玩转图像](/image_as_3d_array.ipynb)。
>
> 你也可以学习[图像滤镜（边缘检测，磨皮，模糊。。。）的原理](/filters_are_easy.ipynb)。

图像或视频还有一个属性是宽高比，它简单地描述了图像或像素的宽度和高度之间的比例关系。

当人们说这个电影或照片是 16:9 时，通常是指显示宽高比（DAR），然而我们也可以有不同形状的单个像素，我们称为像素宽高比（PAR）。

![display aspect ratio](/i/DAR.png "display aspect ratio")

![pixel aspect ratio](/i/PAR.png "pixel aspect ratio")

> ## DVD 的 DAR 是 4:3
>
> 虽然 DVD 的实际分辨率是 704x480，但它依然保持 4:3 的宽高比，因为它有一个 10:11（704x10／480x11）的 PAR。

现在我们可以将**视频**定义为在**单位时间**内**连续的 n 帧**，这可以视作一个新的维度，n 即为帧率，若单位时间为秒，则等同于 FPS (每秒帧数 Frames Per Second)。

![video](/i/video.png "video")

播放一段视频每秒所需的数据量就是它的**比特率**，俗称码率。
> 比特率 = 宽 * 高 * 色深 * 帧每秒

例如，一段每秒 30 帧，每像素 24 bits，分辨率是 480x240 的视频，如果我们不做任何压缩，它将需要 **82,944,000 比特每秒**或 82.944 Mbps (30x480x240x24)。

当**比特率**几乎恒定时称为恒定比特率（**CBR**）；但它也可以变化，称为可变比特率（**VBR**）。

> 这个图形显示了一个受限的 VBR，当帧为黑色时不会花费太多的数据量。
>
> ![constrained vbr](/i/vbr.png "constrained vbr")

在早期，工程师们想出了一项技术用于将视频的感官帧率加倍而**没有消耗额外带宽**。这项技术被称为**隔行扫描**；总的来说，它在一个时间点发送一个画面——画面只能填充屏幕的一半，而下一个时间点发送的画面用于填充屏幕的另一半。

如今的屏幕渲染大多使用**逐行扫描技术**。在这种显示、存储、传输运动图像的方法中，每帧中的所有行都会被依次绘制。

![interlaced vs progressive](/i/interlaced_vs_progressive.png "interlaced vs progressive")

现在我们知道了数字化**图像**的原理；它的**颜色**的编排方式；给定**帧率**和**分辨率**时，展示一个视频需要花费多少**比特率**；它是恒定的（CBR）还是可变的（VBR）；还有很多其它内容，如隔行视频和 PAR。

> ## 自己动手：检查视频属性
> 你可以[使用 ffmpeg 或 mediainfo 检查大多数属性的解释](https://github.com/leandromoreira/introduction_video_technology/blob/master/encoding_pratical_examples.md#inspect-stream)。

# 消除冗余

{'fraction_non_alphanumeric': 0.19899648770697442, 'fraction_numerical': 0.027696939287506273, 'mean_word_length': 6.063075832742736, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 45, 'https://': 10, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1761274', 'n_tokens_mistral': 7659, 'n_tokens_neox': 8307, 'n_words': 523}