一、引言
FFmpeg源碼中經常使用到bytestream_get_byte這個函數,比如使用FFmpeg對BMP圖片進行解析,其源碼會調用函數bmp_decode_frame,而該函數內部會通過bytestream_get_byte讀取BMP 的header。本文講解函數bytestream_get_byte的作用和內部實現。本文演示用的FFmpeg源碼版本為5.0.3,該ffmpeg在CentOS 7.5上通過10.2.1版本的gcc編譯
二、bytestream_get_byte函數內部實現
?FFmpeg源碼目錄下的libavutil/attributes.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#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
其中:__GNUC__ 、__GNUC_MINOR__?分別代表gcc的主版本號,次版本號。
所以下面這段條件編譯指令
#if AV_GCC_VERSION_AT_LEAST(3,1)# ? ?define av_always_inline __attribute__((always_inline)) inline
的意思是如果gcc主版本號不小于3,次版本號不小于1,就執行
# define av_always_inline __attribute__((always_inline)) inline
我的gcc版本為10.2.1,滿足該條件,所以會定義該宏。__attribute__((always_inline))的意思是強制內聯,具體可以參考:《__attribute__((always_inline))》
FFmpeg源碼目錄下的libavutil/intreadwrite.h中存在宏定義:
#define AV_RB8(x) (((const uint8_t*)(x))[0])
#define AV_WB8(p, d) do { ((uint8_t*)(p))[0] = (d); } while(0)
libavcodec/bytestream.h 中存在如下宏定義
#define DEF(type, name, bytes, read, write) \
static av_always_inline type bytestream_get_ ## name(const uint8_t **b) \
{ \(*b) += bytes; \return read(*b - bytes); \
}
DEF(unsigned int, byte, 1, AV_RB8 , AV_WB8)
語句 static av_always_inline type bytestream_get_ ## name(const uint8_t **b)? 中## 為宏定義的操作連接符。具體可以參考:《define的一些騷操作:##操作連接符、#@字符化操作符、#字符串化操作符、\行繼續操作》
所以宏定義DEF(unsigned int, byte, 1, AV_RB8 , AV_WB8) 等價于:
static __attribute__((always_inline)) inline unsigned int bytestream_get_byte(const uint8_t **b)
{ (*b) += 1; return (((const uint8_t*)(*b - 1))[0]);
}
不強制內聯,變成普通的函數相當于:
static unsigned int bytestream_get_byte(const uint8_t **b)
{ (*b) += 1; return (((const uint8_t*)(*b - 1))[0]);
}
編寫測試例子main.c :
#include <stdint.h>
#include "stdio.h"static unsigned int bytestream_get_byte(const uint8_t **b)
{ (*b) += 1; return (((const uint8_t*)(*b - 1))[0]);
} int main()
{const uint8_t *buf = "ABCDEF";printf("%c\n", bytestream_get_byte(&buf));printf("%c\n", bytestream_get_byte(&buf));printf("%c\n", bytestream_get_byte(&buf));printf("%c\n", bytestream_get_byte(&buf));printf("%c\n", bytestream_get_byte(&buf));return 0;
}
Linux平臺下使用gcc編譯,輸出為:
通過該例子可以很容易看出來,函數bytestream_get_byte作用就是返回(以形參*b為首地址的)緩沖區中的第一個字符,并將地址(*b)加1,這樣再次調用函數bytestream_get_byte時就會返回緩沖區的第二個字符。以此類推。比如上述測試例子中,最開始buf指向"ABCDEF"。第一次執行printf("%c\n", bytestream_get_byte(&buf))時,會輸出'A',然后buf指向"BCDEF";第二次執行printf("%c\n", bytestream_get_byte(&buf))時,會輸出'B',然后buf指向"CDEF",以此類推。
不將FFmpeg的宏定義展開,則上述測試例子可以修改為 main.c:
#include <stdint.h>
#include "stdio.h"#define av_always_inline __attribute__((always_inline)) inline#define AV_RB8(x) (((const uint8_t*)(x))[0])
#define AV_WB8(p, d) do { ((uint8_t*)(p))[0] = (d); } while(0)#define DEF(type, name, bytes, read, write) \
static av_always_inline type bytestream_get_ ## name(const uint8_t **b) \
{ \(*b) += bytes; \return read(*b - bytes); \
} DEF(unsigned int, byte, 1, AV_RB8 , AV_WB8)int main()
{const uint8_t *buf = "ABCDEF";printf("%c\n", bytestream_get_byte(&buf));printf("%c\n", bytestream_get_byte(&buf));printf("%c\n", bytestream_get_byte(&buf));printf("%c\n", bytestream_get_byte(&buf));printf("%c\n", bytestream_get_byte(&buf));return 0;
}
Linux平臺下使用gcc編譯,輸出為: