OBSのソース映像の上に選択したカラーをオーバーレイのように乗算するフィルタを作ってみます。
プラグイン作りもやっと今回で実用的な感じになってきた…
プラグインの機能としてはフィルタプロパティでユーザーが選んだ色をソースの上に上書きする単純なもので、緑色固定の単純ケースは公式プロジェクトで公開されています。
test-filter.cコード:https://github.com/obsproject/obs-studio/blob/master/test/test-input/test-filter.c
.effectサンプル:https://github.com/obsproject/obs-studio/blob/master/test/test-input/data/test.effect
ビルドするには#1で行ったplugin-main.cをtest-filter.cの内容に書き換えるとビルド完成です。
effectファイルは言語設定の一つ上のディレクトリに保存します。(root/data/)
effectファイルはDirect3D 11 HLSL effect filesと殆ど同じもので、わかりやすく言うとシェーダーです。[Rendering Graphics]
言語設定ファイルは#3と同じ構造を想定しています。(root/data/locate/)
このプラグインテンプレートを修正して下図のようなプラグインを作成します。
obs_source_infoの実装
struct obs_source_info my_ef_info = {
.id = "my plugin sample",
.type = OBS_SOURCE_TYPE_FILTER,
.output_flags = OBS_SOURCE_VIDEO,
.get_name = get_my_plugin_name,
.create = my_filter_create,
.destroy = my_filter_destroy,
.update = my_filter_update,
.video_render = my_video_render,
.get_properties = get_my_filter_properties,
};
id,type,output_flags,get_name,get_propertiesの5つは前回のプロパティを表示する為の関数です。
加えて今回はcreate,destroy,update,video_renderの4つを空のダミー実装ではなく、まじめに実装していきます。
次にプラグイン内のどの関数からもアクセス可能なクラス変数的なものを用意します。
test-filter.cではtest_filter構造体が冒頭に定義されていますが、他のプラグインを見ているとcontextという変数名が多く使われているので今回は下記のような構造体を用意しました。
struct my_context {
obs_source_t *source; //適用するソース
gs_effect_t *effect; //適用するシェーダー(effectファイル)
struct vec4 color_rgba; //乗算したい色
};
カラー選択のUI実装法[get_properties]
プロパティのUIは公式のapiドキュメントを参照してobs_properties_add_color_alphaを使う事にしました。
(RGBAそれぞれの値をスライダで設定したい場合はobs_properties_add_float_sliderを用意するのもいいかもしれません)
static obs_properties_t *get_my_filter_properties(void *data)
{
obs_properties_t *props = obs_properties_create();
obs_properties_add_color_alpha(props, "color1", obs_module_text("MyLabel"));
UNUSED_PARAMETER(data);
return props;
}
この関数一つで下記のカラーパレットが使えるようになります。便利。
カラー選択時に読み込まれるプラグイン関数[update]
obs_source_infoは沢山の関数が用意されていますが、今回はupdateを利用しました。
参考までに下記がよく使われる更新用の関数です。(公式ドキュメント)
- create : プラグイン読み込み時に実行。init関数的な扱いです。
- destroy : プラグイン破棄時に実行。終了処理を主に担当。
- update : プラグインの設定値変更時。
- video_render : ビデオ描画の為に毎フレーム呼ばれる。
- video_tick : 指定時間毎に呼ばれる。
update関数で行うことはプロパティで設定された値をobs_data_get_int関数でunit32として取得して、vec4(RGBA)に変換しています。
static void my_filter_update(void *data, obs_data_t *settings)
{
struct my_context *context = data;
uint32_to_rgba(&context->color_rgba,
(uint32_t)obs_data_get_int(settings, "color1"));
}
カラー選択の値とRGBAの変換
変換関数のuint32_to_rgbaは下記の通りです。
void uint32_to_rgba(struct vec4 *color, uint32_t rgba) {
color->x = (rgba & 0xff) / 255.0f; // R
color->y = ((rgba & 0xff00) >> 8) / 255.0f; // G
color->z = ((rgba & 0xff0000) >> 16) / 255.0f; // B
color->w = ((rgba & 0xff000000) >> 24) / 255.0f; // A
}
この変換関数はobs-text-pangoというプロジェクトを参考にさせて頂きました。
シェーダー(.effect)の変数設定
video_render関数内で実行しています。
gs_effect_get_param_by_name関数でeffectファイル内のuniform float4 color変数を指定し、update関数で取得したrgbaデータを設定します。
static void my_video_render(void *data, gs_effect_t *effect)
{
struct my_context *context = data;
if (!obs_source_process_filter_begin(context->source, GS_RGBA,
OBS_ALLOW_DIRECT_RENDERING))
return;
gs_eparam_t *color_val =
gs_effect_get_param_by_name(context->effect, "color");
gs_effect_set_vec4(gs_effect_get_param_by_name(context->effect, "color"),
&context->color_rgba);
obs_source_process_filter_end(context->source, context->effect, 0, 0);
UNUSED_PARAMETER(effect);
}
初期化・終了処理
殆どサンプル通りのことをしています。
static void my_filter_destroy(void *data)
{
struct my_context *context = data;
if (context) {
obs_enter_graphics();
gs_effect_destroy(context->effect);
bfree(context);
obs_leave_graphics();
}
obs_log(LOG_INFO, "my plugin desutroy");
}
static void *my_filter_create(obs_data_t *settings, obs_source_t *source)
{
struct my_context *context = bzalloc(sizeof(struct my_context));
char *effect_file;
obs_enter_graphics();
effect_file = obs_module_file("my-sample.effect");
context->source = source;
context->effect = gs_effect_create_from_file(effect_file, NULL);
context->color_rgba = (struct vec4){0.5f, 1.0f, 0.5f, 1.0f};
bfree(effect_file);
if (!context->effect) {
my_filter_destroy(context);
context = NULL;
}
obs_leave_graphics();
UNUSED_PARAMETER(settings);
return context;
}
obs_enter_graphics/obs_leave_graphicsの使い方
上記のcreate関数、destroy関数ではvideo_render関数で使用しなかったobs_enter_graphics()/obs_leave_graphics()を使っています。
video_render関数とobs_display_add_draw_callback()
、 obs_add_main_render_callback()
外でシェーダー関連の関数や変数を扱う場合はobs_enter_graphics()/obs_leave_graphics()の関数で挟んで使用する必要があります。[https://docs.obsproject.com/graphics#the-graphics-context]
以上がAPIドキュメントや公式サンプル実装の補足です。
ドキュメントだけ読んで実装出来る人いるんだろうか…