FFmpeg源码中,计算CRC校验的实现

一、CRC简介

CRC(Cyclic Redundancy Check),即循环冗余校验,是一种根据网络数据包或电脑文件等数据产生简短固定位数校核码的快速算法,主要用来检测或校核数据传输或者保存后可能出现的错误。CRC利用除法及余数的原理,实现错误侦测的功能,具有原理清晰、实现简单等优点。注意,CRC校验只能对数据进行检错,而不能对其纠错。因此,在CAN总线上的每个节点一旦发现数据有错,CAN控制器会根据总线仲裁原则和受损报文优先发送原则对已损坏的报文进行自动重传。具体可以参考:《百度百科------CRC》。

二、CRC校验原理

可以参考:《CRC校验原理及实现》、《什么是CRC循环冗余校验,是如何对数据进行计算的?》、《CRC码计算及校验原理的最通俗诠释》等文章。

三、CRC8/CRC16/CRC32

常见的CRC校验类型有CRC8/CRC16/CRC32。其中:

CRC8:一种较短的CRC校验类型,其生成的校验码由8比特组成,即一个字节。这意味着它能提供的唯一校验结果数量为2^8=256种。

CRC16:相较于CRC8,CRC16提供了更强的错误检测能力,其生成的校验码由16比特构成,对应于2^16种不同的校验值。这种扩展提高了算法对单比特错误以及一定长度突发错误序列的检测概率。

CRC32:位于CRC系列顶端的是CRC32,它产生的校验码有32比特,可形成2^32种不同组合。这种级别的CRC校验尤其适用于大数据传输、高可靠性要求的数据完整性保证场合,如ISO 9660文件系统、ZIP压缩文件格式,以及TCP/IP协议栈中的IP头校验等。

具体可以参考:《CRC8/CRC16/CRC32全面对比详解》。

四、FFmpeg源码中,计算CRC校验的实现

FFmpeg源码中通过av_crc函数计算CRC校验,该函数定义在libavutil/crc.c中:

cpp 复制代码
/**
 * Calculate the CRC of a block.
 * @param ctx initialized AVCRC array (see av_crc_init())
 * @param crc CRC of previous blocks if any or initial value for CRC
 * @param buffer buffer whose CRC to calculate
 * @param length length of the buffer
 * @return CRC updated with the data from the given block
 *
 * @see av_crc_init() "le" parameter
 */
uint32_t av_crc(const AVCRC *ctx, uint32_t crc,
                const uint8_t *buffer, size_t length)
{
    const uint8_t *end = buffer + length;

#if !CONFIG_SMALL
    if (!ctx[256]) {
        while (((intptr_t) buffer & 3) && buffer < end)
            crc = ctx[((uint8_t) crc) ^ *buffer++] ^ (crc >> 8);

        while (buffer < end - 3) {
            crc ^= av_le2ne32(*(const uint32_t *) buffer); buffer += 4;
            crc = ctx[3 * 256 + ( crc        & 0xFF)] ^
                  ctx[2 * 256 + ((crc >> 8 ) & 0xFF)] ^
                  ctx[1 * 256 + ((crc >> 16) & 0xFF)] ^
                  ctx[0 * 256 + ((crc >> 24)       )];
        }
    }
#endif
    while (buffer < end)
        crc = ctx[((uint8_t) crc) ^ *buffer++] ^ (crc >> 8);

    return crc;
}

形参ctx:输入型参数,指向通过av_crc_get_table函数获取到的CRC table,即初始化的AVCRC数组。

形参crc:输入型参数,先前块的CRC(如果有的话)或CRC的初始值。

形参buffer:输入型参数,指向缓冲区的指针,该缓冲区存放要计算其CRC校验的数据。

形参length:输入型参数,形参buffer指向的缓冲区的长度,单位为字节。

返回值:计算出来的CRC校验。

五、av_crc函数的使用例子

(一)通过av_crc函数计算CRC校验

需要校验的数据如下(以十六进制表示),总共28个字节数据:

bash 复制代码
02 B0 1D 00 01 C1 00 00 E1 00 F0 00 1B E1 00 F0 00 0F E1 01 F0 06 0A 04 75 6E 64 00

以参数模型为CRC-32/MPEG-2为例,通过 CRC(循环冗余校验)在线计算 得出上面数据的CRC校验为0x08,0x7D,0xE8,0x77:

把FFmpeg中CRC相关的函数移植出来,编写测试例子main.cpp:

cpp 复制代码
#include <stdio.h>
#include <stdint.h>
//#include <stdint-uintn.h>


#ifdef __GNUC__
#    define AV_GCC_VERSION_AT_LEAST(x,y) (__GNUC__ > (x) || __GNUC__ == (x) && __GNUC_MINOR__ >= (y))
#    define AV_GCC_VERSION_AT_MOST(x,y)  (__GNUC__ < (x) || __GNUC__ == (x) && __GNUC_MINOR__ <= (y))
#else
#    define AV_GCC_VERSION_AT_LEAST(x,y) 0
#    define AV_GCC_VERSION_AT_MOST(x,y)  0
#endif


#if AV_GCC_VERSION_AT_LEAST(2,6) || defined(__clang__)
#    define av_const __attribute__((const))
#else
#    define av_const
#endif


#ifndef av_always_inline
#if AV_GCC_VERSION_AT_LEAST(3,1)
#    define av_always_inline __attribute__((always_inline)) inline
#elif defined(_MSC_VER)
#    define av_always_inline __forceinline
#else
#    define av_always_inline inline
#endif
#endif


#define AV_BSWAP16C(x) (((x) << 8 & 0xff00)  | ((x) >> 8 & 0x00ff))
#define AV_BSWAP32C(x) (AV_BSWAP16C(x) << 16 | AV_BSWAP16C((x) >> 16))
#define AV_BSWAP64C(x) (AV_BSWAP32C(x) << 32 | AV_BSWAP32C((x) >> 32))


#define AVOnce char
#define AV_ONCE_INIT 0

static inline int ff_thread_once(char *control, void (*routine)(void))
{
    if (!*control) {
        routine();
        *control = 1;
    }
    return 0;
}


#define	EINVAL		22	/* Invalid argument */
#define AVERROR(e) (-(e))   ///< Returns a negative error code from a POSIX error code, to return from library functions.

#define CRC_TABLE_SIZE 1024

#define av_le2ne32(x) (x)

typedef uint32_t AVCRC;

typedef enum {
    AV_CRC_8_ATM,
    AV_CRC_16_ANSI,
    AV_CRC_16_CCITT,
    AV_CRC_32_IEEE,
    AV_CRC_32_IEEE_LE,  /*< reversed bitorder version of AV_CRC_32_IEEE */
    AV_CRC_16_ANSI_LE,  /*< reversed bitorder version of AV_CRC_16_ANSI */
    AV_CRC_24_IEEE,
    AV_CRC_8_EBU,
    AV_CRC_MAX,         /*< Not part of public API! Do not use outside libavutil. */
}AVCRCId;

static AVCRC av_crc_table[AV_CRC_MAX][CRC_TABLE_SIZE];

#define DECLARE_CRC_INIT_TABLE_ONCE(id, le, bits, poly)                                       \
static AVOnce id ## _once_control = AV_ONCE_INIT;                                             \
static void id ## _init_table_once(void)                                                      \
{                                                                                             \
    av_crc_init(av_crc_table[id], le, bits, poly, sizeof(av_crc_table[id])); \
}


#ifndef av_bswap32
static av_always_inline av_const uint32_t av_bswap32(uint32_t x)
{
    return AV_BSWAP32C(x);
}
#endif


#define CRC_INIT_TABLE_ONCE(id) ff_thread_once(&id ## _once_control, id ## _init_table_once)

DECLARE_CRC_INIT_TABLE_ONCE(AV_CRC_8_ATM,      0,  8,       0x07)
DECLARE_CRC_INIT_TABLE_ONCE(AV_CRC_8_EBU,      0,  8,       0x1D)
DECLARE_CRC_INIT_TABLE_ONCE(AV_CRC_16_ANSI,    0, 16,     0x8005)
DECLARE_CRC_INIT_TABLE_ONCE(AV_CRC_16_CCITT,   0, 16,     0x1021)
DECLARE_CRC_INIT_TABLE_ONCE(AV_CRC_24_IEEE,    0, 24,   0x864CFB)
DECLARE_CRC_INIT_TABLE_ONCE(AV_CRC_32_IEEE,    0, 32, 0x04C11DB7)
DECLARE_CRC_INIT_TABLE_ONCE(AV_CRC_32_IEEE_LE, 1, 32, 0xEDB88320)
DECLARE_CRC_INIT_TABLE_ONCE(AV_CRC_16_ANSI_LE, 1, 16,     0xA001)


int av_crc_init(AVCRC *ctx, int le, int bits, uint32_t poly, int ctx_size)
{
    unsigned i, j;
    uint32_t c;

    if (bits < 8 || bits > 32 || poly >= (1LL << bits))
        return AVERROR(EINVAL);
    if (ctx_size != sizeof(AVCRC) * 257 && ctx_size != sizeof(AVCRC) * 1024)
        return AVERROR(EINVAL);

    for (i = 0; i < 256; i++) {
        if (le) {
            for (c = i, j = 0; j < 8; j++)
                c = (c >> 1) ^ (poly & (-(c & 1)));
            ctx[i] = c;
        } else {
            for (c = i << 24, j = 0; j < 8; j++)
                c = (c << 1) ^ ((poly << (32 - bits)) & (((int32_t) c) >> 31));
            ctx[i] = av_bswap32(c);
        }
    }
    ctx[256] = 1;
#if !CONFIG_SMALL
    if (ctx_size >= sizeof(AVCRC) * 1024)
        for (i = 0; i < 256; i++)
            for (j = 0; j < 3; j++)
                ctx[256 * (j + 1) + i] =
                    (ctx[256 * j + i] >> 8) ^ ctx[ctx[256 * j + i] & 0xFF];
#endif

    return 0;
}


const AVCRC *av_crc_get_table(AVCRCId crc_id)
{
#if !CONFIG_HARDCODED_TABLES
    switch (crc_id) {
    case AV_CRC_8_ATM:      CRC_INIT_TABLE_ONCE(AV_CRC_8_ATM); break;
    case AV_CRC_8_EBU:      CRC_INIT_TABLE_ONCE(AV_CRC_8_EBU); break;
    case AV_CRC_16_ANSI:    CRC_INIT_TABLE_ONCE(AV_CRC_16_ANSI); break;
    case AV_CRC_16_CCITT:   CRC_INIT_TABLE_ONCE(AV_CRC_16_CCITT); break;
    case AV_CRC_24_IEEE:    CRC_INIT_TABLE_ONCE(AV_CRC_24_IEEE); break;
    case AV_CRC_32_IEEE:    CRC_INIT_TABLE_ONCE(AV_CRC_32_IEEE); break;
    case AV_CRC_32_IEEE_LE: CRC_INIT_TABLE_ONCE(AV_CRC_32_IEEE_LE); break;
    case AV_CRC_16_ANSI_LE: CRC_INIT_TABLE_ONCE(AV_CRC_16_ANSI_LE); break;
    default: ;
    }
#endif
    return av_crc_table[crc_id];
}


uint32_t av_crc(const AVCRC *ctx, uint32_t crc,
                const uint8_t *buffer, size_t length)
{
    const uint8_t *end = buffer + length;

#if !CONFIG_SMALL
    if (!ctx[256]) {
        while (((intptr_t) buffer & 3) && buffer < end)
            crc = ctx[((uint8_t) crc) ^ *buffer++] ^ (crc >> 8);

        while (buffer < end - 3) {
            crc ^= av_le2ne32(*(const uint32_t *) buffer); buffer += 4;
            crc = ctx[3 * 256 + ( crc        & 0xFF)] ^
                  ctx[2 * 256 + ((crc >> 8 ) & 0xFF)] ^
                  ctx[1 * 256 + ((crc >> 16) & 0xFF)] ^
                  ctx[0 * 256 + ((crc >> 24)       )];
        }
    }
#endif
    while (buffer < end)
        crc = ctx[((uint8_t) crc) ^ *buffer++] ^ (crc >> 8);

    return crc;
}


int main()
{
    uint8_t cur_section_buf[28] = {0x02, 0xB0, 0x1D, 0x00, 0x01, 0xC1, 0x00, 0x00, 0xE1, 0x00, 0xF0, 0x00, 0x1B, 
    0xE1, 0x00, 0xF0, 0x00, 0x0F, 0xE1, 0x01, 0xF0, 0x06, 0x0A, 0x04, 0x75, 0x6E, 0x64, 0x00};
    int crc = av_crc(av_crc_get_table(AV_CRC_32_IEEE), -1, cur_section_buf, sizeof(cur_section_buf));
    printf("crc:%d\n", crc);

    return 0;
}

在Linux系统上编译,运行,打印十进制的2011725064,即0x77,0xE8, 0x7D,0x08。可以看到跟上述"CRC在线计算"的计算结果是相符的,只是高低位顺序不一样而已:

(二)通过av_crc函数判断CRC校验是否正确

av_crc函数还有一个用途是用来判断一段包含CRC校验的数据中,CRC校验是否正确。我们改写上面的man.cpp:

cpp 复制代码
    uint8_t cur_section_buf[32] = {0x02, 0xB0, 0x1D, 0x00, 0x01, 0xC1, 0x00, 0x00, 0xE1, 0x00, 0xF0, 0x00, 0x1B, 
    0xE1, 0x00, 0xF0, 0x00, 0x0F, 0xE1, 0x01, 0xF0, 0x06, 0x0A, 0x04, 0x75, 0x6E, 0x64, 0x00, 0x08, 0x7D, 0xE8, 0x77};  
    int crc_valid = !av_crc(av_crc_get_table(AV_CRC_32_IEEE), -1, cur_section_buf, sizeof(cur_section_buf));
    printf("crc_valid:%d\n", crc_valid);

重新编译,运行,打印"1"表示CRC校验正确,打印|"0"表示校验不正确:

相关推荐
m0_7482384210 小时前
【Vue3】前端使用 FFmpeg.wasm 完成用户视频录制,并对视频进行压缩处理
前端·ffmpeg·wasm
阿龍178710 小时前
个人ffmpeg笔记(一)
笔记·ffmpeg
非ban必选10 小时前
JavaCV视频文件音视频分离
ffmpeg·音视频
最 上 川20 小时前
学在西电录播课使用python下载,通过解析m3u8协议、多线程下载ts视频块以及ffmpeg合并
爬虫·python·ffmpeg·音视频
AiFlutter1 天前
基于nginx和ffmpeg搭建HTTP FLV流媒体服务器
nginx·http·ffmpeg
TYUT_xiaoming1 天前
ffmpeg 常用基本命令
ffmpeg
__万波__2 天前
使用ffmpeg将视频与字幕合并为一个文件并将视频拼接
ffmpeg
梦琪小生4 天前
FFmpeg+Mediamtx+VLC 简单实现视频流的推流拉流效果
ffmpeg
桃花岛主705 天前
FFmpeg+Nginx+VLC打造M3U8M3U8点播
运维·nginx·ffmpeg