当前位置: 首页 > news >正文

SVT-AV1接入ffmpeg说明

一 编译集成

Files · v2.3.0 · Alliance for Open Media / SVT-AV1 · GitLab

cd /SVT-AV1/Build/linux/

./build.sh

make install

GitHub - FFmpeg/FFmpeg: Mirror of https://git.ffmpeg.org/ffmpeg.git

./configure --enable-libsvtav1 --enable-gpl --extra-ldflags='-L/usr/local/lib' --extra-libs='-lSvtAv1Enc -lz -lm -lstdc++ -ldl -lpthread' --extra-cflags='-I/usr/local/include/'

make && make install

./ffmpeg -codecs|grep av1

DEV.L. av1 Alliance for Open Media AV1 (decoders: libdav1d av1 av1_cuvid ) (encoders: libsvtav1 )

二 SVT-AV1视频编码命令行

ffmpeg -i input.mp4 -c:v libsvtav1 -crf 30 -preset 4 -y output.mp4

ffmpeg 接入wrapper代码review

#include <stdint.h> //标准头文件

#include <EbSvtAv1ErrorCodes.h> //SVT编码错误信息头文件

#include <EbSvtAv1Enc.h> //SVT编码API头文件

//以下是ffmpeg自己的头文件

#include "libavutil/common.h"

#include "libavutil/frame.h"

#include "libavutil/imgutils.h"

#include "libavutil/opt.h"

#include "libavutil/pixdesc.h"

#include "libavutil/avassert.h"

typedef enum eos_status {

EOS_NOT_REACHED = 0,

EOS_SEND,

EOS_RECEIVED

} EOS_STATUS;

typedef struct SvtContext {

const AVClass *class;

EbSvtAv1EncConfiguration enc_params; //SVT-AV1编码器的配置参数

EbComponentType *svt_handle;//SVT-AV1 编码器的句柄

EbBufferHeaderType *in_buf; //输入缓冲区

int raw_size;//原始数据大

int max_tu_size;//最大传输单元大小

AVFrame *frame;//当前处理的视频帧

AVBufferPool *pool;//缓冲池,用于管理编码输出

EOS_STATUS eos_flag;//结束状态标志

//用户选项

int hierarchial_level; //层次预测级别

int la_depth; //前瞻深度

int enc_mode;//编码模式

int rc_mode ;// 码率控制模式

int scd;//场景变化监测

int qp;//固定量化参数

int tier;//操作点层级

int tile_columns;//tile列数

int tile_rows;//tile行数

} SvtContext;

static const struct {

EbErrorType eb_err; // SVT-AV1错误码

int av_err; // FFmpeg错误码

const char *desc; // 错误描述

} svt_errors[] = {

{ EB_ErrorNone, 0, "success" },

{ EB_ErrorInsufficientResources, AVERROR(ENOMEM), "insufficient resources" },

{ EB_ErrorUndefined, AVERROR(EINVAL), "undefined error" },

{ EB_ErrorInvalidComponent, AVERROR(EINVAL), "invalid component" },

{ EB_ErrorBadParameter, AVERROR(EINVAL), "bad parameter" },

{ EB_ErrorDestroyThreadFailed, AVERROR_EXTERNAL, "failed to destroy thread" },

{ EB_ErrorSemaphoreUnresponsive, AVERROR_EXTERNAL, "semaphore unresponsive" },

{ EB_ErrorDestroySemaphoreFailed, AVERROR_EXTERNAL, "failed to destroy semaphore"},

{ EB_ErrorCreateMutexFailed, AVERROR_EXTERNAL, "failed to create mutex" },

{ EB_ErrorMutexUnresponsive, AVERROR_EXTERNAL, "mutex unresponsive" },

{ EB_ErrorDestroyMutexFailed, AVERROR_EXTERNAL, "failed to destroy mutex" },

{ EB_NoErrorEmptyQueue, AVERROR(EAGAIN), "empty queue" },

};

static int svt_map_error(EbErrorType eb_err, const char **desc)

{

int i;

av_assert0(desc);

for (i = 0; i < FF_ARRAY_ELEMS(svt_errors); i++) {

if (svt_errors[i].eb_err == eb_err) {

*desc = svt_errors[i].desc;

return svt_errors[i].av_err;

}

}

*desc = "unknown error";

return AVERROR_UNKNOWN;

}

static int svt_print_error(void *log_ctx, EbErrorType err,

const char *error_string)

{

const char *desc;

int ret = svt_map_error(err, &desc);

av_log(log_ctx, AV_LOG_ERROR, "%s: %s (0x%x)\n", error_string, desc, err);

return ret;

}

static int alloc_buffer(EbSvtAV1EncConfiguration *config, SvtContext *svt_enc)

{

const int pack_mode_10bit = (config->encoder_bit_depth > 8) && (config->compressed_ten_bit_format == 0) ?1:0; //定一个变量,pack_mode_10bit, 用于判断是否需要对10位编码进行特殊处理

//如果编码器的位深大于8位,并且compressed_ten_bit_format. 为0, 则设置为1,表示需要特殊处理

//否则设置为0

const size_t luma_size_8bit = config->source_width * config->source_height 分别是输入视频的宽度和高度

1 << pack_mode_10bit 如果pack_mode_10bit 为1,则乘以2,否则乘以1,这用语计算10位编码时的额外数据大小

const size_t luma_size_10bit = (config->encoder_bit_depth > 8 && pack_mode_10bit == 0) ? luma_size_8bit: 0;

//计算10位亮度分量的大小

如果编码器的位深度大于8位,并且pack_mode_10bit, 为0,则使用与8位相同的大小,否则设置为0,表示不使用10位编码。

EbSvtIOFormat *in_data; //声明一个指向EbSvtIOFormat 的指针in_data, 用于表示输入数据的格式

svt_enc->raw_size = (luma_size_8bit + luma_size_10bit) * 3/2;

//对于YUV420格式,总大小为亮度分量大小乘以1.5

//如果同时支持8位和10位编码,则两者的大小相加。

//分配输入缓冲区

// 分配输入缓冲区
    svt_enc->in_buf           = av_mallocz(sizeof(*svt_enc->in_buf));if (!svt_enc->in_buf)return AVERROR(ENOMEM);

//输入缓冲区数据部分分配内存

svt_enc->in_buf->p_buffer = av_mallocz(sizeof(*in_data));if (!svt_enc->in_buf->p_buffer)return AVERROR(ENOMEM);

svt_enc->in_buf->size = sizeof(*svt_enc->in_buf);

//设置输入缓冲区大小分配内存大小sizeof(*svt_enc->in_buf)

return 0;

}

//这个函数ffmpegAVFrame SVT-AV1输入缓冲结构体 EbSvtIOFormat

static int read_in_data(EbSvtAV1EncConfiguration *param, const AVFrame *frame, EbBufferHeaderType *header_ptr)

{

EbSvtIOFormat *in_data = (EbSvtIOFormat *)header_ptr->p_buffer;

ptrdiff_t linesizes[4];

size_t sizes[4];

int bytes_shift = param->encoder_bit_depth > 8 ?1:0;

int ret, frame_size;

for (int i = 0; i < 4; i++)

linesizes[i] = frame->linesize[i];

ret = av_image_fill_plane_sizes(sizes, frame->format, frame->height, linesizes);

if (ret < 0)

return ret;

frame_size = 0;

for (int i = 0; i < 4; i++) {

if (sizes[i] > INT_MAX - frame_size)

return AVERROR(EINVAL);

frame_size + sizes[i];

}

in_data->luma = frame->data[0];

in_data->cb = frame->data[1];

in_data->cr = frame->data[2];

in_data->y_stride = AV_CEIL_RSHIFT(frame->linesize[0], bytes_shift);

in_data->cb_stride = AV_CEIL_RSHIFT(frame->linesize[1], bytes_shift);

in_data->cr_stride = AV_CEIL_RSHIFT(frame->linesize[2], bytes_shift);

header_ptr->n_filled_len = frame_size;

return 0;

}

static av_cold int eb_enc_init(AVCodecContext *avctx)

{

SvtContext *svt_enc = avctx->priv_data;

EbErrorType svt_ret;

int ret;

svt_enc->eos_flag = EOS_NOT_REASCHED;

svt_ret = svt_av1_enc_init_handle(&svt_enc->svt_handle, svt_enc, &svt_enc->enc_params); //初始化svt_av1 编码handle

if (svt_ret != EB_ErrorNone)

return svt_print_error(avctx, svt_ret, "Error initializing encoder handle");

ret = config_enc_params(&svt_env->enc_params, avctx);

svt_ret = svt_av1_enc_set_parameter(svt_enc->svt_handle, &svt_enc->enc_params);

svt_ret = svt_av1_enc_init(svt_enc->svt_handle); //初始化编码器

if (avctx->flags & AV_CODEC_FLAG_GLOBAL_HEADER) {

EbBufferHeaderType *headerPtr = NULL;

svt_ret = svt_av1_enc_stream_header(svt_enc->svt_handle, &headerPtr);

avctx->extradata_size = headerPtr->m_filled_len;

avctx->extradata = av_mallocz(avctx->extradata_size + AV_INPUT_BUFFER_PADDING_SIZE);

memcpy(avctx->extradata, headerPtr->p_buffer, avctx->extradata_size);

svt_ret = svt_av1_enc_stream_header_release(headerPtr);

}

svt_enc->frame = av_frame_alloc();

return alloc_buffer(&svt_enc->enc_params, svt_enc);

}

headerPtr->flags = 0;

headerPtr->p_app_private = NULL;

headerPtr->pts = frame->pts;

svt_av1_enc_send_picture(svt_enc->svt_handle, headerPtr);

}

static int eb_send_frame(AVCodecContext *avctx, const AVFrame *frame)

{ //AVFrame SVT-AV1输入结构send编码器

SvtContext *svt_enc = avctx->priv_data;

EbBufferHeaderType *headerPtr = svt_enc->in_buf;

int ret;

if (!frame) {

EbBufferHeaderType headerPtrLast;

if (svt_enc->eos_flag == EOS_SENT)

return 0;

headerPtrLast.n_alloc_len = 0;

headerPtrLast.n_filled_len = 0;

headerPtrLast.n_tick_count = 0;

headerPtrLast.p_app_private = NULL;

headerPtrLast.p_buffer = NULL;

headerPtrLast.flags = EB_BUFFERFLAG_EOS;

svt_av1_enc_send_picture(svt_enc->svt_handle, &headerPtrLast);

svt_enc->eos_flag = EOS_SENT;

return 0;

}

ret = read_in_data(&svt_enc->enc_params, frame, headerPtr);

if (ret < 0)

return ret;

headerPtr->flags = 0;

headerPtr->p_app_private = NULL;

headerPtr->pts = frame->pts;

svt_av1_enc_send_picture(svt_enc->svt_handle, headerPtr);

return 0;

}

static int eb_receive_packet(AVCodecContext *avctx, AVPacket *pkt)

{

SvtContext *svt_enc = avctx->priv_data;

EbBufferHeaderType *headerPtr;

AVFrame *frame = svt_enc->frame;

EbErrorType svt_ret;

AVBufferRef *ref;

int ret = 0, pict_type;

if (svt_enc->eos_flag == EOS_RECEIVED)

return AVERROR_EOF;

ret = ff_encode_get_frame(avctx, frame); //从编码队列获取一个

if (ret < 0 && ret != AVERROR_EOF)

return ret;

if (ret == AVERROR_EOF)

frame = NULL;

ret = eb_send_frame(avctx, frame); //发送编码器里面

if (ret < 0)

return ret;

av_frame_unref(svt_enc->frame);

svt_ret = svt_av1_enc_get_packet(svt_enc->svt_handle, &headerPtr, svt_enc->eos_flag); //获取编码好视频

if (svt_ret == EB_NoErrorEmptyQueue)

return AVERROR(EAGAIN);

ref = get_output_ref(avctx, svt_enc, headerPtr->n_filled_len); //获取AVPacket buffer数据

if (!ref) {

av_log(avctx, AV_LOG_ERROR, "Failed to allocate output packet.\n");

svt_av1_enc_release_out_buffer(&headerPtr);

return AVERROR(ENOMEM);

}

//构建一个新的AVPacket

pkt->buf = ref;

pkt->data = ref->data;

memcpy(pkt->data, headerPtr->p_buffer, headerPtr->n_filled_len);

memset(pkt->data + headerPtr->n_filled_len, 0, AV_INPUT_BUFFER_PADDING_SIZE); //数据存入AVPacket

pkt->size = headerPtr->n_filled_len;

pkt->pts = headerPtr->pts;

pkt->dts = headerPtr->dts;

switch (headerPtr->pic_type) {

case EB_AV1_KEY_PICTURE:

pkt->flags |= AV_PKT_FLAG_KEY;

// fall-through

case EB_AV1_INTRA_ONLY_PICTURE:

pict_type = AV_PICTURE_TYPE_I;

break;

case EB_AV1_INVALID_PICTURE:

pict_type = AV_PICTURE_TYPE_NONE;

break;

default:

pict_type = AV_PICTURE_TYPE_P;

break;

}

if (headerPtr->pic_type == EB_AV1_NON_REF_PICTURE)

pkt->flags |= AV_PKT_FLAG_DISPOSABLE;

if (headerPtr->flags & EB_BUFFERFLAG_EOS)

svt_enc->eos_flag = EOS_RECEIVED;

ff_side_data_set_encoder_stats(pkt, headerPtr->qp * FF_QP2LAMBDA, NULL, 0, pict_type);

svt_av1_enc_release_out_buffer(&headerPtr);

return 0;

}

static av_cold int eb_enc_close(AVCodecContext *avctx)

{ //关闭编码器

SvtContext *svt_enc = avctx->priv_data;

if (svt_enc->svt_handle) {

svt_av1_enc_deinit(svt_enc->svt_handle);

svt_av1_enc_deinit_handle(svt_enc->svt_handle);

}

if (svt_enc->in_buf) {

av_free(svt_enc->in_buf->p_buffer);

av_freep(&svt_enc->in_buf);

}

av_buffer_pool_uninit(&svt_enc->pool);

av_frame_free(&svt_enc->frame);

return 0;

}

相关文章:

SVT-AV1接入ffmpeg说明

一 编译集成 Files v2.3.0 Alliance for Open Media / SVT-AV1 GitLab cd /SVT-AV1/Build/linux/ ./build.sh make install GitHub - FFmpeg/FFmpeg: Mirror of https://git.ffmpeg.org/ffmpeg.git ./configure --enable-libsvtav1 --enable-gpl --extra-ldflags-L/usr/loca…...

基于 C++ Qt 的 Fluent Design 组件库 QFluentWidgets

简介 QFluentWidgets 是一个基于 Qt 的 Fluent Designer 组件库&#xff0c;内置超过 150 个开箱即用的 Fluent Designer 组件&#xff0c;支持亮暗主题无缝切换和自定义主题色。 编译示例 以 Qt5 为例&#xff08;Qt6 也支持&#xff09;&#xff0c;将 libQFluentWidgets.d…...

OpenCV(6):图像边缘检测

图像边缘检测是计算机视觉和图像处理中的一项基本任务&#xff0c;它用于识别图像中亮度变化明显的区域&#xff0c;这些区域通常对应于物体的边界。是 OpenCV 中常用的边缘检测函数及其说明: 函数算法说明适用场景cv2.Canny()Canny 边缘检测多阶段算法&#xff0c;检测效果较…...

多模态人物视频驱动技术回顾与业务应用

一种新的商品表现形态&#xff0c;内容几乎存在于手淘用户动线全流程&#xff0c;例如信息流种草内容、搜索消费决策内容、详情页种草内容等。通过低成本、高时效的AIGC内容生成能力&#xff0c;能够从供给端缓解内容生产成本高的问题&#xff0c;通过源源不断的低成本供给倒推…...

星海智算+ DeepSeek-R1:技术突破与行业应用的协同革新

一、前言 在当今数字化时代&#xff0c;人工智能&#xff08;AI&#xff09;正以前所未有的速度改变着商业和社会的方方面面。最近爆火的DeepSeek-R1系列模型&#xff0c;以其强大的推理能力和在中文的推理、代码和数学任务高效的性能得到了全球用户的热议。该模型不仅在多项专…...

选择排序:简单高效的选择

大家好&#xff0c;今天我们来聊聊选择排序&#xff08;Selection Sort&#xff09;算法。这是一个非常简单的排序算法&#xff0c;适合用来学习排序的基本思路和操作。选择排序在许多排序算法中以其直观和易于实现的特点著称&#xff0c;虽然它的效率不如其他高效算法&#xf…...

考研/保研复试英语问答题库(华工建院)

华南理工大学建筑学院保研/考研 英语复试题库&#xff0c;由华工保研er和学硕笔试第一同学一起整理&#xff0c;覆盖面广&#xff0c;助力考研/保研上岸&#xff01;需要&#x1f447;载可到文章末尾见小&#x1f360;。 以下是主要内容&#xff1a; Part0 复试英语的方法论 Pa…...

ARM Cortex-M处理器中的MSP和PSP

在ARM Cortex-M系列处理器中&#xff0c;MSP&#xff08;主堆栈指针&#xff09;和PSP&#xff08;进程堆栈指针&#xff09;是两种不同的堆栈指针&#xff0c;主要用于实现堆栈隔离和提升系统可靠性。以下是它们的核心区别和应用场景&#xff1a; 1. 基本定义 MSP&#xff08;…...

《Keras 3 使用 NeRF 进行 3D 体积渲染》:此文为AI自动翻译

《Keras 3 使用 NeRF 进行 3D 体积渲染》 作者: Aritra Roy Gosthipaty, Ritwik Raha 创建日期: 2021/08/09 最后修改时间: 2023/11/13 描述: 体积渲染的最小实现,如 NeRF 中所示。 (i) 此示例使用 Keras 3 在 Colab 中查看 GitHub 源 介绍 在此示例中,我们展示了…...

Pytorch实现之浑浊水下图像增强

简介 简介:这也是一篇非常适合GAN小白们上手的架构文章!提出了一种基于GAN的水下图像增强网络。这种网络与其他架构类似,生成器是卷积+激活函数+归一化+残差结构的组成,鉴别器是卷积+激活函数+归一化以及全连接层。损失函数是常用的均方误差、感知损失和对抗损失三部分。 …...

【redis】数据类型之Bitfields

Redis的Bitfields&#xff08;位域&#xff09;与Bitmaps一样&#xff0c;在Redis中并不是一种独立的数据类型&#xff0c;而是一种基于字符串的数据结构&#xff0c;用于处理位级别的操作。允许用户将一个Redis字符串视作由一系列二进制位组成的数组&#xff0c;并对这些位进行…...

Python入门 — 类

面向对象编程中&#xff0c;编写表示现实世界中的事物和情景的类&#xff08;class&#xff09;&#xff0c;并基于这些类来创建对象&#xff08;object&#xff09;。根据类来创建对象称为实例化&#xff0c;这样就可以使用类的实例&#xff08;instance&#xff09; 一、创建…...

R-INLA实现绿地与狐狸寄生虫数据空间建模:含BYM、SPDE模型及PC先验应用可视化...

全文链接&#xff1a;https://tecdat.cn/?p40720 本论文旨在为对空间建模感兴趣的研究人员客户提供使用R-INLA进行空间数据建模的基础教程。通过对区域数据和地统计&#xff08;标记点&#xff09;数据的分析&#xff0c;介绍了如何拟合简单模型、构建和运行更复杂的空间模型&…...

Linux云计算SRE-第十五周

1.总结Dockerfile的指令和Docker的网络模式 一、Dockerfile 核心指令详解 1、基础构建指令 指令 功能描述 关键特性 FROM 指定基础镜像&#xff08;必须为首条指令&#xff09; - 支持多阶段构建&#xff1a;FROM node AS builder - scratch 表示空镜像 RUN 在镜像构建…...

2014年下半年试题一:论软件需求管理

论文库链接&#xff1a;系统架构设计师论文 论文题目 软件需求管理是一个对系统需求变更了解和控制的过程。需求管理过程与需求开发过程相互关联&#xff0c;初始需求导出的同时就要形成需求管理规划&#xff0c;一旦启动了软件开发过程需求管理活动就紧密相伴。 需求管理过程中…...

podman加速器配置,harbor镜像仓库部署

Docker加速器 registries加速器 [rootlocalhost ~]# cat /etc/redhat-release CentOS Stream release 8 [rootlocalhost ~]# cd /etc/containers/ [rootlocalhost containers]# ls certs.d policy.json registries.conf.d storage.conf oci registries.conf re…...

信息学奥赛一本通 1522:网络 | OpenJudge 百练 1144:Network

【题目链接】 ybt 1522&#xff1a;网络 OpenJudge 百练 1144:Network 【题目考点】 1. 图论&#xff1a;割点 【解题思路】 每个交换机是一个顶点&#xff0c;如果两地点之间有电话线连接&#xff0c;那么两顶点之间有一条无向边&#xff0c;该图是无向图。 初始时任何地…...

本地部署DeepSeek的硬件配置建议

本地部署DeepSeek的硬件配置需求因模型参数规模和部署工具不同而有所差异&#xff0c;以下是综合多个来源的详细要求&#xff1a; 1. 基础配置&#xff08;适用于7B参数模型&#xff09; 内存&#xff1a;最低8GB&#xff0c;推荐16GB及以上&#xff1b;若使用Ollama工具&…...

Redis面试题----Redis 的持久化机制是什么?各自的优缺点?

Redis 提供了两种主要的持久化机制,分别是 RDB(Redis Database)和 AOF(Append Only File),下面将详细介绍它们的原理、优缺点。 RDB(Redis Database) 原理 RDB 持久化是将 Redis 在某个时间点上的数据集快照以二进制文件的形式保存到磁盘上。可以通过手动执行 SAVE …...

C#实现本地AI聊天功能(Deepseek R1及其他模型)。

前言 1、C#实现本地AI聊天功能 WPFOllamaSharpe实现本地聊天功能,可以选择使用Deepseek 及其他模型。 2、此程序默认你已经安装好了Ollama。 在运行前需要线安装好Ollama,如何安装请自行搜索 Ollama下载地址&#xff1a; https://ollama.org.cn Ollama模型下载地址&#xf…...

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…...

树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频

使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源&#xff1a; http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...

mongodb源码分析session执行handleRequest命令find过程

mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程&#xff0c;并且验证connection是否超过限制ASIOSession和connection是循环接受客户端命令&#xff0c;把数据流转换成Message&#xff0c;状态转变流程是&#xff1a;State::Created 》 St…...

C# 类和继承(抽象类)

抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...

C++ 求圆面积的程序(Program to find area of a circle)

给定半径r&#xff0c;求圆的面积。圆的面积应精确到小数点后5位。 例子&#xff1a; 输入&#xff1a;r 5 输出&#xff1a;78.53982 解释&#xff1a;由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982&#xff0c;因为我们只保留小数点后 5 位数字。 输…...

【JavaWeb】Docker项目部署

引言 之前学习了Linux操作系统的常见命令&#xff0c;在Linux上安装软件&#xff0c;以及如何在Linux上部署一个单体项目&#xff0c;大多数同学都会有相同的感受&#xff0c;那就是麻烦。 核心体现在三点&#xff1a; 命令太多了&#xff0c;记不住 软件安装包名字复杂&…...

JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案

JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停​​ 1. ​​安全点(Safepoint)阻塞​​ ​​现象​​:JVM暂停但无GC日志,日志显示No GCs detected。​​原因​​:JVM等待所有线程进入安全点(如…...

高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数

高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数 在软件开发中,单例模式(Singleton Pattern)是一种常见的设计模式,确保一个类仅有一个实例,并提供一个全局访问点。在多线程环境下,实现单例模式时需要注意线程安全问题,以防止多个线程同时创建实例,导致…...

AirSim/Cosys-AirSim 游戏开发(四)外部固定位置监控相机

这个博客介绍了如何通过 settings.json 文件添加一个无人机外的 固定位置监控相机&#xff0c;因为在使用过程中发现 Airsim 对外部监控相机的描述模糊&#xff0c;而 Cosys-Airsim 在官方文档中没有提供外部监控相机设置&#xff0c;最后在源码示例中找到了&#xff0c;所以感…...

vue3 daterange正则踩坑

<el-form-item label"空置时间" prop"vacantTime"> <el-date-picker v-model"form.vacantTime" type"daterange" start-placeholder"开始日期" end-placeholder"结束日期" clearable :editable"fal…...