obs-audio-plugin-writing

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

OBS Audio Plugin Development

OBS Studio音频插件开发

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.
开发OBS Studio音频插件,包括音频源(生成器、捕获设备)和音频滤镜(增益、均衡器、压缩器)。涵盖实时音频处理模式、filter_audio回调以及音频专属设置。

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
  • 视频插件 → 请使用obs-plugin-developing(后续技能)
  • 输出/编码器插件 → 请使用obs-plugin-developing
  • 代码审核 → 请使用obs-plugin-reviewing

Quick Start: Audio Filter in 5 Steps

快速入门:5步实现音频滤镜

Step 1: Create Plugin Structure

步骤1:创建插件结构

c
#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;
}
c
#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

步骤2:定义上下文结构

c
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) */
};
c
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

步骤3:实现核心回调函数

c
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());
}
c
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

步骤4:实现filter_audio回调函数

c
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;
}
c
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

步骤5:定义设置与属性

c
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;
}
c
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

步骤6:注册滤镜

c
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,
};
c
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.
音频源用于生成或捕获音频,并将其推送至OBS。

Audio Source Structure

音频源结构

c
struct source_data {
    obs_source_t *source;
    pthread_t thread;
    os_event_t *event;
    bool active;
    uint64_t timestamp;
};
c
struct source_data {
    obs_source_t *source;
    pthread_t thread;
    os_event_t *event;
    bool active;
    uint64_t timestamp;
};

Audio Source Registration

音频源注册

c
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() */
};
c
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

推送音频数据

c
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;
}
c
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)

obs_audio_data(滤镜输入)

c
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 */
};
c
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)

obs_source_audio(源输出)

c
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;
};
c
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_MONO
1Single channel
SPEAKERS_STEREO
2Left, Right
SPEAKERS_2POINT1
3L, R, LFE
SPEAKERS_4POINT0
4L, R, C, S
SPEAKERS_4POINT1
5L, R, C, LFE, S
SPEAKERS_5POINT1
6L, R, C, LFE, SL, SR
SPEAKERS_7POINT1
8L, R, C, LFE, SL, SR, BL, BR
常量声道数描述
SPEAKERS_MONO
1单声道
SPEAKERS_STEREO
2左声道、右声道
SPEAKERS_2POINT1
3左、右、低音炮
SPEAKERS_4POINT0
4左、右、中置、环绕声
SPEAKERS_4POINT1
5左、右、中置、低音炮、环绕声
SPEAKERS_5POINT1
6左、右、中置、低音炮、左环绕、右环绕
SPEAKERS_7POINT1
8左、右、中置、低音炮、左环绕、右环绕、左后环绕、右后环绕

Audio Formats

音频格式

ConstantTypeDescription
AUDIO_FORMAT_U8BIT
uint8_tUnsigned 8-bit
AUDIO_FORMAT_16BIT
int16_tSigned 16-bit
AUDIO_FORMAT_32BIT
int32_tSigned 32-bit
AUDIO_FORMAT_FLOAT
float32-bit float interleaved
AUDIO_FORMAT_FLOAT_PLANAR
float32-bit float planar
常量类型描述
AUDIO_FORMAT_U8BIT
uint8_t无符号8位音频
AUDIO_FORMAT_16BIT
int16_t有符号16位音频
AUDIO_FORMAT_32BIT
int32_t有符号32位音频
AUDIO_FORMAT_FLOAT
float32位浮点交错音频
AUDIO_FORMAT_FLOAT_PLANAR
float32位浮点平面音频

Audio Math Functions

音频数学函数

Include
<media-io/audio-math.h>
for these utilities:
c
/* 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 */
引入
<media-io/audio-math.h>
以使用以下工具函数:
c
/* 将分贝转换为线性倍数 */
float db_to_mul(float db);
/* 示例:db_to_mul(-6.0) ≈ 0.5(音量减半) */

/* 将线性倍数转换为分贝 */
float mul_to_db(float mul);
/* 示例:mul_to_db(0.5) ≈ -6.0 dB */

Audio Properties UI

音频属性UI

Gain Slider (dB)

增益滑块(分贝)

c
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");
c
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

声道选择器

c
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);
c
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

频率选择器

c
obs_property_t *freq = obs_properties_add_int_slider(
    props, "frequency", "Frequency (Hz)", 20, 20000, 1);
obs_property_int_set_suffix(freq, " Hz");
c
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 destroy
Memory leakAlways free context
Global stateThread safety issuesUse context struct
Blocking in
filter_audio
Audio glitchesNever block/wait
Ignoring NULL channelsCrashCheck
audio->data[c]
Ignoring
audio->frames
Buffer overflowAlways use frame count
Malloc in
filter_audio
Performance issuesPre-allocate buffers
模式问题点解决方案
缺失
OBS_DECLARE_MODULE()
插件无法加载在文件开头添加该宏
obs_module_load()
返回
false
插件静默加载失败成功时返回
true
销毁函数中缺失
bfree()
内存泄漏务必释放上下文内存
使用全局状态线程安全问题使用上下文结构体
filter_audio
中阻塞线程
音频出现卡顿绝对不要阻塞/等待
忽略NULL声道程序崩溃检查
audio->data[c]
是否为空
忽略
audio->frames
缓冲区溢出始终使用帧计数进行处理
filter_audio
中调用malloc
性能问题
create
回调中预分配缓冲区

Anti-Pattern Examples

反模式示例

c
/* 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 */
c
/* 错误:全局状态 */
static float g_gain = 1.0;  /* 错误 - 非线程安全 */

/* 正确:上下文结构体 */
struct filter_data {
    float gain;  /* 每个实例独立的状态 */
};

/* 错误:忽略NULL声道 */
for (size_t c = 0; c < channels; c++) {
    adata[c][i] *= gain;  /* 若声道不存在会崩溃 */
}

/* 正确:检查声道是否存在 */
for (size_t c = 0; c < channels; c++) {
    if (audio->data[c]) {  /* 先检查 */
        adata[c][i] *= gain;
    }
}

/* 错误:在音频回调中阻塞 */
static struct obs_audio_data *filter_audio(void *data, struct obs_audio_data *audio)
{
    pthread_mutex_lock(&mutex);  /* 错误 - 会导致卡顿 */
    /* ... */
}

/* 正确:使用原子操作或无锁结构 */

Common Pitfalls

常见陷阱

ProblemCauseSolution
No audio outputWrong
output_flags
Use
OBS_SOURCE_AUDIO
Filter not appliedMissing
filter_audio
callback
Implement 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_defaults
Implement defaults callback
问题点原因解决方案
无音频输出
output_flags
设置错误
使用
OBS_SOURCE_AUDIO
滤镜未生效缺失
filter_audio
回调
实现该回调函数
音频出现爆音/杂音音频线程被阻塞绝对不要阻塞线程
音量过大直接使用分贝值使用
db_to_mul()
进行转换
仅支持单声道硬编码声道数使用
audio_output_get_channels()
获取声道数
设置未保存缺失
get_defaults
回调
实现默认设置回调函数

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)
c
/* 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 ... */
}
filter_audio
回调运行在独立于主线程的音频线程中。
规则:
  1. 绝对不要阻塞线程(不使用互斥锁、I/O操作、内存分配)
  2. 共享状态使用原子操作
  3. 所有缓冲区在
    create
    回调中预分配
  4. 配置变更在
    update
    (主线程)中进行
c
/* 安全模式:原子更新增益 */
#include <stdatomic.h>

struct filter_data {
    atomic_int gain_int;  /* 将增益*1000存储为整数 */
};

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;
    /* ... 处理音频 ... */
}

External Documentation

外部文档

Context7 (Real-time docs)

Context7(实时文档)

mcp__context7__get-library-docs
context7CompatibleLibraryID: "/obsproject/obs-studio"
topic: "audio filter plugin filter_audio"
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
  • REFERENCE.md - 完整API文档
  • TEMPLATES.md - 可直接使用的代码模板

Related Skills

相关技能

  • obs-plugin-developing - Plugin types overview, build system
  • obs-plugin-reviewing - Code review checklist
  • obs-plugin-developing - 插件类型概述、构建系统
  • obs-plugin-reviewing - 代码审核清单