この記事は TouchDesigner Advent Calendar 2025 の5日目の記事です。
はじめに
くりーぱーと申します。
謎の団体 でいろいろしていた人でした。↓たまに映像オペとかやってたりします。
技術記事を書くのは初めてなので、温かく見ていただけると嬉しいです。
今回は、TouchDesigner 向けに独自の TOP を作り、 Open Media Transport (OMT) を使って映像を配信できるようにしてみました。
1. Open Media Transport (OMT) とは
Open Media Transport (OMT) is an open-source network protocol for high performance, low latency video over a local area network (LAN). It has been designed to support multiple high-definition A/V feeds on a standard gigabit network without any specialised hardware.
引用元:OpenMediaTransport README (MIT License)
https://github.com/openmediatransport
簡単に言うと、LAN 上で高品質な映像を低遅延でやり取りするためのプロトコルです。TouchDesigner ではネットワーク映像伝送に NDI がよく使われていますが、Open Media Transport (OMT) も同じくローカルネットワーク内で映像を扱うための選択肢のひとつになります。
Open Media Transport (OMT) について詳しく知りたい人向けの解説記事
2. 独自のオペレーターの作り方
TouchDesigner では、C++ を使って独自の Operator(Custom OP)を作ることができます。標準の TOP / CHOP / SOP / POP / DAT ではできない処理を独自に実装できるため、外部デバイスとの連携や独自のプロトコル対応など、用途に合わせた拡張が可能です。
Sample
C++ Custom OP のサンプルは、GitHub の公式リポジトリに基本的な例が用意されているほか、TouchDesigner のインストールフォルダにある Samples/CPlusPlus フォルダにも同梱されています。
3. Open Media Transport (OMT) の Sample
Open Media Transport (OMT) にも C++ の Sample が GitHub 上にあるので参考にしていきます。
Sample の omtsendtest.cpp を見てみる
コードを見てみると映像、音声、メタデータを送信可能なようです。
まず初めに指定したストリーム名と品質設定を使って OMT の送信セッション(Sender)を作成するようです。
omt_send_t * snd = omt_send_create(name.c_str(), OMTQuality_Default);
次に送信するフレーム構造体 OMTMediaFrame を定義します。
video_frame.Typeは 映像、音声、メタデータが選択でき、今回は映像フレームを送信するので OMTFrameType_Video が選択されています。
OMTMediaFrame video_frame = {};
video_frame.Type = OMTFrameType_Video;
video_frame.Width = 1920;
video_frame.Height = 1080;
Sample では UYVY が選択されていますが、Open Media Transport (OMT) では以下のコーデックをサポートしています。
video_frame.Codec = OMTCodec_UYVY;
/**
* Supported Codecs:
*
* VMX1 = Fast video codec
* UYVY = 16bpp YUV format
* YUY2 = 16bpp YUV format YUYV pixel order
* UYVA = 16pp YUV format immediately followed by an alpha plane
* NV12 = Planar 4:2:0 YUV format. Y plane followed by interleaved half height U/V plane.
* YV12 = Planar 4:2:0 YUV format. Y plane followed by half height U and V planes.
* BGRA = 32bpp RGBA format (Same as ARGB32 on Win32)
* P216 = Planar 4:2:2 YUV format. 16bit Y plane followed by interlaved 16bit UV plane.
* PA16 = Same as P216 folowed by an additional 16bit alpha plane.
* FPA1 = Floating-point Planar Audio 32bit
*
*/
ここでは映像フレームを送るために必要な最低限の情報を準備しています。映像データの入れ物を用意して、フレームサイズや色空間、フレームレートといった情報を定義しています。
video_frame.Data = malloc(video_frame.DataLength);
video_frame.DataLength = video_frame.Stride * video_frame.Height;
video_frame.Stride = video_frame.Width * 2;
video_frame.Timestamp = -1;
video_frame.ColorSpace = OMTColorSpace_BT709;
video_frame.FrameRateN = 60000;
video_frame.FrameRateD = 1000;
最後に、実際に映像フレームを送信する処理を呼び出します。
omt_send(snd, &video_frame);
今回は映像のみしか触れていませんが、音声に関しても Smaple に記載されているので参考にしてみてください。
4. Open Media Transport (OMT) の TOP の作成
Plugin Entry Points
info->customOPInfo.minInputs; の値を 1 にしておくと入力が存在しない場合 Error が出るようになります。
extern "C"
{
DLLEXPORT
void
FillTOPPluginInfo(TD::TOP_PluginInfo* info)
{
if (!info->setAPIVersion(TD::TOPCPlusPlusAPIVersion))
return;
info->executeMode = TD::TOP_ExecuteMode::CPUMem;
info->customOPInfo.opType->setString("Omtouttop");
info->customOPInfo.opLabel->setString("OMT Out");
info->customOPInfo.opIcon->setString("OMO");
info->customOPInfo.authorName->setString("example");
info->customOPInfo.authorEmail->setString("example@email.com");
info->customOPInfo.minInputs = 1;
info->customOPInfo.maxInputs = 1;
}
DLLEXPORT
TD::TOP_CPlusPlusBase*
CreateTOPInstance(const TD::OP_NodeInfo* info, TD::TOP_Context* context)
{
return new OMTOutTOP(info, context);
}
DLLEXPORT
void
DestroyTOPInstance(TD::TOP_CPlusPlusBase* instance, TD::TOP_Context* context)
{
delete (OMTOutTOP*)instance;
}
};
Parameter Window の作成
.page で独自の名前でページで作成できるようです。作成したコードと実際の Parameter Window の画像を載せておきます。
void OMTOutTOP::setupParameters(TD::OP_ParameterManager* manager, void* reserved1)
{
// Enable Streaming(ON/OFF toggle)
{
TD::OP_NumericParameter np;
np.name = "Active";
np.label = "Active";
np.page = "OMT Out";
np.defaultValues[0] = false;
TD::OP_ParAppendResult res = manager->appendToggle(np);
assert(res == TD::OP_ParAppendResult::Success);
}
// Set Stream Name
{
TD::OP_StringParameter sp;
sp.name = "Streamname";
sp.label = "Stream Name";
sp.page = "OMT Out";
sp.defaultValue = "TouchDesigner";
TD::OP_ParAppendResult res = manager->appendString(sp);
assert(res == TD::OP_ParAppendResult::Success);
}
// Set FPS
{
TD::OP_NumericParameter np;
np.name = "Fps";
np.label = "FPS";
np.page = "OMT Out";
np.defaultValues[0] = 60;
np.minSliders[0] = 1;
np.maxSliders[0] = 120;
np.minValues[0] = 1;
np.maxValues[0] = 120;
np.clampMins[0] = true;
np.clampMaxes[0] = true;
TD::OP_ParAppendResult res = manager->appendInt(np);
assert(res == TD::OP_ParAppendResult::Success);
}
// Set Quality
{
TD::OP_StringParameter sp;
sp.name = "Quality";
sp.label = "Quality";
sp.page = "OMT Out";
sp.defaultValue = "Medium";
std::array<const char*, 3> Names =
{
"Low",
"Medium",
"High"
};
std::array<const char*, 3> Labels =
{
"Low",
"Medium",
"High"
};
TD::OP_ParAppendResult res = manager->appendMenu(sp, int(Names.size()), Names.data(), Labels.data());
assert(res == TD::OP_ParAppendResult::Success);
}
}
Error の出し方
myError に "Unable to create OMT sender for specified name." のように文字列を入れると Error を出すことが可能です。今回は Open Media Transport (OMT) で StremaName の競合を自動で防止する仕組みがなかったので TouchDesigner 側で実装しました。
void OMTOutTOP::getErrorString(TD::OP_String* error, void*)
{
error->setString(myError.c_str());
myError.clear();
}
texture の取り出し
独自のオペレーターに接続された TOP からフレームのGPUテクスチャを取得し、CPU メモリ側へ取り出しています。
if (inputs->getNumInputs() == 0)
{
return;
}
const TD::OP_TOPInput* top_input = inputs->getInputTOP(0);
if (!top_input)
{
return;
}
TD::OP_TOPInputDownloadOptions download_options;
download_options.pixelFormat = TD::OP_PixelFormat::BGRA8Fixed;
download_options.verticalFlip = true;
OP_SmartRef<OP_TOPDownloadResult> download_result = top_input->downloadTexture(download_options, nullptr);
Node Viewer の更新
Node Viewer の更新を行います。こちらの方法では前フレームのダウンロード結果を使う方式のため1フレーム遅れます。
if (!prev_down_result_)
{
return;
}
TD::TOP_UploadInfo upload_info;
upload_info.textureDesc = prev_down_result_->textureDesc;
upload_info.firstPixel = TD::TOP_FirstPixel::TopLeft;
upload_info.colorBufferIndex = 0;
TD::OP_SmartRef<TD::TOP_Buffer> buf = context_->createOutputBuffer(prev_down_result_->size, TD::TOP_BufferFlags::None, nullptr);
if (!buf)
{
return;
}
std::memcpy(buf->data, prev_down_result_->getData(), prev_down_result_->size);
output->uploadBuffer(&buf, upload_info, nullptr);
取り出したデータを OMTMediaFrame に受け渡しています。
const int width = download_result->textureDesc.width;
const int height = download_result->textureDesc.height;
OMTMediaFrame video_frame = {};
video_frame.Type = OMTFrameType_Video;
video_frame.Codec = OMTCodec_BGRA;
video_frame.Width = width;
video_frame.Height = height;
video_frame.Timestamp = -1;
video_frame.ColorSpace = OMTColorSpace_BT709;
video_frame.Flags = OMTVideoFlags_None;
video_frame.Stride = width * 4;
video_frame.DataLength = video_frame.Stride * video_frame.Height;
video_frame.Data = download_result->getData();
video_frame.FrameRateN = fps_ * 1000;
video_frame.FrameRateD = 1000;
video_frame.FrameMetadata = NULL;
video_frame.FrameMetadataLength = 0;
omt_send(sender_, &video_frame);
5. 独自のオペレーター の TouchDesigner への導入
dll の配置
生成された dll を C:/Users/<username>/Documents/Derivative/Plugins (Windows) のフォルダー内に配置します。また以下のリンクから OpenMediaTransport バイナリリリースをダウンロードし、libomt.dll を同じ階層に配置してください。また libvmx.dll を C:\Program Files\Derivative\TouchDesigner\bin に配置してください。
TouchDesigner の起動
初回起動時は読み込みの許可を求めるダイアログが表示されるので OK を押します。
OP Create Dialog の Custom ページに作成した独自のオペレーターが追加されます。
6. 作成した独自のオペレーターの OMT Out TOP
OMTViewer.Desktop で挙動を見てみる
OMTViewer が配布されているのでダウンロードしてみます。
OMT Out TOP で出力を開始すると OMTViewer で出力が確認されました。
最後に
今回は TouchDesigner でOpen Media Transport (OMT) を使ってみる ということで独自オペレーターを作成してみました。ただ映像だけではなく音声も対応していないため、今後はさらなる拡張が必要のようです。
2025.12.13 追記
本記事で紹介したサンプルについて GitHub にて公開しました。
本記事の一部は以下のMIT Licensedプロジェクトから引用しています:
OpenMediaTransport
https://github.com/openmediatransport/openmediatransport
Copyright (c) 2025 Open Media Transport Contributors
MIT License






