obs源码分析与调试:obs初始化过程

英语能力比较好的读者可以先看看官方文档:
obsproject.com/docs/backen…
OBS 的后端(即 libobs)使用 C 语言实现,提供了最核心的功能,包括:主流程、音视频子系统、通用的插件框架。

  • core/libobs/libobs 定义了最核心的数据类型和初始化方法
  • core/libobs/media-io 关于“音频编码和输出”、“视频编码和输出”最核心的流程都在这里
  • core/libobs/graphics 关于“视频渲染”最核心的方法都在这里

OBS 的前端(即 obs)基于 Qt/C++ 实现,实现了 UI 层的逻辑,可以调用 libobs 的方法与后端交互。

  • frontend/obs/obs-app.cpp 应用程序的入口
  • frontend/obs/window-basic-main.cpp 主窗口

main 函数

f10 vs将跳转到mian函数第一行:

D:\opensrc\obs\obs_src_19041\obs_src_19041\obs-studio-19141\obs-studio\UI\obs-app.cpp

前期基本工作 

设置程序crash 捕获dmp

obs_init_win32_crash_handler

设置日志

base_get_log_handler(&def_log_handler, nullptr);

更新系统设置

upgrade_settings();

路径:Administrator\AppData\Roaming\obs-studio/basic/profiles

初始化curl

curl_global_init(CURL_GLOBAL_ALL);

运行程序

int ret = run_program(logFile, argc, argv

qt高清dpi设置

#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))QGuiApplication::setAttribute(opt_disable_high_dpi_scaling? Qt::AA_DisableHighDpiScaling: Qt::AA_EnableHighDpiScaling);
#endif
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) && defined(_WIN32)QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
#endifQCoreApplication::addLibraryPath(".");

);

QApplication

OBSApp program(argc, argv, profilerNameStore.get());
 

多语言

OBSTranslator

程序单例运行

RunOnceMutex rom = GetRunOnceMutex(already_running);

启动主窗口

bool OBSApp::OBSInit()

obs初始化

if (!StartupOBS(locale.c_str(), GetProfilerNameStore()))return false;

调用D:\opensrc\obs\obs_src_19041\obs_src_19041\obs-studio-19141\obs-studio\libobs\obs.c
bool obs_startup(const char *locale, const char *module_config_path,
         profiler_name_store_t *store) 

该功能调用了libobs 库的函数obs_startup

D:\opensrc\obs\obs_src_19041\obs_src_19041\obs-studio-19141\obs-studio\libobs\obs.h 定义了libobs的导出函数:

....../*** Initializes OBS** @param  locale              The locale to use for modules* @param  module_config_path  Path to module config storage directory*                             (or NULL if none)* @param  store               The profiler name store for OBS to use or NULL*/
EXPORT bool obs_startup(const char *locale, const char *module_config_path,profiler_name_store_t *store);/** Releases all data associated with OBS and terminates the OBS context */
EXPORT void obs_shutdown(void);/** @return true if the main OBS context has been initialized */
EXPORT bool obs_initialized(void);
......

主窗口

主界面:OBSBasic.ui

 ResetAudio()  设置音频

ResetVideo() 设置视频

TimedCheckForUpdates  检查更新   

路径:C:\Users\Administrator\AppData\Roaming\obs-studio\updates 

系统托盘:SystemTray

消息循环

    ret = program.exec();

退出程序

	blog(LOG_INFO, "Number of memory leaks: %ld", bnum_allocs());base_set_log_handler(nullptr, nullptr);

OBS初始化 OBSBasic::OBSInit

(1)InitBasicConfig

(2) 音频初始化:音频线程创建

WASAPI的全称是Windows Audio Session API(Windows音频会话API),是从Windows Vista之后引入的UAA(Universal Audio Architecture)音频架构所属的API。

About WASAPI - Win32 apps | Microsoft Learn

bool OBSBasic::ResetAudio()
{ProfileScope("OBSBasic::ResetAudio");struct obs_audio_info ai;ai.samples_per_sec =config_get_uint(basicConfig, "Audio", "SampleRate");const char *channelSetupStr =config_get_string(basicConfig, "Audio", "ChannelSetup");if (strcmp(channelSetupStr, "Mono") == 0)ai.speakers = SPEAKERS_MONO;else if (strcmp(channelSetupStr, "2.1") == 0)ai.speakers = SPEAKERS_2POINT1;else if (strcmp(channelSetupStr, "4.0") == 0)ai.speakers = SPEAKERS_4POINT0;else if (strcmp(channelSetupStr, "4.1") == 0)ai.speakers = SPEAKERS_4POINT1;else if (strcmp(channelSetupStr, "5.1") == 0)ai.speakers = SPEAKERS_5POINT1;else if (strcmp(channelSetupStr, "7.1") == 0)ai.speakers = SPEAKERS_7POINT1;elseai.speakers = SPEAKERS_STEREO;return obs_reset_audio(&ai);
}

使用的是libobs 库的导出函数:d:\opensrc\obs\obs_src_19041\obs_src_19041\obs-studio-19141\obs-studio\libobs\obs.h

 * 设置基本音频输出格式/通道/样本/等
  *
  * @note 如果输出当前处于活动状态,则无法重置基本音频。

/*** Sets base audio output format/channels/samples/etc** @note Cannot reset base audio if an output is currently active.*/
EXPORT bool obs_reset_audio(const struct obs_audio_info *oai);
bool obs_reset_audio(const struct obs_audio_info *oai)
{struct audio_output_info ai;/* don't allow changing of audio settings if active. */if (obs->audio.audio && audio_output_active(obs->audio.audio))return false;obs_free_audio();if (!oai)return true;ai.name = "Audio";ai.samples_per_sec = oai->samples_per_sec;ai.format = AUDIO_FORMAT_FLOAT_PLANAR;ai.speakers = oai->speakers;ai.input_callback = audio_callback;blog(LOG_INFO, "---------------------------------");blog(LOG_INFO,"audio settings reset:\n""\tsamples per sec: %d\n""\tspeakers:        %d",(int)ai.samples_per_sec, (int)ai.speakers);return obs_init_audio(&ai);
}

static bool obs_init_audio(struct audio_output_info *ai)
{struct obs_core_audio *audio = &obs->audio;int errorcode;pthread_mutex_init_value(&audio->monitoring_mutex);if (pthread_mutex_init_recursive(&audio->monitoring_mutex) != 0)return false;audio->user_volume = 1.0f;audio->monitoring_device_name = bstrdup("Default");audio->monitoring_device_id = bstrdup("default");errorcode = audio_output_open(&audio->audio, ai);if (errorcode == AUDIO_OUTPUT_SUCCESS)return true;else if (errorcode == AUDIO_OUTPUT_INVALIDPARAM)blog(LOG_ERROR, "Invalid audio parameters specified");elseblog(LOG_ERROR, "Could not open audio output");return false;
}

创建音频线程D:\opensrc\obs\obs_src_19041\obs_src_19041\obs-studio-19141\obs-studio\libobs\media-io\audio-io.c

audio_output_open   :pthread_create(&out->thread, NULL, audio_thread, out


int audio_output_open(audio_t **audio, struct audio_output_info *info)
{struct audio_output *out;bool planar = is_audio_planar(info->format);if (!valid_audio_params(info))return AUDIO_OUTPUT_INVALIDPARAM;out = bzalloc(sizeof(struct audio_output));if (!out)goto fail0;memcpy(&out->info, info, sizeof(struct audio_output_info));out->channels = get_audio_channels(info->speakers);out->planes = planar ? out->channels : 1;out->input_cb = info->input_callback;out->input_param = info->input_param;out->block_size = (planar ? 1 : out->channels) *get_audio_bytes_per_channel(info->format);if (pthread_mutex_init_recursive(&out->input_mutex) != 0)goto fail0;if (os_event_init(&out->stop_event, OS_EVENT_TYPE_MANUAL) != 0)goto fail1;if (pthread_create(&out->thread, NULL, audio_thread, out) != 0)goto fail2;out->initialized = true;*audio = out;return AUDIO_OUTPUT_SUCCESS;fail2:os_event_destroy(out->stop_event);
fail1:pthread_mutex_destroy(&out->input_mutex);
fail0:audio_output_close(out);return AUDIO_OUTPUT_FAIL;
}

(3)视频初始化:视频线程创建

int OBSBasic::ResetVideo()
{if (outputHandler && outputHandler->Active())return OBS_VIDEO_CURRENTLY_ACTIVE;ProfileScope("OBSBasic::ResetVideo");struct obs_video_info ovi;int ret;//1 加载本地配置GetConfigFPS(ovi.fps_num, ovi.fps_den);const char *colorFormat =config_get_string(basicConfig, "Video", "ColorFormat");const char *colorSpace =config_get_string(basicConfig, "Video", "ColorSpace");const char *colorRange =config_get_string(basicConfig, "Video", "ColorRange");ovi.graphics_module = App()->GetRenderModule();ovi.base_width =(uint32_t)config_get_uint(basicConfig, "Video", "BaseCX");ovi.base_height =(uint32_t)config_get_uint(basicConfig, "Video", "BaseCY");ovi.output_width =(uint32_t)config_get_uint(basicConfig, "Video", "OutputCX");ovi.output_height =(uint32_t)config_get_uint(basicConfig, "Video", "OutputCY");ovi.output_format = GetVideoFormatFromName(colorFormat);ovi.colorspace = astrcmpi(colorSpace, "601") == 0? VIDEO_CS_601: (astrcmpi(colorSpace, "709") == 0? VIDEO_CS_709: VIDEO_CS_SRGB);ovi.range = astrcmpi(colorRange, "Full") == 0 ? VIDEO_RANGE_FULL: VIDEO_RANGE_PARTIAL;ovi.adapter =config_get_uint(App()->GlobalConfig(), "Video", "AdapterIdx");ovi.gpu_conversion = true;ovi.scale_type = GetScaleType(basicConfig);if (ovi.base_width < 8 || ovi.base_height < 8) {ovi.base_width = 1920;ovi.base_height = 1080;config_set_uint(basicConfig, "Video", "BaseCX", 1920);config_set_uint(basicConfig, "Video", "BaseCY", 1080);}if (ovi.output_width < 8 || ovi.output_height < 8) {ovi.output_width = ovi.base_width;ovi.output_height = ovi.base_height;config_set_uint(basicConfig, "Video", "OutputCX",ovi.base_width);config_set_uint(basicConfig, "Video", "OutputCY",ovi.base_height);}//2 调用int obs_reset_video(struct obs_video_info *ovi)ret = AttemptToResetVideo(&ovi);if (IS_WIN32 && ret != OBS_VIDEO_SUCCESS) {if (ret == OBS_VIDEO_CURRENTLY_ACTIVE) {blog(LOG_WARNING, "Tried to reset when ""already active");return ret;}/* Try OpenGL if DirectX fails on windows */if (astrcmpi(ovi.graphics_module, DL_OPENGL) != 0) {blog(LOG_WARNING,"Failed to initialize obs video (%d) ""with graphics_module='%s', retrying ""with graphics_module='%s'",ret, ovi.graphics_module, DL_OPENGL);ovi.graphics_module = DL_OPENGL;ret = AttemptToResetVideo(&ovi);}} else if (ret == OBS_VIDEO_SUCCESS) {ResizePreview(ovi.base_width, ovi.base_height);if (program)ResizeProgram(ovi.base_width, ovi.base_height);}if (ret == OBS_VIDEO_SUCCESS) {OBSBasicStats::InitializeValues();OBSProjector::UpdateMultiviewProjectors();}return ret;
}


int obs_reset_video(struct obs_video_info *ovi)
{if (!obs)return OBS_VIDEO_FAIL;/* don't allow changing of video settings if active. */if (obs->video.video && obs_video_active())return OBS_VIDEO_CURRENTLY_ACTIVE;if (!size_valid(ovi->output_width, ovi->output_height) ||!size_valid(ovi->base_width, ovi->base_height))return OBS_VIDEO_INVALID_PARAM;struct obs_core_video *video = &obs->video;stop_video();obs_free_video();/* align to multiple-of-two and SSE alignment sizes */ovi->output_width &= 0xFFFFFFFC;ovi->output_height &= 0xFFFFFFFE;if (!video->graphics) {int errorcode = obs_init_graphics(ovi);if (errorcode != OBS_VIDEO_SUCCESS) {obs_free_graphics();return errorcode;}}const char *scale_type_name = "";switch (ovi->scale_type) {case OBS_SCALE_DISABLE:scale_type_name = "Disabled";break;case OBS_SCALE_POINT:scale_type_name = "Point";break;case OBS_SCALE_BICUBIC:scale_type_name = "Bicubic";break;case OBS_SCALE_BILINEAR:scale_type_name = "Bilinear";break;case OBS_SCALE_LANCZOS:scale_type_name = "Lanczos";break;case OBS_SCALE_AREA:scale_type_name = "Area";break;}bool yuv = format_is_yuv(ovi->output_format);const char *yuv_format = get_video_colorspace_name(ovi->colorspace);const char *yuv_range =get_video_range_name(ovi->output_format, ovi->range);blog(LOG_INFO, "---------------------------------");blog(LOG_INFO,"video settings reset:\n""\tbase resolution:   %dx%d\n""\toutput resolution: %dx%d\n""\tdownscale filter:  %s\n""\tfps:               %d/%d\n""\tformat:            %s\n""\tYUV mode:          %s%s%s",ovi->base_width, ovi->base_height, ovi->output_width,ovi->output_height, scale_type_name, ovi->fps_num, ovi->fps_den,get_video_format_name(ovi->output_format),yuv ? yuv_format : "None", yuv ? "/" : "", yuv ? yuv_range : "");return obs_init_video(ovi);
}

obs_init_video

video_output_open 线程 :编解码相关

obs_graphics_thread :图形

static int obs_init_video(struct obs_video_info *ovi)
{struct obs_core_video *video = &obs->video;struct video_output_info vi;int errorcode;make_video_info(&vi, ovi);video->base_width = ovi->base_width;video->base_height = ovi->base_height;video->output_width = ovi->output_width;video->output_height = ovi->output_height;video->gpu_conversion = ovi->gpu_conversion;video->scale_type = ovi->scale_type;set_video_matrix(video, ovi);errorcode = video_output_open(&video->video, &vi);if (errorcode != VIDEO_OUTPUT_SUCCESS) {if (errorcode == VIDEO_OUTPUT_INVALIDPARAM) {blog(LOG_ERROR, "Invalid video parameters specified");return OBS_VIDEO_INVALID_PARAM;} else {blog(LOG_ERROR, "Could not open video output");}return OBS_VIDEO_FAIL;}gs_enter_context(video->graphics);if (ovi->gpu_conversion && !obs_init_gpu_conversion(ovi))return OBS_VIDEO_FAIL;if (!obs_init_textures(ovi))return OBS_VIDEO_FAIL;gs_leave_context();if (pthread_mutex_init(&video->gpu_encoder_mutex, NULL) < 0)return OBS_VIDEO_FAIL;if (pthread_mutex_init(&video->task_mutex, NULL) < 0)return OBS_VIDEO_FAIL;#ifdef __APPLE__errorcode = pthread_create(&video->video_thread, NULL,obs_graphics_thread_autorelease, obs);
#elseerrorcode = pthread_create(&video->video_thread, NULL,obs_graphics_thread, obs);
#endifif (errorcode != 0)return OBS_VIDEO_FAIL;video->thread_initialized = true;video->ovi = *ovi;return OBS_VIDEO_SUCCESS;
}

(4) dll 加载     obs_load_all_modules();

obs.c中定义了obs根对象

struct obs_core *obs = NULL;

(5) ResetOutputs 输出模式


void OBSBasic::ResetOutputs()
{ProfileScope("OBSBasic::ResetOutputs");const char *mode = config_get_string(basicConfig, "Output", "Mode");bool advOut = astrcmpi(mode, "Advanced") == 0;if (!outputHandler || !outputHandler->Active()) {outputHandler.reset();outputHandler.reset(advOut ? CreateAdvancedOutputHandler(this): CreateSimpleOutputHandler(this));delete replayBufferButton;delete replayLayout;if (outputHandler->replayBuffer) {replayBufferButton = new ReplayBufferButton(QTStr("Basic.Main.StartReplayBuffer"), this);replayBufferButton->setCheckable(true);connect(replayBufferButton.data(),&QPushButton::clicked, this,&OBSBasic::ReplayBufferClicked);replayBufferButton->setSizePolicy(QSizePolicy::Ignored,QSizePolicy::Fixed);replayLayout = new QHBoxLayout(this);replayLayout->addWidget(replayBufferButton);replayBufferButton->setProperty("themeID","replayBufferButton");ui->buttonsVLayout->insertLayout(2, replayLayout);setTabOrder(ui->recordButton, replayBufferButton);setTabOrder(replayBufferButton,ui->buttonsVLayout->itemAt(3)->widget());}if (sysTrayReplayBuffer)sysTrayReplayBuffer->setEnabled(!!outputHandler->replayBuffer);} else {outputHandler->Update();}
}

输出模式分简单模式与复杂模式:

BasicOutputHandler *CreateSimpleOutputHandler(OBSBasic *main)
{return new SimpleOutput(main);
}

简单输出的构造:

SimpleOutput::SimpleOutput(OBSBasic *main_) : BasicOutputHandler(main_)
{const char *encoder = config_get_string(main->Config(), "SimpleOutput","StreamEncoder");if (strcmp(encoder, SIMPLE_ENCODER_QSV) == 0) {LoadStreamingPreset_h264("obs_qsv11");} else if (strcmp(encoder, SIMPLE_ENCODER_AMD) == 0) {LoadStreamingPreset_h264("amd_amf_h264");} else if (strcmp(encoder, SIMPLE_ENCODER_NVENC) == 0) {const char *id = EncoderAvailable("jim_nvenc") ? "jim_nvenc": "ffmpeg_nvenc";LoadStreamingPreset_h264(id);} else {LoadStreamingPreset_h264("obs_x264");}if (!CreateAACEncoder(aacStreaming, aacStreamEncID, GetAudioBitrate(),"simple_aac", 0))throw "Failed to create aac streaming encoder (simple output)";if (!CreateAACEncoder(aacArchive, aacArchiveEncID, GetAudioBitrate(),SIMPLE_ARCHIVE_NAME, 1))throw "Failed to create aac arhive encoder (simple output)";LoadRecordingPreset();if (!ffmpegOutput) {bool useReplayBuffer = config_get_bool(main->Config(),"SimpleOutput", "RecRB");if (useReplayBuffer) {obs_data_t *hotkey;const char *str = config_get_string(main->Config(), "Hotkeys", "ReplayBuffer");if (str)hotkey = obs_data_create_from_json(str);elsehotkey = nullptr;replayBuffer = obs_output_create("replay_buffer",Str("ReplayBuffer"),nullptr, hotkey);obs_data_release(hotkey);if (!replayBuffer)throw "Failed to create replay buffer output ""(simple output)";obs_output_release(replayBuffer);signal_handler_t *signal =obs_output_get_signal_handler(replayBuffer);startReplayBuffer.Connect(signal, "start",OBSStartReplayBuffer, this);stopReplayBuffer.Connect(signal, "stop",OBSStopReplayBuffer, this);replayBufferStopping.Connect(signal, "stopping",OBSReplayBufferStopping,this);replayBufferSaved.Connect(signal, "saved",OBSReplayBufferSaved, this);}fileOutput = obs_output_create("ffmpeg_muxer", "simple_file_output", nullptr, nullptr);if (!fileOutput)throw "Failed to create recording output ""(simple output)";obs_output_release(fileOutput);}startRecording.Connect(obs_output_get_signal_handler(fileOutput),"start", OBSStartRecording, this);stopRecording.Connect(obs_output_get_signal_handler(fileOutput), "stop",OBSStopRecording, this);recordStopping.Connect(obs_output_get_signal_handler(fileOutput),"stopping", OBSRecordStopping, this);
}

使用ffmpeg_muxer : 

视音频复用器(Muxer)即是将视频压缩数据(例如H.264)和音频压缩数据(例如AAC)合并到一个封装格式数据(例如MKV)中去。如图所示。在这个过程中并不涉及到编码和解码。

obs_output_t *obs_output_create(const char *id, const char *name,obs_data_t *settings, obs_data_t *hotkey_data)
{const struct obs_output_info *info = find_output(id);struct obs_output *output;int ret;output = bzalloc(sizeof(struct obs_output));pthread_mutex_init_value(&output->interleaved_mutex);pthread_mutex_init_value(&output->delay_mutex);pthread_mutex_init_value(&output->caption_mutex);pthread_mutex_init_value(&output->pause.mutex);if (pthread_mutex_init(&output->interleaved_mutex, NULL) != 0)goto fail;if (pthread_mutex_init(&output->delay_mutex, NULL) != 0)goto fail;if (pthread_mutex_init(&output->caption_mutex, NULL) != 0)goto fail;if (pthread_mutex_init(&output->pause.mutex, NULL) != 0)goto fail;if (os_event_init(&output->stopping_event, OS_EVENT_TYPE_MANUAL) != 0)goto fail;if (!init_output_handlers(output, name, settings, hotkey_data))goto fail;os_event_signal(output->stopping_event);if (!info) {blog(LOG_ERROR, "Output ID '%s' not found", id);output->info.id = bstrdup(id);output->owns_info_id = true;} else {output->info = *info;}output->video = obs_get_video();output->audio = obs_get_audio();if (output->info.get_defaults)output->info.get_defaults(output->context.settings);ret = os_event_init(&output->reconnect_stop_event,OS_EVENT_TYPE_MANUAL);if (ret < 0)goto fail;output->reconnect_retry_sec = 2;output->reconnect_retry_max = 20;output->valid = true;output->control = bzalloc(sizeof(obs_weak_output_t));output->control->output = output;obs_context_data_insert(&output->context, &obs->data.outputs_mutex,&obs->data.first_output);if (info)output->context.data =info->create(output->context.settings, output);if (!output->context.data)blog(LOG_ERROR, "Failed to create output '%s'!", name);blog(LOG_DEBUG, "output '%s' (%s) created", name, id);return output;fail:obs_output_destroy(output);return NULL;
}

点击Ui上开始录制,调用堆栈如下;D:\opensrc\obs\obs_src_19041\obs_src_19041\obs-studio-19141\obs-studio\plugins\obs-ffmpeg\obs-ffmpeg-mux.c

obs.dll中使用了obs-ffmpeg.dll 

obs-ffmpeg库的加载:D:\opensrc\obs\obs_src_19041\obs_src_19041\obs-studio-19141\obs-studio\plugins\obs-ffmpeg\obs-ffmpeg.c

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

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

相关文章

基于微信小程序的上课签到系统(数据库+报告+文档+演示视频)

系统技术语言介绍&#xff1a; 本系统采用了SSM (Spring Spring MVC Mybatis&#xff09;架构&#xff0c; MySQL作为基础数据库&#xff0c;微信开发工具作为前端基础&#xff0c;前端采用了 wxml的设计语言&#xff0c;Idea作为后台的开发工具。 功能列表&#xff1a; 1.…

RabbitMQ 能保证消息可靠性吗

系列文章目录 消息队列选型——为什么选择RabbitMQ RabbitMQ 五种消息模型 RabbitMQ 能保证消息可靠性吗 系列文章目录前言一、消息可靠性的定义二、几种不可靠的场景三、防意外丢失1. 消息持久化2. 队列持久化3. 发布确认3.1 简单发布确认3.2 批量发布确认3.3 异步发布确认 4…

深度学习视角下的视频息肉分割

结直肠癌(CRC)是全球第二大致命癌症和第三大常见的恶性肿瘤&#xff0c;据估计每年会在全球范围内造成数百万人发病和死亡。结直肠癌患者在第一阶段的生存概率超过95%&#xff0c;但在第四和第五阶段却大幅下降到35%以下。因此&#xff0c;通过结肠镜、乙状结肠镜等筛查技术对阳…

【学习周报】

最近看过的几篇论文里&#xff0c;VALOR和InstructBLIP模型使用了cross-attention机制&#xff0c;以这两篇论文为基础着重学习cross-attention相关的代码和思路。 学习内容&#xff1a; cross-attention机制学习lstm与transformer 学习时间&#xff1a; 6.26 ~ 7.1 学习笔记…

ModaHub AI模型开源社区——向量数据库Milvus存储操作教程

目录 存储操作 数据插入 数据落盘 定时触发 客户端触发 缓冲区达到上限触发 数据合并 建立索引 删除 删除集合 删除分区 删除实体 数据段整理 数据读取 常见问题 存储操作 阅读本文前&#xff0c;请先阅读 存储相关概念。 数据插入 客户端通过调用 insert 接…

多通道分离与合并

1、分离 2、合并 Mat img imread("F:/testMap/plan.png");Mat imgs[3];split(img,imgs);//分离Mat img0,img1,img2;img0 imgs[0];img1 imgs[1]; img2 imgs[2];Mat img_H;merge(imgs,3,img_H);//合并vector<Mat> imgsV; imgsV.push_back(img0);imgsV.push_b…

git常用命令之分支操作

3. 分支 3.1 创建分支 场景1&#xff1a;创建新分支 工作中遇到的使用方式&#xff1a;1.拉取新分支&#xff0c;2.做新需求 /修复BUG &#xff0c;3.基于新分支提PR到目标分支 命令作用延展阅读git branch test11. 基于当前分支最新提交新建分支 test1(但不会切换到test1分…

Bootstrap 按钮

文章目录 Bootstrap 按钮按钮大小按钮状态激活状态禁用状态 按钮标签 Bootstrap 按钮 本章将通过实例讲解如何使用 Bootstrap 按钮。任何带有 class .btn 的元素都会继承圆角灰色按钮的默认外观。但是 Bootstrap 提供了一些选项来定义按钮的样式&#xff0c;具体如下表所示&…

Redis - Redis GEO实现经纬度测算距离,附近搜索范围

Redis GEO 主要用于存储地理位置信息&#xff0c;并对存储的信息进行操作&#xff0c;该功能在 Redis 3.2 版本新增 一、Redis GEO 操作方法 geoadd&#xff1a;添加地理位置的坐标 geopos&#xff1a;获取地理位置的坐标 geodist&#xff1a;计算两个位置之间的距离 geor…

【ArcGIS Pro二次开发】(43):线闭合

当我们需要将多段线【polyline】转为面【polygon】的时候&#xff0c;必须保证线是闭合的&#xff0c;不然是无法生成面的&#xff0c;如下图&#xff1a; 如果cad线段&#xff0c;可以在属性里将闭合选项设置为是&#xff0c;实现线的闭合&#xff1a; 但如果是在ArcGIS Pro里…

C++【初识哈希】

✨个人主页&#xff1a; 北 海 &#x1f389;所属专栏&#xff1a; C修行之路 &#x1f383;操作环境&#xff1a; Visual Studio 2019 版本 16.11.17 文章目录 &#x1f307;前言&#x1f3d9;️正文1、哈希思想2、哈希函数2.1、哈希函数的设计原则2.2、常见的哈希函数 3、哈希…

云原生——什么是云原生?

❄️作者介绍&#xff1a;奇妙的大歪❄️ &#x1f380;个人名言&#xff1a;但行前路&#xff0c;不负韶华&#xff01;&#x1f380; &#x1f43d;个人简介&#xff1a;云计算网络运维专业人员&#x1f43d; 前言 伴随云计算的滚滚浪潮&#xff0c;云原生(CloudNative…