编解码标准-H.264

news2024/12/26 23:48:15

H.264是MPEG-4家族中的一员,即MPEG-4系列文档ISO-14496的第10部分,因此被称作MPEG-4 AVC,MPEG-4重点考虑灵活性和交互性,而H.264着重强调更高的编码压缩率和传输的可靠性。

1、H.264 编码流程

1.1、slice&block

第一步:切片、切宏块,宏块(16x16)是编码的基本单元

第二步:不合理的宏块之间会出现块效应,即色差明显,所以继续切割,分成很多个8*8或4*4的子块

第三步:对子块进行算法编码,期间使用到的算法包括:

  • 帧内预测(内部压缩)
  • 帧间预测(外部压缩)
  • 量化编码
  • 熵编码

其中,每一个切片都有片头 + 多个宏块组成。

以16x16的宏块为编码最小单元,一个宏块可以被分成多个4x4或8x8的块,同一个宏块内,像素的相似程度会比较高,若16x16的宏块中,像素相差较大,那么就需要继续细分。

当对一个宏块进行编码的时候,每个宏块都会被分割成多种不同大小的子块进行预测。

1.2、GOP内压缩

GOP内 I、B、P帧

帧大小:I > P > B

压缩率:B > P > I

编码B帧的时候,需要先把B后面的帧先编码,然后参考之后才能继续编码B帧,例如:

PTS:IDR1、B2、B3、P4、B5、B6、P7、B8、B9、I10、B11、B12、P13、B14、B15、P16

DTS:IDR1、P4、B2、B3、P7、B5、B6、I10、B8、B9、P13、B11、B12、P16、B14、B15

每次编码B的时候,就要把后面最近的 I 或者 P 拿到前面来,如果已经之前被编码过,就不需要了。

IDR帧

I 帧不需要参考任何帧,但B、P帧可能去参考I帧之前的帧,但如果遇到IDR帧,就不能参考IDR帧之前的帧

其核心的作用,就是为了让编码重新同步,立即将参考帧队列清空,已编码的数据全部清除掉。如果之前的参考序列出现了错误,这里就可以立刻矫正。IDR后面的数据又能重新开始编码,不会收到前面的错误影响。

1.3、编码配置

实时视频会议一直是继续向更高质量,更低带宽的方向发展。H.264 High profile 技术于2010年率先被polycom应用于视频会议系统。

比h.264 baseline进一步节约了近一半的带宽。在高清实时会议中,采用H.264 baseline,带宽要求还是比较高的,特别是要做1080P 30pfs甚至60pfs时。如果能减少一半带宽,意味着节省2-4M带宽,如果是在MCU侧,则带宽节省就更可观了。

AVC/H.264 规定了多种不同的配置:基线、主要、扩展、高

  • 基线(Baseline Profile),不支持B帧,只支持无交错模式,主要是用于可视电话,会议电视,无线通讯等实时通信;
  • 主要(Main Profile),提供I/P/B 帧,支持无交错和交错,用于数字广播电视和数字视频存储;
  • 扩展(Extend Profile),也叫扩展Profile,
  • 高(High Profile),在 Main Profile 的基础上增加了8x8 内部预测、提高了压缩效率;

2、H.264的功能分层

H.264的原始码流(裸流)是由⼀个接⼀个NALU组成,它的功能分为两层:“编码层”和“网络层”。

  • VCL(视频编码层):包括核⼼压缩引擎和块、宏块和⽚的语法级别定义,设计⽬标是尽可能地独⽴于⽹络进⾏⾼效的编码;
  • NAL(⽹络提取层):负责将VCL产⽣的⽐特字符串适配到各种各样的⽹络,就是把已经编码后的数据封装到网络包中去;

H264在网络中的传输,是以一连串NALU的形式传输的,一张图像有可能存在2个NALU。

其中,NALU装了不同的东西:

  • SPS:序列参数集,SRS中保存了一组编码视频序列的全局参数(发 I 帧之前至少要发一次)
  • PPS:图像参数集(发 I 帧之前至少要发一次)
  • I 帧:I 帧或 I 帧的一部分;
  • P帧:P帧或P帧的一部分;
  • B帧:B帧或B帧的一部分;

如果H.264码流解不出来,就要去看看是不是SPS或PPS不存在?

3、H.264流结构

2.1、AnnexB/AVCC

H.264流有两种格式:

  • 一种是annexb,也是传统模式,裸流一般都是annexb格式
    • annexb格式会在数据包前面加上startcode,然后在后面加上UALU包(NALU Header + RBSP)
    • 这个startcode用来做字节流对齐,以及分割流数据。
    • 将SPS和PPS都作为一个NALU进行封装,每一次遇到 I 帧之前,都是重复加上SPS和PPS,这个SPS的作用就是提供了序列信息,比如解码信息,而这个PPS提供的是图像信息,比如如何压缩等。
    • NALU封装了SPS、PPS、SEI、
    • annexb格式的好处,就是解码器可以从任意一个包开始解码。

  • 另外种就是AVCC模式,例如mp4、mkv都属于AVCC格式
    • 没有startcode,直接是一个个UALU包
    • 解码器配置参数在一开始就配置好了,使用NALU长度作为NALU的边界,不需要额外的起始码
    • SPS和PPS都封装在文件头部的extradata中;
    • 好处就是播放器直接能识别,去除了大量的startcode、sps、pps,缩小了文件大小;

比方说在ffmpeg中,我们解封装mp4后,需要对H264进行解码,而解码之前必须要对H264裸流进行一个转封装过滤,将h264的 “mp4版本” 转换为 “annexb版本” 的过程。

RTP包中接收的264包是不含有0x00,0x00,0x00,0x01头的,这部分是RTP接收以后,另外再加上去的,解码的时候再做判断的。

/** 
 * 解码PS流的Extradata
 * h264_parse()
 *        |
 *    ff_h264_decode_extradata() |--> decode_extradata_ps
 *                               |--> decode_extradata_ps_mp4()
 * 
 * decode_extradata_ps():
 *
 *
 * decode_extradata_ps_mp4(): 
 *    MP4中SPS和PPS存放在 moov->trak->mdia->minf->stbl->stsd: 
 *       Extensions = Size + Type(avcC) + Extradata
 *     
 */
static int decode_extradata_ps(const uint8_t *data, int size, H264ParamSets *ps,
int is_avc, void *logctx)
{
    // H264包
    H2645Packet pkt = { 0 };
    int i, ret = 0;
    
    ret = ff_h2645_packet_split(&pkt, data, size, logctx, is_avc, 2, AV_CODEC_ID_H264, 1, 0);
    if (ret < 0) {
        ret = 0;
        goto fail;
    }
    // 包里面有多少个NAL?
    for (i = 0; i < pkt.nb_nals; i++) {
        // 解析NAL类型
        H2645NAL *nal = &pkt.nals[i];
        switch (nal->type) {
            // SPS(7): 25字节左右
            case H264_NAL_SPS: {
                GetBitContext tmp_gb = nal->gb;
                ret = ff_h264_decode_seq_parameter_set(&tmp_gb, logctx, ps, 0);
                if (ret >= 0)
                    break;
                av_log(logctx, AV_LOG_DEBUG,
                    "SPS decoding failure, trying again with the complete NAL\n");
                init_get_bits8(&tmp_gb, nal->raw_data + 1, nal->raw_size - 1);
                ret = ff_h264_decode_seq_parameter_set(&tmp_gb, logctx, ps, 0);
                if (ret >= 0)
                    break;
                ret = ff_h264_decode_seq_parameter_set(&nal->gb, logctx, ps, 1);
                if (ret < 0)
                    goto fail;
                break;
            }
            // PPS(8): 5字节左右
            case H264_NAL_PPS:
                ret = ff_h264_decode_picture_parameter_set(&nal->gb, logctx, ps,
                    nal->size_bits);
                if (ret < 0)
                    goto fail;
                break;
            default:
                av_log(logctx, AV_LOG_VERBOSE, "Ignoring NAL type %d in extradata\n",
                    nal->type);
                break;
        }
    }
    fail:
    ff_h2645_packet_uninit(&pkt);
    return ret;
}
typedef struct H264ParamSets {
	// SPS列表
    AVBufferRef *sps_list[MAX_SPS_COUNT];
	// PPS列表
    AVBufferRef *pps_list[MAX_PPS_COUNT];

    AVBufferRef *pps_ref;

    /* currently active parameters sets */
    const PPS *pps;
    const SPS *sps;

    int overread_warning_printed[2];
} H264ParamSets;

2.2、SPS

序列参数集,保存了一组编码视频序列的全局参数,保存了:profile、level、视频宽和高、颜色空间等。在H.264的各种语法元素中,SPS中的信息至关重要。如果其中的数据丢失或出现错误,那么解码过程很可能会失败。

SPS 中的信息至关重要,如果其中的数据丢失,解码过程就可能失败。SPS 和 PPS 通常作为解码器的初始化参数。一般情况,SPS 和 PPS 所在的 NAL 单元位于整个码流的起始位置,但是在某些场景下,在码率中间也可能出现这两种结构:

  • 解码器要在码流中间开始解码。比如,直播流。
  • 编码器在编码过程中改变了码率的参数。比如,图像的分辨率。

2.3、PPS

每一帧编码后数据所依赖的参数,都保存在PPS中,主要体现的就是图像编码信息。

2.4、NALU

2.4.1、nal_unit_header

NALU头就一个字节,包含了对NALU的描述,1)重要程度;2)NALU类型

F

1B

禁止位,0表示正常,1表示错误,一般都是0

NRI

2B

重要级别,00不重要,01,10,11非常重要

TYPE

5B

表示该NALU的类型是什么?

例如:

每个NAL分割的时候,00 00 00 01为startcode,头部的2个startcode分别代表了SPS和PPS,从第3个startcode开始,就是NALU(I、B、P帧)。

  • 0x00 0x00 0x00 0x01 + 0x67

十六进制转为二进制:0x0 11 00111,NALU类型=7,表示PSP

  • 0x00 0x00 0x00 0x01 + 0x68

十六进制转为二进制:0x0 11 01000,NALU类型=8,表示PPS

  • 0x00 0x00 0x01 + 0x65

十六进制转为二进制:0x0 11 00101,NALU类型=5,表示 I 帧

  • 0x00 0x00 0x00 0x01 + 0x41

十六进制转为二进制:0x0 10 00001,NALU类型=1,表示 P 帧

  • 0x00 0x00 0x00 0x01 + 0x01

十六进制转为二进制:0x0 00 00001,NALU类型=1,表示 B 帧

2.4.2、nal_unit_rbsp

NALU的主体涉及到三个重要的名词,分别为EBSP、RBSP和SODB。

其中EBSP完全等价于NALU主体,而且它们三个的结构关系为:

EBSP包含RBSP,RBSP包含SODB。

NALU = EBSP + 0x03(防竞争字节)+ ...... + EBSP + 0x03

NALU = RBSP + 补齐字节

1、SODB

String Of Data Bits 原始数据比特流,就是最原始的编码/压缩得到的数据

2、RBSP

Raw Byte Sequence Payload,又称原始字节序列载荷。和SODB关系如下:

RBSP = SODB + RBSP Trailing Bits(RBSP尾部补齐字节)引入RBSP Trailing Bits做8位字节补齐。

3、EBSP

Encapsulated Byte Sequence Payload:扩展字节序列载荷。

如果RBSP中也包括了StartCode(0x000001或0x00000001)怎么办呢?所以,就有了防止竞争字节(0x03),编码时,扫描RBSP,如果遇到连续两个0x00字节,就在后面添加防止竞争字节(0x03);解码时,同样扫描EBSP,进行逆向操作即可。

2.4.3、SliceHeader

  • first_mb_in_slice:片中的第一个宏块的地址, 片通过这个句法元素来标定它自己的地址。要注意的是在帧场自适应模式下,宏块都是成对出现,这时本句法元素表示的是第几个宏块对,对应的第一个宏块的真实地址应该是:2 * first_mb_in_slice;
  • slice_type:指明片的类型,IDR 图像时, slice_type 等于 2, 4, 7, 9;

slice_type 的值在 5 到 9 范围内表示,除了当前条带的编码类型,所有当前编码图像的其他条带的 slice_type 值应与当前条带的 slice_type 值一样,或者等于当前条带的 slice_type 值减 5。

当 nal_unit_type 等于 5(IDR 图像)时,slice_type 应等于 2、 4、 7 或 9。当 num_ref_frames 等于 0 时, slice_type 应等于 2、 4、 7 或 9。

  • pic_parameter_set_id:当前slice所依赖的pps的id;
  • colour_plane_id:当标识位separate_colour_plane_flag为true时,colour_plane_id表示当前的颜色分量,0、1、2分别表示Y、U、V分量;
  • frame_num:每个参考帧都有一个依次连续的 frame_num 作为它们的标识,这指明了各图像的解码顺序。但事实上我们在表 中可以看到, frame_num 的出现没有 if 语句限定条件,这表明非参考帧的片头也会出现 frame_num。只是当该个图像是参考帧时,它所携带的这个句法元素在解码时才有意义;

  • field_pic_flag:场编码标识位。当该标识位为1时表示当前slice按照场进行编码;该标识位为0时表示当前slice按照帧进行编码;
  • bottom_field_flag:底场标识位。该标志位为1表示当前slice是某一帧的底场;为0表示当前slice为某一帧的顶场;
  • idr_pic_id:表示IDR帧的序号。某一个IDR帧所属的所有slice,其idr_pic_id应保持一致。IDR 图像的标识。不同的 IDR 图像有不同的 idr_pic_id 值。值得注意的是,IDR 图像有不等价于 I 图像,只有在作为 IDR 图像的 I 帧才有这个句法元素,在场模式下, IDR 帧的两个场有相同的 idr_pic_id 值。 idr_pic_id 的取值范围是 [0,65535] 和 frame_num 类似,当它的值超出这个范围时,它会以循环的方式重新开始计数;
  • pic_order_cnt_lsb:表示当前帧序号的另一种计量方式;
  • delta_pic_order_cnt_bottom:表示顶场与底场POC差值的计算方法,不存在则默认为0;
  • slice_qp_delta:指出在用于当前片的所有宏块的量化参数的初始值;

2.4.4、rbsp_trailing_bits

但是只在 NALU 前面加上起始码是会产生问题了,因为原始码流中,是有可能出现 0 0 0 1 或者 0 0 1 的,这样就会导致读取程序将一个 NALU 误分割成多个 NALU。为了防止这种情况发生,AnnexB 引入了防竞争字节(Emulation Prevention Bytes)的概念。

所谓防竞争字节(Emulation Prevention Bytes),就是在给 NALU 添加起始码之前,先对码流进行一次遍历,查找码流里面的存在的 000、001、002、003 的字节,然后对其进行如下修改。

 

// EBSP->RBSP 反向处理
std::vector<uint8_t> EBSP2RBSP(uint8_t* buffer, int len) {
	// 00 00 03 去掉03
	std::vector<uint8_t> ebsp;
	int i = 0;
	for (i = 0; i < len-2; ++i) {
		if (buffer[i] == 0x00 && buffer[i+1] == 0x00 && buffer[i+2] == 0x03) {
			ebsp.push_back(buffer[i++]);
			ebsp.push_back(buffer[i++]);
		}
		else {
			ebsp.push_back(buffer[i]);
		}
	}
	for (; i < len; ++i) {
		ebsp.push_back(buffer[i]);
	}
	return ebsp;
}

4、H.264内I、B、P帧的关系?

GOP编码后的顺序是解码顺序,解码后看到的是显示顺序。

控制GOP也可以控制延迟问题,减少I帧时间的间距,一般控制在2秒比较合适。

4.1、I 关键帧

不需要参考其他画面,靠自己就能被解码成完整图像,属于“帧内编码”。

  1. 采用帧内编码
  2. 占数据信息量比较大
  3. 是一个GOP的基础帧
  4. 不需要考虑运动矢量

4.2、P帧

P帧代表预测帧,除了空域预测以外,它还可以通过时域预测来进行压缩。

通过与其相邻的前一帧(I 或 P)不同像素点进行压缩本帧数据,属于“帧间编码”。

以I帧和P帧为例。如果你只使用这两种类型的帧,那么每一帧要么参考自身(I 帧),要么参考前一帧(P 帧)。因此,帧可以以相同的顺序进出编码器。这里,呈现顺序(或显示顺序)与编码、解码顺序相同。

4.3、B帧

B帧可以参考在其前后出现的帧,采用双向预测(前后 I 或 P ),大大提高压缩倍数,想要理解B帧的作用,我们需要先理解显示顺序和解码顺序的概念。

按照解码顺序,解码器先解码帧1(I帧),然后是帧2(P帧)。但它却无法显示帧2,因为在解码顺序中的实际上是帧4!所以,解码器需要将帧2(按解码顺序)放入缓冲区,然后等待显示它的时机。

所以,编码器和解码器需要在内存中维护两个“顺序”或“序列”:一个将帧放置在正确的显示顺序中,另一个用于将帧按照编码和解码所需顺序放置。

  • 显示队列:1、2、3、4、5、6、7
  • 解码队列:1、2、3、2、6、7、5

所以在GOP内部,I与I之间为一个组,I组内部P与P之间一个组,P组内部先解码P,后解码B

  • B帧压缩率最大,用来预测运动轨迹;
  • P次之,用来表示和前一帧的差异;
  • I帧最小,其本身独立完成编码。

4.4、IDR帧

视频第一个I帧,称为IDR帧,IDR一定是I帧,但I帧不一定是IDR帧。

4.5、开放GOP和闭合GOP

所谓开放GOP,就是 I 帧可以被跨越,而闭合GOP,就是 I 帧是一个IDR,该 IDR 帧不能被之前的帧参考。

这也要就要求了,在 IDR 帧之前,必须有一个P帧,否则,IDR 帧相邻的B帧就无法向后预测。

IDR和闭合GOP到底有什么用处?

  • ABR视频流:

在ABR视频流中,播放器可以根据带宽和解码器缓冲器的填充程度在不同配置文件(组合不同码率和分辨率的视频)之间切换。如果播放器要从1080p切换到360p,那么它就需要这种利落的切换。此时IDR发挥作用,这样播放器就能刷新缓冲,让360p的视频流进入。

  • 错误恢复:

如果你在流化视频时使用HLS,并且每个视频片段都以IDR开始,这意味着片段中的所有帧都不能参考前、后片段中的帧。所以如果因为某个错误而失去其中一个片段,播放器仍然能继续接收下一个视频片段。有趣的是,Apple 的 HLS 规范提到应该每两秒使用一次 IDR。(注意:规范没有说视频片段持续时间应该是两秒,而是指 GOP 的大小是两秒)

所以说,IDR不能太频繁,因为会影响压缩效率。但又要保证视频播放过程中丢包不受到影响,所以最好的办法,就是间隔一段时间使用固定的IDR。

  • 快进快退:

我们之前提到过,IDR非常有助于实现快进快退。播放器需找到距离最近的IDR,然后开始从这一点播放视频流。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/164678.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

字节面试官: 让你设计一个MQ每秒要抗几十万并发,怎么做?

目录 1、页缓存技术 磁盘顺序写2、零拷贝技术3、最后的总结 这篇文章来聊一下Kafka的一些架构设计原理&#xff0c;这也是互联网公司面试时非常高频的技术考点。 Kafka是高吞吐低延迟的高并发、高性能的消息中间件&#xff0c;在大数据领域有极为广泛的运用。配置良好的Kaf…

电商项目之如何迁移千万级别的数据表

1 背景 电商系统一般都会有一张表记录买家的浏览器信息&#xff0c;包含但不限于浏览器ip、浏览器cookie信息、浏览器user-agent、当前页面的url、当前页面的refer。买家在电商网站上每一次操作&#xff0c;都会记录到该表。该表的数量量至少达到千万级级别。该表有什么用处&a…

07.优雅地断开套接字连接

优雅地断开套接字连接 本章将讨论如何优雅地断开相互连接的套接字。之前用的方法不够优雅是因为&#xff0c;我们是调用close或closesocket函数单方面断开连接的。 基于TCP的半关闭 TCP中的断开连接过程比建立连接过程更重要&#xff0c;因为连接过程中一般不会出现大的变数…

八、MySQL 常用函数汇总(1)

文章目录一、函数1.1 函数简介1.2 不同DBMS函数的差异二、数学函数2.1 绝对值函数ABS(x)和返回圆周率的函数PI()2.2 平方根函数SQRT(x)和求余函数MOD(x,y)2.3 获取整数的函数CEIL(x)、CEILING(x)和FLOOR(x)2.4 获取随机数的函数RAND()和RAND(x)2.5 函数ROUND(x)、ROUND(x,y)和T…

keytool 工具介绍

使用JDK自带的 keytool 工具&#xff1a; 简介 keytool 命令是一个密钥和证书管理的工具。它允许用户使用数字签名管理自己的公钥/私钥对和相关证书&#xff0c;用于自我身份验证(向其他用户和服务验证自己)或数据完整性和身份验证服务。keytool 命令还允许用户缓存通信对等体…

【七牛云 后端】笔试面

一、选择、填空知识点整理 1. fork() 函数 fork() 函数通过系统调用创建一个与原来进程相同的进程&#xff08;如果初始参数或者传入的变量不同&#xff0c;两个进程也可以做不同的事&#xff09; 示例 —— #include <stdio.h> int main() {for(int i0; i<2; i){…

centos7 安装git

一、查看是否安装过git git --version若出现以上版本号&#xff0c;则代表已经安装了git&#xff0c;不需要再次安装了&#xff0c;git安装&#xff0c;分为用yum安装和下载git源码编译安装&#xff0c;以下两种方法&#xff1a; 二、使用yum安装git yum -y install git安装…

磨金石教育摄影技能干货分享|如何在纪实摄影中体现艺术内涵

纪实摄影往往是摄影师在生活中或者旅行中随手拍下的&#xff0c;记录人们平常的瞬间。这类摄影往往强调真实性&#xff0c;在技巧上不会过多的追求。但有时候摄影师为了体现照片中更多的内涵&#xff0c;或者个人的情感思想&#xff0c;会运用一些摄影技巧来表现。下面就让我们…

动态内存与动态内存函数详解

文章目录前言一、动态内存函数的介绍1.malloc函数2.calloc3.realloc4.free二、两种常见的内存申请方法1.返回指针2.传二级指针三.常见的动态内存错误1.对NULL指针的解引用操作2.对动态开辟空间的越界访问3.对非动态开辟内存使用free释放4.使用free释放一块动态开辟内存的一部分…

6. 描述性统计函数:summary 、Fivenum、describe、describeBy、stat.desc、 Aggregate、summaryBy

b站课程视频链接&#xff1a; https://www.bilibili.com/video/BV19x411X7C6?p1 腾讯课堂(最新&#xff0c;但是要花钱&#xff0c;我花99&#x1f622;&#x1f622;元买了&#xff0c;感觉讲的没问题&#xff0c;就是知识点结构有点乱&#xff0c;有点废话&#xff09;&…

ESP32设备驱动-ADXL345三轴加速计驱动

ADXL345三轴加速计驱动 文章目录 ADXL345三轴加速计驱动1、ADXL345介绍2、硬件准备3、软件准备4、驱动实现1、ADXL345介绍 ADXL345 是一款小型、薄型、低功耗、3 轴加速度计,具有高达 16g 的高分辨率(13 位)测量值。数字输出数据采用 16 位二进制补码格式,可通过 SPI(3 线…

C进阶:动态内存函数 malloc calloc realloc free及常见动态内存开辟错误

本文主要讲解动态内存开辟的有关知识。 目录 一.malloc 与 free 1.malloc 2.free 3.实例&#xff1a; 二.calloc 三.realloc 四.常见的动态内存错误 1.对NULL指针的解引用操作 2.对动态开辟空间的越界访问 3.对非动态开辟内存使用free释放 4.使用free释放一块动态…

springboot 构建多模块项目

新建 spring initializr 父项目 Spring 官方提供了 Spring Initializr 来引导大家快速构建一个应用项目。在此不细说。 idea 创建 1、 new – project 注意jdk版本 和 勾选 default,然后next 2、 填写项目信息 3、选择项目组成部分,也可后期加上 4、选择项目位置 --选…

Docker入门:介绍Docker的常用命令、镜像的加载原理、Dockerfile打包、Docker网络、Docker Compose容器编排+监控

Docker入门 1&#xff09;从面向对象角度来看&#xff0c;Docker利用容器&#xff08;Container&#xff09;独立运行一个或一组应用&#xff0c;应用程序或服务运行在容器里面&#xff0c;容器就类似于一个虚拟化的运行环境&#xff0c;容器是用镜像创建出来的运行实例。就像是…

区块链笔记4--BTC实现

1 UTXO &#xff1a;区块链是一个去中心化的账本&#xff0c;比特币采用了 基于交易的账本模式。然而&#xff0c;系统中并无显示记录账户包含BTC数&#xff0c;实际上其需要通过交易记录进行推算。在比特币系统中&#xff0c;全节点需要维护一个名为 UTXO(UnspentTransaction …

Java抽象类:案例、特点、模板方法模式

一.抽象类的案例 案例&#xff1a;加油站支付卡 系统需求&#xff1a; 某加油站推出了2种支付卡&#xff1a; 1、预存1万元的金卡&#xff0c;后续加油享受8折优惠&#xff1b; 2、预存5千元的银卡&#xff0c;后续加油享受8.5折优惠。 请分别实现2种卡片进入收银系统后的…

记录每日LeetCode 2293.极大极小游戏 Java实现

题目描述&#xff1a; 给你一个下标从 0 开始的整数数组 nums &#xff0c;其长度是 2 的幂。 对 nums 执行下述算法&#xff1a; 设 n 等于 nums 的长度&#xff0c;如果 n 1 &#xff0c;终止 算法过程。否则&#xff0c;创建 一个新的整数数组 newNums &#xff0c;新数组…

分享66个HTMLCSS源码,总有一款适合您

HTML&CSS源码 分享66个HTML&CSS源码&#xff0c;总有一款适合您 下面是文件的名字&#xff0c;我放了一些图片&#xff0c;文章里不是所有的图主要是放不下...&#xff0c;大家下载后可以看到。 源码下载链接&#xff1a;https://pan.baidu.com/s/1AeVqON7byvt-ngB_U…

位段与枚举

目录 1、位段 1、位段的声明 2、位段的内存分配特点 3、位段的跨平台问题 4、位段的应用 2、枚举 1、枚举类型的定义 2、枚举的优点 3、联合体&#xff08;共用体&#xff09; 1、联合类型的定义 2、联合体的特点即大小计算 1、位段 1、位段的声明 位段的位指的是…

Pr初识01

Pr初识1.关于Pr&#xff1a;2.项目序列3.PR工作界面4.导入素材5.制式与素材管理6.剪辑与工具7.剪辑与工具&#xff08;下&#xff09;8.工具面板与时间轴面板进阶9.关键帧动画10.视频特效11.视频特效&#xff08;下&#xff09;12.音频及结合AU去除噪音13.字幕运用1.关于Pr&…