obs-audio-plugin-writing

Create OBS Studio audio plugins including audio sources, audio filters, and real-time audio processing. Covers obs_source_info for audio, filter_audio callback, audio data structures, settings API, and properties UI. Use when developing audio plugins for OBS.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "obs-audio-plugin-writing" with this command: npx skills add meriley/claude-code-skills/meriley-claude-code-skills-obs-audio-plugin-writing

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

ConstantChannelsDescription
SPEAKERS_MONO1Single channel
SPEAKERS_STEREO2Left, Right
SPEAKERS_2POINT13L, R, LFE
SPEAKERS_4POINT04L, R, C, S
SPEAKERS_4POINT15L, R, C, LFE, S
SPEAKERS_5POINT16L, R, C, LFE, SL, SR
SPEAKERS_7POINT18L, R, C, LFE, SL, SR, BL, BR

Audio Formats

ConstantTypeDescription
AUDIO_FORMAT_U8BITuint8_tUnsigned 8-bit
AUDIO_FORMAT_16BITint16_tSigned 16-bit
AUDIO_FORMAT_32BITint32_tSigned 32-bit
AUDIO_FORMAT_FLOATfloat32-bit float interleaved
AUDIO_FORMAT_FLOAT_PLANARfloat32-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

PatternProblemSolution
Missing OBS_DECLARE_MODULE()Plugin won't loadAdd macro at file start
return false from obs_module_load()Plugin fails silentlyReturn true on success
Missing bfree() in destroyMemory leakAlways free context
Global stateThread safety issuesUse context struct
Blocking in filter_audioAudio glitchesNever block/wait
Ignoring NULL channelsCrashCheck audio->data[c]
Ignoring audio->framesBuffer overflowAlways use frame count
Malloc in filter_audioPerformance issuesPre-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

ProblemCauseSolution
No audio outputWrong output_flagsUse OBS_SOURCE_AUDIO
Filter not appliedMissing filter_audio callbackImplement callback
Crackles/popsBlocking in audio threadNever block
Volume too loudUsing dB directlyConvert with db_to_mul()
Mono onlyHardcoded channel countUse audio_output_get_channels()
Settings not savedMissing get_defaultsImplement 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

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

obs-cpp-qt-patterns

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

vendure-admin-ui-writing

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

vendure-entity-writing

No summary provided by upstream source.

Repository SourceNeeds Review