Stream to multiple RTMP / SRT / WHIP endpoints simultaneously from a single OBS Studio scene. This is a ground-up C++20 rewrite of sorayuki/obs-multi-rtmp, installable side-by-side with the original.
Built on the OBS plugin template. Same persistence format as upstream — your old configs load without migration.
The upstream plugin works, but the codebase had grown organically and was hard to reason about:
shared_ptrfor every config struct- A single 1,163-line edit dialog re-implementing OBS's property UI by hand
- Manual
obs_*_releasecalls sprinkled throughout business logic - File-scope singletons for config and UI-thread posting
- Mixed concerns: one class was both a
QWidgetand an OBS signal handler
This fork rewrites every line of src/ from scratch with a cleaner
architecture, then proves it by building cleanly on macOS, Windows, and
Linux with the same CI pipeline the original uses.
| Area | Upstream | This rewrite |
|---|---|---|
Total LOC in src/ |
3,546 | 2,083 (−41%) |
| Largest file | 1,163 (edit dialog) | 423 |
| Config model | shared_ptr + std::list |
value types + std::vector |
| OBS handle ownership | manual release calls | RAII smart pointers |
| Threading | ad-hoc global singleton | injected UI poster |
| Edit dialog | hand-rolled tabs + manual forms | delegates to reusable PropertiesView |
| C++ standard | C++17-ish | C++20 throughout |
| Error codes | magic -1/-2/-3 |
real OBS_OUTPUT_* constants |
See oldsrc/ for the preserved upstream source, and
git log rewrite/from-scratch for the full rewrite history.
src/
├── Plugin.cpp module entry, dock registration
├── Logging.h blog() macros
├── ObsPtr.h RAII smart pointers for obs_*_t handles
├── Config.{h,cpp} value-type model + JSON persistence
├── Protocols.{h,cpp} RTMP / SRT / WHIP registry
├── OutputSession.{h,cpp} one streaming target: state machine + stats
└── ui/
├── Dock.{h,cpp} main dock widget
├── TargetRow.{h,cpp} one row per target
├── PropertiesView.{h,cpp} obs_properties_t → QFormLayout
└── EditDialog.{h,cpp} add/edit modal
Design principles applied throughout:
- RAII over every OBS handle. No manual
obs_*_releaseoutsideObsPtr.h. - Value types for config. No
shared_ptrsoup. - Strict separation of concerns.
OutputSessionhas zero Qt dependencies;TargetRowhas zero direct OBS API calls. - UI thread marshalling via
std::function. OBS signals post to Qt via an injectedUiPoster, not a global singleton. - No globals.
Dockowns theMultiOutputConfigdirectly. - C++20 idioms.
std::erase_if,contains, designated initializers,std::span,string_viewat boundaries.
This plugin is namespaced with an -aa suffix everywhere it would
otherwise collide with the upstream plugin:
| Thing | Upstream | This fork |
|---|---|---|
| Bundle name | obs-multi-rtmp.plugin |
obs-multi-rtmp-aa.plugin |
| macOS bundle id | net.sorayuki.obs-multi-rtmp |
com.ahmadawais.obs-multi-rtmp-aa |
| OBS dock id | obs-multi-rtmp-dock |
obs-multi-rtmp-aa-dock |
| Dock title | Multiple output |
Multiple output AA |
| Profile config file | obs-multi-rtmp.json |
obs-multi-rtmp-aa.json |
| Log tag | [obs-multi-rtmp] |
[obs-multi-rtmp-aa] |
| Windows installer | obs-multi-rtmp-setup.exe |
obs-multi-rtmp-aa-setup.exe |
Install both simultaneously and they will not touch each other's state.
Download the latest macOS build from the
Actions tab →
pick a successful run → scroll to Artifacts → grab
obs-multi-rtmp-aa-<version>-macos-universal-<sha>.
Then run the provided installer script:
./scripts/install-macos.shIt will:
- Auto-find the newest
obs-multi-rtmp-aa*.zipin~/Downloads - Unwrap the nested
.zip→.tar.xz→.pluginbundle - Print the architectures to confirm it's a universal binary
- Quit OBS if it's running (with confirmation)
- Copy the bundle into
~/Library/Application Support/obs-studio/plugins/ - Strip
com.apple.quarantineso Gatekeeper doesn't silently reject it
You can also pass an explicit path:
./scripts/install-macos.sh path/to/obs-multi-rtmp-aa.zip
./scripts/install-macos.sh path/to/obs-multi-rtmp-aa.pluginAfter install, open OBS → Docks → enable Multiple output AA.
Download obs-multi-rtmp-aa-<version>-windows-x64-<sha> from the Actions
artifacts, unzip, and run obs-multi-rtmp-aa-<version>-Installer.exe.
Download the Ubuntu artifact and install the .deb:
sudo dpkg -i obs-multi-rtmp-aa_*.deb# macOS
cmake --preset macos
cmake --build build_macos --config RelWithDebInfo
# Windows
cmake --preset windows-x64
cmake --build build_x64 --config RelWithDebInfo
# Ubuntu
cmake --preset ubuntu-x86_64
cmake --build build_x86_64Deps (OBS sources, libobs prebuilt, Qt6 prebuilt) are fetched automatically by the OBS plugin template during configure.
Requires:
- CMake 3.28+
- On macOS: Xcode 15+ (the preset uses the Xcode generator)
- On Windows: Visual Studio 2022
- On Linux: GCC 12+ or Clang 15+
- ✅ Builds clean on macOS, Windows, Ubuntu (GitHub Actions)
- ✅ Feature parity with upstream for RTMP / SRT / WHIP outputs
- ✅ Loads upstream configs unchanged
⚠️ Not code-signed — macOS builds requirexattr -dr com.apple.quarantine(the install script does this)- 🚧 Per-target scene override and per-track audio mapping are supported by
the config layer and by the streaming pipeline, but not exposed in the
edit dialog UI yet (the v1 pass kept the dialog minimal). Edit
obs-multi-rtmp-aa.jsonin your profile directory to use them until the UI catches up.
- Original plugin: SoraYuki — sorayuki/obs-multi-rtmp
- Rewrite: Ahmad Awais
- OBS Studio plugin template: https://github.com/obsproject/obs-plugintemplate
GPL-2.0-or-later, matching upstream. See LICENSE.