FFmpeg 编码和解码
文章目录
- 音频格式
- AAC
- ADIF音频数据交换格式
- ADTS音频数据传输流
- 音频解码
- 音频编码
- 视频格式
- H264
- GOP图像组
- I帧,P帧,B帧
- H264压缩技术
- H264压缩级别
- H264视频级别
- H264码流结构
- SPS
- PPS
- 解码视频
- 编码视频
音频格式
AAC
AAC全称 Advanced Audio Coding,是一种专为声音数据设计的文件压缩格式。出现于1997年,基于MPEG-2的音频编码技术。由Fraunhofer IIS、杜比实验室、AT&T、索尼等公司共同开发,目的是取代MP3格式。与MP3不同,它采用了全新的算法进行编码,更加高效,具有更高的性价比。利用AAC格式,可使人感觉声音质量没有明显降低的前提下,更加小巧。
优点:相较于mp3,AAC格式的音质更佳,文件更小。
不足:AAC属于有损压缩的格式,与时下流行的APE、FLAC等无损格式相比,音质存在本质上的差距。加之传输速度更快的USB3.0和16G以上大容量MP3正在加速普及,也使得AAC头上小巧的光环不复存在。
AAC共有9种规格,以适应不同场合的需要:
- MPEG-2 AAC LC 低复杂度规格(low complexity),比较简单,没有增益控制,但提高了编码效率,在中等码率的编码效率及音质方面,都能找到平衡点
- MPEG-2 AAC Main 主规格
- MPEG-2 AAC SSR 可变采样率规格(scaleable sample rate)
- MPEG-4 AAC LC 低复杂度规格,现在的手机比较常见的MP4文件中的音频部分就包括了该规格音频文件
- MPEG-4 AAC Main 主规格。包含了除增益控制之外的全部功能,其音质最好。
- MPEG-4 AAC SSR 可变采样率规格
- MPEG-4 AAC LTP 长时期预测规格(Long Term Predicition)
- MPEG-4 AAC LD 低延迟规格 (Low Delay)
- MPEG-4 AAC HE 高效率规格(High Efficiency)这种规格适合用于低码率编码,有Nero AAC编码器支持
HE:High Efficiency(高效性)。 HE-AAC v1(又称AACPlusV1,SBR),用容器的方法实现了AAC(LC)+SBR技术。SBR其实代表的是Spectral Band Replication(频段复制)。简要叙述一下,音乐的主要频谱集中在低频段,高频段幅度很小,但很重要,决定了音质。如果对整个频段编码,若是为了保护高频就会造成低频段编码过细以致文件巨大,若是保存了低频的主要成分而失去高频成分就会丧失音质。SBR把频谱切割开来,低频单独编码保存主要成分,高频单独放大编码保存音质,“统筹兼顾”了,在减少文件大小的情况下还保存了音质,完美地化解了这一矛盾。
HEv2:用容器的方法包含了HE-AAC v1和PS技术。PS指parametric stereo(参数立体声)。原来的立体声文件大小是一个声道的两倍。但是两个声道的声音存在某种相似性,根据香农信息熵编码定理,相关性应该被去掉才能减小文件大小。所以PS技术存储了一个声道的全部信息,然后,花很少的字节用参数描述另一个声道和它不同的地方
目前使用最多的是LC和HE(适合低码率)。流行的Nero AAC编码程序只支持LC,HE、HEv2这三种规格,编码后的AAC音频,规格显示都是LC,HE其实就是AAC(LC)+SBR技术,HEv2就是AAC(LC)+SBR+PS技术。
AAC音频格式分为ADIF和ADTS
- ADIF:audio data interchange format,音频数据交换格式。这种格式的特征是可以确定的找到这个音频数据的开始,不需进行在音频数据流中间开始的解码,即它的解码必须在明确定义的开始处进行。故这种格式常用在磁盘文件中。
- ADTS: audio data transport stream,音频数据传输流。这种格式的特征是它是一个有同步字的比特流,解码可以在这个流中任何位置开始,它的特征类似于mp3数据流格式。
简单的说,ADTS可以在任意帧解码,也就是说,它每一帧都有头信息。ADIF只有一个统一的头,所以必须得到所有的数据后解码。且这两种的header格式也是不同的,目前一般编码后的和抽取出的都是ADTS格式的音频流,两者具体的组织结构如下所示:
空白处表示前后帧
有时候当你编码AAC裸流的时候,会遇到写出来的AAC并不能在PC和手机上播放,很大可能就是AAC文件的每一帧里缺少了ADTS头信息文件的包装拼接。秩序奥加入头文件ADTS即可。
ADIF音频数据交换格式
其中adif_header如下表:
raw_data_stream如图所示
byte_alignment为了保持字节对齐用,
可以看到ADIF只有一个header,里面没有关于每帧的信息,因为每帧不是固定大小,故只能按照顺序进行解码,也无法跳播或快进快退。除非自己解码遍历整个文件,建立每帧位置表。
ADTS音频数据传输流
一个AAC原始数据块长度是可变的,对原始帧加上ADTS头进行ADTS的封装,就形成了ADTS帧,AAC音频文件的每一帧由ADTS Header和AAC Audio Data组成。结构体如下:
ADTS头部信息占据了整个文件中的前7或9个字节,分为2部分:
- adts_fixed_header():固定头信息,头信息中的每一帧都相同,其中包括了一个固定的同步标记(syncword),该标记用于确定音频帧的边界位置。
- adts_variable_header():可变头信息,头信息则在帧与帧之间可变
一个完整的头信息就是固定头+可变头。
以下是对头信息中各字段的详细介绍:
- 固定头
- syncword:同步字,12bit,同步字是ADTS文件的标志符,它用于确定音频帧的开始位置和结束位置,通常为0xFFF
- ID:9bit,ID指示使用的MPEG版本。0表示MPEG-4,1表示MPEG-2
- layer:2bit,Layer定义了音频流所属的层级,对于AAC来说,其值为0。
- protection_absent:1bit,指示是否启动CRC错误校验。0表明数据经过CRC校验,否则未经过
- profile:2bit,指示编码所使用的AAC规范类型,
在MPEG-2 中定义了3种
MPEG-4中,profile的值=MPEG-4 Audio Object Type - 1
- sampling_frequency_index:4bit,表示采样率的索引,它告诉解码器当前音频数据的采样率。值的范围是0-15,每一个值表示一个特定的采样率。
- private_bit:1bit,为私有比特,通常被设置为0,没有实际作用。
- channel_configuration:3bit,指示音频的通道数,范围也是0-15
- original_copy:1bit,指示编码数据是否被原始产生,通常为0
- home:1bit,通常被设置为0,没有实际作用
- 可变头
- copyrighted_id_bit:编码时设置为0,解码时忽略
- copyrighted_id_start:编码时设置为0,解码时忽略
- aac_frame_length:ADTS帧长度,它包括ADTS长度和AAC声音数据长度。即aac_frame_length = ( protection_absent == 0 ? 9 : 7 ) + audio_data_length
- adts_buffer_fullness: 固定位0x7FF。表示是码率可变的码流
- number_of_raw_data_blocks_in_frame:ADTS帧中有number_of_raw_data_blocks_in_frame + 1个AAC原始帧。(一个AAC原始帧包含一段时间内1024个采样及相关数据)
AAC ES是AAC音频编码的一种基本数据格式,也是AAC音频数据在流式传输和文件存储中的常见格式之一。
不同于其他容器格式,它不包含额外的元数据或结构信息,仅包含未经任何封装或压缩处理的原始音频数据。这些原始数据可以作为音频文件或流传输的基础,同时也可以用于对AAC音频进行转码、编辑或者重组。
通常由一系列连续的AAC音频帧组成,每个帧以一个特定的标志符开始,该标志符表示这是一个AAC音频帧。在AAC ES中,每个音频帧拥有相同的长度,但并一定包含相同数量的采样点,因为采样率和声道数量可能会发生变化。
另一个关键特征是其比特流顺序,即数字音频数据的组织方式,AAC ES采用大端字节顺序,其中高位字节排在前面,地位字节排在后面。此外,在AAC ES中,音频数据按照从左到右、自上而下的顺序排列,与典型的文本文件不同。
总之,AAC ES是AAC音频编码的一种基本数据格式,它通常由一系列AAC音频帧组成,并且不包含任何附加的元数据或结构信息。AAC ES可以作为音频文件或流传输的基础,同时也可以用于对AAC音频进行转码、编辑或重组。由于其简单性和灵活性,AAC ES受到了广泛的应用,并且成为了数字音频编码领域的标准之一。
音频解码
void Widget::decode()
{char errbuf[1024]; // 错误信息缓冲区const char *inputFile = "../source/audio.aac"; // 输入文件const char *outputFile = "../output/out.pcm"; // 输出文件AVFormatContext *pInFormatCtx = NULL; // 打开文件上下文// 打开输入文件if (avformat_open_input(&pInFormatCtx, inputFile, NULL, NULL) != 0){av_strerror(1, errbuf, sizeof(errbuf));printf("无法打开输入文件:%s\n", errbuf);return;}// 获取输入文件信息if (avformat_find_stream_info(pInFormatCtx, NULL) < 0){av_strerror(1, errbuf, sizeof(errbuf));printf("无法获取输入文件信息:%s\n", errbuf);return;}// 查找音频流int audioStreamIndex = av_find_best_stream(pInFormatCtx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);if (audioStreamIndex < 0){printf("无法找到音频流\n");return;}// 查找解码器const AVCodec *pCodec = avcodec_find_decoder(pInFormatCtx->streams[audioStreamIndex]->codecpar->codec_id);if (!pCodec){printf("无法找到解码器\n");return;}// 创建解码器上下文AVCodecContext *pCodecCtx = avcodec_alloc_context3(pCodec);if (!pCodecCtx){printf("无法创建解码器上下文\n");return;}// 复制解码器参数if (avcodec_parameters_to_context(pCodecCtx, pInFormatCtx->streams[audioStreamIndex]->codecpar) < 0){printf("无法复制解码器参数\n");return;}// 打开解码器if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0){printf("无法打开解码器\n");return;}// 创建输出文件FILE *pOutFile = fopen(outputFile, "wb");if (!pOutFile){printf("无法创建输出文件\n");return;}// 解码数据AVPacket packet; // 数据包AVFrame *pFrame = av_frame_alloc(); // 解码后的数据帧while (av_read_frame(pInFormatCtx, &packet) >= 0){// 解码数据包int ret = avcodec_send_packet(pCodecCtx, &packet);if (ret < 0){return; // 解码失败}while (ret >= 0){ret = avcodec_receive_frame(pCodecCtx, pFrame);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF){break;}else if (ret < 0){av_strerror(ret, errbuf, sizeof(errbuf));printf("解码数据帧失败:%s\n", errbuf);break;}int fmtByteSize = av_get_bytes_per_sample((AVSampleFormat)pFrame->format); // 每个样本的字节数for (int i = 0; i < pFrame->nb_samples; i++) // 每个样本{for (int ch = 0; ch < pFrame->ch_layout.nb_channels; ch++) // 每个声道{fwrite(pFrame->data[ch] + i * fmtByteSize, 1, fmtByteSize, pOutFile); // 写入文件}}av_frame_unref(pFrame); // 释放数据帧}av_packet_unref(&packet); // 释放数据包}// 编码器刷新,将剩余的数据帧编码为数据包int ret = avcodec_send_packet(pCodecCtx, NULL);while (ret >= 0){ret = avcodec_receive_frame(pCodecCtx, pFrame);if (ret < 0){break;}int fmtByteSize = av_get_bytes_per_sample((AVSampleFormat)pFrame->format); // 每个样本的字节数for (int i = 0; i < pFrame->nb_samples; i++) // 每个样本{for (int ch = 0; ch < pFrame->ch_layout.nb_channels; ch++) // 每个声道{fwrite(pFrame->data[ch] + i * fmtByteSize, 1, fmtByteSize, pOutFile); // 写入文件}}av_frame_unref(pFrame); // 释放数据帧}qDebug() << "解码完成";// 释放资源fclose(pOutFile);av_frame_free(&pFrame);avcodec_free_context(&pCodecCtx);avformat_close_input(&pInFormatCtx);
}
音频编码
写入adts头的函数
void Widget::writeADTSHeader(std::ofstream &file, const AVCodecContext *codecContext, const int packetSize)
{uint8_t adtsHeader[7]{0};// 写入ADTS头,注意位运算优先级比较低,所以括号不能省略adtsHeader[0] = 0xFF;adtsHeader[1] = 0xF1; // 0xF1表示MPEG-4 AAAC 校验位为1adtsHeader[2] = (codecContext->profile << 6) + (4 << 2) + (codecContext->ch_layout.nb_channels >> 2);adtsHeader[3] = ((codecContext->ch_layout.nb_channels & 3) << 6) + ((packetSize + 7) >> 11); // 固定头的后4位+可变头的前4位adtsHeader[4] = ((packetSize + 7) >> 3) & 0xFF;adtsHeader[5] = (((packetSize + 7) & 0x07) << 5) + 0x1F;adtsHeader[6] = 0xFC;file.write((char *)adtsHeader, 7);
}
编码函数
void Widget::encode()
{char errbuf[1024]; // 错误信息缓冲区const char *inputFile = "../../source/audio.pcm"; // 输入文件const char *outputFile = "../../output/out1.aac"; // 输出文件// 查找AAC编码器const AVCodec *pCodec = avcodec_find_encoder(AV_CODEC_ID_AAC);if (!pCodec){printf("无法找到编码器\n");return;}// 创建编码器上下文AVCodecContext *pCodecCtx = avcodec_alloc_context3(pCodec);if (!pCodecCtx){printf("无法创建编码器上下文\n");return;}pCodecCtx->sample_fmt = AV_SAMPLE_FMT_FLTP; // 设置采样格式pCodecCtx->sample_rate = 44100; // 设置采样率AVChannelLayout ch_layout = AV_CHANNEL_LAYOUT_STEREO; // 设置声道布局av_channel_layout_copy(&pCodecCtx->ch_layout, &ch_layout);pCodecCtx->bit_rate = 128'000; // 设置比特率pCodecCtx->flags = AV_CODEC_FLAG_GLOBAL_HEADER; // 设置全局头标志pCodecCtx->profile = AV_PROFILE_AAC_HE_V2; // 设置AAC编码器配置文件// 打开编码器if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0){printf("无法打开编码器\n");return;}std::ifstream pInFile(inputFile, std::ifstream::binary); // 打开输入文件std::ofstream pOutFile(outputFile, std::ofstream::binary); // 打开输出文件if (!pInFile.is_open() || !pOutFile.is_open()){return;}// 创建数据包AVPacket *packet = av_packet_alloc();if (!packet){printf("无法创建数据包\n");return;}// 创建数据帧AVFrame *pFrame = av_frame_alloc();if (!pFrame){printf("无法创建数据帧\n");return;}pFrame->sample_rate = pCodecCtx->sample_rate; // 设置采样率pFrame->format = pCodecCtx->sample_fmt; // 设置采样格式av_channel_layout_copy(&pFrame->ch_layout, &pCodecCtx->ch_layout); // 设置声道布局pFrame->nb_samples = pCodecCtx->frame_size; // 设置每帧的样本数// 分配内存if (av_frame_get_buffer(pFrame, 0) < 0){printf("无法分配内存\n");return;}// 分配缓冲区int fmtByteSize = av_get_bytes_per_sample(pCodecCtx->sample_fmt); // 获取每个样本的字节数uint8_t *pBuffer = (uint8_t *)av_malloc(fmtByteSize * pCodecCtx->frame_size * pCodecCtx->ch_layout.nb_channels); // 分配缓冲区if (!pBuffer){printf("无法分配缓冲区\n");return;}// 读取数据while (!pInFile.eof()){// 读取数据for (int i = 0; i < pFrame->nb_samples * pFrame->ch_layout.nb_channels / 2; i++){pInFile.read((char *)pBuffer, fmtByteSize * 2);if (pInFile.eof()){break;}for (int ch = 0; ch < pFrame->ch_layout.nb_channels; ch++) // 每个声道{memcpy(pFrame->data[ch] + i * fmtByteSize, pBuffer + ch * fmtByteSize, fmtByteSize); // 复制数据到数据帧}}// 编码int ret = avcodec_send_frame(pCodecCtx, pFrame);if (ret < 0)break;while (ret >= 0){ret = avcodec_receive_packet(pCodecCtx, packet);if (ret < 0)break;writeADTSHeader(pOutFile, pCodecCtx, packet->size); // 写入ADTS头pOutFile.write((char *)packet->data, packet->size); // 写入数据av_packet_unref(packet); // 释放数据包}// 清空缓冲区memset(pBuffer, 0, fmtByteSize * pFrame->nb_samples * pFrame->ch_layout.nb_channels);}// 清空编码器,防止缓冲区有数据int ret = avcodec_send_frame(pCodecCtx, nullptr);while (ret >= 0){ret = avcodec_receive_packet(pCodecCtx, packet);if (ret < 0)break;writeADTSHeader(pOutFile, pCodecCtx, packet->size); // 写入ADTS头pOutFile.write((char *)packet->data, packet->size); // 写入数据av_packet_unref(packet); // 释放数据包}// 释放资源av_frame_free(&pFrame);av_packet_free(&packet);avcodec_free_context(&pCodecCtx);pInFile.close();pOutFile.close();av_free(pBuffer);printf("转换完成\n");
}
视频格式
H264
H264从1999年开始,到2003年形成草案,最后在2007年定稿有待核实。H264是MPG-4标准所定义的最新编码格式,同时也是技术含量最高、代表最新技术水平的视频编码格式之一,在ITU的标准称为H.264,在MPEG的标准里是MPEG-4的一个组成部分(MPEG-4 Part10),又叫Advanced Video Codec,因此常常被称为MPG-4 AVC或者直接叫AVC。H264视频格式是经过有损压缩的,但在技术上尽可能做的降低存储体积下获得较好图像质量和低带宽图像快速传输。压缩比大约是1%
GOP图像组
GOP(Group of Pictures)顾名思义,就是一组图片,在实际操作中,就是一组完整的视频帧,怎么叫做完整的视频帧?就是说一个GOP拿出来,必须能够完整地播放、显示。也就是两个I帧之间的间隔。
I帧,P帧,B帧
- I帧(intraframe frame),也叫关键帧,采用帧内压缩技术,将其解压出来就是一张完整的图片。GOP中第一个I帧也被称为IDR帧。
- P帧(forward Predicted frame),向前参考帧,也叫预测帧。压缩时,只参考前面已经处理的帧,采用帧间压缩技术,它只占用I帧一半大小。
- B帧(Bidirectionally predicted frame),双向参考帧,也叫双向预测帧。压缩时既参考前面已经处理的帧,也参考后面的帧,使用帧间压缩技术。它占I帧1/4大小
IDR帧和I帧的区别
一个序列的第一个图像叫做IDR帧(立即刷新图像),IDR帧都是I帧。I帧和IDR帧都是用帧内预测。不用参考任何帧但是之后的P帧和B帧是有可能参考这个I帧之前的帧的。IDR就不允许这样。
其核心作用是,当解码器解码到IDR帧时,立即将参考帧队列清空,将已解码的数据全部输出或抛弃,重新查找参数集,开始一个新的序列。这样,使错误不致传播,从IDR帧开始算新的序列,开始编码。
H264压缩技术
H264首先会把一张图片分成一个个的片,片里面又会分成一个个宏块。
宏块是视频压缩操作的基本单元。无论是帧内压缩还是帧间压缩,它们都以宏块为单位。
以下面的图片为例
划分片
划分宏块
H264默认是用16x16 大小的区域作为一个宏块,也可以划分成8x8大小。划分好宏块后,计算宏块的像素值
划分子块
H264对比较平坦的图像使用16x16大小的宏块。但是为了更高的压缩率,还可以再这个宏块上划分出更小的子块,子块的大小可以是8x16,16x8,8x8,4x8,8x4,4x4非常的灵活
上图红框内的16x16宏块大部分是蓝色背景,而三只鹰的部分图像被划在了该宏块内,为了更好地处理三只鹰部分的图像, 就在这个宏块内又划分出了多个子块。
这样再经过帧内压缩,可以得到更高效的数据。
常见的宏块尺寸如下:
帧内压缩也称为空间压缩,当压缩一帧图像时,仅考虑本帧的数据而不考虑相邻帧之间的冗余信息,这实际上与静态图像压缩类似。帧内一般采用有损压缩算法,由于帧内压缩是编码一个完整的图像,所以可以独立地解码、显示。帧内压缩一般达不到很高的压缩,跟编码JPEG差不多。
帧间压缩,相邻几帧的数据有很大的相关性,或者说前后两帧信息变化很小的特点。也即连续的视频其相邻帧之间具有冗余信息,根据这一特性,压缩相邻帧之间的冗余量就可以进一步提高压缩量,减小压缩比。帧间压缩也称为时间压缩,它通过比较时间轴上不同帧之间的数据进行压缩。帧间压缩一般是无损的,帧差值算法是一种典型的时间压缩法,它通过比较本帧与相邻帧之间的差异,仅记录本帧与其相邻帧的差值,这样可以大大减少数据量。
帧内预测
如图所示,左侧显示的是H264对于帧内预测提供的9种模式
9种帧内预测模式
- 第一种是纵列的即垂直模式,左边以及上边都是已经推测出来的宏块,这个时候就可以将下面4x4的宏块预测出来,对于垂直模式来说,上边A对应下面的一整列都是A,第二个B,对应一列下面也都是B,以此类推。
- 第二种是横向的水平模式,还是左边以及上边都是都是预测出来的数据,预测结果第一行是I,第二行是j,以此类推。
- 第三种是求平均值,在目标的4x4宏块内都是ABCD加上IJKL的求平均值,例如宏块内的小a,它的值是ABCD加IJKL求平均值得出,也就是下面每一个4x4内的像素值都是一样的。需要注意的是:像素a对应的值是(ABCD+IJKL)/2,而不是(A+I)/2。
H264压缩级别
H264 Profile 是对视频压缩特性的描述,Profile越高,说明采用了越高级的压缩特性。
H264视频级别
H264 Level是对视频的描述,Level越高,视频的码率、分辨率、FPS越高。
H264码流结构
H264的两种码流格式,它们分别为:字节流格式和RTP包格式。
- 字节流格式(Annexb):这是在H264官方协议文档中规定的格式,处于文档附录B(Annex-B Byte stream format)中,所以它也成为了大多数编码器,默认的输出格式。它的基本数据单位为NAL单元,也即NALU。为了从字节流中提取出NALU,协议规定,在每个NALU的前面加上起始码:0x000001或0x00000001
- RTP包格式:这种格式并没有在H264中规定,那为什么还要介绍它呢?是因为在H264的官方参考软件JM里,有这种封装格式的实现。在这种格式中,NALU并不需要起始码Start_Code来进行识别,而是在NALU开始的若干字节(1,2,4字节),代表NALU的长度。
码流分层图
接下来我们主要讲字节流格式,H264码流分为2层,NAL层和VCL层
- NAL(Network Abstraction Layer)层,视频数据网络抽象层,是H264码流的基本单元。
- VCL(Video Coding Layer)层,视频数据编码层,VCL结构关系如下图所示,在视频帧序列由图像组成,图像中包含分片,分片里面包含宏块,宏块里面又包含子块
接下来我们来看NALU,NALU由NALU头部和NALU载荷 (Raw Byte Sequence Payload ,RBSP) 组成,其中NALU头部包括禁止位 (forbidden_zero_bit)、参考标识(nal_ref_idc)和NALU类型(nal_uint_type)等信息。NALU载荷则是包含实际视频数据的原始字节序列。
对于一个H264裸流,它就是由一个起始码(StartCode)和一个NALU单元组成
由上图可以知道,NALU单元 = NALU Header + RBSP ,其中RBSP原始字节序列载荷,即在SODB的后面添加了trailing bit,即一个bit 1和若干个bit0,以使字节对齐,SODB(String of Data Bits)原始数据比特流,原始数据比特流长度不一定是8的倍数,故需要补齐,他是由VCL层产生的
在每一个I帧之前,都会存在一个SPS和PPS。
- SPS(Sequence Parameter Set) 序列参数集,作用于一连串连续的视频图像。如seq_parameter_set_id、帧数及POC(picture order count)的约束、参考帧数目、解码图像尺寸和熵编码模式选择标识等
- PPS(Picture Parameter Set)图像参数集,作用于视频序列中的图像。如pic_parameter_set_id、熵编码模式选择标识、片组数目、初始量化参数和去方块滤波系数调整标识等
SPS
SPS结构如下:
- profile_idc:标识当前H264码流的profile。我们知道H264中定义了三种常用的档次profile:
- 基准档次:baseline profile
- 主要档次:main profile
- 扩展档次:extended profile
在H264的SPS中,第一个字节表示profile_idc,根据profile_idc的值可以确定码流符合哪一种档次。判断规律为: - profile_idc = 66 → baseline profile
- profile_idc = 77 → main profile
- profile_idc = 88 → extended profile
在新版的标准中,还包括了High、High 10、High 4:2:2、High 4:4:4、High 10 Intra、High4:2:2 Intra、High 4:4:4 Intra、CAVLC 4:4:4 Intra等,每一种都由不同的profile_idc表示。另外,constraint_set0_flag ~ constraint_set5_flag是在编码的档次方面对码流增加的其他一些额外限制性条件。
- level_idc:标识当前码流的Level。编码的Level定义了某种条件下的最大视频分辨率、最大视频帧率等参数,码流所遵从的Level由level_idc指定。
- seq_parameter_set_id:表示当前的序列参数集的id,通过该id值,图像参数集pps可以引用其代表的sps中的参数。
- log2_max_frame_num_minus4:用于计算MaxFrameNum的值。计算公式为 M a x F r a m e N u m = 2 l o g 2 m a x f r a m e n u m m i n u s 4 + 4 MaxFrameNum = 2^{log2_max_frame_num_minus4 + 4} MaxFrameNum=2log2maxframenumminus4+4。MaxFrameNum是frame_num的上限值,frame_num是图像序号的一种表示方法,在帧间编码中常用作一种参考帧标记的手段。
- pic_order_cnt_type:表示解码picture order count(POC)的方法。POC是另一种计量图像序号的方式,与frame_num有着不同的计算方法。该语法元素的取值为0、1或2
- log2_max_pic_order_cnt_lsb_minus4:用于计算MaxPicOrderCntLsb的值,该值表示POC的上限。计算方法为 M a x P i c O r d e r C n t L s b = 2 l o g 2 m a x p i c o r d e r c n t l s b m i n u s 4 + 4 MaxPicOrderCntLsb = 2^{log2_max_pic_order_cnt_lsb_minus4 + 4} MaxPicOrderCntLsb=2log2maxpicordercntlsbminus4+4。
-
- max_num_ref_frames:用于表示参考帧的最大数目。
-
gaps_in_frame_num_value_allowed_flag:标识位,说明frame_num中是否允许不连续的值。
-
pic_width_in_mbs_minus1:用于计算图像的宽度。单位为宏块个数,因此图像的实际宽度为:
f r a m e w i d t h = 16 × ( p i c w i d t h i n m b s m i n u s 1 + 1 ) frame_width = 16 \times (pic_width_in_mbs_minus1 + 1) framewidth=16×(picwidthinmbsminus1+1)
-
pic_height_in_map_units_minus1:使用PicHeightInMapUnits来度量视频中一帧图像的高度。PicHeightInMapUnits并非图像明确的以像素或宏块为单位的高度,而需要考虑该宏块是帧编码或场编码。PicHeightInMapUnits的计算方式为:
P i c H e i g h t I n M a p U n i t s = p i c h e i g h t i n m a p u n i t s m i n u s 1 + 1 PicHeightInMapUnits = pic_height_in_map_units_minus1 + 1 PicHeightInMapUnits=picheightinmapunitsminus1+1
-
frame_mbs_only_flag:标识位,说明宏块的编码方式。当该标识位为0时,宏块可能为帧编码或场编码;该标识位为1时,所有宏块都采用帧编码。根据该标识位取值不同,PicHeightInMapUnits的含义也不同,为0时表示一场数据按宏块计算的高度,为1时表示一帧数据按宏块计算的高度。
按照宏块计算的图像实际高度FrameHeightInMbs的计算方法为:
F r a m e H e i g h t I n M b s = ( 2 − f r a m e m b s o n l y f l a g ) × P i c H e i g h t I n M a p U n i t s FrameHeightInMbs = ( 2 − frame_mbs_only_flag ) \times PicHeightInMapUnits FrameHeightInMbs=(2−framembsonlyflag)×PicHeightInMapUnits
-
mb_adaptive_frame_field_flag:
-
标识位,说明是否采用了宏块级的帧场自适应编码。当该标识位为0时,不存在帧编码和场编码之间的切换;当标识位为1时,宏块可能在帧编码和场编码模式之间进行选择。
-
direct_8x8_inference_flag:标识位,用于B_Skip、B_Direct模式运动矢量的推导计算。
-
frame_cropping_flag:标识位,说明是否需要对输出的图像帧进行裁剪。
-
vui_parameters_present_flag:标识位,说明SPS中是否存在VUI信息。
-
PPS
PPS结构如下:
其中的每一个语法元素及其含义如下:
-
pic_parameter_set_id:表示当前PPS的id。某个PPS在码流中会被相应的slice引用,slice引用PPS的方式就是在Slice header中保存PPS的id值。该值的取值范围为[0,255]。
-
seq_parameter_set_id:表示当前PPS所引用的激活的SPS的id。通过这种方式,PPS中也可以取到对应SPS中的参数。该值的取值范围为[0,31]。
-
entropy_coding_mode_flag:熵编码模式标识,该标识位表示码流中熵编码/解码选择的算法。对于部分语法元素,在不同的编码配置下,选择的熵编码方式不同。例如在一个宏块语法元素中,宏块类型mb_type的语法元素描述符为“ue(v)| ae(v)”,在baseline profile等设置下采用指数哥伦布编码,在main profile等设置下采用CABAC编码。
标识位entropy_coding_mode_flag的作用就是控制这种算法选择。当该值为0时,选择左边的算法,通常为指数哥伦布编码或者CAVLC;当该值为1时,选择右边的算法,通常为CABAC。
-
bottom_field_pic_order_in_frame_present_flag:标识位,用于表示另外条带头中的两个语法元素delta_pic_order_cnt_bottom和delta_pic_order_cn是否存在的标识。这两个语法元素表示了某一帧的底场的POC的计算方法。
-
num_slice_groups_minus1:表示某一帧中slice group的个数。当该值为0时,一帧中所有的slice都属于一个slice group。slice group是一帧中宏块的组合方式,定义在协议文档的3.141部分。
-
num_ref_idx_l0_default_active_minus1、num_ref_idx_l0_default_active_minus1表示当Slice Header中的num_ref_idx_active_override_flag标识位为0时,P/SP/Bslice的语法元素num_ref_idx_l0_active_minus1和num_ref_idx_l1_active_minus1的默认值。
-
weighted_pred_flag:标识位,表示在P/SP slice中是否开启加权预测。
-
weighted_bipred_idc:表示在B Slice中加权预测的方法,取值范围为[0,2]。0表示默认加权预测,1表示显式加权预测,2表示隐式加权预测。
-
pic_init_qp_minus26和pic_init_qs_minus26:表示初始的量化参数。实际的量化参数由该参数、slice header中的 s l i c e q p d e l t a ÷ s l i c e q s d e l t a slice_qp_delta \div slice_qs_delta sliceqpdelta÷sliceqsdelta计算得到。
-
chroma_qp_index_offset:用于计算色度分量的量化参数,取值范围为[-12,12]。
-
deblocking_filter_control_present_flag:标识位,用于表示Slice header中是否存在用于去块滤波器控制的信息。当该标志位为1时,slice header中包含去块滤波相应的信息;当该标识位为0时,slice header中没有相应的信息。
-
constrained_intra_pred_flag:若该标识为1,表示I宏块在进行帧内预测时只能使用来自I和SI类型宏块的信息;若该标识位0,表示I宏块可以使用来自Inter类型宏块的信息。
-
redundant_pic_cnt_present_flag:标识位,用于表示Slice header中是否存在redundant_pic_cnt语法元素。当该标志位为1时,slice header中包含redundant_pic_cnt;当该标识位为0时,slice header中没有相应的信息。
解码视频
void Widget::H264Decode()
{char errbuf[1024];const char *infile = "../../source/video.h264";const char *outfile = "../../output/out.yuv";AVFormatContext *fmt_ctx = nullptr; // 输入文件上下文// 打开输入文件if (avformat_open_input(&fmt_ctx, infile, nullptr, nullptr) < 0){av_log(NULL, AV_LOG_ERROR, "无法打开资源文件 %s\n", infile);return;}// 获取资源信息if (avformat_find_stream_info(fmt_ctx, nullptr) < 0){av_log(NULL, AV_LOG_ERROR, "无法获取资源信息\n");return;}// 查找视频流int video_stream_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);if (video_stream_index < 0){av_log(NULL, AV_LOG_ERROR, "无法找到视频流\n");return;}// 查找解码器const AVCodec *codec = avcodec_find_decoder(fmt_ctx->streams[video_stream_index]->codecpar->codec_id);if (!codec){av_log(NULL, AV_LOG_ERROR, "无法找到解码器\n");return;}// 创建解码器上下文AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);if (!codec_ctx){av_log(NULL, AV_LOG_ERROR, "无法创建解码器上下文\n");return;}// 填充解码器上下文if (avcodec_parameters_to_context(codec_ctx, fmt_ctx->streams[video_stream_index]->codecpar) < 0){av_log(NULL, AV_LOG_ERROR, "无法填充解码器上下文\n");return;}// 打开解码器if (avcodec_open2(codec_ctx, codec, nullptr) < 0){av_log(NULL, AV_LOG_ERROR, "无法打开解码器\n");return;}// 打开输出文件std::ofstream outfileSream(outfile, std::ios::binary);if (!outfileSream.is_open()){av_log(NULL, AV_LOG_ERROR, "无法打开输出文件\n");return;}// 读取数据包AVPacket *pkt = av_packet_alloc(); // 分配数据包AVFrame *frame = av_frame_alloc(); // 分配帧int ret = 0;while (av_read_frame(fmt_ctx, pkt) >= 0){// 解码ret = avcodec_send_packet(codec_ctx, pkt); // 发送数据包到解码器if (pkt->stream_index != video_stream_index)continue; // 不是视频流就跳过if (ret < 0){av_log(NULL, AV_LOG_ERROR, "解码失败\n");break;}while (ret >= 0){ret = avcodec_receive_frame(codec_ctx, frame); // 从解码器接收解码后的帧if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)break;else if (ret < 0){av_log(NULL, AV_LOG_ERROR, "解码失败\n");break;}// 写入文件for (int i = 0; i < frame->height; i++){outfileSream.write((char *)frame->data[0] + i * frame->linesize[0], frame->width); // 写入Y分量}for (int i = 0; i < frame->height / 2; i++){outfileSream.write((char *)frame->data[1] + i * frame->linesize[1], frame->width / 2); // 写入U分量}for (int i = 0; i < frame->height / 2; i++){outfileSream.write((char *)frame->data[2] + i * frame->linesize[2], frame->width / 2); // 写入V分量}av_frame_unref(frame); // 清空帧}av_packet_unref(pkt); // 清空数据包}// 清空编码器ret = avcodec_send_packet(codec_ctx, nullptr);while (ret >= 0){ret = avcodec_receive_frame(codec_ctx, frame);if (ret < 0)break;// 写入文件for (int i = 0; i < frame->height; i++){outfileSream.write((char *)frame->data[0] + i * frame->linesize[0], frame->width); // 写入Y分量}for (int i = 0; i < frame->height / 2; i++){outfileSream.write((char *)frame->data[1] + i * frame->linesize[1], frame->width / 2); // 写入U分量}for (int i = 0; i < frame->height / 2; i++){outfileSream.write((char *)frame->data[2] + i * frame->linesize[2], frame->width / 2); // 写入V分量}}qDebug() << "解码完成";// 释放资源av_frame_free(&frame);av_packet_free(&pkt);outfileSream.close();avcodec_free_context(&codec_ctx);avformat_close_input(&fmt_ctx);
}
编码视频
void Widget::H264Encode()
{char errorBuffer[1024];const char *inputFile = "../../source/video.yuv";const char *outputFile = "../../output/out.h264";// 查找H264编码器const AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_H264);if (!codec){qDebug() << "找不到编码器";return;}// 创建编码器上下文AVCodecContext *codecContext = avcodec_alloc_context3(codec);if (!codecContext){qDebug() << "创建编码器上下文失败";return;}// 设置编码器参数codecContext->profile = AV_PROFILE_H264_HIGH_444; // 设置编码器压缩级别codecContext->level = 50; // 设置编码器视频质量级别,50表示5.0codecContext->width = 1280; // 设置编码器视频宽度codecContext->height = 720; // 设置编码器视频高度codecContext->pix_fmt = AV_PIX_FMT_YUV420P; // 设置编码器视频像素格式codecContext->time_base = AVRational{1, 30}; // 设置编码器时间基准codecContext->framerate = AVRational{30, 1}; // 设置编码器帧率codecContext->gop_size = 30; // 设置编码器I帧间隔codecContext->max_b_frames = 3; // 设置编码器B帧最大数量codecContext->keyint_min = 3; // 设置编码器最小I帧间隔codecContext->has_b_frames = 1; // 设置编码器是否支持B帧codecContext->refs = 3; // 设置编码器参考帧数量codecContext->bit_rate = 2000000; // 设置编码器比特率// 打开编码器CoUninitialize(); // 初始化COM库,头文件<objbase.h>,这里防止报错COM must not be in STA modeint ret = avcodec_open2(codecContext, codec, nullptr);if (ret < 0){av_strerror(ret, errorBuffer, sizeof(errorBuffer));qDebug() << "打开编码器失败:" << errorBuffer;return;}// 打开输入文件FILE *inputFilePtr = fopen(inputFile, "rb");if (!inputFilePtr){qDebug() << "Could not open input file";return;}// 打开输出文件FILE *outputFilePtr = fopen(outputFile, "wb");if (!outputFilePtr){qDebug() << "Could not open output file";return;}// 创建AVFrame对象AVFrame *frame = av_frame_alloc();if (!frame){qDebug() << "Could not allocate video frame";return;}frame->format = codecContext->pix_fmt;frame->width = codecContext->width;frame->height = codecContext->height;// 分配AVFrame对象缓冲区ret = av_frame_get_buffer(frame, 0);if (ret < 0){av_strerror(ret, errorBuffer, sizeof(errorBuffer));qDebug() << "不能为帧分配缓冲区:" << errorBuffer;return;}// 创建AVPacket对象AVPacket *packet = av_packet_alloc();if (!packet){qDebug() << "不能分配数据包";return;}// 读取输入文件数据int ySize = codecContext->width * codecContext->height;int frameCount = 0;while (!feof(inputFilePtr)){fread(frame->data[0], ySize, 1, inputFilePtr); // Y分量fread(frame->data[1], ySize / 4, 1, inputFilePtr); // U分量fread(frame->data[2], ySize / 4, 1, inputFilePtr); // V分量frame->pts = frameCount; // 设置帧时间戳// 编码一帧数据ret = avcodec_send_frame(codecContext, frame);if (ret < 0){av_strerror(ret, errorBuffer, sizeof(errorBuffer));qDebug() << "编码失败:" << errorBuffer;return;}while (ret >= 0){ret = avcodec_receive_packet(codecContext, packet);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)break;else if (ret < 0){av_strerror(ret, errorBuffer, sizeof(errorBuffer));qDebug() << "编码失败:" << errorBuffer;return;}// 写入输出文件fwrite(packet->data, packet->size, 1, outputFilePtr);av_packet_unref(packet);}frameCount++; // 帧计数器加1}// 清空编码器ret = avcodec_send_frame(codecContext, nullptr);while (ret >= 0){ret = avcodec_receive_packet(codecContext, packet);if (ret < 0)break;fwrite(packet->data, packet->size, 1, outputFilePtr); // 写入输出文件}// 释放资源av_packet_free(&packet);av_frame_free(&frame);avcodec_free_context(&codecContext);fclose(inputFilePtr);fclose(outputFilePtr);qDebug() << "编码完成!";
}
相关文章:
FFmpeg 编码和解码
文章目录 音频格式AACADIF音频数据交换格式ADTS音频数据传输流 音频解码音频编码 视频格式H264GOP图像组I帧,P帧,B帧H264压缩技术H264压缩级别H264视频级别H264码流结构SPSPPS 解码视频编码视频 音频格式 AAC AAC全称 Advanced Audio Coding࿰…...
【Ubuntu】Ubuntu server 18.04 搭建Slurm并行计算环境(包含NFS)
Ubuntu server 18.04 搭建Slurm并行计算环境(包含NFS) 一、Munge 认证模块 1.1、安装 munge 主节点和子节点都安装munge #安装 sudo apt update && sudo apt install munge libmunge-dev#设置开机启动 sudo systemctl enable munge sudo syste…...
WPF 绘制过顶点的圆滑曲线 (样条,贝塞尔)
在一个WPF项目中要用到样条曲线,必须过顶点,圆滑后还不能太走样,捣鼓一番,发现里面颇有玄机,于是把我多方抄来改造的方法发出来,方便新手: 如上图,看代码吧: ----------…...
Spring 的不同事务传播行为
目录 Spring 的不同事务传播行为 PROPAGATION_REQUIRES_NEW事务传播行为什么情况下会使用? 一、PROPAGATION_REQUIRES_NEW的含义 二、使用场景 三、注意事项 PROPAGATION_NESTED事务传播行为什么情况下会使用? 一、PROPAGATION_NESTED的含义 二、使用场景 三、嵌套事…...
PlantUML 时序图 基本例子
基本的例子 序列-> 用于绘制两个参与者之间的信息。参与者不必明确声明。 要有一个点状的箭头,就用--> 也可以用<- 和<-- 。这不会改变绘图,但可能提高可读性。注意,这只适用于顺序图,其他图的规则不同。 plantum…...
QILSTE H8-C414SY高亮黄光LED灯珠 发光二极管LED
在电子组件的复杂世界中,H8-C414SY型号的LED以其精确的技术参数和卓越的性能,成为了工程师和技术人员不可忽视的选择。本文将通过对这款高亮黄光LED的技术参数进行深入分析,增加文本的复杂性和突发性,以提供一份详尽的技术参考。 …...
git clone 和 conda 换源
文章目录 git clone 通过 sshconda 创建虚拟环境通过 env.yml 文件conda 换源 git clone 通过 ssh git clone ssh://用户名IP地址:/仓库名字.gitconda 创建虚拟环境通过 env.yml 文件 conda env create -f environment.ymlconda 换源 Step 1 生成 .bashrc 文件在家目录下。…...
Spring Boot 多数据源解决方案:dynamic-datasource-spring-boot-starter 的奥秘(下)
在上一篇博客《Spring Boot 多数据源解决方案:dynamic-datasource-spring-boot-starter 的奥秘》介绍了dynamic-datasource-spring-boot-starter的自动配置类和配置属性类之后,本文继续来剖析多数据源是如何切换的,什么时候切换的。 前文中提…...
移动 APP 设计规范参考
一、界面设计规范 布局原则: 内容优先:以内容为核心进行布局,突出用户需要的信息,简化页面导航,提升屏幕空间利用率.一致性:保持界面元素风格一致,包括颜色、字体、图标等,使用户在…...
yolov6算法及其改进
yolov6算法及其改进 1、YOLOV6简介2、RepVGG重参思想3、YOLOv6架构改进3.1、Backbone方面3.2、SPP改进3.3、Neck改进3.4、Head改进 4、正负样本匹配与损失函数4.1、TaskAligned样本匹配4.2、VFL Loss分类损失函数4.3、SIOU损失函数4.4、DFL损失函数 1、YOLOV6简介 YOLOv6设计主…...
java自定义注解对枚举类型参数的校验
目录 1.前提准备条件 1.1 pom.xml文件依赖: 1.2 枚举类: 1.3 controller接口: 1.4 实体参数: 1.5 knife4j的配置 2.实现要求 3.实现步骤 3.1 自定义注解类: 3.2 使用注解: 3.3 添加注解校验类: …...
K8S-LLM:用自然语言轻松操作 Kubernetes
在 Kubernetes (K8s) 的日常管理中,复杂的命令行操作常常让开发者感到头疼。无论是部署应用、管理资源还是调试问题,都需要记住大量的命令和参数。Kubernetes 作为容器编排的行业标准,其强大的功能伴随着陡峭的学习曲线和复杂的命令行操作。这…...
【GO基础学习】gin的使用
文章目录 模版使用流程参数传递路由分组数据解析和绑定gin中间件 模版使用流程 package mainimport ("net/http""github.com/gin-gonic/gin" )func main() {// 1.创建路由r : gin.Default()// 2.绑定路由规则,执行的函数// gin.Context&#x…...
【PCIe 总线及设备入门学习专栏 4.5 -- PCIe Message and PCIe MSI】
文章目录 PCIe Message 与 MSIPCIe Message 和 MSI 的作用与关系MSI 的配置与寄存器MSI 和 ARM GIC 的关系示例:MSI 在 ARM GIC 的实际应用总结 PCIe Message 与 MSI 本文将介绍 PCIe message 的作用以及message 与 MSI 的关系,再介绍 MSI 如何配置以及…...
sklearn_pandas.DataFrameMapper的用法
文章目录 介绍主要作用基本用法示例对不同列应用不同的转换器对多列应用相同的转换器输出为 Pandas DataFrame 注意事项转换器的适用性:输出格式:与 scikit-learn 的兼容性: 介绍 DataFrameMapper 是 sklearn-pandas 库中的一个工具…...
【2024年-7月-27日-开源社区openEuler实践记录】剖析 elease - management:优化软件发布流程的开源方案
开篇介绍 大家好,我是 fzr123,在软件开发流程管控领域探索许久,今天要给大家详细说说release - management这个极具价值的开源项目。在软件开发的生命周期里,发布管理至关重要,它关乎着软件能否稳定、高效且按时交付&…...
CPT203 Software Engineering 软件工程 Pt.1 概论和软件过程(中英双语)
文章目录 1.Introduction1.1 What software engineering is and why it is important(什么是软件工程,为什么它很重要)1.1 We can’t run the modern world without software(我们的世界离不开软件)1.1.1 What is Soft…...
Mysql数据库Redo日志和Undo日志的理解
数据库redo日志和undo日志 1、redo日志1.1 redo日志的作用1.1.1 不使用redo日志的问题1.1.2 使用redo日志的好处 1.2 redo日志刷盘策略 2、undo日志2.1 undo日志的作用2.2 undo日志的简要生成过程 1、redo日志 事务的4大特性(ACID):原子性、…...
大厂高频总线协议面试题及参考答案(几百家面试题挑选最高频精华)
目录 请介绍一下 SPI 总线协议及其工作原理,包括 SPI 有哪四种模式以及四根线的电气特性是什么? SPI 通信的波特率是多少,时钟来源是什么?SPI 的帧长度和数据格式是怎样的? 请简述 IIC 协议及其工作原理,包括 IIC 协议最多能挂载多少个从设备? IIC 总线上挂不同的设备…...
使用策略模式时的一个生效问题
策略模式的替换场景: 1:产品有默认策略A,B,项目扩展策略C,此为正常扩展。 2:产品有默认策略A,B,项目需要改写策略B,此为项目替换默认策略。 3:产品有默认策略A,B,项目扩展策略C,产品需要反向扩展…...
活动预告 |【Part2】 Azure 在线技术公开课:迁移和保护 Windows Server 和 SQL Server 工作负载
课程介绍 通过 Microsoft Learn 免费参加 Microsoft Azure 在线技术公开课,掌握创造新机遇所需的技能,加快对 Microsoft 云技术的了解。参加我们举办的“迁移和保护 Windows Server 和 SQL Server 工作负载”活动,了解 Azure 如何为将工作负载…...
关于 PCB线路板细节锣槽问题 的解决方法
若该文为原创文章,转载请注明原文出处 本文章博客地址:https://hpzwl.blog.csdn.net/article/details/144783817 长沙红胖子Qt(长沙创微智科)博文大全:开发技术集合(包含Qt实用技术、树莓派、三维、OpenCV…...
机器学习特征选择
一、特征选择概述 在实际的数据集中,往往包含了大量的特征,但并非所有特征都对我们要预测的目标变量(如分类任务中的类别标签,回归任务中的数值目标)有积极作用。有些特征可能携带的信息量极少,甚至会引入…...
第R5周:天气预测
🍨 本文为🔗365天深度学习训练营 中的学习记录博客🍖 原作者:K同学啊 任务说明:该数据集提供了来自澳大利亚许多地点的大约 10 年的每日天气观测数据。你需要做的是根据这些数据对RainTomorrow进行一个预测,…...
【每日学点鸿蒙知识】ets匿名类、获取控件坐标、Web显示iframe标签、软键盘导致上移、改变Text的背景色
1、HarmonyOS ets不支持匿名类吗? 不支持,需要显式标注对象字面量的类型,可以参考以下文档:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/typescript-to-arkts-migration-guide-V5#%E9%9C%80%E8%A6%81%E6%…...
【Vim Masterclass 笔记02】第3章:Vim 核心知识 + L08:Vim 核心浏览命令 + L09:Vim 核心浏览命令同步练习
文章目录 Section 3:Vim Essentials(Vim 核心知识)S03L08 Essential Navigation Commands1 光标的上下左右移动2 上 / 下翻页3 基于单词前移4 基于单词后移5 重新定位视图中的文本(页面重绘)6 定位到所在行的行首7 光标…...
Spring Boot教程之四十:使用 Jasypt 加密 Spring Boot 项目中的密码
如何使用 Jasypt 加密 Spring Boot 项目中的密码 在本文中,我们将学习如何加密 Spring Boot 应用程序配置文件(如 application.properties 或 application.yml)中的数据。在这些文件中,我们可以加密用户名、密码等。 您经常会遇到…...
unity 按钮发送数据到服务器端
1. canvas 明显大于 main camera显示范围 按钮设置 reset 2 设置场景背景图片 2. 如何设置 sense 与背景图大小一致 stretch 没有生效 手动拖拽到 camera 大小 3 将button 按钮拖拽到背景image 中...
【TG\SE二次开发】天工CAD二次开发-c++模板介绍
VS的安装的环境: 1. Visual Studio EnterPrise 2022版本 2. 涉及到的工作负荷: 使用C的桌面开发、通用Windows平台开发 3. 特别要求的单个组件: 适用于最新的v143生成工具的CATL(x86和x64)组件、适用于最新的v143生…...
C语言预处理
预处理 C语言的编译步骤 预处理编译汇编链接 C语言的预处理 预处理就是在源文件编译之前,所进行的一部分预备操作,这部分操作是由预处理程序自动完成;当源文件在编译时,编译器会自动调用预处理程序来完成预处理操作执行的解析…...
DPIN基金会在曼谷发布全球去中心化GPU算力网络计划
12月12日,DPIN基金会在泰国曼谷举行的“DPIN—AIDePIN全球共识发布会”上,展示了其构建全球去中心化GPU算力网络的宏伟蓝图与愿景。DPIN基金会致力于开发一个基于人工智能与去中心化物理基础设施网络(DePIN)的高性能计算平台&…...
ONNX Runtime gpu版本安装
ONNX Runtime版本与cudatoolkit版本对应关系:NVIDIA - CUDA | onnxruntime onnx runtime发的版本:Releases microsoft/onnxruntime onnx runtime 官网:ONNX Runtime | Home onnx和onnx runtime版本对应关系:Compatibility | o…...
深入理解 MVCC 与 BufferPool 缓存机制
深入理解 MVCC 与 BufferPool 缓存机制 在 MySQL 数据库中,MVCC(Multi-Version Concurrency Control)多版本并发控制机制和 BufferPool 缓存机制是非常重要的概念,它们对于保证数据的一致性、并发性以及提升数据库性能起着关键作用…...
RockyLinux介绍及初始化
文章目录 一、背景二、下载 RockyLinux9 镜像三、环境初始化四、安装 Docker 环境 一、背景 这里讲一个小故事: 我们都知道Linux 内核是由芬兰计算机科学家林纳斯托瓦兹 (Linus Torvalds) 于 1991 年首次开发的,随后有一个非常重要的公司RetHat成立&am…...
Python中切片操作符
在Python中,切片是一种操作符,允许你获取序列(如列表、元组、字符串)的一部分。切片操作返回序列的一个子集,这个子集是一个新的对象,与原始序列是独立的。切片操作通常用于列表、元组、字符串等。 切片语…...
python -【es】基本使用
一. 前言 在Python中使用Elasticsearch(ES)通常涉及安装Elasticsearch的Python客户端库,然后通过该库与Elasticsearch集群进行交互。 二. 基本使用 1. 安装Elasticsearch Python客户端库 首先,你需要安装elasticsearch库。你可…...
JVM实战—5.G1垃圾回收器的原理和调优
大纲 1.G1垃圾回收器的工作原理 2.G1分代回收原理—性能为何比传统GC好 3.使用G1垃圾回收器时应如何设置参数 4.如何基于G1垃圾回收器优化性能 5.问题汇总 1.G1垃圾回收器的工作原理 (1)ParNew CMS的组合有哪些痛点 (2)G1垃圾回收器 (3)G1如何实现垃圾回收的停顿时间是…...
Python爬虫(selenium)从网站获取信息并存入数据库(mysql)
简介: 在本篇博客中,我们将介绍如何使用Python编写一个简单的网络爬虫,从指定网站上获取图书信息,并将这些信息存入数据库。这个项目涉及到Python编程、selenium爬虫技术以及数据库操作等内容,适合对这些领域感兴趣的初…...
spring中使用@Validated,什么是JSR 303数据校验,spring boot中怎么使用数据校验
文章目录 一、JSR 303后台数据校验1.1 什么是 JSR303?1.2 为什么使用 JSR 303? 二、Spring Boot 中使用数据校验2.1 基本注解校验2.1.1 使用步骤2.1.2 举例Valid注解全局统一异常处理 2.2 分组校验2.2.1 使用步骤2.2.2 举例Validated注解Validated和Vali…...
RabbitMQ中的异步Confirm模式:提升消息可靠性的利器
在现代分布式系统中,消息队列(Message Queue)扮演着至关重要的角色,它能够解耦系统组件、提高系统的可扩展性和可靠性。RabbitMQ作为一款广泛使用的消息队列中间件,提供了多种机制来确保消息的可靠传递。其中ÿ…...
C++ 设计模式:代理模式(Proxy Pattern)
链接:C 设计模式 链接:C 设计模式 - 门面模式 链接:C 设计模式 - 中介者 链接:C 设计模式 - 适配器 代理模式(Proxy Pattern)是一种结构型设计模式,它为其他对象提供一种代理以控制(…...
41.1 预聚合提速实战项目之需求分析和架构设计
本节重点介绍 : 需求分析架构设计 需求分析 使用预聚合提速查询并且降低高基数查询对后端的压力用户无需变更grafana上的查询语句,后端自动替换效果图 架构设计 架构图 解决方案说明 heavy_query对用户侧表现为查询速度慢在服务端会导致资源占用过多甚至打挂…...
学习路之VScode--自定义按键写注释(插件)
1. 安装 "KoroFileHeader" 插件 首先,在 VScode 中搜索并安装名为 "KoroFileHeader" 的插件。你可以通过在扩展商店中搜索插件名称来找到并安装它。 2. 进入 VScode 设置页面 点击 VScode 左下角的设置图标,然后选择 "设置&q…...
Web安全 - “Referrer Policy“ Security 头值不安全
文章目录 概述原因分析风险说明Referrer-Policy 头配置选项1. 不安全的策略no-referrer-when-downgradeunsafe-url 2. 安全的策略no-referreroriginorigin-when-cross-originsame-originstrict-originstrict-origin-when-cross-origin 推荐配置Nginx 配置示例 在 Nginx 中配置 …...
Milvus×EasyAi:如何用java从零搭建人脸识别应用
如何从零搭建一个人脸识别应用?不妨试试原生Java人工智能算法:EasyAi Milvus 的组合拳。 本文将使用到的软件和工具包括: EasyAi:人脸特征向量提取Milvus:向量数据库用于高效存储和检索数据。 01. EasyAi:…...
CGAL windows 安装教程
1.下载源代码 CGAL官网下载https://github.com/CGAL/cgal/releases 2.下载boost库 BOOST官网下载https://www.boost.org/ 3.下载 GMP and MPFR 4.配置VS2022 头文件: 库路径 做完以上步骤,可以使用CGAL了!...
低代码开源项目Joget的研究——Joget8社区版安装部署
大纲 环境准备安装必要软件配置Java配置JAVA_HOME配置Java软链安装三方库 获取源码配置MySql数据库创建用户创建数据库导入初始数据 配置数据库连接配置sessionFactory(非必须,如果后续保存再配置)编译下载tomcat启动下载aspectjweaver移动jw…...
【Ubuntu 20.4安装截图软件 flameshot 】
步骤一: 安装命令: sudo apt-get install flameshot 步骤二: 设置快捷方式: Ubuntu20.4 设置菜单,点击 号 步骤三: 输入软件名称, 软件快捷命令(flameshot gui)&am…...
AUTOSAR 平台介绍 (R24-11新标准发布!)
AUTOSAR 平台介绍——R24-11 0引言 随着技术的不断进步和市场需求的变化,AUTOSAR在汽车行业中的重要性日益增强。R24-11版本作为AUTOSAR平台的重要更新,旨在提升系统性能、安全性和用户体验。本文将详细介绍R24-11版本的主要更新内容 1 AUTOSAR介绍 AUTOSAR汽车软件架构,旨…...
Framework开发入门(一)之源码下载
一、使用Linux操作系统的小伙伴可以跳转到官网链接按提示操作 官网源码地址:下载源代码 | Android Open Source Project 1.创建一个空目录来存放您的工作文件。为其指定一个您喜欢的任意名称: mkdir WORKING_DIRECTORYcdWORKING_DIRECTORY …...