Skip to content

Latest commit

 

History

History
465 lines (335 loc) · 23.5 KB

File metadata and controls

465 lines (335 loc) · 23.5 KB

CLAUDE.md — Branch Output Plugin for OBS Studio

Project Overview

Branch Output is an OBS Studio plugin (shared library / module) that adds an effect filter allowing individual sources or scenes to stream and/or record independently from the main OBS output. It is developed and maintained by OPENSPHERE Inc. under the GPLv2+ license.

  • Repository language: C++ 17 with Qt 6 for UI
  • Build system: CMake (≥ 3.16)
  • Based on the official obs-plugintemplate
  • Target OBS Studio version: ≥ 30.1.0 (Qt6, x64 / ARM64 / Apple Silicon)

Repository Layout

branch-output/
├── CMakeLists.txt          # Top-level CMake project definition
├── CMakePresets.json        # CMake presets (windows-x64, macos, linux-x86_64, CI variants)
├── buildspec.json           # Build specification (name, version, dependencies, UUIDs)
├── build.ps1                # Local Windows build script (RelWithDebInfo)
├── .clang-format            # C++ code style (clang-format ≥ 16)
├── .cmake-format.json       # CMake code style
├── .github/
│   ├── actions/             # Reusable format-check actions
│   └── workflows/           # CI workflows (build, format check, release)
├── cmake/                   # CMake helpers & platform-specific modules
├── build-aux/               # Auxiliary build scripts (format runners)
├── data/
│   ├── locale/              # Translation files (en-US, ja-JP, zh-CN, ko-KR, etc.)
│   └── scripts/             # Sample Python scripts for OBS Script (file name override, etc.)
├── src/
│   ├── plugin-main.cpp      # Plugin entry point, BranchOutputFilter core logic
│   ├── plugin-main.hpp      # BranchOutputFilter class declaration
│   ├── plugin-streaming.cpp # Streaming output logic (individual start/stop, per-slot control)
│   ├── plugin-stream-recording.cpp  # Stream recording output logic (individual start/stop)
│   ├── plugin-replay-buffer.cpp  # Replay buffer output logic (individual start/stop)
│   ├── plugin-ui.cpp        # OBS properties UI, filter settings UI
│   ├── plugin-support.h     # Auto-generated plugin support header
│   ├── plugin-support.c.in  # Template for plugin-support.c
│   ├── utils.cpp / .hpp     # Utility functions, encoder helpers, profile helpers
│   ├── audio/
│   │   └── audio-capture.cpp / .hpp  # Audio capture abstraction
│   ├── video/
│   │   ├── filter-video-capture.cpp / .hpp  # Filter input video capture (GPU texrender proxy)
│   │   └── crop-rect-preview-renderer.cpp / .hpp  # Crop rectangle preview overlay renderer
│   └── UI/
│       ├── output-status-dock.cpp / .hpp  # Status dock widget
│       ├── resources.qrc    # Qt resource file
│       └── images/          # Icons and images
└── release/                 # Build output directory

Build Instructions

Prerequisites

Platform Toolchain
Windows Visual Studio 17 2022, CMake ≥ 3.16, Qt 6
macOS Xcode ≤ 16.4.0 (macOS SDK ≤ 15.5), CMake ≥ 3.16, Qt 6
Linux GCC / Clang, CMake ≥ 3.16, Qt 6

Note (macOS): This project requires Xcode 16.4.0 (macOS 15.5 SDK) or below. Xcode 26 / macOS SDK 26 and later are currently unsupported due to compatibility constraints with OBS Studio's build dependencies.

OBS Studio sources and pre-built dependencies are fetched automatically via buildspec.json (obs-studio 30.1.2, obs-deps, Qt6).

clang-format

This project requires clang-format version 16 or later (as specified in .clang-format). CI (GitHub Actions) enforces version 17.0.3 exactly via build-aux/.run-format.zsh.

Platform Install method
Windows winget install -v 17.0.3 LLVM.LLVM or download from the LLVM 17.0.3 release page. Ensure clang-format is on your PATH.
macOS brew install llvm@17 — then use $(brew --prefix llvm@17)/bin/clang-format or add it to PATH.
Linux Install via package manager (e.g., apt install clang-format-17 on Ubuntu/Debian, or dnf install clang-tools-extra-17.0.3 on Fedora). The script also looks for clang-format-17 on PATH.

Verify installation:

clang-format --version
# Expected: clang-format version 17.0.3 (...)

To format all source files locally:

# PowerShell (Windows / cross-platform)
Get-ChildItem -Recurse -Path src -Include *.cpp,*.hpp,*.h,*.c | ForEach-Object { clang-format -i -style=file -fallback-style=none $_.FullName }
# Bash / Zsh (macOS / Linux)
find src -type f \( -name '*.cpp' -o -name '*.hpp' -o -name '*.h' -o -name '*.c' \) -exec clang-format -i -style=file -fallback-style=none {} +

cmake-format

This project requires cmake-format version 0.6.13 or later. Configuration is in .cmake-format.json. CI (GitHub Actions) enforces this version via build-aux/.run-format.zsh.

Install via pip:

pip install cmake-format>=0.6.13

Verify installation:

cmake-format --version
# Expected: 0.6.13 or later

To format all CMake files locally:

# PowerShell (Windows / cross-platform)
Get-ChildItem -Recurse -Include CMakeLists.txt,*.cmake | Where-Object { $_.FullName -notmatch 'build_' } | ForEach-Object { cmake-format -i $_.FullName }
# Bash / Zsh (macOS / Linux)
find . -type f \( -name 'CMakeLists.txt' -o -name '*.cmake' \) -not -path '*/build_*/*' -exec cmake-format -i {} +

Windows (local)

# Configure + Build + Install
.\build.ps1

# With Inno Setup installer
.\build.ps1 -installer

Or manually:

cmake --fresh -S . -B build_x64 -G "Visual Studio 17 2022" -A x64
cmake --build build_x64 --config RelWithDebInfo
cmake --install build_x64 --prefix release/Package --config RelWithDebInfo

CMake Presets

Use cmake --preset <name> with one of:

  • windows-x64 / windows-ci-x64
  • macos / macos-ci
  • linux-x86_64 / linux-ci-x86_64

Architecture & Key Concepts

Plugin Entry Point

The plugin registers an OBS effect filter via obs_source_info (filter ID: osi_branch_output). The main class is BranchOutputFilter (declared in plugin-main.hpp), which is a QObject subclass.

Core Responsibilities of BranchOutputFilter

Area Description
Streaming Creates up to MAX_SERVICES (8) independent streaming outputs with dedicated services, encoders, and reconnect logic.
Recording Supports file recording with various container formats, time/size-based splitting, pause/unpause, and chapter markers.
Replay Buffer Supports replay buffer output (implemented in plugin-replay-buffer.cpp). Allows saving the last N seconds of encoded output to file on demand via hotkey or UI button.
Audio Manages up to MAX_AUDIO_MIXES audio contexts via AudioCapture class. Supports filter audio, per-source audio, and audio track selection.
Video Creates an OBS view (obs_view_t) with a private video output for per-filter encoding and resolution control. Supports filter input mode via FilterVideoCapture class, which captures the filter's input using GPU gs_texrender and provides a private proxy source for the obs_view, avoiding CPU roundtrips and enabling GPU encoder compatibility (NVENC, QSV, AMF, etc.).
UI Properties panel built via OBS properties API (plugin-ui.cpp). Status dock (BranchOutputStatusDock) shows live statistics for all filters, including replay buffer save buttons.
Hotkeys Registers hotkey pairs for enable/disable, split recording, pause/unpause, chapter markers, save replay buffer, and per-output enable/disable (streaming per-slot, recording, replay buffer).
Interlock Can link filter activation to OBS streaming, recording, virtual camera, replay buffer, or individual (per-output-type) states. The "Individual" mode maps each Branch Output type to its OBS counterpart independently.
Individual Start/Stop Allows streaming, recording, and replay buffer to be started/stopped independently via ensureInfrastructure() / releaseInfrastructureIfIdle() to separate shared resource lifecycle from individual output lifecycle. Per-output user intent is tracked via atomic flags (streamingUserEnabled[], recordingUserEnabled, replayBufferUserEnabled).
Cropping Supports relative (margin) and absolute (region) video cropping with CropRect struct. Live preview via CropRectPreviewRenderer.
Blanking Uses a private solid-color source to blank output when the parent source is inactive.

Threading Model

  • OBS callbacks (video render, audio filter, video tick) may run on different threads from the UI thread.
  • Three recursive mutexes (pluginMutex, outputMutex, audioMutex) initialized via pthread_mutex_init_recursive(). Lock ordering: pluginMutexoutputMutex.
  • audioMutex protects audio capture pointers against concurrent release in releaseInfrastructureIfIdle().
  • QMutex protects audio buffers in AudioCapture.
  • Atomic fields (std::atomic<bool> for outputStarting, streamingUserEnabled[], recordingUserEnabled, replayBufferUserEnabled; std::atomic<uint64_t> for reconnectAttemptingAt) eliminate data races from OBS signal callbacks.
  • UI updates use QMetaObject::invokeMethod with Qt::QueuedConnection for thread safety.
  • Settings changes are tracked via revision counters (storedSettingsRev / activeSettingsRev) to defer restarts.

OBS API Usage

The plugin heavily uses:

  • obs-module.h — Module registration, locale, config paths
  • obs-frontend-api.h — Profile config, scene enumeration, frontend events
  • obs.hpp — OBS RAII wrappers (OBSSourceAutoRelease, OBSEncoderAutoRelease, OBSOutputAutoRelease, etc.)
  • util/deque.h, util/threading.h, util/platform.h — Low-level utilities

Code Style & Formatting

C++ (clang-format)

  • Standard: C++17
  • Column limit: 120
  • Indent: 4 spaces (tabs not used)
  • Brace style: Custom — functions use next-line braces; control statements use same-line braces
  • clang-format version: ≥ 16 required
  • Run: clang-format -i src/**/*.cpp src/**/*.hpp

CMake (cmake-format)

  • Line width: 120
  • Tab size: 2
  • Config: .cmake-format.json

CI Enforcement

Format is checked in CI via .github/workflows/check-format.yaml using reusable actions (run-clang-format, run-cmake-format). PRs to master/main/dev and pushes to master/dev are validated.


Coding Guidelines

General Rules

  1. Follow OBS plugin conventions — Use obs_log() for logging, OBS_DECLARE_MODULE() for entry, locale via obs_module_text().
  2. RAII wrappers — Always use OBSSourceAutoRelease, OBSDataAutoRelease, etc. instead of manual obs_*_release().
  3. Thread safety — Any data shared between OBS callbacks and UI must be guarded by mutex. Use QMetaObject::invokeMethod with Qt::QueuedConnection when calling UI methods from non-UI threads.
  4. Settings migration — When changing settings schema, add backward-compatible migration code in the constructor (see audio_source migration example in BranchOutputFilter constructor).
  5. Qt MOC — Classes using Q_OBJECT must be in headers. AUTOMOC, AUTOUIC, AUTORCC are enabled.

Naming Conventions

  • Classes: PascalCase (BranchOutputFilter, AudioCapture, FilterVideoCapture, CropRectPreviewRenderer, OutputCell, OutputTableRow)
  • Methods: camelCase (startOutput, stopRecordingOutput, onIntervalTimerTimeout)
  • Constants/Macros: UPPER_SNAKE_CASE (MAX_SERVICES, FILTER_ID, OUTPUT_MAX_RETRIES)
  • Member variables: camelCase, no prefix (filterSource, videoEncoder, recordingActive)
  • Static callbacks: camelCase with descriptive prefix (onEnableFilterHotkeyPressed, audioFilterCallback)

Ternary Operators

  • Avoid multi-line ternary expressions — clang-format cannot reliably format nested or multi-line ternary operators (a ? b : c) in a readable way. Use if/else if/else statements instead when the expression would span multiple lines.

Error Handling

  • Use obs_log(LOG_ERROR, ...) for errors, LOG_WARNING for warnings, LOG_INFO for lifecycle events, LOG_DEBUG for verbose tracing.
  • Check return values from OBS API calls; handle gracefully (do not crash OBS).
  • Never throw C++ exceptions across OBS API boundaries.

Localization

  • All user-facing strings must use obs_module_text("Key") or the QTStr("Key") helper.
  • Add new keys to data/locale/en-US.ini (primary) and ideally to ja-JP.ini.
  • Locale files use INI format: Key="Value".

Testing & Validation

  • There are no automated unit tests in this repository currently.
  • Manual testing is required: load the plugin in OBS Studio, add the "Branch Output" filter, and verify streaming/recording behavior.
  • Test on all supported platforms when possible (Windows x64, macOS, Linux).
  • Verify Studio Mode compatibility (Branch Output ignores studio mode's program and outputs from preview).
  • Verify that enabling and then disabling the Branch Output filter does not cause a crash.
  • Verify that shutting down OBS does not crash when the Branch Output filter is inactive or active, respectively.
  • Verify that switching scene collections does not crash when the Branch Output filter is inactive or active, respectively.
  • Verify that no memory leaks are logged on OBS shutdown.
  • Verify that mutex does not cause deadlocks.
  • Verify that blanking mode does not leak video or audio unintentionally — when the source is not visible in the main output (Program) and blanking is enabled, the Branch Output must emit a black frame (no source imagery). If audio muting is also enabled, all audio tracks (including master track) must be silenced.
  • Verify that Individual interlock mode correctly starts/stops each output type independently following its OBS counterpart.
  • Verify that per-output checkboxes in the Status Dock correctly enable/disable individual outputs in all interlock modes.
  • Verify that per-output hotkeys toggle the correct output and that the Status Dock checkboxes sync immediately.
  • Verify that video cropping (both relative and absolute modes) produces the correct output resolution and content.
  • Verify that the crop preview rectangle displays correctly and is hidden when the properties dialog closes.

CI / CD

Workflow Trigger Purpose
push.yaml Push to master/main/dev/release/**, tags Format check + build + release creation
build-project.yaml Called by push/PR workflows Multi-platform build (Windows, macOS, Linux)
check-format.yaml Called by push/PR workflows clang-format and cmake-format validation
pr-pull.yaml Pull requests to master/main/dev Format check + build validation
dispatch.yaml Manual dispatch On-demand builds

Release tags follow semver: X.Y.Z for stable, X.Y.Z-beta/X.Y.Z-rc for pre-releases.


Common Tasks for AI Agents

Adding a New Setting / Feature

  1. Add the setting key to BranchOutputFilter::getDefaults() in plugin-ui.cpp.
  2. Add UI controls in plugin-ui.cpp (use OBS properties API: obs_properties_add_*).
  3. Handle the setting in startOutput() / stopOutput() / updateCallback() as needed.
  4. Add locale strings to all locale files under data/locale/ (en-US.ini, ja-JP.ini, zh-CN.ini, ko-KR.ini, de-DE.ini, fr-FR.ini, ca-ES.ini, ro-RO.ini, ru-RU.ini, uk-UA.ini). en-US.ini is the primary (required); all others should also be updated.
  5. Ensure backward compatibility — existing saved settings must still load correctly.

Adding a New Streaming Service Slot

  • The plugin supports up to MAX_SERVICES (8) service slots.
  • Each slot has its own BranchOutputStreamingContext with output, service, and signals.
  • Use getIndexedPropNameFormat() for indexed property names.

Modifying Audio Handling

  • Audio capture is abstracted through AudioCapture class in src/audio/.
  • The class manages an audio buffer, supports push/pop patterns, and handles format conversion.
  • Audio mixers and encoder binding are managed in BranchOutputFilter::startOutput().

Modifying the Status Dock

  • BranchOutputStatusDock in src/UI/ is a QFrame-based dock widget.
  • It uses a QTableWidget with custom cell classes (OutputTableCellItem, LabelCell, FilterCell, OutputCell).
  • OutputCell provides per-output enable/disable checkboxes (eye icon) and folder-open click for recording/replay buffer rows.
  • The outputUserEnabledChanged signal provides immediate checkbox sync when state changes from hotkeys or other sources.
  • Thread-safe updates via QMetaObject::invokeMethod.

Modifying Replay Buffer

  • Replay buffer logic is implemented in src/plugin-replay-buffer.cpp.
  • BranchOutputFilter manages replay buffer lifecycle (createAndStartReplayBuffer, stopReplayBufferOutput, saveReplayBuffer), plus individual start/stop (startReplayBufferIndividual, stopReplayBufferIndividual).
  • Settings creation is handled by createReplayBufferSettings().
  • The status dock includes a save button per replay buffer row and a global "Save All Replay Buffers" button.
  • Replay buffer can be linked to filter activation via INTERLOCK_TYPE_REPLAY_BUFFER or INTERLOCK_TYPE_INDIVIDUAL.

Modifying Filter Video Capture

  • FilterVideoCapture in src/video/ captures the filter's input via GPU gs_texrender.
  • It provides a private proxy source (PROXY_SOURCE_ID: osi_branch_output_proxy) for obs_view binding.
  • The proxy source renders the captured texrender texture directly on the GPU, avoiding CPU roundtrips and enabling GPU encoder compatibility.
  • Lifecycle: created/destroyed in startOutput() / stopOutput() when filter input mode (useFilterInput) is enabled.
  • Key methods: captureFilterInput() (called from video_render), renderTexture() (called from proxy source's video_render), drawCapturedTexture() (passthrough to main output).

Modifying Video Cropping

  • Cropping is configured via settings (crop_type, crop_relative_*, crop_absolute_*) in plugin-ui.cpp.
  • CropRect struct (defined in utils.hpp) holds resolved crop parameters.
  • calculateCrop() in plugin-main.cpp resolves relative/absolute crop settings into a CropRect.
  • determineOutputResolution() accounts for cropping when computing the final output resolution.
  • CropRectPreviewRenderer in src/video/ draws a live preview rectangle overlay in the filter's video_render callback. The preview is transient (not saved to settings) and automatically hidden when the properties dialog is closed.

Modifying Streaming

  • Streaming logic is implemented in src/plugin-streaming.cpp.
  • Supports up to MAX_SERVICES (8) independent streaming slots via BranchOutputStreamingContext.
  • Individual start/stop per slot via startSingleStreamingIndividual() / stopSingleStreamingIndividual(), and batch via startStreamingIndividual() / stopStreamingIndividual().
  • Per-slot user intent flags (streamingUserEnabled[]) are atomic and persisted via saveCallback.

Modifying Stream Recording

  • Stream recording logic is implemented in src/plugin-stream-recording.cpp.
  • Supports individual start/stop via startRecordingIndividual() / stopRecordingIndividual().
  • Proc handlers for file name format override are registered here (override_recording_file_name_format, clear_recording_file_name_format_override).

Important Warnings

  • Do NOT call obs_filter_get_parent() in the BranchOutputFilter constructor — it returns nullptr at that point. Use addCallback() instead.
  • Private sources (not visible in frontend) are intentionally excluded from status dock and timer registration.
  • Settings revisions (storedSettingsRev / activeSettingsRev) exist to avoid stopping output during reconnect attempts. Do not bypass this mechanism.
  • Encoder compatibility — The plugin maps "simple" encoder names to actual encoder IDs, with version-specific fallbacks (OBS 30 vs OBS 31). See getSimpleVideoEncoder() in utils.hpp.
  • Memory management — Use OBS RAII wrappers. Raw bfree() / obs_data_release() calls are error-prone.
  • .gitignore uses allowlist pattern — New top-level files/directories must be explicitly un-ignored with ! prefix.
  • FilterVideoCapture proxy source — The proxy source type (osi_branch_output_proxy) must be registered at module load via FilterVideoCapture::createProxySourceInfo(). The proxy source is private and intentionally not visible in the OBS frontend.

Agent Teams

Communication Language

  • Agents must respond in the same language the user is using (e.g., reply in Japanese if the user writes in Japanese, in English if the user writes in English).

Team Leader Policy

  • The team leader must focus exclusively on orchestrating teammates.

Team Creation Policy

  • Team size should be 3–5 members, selected from the cast below based on the task.

  • Each teammate must work on different files to avoid edit conflicts.

  • Start with research and review, then launch the team for parallel execution.

  • Do not use subagents for tasks that can be handled by agent teams.

    Exception: Use subagents for parallel reviews.

Agent Cast

cpp-sensei — C++ Native Application Specialist

Expert in C++ language specifications and Windows/macOS/Linux native application development.

  • C++ implementation work
  • C++ coding advice
  • Code review from the perspective of C++ language specifications and coding standards
  • Multithreading implementation and thread safety advice

obs-sensei — OBS Studio Plugin Specialist

Expert in OBS Studio internals, the OBS Studio API, and OBS Studio plugins.

  • OBS Studio specification advice
  • OBS Studio API selection
  • OBS Studio plugin specification advice

qt-sensei — Qt Specialist

Expert in Qt framework, GUI application design, implementation, and testing.

  • Qt specification advice
  • Qt API selection
  • Qt GUI construction advice
  • Qt object design advice

network-sensei — Network Specialist

Expert in network programming, TCP/IP, HTTP, SSL/TLS, WebSocket, socket communication, and streaming protocols such as RTMP/SRT/WebRTC.

  • Network programming advice
  • Network protocol implementation advice
  • Streaming protocol implementation advice
  • Security advice
  • Network code review

translation-sensei — Translation Specialist

Multilingual translator.

  • Locale INI file translation
  • Document translation

av-sensei — Audio/Video/Streaming Specialist

Expert in video, audio, and streaming technologies, media quality, video processing, audio processing, encoders, and broadcast operations.

  • Technical advice on video, audio, and streaming
  • Quality advice on video, audio, and streaming
  • Encoder configuration advice
  • Broadcast operations advice

devops-sensei — DevOps Specialist

Expert in CI/CD (GitHub Actions), CMake, clang-format, VS Code, Inno Setup, and other development environment and build process tooling.

  • GitHub Actions workflow editing and review
  • CMake build script editing and review
  • Inno Setup build script editing and review

python-sensei — Python Specialist

Expert in Python scripting, OBS Studio Script design, implementation, and testing.

  • Python implementation work
  • Python coding advice
  • Code review from the perspective of Python language specifications and coding standards