FFmpeg 硬编码VideoToolBox流程

介绍

  1. FFmpeg已经提供对 VideoToolBox 的编解码支持;主要涉及到的文件有videotoolbox.c、videotoolbox.h、videotoolboxenc.c、ffmepg_videotoolbox.c。
  2. 在编译 FFmpeg 源码时,想要支持VideoToolBox,在 configure 时,需要–enable-videotoolbox 命令。
  3. 命令行ffmpeg -hwaccels查看支持哪些硬编码器。
  4. ffmpeg 支持 videotoolbox h264 和 h265 的编码,即 h264_videotoolbox、hevc_videotoolbox。
  5. 命令行 ffmpeg -h encoder=hevc_videotoolbox可以查看对应设备的硬编码有哪些编码参数(要是 h264,即 ffmpeg -h encoder=h264_videotoolbox查看编码参数)。
    在这里插入图片描述
    在这里插入图片描述

FFmpeg

  1. FFmpeg 是一个可以处理音视频的软件,功能非常强大,主要包括,编解码转换,封装格式转换,滤镜特效。
  2. FFmpeg支持各种网络协议,支持 RTMP ,RTSP,HLS 等高层协议的推拉流,也支持更底层的TCP/UDP 协议推拉流。
  3. FFmpeg 可以在 Windows,Linux,Mac,iOS,Android等操作系统上运行。
  4. FFmpeg 是 " Fast Forward mpeg " 的缩写;
  5. FFMPEG从功能上划分为几个模块,分别为核心工具(libutils)、媒体格式(libavformat)、编解码(libavcodec)、设备(libavdevice)和后处理(libavfilter, libswscale, libpostproc),分别负责提供公用的功能函数、实现多媒体文件的读包和写包、完成音视频的编解码、管理音视频设备的操作以及进行音视频后处理。

VideoToolBox

  1. VideoToolBox是一个优化的视频编解码器框架,由苹果公司开发并针对iOS和macOS平台进行优化,作为现代移动应用程序中不可或缺的组成部分之一,它被用于H.264解码和编码,HEVC解码和编码,以及MPEG-2解码和编码,同时还支持对Core Audio和Core Video的访问。
  2. VideoToolBox的优点是高效性、易用性;在iOS和macOS设备上,它的编解码速度比其他框架要快得多;此外,它为开发人员提供了各种功能,包括修改视频帧速率,更改编码格式等等。

FFmpeg 硬编码 VideoToolBox 流程

  1. 可以看出,FFmpeg 与 VideoToolBox之间的交互,主要通过三个函数指针 init、encode2、close 来完成;
  2. 从整体流程分析,VideoToolBox 的工作流程是:
    创建 一个压缩会话 ;
    添加会话属性 ;
    编码视频帧、接受视频编码回调 ;
    强制完成一些或者全部未处理的视频帧;
    释放压缩会话、释放内存资源。
  3. init模块核心函数是 vtenc_configure_encode();
  4. encode2模块核心函数是vtenc_send_frame();
  5. close 模块的核心函数是VTCompressionSessionCompleteFrames();
    在这里插入图片描述

h264_videotoolbox

  1. VideoToolBox 的h264硬编码通过三个结构体h264_optionsh264_videotoolbox_classff_h264_videotoolbox_encoder来完成与 FFmpeg 的交互。
  2. h264_options主要涉及的是内部参数,例如 profile、level、熵编码选择等。
  3. h264_videotoolbox_class来定义 h264的私有类,指定编码类型和编码参数。
  4. ff_h264_videotoolbox_encoder是具体的对外与 FFmpeg 的交互结构体,完成h264硬编码。
static const AVOption h264_options[] = {{ "profile", "Profile", OFFSET(profile), AV_OPT_TYPE_INT, { .i64 = H264_PROF_AUTO }, H264_PROF_AUTO, H264_PROF_COUNT, VE, "profile" },{ "baseline", "Baseline Profile", 0, AV_OPT_TYPE_CONST, { .i64 = H264_PROF_BASELINE }, INT_MIN, INT_MAX, VE, "profile" },{ "main",     "Main Profile",     0, AV_OPT_TYPE_CONST, { .i64 = H264_PROF_MAIN     }, INT_MIN, INT_MAX, VE, "profile" },{ "high",     "High Profile",     0, AV_OPT_TYPE_CONST, { .i64 = H264_PROF_HIGH     }, INT_MIN, INT_MAX, VE, "profile" },{ "extended", "Extend Profile",   0, AV_OPT_TYPE_CONST, { .i64 = H264_PROF_EXTENDED }, INT_MIN, INT_MAX, VE, "profile" },{ "level", "Level", OFFSET(level), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 52, VE, "level" },{ "1.3", "Level 1.3, only available with Baseline Profile", 0, AV_OPT_TYPE_CONST, { .i64 = 13 }, INT_MIN, INT_MAX, VE, "level" },{ "3.0", "Level 3.0", 0, AV_OPT_TYPE_CONST, { .i64 = 30 }, INT_MIN, INT_MAX, VE, "level" },{ "3.1", "Level 3.1", 0, AV_OPT_TYPE_CONST, { .i64 = 31 }, INT_MIN, INT_MAX, VE, "level" },{ "3.2", "Level 3.2", 0, AV_OPT_TYPE_CONST, { .i64 = 32 }, INT_MIN, INT_MAX, VE, "level" },{ "4.0", "Level 4.0", 0, AV_OPT_TYPE_CONST, { .i64 = 40 }, INT_MIN, INT_MAX, VE, "level" },{ "4.1", "Level 4.1", 0, AV_OPT_TYPE_CONST, { .i64 = 41 }, INT_MIN, INT_MAX, VE, "level" },{ "4.2", "Level 4.2", 0, AV_OPT_TYPE_CONST, { .i64 = 42 }, INT_MIN, INT_MAX, VE, "level" },{ "5.0", "Level 5.0", 0, AV_OPT_TYPE_CONST, { .i64 = 50 }, INT_MIN, INT_MAX, VE, "level" },{ "5.1", "Level 5.1", 0, AV_OPT_TYPE_CONST, { .i64 = 51 }, INT_MIN, INT_MAX, VE, "level" },{ "5.2", "Level 5.2", 0, AV_OPT_TYPE_CONST, { .i64 = 52 }, INT_MIN, INT_MAX, VE, "level" },{ "coder", "Entropy coding", OFFSET(entropy), AV_OPT_TYPE_INT, { .i64 = VT_ENTROPY_NOT_SET }, VT_ENTROPY_NOT_SET, VT_CABAC, VE, "coder" },{ "cavlc", "CAVLC entropy coding", 0, AV_OPT_TYPE_CONST, { .i64 = VT_CAVLC }, INT_MIN, INT_MAX, VE, "coder" },{ "vlc",   "CAVLC entropy coding", 0, AV_OPT_TYPE_CONST, { .i64 = VT_CAVLC }, INT_MIN, INT_MAX, VE, "coder" },{ "cabac", "CABAC entropy coding", 0, AV_OPT_TYPE_CONST, { .i64 = VT_CABAC }, INT_MIN, INT_MAX, VE, "coder" },{ "ac",    "CABAC entropy coding", 0, AV_OPT_TYPE_CONST, { .i64 = VT_CABAC }, INT_MIN, INT_MAX, VE, "coder" },{ "a53cc", "Use A53 Closed Captions (if available)", OFFSET(a53_cc), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, VE },COMMON_OPTIONS{ NULL },
};static const AVClass h264_videotoolbox_class = {.class_name = "h264_videotoolbox",.item_name  = av_default_item_name,.option     = h264_options,.version    = LIBAVUTIL_VERSION_INT,
};AVCodec ff_h264_videotoolbox_encoder = {.name             = "h264_videotoolbox",.long_name        = NULL_IF_CONFIG_SMALL("VideoToolbox H.264 Encoder"),.type             = AVMEDIA_TYPE_VIDEO,.id               = AV_CODEC_ID_H264,.priv_data_size   = sizeof(VTEncContext),.pix_fmts         = avc_pix_fmts,.init             = vtenc_init,.encode2          = vtenc_frame,.close            = vtenc_close,.capabilities     = AV_CODEC_CAP_DELAY,.priv_class       = &h264_videotoolbox_class,.caps_internal    = FF_CODEC_CAP_INIT_THREADSAFE |FF_CODEC_CAP_INIT_CLEANUP,
};

hevc_videotoolbox

  1. VideoToolBox 的HEVC硬编码通过三个结构体hevc_optionshevc_videotoolbox_classff_hevc_videotoolbox_encoder来完成与 FFmpeg 的交互。
  2. hevc_options主要涉及的是内部参数,例如 profile的选择。
  3. hevc_videotoolbox_class来定义 HEVC的私有类,指定编码类型和编码参数。
  4. ff_hevc_videotoolbox_encoder是具体的对外与 FFmpeg 的交互结构体,完成HEVC硬编码。
static const AVOption hevc_options[] = {{ "profile", "Profile", OFFSET(profile), AV_OPT_TYPE_INT, { .i64 = HEVC_PROF_AUTO }, HEVC_PROF_AUTO, HEVC_PROF_COUNT, VE, "profile" },{ "main",     "Main Profile",     0, AV_OPT_TYPE_CONST, { .i64 = HEVC_PROF_MAIN   }, INT_MIN, INT_MAX, VE, "profile" },{ "main10",   "Main10 Profile",   0, AV_OPT_TYPE_CONST, { .i64 = HEVC_PROF_MAIN10 }, INT_MIN, INT_MAX, VE, "profile" },COMMON_OPTIONS{ NULL },
};static const AVClass hevc_videotoolbox_class = {.class_name = "hevc_videotoolbox",.item_name  = av_default_item_name,.option     = hevc_options,.version    = LIBAVUTIL_VERSION_INT,
};AVCodec ff_hevc_videotoolbox_encoder = {.name             = "hevc_videotoolbox",.long_name        = NULL_IF_CONFIG_SMALL("VideoToolbox H.265 Encoder"),.type             = AVMEDIA_TYPE_VIDEO,.id               = AV_CODEC_ID_HEVC,.priv_data_size   = sizeof(VTEncContext),.pix_fmts         = hevc_pix_fmts,.init             = vtenc_init,.encode2          = vtenc_frame,.close            = vtenc_close,.capabilities     = AV_CODEC_CAP_DELAY | AV_CODEC_CAP_HARDWARE,.priv_class       = &hevc_videotoolbox_class,.caps_internal    = FF_CODEC_CAP_INIT_THREADSAFE |FF_CODEC_CAP_INIT_CLEANUP,.wrapper_name     = "videotoolbox",
};

核心模块介绍

.init

  1. .init模块完成初始化工作,对应的函数是vtenc_init();函数内部主要完成了线程初始化、配置编码器、检索属性以及 B 帧的相关处理。
    在这里插入图片描述
static av_cold int vtenc_init(AVCodecContext *avctx)
{VTEncContext    *vtctx = avctx->priv_data;CFBooleanRef    has_b_frames_cfbool;int             status;pthread_once(&once_ctrl, loadVTEncSymbols);pthread_mutex_init(&vtctx->lock, NULL);pthread_cond_init(&vtctx->cv_sample_sent, NULL);vtctx->session = NULL;status = vtenc_configure_encoder(avctx);if (status) return status;status = VTSessionCopyProperty(vtctx->session,kVTCompressionPropertyKey_AllowFrameReordering,kCFAllocatorDefault,&has_b_frames_cfbool);if (!status && has_b_frames_cfbool) {//Some devices don't output B-frames for main profile, even if requested.vtctx->has_b_frames = CFBooleanGetValue(has_b_frames_cfbool);CFRelease(has_b_frames_cfbool);}avctx->has_b_frames = vtctx->has_b_frames;return 0;
}
  1. vtenc_configure_encoder()函数是 init 模块的核心函数,主要完成编码器的配置工作;根据编码器类型(h264/HEVC)来配置 profile、level、熵编码等信息;此外还会选择裁剪信息、传递函数、YCbCr 矩阵、颜色原色以及额外信息;最后调用vtenc_create_encoder()完成编码器的创建;
    在这里插入图片描述
static int vtenc_configure_encoder(AVCodecContext *avctx)
{CFMutableDictionaryRef enc_info;CFMutableDictionaryRef pixel_buffer_info;CMVideoCodecType       codec_type;VTEncContext           *vtctx = avctx->priv_data;CFStringRef            profile_level;CFNumberRef            gamma_level = NULL;int                    status;codec_type = get_cm_codec_type(avctx->codec_id);if (!codec_type) {av_log(avctx, AV_LOG_ERROR, "Error: no mapping for AVCodecID %d\n", avctx->codec_id);return AVERROR(EINVAL);}vtctx->codec_id = avctx->codec_id;if (vtctx->codec_id == AV_CODEC_ID_H264) {vtctx->get_param_set_func = CMVideoFormatDescriptionGetH264ParameterSetAtIndex;vtctx->has_b_frames = avctx->max_b_frames > 0;if(vtctx->has_b_frames && vtctx->profile == H264_PROF_BASELINE){av_log(avctx, AV_LOG_WARNING, "Cannot use B-frames with baseline profile. Output will not contain B-frames.\n");vtctx->has_b_frames = false;}if (vtctx->entropy == VT_CABAC && vtctx->profile == H264_PROF_BASELINE) {av_log(avctx, AV_LOG_WARNING, "CABAC entropy requires 'main' or 'high' profile, but baseline was requested. Encode will not use CABAC entropy.\n");vtctx->entropy = VT_ENTROPY_NOT_SET;}if (!get_vt_h264_profile_level(avctx, &profile_level)) return AVERROR(EINVAL);} else {vtctx->get_param_set_func = compat_keys.CMVideoFormatDescriptionGetHEVCParameterSetAtIndex;if (!vtctx->get_param_set_func) return AVERROR(EINVAL);if (!get_vt_hevc_profile_level(avctx, &profile_level)) return AVERROR(EINVAL);}enc_info = CFDictionaryCreateMutable(kCFAllocatorDefault,20,&kCFCopyStringDictionaryKeyCallBacks,&kCFTypeDictionaryValueCallBacks);if (!enc_info) return AVERROR(ENOMEM);#if !TARGET_OS_IPHONEif(vtctx->require_sw) {CFDictionarySetValue(enc_info,compat_keys.kVTVideoEncoderSpecification_EnableHardwareAcceleratedVideoEncoder,kCFBooleanFalse);} else if (!vtctx->allow_sw) {CFDictionarySetValue(enc_info,compat_keys.kVTVideoEncoderSpecification_RequireHardwareAcceleratedVideoEncoder,kCFBooleanTrue);} else {CFDictionarySetValue(enc_info,compat_keys.kVTVideoEncoderSpecification_EnableHardwareAcceleratedVideoEncoder,kCFBooleanTrue);}
#endifif (avctx->pix_fmt != AV_PIX_FMT_VIDEOTOOLBOX) {status = create_cv_pixel_buffer_info(avctx, &pixel_buffer_info);if (status)goto init_cleanup;} else {pixel_buffer_info = NULL;}vtctx->dts_delta = vtctx->has_b_frames ? -1 : 0;get_cv_transfer_function(avctx, &vtctx->transfer_function, &gamma_level);get_cv_ycbcr_matrix(avctx, &vtctx->ycbcr_matrix);get_cv_color_primaries(avctx, &vtctx->color_primaries);if (avctx->flags & AV_CODEC_FLAG_GLOBAL_HEADER) {status = vtenc_populate_extradata(avctx,codec_type,profile_level,gamma_level,enc_info,pixel_buffer_info);if (status)goto init_cleanup;}status = vtenc_create_encoder(avctx,codec_type,profile_level,gamma_level,enc_info,pixel_buffer_info,&vtctx->session);init_cleanup:if (gamma_level)CFRelease(gamma_level);if (pixel_buffer_info)CFRelease(pixel_buffer_info);CFRelease(enc_info);return status;
}
  1. vtenc_create_encoder()完成编码器创建工作;调用VTCompressionSessionCreate()创建压缩帧实例,接着会创建码率/码控等各类对象,并配置相应属性;最后,(可选)调用VTCompressionSessionPrepareToEncodeFrames()完成编码前的合理资源分配。
    在这里插入图片描述

.encode2

  1. .encode2模块完成具体的编码工作,对应的函数是 vtenc_frame();判断 AVFrame里是否有帧数据,有数据就调用vtenc_send_frame()完成具体的编码,没有就 flush 下;然后调用 vtenc_q_pop()完成线程相关操作;最后利用vtenc_cm_to_avpacket()得到数据包信息,如 SEI、pts、dts 等。
    在这里插入图片描述
static av_cold int vtenc_frame(AVCodecContext *avctx,AVPacket       *pkt,const AVFrame  *frame,int            *got_packet)
{VTEncContext *vtctx = avctx->priv_data;bool get_frame;int status;CMSampleBufferRef buf = NULL;ExtraSEI *sei = NULL;if (frame) {status = vtenc_send_frame(avctx, vtctx, frame);if (status) {status = AVERROR_EXTERNAL;goto end_nopkt;}if (vtctx->frame_ct_in == 0) {vtctx->first_pts = frame->pts;} else if(vtctx->frame_ct_in == 1 && vtctx->has_b_frames) {vtctx->dts_delta = frame->pts - vtctx->first_pts;}vtctx->frame_ct_in++;} else if(!vtctx->flushing) {vtctx->flushing = true;status = VTCompressionSessionCompleteFrames(vtctx->session,kCMTimeIndefinite);if (status) {av_log(avctx, AV_LOG_ERROR, "Error flushing frames: %d\n", status);status = AVERROR_EXTERNAL;goto end_nopkt;}}*got_packet = 0;get_frame = vtctx->dts_delta >= 0 || !frame;if (!get_frame) {status = 0;goto end_nopkt;}status = vtenc_q_pop(vtctx, !frame, &buf, &sei);if (status) goto end_nopkt;if (!buf)   goto end_nopkt;status = vtenc_cm_to_avpacket(avctx, buf, pkt, sei);if (sei) {if (sei->data) av_free(sei->data);av_free(sei);}CFRelease(buf);if (status) goto end_nopkt;*got_packet = 1;return 0;end_nopkt:av_packet_unref(pkt);return status;
}
  1. vtenc_send_frame()完成编码核心工作;内部主要调用 VideoToolBox 的核心 API函数VTCompressionSessionEncodeFrame()完成具体的编码工作。
    在这里插入图片描述
static int vtenc_send_frame(AVCodecContext *avctx,VTEncContext   *vtctx,const AVFrame  *frame)
{CMTime time;CFDictionaryRef frame_dict;CVPixelBufferRef cv_img = NULL;AVFrameSideData *side_data = NULL;ExtraSEI *sei = NULL;int status = create_cv_pixel_buffer(avctx, frame, &cv_img);if (status) return status;status = create_encoder_dict_h264(frame, &frame_dict);if (status) {CFRelease(cv_img);return status;}side_data = av_frame_get_side_data(frame, AV_FRAME_DATA_A53_CC);if (vtctx->a53_cc && side_data && side_data->size) {sei = av_mallocz(sizeof(*sei));if (!sei) {av_log(avctx, AV_LOG_ERROR, "Not enough memory for closed captions, skipping\n");} else {int ret = ff_alloc_a53_sei(frame, 0, &sei->data, &sei->size);if (ret < 0) {av_log(avctx, AV_LOG_ERROR, "Not enough memory for closed captions, skipping\n");av_free(sei);sei = NULL;}}}time = CMTimeMake(frame->pts * avctx->time_base.num, avctx->time_base.den);status = VTCompressionSessionEncodeFrame(vtctx->session,cv_img,time,kCMTimeInvalid,frame_dict,sei,NULL);if (frame_dict) CFRelease(frame_dict);CFRelease(cv_img);if (status) {av_log(avctx, AV_LOG_ERROR, "Error: cannot encode frame: %d\n", status);return AVERROR_EXTERNAL;}return 0;
}

.close

  1. .close 模块完成关闭回收工作,对应的函数是 vtenc_close();内部主要进行线程的销毁、强制完成一些或全部未处理的视频帧、清除帧队列、释放资源的工作。
    在这里插入图片描述
static av_cold int vtenc_close(AVCodecContext *avctx)
{VTEncContext *vtctx = avctx->priv_data;pthread_cond_destroy(&vtctx->cv_sample_sent);pthread_mutex_destroy(&vtctx->lock);if(!vtctx->session) return 0;VTCompressionSessionCompleteFrames(vtctx->session,kCMTimeIndefinite);clear_frame_queue(vtctx);CFRelease(vtctx->session);vtctx->session = NULL;if (vtctx->color_primaries) {CFRelease(vtctx->color_primaries);vtctx->color_primaries = NULL;}if (vtctx->transfer_function) {CFRelease(vtctx->transfer_function);vtctx->transfer_function = NULL;}if (vtctx->ycbcr_matrix) {CFRelease(vtctx->ycbcr_matrix);vtctx->ycbcr_matrix = NULL;}return 0;
}

参考

  1. https://developer.apple.com/documentation/videotoolbox
  2. http://ffmpeg.org/

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

激活函数总结(十一):激活函数补充(Absolute、Bipolar、Bipolar Sigmoid)

激活函数总结&#xff08;十一&#xff09;&#xff1a;激活函数补充 1 引言2 激活函数2.1 Absolute激活函数2.2 Bipolar激活函数2.3 Bipolar Sigmoid激活函数 3. 总结 1 引言 在前面的文章中已经介绍了介绍了一系列激活函数 (Sigmoid、Tanh、ReLU、Leaky ReLU、PReLU、Swish、…

第十三章 SpringBoot项目(总)

1.创建SpringBoot项目 1.1.设置编码 1.4.导入已有的spring boot项目 2.快速搭建Restfull风格的项目 2.1.返回字符串 RestController public class IndexController {RequestMapping("/demo1")public Object demo1() {System.out.println("demo1 ran...."…

2022年12月 C/C++(二级)真题解析#中国电子学会#全国青少年软件编程等级考试

第1题:数组逆序重放 将一个数组中的值按逆序重新存放。例如,原来的顺序为8,6,5,4,1。要求改为1,4,5,6,8。 输入 输入为两行:第一行数组中元素的个数n(1 输出 输出为一行:输出逆序后数组的整数,每两个整数之间用空格分隔。 样例输入 5 8 6 5 4 1 样例输出 1 4 5 6 8 以下是…

网络安全--linux下Nginx安装以及docker验证标签漏洞

目录 一、Nginx安装 二、docker验证标签漏洞 一、Nginx安装 1.首先创建Nginx的目录并进入&#xff1a; mkdir /soft && mkdir /soft/nginx/cd /soft/nginx/ 2.下载Nginx的安装包&#xff0c;可以通过FTP工具上传离线环境包&#xff0c;也可通过wget命令在线获取安装包…

Linux:shell脚本:基础使用(5)《正则表达式-sed工具》

sed是一种流编辑器&#xff0c;它是文本处理中非常中的工具&#xff0c;能够完美的配合正则表达式使用&#xff0c;功能不同凡响。 处理时&#xff0c;把当前处理的行存储在临时缓冲区中&#xff0c;称为“模式空间”&#xff08;pattern space&#xff09;&#xff0c;接着用s…

深入篇【C++】手搓模拟实现二叉搜索树(递归/非递归版本)常见应用场景(K模型与KV模型)

深入篇【C】手搓模拟实现二叉搜索树(递归/非递归版本&#xff09;&&常见应用场景 Ⅰ.二叉搜索树概念Ⅱ.二叉搜索树模拟实现(递归与非递归)①.定义结点②.构造二叉树③.插入结点④.删除结点(重要)⑤.查找结点⑥.析构二叉树⑦.拷贝二叉树⑧.二叉树赋值 Ⅲ.二叉搜索树应用…

SpringBoot复习:(48)RedisAutoConfiguration自动配置类

RedisAutoConfiguration类代码如下&#xff1a; 可以看到在这个类中配置了2个bean: redisTemplate和stringRedisTemplate. 而它通过EnableConfigurationProperties(RedisProperties.class)注解&#xff0c;把配置文件中配置的Redis相关的信息引入进来了&#xff0c;RedisPrope…

元素在div中水平居中

先看一下行级元素在div中水平居中&#xff1b; <!DOCTYPE html> <html> <head> <meta charset"utf-8"> <title>div demo </title> <style> body {background-color:#d0e4fe; }</style> </head><body>&…

使用css实现时间线布局(TimeLine)

前言 在使用uni-app开发微信小程序过程中&#xff0c;遇到了时间轴布局&#xff0c;由于每项的内容高度不一致&#xff0c;使用uniapp自带的扩展组件uni-steps&#xff0c;样式布局无法对齐竖线&#xff0c;于是自己造轮子&#xff0c;完成特殊的布局。显示效果如下&#xff1…

71 # 协商缓存的配置:通过内容

对比&#xff08;协商&#xff09;缓存 比较一下再去决定是用缓存还是重新获取数据&#xff0c;这样会减少网络请求&#xff0c;提高性能。 对比缓存的工作原理 客户端第一次请求服务器的时候&#xff0c;服务器会把数据进行缓存&#xff0c;同时会生成一个缓存标识符&#…

day12 13-牛客67道剑指offer-JZ83、70、63、47、48、46、21、81

1. JZ83 剪绳子&#xff08;进阶版&#xff09; class Solution { public:int jumpFloorII(int number) {if(number < 1) return number;int temp 1;int res 0;/*2级台阶 23级台阶 44级台阶 65级台阶 16*/for(int i2; i<number; i){res 2 * temp;temp res;}return re…

docker 安装elasticsearch、kibana

下载es镜像 docker pull elasticsearch 启动es容器 docker run --name elasticsearch -p 9200:9200 -p 9300:9300 -e "discovery.typesingle-node" -e ES_JAVA_OPTS"-Xms512m -Xmx512m" -d elasticsearch 验证es界面访问 ​​​​​http://节点ip:9200/ ​…

应用在汽车前照灯系统中的环境光传感芯片

为了保证行车照明的安全性和方便性&#xff0c;减轻驾驶员的劳动强度。近年来&#xff0c;出现了许多新的照明控制系统&#xff0c;例如用于日间驾驶的自动照明系统、光束调节系统、延迟控制等。尤其是汽车自适应前照灯系统&#xff0c;它是一种能够自动改变两种以上的光型以适…

零售行业供应链管理核心KPI指标(一) – 能力、速度、效率和成本

有关零售行业供应链管理KPI指标的综合性分享&#xff0c;涉及到供应链能力、速度、效率和成本总共九大指标&#xff0c;是一个大框架&#xff0c;比较核心也比较综合。 衡量消费品零售企业供应链管理效率和水平的核心KPI通常有哪些&#xff1f; 图片来源-派可数据&#xff08;…

SpringBoot 操作Redis、创建Redis文件夹、遍历Redis文件夹

文章目录 前言依赖连接 RedisRedis 配置文件Redis 工具类操作 Redis创建 Redis 文件夹查询数据遍历 Redis 文件夹 前言 Redis 是一种高性能的键值存储数据库&#xff0c;支持网络、可基于内存亦可持久化的日志型&#xff0c;而 Spring Boot 是一个简化了开发过程的 Java 框架。…

【ES6】—解构赋值

一、定义 解构赋值&#xff1a;解构赋值就是一种模式的匹配&#xff0c;只要等号两边的模式完全相同的&#xff0c;那么左边的变量就会被赋值对应右边的值 二、数组的解构赋值 PS&#xff1a;数组解构赋值时&#xff0c;是通过索引的唯一性赋值的 1. 一维数组解构赋值 (1)…

《Go 语言第一课》课程学习笔记(六)

变量声明&#xff1a;静态语言有别于动态语言的重要特征 变量所绑定的内存区域是要有一个明确的边界的。也就是说&#xff0c;通过这样一个变量&#xff0c;我们究竟可以操作 4 个字节内存还是 8 个字节内存&#xff0c;又或是 256 个字节内存&#xff0c;编程语言的编译器或解…

nginx部署时http接口正常,ws接口404

可以这么配置 map $http_upgrade $connection_upgrade {default upgrade; close; }upstream wsbackend{server ip1:port1;server ip2:port2;keepalive 1000; }server {listen 20038;location /{ proxy_http_version 1.1;proxy_pass http://wsbackend;proxy_redirect off;proxy…

FreeRTOS源码分析-12 低功耗管理

目录 1 STM32低功耗管理概念及应用 1.1睡眠模式 1.2 停止模式 1.3 待机模式 2 Tickless低功耗管理 2.1 Tickless低功耗模式介绍 2.2 FreeRTOS低功耗模式配置 2.3 FreeRTOS低功耗模式应用 3 低功耗管理实际项目开发 3.1 低功耗设计必须要掌握的硬件知识 …

LangChain-ChatGLM在WIndows10下的部署

LangChain-ChatGLM在WIndows10下的部署 参考资料 1、LangChain ChatGLM2-6B 搭建个人专属知识库中的LangChain ChatGLM2-6B 构建知识库这一节&#xff1a;基本的逻辑和步骤是对的&#xff0c;但要根据Windows和现状做很多调整。 2、没有动过model_config.py中的“LORA_MOD…