ff_h2645_extract_rbsp函数的声明放在FFmpeg源码(本文演示用的FFmpeg源码版本为5.0.3,该ffmpeg在CentOS 7.5上通过10.2.1版本的gcc编译)的头文件libavcodec/h2645_parse.h中。
- /**
- * Extract the raw (unescaped) bitstream.
- */
- int ff_h2645_extract_rbsp(const uint8_t *src, int length, H2645RBSP *rbsp,
- H2645NAL *nal, int small_padding);
该函数在H.264/H.265的解码时被调用。作用是将去掉第一个startcode的H.264/H.265码流(以下全部以H.264码流为例) 中的第一个NALU 提取出来,分别去掉和保留防竞争字节,存贮到形参nal 指向的缓冲区中。关于 NALU和防竞争字节的概念可以参考:音视频入门基础:H.264专题(3)——EBSP, RBSP和SODB
形参src:输入型参数。指向缓冲区的指针,该缓冲区存放 去掉第一个startcode(起始码)后的H.264码流。
形参length:输入型参数。指针src指向的缓冲区的长度,单位为字节。
形参rbsp为H2645RBSP类型,为输出型参数。
结构体H2645RBSP 定义如下:
- typedef struct H2645RBSP {
- uint8_t *rbsp_buffer;
- AVBufferRef *rbsp_buffer_ref;
- int rbsp_buffer_alloc_size;
- int rbsp_buffer_size;
- } H2645RBSP;
执行ff_h2645_extract_rbsp函数后,
rbsp->rbsp_buffer 变为:去掉startcode和防竞争字节后的H.264码流,可能包含多个NALU。
rbsp->rbsp_buffer_size 变为:rbsp->rbsp_buffer的大小,单位为字节。
形参nal为H2645NAL类型,为输出型参数。
结构体H2645NAL定义如下:
- typedef struct H2645NAL {
- const uint8_t *data;
- int size;
-
- /**
- * Size, in bits, of just the data, excluding the stop bit and any trailing
- * padding. I.e. what HEVC calls SODB.
- */
- int size_bits;
-
- int raw_size;
- const uint8_t *raw_data;
-
- GetBitContext gb;
-
- /**
- * NAL unit type
- */
- int type;
-
- /**
- * H.264 only, nal_ref_idc
- */
- int ref_idc;
-
- /**
- * HEVC only, nuh_temporal_id_plus_1 - 1
- */
- int temporal_id;
-
- /*
- * HEVC only, identifier of layer to which nal unit belongs
- */
- int nuh_layer_id;
-
- int skipped_bytes;
- int skipped_bytes_pos_size;
- int *skipped_bytes_pos;
- } H2645NAL;
执行ff_h2645_extract_rbsp函数后,
nal->data变为:指向缓冲区的指针。该缓冲区存放 “指针src指向的缓冲区中的第一个NALU”,该NALU去掉了startcode和防竞争字节,但保留了NALU Header。(可以理解为NALU Header + RBSP)
nal->size变为:nal->data指向的缓冲区的大小,单位为字节。
nal->raw_data变为:指向缓冲区的指针。该缓冲区存放 “指针src指向的缓冲区中的第一个NALU”,该NALU去掉了startcode,但保留了防竞争字节和NALU Header。(可以理解为NALU Header + EBSP)
nal->raw_size变为:nal->raw_data指向的缓冲区的大小,单位为字节。
形参small_padding:输入型参数。值一般等于1,可以忽略。
返回值:整形。值等同于nal->raw_size,为nal->raw_data指向的缓冲区的大小,单位为字节
ff_h2645_extract_rbsp函数定义在libavcodec/h2645_parse.c中:
- int ff_h2645_extract_rbsp(const uint8_t *src, int length,
- H2645RBSP *rbsp, H2645NAL *nal, int small_padding)
- {
- int i, si, di;
- uint8_t *dst;
-
- nal->skipped_bytes = 0;
- #define STARTCODE_TEST \
- if (i + 2 < length && src[i + 1] == 0 && src[i + 2] <= 3) { \
- if (src[i + 2] != 3 && src[i + 2] != 0) { \
- /* startcode, so we must be past the end */ \
- length = i; \
- } \
- break; \
- }
- #if HAVE_FAST_UNALIGNED
- #define FIND_FIRST_ZERO \
- if (i > 0 && !src[i]) \
- i--; \
- while (src[i]) \
- i++
- #if HAVE_FAST_64BIT
- for (i = 0; i + 1 < length; i += 9) {
- if (!((~AV_RN64(src + i) &
- (AV_RN64(src + i) - 0x0100010001000101ULL)) &
- 0x8000800080008080ULL))
- continue;
- FIND_FIRST_ZERO;
- STARTCODE_TEST;
- i -= 7;
- }
- #else
- for (i = 0; i + 1 < length; i += 5) {
- if (!((~AV_RN32(src + i) &
- (AV_RN32(src + i) - 0x01000101U)) &
- 0x80008080U))
- continue;
- FIND_FIRST_ZERO;
- STARTCODE_TEST;
- i -= 3;
- }
- #endif /* HAVE_FAST_64BIT */
- #else
- for (i = 0; i + 1 < length; i += 2) {
- if (src[i])
- continue;
- if (i > 0 && src[i - 1] == 0)
- i--;
- STARTCODE_TEST;
- }
- #endif /* HAVE_FAST_UNALIGNED */
-
- if (i >= length - 1 && small_padding) { // no escaped 0
- nal->data =
- nal->raw_data = src;
- nal->size =
- nal->raw_size = length;
- return length;
- } else if (i > length)
- i = length;
-
- dst = &rbsp->rbsp_buffer[rbsp->rbsp_buffer_size];
-
- memcpy(dst, src, i);
- si = di = i;
- while (si + 2 < length) {
- // remove escapes (very rare 1:2^22)
- if (src[si + 2] > 3) {
- dst[di++] = src[si++];
- dst[di++] = src[si++];
- } else if (src[si] == 0 && src[si + 1] == 0 && src[si + 2] != 0) {
- if (src[si + 2] == 3) { // escape
- dst[di++] = 0;
- dst[di++] = 0;
- si += 3;
-
- if (nal->skipped_bytes_pos) {
- nal->skipped_bytes++;
- if (nal->skipped_bytes_pos_size < nal->skipped_bytes) {
- nal->skipped_bytes_pos_size *= 2;
- av_assert0(nal->skipped_bytes_pos_size >= nal->skipped_bytes);
- av_reallocp_array(&nal->skipped_bytes_pos,
- nal->skipped_bytes_pos_size,
- sizeof(*nal->skipped_bytes_pos));
- if (!nal->skipped_bytes_pos) {
- nal->skipped_bytes_pos_size = 0;
- return AVERROR(ENOMEM);
- }
- }
- if (nal->skipped_bytes_pos)
- nal->skipped_bytes_pos[nal->skipped_bytes-1] = di - 1;
- }
- continue;
- } else // next start code
- goto nsc;
- }
-
- dst[di++] = src[si++];
- }
- while (si < length)
- dst[di++] = src[si++];
-
- nsc:
- memset(dst + di, 0, AV_INPUT_BUFFER_PADDING_SIZE);
-
- nal->data = dst;
- nal->size = di;
- nal->raw_data = src;
- nal->raw_size = si;
- rbsp->rbsp_buffer_size += si;
-
- return si;
- }
ff_h2645_extract_rbsp函数中存在如下代码:
- int ff_h2645_extract_rbsp(const uint8_t *src, int length,
- H2645RBSP *rbsp, H2645NAL *nal, int small_padding)
- {
- //...
-
- #define STARTCODE_TEST \
- if (i + 2 < length && src[i + 1] == 0 && src[i + 2] <= 3) { \
- if (src[i + 2] != 3 && src[i + 2] != 0) { \
- /* startcode, so we must be past the end */ \
- length = i; \
- } \
- break; \
- }
-
- //...
-
- for (i = 0; i + 1 < length; i += 2) {
- if (src[i])
- continue;
- if (i > 0 && src[i - 1] == 0)
- i--;
- STARTCODE_TEST;
- }
-
- //...
- }
其中STARTCODE_TEST是宏定义。将宏展开,上述代码相当于:
- int ff_h2645_extract_rbsp(const uint8_t *src, int length,
- H2645RBSP *rbsp, H2645NAL *nal, int small_padding)
- {
- //...
-
- for (i = 0; i + 1 < length; i += 2) {
- if (src[i])
- continue;
- if (i > 0 && src[i - 1] == 0)
- i--;
- if (i + 2 < length && src[i + 1] == 0 && src[i + 2] <= 3) {
- if (src[i + 2] != 3 && src[i + 2] != 0) {
- /* startcode, so we must be past the end */
- length = i;
- }
- break;
- }
- }
-
- //...
- }
上述代码中,首先会通过语句:
- for (i = 0; i + 1 < length; i += 2) {
- if (src[i])
- continue;
- if (i > 0 && src[i - 1] == 0)
- i--;
- //...
- }
来判断H.264码流中是否存在ASCII 码为 0 (值为'\0')的的字符,如果存在则表明接下来的数据中可能会出现startcode(起始码)或防竞争字节。然后执行下面代码
- if (i + 2 < length && src[i + 1] == 0 && src[i + 2] <= 3) {
- if (src[i + 2] != 3 && src[i + 2] != 0) {
- /* startcode, so we must be past the end */
- length = i;
- }
- break;
- }
来判断是否是起始码,如果是起始码或防竞争字节就通过break;跳出循环。
继续执行语句。满足下面条件,说明是防竞争字节:
- else if (src[si] == 0 && src[si + 1] == 0 && src[si + 2] != 0) {
- if (src[si + 2] == 3) { // escape
-
- //...
- }
如果是防竞争字节,通过下面语句去掉防竞争字节:
- dst[di++] = 0;
-
- dst[di++] = 0;
-
- si += 3;
- //...
如果不满足条件if (src[si + 2] == 3),说明遇到下一个起始码,表示这个NALU结束了。执行else语句,跳转到“nsc”:
- if (src[si + 2] == 3) { // escape
- //...
- }
- else // next start code
- goto nsc;
- //...
跳转到“nsc”后,给输出型参数赋值,并返回。
- nsc:
- memset(dst + di, 0, AV_INPUT_BUFFER_PADDING_SIZE);
-
- nal->data = dst;
- nal->size = di;
- nal->raw_data = src;
- nal->raw_size = si;
- rbsp->rbsp_buffer_size += si;
-
- return si;
由于ff_h2645_extract_rbsp函数在H.264/H.265的解码时被调用。所以理论上修改该函数(使用算法优化,用空间换时间等策略)可以降低FFmpeg转码时的cpu使用率。具体可以参考:Imagine Computing创新技术大赛赛道2参赛攻略 - 007gzs