diff --git a/README.md b/README.md index 49b5574..4921849 100644 --- a/README.md +++ b/README.md @@ -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--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--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 diff --git a/buildspec.json b/buildspec.json index 55b5362..143fb01 100644 --- a/buildspec.json +++ b/buildspec.json @@ -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" } diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index e69de29..af21b73 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -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" diff --git a/data/locale/ja-JP.ini b/data/locale/ja-JP.ini new file mode 100644 index 0000000..25f7cad --- /dev/null +++ b/data/locale/ja-JP.ini @@ -0,0 +1,8 @@ +MixTrack="ミックストラック" +Input="入力" +Track1="トラック 1" +Track2="トラック 2" +Track3="トラック 3" +Track4="トラック 4" +Track5="トラック 5" +Track6="トラック 6" diff --git a/src/plugin-main.c b/src/plugin-main.c index f32f66f..1220d92 100644 --- a/src/plugin-main.c +++ b/src/plugin-main.c @@ -1,6 +1,6 @@ /* -Plugin Name -Copyright (C) +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 @@ -22,8 +22,169 @@ with this program. If not, see 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; } diff --git a/src/plugin-support.c.in b/src/plugin-support.c.in index 44a2c1c..e05fda8 100644 --- a/src/plugin-support.c.in +++ b/src/plugin-support.c.in @@ -1,6 +1,6 @@ /* -Plugin Name -Copyright (C) +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 diff --git a/src/plugin-support.h b/src/plugin-support.h index 8ffb57c..dc3bc58 100644 --- a/src/plugin-support.h +++ b/src/plugin-support.h @@ -1,6 +1,6 @@ /* -Plugin Name -Copyright (C) +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