天天看點

OBS源碼分析(十)-濾鏡的建立

一、OBS的濾鏡都是以插件的方式去加載的,對應的OBS項目工程是:

OBS源碼分析(十)-濾鏡的建立

二、濾鏡的建立過程

1、右擊選擇的源

void OBSBasic::on_sources_customContextMenuRequested(const QPoint &pos)
{
	if (ui->scenes->count()) {
		QModelIndex idx = ui->sources->indexAt(pos);
		CreateSourcePopupMenu(idx.row(), false);
	}
}
           
void OBSBasic::CreateSourcePopupMenu(int idx, bool preview)
{
	QMenu popup(this);
	delete previewProjectorSource;
	delete sourceProjector;
	delete scaleFilteringMenu;
	delete colorMenu;
	delete colorWidgetAction;
	delete colorSelect;
	delete deinterlaceMenu;

	if (preview) {
		QAction *action = popup.addAction(
			QTStr("Basic.Main.PreviewConextMenu.Enable"), this,
			SLOT(TogglePreview()));
		action->setCheckable(true);
		action->setChecked(
			obs_display_enabled(ui->preview->GetDisplay()));
		if (IsPreviewProgramMode())
			action->setEnabled(false);

		popup.addAction(ui->actionLockPreview);
		popup.addMenu(ui->scalingMenu);

		previewProjectorSource = new QMenu(QTStr("PreviewProjector"));
		AddProjectorMenuMonitors(previewProjectorSource, this,
					 SLOT(OpenPreviewProjector()));

		popup.addMenu(previewProjectorSource);

		QAction *previewWindow =
			popup.addAction(QTStr("PreviewWindow"), this,
					SLOT(OpenPreviewWindow()));

		popup.addAction(previewWindow);

		popup.addSeparator();
	}

	QPointer<QMenu> addSourceMenu = CreateAddSourcePopupMenu();
	if (addSourceMenu)
		popup.addMenu(addSourceMenu);

	ui->actionCopyFilters->setEnabled(false);
	ui->actionCopySource->setEnabled(false);

	if (ui->sources->MultipleBaseSelected()) {
		popup.addSeparator();
		popup.addAction(QTStr("Basic.Main.GroupItems"), ui->sources,
				SLOT(GroupSelectedItems()));

	} else if (ui->sources->GroupsSelected()) {
		popup.addSeparator();
		popup.addAction(QTStr("Basic.Main.Ungroup"), ui->sources,
				SLOT(UngroupSelectedGroups()));
	}

	popup.addSeparator();
	popup.addAction(ui->actionCopySource);
	popup.addAction(ui->actionPasteRef);
	popup.addAction(ui->actionPasteDup);
	popup.addSeparator();

	popup.addSeparator();
	popup.addAction(ui->actionCopyFilters);
	popup.addAction(ui->actionPasteFilters);
	popup.addSeparator();

	if (idx != -1) {
		if (addSourceMenu)
			popup.addSeparator();

		OBSSceneItem sceneItem = ui->sources->Get(idx);
		bool lock = obs_sceneitem_locked(sceneItem);
		obs_source_t *source = obs_sceneitem_get_source(sceneItem);
		uint32_t flags = obs_source_get_output_flags(source);
		bool isAsyncVideo = (flags & OBS_SOURCE_ASYNC_VIDEO) ==
				    OBS_SOURCE_ASYNC_VIDEO;
		bool hasAudio = (flags & OBS_SOURCE_AUDIO) == OBS_SOURCE_AUDIO;
		QAction *action;

		colorMenu = new QMenu(QTStr("ChangeBG"));
		colorWidgetAction = new QWidgetAction(colorMenu);
		colorSelect = new ColorSelect(colorMenu);
		popup.addMenu(AddBackgroundColorMenu(
			colorMenu, colorWidgetAction, colorSelect, sceneItem));
		popup.addAction(QTStr("Rename"), this,
				SLOT(EditSceneItemName()));
		popup.addAction(QTStr("Remove"), this,
				SLOT(on_actionRemoveSource_triggered()));
		popup.addSeparator();
		popup.addMenu(ui->orderMenu);
		popup.addMenu(ui->transformMenu);

		ui->actionResetTransform->setEnabled(!lock);
		ui->actionRotate90CW->setEnabled(!lock);
		ui->actionRotate90CCW->setEnabled(!lock);
		ui->actionRotate180->setEnabled(!lock);
		ui->actionFlipHorizontal->setEnabled(!lock);
		ui->actionFlipVertical->setEnabled(!lock);
		ui->actionFitToScreen->setEnabled(!lock);
		ui->actionStretchToScreen->setEnabled(!lock);
		ui->actionCenterToScreen->setEnabled(!lock);
		ui->actionVerticalCenter->setEnabled(!lock);
		ui->actionHorizontalCenter->setEnabled(!lock);

		sourceProjector = new QMenu(QTStr("SourceProjector"));
		AddProjectorMenuMonitors(sourceProjector, this,
					 SLOT(OpenSourceProjector()));

		QAction *sourceWindow = popup.addAction(
			QTStr("SourceWindow"), this, SLOT(OpenSourceWindow()));

		popup.addAction(sourceWindow);

		popup.addSeparator();

		if (hasAudio) {
			QAction *actionHideMixer =
				popup.addAction(QTStr("HideMixer"), this,
						SLOT(ToggleHideMixer()));
			actionHideMixer->setCheckable(true);
			actionHideMixer->setChecked(SourceMixerHidden(source));
		}

		if (isAsyncVideo) {
			deinterlaceMenu = new QMenu(QTStr("Deinterlacing"));
			popup.addMenu(
				AddDeinterlacingMenu(deinterlaceMenu, source));
			popup.addSeparator();
		}

		QAction *resizeOutput =
			popup.addAction(QTStr("ResizeOutputSizeOfSource"), this,
					SLOT(ResizeOutputSizeOfSource()));

		int width = obs_source_get_width(source);
		int height = obs_source_get_height(source);

		resizeOutput->setEnabled(!obs_video_active());

		if (width < 8 || height < 8)
			resizeOutput->setEnabled(false);

		scaleFilteringMenu = new QMenu(QTStr("ScaleFiltering"));
		popup.addMenu(
			AddScaleFilteringMenu(scaleFilteringMenu, sceneItem));
		popup.addSeparator();

		popup.addMenu(sourceProjector);
		popup.addAction(sourceWindow);
		popup.addSeparator();

		action = popup.addAction(QTStr("Interact"), this,
					 SLOT(on_actionInteract_triggered()));

		action->setEnabled(obs_source_get_output_flags(source) &
				   OBS_SOURCE_INTERACTION);
		//選擇濾鏡選項
		***popup.addAction(QTStr("Filters"), this, SLOT(OpenFilters()));***
		popup.addAction(QTStr("Properties"), this,
				SLOT(on_actionSourceProperties_triggered()));

		ui->actionCopyFilters->setEnabled(true);
		ui->actionCopySource->setEnabled(true);
	} else {
		ui->actionPasteFilters->setEnabled(false);
	}

	popup.exec(QCursor::pos());
}
           

擷取目前源的對象source

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

	CreateFiltersWindow(source);
}
           

建立一個濾鏡對象

void OBSBasic::CreateFiltersWindow(obs_source_t *source)
{
	if (filters)
		filters->close();
	//建立一個濾鏡對象,濾鏡對應的源對象是source
	//OBSBasicFilters本身事ui視窗
	filters = new OBSBasicFilters(this, source);
	//顯示濾鏡視窗
	filters->Init();
	filters->setAttribute(Qt::WA_DeleteOnClose, true);
}
           

看看OBSBasicFilters的構造函數做了什麼事

OBSBasicFilters::OBSBasicFilters(QWidget *parent, OBSSource source_)
	: QDialog(parent),
	  ui(new Ui::OBSBasicFilters),
	  source(source_),
	  //初始化了OBS自定義的信号
	  addSignal(obs_source_get_signal_handler(source), "filter_add",
		    OBSBasicFilters::OBSSourceFilterAdded, this),
	  removeSignal(obs_source_get_signal_handler(source), "filter_remove",
		       OBSBasicFilters::OBSSourceFilterRemoved, this),
	  reorderSignal(obs_source_get_signal_handler(source),
			"reorder_filters", OBSBasicFilters::OBSSourceReordered,
			this),
	  removeSourceSignal(obs_source_get_signal_handler(source), "remove",
			     OBSBasicFilters::SourceRemoved, this),
	  renameSourceSignal(obs_source_get_signal_handler(source), "rename",
			     OBSBasicFilters::SourceRenamed, this),
	  noPreviewMargin(13)
{
	main = reinterpret_cast<OBSBasic *>(parent);

	setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);

	ui->setupUi(this);
	UpdateFilters();

	ui->asyncFilters->setItemDelegate(
		new VisibilityItemDelegate(ui->asyncFilters));
	ui->effectFilters->setItemDelegate(
		new VisibilityItemDelegate(ui->effectFilters));

	const char *name = obs_source_get_name(source);
	setWindowTitle(QTStr("Basic.Filters.Title").arg(QT_UTF8(name)));

#ifndef QT_NO_SHORTCUT
	ui->actionRemoveFilter->setShortcut(
		QApplication::translate("OBSBasicFilters", "Del", nullptr));
#endif // QT_NO_SHORTCUT

	addAction(ui->actionRemoveFilter);
	addAction(ui->actionMoveUp);
	addAction(ui->actionMoveDown);

	installEventFilter(CreateShortcutFilter());

	connect(ui->asyncFilters->itemDelegate(),
		SIGNAL(closeEditor(QWidget *,
				   QAbstractItemDelegate::EndEditHint)),
		this,
		SLOT(AsyncFilterNameEdited(
			QWidget *, QAbstractItemDelegate::EndEditHint)));

	connect(ui->effectFilters->itemDelegate(),
		SIGNAL(closeEditor(QWidget *,
				   QAbstractItemDelegate::EndEditHint)),
		this,
		SLOT(EffectFilterNameEdited(
			QWidget *, QAbstractItemDelegate::EndEditHint)));

	QPushButton *close = ui->buttonBox->button(QDialogButtonBox::Close);
	connect(close, SIGNAL(clicked()), this, SLOT(close()));
	close->setDefault(true);

	ui->buttonBox->button(QDialogButtonBox::Reset)
		->setText(QTStr("Defaults"));

	connect(ui->buttonBox->button(QDialogButtonBox::Reset),
		SIGNAL(clicked()), this, SLOT(ResetFilters()));

	uint32_t caps = obs_source_get_output_flags(source);
	bool audio = (caps & OBS_SOURCE_AUDIO) != 0;
	bool audioOnly = (caps & OBS_SOURCE_VIDEO) == 0;
	bool async = (caps & OBS_SOURCE_ASYNC) != 0;

	if (!async && !audio) {
		ui->asyncWidget->setVisible(false);
		ui->separatorLine->setVisible(false);
	}
	if (audioOnly) {
		ui->effectWidget->setVisible(false);
		ui->separatorLine->setVisible(false);
	}

	if (audioOnly || (audio && !async))
		ui->asyncLabel->setText(QTStr("Basic.Filters.AudioFilters"));

	auto addDrawCallback = [this]() {
		obs_display_add_draw_callback(ui->preview->GetDisplay(),
					      OBSBasicFilters::DrawPreview,
					      this);
	};

	enum obs_source_type type = obs_source_get_type(source);
	bool drawable_type = type == OBS_SOURCE_TYPE_INPUT ||
			     type == OBS_SOURCE_TYPE_SCENE;

	if ((caps & OBS_SOURCE_VIDEO) != 0) {
		ui->rightLayout->setContentsMargins(0, 0, 0, 0);
		ui->preview->show();
		if (drawable_type)
			connect(ui->preview, &OBSQTDisplay::DisplayCreated,
				addDrawCallback);
	} else {
		ui->rightLayout->setContentsMargins(0, noPreviewMargin, 0, 0);
		ui->rightContainerLayout->insertStretch(1);
		ui->preview->hide();
	}

	QAction *renameAsync = new QAction(ui->asyncWidget);
	renameAsync->setShortcutContext(Qt::WidgetWithChildrenShortcut);
	connect(renameAsync, SIGNAL(triggered()), this,
		SLOT(RenameAsyncFilter()));
	ui->asyncWidget->addAction(renameAsync);

	QAction *renameEffect = new QAction(ui->effectWidget);
	renameEffect->setShortcutContext(Qt::WidgetWithChildrenShortcut);
	connect(renameEffect, SIGNAL(triggered()), this,
		SLOT(RenameEffectFilter()));
	ui->effectWidget->addAction(renameEffect);

#ifdef __APPLE__
	renameAsync->setShortcut({Qt::Key_Return});
	renameEffect->setShortcut({Qt::Key_Return});
#else
	renameAsync->setShortcut({Qt::Key_F2});
	renameEffect->setShortcut({Qt::Key_F2});
#endif
}
           

二、選擇要添加的濾鏡

void OBSBasicFilters::on_addEffectFilter_clicked()
{
	QScopedPointer<QMenu> popup(CreateAddFilterPopupMenu(false));
	if (popup)
		popup->exec(QCursor::pos());
}
           

周遊所有的OBS濾鏡

QMenu *OBSBasicFilters::CreateAddFilterPopupMenu(bool async)
{
	uint32_t sourceFlags = obs_source_get_output_flags(source);
	const char *type_str;
	bool foundValues = false;
	size_t idx = 0;

	struct FilterInfo {
		string type;
		string name;

		inline FilterInfo(const char *type_, const char *name_)
			: type(type_), name(name_)
		{
		}
	};

	vector<FilterInfo> types;
	while (obs_enum_filter_types(idx++, &type_str)) {
		const char *name = obs_source_get_display_name(type_str);
		uint32_t caps = obs_get_source_output_flags(type_str);

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

		auto it = types.begin();
		for (; it != types.end(); ++it) {
			if (it->name >= name)
				break;
		}

		types.emplace(it, type_str, name);
	}

	QMenu *popup = new QMenu(QTStr("Add"), this);
	for (FilterInfo &type : types) {
		uint32_t filterFlags =
			obs_get_source_output_flags(type.type.c_str());

		if (!filter_compatible(async, sourceFlags, filterFlags))
			continue;

		QAction *popupItem =
			new QAction(QT_UTF8(type.name.c_str()), this);
		popupItem->setData(QT_UTF8(type.type.c_str()));x
		//選擇添加的濾鏡
		connect(popupItem, SIGNAL(triggered(bool)), this,
			SLOT(AddFilterFromAction()));
		popup->addAction(popupItem);

		foundValues = true;
	}

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

	return popup;
}
           
void OBSBasicFilters::AddFilterFromAction()
{
	QAction *action = qobject_cast<QAction *>(sender());
	if (!action)
		return;
	//添加濾鏡
	AddNewFilter(QT_TO_UTF8(action->data().toString()));
}
           
void OBSBasicFilters::AddNewFilter(const char *id)
{
	if (id && *id) {
		obs_source_t *existing_filter;
		string name = obs_source_get_display_name(id);

		QString placeholder = QString::fromStdString(name);
		QString text{placeholder};
		int i = 2;
		while ((existing_filter = obs_source_get_filter_by_name(
				source, QT_TO_UTF8(text)))) {
			obs_source_release(existing_filter);
			text = QString("%1 %2").arg(placeholder).arg(i++);
		}

		bool success = NameDialog::AskForName(
			this, QTStr("Basic.Filters.AddFilter.Title"),
			QTStr("Basic.Filters.AddFilter.Text"), name, text);
		if (!success)
			return;

		if (name.empty()) {
			OBSMessageBox::warning(this,
					       QTStr("NoNameEntered.Title"),
					       QTStr("NoNameEntered.Text"));
			AddNewFilter(id);
			return;
		}

		existing_filter =
			obs_source_get_filter_by_name(source, name.c_str());
		if (existing_filter) {
			OBSMessageBox::warning(this, QTStr("NameExists.Title"),
					       QTStr("NameExists.Text"));
			obs_source_release(existing_filter);
			AddNewFilter(id);
			return;
		}

		obs_source_t *filter =
			obs_source_create(id, name.c_str(), nullptr, nullptr);
		if (filter) {
			const char *sourceName = obs_source_get_name(source);

			blog(LOG_INFO,
			     "User added filter '%s' (%s) "
			     "to source '%s'",
			     name.c_str(), id, sourceName);
			//添加濾鏡函數
			obs_source_filter_add(source, filter);
			obs_source_release(filter);
		}
	}
}

           
void obs_source_filter_add(obs_source_t *source, obs_source_t *filter)
{
	struct calldata cd;
	uint8_t stack[128];

	if (!obs_source_valid(source, "obs_source_filter_add"))
		return;
	if (!obs_ptr_valid(filter, "obs_source_filter_add"))
		return;

	pthread_mutex_lock(&source->filter_mutex);

	if (da_find(source->filters, &filter, 0) != DARRAY_INVALID) {
		blog(LOG_WARNING, "Tried to add a filter that was already "
				  "present on the source");
		pthread_mutex_unlock(&source->filter_mutex);
		return;
	}

	if (!source->owns_info_id && !filter_compatible(source, filter)) {
		pthread_mutex_unlock(&source->filter_mutex);
		return;
	}

	obs_source_addref(filter);

	filter->filter_parent = source;
	filter->filter_target = !source->filters.num ? source
						     : source->filters.array[0];

	da_insert(source->filters, 0, &filter);

	pthread_mutex_unlock(&source->filter_mutex);

	calldata_init_fixed(&cd, stack, sizeof(stack));
	calldata_set_ptr(&cd, "source", source);
	calldata_set_ptr(&cd, "filter", filter);
	//回調函數信号
	signal_handler_signal(source->context.signals, "filter_add", &cd);

	blog(LOG_DEBUG, "- filter '%s' (%s) added to source '%s'",
	     filter->context.name, filter->info.id, source->context.name);
}
           

回調函數

```cpp
void OBSBasicFilters::AddFilter(OBSSource filter)
{
	uint32_t flags = obs_source_get_output_flags(filter);
	bool async = (flags & OBS_SOURCE_ASYNC) != 0;
	QListWidget *list = async ? ui->asyncFilters : ui->effectFilters;

	QListWidgetItem *item = new QListWidgetItem();
	Qt::ItemFlags itemFlags = item->flags();

	item->setFlags(itemFlags | Qt::ItemIsEditable);
	item->setData(Qt::UserRole, QVariant::fromValue(filter));

	list->addItem(item);
	list->setCurrentItem(item);
	SetupVisibilityItem(list, item, filter);
}
           

同時會響應槽函數

void OBSBasicFilters::on_effectFilters_currentRowChanged(int row)
{
	//顯示濾鏡屬性設定
	UpdatePropertiesView(row, false);
}
           
void OBSBasicFilters::UpdatePropertiesView(int row, bool async)
{
	if (view) {
		updatePropertiesSignal.Disconnect();
		ui->rightLayout->removeWidget(view);
		view->deleteLater();
		view = nullptr;
	}

	OBSSource filter = GetFilter(row, async);
	if (!filter)
		return;

	obs_data_t *settings = obs_source_get_settings(filter);
	//建立屬性頁
	view = new OBSPropertiesView(
		settings, filter,
		(PropertiesReloadCallback)obs_source_properties,
		(PropertiesUpdateCallback)obs_source_update);

	updatePropertiesSignal.Connect(obs_source_get_signal_handler(filter),
				       "update_properties",
				       OBSBasicFilters::UpdateProperties, this);

	obs_data_release(settings);

	view->setMaximumHeight(250);
	view->setMinimumHeight(150);
	ui->rightLayout->addWidget(view);
	view->show();
}
           

來看看OBSPropertiesView構造函數都是幹什麼事了

void OBSPropertiesView::ReloadProperties()
{
//将目前源的屬性給了智能指針properties
	if (obj) {
		properties.reset(reloadCallback(obj));
	} else {
		properties.reset(reloadCallback((void *)type.c_str()));
		obs_properties_apply_settings(properties.get(), settings);
	}

	uint32_t flags = obs_properties_get_flags(properties.get());
	deferUpdate = (flags & OBS_PROPERTIES_DEFER_UPDATE) != 0;

	RefreshProperties();
}
           

然後周遊每一個屬性并顯示界面上

void OBSPropertiesView::RefreshProperties()
{
	int h, v;
	GetScrollPos(h, v);

	children.clear();
	if (widget)
		widget->deleteLater();

	widget = new QWidget();

	QFormLayout *layout = new QFormLayout;
	layout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow);
	widget->setLayout(layout);

	QSizePolicy mainPolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

	layout->setLabelAlignment(Qt::AlignRight);

	obs_property_t *property = obs_properties_first(properties.get());
	bool hasNoProperties = !property;
	周遊每一個屬性
	while (property) {
		AddProperty(property, layout);
		obs_property_next(&property);
	}

	setWidgetResizable(true);
	setWidget(widget);
	SetScrollPos(h, v);
	setSizePolicy(mainPolicy);

	lastFocused.clear();
	if (lastWidget) {
		lastWidget->setFocus(Qt::OtherFocusReason);
		lastWidget = nullptr;
	}

	if (hasNoProperties) {
		QLabel *noPropertiesLabel = new QLabel(NO_PROPERTIES_STRING);
		layout->addWidget(noPropertiesLabel);
	}

	emit PropertiesRefreshed();
}
           

AddProperty函數就不再贅述了,都是關于界面的東西

繼續閱讀