obs-plugin-reviewing
npx skills add https://github.com/meriley/claude-code-skills --skill obs-plugin-reviewing
Agent 安装分布
Skill 文档
OBS Plugin Code Review
Purpose
Audit OBS Studio plugins for correctness, performance, memory safety, and thread safety. Covers module registration, callback implementations, resource management, and audio-specific patterns.
When NOT to Use
- Writing new plugins â Use obs-audio-plugin-writing
- Getting started â Use obs-plugin-developing
- Need guidance on which skill â Use obs-plugin-expert agent
Quick Review Checklist
Run these checks in order. Stop at first P0 issue.
P0 – CRITICAL (Must Fix)
| Check | Pattern | Command |
|---|---|---|
| Missing module macro | No OBS_DECLARE_MODULE() |
grep -r "OBS_DECLARE_MODULE" src/ |
| Module load returns false | return false in load |
grep -A5 "obs_module_load" src/ |
| Memory leak in destroy | Missing bfree() |
Manual review of destroy callbacks |
| Blocking in audio callback | Mutex/sleep in filter_audio | grep -A20 "filter_audio" src/ |
| NULL dereference | No channel null check | grep -B5 -A10 "audio->data\[" src/ |
P1 – HIGH (Should Fix)
| Check | Pattern | Command |
| ———————- | —————————- | ——————————- | ————- |
| Buffer overflow risk | Ignoring audio->frames | grep -A15 "filter_audio" src/ |
| Global state | Static variables | grep "^static.*=" src/*.c |
| Missing error handling | No NULL checks | Manual review |
| Thread safety issues | Shared state without atomics | grep -E "(pthread | mutex)" src/ |
P2 – MEDIUM (Consider Fixing)
| Check | Pattern | Command |
|---|---|---|
| Missing defaults | No get_defaults callback | grep "get_defaults" src/ |
| Hard-coded strings | No obs_module_text() | grep -E "\"[A-Z][a-z]" src/*.c |
| Missing locale | No en-US.ini | ls data/locale/ |
P3 – LOW (Nice to Have)
| Check | Pattern | Command |
| ————— | ———————– | ————— | ————— |
| Missing logging | No blog/obs_log calls | grep -E "(blog | obs_log)" src/ |
| Code style | Inconsistent formatting | Manual review |
| Documentation | Missing comments | Manual review |
Module Registration Review
Required Elements
Every OBS plugin MUST have:
/* 1. Module declaration macro - REQUIRED */
OBS_DECLARE_MODULE()
/* 2. Optional but recommended: locale support */
OBS_MODULE_USE_DEFAULT_LOCALE("plugin-name", "en-US")
/* 3. Module load function - REQUIRED, must return true */
bool obs_module_load(void)
{
/* Register all sources/outputs/encoders/services */
obs_register_source(&my_source);
return true; /* MUST return true on success */
}
/* 4. Module unload - optional but recommended */
void obs_module_unload(void)
{
/* Cleanup global resources */
}
Common Module Errors
| Error | Impact | Solution |
|---|---|---|
Missing OBS_DECLARE_MODULE() |
Plugin won’t load | Add macro at file start |
obs_module_load returns false |
Plugin fails silently | Return true on success |
| No unload cleanup | Resource leak | Implement obs_module_unload |
| Wrong locale path | Strings not translated | Use data/locale/en-US.ini |
Grep Commands for Module Review
# Check for module macro
grep -l "OBS_DECLARE_MODULE" src/*.c
# Check module_load return value
grep -A10 "obs_module_load" src/*.c | grep -E "(return|false|true)"
# Check for source registration
grep "obs_register_source" src/*.c
# Check for locale setup
grep "OBS_MODULE_USE_DEFAULT_LOCALE" src/*.c
Memory Safety Review
Allocation/Deallocation Pattern
OBS uses custom memory functions:
| OBS Function | Standard Equivalent | Purpose |
|---|---|---|
bzalloc(size) |
calloc(1, size) |
Zero-initialized allocation |
bfree(ptr) |
free(ptr) |
Free memory |
bmalloc(size) |
malloc(size) |
Raw allocation |
brealloc(ptr, size) |
realloc(ptr, size) |
Resize allocation |
Memory Safety Checklist
â¡ Every bzalloc/bmalloc has matching bfree in destroy callback
â¡ Context struct freed in destroy callback
â¡ No memory allocation in audio/video callbacks
â¡ Pre-allocated buffers for real-time processing
â¡ Reference counting handled correctly (obs_source_addref/release)
Common Memory Errors
/* BAD: Memory leak - no bfree in destroy */
static void *my_create(obs_data_t *settings, obs_source_t *source)
{
struct my_data *ctx = bzalloc(sizeof(*ctx));
ctx->buffer = bmalloc(4096); /* Also needs freeing! */
return ctx;
}
static void my_destroy(void *data)
{
/* WRONG: Missing bfree calls! */
}
/* GOOD: Proper cleanup */
static void my_destroy(void *data)
{
struct my_data *ctx = data;
if (ctx) {
bfree(ctx->buffer); /* Free nested allocations first */
bfree(ctx); /* Free context last */
}
}
Grep Commands for Memory Review
# Find all allocations
grep -n "bzalloc\|bmalloc\|brealloc" src/*.c
# Find all deallocations
grep -n "bfree" src/*.c
# Check destroy callbacks
grep -A20 "_destroy\|\.destroy" src/*.c
# Find potential leaks (alloc without corresponding free)
# Manual comparison of above results
Thread Safety Review
OBS Threading Model
| Thread | Callbacks | Rules |
|---|---|---|
| Main Thread | create, destroy, update, get_properties, get_defaults | Safe to block, allocate memory |
| Audio Thread | filter_audio | NEVER block, no allocations |
| Video Thread | video_render, video_tick | Minimize blocking |
Audio Thread Rules (CRITICAL)
The filter_audio callback runs on the audio thread:
/* FORBIDDEN in filter_audio: */
pthread_mutex_lock(&mutex); /* NEVER: Can cause audio glitches */
sleep(1); /* NEVER: Blocks audio processing */
bmalloc(size); /* NEVER: Allocation can block */
obs_data_get_*(); /* NEVER: Settings access can lock */
fopen(); /* NEVER: I/O blocks */
/* ALLOWED in filter_audio: */
atomic_load(&value); /* OK: Lock-free */
ctx->cached_value; /* OK: Pre-computed in update() */
math operations; /* OK: CPU-bound is fine */
Safe Configuration Updates
/* Pattern: Update on main thread, read atomically on audio thread */
#include <stdatomic.h>
struct filter_data {
atomic_int gain_scaled; /* Atomic for thread-safe reads */
};
/* Main thread: update callback */
static void filter_update(void *data, obs_data_t *settings)
{
struct filter_data *ctx = data;
double db = obs_data_get_double(settings, "gain_db");
int scaled = (int)(db_to_mul((float)db) * 1000);
atomic_store(&ctx->gain_scaled, scaled);
}
/* Audio thread: filter_audio callback */
static struct obs_audio_data *filter_audio(void *data,
struct obs_audio_data *audio)
{
struct filter_data *ctx = data;
float gain = atomic_load(&ctx->gain_scaled) / 1000.0f;
/* ... process with gain ... */
return audio;
}
Grep Commands for Thread Safety
# Find mutex usage in audio callbacks
grep -B5 -A20 "filter_audio" src/*.c | grep -E "(mutex|lock|pthread)"
# Find memory allocation in audio callbacks
grep -B5 -A20 "filter_audio" src/*.c | grep -E "(malloc|alloc|bfree)"
# Find I/O in audio callbacks
grep -B5 -A20 "filter_audio" src/*.c | grep -E "(fopen|fread|fwrite)"
# Check for atomic usage
grep -n "atomic\|_Atomic" src/*.c
Callback Correctness Review
Required Callbacks
| Callback | Required? | Purpose |
|---|---|---|
.id |
YES | Unique identifier string |
.type |
YES | Source type enum |
.output_flags |
YES | Capability flags |
.get_name |
YES | Display name function |
.create |
YES | Instance creation |
.destroy |
YES | Instance cleanup |
.update |
Recommended | Settings application |
.get_defaults |
Recommended | Default values |
.get_properties |
Recommended | UI generation |
.filter_audio |
For audio filters | Audio processing |
Audio Filter Callback Review
/* Review this pattern in filter_audio: */
static struct obs_audio_data *filter_audio(void *data,
struct obs_audio_data *audio)
{
struct filter_data *ctx = data;
float **adata = (float **)audio->data;
/* CHECK 1: Are all channels processed? */
for (size_t c = 0; c < ctx->channels; c++) {
/* CHECK 2: Is NULL channel check present? */
if (!audio->data[c]) /* REQUIRED: Some channels may be NULL */
continue;
/* CHECK 3: Is frames count used correctly? */
for (size_t i = 0; i < audio->frames; i++) {
adata[c][i] *= ctx->gain;
}
}
/* CHECK 4: Is audio returned (not NULL)? */
return audio;
}
Callback Review Checklist
â¡ filter_audio checks audio->data[c] for NULL before access
â¡ filter_audio uses audio->frames for loop bounds (not hardcoded)
â¡ filter_audio returns the audio pointer
â¡ update callback converts dB to linear (if applicable)
â¡ destroy callback frees all allocated memory
â¡ create callback initializes all context fields
â¡ get_properties creates all needed UI elements
â¡ get_defaults sets sensible default values
Settings Validation Review
Settings API Patterns
/* GOOD: Proper settings handling */
static void filter_update(void *data, obs_data_t *settings)
{
struct filter_data *ctx = data;
/* Get values with type checking */
double gain_db = obs_data_get_double(settings, "gain_db");
bool enabled = obs_data_get_bool(settings, "enabled");
/* Validate ranges */
if (gain_db < -60.0) gain_db = -60.0;
if (gain_db > 30.0) gain_db = 30.0;
/* Convert and store */
ctx->gain = db_to_mul((float)gain_db);
ctx->enabled = enabled;
}
/* GOOD: Proper defaults */
static void filter_defaults(obs_data_t *settings)
{
obs_data_set_default_double(settings, "gain_db", 0.0);
obs_data_set_default_bool(settings, "enabled", true);
}
Common Settings Errors
| Error | Problem | Solution |
|---|---|---|
| No range validation | Values out of bounds | Check min/max in update |
| Wrong type function | Data corruption | Match obsdata_get* to data type |
| No defaults | Unpredictable behavior | Implement get_defaults |
| Hardcoded defaults | Maintenance burden | Use get_defaults callback |
Build System Review
Required Build Files
project/
âââ CMakeLists.txt # Build configuration
âââ buildspec.json # Plugin metadata
âââ src/
â âââ plugin-main.c # Module entry point
â âââ *.c # Source files
âââ data/
âââ locale/
âââ en-US.ini # Localization strings
CMakeLists.txt Review
# Required: Minimum CMake version
cmake_minimum_required(VERSION 3.28...3.30)
# Required: Project name and version
project(my-plugin VERSION 1.0.0)
# Required: Create module library
add_library(${CMAKE_PROJECT_NAME} MODULE)
# Required: Link libobs
find_package(libobs REQUIRED)
target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE OBS::libobs)
# Required: Add source files
target_sources(${CMAKE_PROJECT_NAME} PRIVATE
src/plugin-main.c
src/my-filter.c
)
Build Review Checklist
â¡ CMakeLists.txt uses cmake_minimum_required(VERSION 3.28)
â¡ add_library uses MODULE keyword
â¡ find_package(libobs REQUIRED) is present
â¡ target_link_libraries links OBS::libobs
â¡ All source files are listed in target_sources
â¡ buildspec.json has valid name and version
â¡ data/locale/en-US.ini exists with all strings
Automated Audit Script
Run this script to check for common issues:
#!/bin/bash
# obs-plugin-audit.sh
echo "=== OBS Plugin Audit ==="
echo ""
# P0: Critical checks
echo "P0 - CRITICAL:"
echo " Module macro:"
grep -l "OBS_DECLARE_MODULE" src/*.c || echo " MISSING: OBS_DECLARE_MODULE not found!"
echo " Module load:"
grep -A5 "obs_module_load" src/*.c | grep -q "return true" && echo " OK" || echo " WARNING: Check return value"
echo " Destroy callbacks:"
grep -l "bfree" src/*.c || echo " WARNING: No bfree calls found"
# P1: High priority
echo ""
echo "P1 - HIGH:"
echo " Static variables:"
grep -c "^static.*=" src/*.c | while read line; do echo " $line"; done
echo " Audio thread safety:"
grep -A20 "filter_audio" src/*.c | grep -E "(mutex|lock|malloc)" && echo " WARNING: Potential blocking in audio thread"
# P2: Medium priority
echo ""
echo "P2 - MEDIUM:"
echo " Localization:"
ls data/locale/*.ini 2>/dev/null || echo " WARNING: No locale files found"
echo " Defaults callback:"
grep -l "get_defaults" src/*.c || echo " WARNING: No get_defaults found"
echo ""
echo "=== Audit Complete ==="
Related Files
- CHECKLIST.md – Quick reference review checklist
Related Skills
- obs-plugin-developing – Entry point and overview
- obs-audio-plugin-writing – Audio plugin development patterns
Related Agent
Use obs-plugin-expert agent for:
- Coordinated development and review guidance
- Choosing between skills based on task
- Complex plugin development workflows