FFmpeg源码:ff_h2645_extract_rbsp函数分析

一、ff_h2645_extract_rbsp函数的声明

ff_h2645_extract_rbsp函数的声明放在FFmpeg源码(本文演示用的FFmpeg源码版本为5.0.3,该ffmpeg在CentOS 7.5上通过10.2.1版本的gcc编译)的头文件libavcodec/h2645_parse.h中。

cpp 复制代码
/**
 * 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 定义如下:

cpp 复制代码
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定义如下:

cpp 复制代码
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函数的定义

ff_h2645_extract_rbsp函数定义在libavcodec/h2645_parse.c中:

cpp 复制代码
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函数的内部实现原理分析

ff_h2645_extract_rbsp函数中存在如下代码:

cpp 复制代码
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是宏定义。将宏展开,上述代码相当于:

cpp 复制代码
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;                                                      
        }
    }

    //...
}

上述代码中,首先会通过语句:

cpp 复制代码
    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(起始码)或防竞争字节。然后执行下面代码

cpp 复制代码
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;跳出循环。

继续执行语句。满足下面条件,说明是防竞争字节:

cpp 复制代码
else if (src[si] == 0 && src[si + 1] == 0 && src[si + 2] != 0) {
    if (src[si + 2] == 3) { // escape

//...
}

如果是防竞争字节,通过下面语句去掉防竞争字节:

cpp 复制代码
dst[di++] = 0;

dst[di++] = 0;

si       += 3;
//...

如果不满足条件if (src[si + 2] == 3),说明遇到下一个起始码,表示这个NALU结束了。执行else语句,跳转到"nsc":

cpp 复制代码
if (src[si + 2] == 3) { // escape
//...
}
else // next start code
    goto nsc;
//...

跳转到"nsc"后,给输出型参数赋值,并返回。

cpp 复制代码
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函数降低FFmpeg转码时的cpu使用率

由于ff_h2645_extract_rbsp函数在H.264/H.265的解码时被调用。所以理论上修改该函数(使用算法优化,用空间换时间等策略)可以降低FFmpeg转码时的cpu使用率。具体可以参考:Imagine Computing创新技术大赛赛道2参赛攻略 - 007gzs

相关推荐
aqi0030 分钟前
FFmpeg开发笔记(九十)采用FFmpeg套壳的音视频转码百宝箱FFBox
ffmpeg·音视频·直播·流媒体
齐齐大魔王2 小时前
FFmpeg
ffmpeg
你好音视频4 小时前
FFmpeg RTSP拉流流程深度解析
ffmpeg
IFTICing15 小时前
【环境配置】ffmpeg下载、安装、配置(Windows环境)
windows·ffmpeg
haiy201115 小时前
FFmpeg 编译
ffmpeg
aqi0018 小时前
FFmpeg开发笔记(八十九)基于FFmpeg的直播视频录制工具StreamCap
ffmpeg·音视频·直播·流媒体
八月的雨季 最後的冰吻1 天前
FFmepg--28- 滤镜处理 YUV 视频帧:实现上下镜像效果
ffmpeg·音视频
ganqiuye1 天前
向ffmpeg官方源码仓库提交patch
大数据·ffmpeg·video-codec
草明1 天前
ffmpeg 把 ts 转换成 mp3
ffmpeg
aqi001 天前
FFmpeg开发笔记(九十二)基于Kotlin的开源Android推流器StreamPack
android·ffmpeg·kotlin·音视频·直播·流媒体