问题
下面是在进行 H264 数据分析时碰到的问题
H264 nal indecator 是在 00 00 00 01 后面的一字节,其字段内容为: * forbidden_zero_bit(1) * nal_ref_idc(2) * nal_unit_type(5)
比如对 0x67 / 0b01100111,各字段为: * forbidden_zero_bit = 0 * nal_ref_idc = 0b11 = 3 * nal_unit_type = 0b111 = 7
常规的提取方式如下:
1 2 3 4 5
| uint8_t a = 0x67; uint8_t f = (a & 0b10000000) >> 7; uint8_t nri = (a & 0b01100000) >> 5; uint8_t type = (a & 0b00011111);
|
f = 0, nri = 3, type = 7 结果没有问题。
但是如果采用 bit field 进行数据拆解则获取不到正确的结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| #include <stdio.h> #include <inttypes.h>
struct NalIndicator { uint8_t f : 1; uint8_t nri : 2; uint8_t type : 5; };
int main() { NalIndicator * n;
uint8_t a = 0x67; n = (NalIndicator *)(&a);
uint8_t f = (a & 0b10000000) >> 7; uint8_t nri = (a & 0b01100000) >> 5; uint8_t type = (a & 0b00011111);
printf("sizeof NalIndicator %d\n", sizeof(NalIndicator)); printf("f : %d, %d\n", n->f, f); printf("nri : %d, %d\n", n->nri, nri); printf("type : %d, %d\n", n->type, type);
return 0; }
|
输出 > sizeof NalIndicator 1 f : 1, 0 nri : 3, 3 type : 12, 7 请按任意键继续…
分析
经过分析,发现是字序影响到了 bit field 的顺序而导致的。
按照以上代码的定义,实际上的各字段是按照从右往左排列的,也就是说:
1 2
| 0x67 = 01100111 = 01100,11,1
|
其中 f = 1, nri = 0b11 = 3, type = 0b0110 = 12。
没想到字序除了会决定字节的顺序,还会对 bit field 的顺序造成影响。
解决方式
如果要编写跨平台的代码,需要针对不同字序的平台定义不同的 struct,并通过预处理器进行定义的切换,用户需要定义类似 BIG_ENDIAN / LITTLE_ENDIAN 的宏,比如 SDL 的 SDL_endian.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| /** * \name The two types of endianness */ /* @{ */ #define SDL_LIL_ENDIAN 1234 #define SDL_BIG_ENDIAN 4321 /* @} */
#ifndef SDL_BYTEORDER /* Not defined in SDL_config.h? */ #ifdef __linux__ #include <endian.h> #define SDL_BYTEORDER __BYTE_ORDER #else /* __linux__ */ #if defined(__hppa__) || \ defined(__m68k__) || defined(mc68000) || defined(_M_M68K) || \ (defined(__MIPS__) && defined(__MISPEB__)) || \ defined(__ppc__) || defined(__POWERPC__) || defined(_M_PPC) || \ defined(__sparc__) #define SDL_BYTEORDER SDL_BIG_ENDIAN #else #define SDL_BYTEORDER SDL_LIL_ENDIAN #endif #endif /* __linux__ */ #endif /* !SDL_BYTEORDER */
|
想要在编译阶段自动检测字序是不可能的,无论什么方式都需要在程序执行阶段才能对字序及进行判断
1 2 3
| #include <stdint .h> #define IS_BIG_ENDIAN (*(uint16_t *)"\0\xff" < 0x100)
|
不过我们可以针对用户通过宏定义的字序进行检查,并在运行时抛出异常:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| inline int IsBigEndian() { int i=1; return ! *((char *)&i); }
/* ... */
#ifdef BIG_ENDIAN assert(IsBigEndian()); #elif LITTLE_ENDIAN assert(!IsBigEndian()); #else #error "No endianness macro defined" #endif
|
还是乖乖用位运算吧
参考资料
http://mjfrazer.org/mjfrazer/bitfields/
https://yumichan.net/video-processing/video-compression/introduction-to-h264-nal-unit/