Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 52 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,59 +1,73 @@
# OBS Plugin Template
# Mix Track to Source

## Introduction
```mermaid
flowchart TB
subgraph OBS Mix tracks
T1["Any Track
from 1 to 6"]
T2@{ shape: procs, label: "Output Tracks" }
end

The plugin template is meant to be used as a starting point for OBS Studio plugin development. It includes:
subgraph Mix Track to Source Plugin
subgraph P1["mt2s_callback()"]
P2["obs_source_output_audio()"]
end

* Boilerplate plugin source code
* A CMake project file
* GitHub Actions workflows and repository actions
subgraph OBS Audio Mixer
S["Source"]
end
P2-->S
end
T1-->P1
S-->T2
S-.-x|"To avoid loopback,
the input track
cannot be output"|T1
```

## Supported Build Environments
- Select an input mix track to create an audio source
- The audio source uses the selected mix track as input and allows various filters to be applied
- Allows selection of mix tracks as output, excluding the mix track selected as input
- Buffering causes a minimum delay of 1024 samples (21 ms at 48 kHz)
- If even greater delay is unacceptable, enable **Low Latency Buffering Mode** in OBS Settings > Audio > Advanced

| Platform | Tool |
|-----------|--------|
| Windows | Visual Studio 17 2022 |
| macOS | XCode 16.0 |
| Windows, macOS | CMake 3.30.5 |
| Ubuntu 24.04 | CMake 3.28.3 |
| Ubuntu 24.04 | `ninja-build` |
| Ubuntu 24.04 | `pkg-config`
| Ubuntu 24.04 | `build-essential` |
## Install

## Quick Start
Please download the archive file from the [Releases](https://github.com/semnil/MixTrack2Source/releases) page.

An absolute bare-bones [Quick Start Guide](https://github.com/obsproject/obs-plugintemplate/wiki/Quick-Start-Guide) is available in the wiki.
### Windows

## Documentation
After extracting the archive file, place the `mix-track-to-source` folder in the following location:

All documentation can be found in the [Plugin Template Wiki](https://github.com/obsproject/obs-plugintemplate/wiki).
```
C:\ProgramData\obs-studio\plugins
```

Suggested reading to get up and running:

* [Getting started](https://github.com/obsproject/obs-plugintemplate/wiki/Getting-Started)
* [Build system requirements](https://github.com/obsproject/obs-plugintemplate/wiki/Build-System-Requirements)
* [Build system options](https://github.com/obsproject/obs-plugintemplate/wiki/CMake-Build-System-Options)
### macOS

## GitHub Actions & CI
Run the `mix-track-to-source-<version>-macos-universal.pkg` file to install it.

Default GitHub Actions workflows are available for the following repository actions:

* `push`: Run for commits or tags pushed to `master` or `main` branches.
* `pr-pull`: Run when a Pull Request has been pushed or synchronized.
* `dispatch`: Run when triggered by the workflow dispatch in GitHub's user interface.
* `build-project`: Builds the actual project and is triggered by other workflows.
* `check-format`: Checks CMake and plugin source code formatting and is triggered by other workflows.
### Ubuntu

The workflows make use of GitHub repository actions (contained in `.github/actions`) and build scripts (contained in `.github/scripts`) which are not needed for local development, but might need to be adjusted if additional/different steps are required to build the plugin.
Run the following command:

### Retrieving build artifacts
```
sudo dpkg -i mix-track-to-source-<version>-x86_64-linux-gnu.deb
```

Successful builds on GitHub Actions will produce build artifacts that can be downloaded for testing. These artifacts are commonly simple archives and will not contain package installers or installation programs.

### Building a Release
## Usage

To create a release, an appropriately named tag needs to be pushed to the `main`/`master` branch using semantic versioning (e.g., `12.3.4`, `23.4.5-beta2`). A draft release will be created on the associated repository with generated installer packages or installation programs attached as release artifacts.
- `Add Source` > `Mix Track` to add an audio source
- Select a mix track to use for input from `Track 1` to `Track 6`
- Select outputs for added audio track in the Advanced Audio Properties window
- Cannot select the same track for both input and output
- Deselect the output from other sources as needed

## Signing and Notarizing on macOS

Basic concepts of codesigning and notarization on macOS are explained in the correspodning [Wiki article](https://github.com/obsproject/obs-plugintemplate/wiki/Codesigning-On-macOS) which has a specific section for the [GitHub Actions setup](https://github.com/obsproject/obs-plugintemplate/wiki/Codesigning-On-macOS#setting-up-code-signing-for-github-actions).
## Information for development

Please refer to the template repository information.
https://github.com/obsproject/obs-plugintemplate
12 changes: 6 additions & 6 deletions buildspec.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@
},
"platformConfig": {
"macos": {
"bundleId": "com.example.plugintemplate-for-obs"
"bundleId": "com.semnil.mix-track-to-source"
}
},
"name": "plugintemplate-for-obs",
"displayName": "Plugin Template for OBS",
"name": "mix-track-to-source",
"displayName": "Mix Track to Source",
"version": "1.0.0",
"author": "Your Name Here",
"website": "https://example.com",
"email": "me@example.com"
"author": "semnil",
"website": "https://semnil.com",
"email": "info@semnil.com"
}
8 changes: 8 additions & 0 deletions data/locale/en-US.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
MixTrack="Mix Track"
Input="Input"
Track1="Track 1"
Track2="Track 2"
Track3="Track 3"
Track4="Track 4"
Track5="Track 5"
Track6="Track 6"
8 changes: 8 additions & 0 deletions data/locale/ja-JP.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
MixTrack="ミックストラック"
Input="入力"
Track1="トラック 1"
Track2="トラック 2"
Track3="トラック 3"
Track4="トラック 4"
Track5="トラック 5"
Track6="トラック 6"
165 changes: 163 additions & 2 deletions src/plugin-main.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
Plugin Name
Copyright (C) <Year> <Developer> <Email Address>
Mix Track to Source
Copyright (C) 2026 semnil info@semnil.com

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
Expand All @@ -22,8 +22,169 @@ with this program. If not, see <https://www.gnu.org/licenses/>
OBS_DECLARE_MODULE()
OBS_MODULE_USE_DEFAULT_LOCALE(PLUGIN_NAME, "en-US")

typedef struct {
obs_source_t *source;
size_t mix_idx;
} mt2s_context_t;

void mt2s_avoid_loopback(mt2s_context_t *context, size_t mix_idx)
{
// override output settings to avoid loopback
size_t mixers = obs_source_get_audio_mixers(context->source);
if (mixers != (mixers & ~(1 << mix_idx)))
obs_source_set_audio_mixers(context->source, mixers & ~(1 << mix_idx));
}

static void mt2s_callback(void *param, size_t mix_idx, struct audio_data *data)
{
mt2s_context_t *context = (mt2s_context_t *)param;
if (!context || context->mix_idx != mix_idx || !data || !data->frames)
return;

mt2s_avoid_loopback(context, mix_idx);

struct obs_audio_info oai;
if (obs_get_audio_info(&oai)) {
// bridge data from the mix track to the audio source
struct obs_source_audio output_audio = {.frames = data->frames,
.speakers = oai.speakers,
.format = AUDIO_FORMAT_FLOAT_PLANAR,
.samples_per_sec = oai.samples_per_sec,
.timestamp = data->timestamp};

for (int i = 0; i < MAX_AV_PLANES; i++) {
output_audio.data[i] = data->data[i];
}

obs_source_output_audio(context->source, &output_audio);
}
}

void mt2s_disconnect(mt2s_context_t *context)
{
if (!context || context->mix_idx >= MAX_AUDIO_MIXES)
return;

// disconnect the connected mix track
audio_output_disconnect(obs_get_audio(), context->mix_idx, mt2s_callback, context);
obs_log(LOG_INFO, "audio_output_disconnect(%u)", context->mix_idx);
// initialize the connected mix track id
context->mix_idx = SIZE_MAX;
}

void mt2s_connect(mt2s_context_t *context, size_t mix_idx)
{
audio_t *audio = obs_get_audio();

if (context->mix_idx != mix_idx) {
// disconnect the connected mix track
mt2s_disconnect(context);
}

// connect the mix track
if (audio_output_connect(audio, mix_idx, NULL, mt2s_callback, context)) {
// save the connected mix track id
context->mix_idx = mix_idx;
obs_log(LOG_INFO, "audio_output_connect(%u)", context->mix_idx);
}
}

// lifecycle
void *mt2s_create(obs_data_t *settings, obs_source_t *source)
{
mt2s_context_t *context = malloc(sizeof(mt2s_context_t));

if (context) {
context->source = source;
context->mix_idx = SIZE_MAX;

// deselect all outputs
obs_source_set_audio_mixers(context->source, 0);

mt2s_connect(context, obs_data_get_int(settings, "MixTrack"));
}

return context;
}

void mt2s_destroy(void *data)
{
if (!data)
return;

mt2s_context_t *context = (mt2s_context_t *)data;
mt2s_disconnect(context);
free(context);
}

void mt2s_update(void *data, obs_data_t *settings)
{
if (!data || !settings)
return;

mt2s_context_t *context = (mt2s_context_t *)data;
mt2s_connect(context, obs_data_get_int(settings, "MixTrack"));
}

// configs
const char *obs_module_name(void)
{
return "Mix Track to Source";
}

const char *obs_module_description(void)
{
return "Routing Mix Track to Source";
}

static const char *get_name(void *type_data)
{
UNUSED_PARAMETER(type_data);

return obs_module_text("MixTrack");
}

static void get_defaults(obs_data_t *settings)
{
if (!settings)
return;

obs_data_set_default_int(settings, "MixTrack", 0);
}

static obs_properties_t *get_properties(void *data)
{
UNUSED_PARAMETER(data);

obs_properties_t *props = obs_properties_create();

obs_property_t *list = obs_properties_add_list(props, "MixTrack", obs_module_text("Input"), OBS_COMBO_TYPE_LIST,
OBS_COMBO_FORMAT_INT);
obs_property_list_add_int(list, obs_module_text("Track1"), 0);
obs_property_list_add_int(list, obs_module_text("Track2"), 1);
obs_property_list_add_int(list, obs_module_text("Track3"), 2);
obs_property_list_add_int(list, obs_module_text("Track4"), 3);
obs_property_list_add_int(list, obs_module_text("Track5"), 4);
obs_property_list_add_int(list, obs_module_text("Track6"), 5);

return props;
}

// module
bool obs_module_load(void)
{
struct obs_source_info info = {.id = PLUGIN_NAME,
.type = OBS_SOURCE_TYPE_INPUT,
.output_flags = OBS_SOURCE_AUDIO | OBS_SOURCE_DO_NOT_DUPLICATE,
.icon_type = OBS_ICON_TYPE_AUDIO_OUTPUT,
.get_name = get_name,
.create = mt2s_create,
.destroy = mt2s_destroy,
.get_defaults = get_defaults,
.get_properties = get_properties,
.update = mt2s_update};
obs_register_source(&info);

obs_log(LOG_INFO, "plugin loaded successfully (version %s)", PLUGIN_VERSION);
return true;
}
Expand Down
4 changes: 2 additions & 2 deletions src/plugin-support.c.in
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
Plugin Name
Copyright (C) <Year> <Developer> <Email Address>
Mix Track to Source
Copyright (C) 2026 semnil info@semnil.com

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
Expand Down
4 changes: 2 additions & 2 deletions src/plugin-support.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
Plugin Name
Copyright (C) <Year> <Developer> <Email Address>
Mix Track to Source
Copyright (C) 2026 semnil info@semnil.com

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
Expand Down