obs-audio-plugin-writing
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseOBS 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
扬声器布局
| Constant | Channels | Description |
|---|---|---|
| 1 | Single channel |
| 2 | Left, Right |
| 3 | L, R, LFE |
| 4 | L, R, C, S |
| 5 | L, R, C, LFE, S |
| 6 | L, R, C, LFE, SL, SR |
| 8 | L, R, C, LFE, SL, SR, BL, BR |
| 常量 | 声道数 | 描述 |
|---|---|---|
| 1 | 单声道 |
| 2 | 左声道、右声道 |
| 3 | 左、右、低音炮 |
| 4 | 左、右、中置、环绕声 |
| 5 | 左、右、中置、低音炮、环绕声 |
| 6 | 左、右、中置、低音炮、左环绕、右环绕 |
| 8 | 左、右、中置、低音炮、左环绕、右环绕、左后环绕、右后环绕 |
Audio Formats
音频格式
| Constant | Type | Description |
|---|---|---|
| uint8_t | Unsigned 8-bit |
| int16_t | Signed 16-bit |
| int32_t | Signed 32-bit |
| float | 32-bit float interleaved |
| float | 32-bit float planar |
| 常量 | 类型 | 描述 |
|---|---|---|
| uint8_t | 无符号8位音频 |
| int16_t | 有符号16位音频 |
| int32_t | 有符号32位音频 |
| float | 32位浮点交错音频 |
| float | 32位浮点平面音频 |
Audio Math Functions
音频数学函数
Include for these utilities:
<media-io/audio-math.h>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
严重违规项
| Pattern | Problem | Solution |
|---|---|---|
Missing | Plugin won't load | Add macro at file start |
| Plugin fails silently | Return |
Missing | Memory leak | Always free context |
| Global state | Thread safety issues | Use context struct |
Blocking in | Audio glitches | Never block/wait |
| Ignoring NULL channels | Crash | Check |
Ignoring | Buffer overflow | Always use frame count |
Malloc in | Performance issues | Pre-allocate buffers |
| 模式 | 问题点 | 解决方案 |
|---|---|---|
缺失 | 插件无法加载 | 在文件开头添加该宏 |
| 插件静默加载失败 | 成功时返回 |
销毁函数中缺失 | 内存泄漏 | 务必释放上下文内存 |
| 使用全局状态 | 线程安全问题 | 使用上下文结构体 |
在 | 音频出现卡顿 | 绝对不要阻塞/等待 |
| 忽略NULL声道 | 程序崩溃 | 检查 |
忽略 | 缓冲区溢出 | 始终使用帧计数进行处理 |
在 | 性能问题 | 在 |
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
常见陷阱
| Problem | Cause | Solution |
|---|---|---|
| No audio output | Wrong | Use |
| Filter not applied | Missing | Implement callback |
| Crackles/pops | Blocking in audio thread | Never block |
| Volume too loud | Using dB directly | Convert with |
| Mono only | Hardcoded channel count | Use |
| Settings not saved | Missing | Implement defaults callback |
| 问题点 | 原因 | 解决方案 |
|---|---|---|
| 无音频输出 | | 使用 |
| 滤镜未生效 | 缺失 | 实现该回调函数 |
| 音频出现爆音/杂音 | 音频线程被阻塞 | 绝对不要阻塞线程 |
| 音量过大 | 直接使用分贝值 | 使用 |
| 仅支持单声道 | 硬编码声道数 | 使用 |
| 设置未保存 | 缺失 | 实现默认设置回调函数 |
Thread Safety
线程安全
The callback runs on the audio thread, separate from the main thread.
filter_audioRules:
- Never block (no mutexes, no I/O, no allocations)
- Use atomic operations for shared state
- Pre-allocate all buffers in callback
create - Configuration changes happen in (main thread)
update
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规则:
- 绝对不要阻塞线程(不使用互斥锁、I/O操作、内存分配)
- 共享状态使用原子操作
- 所有缓冲区在回调中预分配
create - 配置变更在(主线程)中进行
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
官方参考
- 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
- 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 - 代码审核清单