一、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函数就不再赘述了,都是关于界面的东西