目录
一、AV1编码技术
AV1是由开放媒体联盟(AOM,Alliance for Open Media)在2018年发布的,AV1的前身是VP9。AOM的成员已经涵盖了Amazon、Cisco、Google、Intel、Microsoft、Mozilla、Netflix、AMD、ARM和 NVIDIA,它成立的目的是为互联网和其他市场创建一个开源的视频编解码器(AV1,AOMedia Video codec),旨在取代VP9并成为与HEVC(H.265)竞争的主要视频编码标准,AV1压缩率比流行的H.264格式高50%、比VP9格式高20%。
AV1诞生原因:
1、专利费
先说专利费,可能刚开始所有人都没有想到H.265的专利费使用会如此之高,授权政策如此复杂。举例HEVC Advance,收费范围广泛同时费用高到令人乍舌。电视、智能手机、流媒体播放器、机顶盒、游戏主机、数字媒体存储设备、监控设备等几乎所有的硬件终端以及Netflix、YouTube等内容提供商都需要付高昂的费用,虽然之前微微下调了一下,但是杯水车薪。终端设备要缴纳0.2-1.2美元不等,封顶年费4000万美元。在内容方面,除了对终端用户提供免费内容的供应商外,其他内容提供商也要根据订阅数、节目数和媒体数收费,封顶年费500万美元。以此估算,每年需向三个专利池和Technicolor公司缴纳的H.265/HEVC专利许可封顶费用会超过1亿美元,鲜血淋漓。
2、反垄断
H.265贵,谷歌趁机出了一个免费的VP9,坚持免费开源,同时解码难度相对更低。但风险点就在于如果VP9广泛面世,那么如此重要的国际标准就会被单个独立巨头垄断,未知风险非常大,可以说是被扼住咽喉。当下企业级市场,侧重点基本都以HEVC/H.265为主,很少看到基于VP9的产品面世,这其中最重要的理由就是,行业中绝大多数企业根本不愿意让这么重要的国际标准被一个独立的公司(谷歌)所控制。
AV1特点:
- 高效压缩:AV1旨在比现有的视频编码标准(如H.264/AVC和HEVC/H.265)提供更高的数据压缩率,这意味着在相同的视频质量下,AV1编码的视频文件将占用更少的存储空间和带宽。
- 开放和免费:与某些其他视频编码标准(如HEVC)不同,AV1是完全开放且免版税的,这使得它对于开发人员和内容创作者来说是一个吸引人的选择,因为它消除了版权费用的负担。
AV1官网地址:https://aomedia.org/
由于是新一代编码技术,虽然具有较高的技术优势,但由于其推出时间相对较晚,市场占有率还不高。目前主要应用于一些对画质要求较高、对成本敏感的领域,如在线视频、OTT 服务等,硬件加速也不像H264/H265那样普及。
二、AV1码流结构(OBU)
YUV视频经过AV1编码之后有以下两种输出格式:
**low-overhead bitstream format:**由一系列OBU(Open Bitstream Units)组成。
**length-delimited format:**标准的Annex B规定了temporal_unit组成bitstram的方式,下图描述了如何将一个temporal_unit打包起来,而多个temporal_unit进行组合则形成了bitstream。该格式优点是很方便跳过某些帧或者temporal_unit。
编码器默认输出格式一般都是low-overhead bitstream format。
OBU类似于H26x中的NALU,H26x码流由NALU(包括起始码)组成,而AV1码流由OBU组成,如下图所示(Elecard Stream Analyzer高版本可解析AV1码流,软件收费,免费使用30天,网上的破解版都比较老,不支持AV1):
上图是AV1的裸流文件,就是由不同类型的OBU组成。和NALU一样,OBU也是由header和payload组成,OBU头部定义如下:
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)表示字段占多少了bit;
obu_forbidden_bit: 一定是0,没有实际意义
**obu_type:**表示obu的类型
|----------|----------------------------|---------------------------------|
| obu_type | Name of obu_type | Description |
| 0 | Reserved | |
| 1 | OBU_SEQUENCE_HEADER | 功能类似SPS |
| 2 | OBU_TEMPORAL_DELIMITER | 时间分隔符,每帧前面都要加 |
| 3 | OBU_FRAME_HEADER | 功能类似PPS |
| 4 | OBU_TILE_GROUP | 一帧由N个TILE_GROUP组成,编码主要信息在此type内 |
| 5 | OBU_METADATA | 声明 profie,level,svc,HDR信息等 |
| 6 | OBU_FRAME | 一个obu_frame就是一帧,一帧有n个tile group |
| 7 | OBU_REDUNDANT_FRAME_HEADER | 当前obu采用上一个obu hdr |
| 8 | OBU_TILE_LIST | 用于large scale,见Annex D |
| 9-14 | Reserved | 保留 |
| 15 | OBU_PADDING | 填充OBU,解码器可以忽略整个padding OBU单元 |
obu_extension_flag: 是否包含extension_header
temporal_id,spatial_id: obu_extension_flag为0时,这两个flag默认为0,大于0时表示为增强层图像;temporal_id表示帧率的分层,spatial_id表示图像分辨率的分层
**obu_has_size_field:**该码流里是否包含了obu_size; obu_size表示该obu若是frame或者一组tile信息时,这些frame和tile的所占字节长度;默认值为obu_size = obu_length - 1 - obu_extension_flag;Low overhead bitstream format格式要求obu_has_size_field必须为1。
**obu_size:**leb128()读取可变长的小字端的无符号整形数,读取一个字节时,如果最高比特为1表示需要读取更多的字节,为0表示这是最后一个字节了。解析过程如下:
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
}
1、Sequence Header OBU
视频宽高和帧率可以通过Sequence Header OBU获取,Sequence Header OBU定义如下:
sequence_header_obu( ) { Type
seq_profile f(3)
still_picture f(1)
reduced_still_picture_header f(1)
if ( reduced_still_picture_header ) {
timing_info_present_flag = 0
decoder_model_info_present_flag = 0
initial_display_delay_present_flag = 0
operating_points_cnt_minus_1 = 0
operating_point_idc[ 0 ] = 0
seq_level_idx[ 0 ] f(5)
seq_tier[ 0 ] = 0
decoder_model_present_for_this_op[ 0 ] = 0
initial_display_delay_present_for_this_op[ 0 ] = 0
} else {
timing_info_present_flag f(1)
if ( timing_info_present_flag ) {
timing_info( )
decoder_model_info_present_flag f(1)
if ( decoder_model_info_present_flag ) {
decoder_model_info( )
}
} else {
decoder_model_info_present_flag = 0
}
initial_display_delay_present_flag f(1)
operating_points_cnt_minus_1 f(5)
for ( i = 0; i <= operating_points_cnt_minus_1; i++ ) {
operating_point_idc[ i ] f(12)
seq_level_idx[ i ] f(5)
if ( seq_level_idx[ i ] > 7 ) {
seq_tier[ i ] f(1)
} else {
seq_tier[ i ] = 0
}
if ( decoder_model_info_present_flag ) {
decoder_model_present_for_this_op[ i ] f(1)
if ( decoder_model_present_for_this_op[ i ] ) {
operating_parameters_info( i )
}
} else {
decoder_model_present_for_this_op[ i ] = 0
}
if ( initial_display_delay_present_flag ) {
initial_display_delay_present_for_this_op[ i ] f(1)
if ( initial_display_delay_present_for_this_op[ i ] ) {
initial_display_delay_minus_1[ i ] f(4)
}
}
}
}
operatingPoint = choose_operating_point( )
OperatingPointIdc = operating_point_idc[ operatingPoint ]
frame_width_bits_minus_1 f(4)
frame_height_bits_minus_1 f(4)
n = frame_width_bits_minus_1 + 1
max_frame_width_minus_1 f(n)
n = frame_height_bits_minus_1 + 1
max_frame_height_minus_1 f(n)
if ( reduced_still_picture_header )
frame_id_numbers_present_flag = 0
else
frame_id_numbers_present_flag f(1)
if ( frame_id_numbers_present_flag ) {
delta_frame_id_length_minus_2 f(4)
additional_frame_id_length_minus_1 f(3)
}
use_128x128_superblock f(1)
enable_filter_intra f(1)
enable_intra_edge_filter f(1)
if ( reduced_still_picture_header ) {
enable_interintra_compound = 0
enable_masked_compound = 0
enable_warped_motion = 0
enable_dual_filter = 0
enable_order_hint = 0
enable_jnt_comp = 0
enable_ref_frame_mvs = 0
seq_force_screen_content_tools = SELECT_SCREEN_CONTENT_TOOLS
seq_force_integer_mv = SELECT_INTEGER_MV
OrderHintBits = 0
} else {
enable_interintra_compound f(1)
enable_masked_compound f(1)
enable_warped_motion f(1)
enable_dual_filter f(1)
enable_order_hint f(1)
if ( enable_order_hint ) {
enable_jnt_comp f(1)
enable_ref_frame_mvs f(1)
} else {
enable_jnt_comp = 0
enable_ref_frame_mvs = 0
}
seq_choose_screen_content_tools f(1)
if ( seq_choose_screen_content_tools ) {
seq_force_screen_content_tools = SELECT_SCREEN_CONTENT_TOOLS
} else {
seq_force_screen_content_tools f(1)
}
if ( seq_force_screen_content_tools > 0 ) {
seq_choose_integer_mv f(1)
if ( seq_choose_integer_mv ) {
seq_force_integer_mv = SELECT_INTEGER_MV
} else {
seq_force_integer_mv f(1)
}
} else {
seq_force_integer_mv = SELECT_INTEGER_MV
}
if ( enable_order_hint ) {
order_hint_bits_minus_1 f(3)
OrderHintBits = order_hint_bits_minus_1 + 1
} else {
OrderHintBits = 0
}
}
enable_superres f(1)
enable_cdef
enable_restoration f(1)
color_config( )
film_grain_params_present f(1)
}
宽高计算方式如下:
width = max_frame_width_minus_1 + 1
height = max_frame_height_minus_1 + 1
如果Sequence Header OBU中的timing_info_present_flag为1时可以计算出视频帧率,如果没有就无法通过Sequence Header OBU计算帧率。
timing_info_present_flag为1时,timing_info( )函数包含如下字段:
num_units_in_display_tick: 画面的更新频率(多少个时间单位更新一次)
time_scale: 时间单位(1秒里包含了多少个时间单位)
equal_picture_interval: 表示视频帧之间是否具有相等的间隔时间,若不相等,需要在码流里编码这个时间;
**num_ticks_per_picture_minus_1(equal_picture_interval为1时有效:**每帧显示多久,和num_units_in_display_tick,time_scale共同决定帧率;
2、Frame header OBU
frame_header_obu( ) {
if ( SeenFrameHeader == 1 ) {
frame_header_copy()
} else {
SeenFrameHeader = 1
uncompressed_header( )
if ( show_existing_frame ) {
decode_frame_wrapup( )
SeenFrameHeader = 0
} else {
TileNum = 0
SeenFrameHeader = 1
}
}
}
uncompressed_header( ) {
if ( frame_id_numbers_present_flag ) {
idLen = ( additional_frame_id_length_minus_1 +
delta_frame_id_length_minus_2 + 3 )
}
allFrames = (1 << NUM_REF_FRAMES) - 1
if ( reduced_still_picture_header ) {
show_existing_frame = 0
frame_type = KEY_FRAME
FrameIsIntra = 1
show_frame = 1
showable_frame = 0
} else {
show_existing_frame
if ( show_existing_frame == 1 ) {
frame_to_show_map_idx
if ( decoder_model_info_present_flag && !equal_picture_interval ) {
temporal_point_info( )
}
refresh_frame_flags = 0
if ( frame_id_numbers_present_flag ) {
display_frame_id f(idLen)
}
frame_type = RefFrameType[ frame_to_show_map_idx ]
if ( frame_type == KEY_FRAME ) {
refresh_frame_flags = allFrames
}
if ( film_grain_params_present ) {
load_grain_params( frame_to_show_map_idx )
}
return
}
frame_type f(2)
FrameIsIntra = (frame_type == INTRA_ONLY_FRAME ||
frame_type == KEY_FRAME)
show_frame f(1)
if ( show_frame && decoder_model_info_present_flag && !equal_picture_interval ) {
temporal_point_info( )
}
if ( show_frame ) {
showable_frame = frame_type != KEY_FRAME
} else {
showable_frame f(1)
}
if ( frame_type == SWITCH_FRAME ||
( frame_type == KEY_FRAME && show_frame ) )
error_resilient_mode = 1
else
error_resilient_mode f(1)
}
if ( frame_type == KEY_FRAME && show_frame ) {
for ( i = 0; i < NUM_REF_FRAMES; i++ ) {
RefValid[ i ] = 0
RefOrderHint[ i ] = 0
}
for ( i = 0; i < REFS_PER_FRAME; i++ ) {
OrderHints[ LAST_FRAME + i ] = 0
}
}
disable_cdf_update f(1)
if ( seq_force_screen_content_tools == SELECT_SCREEN_CONTENT_TOOLS ) {
allow_screen_content_tools f(1)
} else {
allow_screen_content_tools = seq_force_screen_content_tools
}
if ( allow_screen_content_tools ) {
if ( seq_force_integer_mv == SELECT_INTEGER_MV ) {
force_integer_mv f(1)
} else {
force_integer_mv = seq_force_integer_mv
}
} else {
force_integer_mv = 0
}
if ( FrameIsIntra ) {
force_integer_mv = 1
}
if ( frame_id_numbers_present_flag ) {
PrevFrameID = current_frame_id
current_frame_id f(idLen)
mark_ref_frames( idLen )
} else {
current_frame_id = 0
}
if ( frame_type == SWITCH_FRAME )
frame_size_override_flag = 1
else if ( reduced_still_picture_header )
frame_size_override_flag = 0
else
frame_size_override_flag
order_hint
OrderHint = order_hint
if ( FrameIsIntra || error_resilient_mode ) {
primary_ref_frame = PRIMARY_REF_NONE
} else {
primary_ref_frame
}
if ( decoder_model_info_present_flag ) {
buffer_removal_time_present_flag f(1)
if ( buffer_removal_time_present_flag ) {
for ( opNum = 0; opNum <= operating_points_cnt_minus_1; opNum++ ) {
if ( decoder_model_present_for_this_op[ opNum ] ) {
opPtIdc = operating_point_idc[ opNum ]
inTemporalLayer = ( opPtIdc >> temporal_id ) & 1
inSpatialLayer = ( opPtIdc >> ( spatial_id + 8 ) ) & 1
if ( opPtIdc == 0 || ( inTemporalLayer && inSpatialLayer ) ) {
n = buffer_removal_time_length_minus_1 + 1
buffer_removal_time[ opNum ] f(n)
}
}
}
}
}
allow_high_precision_mv = 0
use_ref_frame_mvs = 0
allow_intrabc = 0
if ( frame_type == SWITCH_FRAME ||
( frame_type == KEY_FRAME && show_frame ) ) {
refresh_frame_flags = allFrames
} else {
refresh_frame_flags f(8)
}
if ( !FrameIsIntra || refresh_frame_flags != allFrames ) {
if ( error_resilient_mode && enable_order_hint ) {
for ( i = 0; i < NUM_REF_FRAMES; i++) {
ref_order_hint[ i ] f(OrderHintBits)
if ( ref_order_hint[ i ] != RefOrderHint[ i ] ) {
RefValid[ i ] = 0
}
}
}
}
if ( FrameIsIntra ) {
frame_size( )
render_size( )
if ( allow_screen_content_tools && UpscaledWidth == FrameWidth ) {
allow_intrabc f(1)
}
} else {
if ( !enable_order_hint ) {
frame_refs_short_signaling = 0
} else {
frame_refs_short_signaling f(1)
if ( frame_refs_short_signaling ) {
last_frame_idx f(3)
gold_frame_idx f(3)
set_frame_refs()
}
}
for ( i = 0; i < REFS_PER_FRAME; i++ ) {
if ( !frame_refs_short_signaling )
ref_frame_idx[ i ]
if ( frame_id_numbers_present_flag ) {
n = delta_frame_id_length_minus_2 + 2
delta_frame_id_minus_1 f(n)
DeltaFrameId = delta_frame_id_minus_1 + 1
expectedFrameId[ i ] = ((current_frame_id + (1 << idLen) -
DeltaFrameId ) % (1 << idLen))
}
}
if ( frame_size_override_flag && !error_resilient_mode ) {
frame_size_with_refs( )
} else {
frame_size( )
render_size( )
}
if ( force_integer_mv ) {
allow_high_precision_mv = 0
} else {
allow_high_precision_mv f(1)
}
read_interpolation_filter( )
is_motion_mode_switchable f(1)
if ( error_resilient_mode || !enable_ref_frame_mvs ) {
use_ref_frame_mvs = 0
} else {
use_ref_frame_mvs f(1)
}
for ( i = 0; i < REFS_PER_FRAME; i++ ) {
refFrame = LAST_FRAME + i
hint = RefOrderHint[ ref_frame_idx[ i ] ]
OrderHints[ refFrame ] = hint
if ( !enable_order_hint ) {
RefFrameSignBias[ refFrame ] = 0
} else {
RefFrameSignBias[ refFrame ] = get_relative_dist( hint, OrderHint) > 0
}
}
}
if ( reduced_still_picture_header || disable_cdf_update )
disable_frame_end_update_cdf = 1
else
disable_frame_end_update_cdf f(1)
if ( primary_ref_frame == PRIMARY_REF_NONE ) {
init_non_coeff_cdfs( )
setup_past_independence( )
} else {
load_cdfs( ref_frame_idx[ primary_ref_frame ] )
load_previous( )
}
if ( use_ref_frame_mvs == 1 )
motion_field_estimation( )
tile_info( )
quantization_params( )
segmentation_params( )
delta_q_params( )
delta_lf_params( )
if ( primary_ref_frame == PRIMARY_REF_NONE ) {
init_coeff_cdfs( )
} else {
load_previous_segment_ids( )
}
CodedLossless = 1
for ( segmentId = 0; segmentId < MAX_SEGMENTS; segmentId++ ) {
qindex = get_qindex( 1, segmentId )
LosslessArray[ segmentId ] = qindex == 0 && DeltaQYDc == 0 &&
DeltaQUAc == 0 && DeltaQUDc == 0 &&
DeltaQVAc == 0 && DeltaQVDc == 0
if ( !LosslessArray[ segmentId ] )
CodedLossless = 0
if ( using_qmatrix ) {
if ( LosslessArray[ segmentId ] ) {
SegQMLevel[ 0 ][ segmentId ] = 15
SegQMLevel[ 1 ][ segmentId ] = 15
SegQMLevel[ 2 ][ segmentId ] = 15
} else {
SegQMLevel[ 0 ][ segmentId ] = qm_y
SegQMLevel[ 1 ][ segmentId ] = qm_u
SegQMLevel[ 2 ][ segmentId ] = qm_v
}
}
}
AllLossless = CodedLossless && ( FrameWidth == UpscaledWidth )
loop_filter_params( )
cdef_params( )
lr_params( )
read_tx_mode( )
frame_reference_mode( )
skip_mode_params( )
if ( FrameIsIntra ||
error_resilient_mode ||
!enable_warped_motion )
allow_warped_motion = 0
else
allow_warped_motion
reduced_tx_set
global_motion_params( )
film_grain_params( )
}
frame_header_copy:这是一个函数表示此处应插入之前的frame_header_obu。Note:码流中可以包含几份frame_header_obu的副本,其分布在tile_group_obu中,以保证更好的错误恢复能力。副本的内容须与原始frame_header_obu相同。
frame_header_obu可对应OBU_FRAME_HEADER和OBU_REDUNDANT_FRAME_HEADER两种obu_type
1) 当obu_type为OBU_FRAME_HEADER时OBU payload中有完整的frame_header信息(SeenFrameHeader为0);
**2)**当obu_type为OBU_REDUNDANT_FRAME_HEADER时,复制此前得到的frame_header信息(SeenFrameHeader为1)
uncompressed_header()里面的frame_type ,这个当前帧类型,定义如下:
|------------|--------------------|------------------------------------------------------------------------------------------------------------------|
| frame_type | Name of frame_type | Description |
| 0 | KEY_FRAME | 类似h26x中的IDR帧 |
| 1 | INTER_FRAME | 类似h26x中的P/B帧 |
| 2 | INTRA_ONLY_FRAME | 类似h26x中的I帧 |
| 3 | SWITCH_FRAME | 类似h264的SI/SP帧,或h265的IRAP,是一种新的随机接入点,解码器可以从它开始解码,它具备IDR帧的优点,IDR帧的缺点是所包含的数据量巨大,因此,SFRAME在解决就近快速接入解码的同时,用来提高了码流压缩率。 |
3、Frame OBU
frame_obu( sz ) {
startBitPos = get_position( )
frame_header_obu( )
byte_alignment( )
endBitPos = get_position( )
headerBytes = (endBitPos - startBitPos) / 8
sz -= headerBytes
tile_group_obu( sz )
}
可以看到Frame OBU包含Frame header OBU、Tile group OBU。
4、Temporal Delimiter OBU
Temporal Delimiter OBU仅起到分割的作用,payload为空。一帧图像编码后可能输出多个OBU记录编码后的图像,Temporal Delimiter OBU就负责把不同图像的OBU分割开来,类似于h26x的分割NALU。
5、Tile Group OBU
一帧图像压缩信息(多个tile)。
6、Metadata OBU
表示视频分辨率,帧率等配置信息。
7、Tile List OBU
该obu表示后续编解码的tile的位置信息,及其对应的frame的大小等信息。
8、Padding OBU
填充数据OBU,可以被AV1标准解码器忽略。
9、Reserved OBU
若一个OBU的obu type与上述所有OBU都不相同的话,则该OBU为reserved obu。reserved obu是payload为空的OBU,但是与temporal delimiter obu的不同之处在于,reserved obu是既不解析码流也不对任何变量做任何操作。
总结:
一段AV1码流可以由sequence_header_obu 、frame_obu 组成,也可由sequence_header_obu 、frame_obu ,frame_header_obu、 tile_group_obu 、temporal_delimiter_obu穿插组成。
- obu内包含多帧图片的信息及其一些配置信息,一个obu也可能只是些header信息(配置信息),具体取决于obu的类型;
- obu中一张图片(frame_obu)又被划分为多个tile(tile_group_obu);
- 一个tile被划分为多个LCU(LCU大小为6464或128128,可配置);
- 一个LCU可以继续往下划分为最小8*8的block。
- Block 可以继续分为CU + TU
- CU是预测信息,可以根据同一帧已经完成编解码的部分来推测当前位置信息(帧内预测),也可以根据前面已经解码出来的其他图像来推测当前位置的信息(帧间预测);CU存储的就是选取av1提供的哪种推测策略(mode);由于推测出来的图像和原图存在误差,这个误差就经过处理后存储才TU中;根据CU,TU的信息,可以恢复出来一个block图像;
- 例如,2,4,6,8,(9),我们编解码(9)时,CU可以存储为(mode = +2), TU存储为(tu_dat = -1), 根据cu的mode(2)和前一个位置的信息8,推测当前位置为10,再根据tu_dat得到当前实际数据为9;
三、IVF文件格式
IVF是一个非常简单的视频容器。用于封装VP8/VP9/AV1的数据。
文件的格式如下所示:
IVF Start Header| IVF Frame header | Frame payload(OBU) | IVF Frame header | Frame payload(OBU) |...
IVF Start Header定义如下:
字节 | 描述 |
---|---|
0-3 | 固定的'DKIF'字符串 |
4-5 | version,应该为0 |
6-7 | header的字节长度 |
8-11 | 编码器的FourCC ('VP80', 'VP90', 'AV01') |
12-13 | width in pixels |
14-15 | height in pixels |
16-19 | framerate,单位为(1/timescale) |
20-23 | timescale |
24-27 | 帧的个数 |
28-31 | unused |
IVF Frame header定义如下:
字节 | 描述 |
---|---|
0-3 | Frame playload的字节长度 |
4-11 | 64-bit表示的pts时间戳 |
用Elecard Stream Analyzer查看IVF码流如下图所示:
IVF就是在文件开始加一个IVF start header,并在每帧(Temporal Delimiter OBU)前面加一个IVF frame header。IVF文件后缀名是.ivf,AV1裸流后缀名是.av1。
IVF文件解析示例代码,结合Elecard Stream Analyzer查看码流,就会了解AV1码流结构和IVF文件格式:
//https://www.jianshu.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;
}
四、ffmpeg支持AV1
首先介绍一下常见的AV1编解码器:
aom-av1:AV1编解码器,AOM开放媒体联盟开发-官方,缺点是速度太慢,慢的难以置信。
dav1d:AV1解码器,VLC和FFmpeg联合开发。
SVT-AV1:AV1编码器,编码速度快,Netflix与Intel联合开发。
1、ffmpeg依赖安装
sudo apt-get -y install autoconf automake build-essential libass-dev libfreetype6-dev libsdl2-dev libtheora-dev libtool libva-dev libvdpau-dev libvorbis-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev pkg-config texinfo zlib1g-dev
sudo apt-get install yasm
sudo apt-get install nasm
视频编解码库:
sudo apt-get install libx264-dev
sudo apt-get install libx265-dev
音频编解码库:
sudo apt-get install libfdk-aac-dev
sudo apt-get install libmp3lame-dev
sudo apt-get install libopus-dev
2、安装aom-av1、dav1d、SVT-AV1
这三个不推荐使用apt安装,apt安装的版本比较老,而目前AV1编解码器的更新速度比较快。推荐使用源码安装
aom-av1
git clone https://aomedia.googlesource.com/aom
cd build/
cmake .. -DENABLE_NASM=on -DCMAKE_INSTALL_PREFIX=/usr/local
make -j
sudo make install
dav1d
git clone https://salsa.debian.org/multimedia-team/dav1d.git
mkdir build
cd build
sudo apt install meson
meson setup ..
ninja
ninja install
SVT-AV1
git clone --depth=1 https://gitlab.com/AOMediaCodec/SVT-AV1.git
cd SVT-AV1
cd Build
cmake .. -G"Unix Makefiles" -DCMAKE_BUILD_TYPE=Release
make -j
sudo make install
如果aom-av1、dav1d、SVT-AV1安装过程报错,则需要升级gcc和meson 版本。
3、ffmpeg编译安装
下载ffmpeg,ffmpeg版本不要太老,否则对AV1的支持不够好,这里使用了ffmpeg6.x。
wget https://ffmpeg.org//releases/ffmpeg-6.1.tar.gz
tar -zxvf ffmpeg-6.1.tar.gz
cd ffmpeg-6.1
配置:
./configure --prefix=/usr/local --enable-libx264 --enable-libx265 --disable-x86asm --enable-nonfree --enable-libfdk-aac --enable-libaom --enable-libdav1d --enable-libsvtav1 --enable-shared --enable-gpl --enable-libmp3lame --enable-libopus --extra-cflags=-I/usr/local/include --extra-ldflags=-L/usr/local/lib
编译安装:
make -j
make install
环境配置:
1、sudo vi /etc/ld.so.conf 添加两行库路径:
/usr/local/lib
/usr/local/lib/x86_64-linux-gnu
2、sudo ldconfig
3、vi ~/.profile 添加下面内容
FFMPEG=/usr/local
PATH="$PATH:$FFMPEG/bin"
4、source ~/.profile
用ffmpeg -codecs | grep av1查看AV1编解码器,如下图所示:
五、关于常见格式对AV1的封装
TS:和封装H26x类似,TS直接封装AV1的OBU。
MP4/FLV:封装AV1的时候首先需要加一个AV1CodecConfigurationRecord,类似于H26x中的AVCDecoderConfigurationRecord(H264)/HEVCDecoderConfigurationRecord(H265),之后把OBU进行封装写入文件中即可,此处与H26x略有不同,H26x需要去掉NALU的起始码,在头部加上四个字节表示NALU的长度,而AV1不需要做额外处理。
AV1CodecConfigurationRecord定义如下:
// MP4 Box
Box Type: av1C
Container: AV1 Sample Entry ('av01')
Mandatory: Yes
Quantity: Exactly One
class AV1CodecConfigurationBox extends Box('av1C'){
AV1CodecConfigurationRecord av1Config;
}
aligned (8) class AV1CodecConfigurationRecord {
unsigned int (1) marker = 1;
unsigned int (7) version = 1;
unsigned int (3) seq_profile;
unsigned int (5) seq_level_idx_0;
unsigned int (1) seq_tier_0;
unsigned int (1) high_bitdepth;
unsigned int (1) twelve_bit;
unsigned int (1) monochrome;
unsigned int (1) chroma_subsampling_x;
unsigned int (1) chroma_subsampling_y;
unsigned int (2) chroma_sample_position;
unsigned int (3) reserved = 0;
unsigned int (1) initial_presentation_delay_present;
if (initial_presentation_delay_present) {
unsigned int (4) initial_presentation_delay_minus_one;
} else {
unsigned int (4) reserved = 0;
}
unsigned int (8)[] configOBUs;
}
RTP:和H26x类似,RTP直接封装AV1的OBU。
详细可参考官网的
参考:
https://zhuanlan.zhihu.com/p/640104253