AV1 視頻碼流解析

目錄

  1. 參考
  2. AV1的碼流結(jié)構(gòu)
  3. 碼流解析示例

1. 參考

2. AV1的碼流結(jié)構(gòu)

AV1碼流由OBU(Open Bitstream Unit)碼流單元組成,OBU的結(jié)構(gòu)如下所示。

--------------------------------------
obu_header | *obu_size | obu_payload |
--------------------------------------
  • obu_size指的是obu_payload的字節(jié)大小。
  • obu_size不是必須的,由obu_header中的obu_has_size_field字段標(biāo)識(shí)是否存在,不存在時(shí)需要由外部告知OBU的大小。
  • 標(biāo)準(zhǔn)[1]中定義的“Low overhead bitstream format”格式要求obu_has_size_field必須為1。
  • 對(duì)于需要更容易跳過幀或時(shí)間單位的格式的應(yīng)用程序,[1]定義了Annex B長度分隔的碼流格式,obu_has_size_field不為1。
  • 派生的規(guī)范,例如支持存儲(chǔ)AV1視頻的容器格式,應(yīng)該指出它們所依賴的是哪種格式。
  • 其他打包OBUs視頻流的方法也是允許的。

OBU結(jié)構(gòu)的語法如下所示,略去了各種類型obu解析payload的部分[1]。

open_bitstream_unit( sz ) {                                
    obu_header()                                           
    if ( obu_has_size_field ) {                           
        obu_size                                           //leb128()
    } else {
        obu_size = sz - 1 - obu_extension_flag
    }
....
}

obu_header() {                                          
    obu_forbidden_bit                                   // f(1)
    obu_type                                            // f(4)
    obu_extension_flag                                  // f(1)
    obu_has_size_field                                  // f(1)
    obu_reserved_1bit                                   // f(1)
    if ( obu_extension_flag == 1 )
        obu_extension_header()
}

obu_extension_header() {
    temporal_id                     //f(3)
    spatial_id                      //f(2)
    extension_header_reserved_3bits //f(3)
}
  • f(n)表示從流中讀取n比特組成的無符號(hào)整數(shù),比特位從高到低。
  • obu_header中的obu_extension_flag字段標(biāo)識(shí)是否還有obu_extension_header。
  • obu_type標(biāo)識(shí)OBU的類型,類型的取值如下表所示。
  • leb128()的讀取可變長的小字端的無符號(hào)整形數(shù),具體過程下面有說明。

obu_type取值

obu_type Name of obu_type
0 Reserved
1 OBU_SEQUENCE_HEADER
2 OBU_TEMPORAL_DELIMITER
3 OBU_FRAME_HEADER
4 OBU_TILE_GROUP
5 OBU_METADATA
6 OBU_FRAME
7 OBU_REDUNDANT_FRAME_HEADER
8 OBU_TILE_LIST
9-14 Reserved
15 OBU_PADDING

leb128()

讀取可變長的小字端的無符號(hào)整形數(shù)。讀取一個(gè)字節(jié)時(shí),如果最高比特為1表示需要讀取更多的字節(jié),為0表示這是最后一個(gè)字節(jié)了。解析過程如下:

leb128() {
    value = 0
    Leb128Bytes = 0
    for (i = 0; i < 8; i++) {
        leb128_byte //f(8)
        value |= ( (leb128_byte & 0x7f) << (i*7) )
        Leb128Bytes += 1
        if ( !(leb128_byte & 0x80) ) {
            break
        }
    }
    return value
}

3. 碼流解析示例

示例為解析IVF視頻文件格式中的AV1碼流,打印OBU的統(tǒng)計(jì)信息。IVF視頻格式在IVF視頻文件格式有介紹。

//http://www.lxweimin.com/u/3a66dddbdb3d

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h> 

char *appname = NULL;
FILE *bitstream = NULL;                //!< the bit stream file

typedef enum {
    OBU_SEQUENCE_HEADER = 1,
    OBU_TEMPORAL_DELIMITER = 2,
    OBU_FRAME_HEADER = 3,
    OBU_TILE_GROUP = 4,
    OBU_METADATA = 5,
    OBU_FRAME = 6,
    OBU_REDUNDANT_FRAME_HEADER = 7,
    OBU_TILE_LIST = 8,
    OBU_PADDING = 15 
} OBU_TYPE;

const char* get_obu_type_name(OBU_TYPE type) {
    switch (type) {
        case OBU_SEQUENCE_HEADER: return "SEQ_H";
        case OBU_TEMPORAL_DELIMITER: return "TEM_D";
        case OBU_FRAME_HEADER: return "FRA_H";
        case OBU_TILE_GROUP: return "TIL_G";
        case OBU_METADATA: return "MET_D";
        case OBU_FRAME: return "FRAME";
        case OBU_REDUNDANT_FRAME_HEADER: return "R_F_H";
        case OBU_TILE_LIST: return "TIL_L";
        case OBU_PADDING: return "PADDI";
        default:
            return "UNKNOWN";
    }
}

typedef struct {
    uint64_t obu_header_size;
    unsigned obu_type;                                            
    uint64_t obu_size;  //leb128(), contains the size in bytes of the OBU not including the bytes within obu_header or the obu_size syntax
    int extension_flag;
    int has_size_field;

    //extension_flag == 1
    int temporal_id;
    int spatial_id;
} OBU_t;

typedef struct IVFMetaData {
    char sign[5];
    char codec_tag[5];
    unsigned int width;
    unsigned int height;
    unsigned int framerate;
    unsigned int timescale; 
    unsigned int frame_count;
} IVFMetaData;

int64_t read(FILE *f, unsigned char *buf, int64_t size) {
    return fread(buf, 1, size, f);
}

int64_t skip(FILE *f, int64_t offset) {
    return fseek(f, offset, SEEK_CUR);
}

unsigned int read8(FILE *f) {
    unsigned char val;
    if (read(f, &val, 1) == 1) {
        return val; 
    }
    return 0;
}

unsigned int readl16(FILE *f) {
    unsigned int val;
    val = read8(f);
    val |= read8(f) << 8; 
    return val;
}

unsigned int readl32(FILE *f) {
    unsigned int val;
    val = readl16(f);
    val |= readl16(f) << 16; 
    return val;
}

uint64_t readl64(FILE *f) {
    uint64_t val;
    val = readl32(f);
    val |= ((uint64_t)readl32(f)) << 32;
    return val;
}

uint64_t leb128(FILE *f, int *read_bytes_num) {
    uint64_t val = 0;
    int i = 0;
    for (; i < 8; i++) {
        unsigned int leb128_byte = read8(f);
        val |= ( (leb128_byte & 0x7f) << (i*7) );
        if ( !(leb128_byte & 0x80) ) {
            break;
        }
    }
    *read_bytes_num = i + 1;
    return val; 
}

static int ivf_read_header(IVFMetaData *ivf) {
    read(bitstream, ivf->sign, 4);
    if (strcmp(ivf->sign, "DKIF") != 0) {
        fprintf(stderr, "not a IVF file, sign=%s.\n", ivf->sign);
        return -1;
    }
    skip(bitstream, 2); //version
    skip(bitstream, 2); //header size
    read(bitstream, ivf->codec_tag, 4);
    ivf->width = readl16(bitstream);
    ivf->height = readl16(bitstream);  
    ivf->framerate = readl32(bitstream);
    ivf->timescale = readl32(bitstream);
    ivf->frame_count = readl32(bitstream);
    skip(bitstream, 4); //unused
    return 0;   
}

int get_obu(OBU_t *obu, int sz){
    unsigned char obu_header;
    if (read(bitstream, &obu_header, 1) != 1) {
        fprintf(stderr, "read obu_header failed.\n");
        return -1;
    }
    obu->obu_type = (obu_header >> 3) & 0x0F;
    obu->extension_flag = (obu_header >> 2) & 0x01;
    obu->has_size_field = (obu_header >> 1) & 0x01;
        
    if (obu->extension_flag == 1) {
        unsigned char obu_extension_header;
        if (read(bitstream, &obu_extension_header, 1) != 1) {
            fprintf(stderr, "read obu_extension_header failed.\n");
            return -1;
        } else {
            obu->temporal_id = (obu_extension_header >> 5) & 0x07;  
            obu->spatial_id = (obu_extension_header >> 3) & 0x03;   
        }
    }
    int size_field_bytes_num = 0;
    if (obu->has_size_field == 1) {
        obu->obu_size = leb128(bitstream, &size_field_bytes_num);   
    } else {
        obu->obu_size = sz - 1 - obu->extension_flag;
    }
    obu->obu_header_size = 1 + obu->extension_flag + size_field_bytes_num;
    if (obu->obu_size > 0) { 
        if (0 != skip(bitstream, obu->obu_size)){
            fprintf(stderr, "get_obu: cannot seek in the bitstream file");
            return -1;
        }
    }   
    return 0;
}

/**
 * Analysis AV1 Bitstream in IVF file
 * @param url location of input IVF file contains AV1 bitstream.
 */
int simplest_av1_parser(char *url){
    int ret = 0;
    IVFMetaData *ivf_meta_data;
    OBU_t *obu;
 
    bitstream = fopen(url, "rb+");
    if (!bitstream) {
        printf("Open file error\n");
        return -1;
    }
 
    obu = (OBU_t*) calloc (1, sizeof (OBU_t));
    if (!obu) {
        fprintf(stdout, "Alloc OBU_t Error\n");
        return -1;
    }
    ivf_meta_data = (IVFMetaData *) calloc(1, sizeof(IVFMetaData)); 
    if (!ivf_meta_data) {
        fprintf(stdout, "Alloc IVFMetaData Error\n");
        ret = -1;
        goto end;
    }
    if (ivf_read_header(ivf_meta_data) != 0) {
        fprintf(stderr, "read ivf header failed.\n");
        ret = -1;
        goto end;
    }
    fprintf(stdout, "ivf header: sign=%s, codec_tag=%s, width=%d, height=%d, "
        " framerate=%d, timescale=%d, frame_count=%d\n", 
        ivf_meta_data->sign, 
        ivf_meta_data->codec_tag, 
        ivf_meta_data->width, 
        ivf_meta_data->height, 
        ivf_meta_data->framerate, 
        ivf_meta_data->timescale, 
        ivf_meta_data->frame_count);

    uint64_t data_offset = 32;
    int obu_num = 0;
    int ivf_frame_num = 0;

    printf("----------+-------- OBU Table ---+--------+----------+------------+----------+\n");
    printf("IVF F#num | IVF F#size | OBU_NUM |   POS  |   TYPE   | OBU_H_SIZE | OBU_SIZE |\n");
    printf("----------+------------+---------+--------+----------+------------+----------+\n");

    while(!feof(bitstream)) {
        unsigned int frame_size = readl32(bitstream);
        uint64_t pts = readl64(bitstream);
        data_offset += 12;
        ivf_frame_num++; 
        
        int obu_num_in_ivf_frame = 0;
        int sz = frame_size;
        while (sz > 0) {
            ret = get_obu(obu, sz);
            if (ret < 0 || (obu->obu_size <= 0 && obu->obu_type != OBU_TEMPORAL_DELIMITER)) {
                fprintf(stderr, "get_obu failed. ret=%d, obu->obu_size=%"PRId64"\n", ret, obu->obu_size);
                ret = -1;
                goto end;
            }
            fprintf(stdout,"%10d|%12d|%9d| %7"PRId64"|%10s|%12"PRId64"|%10"PRId64"|\n", 
                    ivf_frame_num, frame_size, obu_num, data_offset, get_obu_type_name(obu->obu_type), obu->obu_header_size,  obu->obu_size);
            uint64_t obu_total_size = obu->obu_header_size + obu->obu_size;
            data_offset += obu_total_size;
            sz -= obu_total_size;
            obu_num++;
            obu_num_in_ivf_frame++;
        }
    }
    
end:
    //Free
    if (obu)
        free (obu);
    if (ivf_meta_data)
        free (ivf_meta_data);   
    fclose(bitstream);
    return ret;
}

void usage() {
    fprintf(stderr, "usage: %s <annexb_type_h264_file>\n", appname);
    exit(1);
}

int main(int argc, char **argv) {
    appname = argv[0];    
    if (argc < 2) {
        usage();
    }
    int ret = 0;
    ret = simplest_av1_parser(argv[1]);
    if (ret != 0) {
        fprintf(stderr, "parse error, ret=%d", ret);
    } else {
        fprintf(stdout, "parse finished.");
    }
    return 0;
}

結(jié)果
程序的輸入為一個(gè)IVF文件的路徑,結(jié)果的一部分如下圖所示。

obu_parser.png

  • 可以看到一個(gè)IVF Frame中包含了多個(gè)OBU。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,501評(píng)論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,673評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,610評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,939評(píng)論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,668評(píng)論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,004評(píng)論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評(píng)論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,173評(píng)論 0 290
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,705評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,426評(píng)論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,656評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評(píng)論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,833評(píng)論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,247評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,580評(píng)論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,371評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,621評(píng)論 2 380

推薦閱讀更多精彩內(nèi)容