• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 时域可分层视频编码
2
3<!--Kit: AVCodec Kit-->
4<!--Subsystem: Multimedia-->
5<!--Owner: @zhanghongran-->
6<!--Designer: @dpy2650--->
7<!--Tester: @cyakee-->
8<!--Adviser: @zengyawen-->
9
10## 基础概念
11
12### 时域可分层视频编码介绍
13
14**可分层视频编码**,又叫可分级视频编码、可伸缩视频编码,是视频编码的扩展标准,目前广泛使用的包含SVC(H.264编码标准采用的可伸缩扩展)和SHVC(H.265编码标准采用的可扩展标准)。
15
16其特点是能一次编码出时域分层、空域分层、质量域分层的码流结构,满足因网络、终端能力和用户需求不同带来的差异化需求。
17
18**时域可分层视频编码**,是指能编码出时域分层码流的视频编码,下图展示了通过参考关系构建的4层时域分层码流结构。
19
20![Temporal scalability 4 layers](figures/temporal-scalability-4layers.png)
21
22从高到低逐层丢弃部分层级的码流(丢弃顺序L3->L2->L1),能实现不同程度的帧率伸缩,以满足传输和解码能力的变化需求。
23
24如下图所示,这是上述4层时域分层码流结构丢弃L3后组成的新的码流结构,能在解码正常的情况下实现帧率减半的效果。其他层的丢弃同理。
25
26![Temporal scalability 4 layers L3 dropped](figures/temporal-scalability-4layers-L3-dropped.png)
27
28### 时域分层码流结构介绍
29基础码流是由一个或多个独立图像组(Group Of Pictures,简称GOP)组合而成的视频码流。GOP是在编码中一组从I帧开始到I帧结束的连续的可独立解码的图像组。
30
31时域分层码流可以在GOP内继续细分为独立的一个或多个时域图像组(Temporal Group Of Pictures,简称TGOP),每一个TGOP由一个基本层和后续的一个或多个增强层组合而成,如上述4层时域分层码流结构中的帧0到帧7是一个TGOP。
32
33- **基本层(Base Layer,简称BL):** 是GOP中的最底层(L0)。在时域分层中,该层用最低帧率进行编码。
34
35- **增强层(Enhance Layer,简称EL):** 是BL之上的层级,由低到高可以分为多层(L1、L2、L3)。在时域分层中,最低层的EL参考BL,进一步编码帧率更高的层级,更高层的EL会参考BL或低层EL,来编码比低层更高帧率的视频。
36
37### 如何实现时域分层码流结构
38
39时域分层码流结构的实现依赖于逐帧指定的参考关系,参考帧根据在解码图像缓存区(Decoded Picture Buffer,简称DPB)驻留的时长分为短期参考帧和长期参考帧。
40
41- **短期参考帧(Short-Term Reference,简称STR):** 是不能长期驻留在DPB中的参考帧,更新方式是先进先出,如果DPB满,旧的短期参考帧会被移出DPB。
42
43- **长期参考帧(Long-Term Reference,简称LTR):** 是能长期驻留在DPB中的参考帧,通过标记替换的方式更新,不主动标记替换就不会更新。
44
45虽然STR个数大于1时,也能实现一定的跨帧参考结构,但受限于存在时效过短,时域分层结构支持的跨度有限。LTR则不存在上述问题,也能覆盖短期参考帧跨帧场景。优选使用LTR实现时域分层码流结构。
46
47## 适用场景
48基于上述描述的时域分层编码特点,推荐以下场景使用:
49
50- 场景1:播放侧无缓存或低缓存的实时编码传输场景,例如视频会议、视频直播、协同办公等。
51
52- 场景2:有视频预览播放或倍速播放需求的视频编码录制场景。
53
54若应用场景不涉及动态调整时域参考结构,且分层结构简单,则推荐使用[全局时域可分层特性](#全局时域可分层特性feature_temporal_scalability),否则使能[长期参考帧特性](#长期参考帧特性feature_long-term_reference)。
55
56## 约束和限制
57
58- 不可以混用全局时域可分层特性和长期参考帧特性。
59
60  由于底层实现归一,全局时域可分层特性和长期参考帧特性不能同时开启。
61
62- 叠加强制IDR配置时,请使用随帧通路配置。
63
64  参考帧仅在GOP内有效,刷新I帧后,DPB随之清空,参考帧也会被清空,因此参考关系的指定受I帧刷新位置影响很大。
65
66  使能时域分层能力后,若需要通过`OH_MD_KEY_REQUEST_I_FRAME`临时请求I帧,应使用生效时机确定的随帧通路配置方式准确告知系统I帧刷新位置以避免参考关系错乱,参考随帧通路配置相关指导,避免使用生效时机不确定的`OH_VideoEncoder_SetParameter`方式。详情请参考[视频编码Surface模式](video-encoding.md#surface模式)"步骤-4:调用OH_VideoEncoder_RegisterParameterCallback()在Configur接口之前注册随帧通路回调。"。
67
68- 支持`OH_AVBuffer`回调通路,不支持`OH_AVMemory`回调通路。
69
70   新特性依赖随帧特性,应避免使用`OH_AVMemory`回调`OH_AVCodecAsyncCallback`,应使用`OH_AVBuffer`回调`OH_AVCodecCallback`。
71
72- 支持时域P分层,不支持时域B分层。
73
74  时域可分层编码按分层帧类型分为基于P帧的时域分层和基于B帧的时域编码,当前支持分层P编码,不支持分层B编码。
75
76- 均匀分层模式当前只支持TGOP为2或4。
77
78## 全局时域可分层特性(Feature_Temporal_Scalability)
79
80### 接口介绍
81
82全局时域可分层特性,适用于编码稳定和简单的时域分层结构,初始配置全局生效,不支持动态修改。开发配置参数如下。
83
84| 配置参数 | 语义                         |
85| -------- | ---------------------------- |
86| OH_MD_KEY_VIDEO_ENCODER_ENABLE_TEMPORAL_SCALABILITY  | 全局时域可分层编码使能参数。 |
87| OH_MD_KEY_VIDEO_ENCODER_TEMPORAL_GOP_SIZE  | 全局时域可分层编码TGOP大小参数。 |
88| OH_MD_KEY_VIDEO_ENCODER_TEMPORAL_GOP_REFERENCE_MODE  | 全局时域可分层编码TGOP参考模式。 |
89
90- **全局时域可分层编码使能参数:** 在配置阶段配置,仅特性支持才会真正使能成功。
91
92- **全局时域可分层编码TGOP大小参数:** 可选配置,影响时域关键帧之间的间隔,用户需要基于自身业务场景下抽帧需求自定义关键帧密度,可在[2, GopSize)范围内配置,若不配置则使用默认值。
93
94- **全局时域可分层编码TGOP参考模式参数:** 可选配置,影响非关键帧参考模式。包括相邻参考`ADJACENT_REFERENCE`、跨帧参考`JUMP_REFERENCE`和均匀分层`UNIFORMLY_SCALED_REFERENCE`。相邻参考相对跨帧参考拥有更好的压缩性能,跨帧参考相对相邻参考拥有更好的丢帧自由度,均匀分层模式丢帧后的码流分布更均匀,如不配置则使用默认值。
95
96    > **说明:**
97    > 均匀分层模式当前只支持TGOP为2或4。
98    >
99
100使用举例1:TGOP=4时的相邻参考模式。
101
102![Temporal gop 4 adjacent reference](figures/temporal-scalability-tgop4-adjacent.png)
103
104使用举例2:TGOP=4时的跨帧参考模式。
105
106![TGOP4 jump reference](figures/temporal-scalability-tgop4-jump.png)
107
108使用举例3:TGOP=4时的均匀分层模式。
109
110![TGOP4 uniformly scaled reference](figures/temporal-scalability-tgop4-uniformly.png)
111
112### 开发指导
113
114基础编码流程请参考[视频编码开发指导](video-encoding.md)。下面将重点说明与基础视频编码流程中的不同之处。
115
1161. 在初始阶段创建编码实例时,校验视频编码器是否支持全局时域可分层特性。
117
118    ```c++
119    // 1.1 获取对应视频编码器能力实例,此处以H.264为例。
120    OH_AVCapability *cap = OH_AVCodec_GetCapability(OH_AVCODEC_MIMETYPE_VIDEO_AVC, true);
121    // 1.2 通过特性能力查询接口校验是否支持全局时域可分层特性。
122    bool isSupported = OH_AVCapability_IsFeatureSupported(cap, VIDEO_ENCODER_TEMPORAL_SCALABILITY);
123    ```
124
125    如果支持,则可以使能全局时域可分层特性。
126
127    ```c++
128    // 创建硬件编码器实例。
129    OH_AVCodec *videoEnc = OH_VideoEncoder_CreateByMime(OH_AVCODEC_MIMETYPE_VIDEO_AVC);
130    ```
131
1322. 在配置阶段,设置全局时域可分层编码特性参数。
133
134    ```c++
135    constexpr int32_t TGOP_SIZE = 3;
136    // 2.1 创建配置用临时AVFormat。
137    auto format = std::shared_ptr<OH_AVFormat>(OH_AVFormat_Create(), OH_AVFormat_Destroy);
138    if (format == nullptr) {
139        // 异常处理。
140    }
141    // 2.2 填充使能参数键值对。
142    OH_AVFormat_SetIntValue(format.get(), OH_MD_KEY_VIDEO_ENCODER_ENABLE_TEMPORAL_SCALABILITY, 1);
143    // 2.3 (可选)填充TGOP大小和TGOP内参考模式键值对。
144    OH_AVFormat_SetIntValue(format.get(), OH_MD_KEY_VIDEO_ENCODER_TEMPORAL_GOP_SIZE, TGOP_SIZE);
145    OH_AVFormat_SetIntValue(format.get(), OH_MD_KEY_VIDEO_ENCODER_TEMPORAL_GOP_REFERENCE_MODE, ADJACENT_REFERENCE);
146    // 2.4 参数配置。
147    int32_t ret = OH_VideoEncoder_Configure(videoEnc, format.get());
148    if (ret != AV_ERR_OK) {
149        // 异常处理。
150    }
151    ```
152
1533. (可选)在输出轮转中,可以获取码流对应时域层级信息。
154
155    开发者可利用已配置的TGOP参数和编码出帧数目获取时域层级信息。
156
157    示例代码如下:
158
159    ```c++
160    constexpr int32_t TGOP_SIZE = 3;
161    uint32_t outPoc = 0;
162    // 通过输出回调中有效帧数,获取TGOP内相对位置,对照配置确认层级。
163    static void OnNewOutputBuffer(OH_AVCodec *codec, uint32_t index, OH_AVBuffer *buffer, void *userData)
164    {
165        struct OH_AVCodecBufferAttr attr;
166        OH_AVErrCode ret = OH_AVBuffer_GetBufferAttr(buffer, &attr);
167        if (ret != AV_ERR_OK) {
168            // 异常处理。
169        }
170        // 刷新I帧后POC归零。
171        if (attr.flags & AVCODEC_BUFFER_FLAGS_SYNC_FRAME) {
172            outPoc = 0;
173        }
174        // 只有XPS的输出需要跳过。
175        if (attr.flags != AVCODEC_BUFFER_FLAGS_CODEC_DATA) {
176            int32_t tGopInner = outPoc % TGOP_SIZE;
177            if (tGopInner == 0) {
178                // 时域关键帧,后续传输、解码流程不可丢弃。
179            } else {
180                // 时域非关键帧,后续传输、解码流程可以丢弃。
181            }
182            outPoc++;
183        }
184    }
185    ```
186
1874. (可选)在输出轮转中,使用步骤3获取的时域层级信息,自适应传输或自适应解码。
188
189    根据获取的时域可分层码流和对应的层级信息,开发者可选择需要的层级进行传输,或携带至对端自适应选帧解码。
190
191## 长期参考帧特性(Feature_Long-Term_Reference)
192
193### 接口介绍
194
195长期参考帧特性提供帧级参考关系配置。适用于灵活和复杂的时域分层结构。
196
197| 配置参数 | 语义                 |
198| -------- | ---------------------------- |
199| OH_MD_KEY_VIDEO_ENCODER_LTR_FRAME_COUNT  |  长期参考帧个数参数。 |
200| OH_MD_KEY_VIDEO_ENCODER_PER_FRAME_MARK_LTR  | 当前帧标记为LTR帧。 |
201| OH_MD_KEY_VIDEO_ENCODER_PER_FRAME_USE_LTR   | 当前帧参考的LTR帧的POC号。  |
202
203- **长期参考帧个数参数:** 在配置阶段进行设置,应不大于实际查询到的最大支持数目。具体查询方法请参见下文开发指导的“步骤-3”。
204- **当前帧标记为LTR帧:** BL层和被跳跃参考的EL层均标记为LTR。
205- **当前帧参考的LTR帧的POC号:** 如当前帧需要跳跃参考前面已被标记为LTR帧的POC号。
206
207使用举例,实现[时域可分层视频编码介绍](#时域可分层视频编码介绍)中的4层时域分层结构的配置如下。
208
2091. 在配置阶段,将`OH_MD_KEY_VIDEO_ENCODER_LTR_FRAME_COUNT` 配置为5。
210
2112. 在运行阶段输入轮转中,按如下表所示随帧配置LTR相关参数,下表中`\`表示不做配置。
212
213    | 配置\POC | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
214    | -------- |---|---|---|---|---|---|---|---|---|---|----|----|----|----|----|----|----|
215    | MARK_LTR | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 0 | 0  | 0  | 1  | 0  | 0  | 0  | 1  |
216    | USE_LTR  | \ | \ | 0 | \ | 0 | \ | 4 | \ | 0 | \ | 8  | \  | 8  | \  | 12 | \  | 8  |
217
218### 开发指导
219
220基础编码流程请参考[视频编码开发指导](video-encoding.md),下面仅针对与基础视频编码过程中存在的区别做具体说明。
221
2221. 在初始阶段创建编码实例时,校验当前视频编码器是否支持LTR特性。
223
224    ```c++
225    constexpr int32_t NEEDED_LTR_COUNT = 5;
226    bool isSupported = false;
227    int32_t supportedLTRCount = 0;
228    // 1.1 获取对应编码器能力实例,此处以H.264为例。
229    OH_AVCapability *cap = OH_AVCodec_GetCapability(OH_AVCODEC_MIMETYPE_VIDEO_AVC, true);
230    // 1.2 通过特性能力查询接口校验是否支持LTR特性。
231    isSupported = OH_AVCapability_IsFeatureSupported(cap, VIDEO_ENCODER_LONG_TERM_REFERENCE);
232    // 1.3 确定支持的LTR数目。
233    if (isSupported) {
234        OH_AVFormat *properties = OH_AVCapability_GetFeatureProperties(cap, VIDEO_ENCODER_LONG_TERM_REFERENCE);
235        if (!OH_AVFormat_GetIntValue(properties, OH_FEATURE_PROPERTY_KEY_VIDEO_ENCODER_MAX_LTR_FRAME_COUNT, &supportedLTRCount)) {
236            // 异常处理。
237        }
238        OH_AVFormat_Destroy(properties);
239        // 1.4 判断LTR是否满足结构需求。
240        isSupported = supportedLTRCount >= NEEDED_LTR_COUNT;
241    }
242    ```
243
244    若支持且LTR数目满足自身码流结构需求,则可以使能LTR特性。
245
2462. 在配置之前注册回调时,注册随帧通路回调。
247
248    Buffer输入模式示例:
249
250    ```c++
251    // 2.1 编码输入回调OH_AVCodecOnNeedInputBuffer实现。
252    static void OnNeedInputBuffer(OH_AVCodec *codec, uint32_t index, OH_AVBuffer *buffer, void *userData)
253    {
254        // 输入帧buffer对应的index,送入InIndexQueue队列。
255        // 输入帧的数据buffer送入InBufferQueue队列。
256        // 数据处理,请参考:
257        // - 写入编码码流。
258        // - 通知编码器码流结束。
259        // - 随帧参数写入。
260        auto format = std::shared_ptr<OH_AVFormat>(OH_AVBuffer_GetParameter(buffer), OH_AVFormat_Destroy);
261        if (format == nullptr) {
262            // 异常处理。
263        }
264        OH_AVFormat_SetIntValue(format.get(), OH_MD_KEY_VIDEO_ENCODER_PER_FRAME_MARK_LTR, 1);
265        OH_AVFormat_SetIntValue(format.get(), OH_MD_KEY_VIDEO_ENCODER_PER_FRAME_USE_LTR, 4);
266        OH_AVBuffer_SetParameter(buffer, format.get());
267        // 通知编码器buffer输入完成。
268        OH_VideoEncoder_PushInputBuffer(codec, index);
269    }
270
271    // 2.2 编码输出回调OH_AVCodecOnNewOutputBuffer实现。
272    static void OnNewOutputBuffer(OH_AVCodec *codec, uint32_t index, OH_AVBuffer *buffer, void *userData)
273    {
274        // 完成帧buffer对应的index,送入outIndexQueue队列。
275        // 完成帧的数据buffer送入outBufferQueue队列。
276        // 数据处理,请参考:
277        // - 释放编码帧。
278        // - 记录POC和LTR生效情况。
279    }
280
281    // 2.3 注册数据回调。
282    OH_AVCodecCallback cb;
283    cb.onNeedInputBuffer = OnNeedInputBuffer;
284    cb.onNewOutputBuffer = OnNewOutputBuffer;
285    OH_VideoEncoder_RegisterCallback(videoEnc, cb, nullptr);
286    ```
287
288    Surface输入模式示例:
289
290    ```c++
291    // 2.1 编码输入参数回调OH_VideoEncoder_OnNeedInputParameter实现。
292    static void OnNeedInputParameter(OH_AVCodec *codec, uint32_t index, OH_AVFormat *parameter, void *userData)
293    {
294        // 输入帧buffer对应的index,送入InIndexQueue队列。
295        // 输入帧的数据avformat送入InFormatQueue队列。
296        // 数据处理,请参考:
297        // - 写入编码码流。
298        // - 通知编码器码流结束。
299        // - 随帧参数写入。
300        OH_AVFormat_SetIntValue(parameter, OH_MD_KEY_VIDEO_ENCODER_PER_FRAME_MARK_LTR, 1);
301        OH_AVFormat_SetIntValue(parameter, OH_MD_KEY_VIDEO_ENCODER_PER_FRAME_USE_LTR, 4);
302        // 通知编码器随帧参数配置输入完成。
303        OH_VideoEncoder_PushInputParameter(codec, index);
304    }
305
306    // 2.2 编码输出回调OH_AVCodecOnNewOutputBuffer实现。
307    static void OnNewOutputBuffer(OH_AVCodec *codec, uint32_t index, OH_AVBuffer *buffer, void *userData)
308    {
309        // 完成帧buffer对应的index,送入outIndexQueue队列。
310        // 完成帧的数据buffer送入outBufferQueue队列。
311        // 数据处理,请参考:
312        // - 释放编码帧。
313        // - 记录POC和LTR生效情况。
314    }
315
316    // 2.3 注册数据回调。
317    OH_AVCodecCallback cb;
318    cb.onNewOutputBuffer = OnNewOutputBuffer;
319    OH_VideoEncoder_RegisterCallback(videoEnc, cb, nullptr);
320    // 2.4 注册随帧参数回调。
321    OH_VideoEncoder_OnNeedInputParameter inParaCb = OnNeedInputParameter;
322    OH_VideoEncoder_RegisterParameterCallback(videoEnc, inParaCb, nullptr);
323    ```
324
3253. 在配置阶段,设置最大LTR数目。
326
327    ```c++
328    constexpr int32_t NEEDED_LTR_COUNT = 5;
329    // 3.1 创建配置用临时AVFormat。
330    auto format = std::shared_ptr<OH_AVFormat>(OH_AVFormat_Create(), OH_AVFormat_Destroy);
331    if (format == nullptr) {
332        // 异常处理。
333    }
334    // 3.2 填充使能LTR个数键值对。
335    OH_AVFormat_SetIntValue(format.get(), OH_MD_KEY_VIDEO_ENCODER_LTR_FRAME_COUNT, NEEDED_LTR_COUNT);
336    // 3.3 参数配置。
337    int32_t ret = OH_VideoEncoder_Configure(videoEnc, format.get());
338    if (ret != AV_ERR_OK) {
339        // 异常处理。
340    }
341    ```
342
3434. (可选)在输出轮转中,可以获取码流的时域层级信息。
344
345    同全局时域可分层特性。
346
347    由于在输入轮转过程中配置了LTR参数,也可以在输入轮转中记录这些参数,并在输出轮转中找到对应的输入参数。
348
3495. (可选)在输出轮转中,使用步骤4获取的时域层级信息,进行自适应传输或自适应解码。
350
351    同全局时域可分层特性。