obs-audio-plugin-writing

📁 meriley/claude-code-skills 📅 5 days ago
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:

  1. Never block (no mutexes, no I/O, no allocations)
  2. Use atomic operations for shared state
  3. Pre-allocate all buffers in create callback
  4. 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

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