【音视频】 FFmpeg 解码H265
一、概述
实现了使用FFmpeg读取对应H265文件,并且保存为对应的yuv文件
二、实现流程
读取文件
- 将H265/H264文件放在
build
路径下,然后指定输出为yuv格式
- 在
main
函数中读取外部参数
if (argc <= 2){fprintf(stderr, "Usage: %s <input file> <output file>\n", argv[0]);exit(0);}filename = argv[1];outfilename = argv[2];
- 打开对应的输入、输出文件
- 读取文件到缓冲区,为解码做准备
// 打开输入文件
infile = fopen(filename, "rb");
if (!infile) {fprintf(stderr, "Could not open %s\n", filename);exit(1);
}
// 打开输出文件
outfile = fopen(outfilename, "wb");
if (!outfile) {av_free(codec_ctx);exit(1);
}// 读取文件进行解码
data = inbuf;
data_size = fread(inbuf, 1, VIDEO_INBUF_SIZE, infile);
- 根据文件的后缀我们来判断该使用什么解码器ID
enum AVCodecID video_codec_id = AV_CODEC_ID_H265;
if(strstr(filename, "264") != NULL)
{video_codec_id = AV_CODEC_ID_H264;
}
else
{printf("default codec id:%d\n", video_codec_id);
}
- 因为这里的文件是H264/H265,没有使用容器封装,需要使用裸流解析器
一、裸流与封装格式的区别
-
裸流(Bare Stream)
- 指未经过容器封装(如 MP4、AVI、FLV)的原始编码数据,直接由编码后的 NALU 或字节流组成(如
.h264
、.h265
文件)。 - 特点:数据无容器头部,仅包含编码后的视频(或音频)数据,需手动识别编码单元边界。
- 指未经过容器封装(如 MP4、AVI、FLV)的原始编码数据,直接由编码后的 NALU 或字节流组成(如
-
封装格式(Container Format)
- 如 MP4、FLV 等,将编码数据(如 H.264 NALU)封装在容器中,包含文件头、索引、时间戳等元数据。
- 特点:解复用器(Demuxer)可直接从容器中提取完整的 NALU 或帧数据,无需额外解析边界。
二、为什么裸流需要解析器?
裸流中编码数据是 连续存储 的,没有容器层提供的结构化信息(如 NALU 边界、帧起始位置)。例如:
- H.264 裸流由多个 NALU 连续组成,仅通过起始码(如
00 00 01
)分隔。 - H.265 裸流类似,但起始码可能为
00 00 00 01
或00 00 01
。
解析器的作用 就是扫描裸流数据,识别这些起始码,将连续的比特流分割为独立的 NALU,确保解码器能逐个处理完整的编码单元。
- 初始化裸流分配器
AVCodecParserContext *parser = NULL// 获取裸流的解析器 AVCodecParserContext(数据) + AVCodecParser(方法)
parser = av_parser_init(codec->id);
- 查找解码器
// 查找解码器
codec = avcodec_find_decoder(video_codec_id); // AV_CODEC_ID_H264
- 分配解码器上下文
// 分配codec上下文
codec_ctx = avcodec_alloc_context3(codec);
if (!codec_ctx) {fprintf(stderr, "Could not allocate audio codec context\n");exit(1);
}
- 打开解码器,把解码器上下文的内容与解码器关联
// 将解码器和解码器上下文进行关联
if (avcodec_open2(codec_ctx, codec, NULL) < 0) {fprintf(stderr, "Could not open codec\n");exit(1);
}
解码h264/h265
- 循环读取文件,每次从裸流解析器读取一帧数据,也就是两个startcode之间的数据
if (!decoded_frame)
{if (!(decoded_frame = av_frame_alloc())){fprintf(stderr, "Could not allocate audio frame\n");exit(1);}
}ret = av_parser_parse2(parser, codec_ctx, &pkt->data, &pkt->size,data, data_size,AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
if (ret < 0)
{fprintf(stderr, "Error while parsing\n");exit(1);
}
data += ret; // 跳过已经解析的数据
data_size -= ret; // 对应的缓存大小也做相应减小
- 根据解码器的类型的进行不同的解析,这里主要是为了打印不同的NALU信息
- 因为H264和H265的NAL头部不一样,因此要分开解析
对应的H264 NALU头部:
- H264解码
if(video_codec_id == AV_CODEC_ID_H264) {if(pkt->data[0] == 0 && pkt->data[1]==0 && pkt->data[2] == 0 && pkt->data[3] == 1 )printf("\nstart_code:%02x %02x %02x %02x, nal_type:%d, size:%d\n", pkt->data[0],pkt->data[1],pkt->data[2],pkt->data[3], pkt->data[4]&0x1f,pkt->size);if(pkt->data[0] == 0 && pkt->data[1]==0 && pkt->data[2] == 1 )printf("\nstart_code:%02x %02x %02x, nal_type:%d, size:%d\n", pkt->data[0],pkt->data[1],pkt->data[2], pkt->data[3]&0x1f,pkt->size);print_h264_nal_unit_type(pkt->data, pkt->size);
}
H265头部
字段 | 位数 | 描述 |
---|---|---|
forbidden_zero_bit | 1 | 禁止位,默认 0,错误时置 1。 |
nal_unit_type | 6 | NALU 类型(如 VPS=32,SPS=33,PPS=34,I 帧 = 16-21 等)。 |
nuh_layer_id | 6 | 层 ID,预留扩展位,默认 0。 |
nuh_temporal_id_plus1 | 3 | 时域 ID,值为实际 ID+1。 |
- H265解码
if(video_codec_id == AV_CODEC_ID_H265) {if(pkt->data[0] == 0 && pkt->data[1]==0 && pkt->data[2] == 0 && pkt->data[3] == 1 )printf("\nstart_code:%02x %02x %02x %02x, nal_type:%d, size:%d\n", pkt->data[0],pkt->data[1],pkt->data[2],pkt->data[3], (pkt->data[4]&0x7e)>>1,pkt->size);if(pkt->data[0] == 0 && pkt->data[1]==0 && pkt->data[2] == 1 )printf("\nstart_code:%02x %02x %02x, nal_type:%d, size:%d\n", pkt->data[0],pkt->data[1],pkt->data[2], (pkt->data[3]&0x7e)>>1,pkt->size);print_h265_nal_unit_type(pkt->data, pkt->size);
}
- 裸流解析器将H264/H265的数据帧,打包为对应的packet
ret = av_parser_parse2(parser, codec_ctx, &pkt->data, &pkt->size,data, data_size,AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
- 最后需要将对应的
AVPacket
送入解码器,解码出对应的AVFrame
AVFrame
这里面的数据就是对应的yuv数据了
if (pkt->size)decode(codec_ctx, pkt, decoded_frame, outfile);
对应的解码函数decode
如下
- 注意内存对齐问题,需要使用
linesize
每行的大小,手动计算边界 - uv分量上下都减半取样,因此取数据的时候都对应的宽高需要减半
static void decode(AVCodecContext *dec_ctx, AVPacket *pkt, AVFrame *frame,FILE *outfile)
{int ret;/* send the packet with the compressed data to the decoder */ret = avcodec_send_packet(dec_ctx, pkt);if(ret == AVERROR(EAGAIN)){fprintf(stderr, "Receive_frame and send_packet both returned EAGAIN, which is an API violation.\n");}else if (ret < 0){fprintf(stderr, "Error submitting the packet to the decoder, err:%s, pkt_size:%d\n",av_get_err(ret), pkt->size);return;}/* read all the output frames (infile general there may be any number of them */while (ret >= 0){// 对于frame, avcodec_receive_frame内部每次都先调用ret = avcodec_receive_frame(dec_ctx, frame);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)return;else if (ret < 0){fprintf(stderr, "Error during decoding\n");exit(1);}static int s_print_format = 0;if(s_print_format == 0){s_print_format = 1;print_video_format(frame);}// 一般H264默认为 AV_PIX_FMT_YUV420P, 具体怎么强制转为 AV_PIX_FMT_YUV420P 在音视频合成输出的时候讲解// frame->linesize[1] 对齐的问题// 正确写法 linesize[]代表每行的字节数量,所以每行的偏移是linesize[]for(int j=0; j<frame->height; j++)fwrite(frame->data[0] + j * frame->linesize[0], 1, frame->width, outfile);for(int j=0; j<frame->height/2; j++)fwrite(frame->data[1] + j * frame->linesize[1], 1, frame->width/2, outfile);for(int j=0; j<frame->height/2; j++)fwrite(frame->data[2] + j * frame->linesize[2], 1, frame->width/2, outfile);// 错误写法 用source.200kbps.766x322_10s.h264/h265测试时可以看出该种方法是错误的// 写入y分量
// fwrite(frame->data[0], 1, frame->width * frame->height, outfile);//Y
// // 写入u分量
// fwrite(frame->data[1], 1, (frame->width) *(frame->height)/4,outfile);//U:宽高均是Y的一半
// // 写入v分量
// fwrite(frame->data[2], 1, (frame->width) *(frame->height)/4,outfile);//V:宽高均是Y的一半}
}
- 打印H264/H265的NALU信息
- 我们来看具体的打印函数
print_h264_nal_unit_type
和print_h265_nal_unit_type
H264 NALU解析
- 先找到startcode,0x00 0x00 0x00 0x01 或 0x00 0x00 0x01
- 然后根据H264的NALU 头部解析就好了
- 这里主要是打印出NALU的类型,比如SPS/PPS、IDR等等
void print_h264_nal_unit_type(uint8_t *data, size_t size)
{int i = 0;while (i+3 < size ) {if(data[i] == 0 && data[i+1]==0 && data[i+2] == 0 && data[i+3] == 1 ) {i += 4;printf("%02x nal_type:%d, pos:%d\n", data[i], data[i]&0x1f, i);continue;}if(data[i] == 0 && data[i+1]==0 && data[i+2] == 1) {i += 3;printf("%02x nal_type:%d, pos:%d\n", data[i], data[i]&0x1f, i);continue;}i++;}
}
H264 NALU解析结果
-
H264 的NALU需要先传输SPS和PPS,对应type为7,8,这里还有6-SEI(图像增强信息),随后跟着一个IDR帧,表示是一个GOP的开始
-
后面的type = 1,可能是B帧,也可能是P帧,属于非I帧的NALU类型,还需要使用
slice_type
来判断
H265 NALU解析
- 这里的原理和H264一致,也是找到startcode,然后根据NALU头部解析
- H265的NALU头部与H264不太一样,所以要调整
void print_h265_nal_unit_type(uint8_t *data, size_t size)
{int i = 0;while (i+3 < size ) {if(data[i] == 0 && data[i+1]==0 && data[i+2] == 0 && data[i+3] == 1 ) {i += 4;printf("%02x nal_type:%d, pos:%d\n", data[i],(data[i]&0x7e)>>1, i);continue;}if(data[i] == 0 && data[i+1]==0 && data[i+2] == 1) {i += 3;printf("%02x nal_type:%d, pos:%d\n",data[i], (data[i]&0x7e)>>1, i);continue;}i++;}
}
H265 NALU解析结果
- 解码器的 id :173,这里H265的解码器
- 然后输出startcode是00 00 00 01
- 起始的NALU类型分别是:32-VPS、33-SPS、34-PPS,39-SEI、20-IDR,前四个都是整个视频的配置信息,通常用于初始化和图像配置,不包含视频数据,后面的IDR是一个GOP的开始,这里开始才有视频数据
- 后续可以看到都是 0 ,1 类型的,对应B帧和P帧的类型,通常在type = 0、1的NALU传输
整体读取的过程
while (data_size > 0){if (!decoded_frame){if (!(decoded_frame = av_frame_alloc())){fprintf(stderr, "Could not allocate audio frame\n");exit(1);}}ret = av_parser_parse2(parser, codec_ctx, &pkt->data, &pkt->size,data, data_size,AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);if (ret < 0){fprintf(stderr, "Error while parsing\n");exit(1);}data += ret; // 跳过已经解析的数据data_size -= ret; // 对应的缓存大小也做相应减小if(video_codec_id == AV_CODEC_ID_H264) {if(pkt->data[0] == 0 && pkt->data[1]==0 && pkt->data[2] == 0 && pkt->data[3] == 1 )printf("\nstart_code:%02x %02x %02x %02x, nal_type:%d, size:%d\n", pkt->data[0],pkt->data[1],pkt->data[2],pkt->data[3], pkt->data[4]&0x1f,pkt->size);if(pkt->data[0] == 0 && pkt->data[1]==0 && pkt->data[2] == 1 )printf("\nstart_code:%02x %02x %02x, nal_type:%d, size:%d\n", pkt->data[0],pkt->data[1],pkt->data[2], pkt->data[3]&0x1f,pkt->size);print_h264_nal_unit_type(pkt->data, pkt->size);}if(video_codec_id == AV_CODEC_ID_H265) {if(pkt->data[0] == 0 && pkt->data[1]==0 && pkt->data[2] == 0 && pkt->data[3] == 1 )printf("\nstart_code:%02x %02x %02x %02x, nal_type:%d, size:%d\n", pkt->data[0],pkt->data[1],pkt->data[2],pkt->data[3], (pkt->data[4]&0x7e)>>1,pkt->size);if(pkt->data[0] == 0 && pkt->data[1]==0 && pkt->data[2] == 1 )printf("\nstart_code:%02x %02x %02x, nal_type:%d, size:%d\n", pkt->data[0],pkt->data[1],pkt->data[2], (pkt->data[3]&0x7e)>>1,pkt->size);print_h265_nal_unit_type(pkt->data, pkt->size);}if (pkt->size)decode(codec_ctx, pkt, decoded_frame, outfile);if (data_size < VIDEO_REFILL_THRESH) // 如果数据少了则再次读取{memmove(inbuf, data, data_size); // 把之前剩的数据拷贝到buffer的起始位置data = inbuf;// 读取数据 长度: VIDEO_INBUF_SIZE - data_sizelen = fread(data + data_size, 1, VIDEO_INBUF_SIZE - data_size, infile);if (len > 0)data_size += len;}
最后可以发现,yuv数据一样的情况,H265的压缩率比H264高的多
冲刷解码器
- 最后需要传入NULL,将解码器中剩余的数据写入文件
/* 冲刷解码器 */
pkt->data = NULL; // 让其进入drain mode
pkt->size = 0;
decode(codec_ctx, pkt, decoded_frame, outfile);
- 关闭文件,释放对应内存
fclose(outfile);
fclose(infile);avcodec_free_context(&codec_ctx);
av_parser_close(parser);
av_frame_free(&decoded_frame);
av_packet_free(&pkt);free(inbuf);
整体代码
/**
* @projectName 07-05-decode_audio
* @brief 解码音频,主要的测试格式aac和mp3
* @author Liao Qingfu
* @date 2020-01-16
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>#include <libavutil/frame.h>
#include <libavutil/mem.h>#include <libavcodec/avcodec.h>#define VIDEO_INBUF_SIZE (1024*1024)
#define VIDEO_REFILL_THRESH 4096static char err_buf[128] = {0};
static char* av_get_err(int errnum)
{av_strerror(errnum, err_buf, 128);return err_buf;
}static void print_video_format(const AVFrame *frame)
{printf("width: %u\n", frame->width);printf("height: %u\n", frame->height);printf("format: %u\n", frame->format);// 格式需要注意
}void print_h264_nal_unit_type(uint8_t *data, size_t size)
{int i = 0;while (i+3 < size ) {if(data[i] == 0 && data[i+1]==0 && data[i+2] == 0 && data[i+3] == 1 ) {i += 4;printf("%02x nal_type:%d, pos:%d\n", data[i], data[i]&0x1f, i);continue;}if(data[i] == 0 && data[i+1]==0 && data[i+2] == 1) {i += 3;printf("%02x nal_type:%d, pos:%d\n", data[i], data[i]&0x1f, i);continue;}i++;}
}void print_h265_nal_unit_type(uint8_t *data, size_t size)
{int i = 0;while (i+3 < size ) {if(data[i] == 0 && data[i+1]==0 && data[i+2] == 0 && data[i+3] == 1 ) {i += 4;printf("%02x nal_type:%d, pos:%d\n", data[i],(data[i]&0x7e)>>1, i);continue;}if(data[i] == 0 && data[i+1]==0 && data[i+2] == 1) {i += 3;printf("%02x nal_type:%d, pos:%d\n",data[i], (data[i]&0x7e)>>1, i);continue;}i++;}
}static void decode(AVCodecContext *dec_ctx, AVPacket *pkt, AVFrame *frame,FILE *outfile)
{int ret;/* send the packet with the compressed data to the decoder */ret = avcodec_send_packet(dec_ctx, pkt);if(ret == AVERROR(EAGAIN)){fprintf(stderr, "Receive_frame and send_packet both returned EAGAIN, which is an API violation.\n");}else if (ret < 0){fprintf(stderr, "Error submitting the packet to the decoder, err:%s, pkt_size:%d\n",av_get_err(ret), pkt->size);return;}/* read all the output frames (infile general there may be any number of them */while (ret >= 0){// 对于frame, avcodec_receive_frame内部每次都先调用ret = avcodec_receive_frame(dec_ctx, frame);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)return;else if (ret < 0){fprintf(stderr, "Error during decoding\n");exit(1);}static int s_print_format = 0;if(s_print_format == 0){s_print_format = 1;print_video_format(frame);}// 一般H264默认为 AV_PIX_FMT_YUV420P, 具体怎么强制转为 AV_PIX_FMT_YUV420P 在音视频合成输出的时候讲解// frame->linesize[1] 对齐的问题// 正确写法 linesize[]代表每行的字节数量,所以每行的偏移是linesize[]for(int j=0; j<frame->height; j++)fwrite(frame->data[0] + j * frame->linesize[0], 1, frame->width, outfile);for(int j=0; j<frame->height/2; j++)fwrite(frame->data[1] + j * frame->linesize[1], 1, frame->width/2, outfile);for(int j=0; j<frame->height/2; j++)fwrite(frame->data[2] + j * frame->linesize[2], 1, frame->width/2, outfile);// 错误写法 用source.200kbps.766x322_10s.h264/h265测试时可以看出该种方法是错误的// 写入y分量
// fwrite(frame->data[0], 1, frame->width * frame->height, outfile);//Y
// // 写入u分量
// fwrite(frame->data[1], 1, (frame->width) *(frame->height)/4,outfile);//U:宽高均是Y的一半
// // 写入v分量
// fwrite(frame->data[2], 1, (frame->width) *(frame->height)/4,outfile);//V:宽高均是Y的一半}
}
// 注册测试的时候不同分辨率的问题
// 提取H264: ffmpeg -i source.200kbps.768x320_10s.flv -vcodec libx264 -an -f h264 source.200kbps.768x320_10s.h264
// 提取MPEG2: ffmpeg -i source.200kbps.768x320_10s.flv -vcodec mpeg2video -an -f mpeg2video source.200kbps.768x320_10s.mpeg2
// 播放:ffplay -pixel_format yuv420p -video_size 768x320 -framerate 25 source.200kbps.768x320_10s.yuv
int main(int argc, char **argv)
{const char *outfilename;const char *filename;const AVCodec *codec;AVCodecContext *codec_ctx= NULL;AVCodecParserContext *parser = NULL;int len = 0;int ret = 0;FILE *infile = NULL;FILE *outfile = NULL;// AV_INPUT_BUFFER_PADDING_SIZE 在输入比特流结尾的要求附加分配字节的数量上进行解码uint8_t *inbuf = malloc(VIDEO_INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE);uint8_t *data = NULL;size_t data_size = 0;AVPacket *pkt = NULL;AVFrame *decoded_frame = NULL;if (argc <= 2){fprintf(stderr, "Usage: %s <input file> <output file>\n", argv[0]);exit(0);}filename = argv[1];outfilename = argv[2];pkt = av_packet_alloc();enum AVCodecID video_codec_id = AV_CODEC_ID_H265;if(strstr(filename, "264") != NULL){video_codec_id = AV_CODEC_ID_H264;}else if(strstr(filename, "mpeg2") != NULL){video_codec_id = AV_CODEC_ID_MPEG2VIDEO;}else{printf("default codec id:%d\n", video_codec_id);}// 查找解码器codec = avcodec_find_decoder(video_codec_id); // AV_CODEC_ID_H264if (!codec) {fprintf(stderr, "Codec not found\n");exit(1);}// 获取裸流的解析器 AVCodecParserContext(数据) + AVCodecParser(方法)parser = av_parser_init(codec->id);if (!parser) {fprintf(stderr, "Parser not found\n");exit(1);}// 分配codec上下文codec_ctx = avcodec_alloc_context3(codec);if (!codec_ctx) {fprintf(stderr, "Could not allocate audio codec context\n");exit(1);}// 将解码器和解码器上下文进行关联if (avcodec_open2(codec_ctx, codec, NULL) < 0) {fprintf(stderr, "Could not open codec\n");exit(1);}// 打开输入文件infile = fopen(filename, "rb");if (!infile) {fprintf(stderr, "Could not open %s\n", filename);exit(1);}// 打开输出文件outfile = fopen(outfilename, "wb");if (!outfile) {av_free(codec_ctx);exit(1);}// 读取文件进行解码data = inbuf;data_size = fread(inbuf, 1, VIDEO_INBUF_SIZE, infile);while (data_size > 0){if (!decoded_frame){if (!(decoded_frame = av_frame_alloc())){fprintf(stderr, "Could not allocate audio frame\n");exit(1);}}ret = av_parser_parse2(parser, codec_ctx, &pkt->data, &pkt->size,data, data_size,AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);if (ret < 0){fprintf(stderr, "Error while parsing\n");exit(1);}data += ret; // 跳过已经解析的数据data_size -= ret; // 对应的缓存大小也做相应减小if(video_codec_id == AV_CODEC_ID_H264) {if(pkt->data[0] == 0 && pkt->data[1]==0 && pkt->data[2] == 0 && pkt->data[3] == 1 )printf("\nstart_code:%02x %02x %02x %02x, nal_type:%d, size:%d\n", pkt->data[0],pkt->data[1],pkt->data[2],pkt->data[3], pkt->data[4]&0x1f,pkt->size);if(pkt->data[0] == 0 && pkt->data[1]==0 && pkt->data[2] == 1 )printf("\nstart_code:%02x %02x %02x, nal_type:%d, size:%d\n", pkt->data[0],pkt->data[1],pkt->data[2], pkt->data[3]&0x1f,pkt->size);print_h264_nal_unit_type(pkt->data, pkt->size);}if(video_codec_id == AV_CODEC_ID_H265) {if(pkt->data[0] == 0 && pkt->data[1]==0 && pkt->data[2] == 0 && pkt->data[3] == 1 )printf("\nstart_code:%02x %02x %02x %02x, nal_type:%d, size:%d\n", pkt->data[0],pkt->data[1],pkt->data[2],pkt->data[3], (pkt->data[4]&0x7e)>>1,pkt->size);if(pkt->data[0] == 0 && pkt->data[1]==0 && pkt->data[2] == 1 )printf("\nstart_code:%02x %02x %02x, nal_type:%d, size:%d\n", pkt->data[0],pkt->data[1],pkt->data[2], (pkt->data[3]&0x7e)>>1,pkt->size);print_h265_nal_unit_type(pkt->data, pkt->size);}if (pkt->size)decode(codec_ctx, pkt, decoded_frame, outfile);if (data_size < VIDEO_REFILL_THRESH) // 如果数据少了则再次读取{memmove(inbuf, data, data_size); // 把之前剩的数据拷贝到buffer的起始位置data = inbuf;// 读取数据 长度: VIDEO_INBUF_SIZE - data_sizelen = fread(data + data_size, 1, VIDEO_INBUF_SIZE - data_size, infile);if (len > 0)data_size += len;}}/* 冲刷解码器 */pkt->data = NULL; // 让其进入drain modepkt->size = 0;decode(codec_ctx, pkt, decoded_frame, outfile);fclose(outfile);fclose(infile);avcodec_free_context(&codec_ctx);av_parser_close(parser);av_frame_free(&decoded_frame);av_packet_free(&pkt);free(inbuf);printf("main finish, please enter Enter and exit\n");return 0;
}
更多资料:https://github.com/0voice
相关文章:

【音视频】 FFmpeg 解码H265
一、概述 实现了使用FFmpeg读取对应H265文件,并且保存为对应的yuv文件 二、实现流程 读取文件 将H265/H264文件放在build路径下,然后指定输出为yuv格式 在main函数中读取外部参数 if (argc < 2){fprintf(stderr, "Usage: %s <input file&…...
Linux 系统 Docker Compose 安装
个人博客地址:Linux 系统 Docker Compose 安装 | 一张假钞的真实世界 本文方法是直接下载 GitHub 项目的 release 版本。项目地址:GitHub - docker/compose: Define and run multi-container applications with Docker。 执行以下命令将发布程序加载至…...

软件测试|FIT故障注入测试工具——ISO 26262合规下的智能汽车安全验证引擎
FIT(Fault Injection Tester)是SURESOFT专为汽车电子与工业控制设计的自动化故障注入测试工具,基于ISO 26262等国际安全标准开发,旨在解决传统测试中效率低、成本高、安全隐患难以复现的问题,其核心功能包括…...

3D拟合测量水杯半径
1,目的。 测量水杯的半径 如图所示: 2,原理。 对 3D 点云对象 进行圆柱体拟合,获取拟合后的半径。 3,注意事项。 在Halcon中使用fit_primitives_object_model_3d进行圆柱体拟合时,输出的primitive_para…...
(21)量子计算对密码学的影响
文章目录 2️⃣1️⃣ 量子计算对密码学的影响 🌌🔍 TL;DR🚀 量子计算:密码学的终结者?⚡ 量子计算的破坏力 🔐 Java密码学体系面临的量子威胁🔥 受影响最严重的Java安全组件 🛡️ 后…...

Python训练打卡Day38
Dataset和Dataloader类 知识点回顾: Dataset类的__getitem__和__len__方法(本质是python的特殊方法)Dataloader类minist手写数据集的了解 在遇到大规模数据集时,显存常常无法一次性存储所有数据,所以需要使用分批训练的…...

Selenium基础操作方法详解
Selenium基础操作方法详解:从零开始编写自动化脚本(附完整代码) 引言 Selenium是自动化测试和网页操作的利器,但对于新手来说,掌握基础操作是成功的第一步。本文将手把手教你使用Selenium完成浏览器初始化、元素定位、…...
Kali Linux从入门到实战:系统详解与工具指南
一、Kali Linux简介 Kali Linux是一款基于Debian的Linux发行版,专为渗透测试和网络安全审计设计,由Offensive Security团队维护。其前身是BackTrack,目前集成了超过600款安全工具,覆盖渗透测试全流程,是网络安全领域…...
【大模型】Bert变种
1. RoBERTa(Robustly optimized BERT approach) 核心改动 取消 NSP(Next Sentence Prediction)任务,研究发现 NSP 对多数下游任务贡献有限。动态遮蔽(dynamic masking):每个 epoch …...
vue-09(使用自定义事件和作用域插槽构建可重用组件)
实践练习:使用自定义事件和作用域插槽构建可重用组件 构建可重用的组件是高效 Vue.js 开发的基石。本课重点介绍如何通过自定义事件和范围插槽来增强组件的可重用性,从而实现更灵活和动态的组件交互。我们将探索如何定义和发出自定义事件,使…...

简单三步FastAdmin 开源框架的安装
简单三步FastAdmin 开源框架的安装 第一步:新建站点1,在宝塔面板中,创建一个新的站点,并填写项目域名。 第二步:上传框架1,框架下载2,上传解压缩 第三步:配置并安装1,进入…...

RISC-V 开发板 MUSE Pi Pro 搭建 Spacengine AI模型部署环境
视频讲解: RISC-V 开发板 MUSE Pi Pro 搭建 Spacengine AI模型部署环境 Spacengine 是由 进迭时空 研发的一套 AI 算法模型部署工具,可以方便的帮助用户部署自己的模型在端侧, 环境部署的方式,官方提供了两种方式: do…...
C++面试5——对象存储区域详解
C++对象存储区域详解 核心观点:内存是程序员的战场,存储区域决定对象的生杀大权!栈对象自动赴死,堆对象生死由你,全局对象永生不死,常量区对象只读不灭。 一、四大地域生死簿 栈区(Stack) • 特点:自动分配释放,速度极快(类似高铁进出站) • 生存期:函数大括号{}就…...

【Unity】AudioSource超过MaxDistance还是能听见
unity版本:2022.3.51f1c1 将SpatialBlend拉到1即可 或者这里改到0 Hearing audio outside max distance - #11 by wderstine - Questions & Answers - Unity Discussions...
基于 51 单片机的智能饮水机控制系统设计与实现
一、引言 随着物联网技术的发展,传统家电的智能化升级成为趋势。本文提出一种基于 51 单片机的智能饮水机设计方案,实现水温精准控制、水位监测、人机交互等功能,具有成本低、稳定性高的特点,适用于家庭和小型办公场景。 二、硬件设计 2.1 核心芯片选型 单片机:选用STC…...

Qt 读取和写入 INI 格式的配置文件
Qt 读取和写入 INI 格式的配置文件 前言:INI 配置文件在 Qt 开发中的重要性基础夯实:INI 文件结构与 QSettings 核心概念1. INI 文件的基本结构2. QSettings 类概述3. 初始化 QSettings 对象4. 基本读写操作5. 高级操作技巧5.1 处理数组和列表5.2 检查键…...
互联网大厂Java求职面试:AI与云原生架构实战解析
互联网大厂Java求职面试:AI与云原生架构实战解析 面试背景设定 场景:某互联网头部企业技术总监办公室,窗外是城市夜景,室内灯光柔和。面试官是一位经验丰富的技术总监,面前摆着一杯黑咖啡和候选人的简历。 候选人&a…...

Spring:从青铜到王者,你的Java修炼手册
一、Spring家族宇宙:原来你是这样的框架(青铜段位) 1.1 Spring的"前世今生":从泡面到满汉全席 2002年的泡面哲学:Rod Johnson在厨房煮泡面时突然顿悟:"Java开发为什么不能像泡面一…...
React和原生事件的区别
一、核心差异对比表 维度原生事件React 事件绑定语法HTML 属性(onclick)或 DOM API(addEventListener)JSX 中使用驼峰式属性(onClick)绑定位置直接绑定到具体 DOM 元素统一委托到根节点(React …...

Qt creator 设计页面控件认识与了解
记录一下 Qt 中的认识与了解: 在 Qt 中,这些功能属于 Qt Designer 的组件过滤系统,旨在帮助开发者在对象浏览器中快速定位和使用不同类型的控件和组件。以下是每个功能的详细讲解: Layouts(布局)&…...
命象架构法 02|你的系统有“用神”吗?
命理中说:“八字无用神,是虚命。” 系统架构中说:“模块无主线,是垃圾桶。” 你设计了无数类,却不知道哪个是核心。 那么你的系统,很可能是没有“用神”的。 01|什么是“用神”?不是你以为的“最好” 命理中,“用神”不是“最强的”,而是对命主最有帮助的。 比如一…...

NVIDIA Mellanox BlueField-2 DPU(Data Processing Unit)智能网卡的调试和使用
专有名词 OOB: BMC: BFB: EMMC: 关键词解释eMMCEmbedded Multi-Media Card——把 NAND 闪存颗粒与控制器封装在一起的板载存储件,类似手机里的“内置储存” .deb:文件是Debian软件包格式的专…...

Tomcat- AJP协议文件读取/命令执行漏洞(幽灵猫复现)详细步骤
一、漏洞描述 Apache Tomcat是由Apache软件基金会属下Jakarta项目开发的Servlet容器.默认情况下,Apache Tomcat会开启AJP连接器,方便与其他Web服务器通过AJP协议进行交互.但Apache Tomcat在AJP协议的实现上存在漏洞,导致攻击者可以通过发送恶意的AJP请求,可以读取或者包含Web应…...

B1、进度汇报(— 25/05/31)
本文档汇总了各成员在 2025 年 5 月 11 日 ~ 5 月 31 日完成的工作。我们遇到了进度问题(收工后需反思): 本学期第十四周(05/19 ~ 05/25)有相当多课程需要提交实验结果或上台展示。本学期第十六周(06/02 ~…...
工作流引擎-11-开源 BPM 项目 jbpm
工作流引擎系列 工作流引擎-00-流程引擎概览 工作流引擎-01-Activiti 是领先的轻量级、以 Java 为中心的开源 BPMN 引擎,支持现实世界的流程自动化需求 工作流引擎-02-BPM OA ERP 区别和联系 工作流引擎-03-聊一聊流程引擎 工作流引擎-04-流程引擎 activiti 优…...
【Prompt Engineering】摸索出的一些小套路
prompt 优化方法 🔹 1. 通用结构模板 模块化的Prompt:Prompt 划分成边界清晰的模块,不同模块间都应有明确的分隔符 以下是通用 Prompt 的推荐结构: [角色设定] [任务描述] [输出格式要求] [补充上下文]角色设定:…...
CSS强制div单行显示不换行
在CSS中,要让<div>的内容强制单行显示且不换行,可通过以下属性组合实现: 核心解决方案: css 复制 下载 div {white-space: nowrap; /* 禁止文本换行 */overflow: hidden; /* 隐藏溢出内容 */text-overflow: e…...
js的时间循环的讲解
JavaScript 事件循环(Event Loop)是其运行时的核心机制,负责处理异步操作,确保单线程的 JavaScript 能够高效地处理并发任务。下面从多个角度详细解析事件循环机制: 1. 核心概念 (1)执行栈(Call Stack) 定义:JavaScript 是单线程的,所有同步任务都在执行栈中依次执…...

Flutter实现不规则瀑布流布局拖拽重排序
因为业务,所以需要用flutter去实现一种不规则图形的瀑布流,但是同时需要支持拖拽并重新排序。效果类似如下。 查询过现有的插件,要么是仅支持同样大小的组件进行排序,要么就是动画效果不是很满意,有点死板,…...

【第4章 图像与视频】4.1 图像的绘制
文章目录 前言在 Canvas 之中绘制图像drawImage() 方法的用法 前言 drawImage() 方法可以将一幅图像的整体或某个部分绘制到 canvas 内的任何位置上,并且允许开发者在绘制过程中对图像进行缩放。也可以将图像绘制在离屏 canvas 中,这样的话就可以对图像…...