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;
};