This is an implementation of a Janus NDI plugin, developed by Meetecho. Its main purpose is receiving streams via WebRTC, and translating them to NDI senders locally. It's the open source version of a plugin that, at the time, was originally used by Broadcast Bridge to help with the recording of CommCon Virtual 2021 remote presentations.
The plugin supports:
- NDI sender with a test pattern and a static name
- Creating one-shot or reusable NDI senders for WebRTC users
- Placeholder images for reusable NDI senders (e.g., when no PeerConnection is feeding them)
- Closing images for one-shot NDI senders (e.g., when the PeerConnection goes away)
- Decode Opus and VP8/VP9/H.264/AV1 (depending on FFmpeg installation) to raw NDI
- Resizing video after decode (with or without keeping the aspect ratio)
- Stereo audio
- Tally events
At the time of writing, the plugin does NOT support:
- Watermarking (partially supported in a private development branch)
- NDI-HX
- Advanced NDI 5 and 6 features (this plugin was implemented when only NDI 4 was available)
To learn more about the plugin, you can refer to this blog post and this other blog post, which explain more in detail how it should be used within the context of Janus-based WebRTC conversations.
The main dependencies are Janus, of course, and the NDI SDK, which needs to be installed as follows:
- headers in /usr/include/NDI
- shared objects to /usr/lib (or /usr/lib64, if it's a 64-bit installation)
To install the plugin itself, you'll also need to satisfy the following dev dependencies:
Support for decoding Opus, VP8, VP9, H.264 and AV1 should be available in the FFmpeg installation, or attempting to decode those codecs will fail.
Set the JANUSP
env variable to configure where Janus is installed, and then issue make
to compile the plugin, e.g.:
JANUSP=/opt/janus make
The plugin will automatically detect whether it's building against Janus 1.x
or 0.x
. Notice that, even when building for 1.x
, this plugin doesn't support multistream: this means that, at the time of writing, each PeerConnection can only be associated with one NDI sender, and each NDI sender can only contain one audio and/or video feed.
Set the JANUSP
env variable to configure where Janus is installed, and then issue make install
to install the plugin, e.g.:
JANUSP=/opt/janus make install
This will also install a template configuration file for the plugin (currently limited to a couple of settings).
To test the plugin in a local setup, you can use the ndi.html
web page in the demo
folder. It will work as any other Janus demo, so please refer to the related instructions in the Janus documentation for info on how to deploy this. You can also use the start_test_pattern
request to test the plugin without the need to establish a PeerConnection: check the API
section below for more information.
When using the demo, opening the web page will prompt you for a display name. Once you do that, a sendonly
PeerConnection will be established with the plugin, and the plugin will create an NDI sender with that display name for you locally. This NDI feed should then become visible to NDI compatible applications (e.g., OBS if you have the NDI plugin installed). Tally events will be displayed in the web page when the related NDI feed is consumed.
Notice that this is just a local demo to showcase the plugin from a functional perspective. In regular scenarios, the Janus instance serving users will not be in the same network as the applications dealing with NDI feeds. Please refer to the blog posts mentioned at the beginning of this page for more information on the type of orchestration you'll need to perform.
The translate
request must be used to setup the PeerConnection and associate it with an NDI source: it expects a name
property to be used by the NDI sender; optional arguments are bitrate
(to send a bitrate cap via REMB) and width
/height
(to force scaling to a static resolution; if missing, the original resolution in the WebRTC stream is used). The following code comes from the sample demo page:
ndi.createOffer(
{
media: { audio: true, video: true },
success: function(jsep) {
Janus.debug("Got SDP!", jsep);
// Send a request to the plugin
var translate = {
request: "translate",
name: "my-test"
}
ndi.send({ message: translate, jsep: jsep });
},
error: function(error) {
Janus.error("WebRTC error:", error);
bootbox.alert("WebRTC error... " + error.message);
}
});
This will create a new NDI source named my-test
available with the provided audio/video streams. The hangup
request can be used to tear down the PeerConnection instead: for one-shot NDI senders, this will release the NDI sender as well. A configure
request can be used to try and tweak a WebRTC stream: bitrate
will send a bitrate cap via REMB, keyframe: true
will trigger a PLI. Notice that REMB will be ignored if the NDI plugin is receiving a WebRTC stream from another Janus instance, rather than a browser.
A test pattern can be sent via NDI by using a start_test_pattern
request, and stopped via stop_test_pattern
. The test pattern is a static image sent at 30fps via NDI, and so can be used to verify whether or not recipients can obtain NDI streams originated by the plugin. Only a single test pattern can be started at a time, since it has a hardcoded janus-ndi-test
name. Both start_test_pattern
and stop_test_pattern
are synchronous requests, and can be invoked via Admin API as well, which means they can be triggered by, e.g., curl one-liners:
curl -d '{ "janus": "message_plugin", "transaction": "123", "admin_secret": "janusoverlord", "plugin": "janus.plugin.ndi", "request": { "request": "start_test_pattern" } }' http://localhost:7088/admin
curl -d '{ "janus": "message_plugin", "transaction": "123", "admin_secret": "janusoverlord", "plugin": "janus.plugin.ndi", "request": { "request": "stop_test_pattern" } }' http://localhost:7088/admin
For a more comprehensive documentation, please refer to docs/API.md.