OBS 源添加 底层逻辑

1,OBSBasic.ui 主界面

sourcesDock->工具栏上有 五个按钮,分别对应源的 添加,删除,设置,上移,下移

2, window-basic-main.hpp 中,定义了槽(这些action采用了默认的槽,所以看不到connect)

//上移
void OBSBasic::on_actionSourceUp_triggered()
{
	OBSSceneItem item = GetCurrentSceneItem(); //获得当前 SceneItem ,然后进行排序
	obs_sceneitem_set_order(item, OBS_ORDER_MOVE_UP);
}

//下移
void OBSBasic::on_actionSourceDown_triggered()
{
	OBSSceneItem item = GetCurrentSceneItem();
	obs_sceneitem_set_order(item, OBS_ORDER_MOVE_DOWN);
}

//设置
void OBSBasic::on_actionSourceProperties_triggered()
{
	OBSSceneItem item = GetCurrentSceneItem();
	OBSSource source = obs_sceneitem_get_source(item);

	if (source)
		CreatePropertiesWindow(source);
}

//添加
void OBSBasic::on_actionAddSource_triggered()
{
	AddSourcePopupMenu(QCursor::pos());
}

//移除
void OBSBasic::on_actionRemoveSource_triggered()
{
	vector<OBSSceneItem> items;

	obs_scene_enum_items(GetCurrentScene(), remove_items, &items);

	if (!items.size())
		return;

	auto removeMultiple = [this](size_t count) {
		QString text = QTStr("ConfirmRemove.TextMultiple")
				       .arg(QString::number(count));

		QMessageBox remove_items(this);
		remove_items.setText(text);
		QAbstractButton *Yes = remove_items.addButton(
			QTStr("Yes"), QMessageBox::YesRole);
		remove_items.addButton(QTStr("No"), QMessageBox::NoRole);
		remove_items.setIcon(QMessageBox::Question);
		remove_items.setWindowTitle(QTStr("ConfirmRemove.Title"));
		remove_items.exec();

		return Yes == remove_items.clickedButton();
	};

	if (items.size() == 1) {
		OBSSceneItem &item = items[0];
		obs_source_t *source = obs_sceneitem_get_source(item);

		if (source && QueryRemoveSource(source))
			obs_sceneitem_remove(item);
	} else {
		if (removeMultiple(items.size())) {
			for (auto &item : items)
				obs_sceneitem_remove(item);
		}
	}
}

3,添加一个source.

void OBSBasic::on_actionAddSource_triggered()
{
	AddSourcePopupMenu(QCursor::pos());
}

从当前poputMenu中,选择光标所选的 source进行添加,效果如下图

void OBSBasic::AddSourcePopupMenu(const QPoint &pos)
{
	if (!GetCurrentScene()) {
		// Tell the user he needs a scene first (help beginners).
		OBSMessageBox::information(
			this, QTStr("Basic.Main.AddSourceHelp.Title"),
			QTStr("Basic.Main.AddSourceHelp.Text"));
		return;
	}

	QScopedPointer<QMenu> popup(CreateAddSourcePopupMenu());
	if (popup)
		popup->exec(pos);
}
QMenu *OBSBasic::CreateAddSourcePopupMenu()
{
	const char *type;
	bool foundValues = false;
	bool foundDeprecated = false;
	size_t idx = 0;

	QMenu *popup = new QMenu(QTStr("Add"), this);
	QMenu *deprecated = new QMenu(QTStr("Deprecated"), popup);

	auto getActionAfter = [](QMenu *menu, const QString &name) {
		QList<QAction *> actions = menu->actions();

		for (QAction *menuAction : actions) {
			if (menuAction->text().compare(name) >= 0)
				return menuAction;
		}

		return (QAction *)nullptr;
	};

	auto addSource = [this, getActionAfter](QMenu *popup, const char *type,
						const char *name) {
		QString qname = QT_UTF8(name);
		QAction *popupItem = new QAction(qname, this);
		popupItem->setData(QT_UTF8(type));
		connect(popupItem, SIGNAL(triggered(bool)), this,
			SLOT(AddSourceFromAction()));

		QAction *after = getActionAfter(popup, qname);
		popup->insertAction(after, popupItem);
	};

	while (obs_enum_input_types(idx++, &type)) { //循环添加 source
		const char *name = obs_source_get_display_name(type);
		uint32_t caps = obs_get_source_output_flags(type);

		if ((caps & OBS_SOURCE_CAP_DISABLED) != 0)
			continue;

		if ((caps & OBS_SOURCE_DEPRECATED) == 0) {
			addSource(popup, type, name);
		} else {
			addSource(deprecated, type, name);
			foundDeprecated = true;
		}
		foundValues = true;
	}

	addSource(popup, "scene", Str("Basic.Scene"));

	popup->addSeparator();
	QAction *addGroup = new QAction(QTStr("Group"), this);
	addGroup->setData(QT_UTF8("group"));
	connect(addGroup, SIGNAL(triggered(bool)), this,
		SLOT(AddSourceFromAction()));
	popup->addAction(addGroup);

	if (!foundDeprecated) {
		delete deprecated;
		deprecated = nullptr;
	}

	if (!foundValues) {
		delete popup;
		popup = nullptr;

	} else if (foundDeprecated) {
		popup->addSeparator();
		popup->addMenu(deprecated);
	}

	return popup;
}
bool obs_enum_input_types(size_t idx, const char **id)
{
	if (!obs)
		return false;

	if (idx >= obs->input_types.num) 
		return false;
	*id = obs->input_types.array[idx].id;//获取obs的input_types
	return true;
}
bool obs_module_load(void)// 加载文本插件 
{
	obs_source_info si = {};
	si.id = "text_gdiplus";
	si.type = OBS_SOURCE_TYPE_INPUT;
	si.output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CUSTOM_DRAW;
	si.get_properties = get_properties;

	si.get_name = [](void *) { return obs_module_text("TextGDIPlus"); };
	si.create = [](obs_data_t *settings, obs_source_t *source) {
		return (void *)new TextSource(source, settings);
	};
	si.destroy = [](void *data) {
		delete reinterpret_cast<TextSource *>(data);
	};
	si.get_width = [](void *data) {
		return reinterpret_cast<TextSource *>(data)->cx;
	};
	si.get_height = [](void *data) {
		return reinterpret_cast<TextSource *>(data)->cy;
	};
	si.get_defaults = [](obs_data_t *settings) {
		obs_data_t *font_obj = obs_data_create();
		obs_data_set_default_string(font_obj, "face", "Arial");
		obs_data_set_default_int(font_obj, "size", 36);

		obs_data_set_default_obj(settings, S_FONT, font_obj);
		obs_data_set_default_string(settings, S_ALIGN, S_ALIGN_LEFT);
		obs_data_set_default_string(settings, S_VALIGN, S_VALIGN_TOP);
		obs_data_set_default_int(settings, S_COLOR, 0xFFFFFF);
		obs_data_set_default_int(settings, S_OPACITY, 100);
		obs_data_set_default_int(settings, S_GRADIENT_COLOR, 0xFFFFFF);
		obs_data_set_default_int(settings, S_GRADIENT_OPACITY, 100);
		obs_data_set_default_double(settings, S_GRADIENT_DIR, 90.0);
		obs_data_set_default_int(settings, S_BKCOLOR, 0x000000);
		obs_data_set_default_int(settings, S_BKOPACITY, 0);
		obs_data_set_default_int(settings, S_OUTLINE_SIZE, 2);
		obs_data_set_default_int(settings, S_OUTLINE_COLOR, 0xFFFFFF);
		obs_data_set_default_int(settings, S_OUTLINE_OPACITY, 100);
		obs_data_set_default_int(settings, S_CHATLOG_LINES, 6);
		obs_data_set_default_bool(settings, S_EXTENTS_WRAP, true);
		obs_data_set_default_int(settings, S_EXTENTS_CX, 100);
		obs_data_set_default_int(settings, S_EXTENTS_CY, 100);
		obs_data_set_default_int(settings, S_TRANSFORM,
					 S_TRANSFORM_NONE);

		obs_data_release(font_obj);
	};
	si.update = [](void *data, obs_data_t *settings) {
		reinterpret_cast<TextSource *>(data)->Update(settings);
	};
	si.video_tick = [](void *data, float seconds) {
		reinterpret_cast<TextSource *>(data)->Tick(seconds);
	};
	si.video_render = [](void *data, gs_effect_t *) {
		reinterpret_cast<TextSource *>(data)->Render();
	};

	obs_register_source(&si); //注册 插件

	const GdiplusStartupInput gdip_input;
	GdiplusStartup(&gdip_token, &gdip_input, nullptr);
	return true;
}

 

#define obs_register_source(info) \
	obs_register_source_s(info, sizeof(struct obs_source_info))
void obs_register_source_s(const struct obs_source_info *info, size_t size)
{
	struct obs_source_info data = {0};
	struct darray *array = NULL;

	if (info->type == OBS_SOURCE_TYPE_INPUT) {
		array = &obs->input_types.da;
	} else if (info->type == OBS_SOURCE_TYPE_FILTER) {
		array = &obs->filter_types.da;
	} else if (info->type == OBS_SOURCE_TYPE_TRANSITION) {
		array = &obs->transition_types.da;
	} else if (info->type != OBS_SOURCE_TYPE_SCENE) {
		source_warn("Tried to register unknown source type: %u",
			    info->type);
		goto error;
	}

	if (get_source_info(info->id)) {
		source_warn("Source '%s' already exists!  "
			    "Duplicate library?",
			    info->id);
		goto error;
	}

	memcpy(&data, info, size);

	/* mark audio-only filters as an async filter categorically */
	if (data.type == OBS_SOURCE_TYPE_FILTER) {
		if ((data.output_flags & OBS_SOURCE_VIDEO) == 0)
			data.output_flags |= OBS_SOURCE_ASYNC;
	}

	if (data.type == OBS_SOURCE_TYPE_TRANSITION) {
		if (data.get_width)
			source_warn("get_width ignored registering "
				    "transition '%s'",
				    data.id);
		if (data.get_height)
			source_warn("get_height ignored registering "
				    "transition '%s'",
				    data.id);
		data.output_flags |= OBS_SOURCE_COMPOSITE | OBS_SOURCE_VIDEO |
				     OBS_SOURCE_CUSTOM_DRAW;
	}

	if ((data.output_flags & OBS_SOURCE_COMPOSITE) != 0) {
		if ((data.output_flags & OBS_SOURCE_AUDIO) != 0) {
			source_warn("Source '%s': Composite sources "
				    "cannot be audio sources",
				    info->id);
			goto error;
		}
		if ((data.output_flags & OBS_SOURCE_ASYNC) != 0) {
			source_warn("Source '%s': Composite sources "
				    "cannot be async sources",
				    info->id);
			goto error;
		}
	}

#define CHECK_REQUIRED_VAL_(info, val, func) \
	CHECK_REQUIRED_VAL(struct obs_source_info, info, val, func)
	CHECK_REQUIRED_VAL_(info, get_name, obs_register_source);
	CHECK_REQUIRED_VAL_(info, create, obs_register_source);
	CHECK_REQUIRED_VAL_(info, destroy, obs_register_source);

	if (info->type != OBS_SOURCE_TYPE_FILTER &&
	    info->type != OBS_SOURCE_TYPE_TRANSITION &&
	    (info->output_flags & OBS_SOURCE_VIDEO) != 0 &&
	    (info->output_flags & OBS_SOURCE_ASYNC) == 0) {
		CHECK_REQUIRED_VAL_(info, get_width, obs_register_source);
		CHECK_REQUIRED_VAL_(info, get_height, obs_register_source);
	}

	if ((data.output_flags & OBS_SOURCE_COMPOSITE) != 0) {
		CHECK_REQUIRED_VAL_(info, audio_render, obs_register_source);
	}
#undef CHECK_REQUIRED_VAL_

	if (size > sizeof(data)) {
		source_warn("Tried to register obs_source_info with size "
			    "%llu which is more than libobs currently "
			    "supports (%llu)",
			    (long long unsigned)size,
			    (long long unsigned)sizeof(data));
		goto error;
	}

	if (array)
		darray_push_back(sizeof(struct obs_source_info), array, &data);
	da_push_back(obs->source_types, &data); //给obs source_types 赋值
	return;

error:
	HANDLE_ERROR(size, obs_source_info, info);
}

总结:

插件加载时进行注册

(1)obs_register_source_s中 定义 obs_struct_info 结构体,并填充。

(2)用obs_struct_info 结构体给全局 obs_core->source_types 赋值

 

 

4,设置 源

void OBSBasic::on_actionSourceProperties_triggered()
{
	OBSSceneItem item = GetCurrentSceneItem();
	OBSSource source = obs_sceneitem_get_source(item);

	if (source)
		CreatePropertiesWindow(source); //创建 属性 编辑窗体
}
void OBSBasic::CreatePropertiesWindow(obs_source_t *source)
{
	if (properties)
		properties->close();

	properties = new OBSBasicProperties(this, source); //属性编辑窗口,根据source决定窗口的布局
	properties->Init();
	properties->setAttribute(Qt::WA_DeleteOnClose, true);
}
//属性编辑窗口
class OBSBasicProperties : public QDialog {
	Q_OBJECT

private:
	QPointer<OBSQTDisplay> preview;

	OBSBasic *main;
	bool acceptClicked;

	OBSSource source;
	OBSSignal removedSignal;
	OBSSignal renamedSignal;
	OBSSignal updatePropertiesSignal;
	OBSData oldSettings;
	OBSPropertiesView *view;
	QDialogButtonBox *buttonBox;
	QSplitter *windowSplitter;

	OBSSource sourceA;
	OBSSource sourceB;
	OBSSource sourceClone;
	bool direction = true;

	static void SourceRemoved(void *data, calldata_t *params);
	static void SourceRenamed(void *data, calldata_t *params);
	static void UpdateProperties(void *data, calldata_t *params);
	static void DrawPreview(void *data, uint32_t cx, uint32_t cy);
	static void DrawTransitionPreview(void *data, uint32_t cx, uint32_t cy);
	void UpdateCallback(void *obj, obs_data_t *settings);
	bool ConfirmQuit();
	int CheckSettings();
	void Cleanup();

private slots:
	void on_buttonBox_clicked(QAbstractButton *button);
	void AddPreviewButton();

public:
	OBSBasicProperties(QWidget *parent, OBSSource source_);
	~OBSBasicProperties();

	void Init();

protected:
	virtual void closeEvent(QCloseEvent *event) override;
	virtual void reject() override;
};

 

要在OBS码中添加图片来,首先需要理解OBS的基本结构和工作原理。OBS(Open Broadcaster Software)是一款开的多媒体直播和录制软件,可以通过它进行实时视频和音频的捕获、混音、编码和广播。 在OBS中,码的添加是通过插件(Plugin)来实现的。插件是一种扩展功能的模块,可以将自定义的代码和资整合到OBS中。 添加图片的过程如下: 1. 创建一个新的插件项目。可以通过OBS提供的插件模板来创建一个新的插件项目,或者根据自己的需求进行修改。 2. 在插件的代码中,添加一个新的类型。这个新的类型就是图片,在代码中定义它的基本属性和方法。例如,可以定义图片路径、尺寸、位置等属性,以及加载、渲染图片等方法。 3. 实现图片加载和渲染逻辑。使用合适的库,如OpenCV、SDL_image等,加载指定路径的图片,并将其渲染到OBS的场景中。这可以通过调用OBS提供的渲染接口来完成。 4. 在插件的设置中,提供一个界面用于用户配置图片路径、尺寸和位置等属性。这可以通过使用OBS提供的UI库和功能来实现。 5. 编译和安装插件。根据OBS的编译和安装流程,将插件代码编译成可执行文件,并将插件文件复制到OBS的插件目录中。 6. 启动OBS,进入“Sources”窗口,在“添加”下拉菜单中选择刚才添加的图片类型。根据使用者的需求,配置图片的相关属性。 通过以上步骤,就可以在OBS中成功添加一个图片。用户可以通过更改配置文件、设置图片路径等方式,动态修改和加载图片。这样,就能够灵活地实现图片的嵌入和切换,从而满足不同场景的需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

土拨鼠不是老鼠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值