AV1视频编解码简介、码流结构(OBU)
我的音视频/流媒体开源项目(github)
目录
一、AV1编码技术
二、AV1码流结构(OBU)
三、IVF文件格式
四、ffmpeg支持AV1
五、关于常见格式对AV1的封装
一、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 = 0Leb128Bytes = 0for (i = 0; i < 8; i++) {leb128_byte //f(8)value |= ( (leb128_byte & 0x7f) << (i*7) )Leb128Bytes += 1if ( !(leb128_byte & 0x80) ) {break}}return value
}
1、Sequence Header OBU
视频宽高和帧率可以通过Sequence Header OBU获取,Sequence Header OBU定义如下:
sequence_header_obu( ) { Typeseq_profile f(3)still_picture f(1)reduced_still_picture_header f(1)if ( reduced_still_picture_header ) {timing_info_present_flag = 0decoder_model_info_present_flag = 0initial_display_delay_present_flag = 0operating_points_cnt_minus_1 = 0operating_point_idc[ 0 ] = 0seq_level_idx[ 0 ] f(5)seq_tier[ 0 ] = 0decoder_model_present_for_this_op[ 0 ] = 0initial_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 + 1max_frame_width_minus_1 f(n)n = frame_height_bits_minus_1 + 1max_frame_height_minus_1 f(n)if ( reduced_still_picture_header )frame_id_numbers_present_flag = 0elseframe_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 = 0enable_masked_compound = 0enable_warped_motion = 0enable_dual_filter = 0enable_order_hint = 0enable_jnt_comp = 0enable_ref_frame_mvs = 0seq_force_screen_content_tools = SELECT_SCREEN_CONTENT_TOOLSseq_force_integer_mv = SELECT_INTEGER_MVOrderHintBits = 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 = 0enable_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_cdefenable_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 = 1uncompressed_header( )if ( show_existing_frame ) {decode_frame_wrapup( )SeenFrameHeader = 0} else {TileNum = 0SeenFrameHeader = 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) - 1if ( reduced_still_picture_header ) {show_existing_frame = 0frame_type = KEY_FRAMEFrameIsIntra = 1show_frame = 1showable_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 = 0if ( 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 = 1elseerror_resilient_mode f(1)}if ( frame_type == KEY_FRAME && show_frame ) {for ( i = 0; i < NUM_REF_FRAMES; i++ ) {RefValid[ i ] = 0RefOrderHint[ 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_idcurrent_frame_id f(idLen)mark_ref_frames( idLen )} else {current_frame_id = 0}if ( frame_type == SWITCH_FRAME )frame_size_override_flag = 1else if ( reduced_still_picture_header )frame_size_override_flag = 0elseframe_size_override_flag order_hint OrderHint = order_hintif ( 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 ) & 1inSpatialLayer = ( opPtIdc >> ( spatial_id + 8 ) ) & 1if ( opPtIdc == 0 || ( inTemporalLayer && inSpatialLayer ) ) {n = buffer_removal_time_length_minus_1 + 1buffer_removal_time[ opNum ] f(n)}}}}}allow_high_precision_mv = 0use_ref_frame_mvs = 0allow_intrabc = 0if ( 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 + 2delta_frame_id_minus_1 f(n)DeltaFrameId = delta_frame_id_minus_1 + 1expectedFrameId[ 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 + ihint = RefOrderHint[ ref_frame_idx[ i ] ]OrderHints[ refFrame ] = hintif ( !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 = 1elsedisable_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 = 1for ( segmentId = 0; segmentId < MAX_SEGMENTS; segmentId++ ) {qindex = get_qindex( 1, segmentId )LosslessArray[ segmentId ] = qindex == 0 && DeltaQYDc == 0 &&DeltaQUAc == 0 && DeltaQUDc == 0 &&DeltaQVAc == 0 && DeltaQVDc == 0if ( !LosslessArray[ segmentId ] )CodedLossless = 0if ( using_qmatrix ) {if ( LosslessArray[ segmentId ] ) {SegQMLevel[ 0 ][ segmentId ] = 15SegQMLevel[ 1 ][ segmentId ] = 15SegQMLevel[ 2 ][ segmentId ] = 15} else {SegQMLevel[ 0 ][ segmentId ] = qm_ySegQMLevel[ 1 ][ segmentId ] = qm_uSegQMLevel[ 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 = 0elseallow_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) / 8sz -= headerBytestile_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 filetypedef 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 syntaxint extension_flag;int has_size_field;//extension_flag == 1int 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); //versionskip(bitstream, 2); //header sizeread(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); //unusedreturn 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://Freeif (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 Oneclass 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。
详细可参考官网的
- AV1 ISO Base Media File Format Binding Specification
参考:
AV1为何有信心打败H.265?-CSDN博客
AV1编码详解-CSDN博客
https://zhuanlan.zhihu.com/p/640104253
AV1 视频码流解析 - 简书
【AV1 spec学习一】OBU类型及码流结构_av1 obu-CSDN博客
IVF视频文件格式 - 简书
相关文章:
AV1视频编解码简介、码流结构(OBU)
我的音视频/流媒体开源项目(github) 目录 一、AV1编码技术 二、AV1码流结构(OBU) 三、IVF文件格式 四、ffmpeg支持AV1 五、关于常见格式对AV1的封装 一、AV1编码技术 AV1是由开放媒体联盟(AOM,Alliance for Open Media)在2018年发布的,AV1的前身…...
Service Work离线体验与性能优化
Service Work离线体验与性能优化 引言 先放个意外事件,万事开头难🤣🤣🤣 原计划是分享离线应用与数据资源缓存的应用实践,结果发现这一技术已被web标准废弃 曾经做过一个PC应用,业务需求要求应用具备容灾…...
linux之进程信号(初识信号,信号的产生)
目录 引入一、初识信号(信号预备知识)1.生活中的信号2.Linux中的信号3.信号进程得出的初步结论 二、信号的产生1.通过终端输入产生信号拓展: 硬件中断2.调用系统函数向进程发信号3.硬件异常产生信号4.软件条件产生信号拓展: 核心转储技术总结一下: 引入 一、初识信…...
为深度学习创建PyTorch张量 - 最佳选项
为深度学习创建PyTorch张量 - 最佳选项 正如我们所看到的,PyTorch张量是torch.Tensor PyTorch类的实例。张量的抽象概念与PyTorch张量之间的区别在于,PyTorch张量为我们提供了一个可以在代码中操作的具体实现。 在上一篇文章中,我们看到了…...
MySQL 与 Redis 数据一致性 2
1. 强一致还是最终一致?2. 先写 MySQL 还是先写Redis?case 1 3. 缓存(Redis)更新还是清除?更新策略更新策略会有数据不一致问题?数据不一致的概率与影响如果使用监听binlog更新数据还会出现数据不一致问题?binlog的消费问题 使用消息队列行不行?其他方案总结: 数据不一致…...
Git | git reset命令详解
关注:CodingTechWork 引言 Git 是一款非常流行的分布式版本控制工具,它帮助开发者有效地管理代码历史,支持多种功能来帮助团队协作、追踪修改和维护代码质量。git reset是 Git 中最强大、最复杂的命令之一,它的主要作用是重置当前…...
Linux高并发服务器开发 第十四天(dup/duo2/fcntl 进程 pcb进程控制块 环境变量)
目录 1.dup 和 dup2 1.1dup 1.2dup2 2.fcntl 3.进程 3.1进程和程序 3.2并发 3.3cpu 3.5pcb进程控制块 3.6进程状态 4.环境变量 1.dup 和 dup2 1.1dup - 将 文件描述符 ,复制产生“新文件描述符” 并返回。新、旧文件描述符,指向同一文件。 …...
[MySQL | 二、基本数据类型]
基本数据类型 一、数值类型举例表结构1. 整数类型zerofill属性 与 int(n) 中 n 的关系 2.bit类型3. 小数类型float类型decimal类型 二、字符串类型1. char2. varchar如何选择定长或变长字符串? 3. 日期时间类型(date datetime timestamp)4. enum枚举类型5. set多选类…...
第G1周:生成对抗网络(GAN)入门
>- **🍨 本文为[🔗365天深度学习训练营]中的学习记录博客** >- **🍖 原作者:[K同学啊]** 本人往期文章可查阅: 深度学习总结 基础任务 1.了解什么是生成对抗网络2.生成对抗网络结构是怎么样的3.学习本文代码&am…...
ROS2 准备工作(虚拟机安装,Ubuntu安装,ROS2系统安装)
准备工作 虚拟机安装 大家可以自行去安装VMware链接:https://pan.baidu.com/s/1KcN1I9FN--Sp1bUsjKqWVA?pwd6666 提取码:6666(提供者:零基础编程入门教程) 教程:【【2025最新版】VMware虚拟机安装教程,手把手教你免…...
FreeType 介绍及 C# 示例
FreeType 是一个开源的字体渲染引擎,用于将字体文件(如 TrueType、OpenType、Type 1 等)转换为位图或矢量图形。它广泛应用于操作系统、图形库、游戏引擎等领域,支持高质量的字体渲染和复杂的文本布局。 FreeType 的核心功能 字体…...
BertTokenizerFast 和 BertTokenizer 的区别
BertTokenizerFast 和 BertTokenizer 都是用于对文本进行标记化的工具,主要用于处理和输入文本数据以供 BERT 模型使用。它们都属于 HuggingFace 的 transformers 库。 主要区别 底层实现: BertTokenizer: 这是一个使用纯 Python 实现的标记器ÿ…...
OpenGL中Shader LOD失效
1)OpenGL中Shader LOD失效 2)DoTween的GC优化 3)开发微信小程序游戏有没有类似Debug真机图形的方法 4)射线和Mesh三角面碰撞检测的算法 这是第418篇UWA技术知识分享的推送,精选了UWA社区的热门话题,涵盖了U…...
[操作系统] 深入理解约翰·冯·诺伊曼体系
约翰冯诺依曼(John von Neumann,1903年12月28日—1957年2月8日),原名诺伊曼亚诺什拉约什(Neumann Jnos Lajos),出生于匈牙利的美国籍犹太人数学家,20世纪最重要的数学家之一…...
计算机网络(五)运输层
5.1、运输层概述 概念 进程之间的通信 从通信和信息处理的角度看,运输层向它上面的应用层提供通信服务,它属于面向通信部分的最高层,同时也是用户功能中的最低层。 当网络的边缘部分中的两个主机使用网络的核心部分的功能进行端到端的通信时…...
网络分析仪测试S参数
S参数的测试 一:S参数的定义 S参数(Scattering Parameters,散射参数)是一个表征器件在射频信号激励下的电气行为的工具,它以输入信号、输出信号为元素的矩阵来表现DUT的“传输”和“散射”效应,输入、输出…...
什么是数据仓库?
什么是数据仓库? 数据仓库(Data Warehouse,简称DW)是一种面向分析和决策的数据存储系统,它将企业中分散的、异构的数据按照一定的主题和模型进行集成和存储,为数据分析、报表生成以及商业智能(…...
.NET8.0多线程编码结合异步编码示例
1、创建一个.NET8.0控制台项目来演示多线程的应用 2、快速创建一个线程 3、多次运行程序,可以得到输出结果 这就是多线程的特点 - 当多个线程并行执行时,它们的具体执行顺序是不确定的,除非我们使用同步机制(如 lock、信号量等&am…...
使用 Charles 调试 Flutter 应用中的 Dio 网络请求
为了成功使用 Charles 抓取并调试 Flutter 应用程序通过 Dio 发起的网络请求,需遵循特定配置步骤来确保应用程序能够识别 Charles 的 SSL 证书,并正确设置代理服务器。 配置 Charles 以支持 HTTPS 请求捕获 Charles 默认会拦截 HTTP 流量;…...
老centos7 升级docker.io为docker-ce 脚本
旧的centos7 之前安装的是docker.io 由于一些原因,像docker compose 等版本变化,以及docker.io源受限等,我们要更新到docker-ce 并使用国内阿里云的源怎么处理?下面直接上脚本,upgrade-docker.sh #!/bin/bashset -e# 创建临时目录 TEMP_DIR"./tmp" mkdir -p "…...
Go Ebiten小游戏开发:贪吃蛇
贪吃蛇是一款经典的小游戏,玩法简单却充满乐趣。本文将介绍如何使用 Go 语言和 Ebiten 游戏引擎开发一个简单的贪吃蛇游戏。通过这个项目,你可以学习到游戏开发的基本流程、Ebiten 的使用方法以及如何用 Go 实现游戏逻辑。 项目简介 贪吃蛇的核心玩法是…...
c语言----------内存管理
内存管理 目录 一。作用域1.1 局部变量1.2 静态(static)局部变量1.3 全局变量1.4 静态(static)全局变量1.5 extern全局变量声明1.6 全局函数和静态函数1.7 总结 二。内存布局2.1 内存分区2.2 存储类型总结2.3内存操作函数1) memset()2) memcpy()3) memmove()4) memcmp() 2.4 堆…...
在一个sql select中作多个sum并分组
有表如下; 单独的对某一个列作sum并分组,结果如下; 对于表的第7、8行,num1都有值,num2都是null,对num2列作sum、按id分组,结果在id为4的行会显示一个null; 同时对2个列作sum&#x…...
如何修复Android上未安装的应用程序
在Android设备上安装应用程序通常是一个简单的过程。然而,“ Android上未安装应用程序”是一种常见的智能手机错误消息,由于一个或多个原因而经常遇到。发现由于即将出现故障而无法充分利用手机,这当然会非常令人沮丧,但幸运的是&…...
#CSS混合模式:解决渐变背景下的文字可见性问题
在现代网页设计中,渐变背景的使用越来越普遍。然而,当我们在渐变背景上放置文字时,常常会遇到一个问题:文字在某些背景颜色下可能变得难以阅读。今天,我们将探讨一个优雅的解决方案:使用CSS混合模式。 问题…...
微信小程序原生与 H5 交互方式
在微信小程序中,原生与 H5 页面(即 WebView 页面)之间的交互通常有以下几种方式: 1. 使用 postMessage 进行通信 微信小程序的 WebView 页面和原生小程序页面可以通过 postMessage 来进行数据传递。 WebView 页面向原生小程序发…...
kotlin中的flow使用,Flow跟生命周期结合
kotlin的Flow可以连续异步发出多个数据。 1. 普通flow,冷流类似于一个函数,当开始收集时才开始运行 val coldStream flow {for (i in 1..5) {delay(100L)emit(i)}} val collect1 buildString {coldStream.collect { append(it).append(", ") } }.remo…...
讲一下ZooKeeper的持久化机制?
大家好,我是锋哥。今天分享关于【讲一下ZooKeeper的持久化机制?】面试题。希望对大家有帮助; 讲一下ZooKeeper的持久化机制? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 ZooKeeper 是一个开源的分布式协调服务&…...
mybatis里面实现动态升降序
问题 最近有一个需求,需要前端告诉后端按照某个字段进行排序。这里主要侧重mybatis的xml实现,其他Spring集成就忽略了。 mapper xml实现 <if test"sortField ! null and sortField ! ">ORDER BY<choose><when test"sor…...
探索网络安全:浅析文件上传漏洞
前言 在数字化时代,网络安全已成为我们每个人都需要关注的重要议题。无论是个人隐私保护,还是企业数据安全,网络威胁无处不在。了解网络安全的基本知识和防护措施,对我们每个人来说都至关重要。 网络安全 网络安全并非只是对网…...
【C++】B2112 石头剪子布
博客主页: [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 💯前言💯题目描述游戏规则:输入格式:输出格式:输入输出样例:解题分析与实现 💯我的做法实现逻辑优点与不足 💯…...
java根据模板导出word,并在word中插入echarts相关统计图片以及表格
引入依赖创建word模板创建ftl模板文件保存的ftl可能会出现占位符分割的问题,需要处理将ftl文件中的图片的Base64删除,并使用占位符代替插入表格,并指定表格的位置在图片下方 Echarts转图片根据模板生成word文档DocUtil导出word文档 生成的wor…...
Linux网络知识——路由表
路由表 1 定义与作用 Linux路由表是一个内核数据结构,用于描述Linux主机与其他网络设备之间的路径,以及如何将数据包从源地址路由到目标地址。路由表的主要作用是指导数据包在网络中的传输路径,确保数据包能够准确、高效地到达目标地址。 …...
ImageSharp图形库学习
一、引言 在当今数字化时代,无论是 Web 应用、桌面程序,还是移动应用,图像处理都扮演着至关重要的角色。从电商平台展示商品图片,到社交媒体分享照片,再到各种软件的图标设计,图像处理无处不在。博主们在内…...
Android string.xml中特殊字符转义
项目中要在string.xml 中显示特殊符号 空格: (普通的英文半角空格但不换行) 窄空格: (中文全角空格 (一个中文宽度)) (半个中文宽度,但两个空格比一个中文…...
Rust 游戏开发框架指南
Rust 游戏开发框架指南 主流游戏引擎 1. Bevy 最受欢迎的 Rust 游戏引擎之一,基于 ECS(实体组件系统)架构。 特点: 🚀 高性能 ECS 系统📦 热重载支持🎨 现代渲染器🔊 内置音频系…...
SpringBoot3+Vue3开发台球计时系统
项目介绍 台球计时系统可以帮助我们自动计算开台时间(从开始到结束的时间段)、自动计算开台费用、结账后生成订单记录进行留存、也可以导出订单记录。 主要功能包含:球桌管理、开台、结账、查看占用明细、查看球台订单、订单管理、查看订单…...
基于springboot的租房网站系统
作者:学姐 开发技术:SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等 文末获取“源码数据库万字文档PPT”,支持远程部署调试、运行安装。 项目包含: 完整源码数据库功能演示视频万字文档PPT 项目编码࿱…...
静态综合路由实验
实验拓扑 实验要求 1.除R5的环回地址外,整个其他所有网段基于192.168.1.0/24进行合理的IP地址划分 2.R1-R4每个路由器存在两个环回接口,用于模拟pc网段;地址也在192.168.1.0/24这个网络范围内 3.R1-R4上不能直接编写到达5.5.5.0/24的静态路由…...
国产编辑器EverEdit - 扩展脚本:新建同类型文件(避免编程学习者反复新建保存练习文件)
1 扩展脚本:在当前文件目录下新建同类型文件 1.1 应用场景 用户在进行编程语言学习时,比如:Python,经常做完一个小练习后,又需要新建一个文件,在新建文件的时候,不但要选择文件类型,…...
Ubuntu Server 24.04 配置静态IP
Ubuntu Server 24.04 配置静态IP 提示:基于Ubuntu Server 24.04进行配置 文章目录 Ubuntu Server 24.04 配置静态IP一、查看网卡信息二、修改网卡信息三、使网卡配置生效四、测试 一、查看网卡信息 使用命令 ip a lo 为本地回环地址 ens33 真实网卡地址 shanfengubu…...
★3.3 事件处理
★3.3.1 ※MouseArea Item <-- MouseArea 属性 acceptedButtons : Qt::MouseButtons containsMouse : bool 【书】只读属性。表明当前鼠标光标是否在MouseArea上,默认只有鼠标的一个按钮处于按下状态时才可以被检测到。 containsPress : bool curs…...
linux系统监视(centos 7)
一.系统监视 1.安装iostat,sar,sysstat(默认没有,安装过可以跳跃) iostat 和 sar: 同样,iostat 和 sar 是 sysstat 软件包的一部分。使用以下命令安装:sudo yum install sysstat解释…...
Java面试总结(1)
问题1 自我介绍: 面试官您好,我叫xxx,是来自xxxx大学软件工程专业的一名应届生,我这次想应聘的是java开发实习生,在校期间,我热爱编程,能够使用java,C,python的编程语言,…...
晨辉面试抽签和评分管理系统之六:面试答题倒计时
晨辉面试抽签和评分管理系统(下载地址:www.chenhuisoft.cn)是公务员招录面试、教师资格考试面试、企业招录面试等各类面试通用的考生编排、考生入场抽签、候考室倒计时管理、面试考官抽签、面试评分记录和成绩核算的面试全流程信息化管理软件。提供了考生…...
关于H5复制ios没有效果
问题场景:今天遇到这样一个问题,需要从后端接口获取到的值进行复制,且不能提现调用获取值,因为是一个数据列表,每个列表元素需要当场点击调用接口获取值进行复制,本来以为很简单的一个需求,当做…...
Windows 蓝牙驱动开发-安装蓝牙设备
蓝牙配置文件驱动程序有两种安装类型: 客户端安装,在此类安装中,远程设备播发其服务,并且计算机与之连接。 示例包括:鼠标、键盘和打印机;服务器端安装,在此类安装中,计算机播发服务…...
你喜欢用什么编辑器?
电脑工作者和程序员所使用的文本编辑器通常需要具备高效率、易用性以及对代码友好等特点,包括语法高亮、自动完成、多文件同时编辑、查找替换、版本控制集成等功能。以下是几个广受开发者欢迎且实用性较强的文本编辑器: Visual Studio Code(V…...
32_Redis分片集群原理
1.Redis集群分片 1.1 Redis集群分片介绍 Redis集群没有使用一致性hash,而是引入了哈希槽的概念。Redis集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽。 用于将密钥映射到散列插槽的基本算法如下: HASH_SLOT = CRC16(key) mod 16384 集群的每…...
小米vela系统(基于开源nuttx内核)——openvela开源项目
前言 在 2024 年 12 月 27 日的小米「人车家全生态」合作伙伴大会上,小米宣布全面开源 Vela 操作系统。同时,OpenVela 项目正式上线 GitHub 和 Gitee,采用的是比较宽松的 Apache 2.0 协议,这意味着全球的开发者都可以参与到 Vela…...