obs-audio-plugin-writing
1
总安装量
1
周安装量
#43090
全站排名
安装命令
npx skills add https://github.com/meriley/claude-code-skills --skill obs-audio-plugin-writing
Agent 安装分布
amp
1
opencode
1
kimi-cli
1
codex
1
github-copilot
1
claude-code
1
Skill 文档
OBS Audio Plugin Development
Purpose
Develop OBS Studio audio plugins including audio sources (generators, capture) and audio filters (gain, EQ, compression). Covers real-time audio processing patterns, the filter_audio callback, and audio-specific settings.
When NOT to Use
- Video plugins â Use obs-plugin-developing (future skills)
- Output/encoder plugins â Use obs-plugin-developing
- Code review â Use obs-plugin-reviewing
Quick Start: Audio Filter in 5 Steps
Step 1: Create Plugin Structure
#include <obs-module.h>
#include <media-io/audio-math.h> /* For db_to_mul, mul_to_db */
OBS_DECLARE_MODULE()
OBS_MODULE_USE_DEFAULT_LOCALE("my-audio-filter", "en-US")
/* Forward declaration */
extern struct obs_source_info my_audio_filter;
bool obs_module_load(void)
{
obs_register_source(&my_audio_filter);
return true;
}
Step 2: Define Context Structure
struct filter_data {
obs_source_t *context; /* Required: Reference to this filter */
size_t channels; /* Number of audio channels */
float gain; /* Gain multiplier (linear, not dB) */
};
Step 3: Implement Core Callbacks
static const char *filter_name(void *unused)
{
UNUSED_PARAMETER(unused);
return obs_module_text("MyAudioFilter");
}
static void *filter_create(obs_data_t *settings, obs_source_t *filter)
{
struct filter_data *ctx = bzalloc(sizeof(*ctx));
ctx->context = filter;
filter_update(ctx, settings); /* Apply initial settings */
return ctx;
}
static void filter_destroy(void *data)
{
struct filter_data *ctx = data;
bfree(ctx); /* CRITICAL: Always free allocated memory */
}
static void filter_update(void *data, obs_data_t *settings)
{
struct filter_data *ctx = data;
double db = obs_data_get_double(settings, "gain_db");
ctx->gain = db_to_mul((float)db);
ctx->channels = audio_output_get_channels(obs_get_audio());
}
Step 4: Implement filter_audio Callback
static struct obs_audio_data *filter_audio(void *data,
struct obs_audio_data *audio)
{
struct filter_data *ctx = data;
float **adata = (float **)audio->data;
for (size_t c = 0; c < ctx->channels; c++) {
if (audio->data[c]) { /* CRITICAL: Check channel exists */
for (size_t i = 0; i < audio->frames; i++) {
adata[c][i] *= ctx->gain;
}
}
}
return audio;
}
Step 5: Define Settings & Properties
static void filter_defaults(obs_data_t *settings)
{
obs_data_set_default_double(settings, "gain_db", 0.0);
}
static obs_properties_t *filter_properties(void *data)
{
UNUSED_PARAMETER(data);
obs_properties_t *props = obs_properties_create();
obs_property_t *p = obs_properties_add_float_slider(
props, "gain_db", obs_module_text("Gain"),
-30.0, 30.0, 0.1);
obs_property_float_set_suffix(p, " dB");
return props;
}
Step 6: Register the Filter
struct obs_source_info my_audio_filter = {
.id = "my_audio_filter",
.type = OBS_SOURCE_TYPE_FILTER,
.output_flags = OBS_SOURCE_AUDIO,
.get_name = filter_name,
.create = filter_create,
.destroy = filter_destroy,
.update = filter_update,
.filter_audio = filter_audio,
.get_defaults = filter_defaults,
.get_properties = filter_properties,
};
Audio Source Development
Audio sources generate or capture audio and push it to OBS.
Audio Source Structure
struct source_data {
obs_source_t *source;
pthread_t thread;
os_event_t *event;
bool active;
uint64_t timestamp;
};
Audio Source Registration
struct obs_source_info my_audio_source = {
.id = "my_audio_source",
.type = OBS_SOURCE_TYPE_INPUT,
.output_flags = OBS_SOURCE_AUDIO,
.get_name = source_name,
.create = source_create,
.destroy = source_destroy,
/* Audio sources typically don't need filter_audio */
/* Instead, they push audio via obs_source_output_audio() */
};
Pushing Audio Data
static void push_audio(struct source_data *ctx, uint8_t *buffer, size_t frames)
{
struct obs_source_audio audio = {
.data[0] = buffer,
.frames = frames,
.speakers = SPEAKERS_MONO, /* Or SPEAKERS_STEREO, etc. */
.samples_per_sec = 48000,
.timestamp = ctx->timestamp,
.format = AUDIO_FORMAT_FLOAT_PLANAR,
};
obs_source_output_audio(ctx->source, &audio);
ctx->timestamp += (uint64_t)frames * 1000000000ULL / 48000;
}
Audio Data Structures
obs_audio_data (Filter Input)
struct obs_audio_data {
uint8_t *data[MAX_AV_PLANES]; /* Audio channel data */
uint32_t frames; /* Number of audio frames */
uint64_t timestamp; /* Timestamp in nanoseconds */
};
obs_source_audio (Source Output)
struct obs_source_audio {
const uint8_t *data[MAX_AV_PLANES];
uint32_t frames;
enum speaker_layout speakers;
enum audio_format format;
uint32_t samples_per_sec;
uint64_t timestamp;
};
Speaker Layouts
| Constant | Channels | Description |
|---|---|---|
SPEAKERS_MONO |
1 | Single channel |
SPEAKERS_STEREO |
2 | Left, Right |
SPEAKERS_2POINT1 |
3 | L, R, LFE |
SPEAKERS_4POINT0 |
4 | L, R, C, S |
SPEAKERS_4POINT1 |
5 | L, R, C, LFE, S |
SPEAKERS_5POINT1 |
6 | L, R, C, LFE, SL, SR |
SPEAKERS_7POINT1 |
8 | L, R, C, LFE, SL, SR, BL, BR |
Audio Formats
| Constant | Type | Description |
|---|---|---|
AUDIO_FORMAT_U8BIT |
uint8_t | Unsigned 8-bit |
AUDIO_FORMAT_16BIT |
int16_t | Signed 16-bit |
AUDIO_FORMAT_32BIT |
int32_t | Signed 32-bit |
AUDIO_FORMAT_FLOAT |
float | 32-bit float interleaved |
AUDIO_FORMAT_FLOAT_PLANAR |
float | 32-bit float planar |
Audio Math Functions
Include <media-io/audio-math.h> for these utilities:
/* Convert decibels to linear multiplier */
float db_to_mul(float db);
/* Example: db_to_mul(-6.0) â 0.5 (half volume) */
/* Convert linear multiplier to decibels */
float mul_to_db(float mul);
/* Example: mul_to_db(0.5) â -6.0 dB */
Audio Properties UI
Gain Slider (dB)
obs_property_t *p = obs_properties_add_float_slider(
props, "gain_db", "Gain", -30.0, 30.0, 0.1);
obs_property_float_set_suffix(p, " dB");
Channel Selector
obs_property_t *ch = obs_properties_add_list(
props, "channel", "Channel",
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
obs_property_list_add_int(ch, "All", -1);
obs_property_list_add_int(ch, "Left", 0);
obs_property_list_add_int(ch, "Right", 1);
Frequency Selector
obs_property_t *freq = obs_properties_add_int_slider(
props, "frequency", "Frequency (Hz)", 20, 20000, 1);
obs_property_int_set_suffix(freq, " Hz");
FORBIDDEN Patterns
Critical Violations
| Pattern | Problem | Solution |
|---|---|---|
Missing OBS_DECLARE_MODULE() |
Plugin won’t load | Add macro at file start |
return false from obs_module_load() |
Plugin fails silently | Return true on success |
Missing bfree() in destroy |
Memory leak | Always free context |
| Global state | Thread safety issues | Use context struct |
Blocking in filter_audio |
Audio glitches | Never block/wait |
| Ignoring NULL channels | Crash | Check audio->data[c] |
Ignoring audio->frames |
Buffer overflow | Always use frame count |
Malloc in filter_audio |
Performance issues | Pre-allocate buffers |
Anti-Pattern Examples
/* BAD: Global state */
static float g_gain = 1.0; /* WRONG - not thread safe */
/* GOOD: Context structure */
struct filter_data {
float gain; /* Per-instance state */
};
/* BAD: Ignoring NULL channels */
for (size_t c = 0; c < channels; c++) {
adata[c][i] *= gain; /* CRASH if channel doesn't exist */
}
/* GOOD: Check channel exists */
for (size_t c = 0; c < channels; c++) {
if (audio->data[c]) { /* Check first */
adata[c][i] *= gain;
}
}
/* BAD: Blocking in audio callback */
static struct obs_audio_data *filter_audio(void *data, struct obs_audio_data *audio)
{
pthread_mutex_lock(&mutex); /* WRONG - can cause glitches */
/* ... */
}
/* GOOD: Use atomic operations or lock-free structures */
Common Pitfalls
| Problem | Cause | Solution |
|---|---|---|
| No audio output | Wrong output_flags |
Use OBS_SOURCE_AUDIO |
| Filter not applied | Missing filter_audio callback |
Implement callback |
| Crackles/pops | Blocking in audio thread | Never block |
| Volume too loud | Using dB directly | Convert with db_to_mul() |
| Mono only | Hardcoded channel count | Use audio_output_get_channels() |
| Settings not saved | Missing get_defaults |
Implement defaults callback |
Thread Safety
The filter_audio callback runs on the audio thread, separate from the main thread.
Rules:
- Never block (no mutexes, no I/O, no allocations)
- Use atomic operations for shared state
- Pre-allocate all buffers in
createcallback - Configuration changes happen in
update(main thread)
/* Safe pattern: atomic gain update */
#include <stdatomic.h>
struct filter_data {
atomic_int gain_int; /* Store gain * 1000 as int */
};
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 gain_scaled = (int)(db_to_mul((float)db) * 1000);
atomic_store(&ctx->gain_int, gain_scaled);
}
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_int) / 1000.0f;
/* ... process audio ... */
}
External Documentation
Context7 (Real-time docs)
mcp__context7__get-library-docs
context7CompatibleLibraryID: "/obsproject/obs-studio"
topic: "audio filter plugin filter_audio"
Official References
- Source Reference: https://docs.obsproject.com/reference-sources
- Settings Reference: https://docs.obsproject.com/reference-settings
- Properties Reference: https://docs.obsproject.com/reference-properties
- Gain Filter Example: https://github.com/obsproject/obs-studio/blob/master/plugins/obs-filters/gain-filter.c
Related Files
- REFERENCE.md – Complete API documentation
- TEMPLATES.md – Ready-to-use code templates
Related Skills
- obs-plugin-developing – Plugin types overview, build system
- obs-plugin-reviewing – Code review checklist