Skip to content

Commit

Permalink
Experimental NDI output.
Browse files Browse the repository at this point in the history
  • Loading branch information
toots committed Oct 20, 2024
1 parent 3d2cb3b commit 4631cd7
Show file tree
Hide file tree
Showing 16 changed files with 872 additions and 3 deletions.
2 changes: 2 additions & 0 deletions src/config/ndi_option.disabled.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
let detected = "no (requires ctypes-foreign)"
let enabled = false
1 change: 1 addition & 0 deletions src/config/ndi_option.enabled.ml
17 changes: 17 additions & 0 deletions src/core/dune
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@
lang_fdkaac
lang_flac
lang_mp3
lang_ndi
lang_ogg
lang_opus
lang_source
Expand Down Expand Up @@ -178,6 +179,8 @@
mutex_utils
native_audio_converter
native_video_converter
ndi_format
ndi_encoder
noblank
noise
normalize
Expand Down Expand Up @@ -644,6 +647,14 @@
(optional)
(modules stereotool_op))

(library
(name liquidsoap_ndi)
(libraries ndi liquidsoap_core)
(library_flags -linkall)
(wrapped false)
(optional)
(modules ndi_out))

(library
(name liquidsoap_theora)
(libraries theora theora.decoder liquidsoap_core liquidsoap_ogg)
Expand Down Expand Up @@ -732,6 +743,7 @@
lo_option
mad_option
memtrace_option
ndi_option
ogg_option
opus_option
osc_option
Expand Down Expand Up @@ -897,6 +909,11 @@
from
(liquidsoap_portaudio -> portaudio_option.enabled.ml)
(-> portaudio_option.disabled.ml))
(select
ndi_option.ml
from
(liquidsoap_ndi -> ndi_option.enabled.ml)
(-> ndi_option.disabled.ml))
(select
posix_time_option.ml
from
Expand Down
8 changes: 8 additions & 0 deletions src/core/encoder/encoder.ml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
type format =
| WAV of Wav_format.t
| AVI of Avi_format.t
| NDI of Ndi_format.t
| Ogg of Ogg_format.t
| MP3 of Mp3_format.t
| Shine of Shine_format.t
Expand Down Expand Up @@ -61,6 +62,12 @@ let type_of_format f =
| AVI a -> audio_video_type a.Avi_format.channels
| MP3 m -> audio_type (if m.Mp3_format.stereo then 2 else 1)
| Shine m -> audio_type m.Shine_format.channels
| NDI { audio = false; video = false } -> assert false
| NDI { audio = true; video = false } ->
audio_type (Lazy.force Frame.audio_channels)
| NDI { audio = true; video = true } ->
audio_video_type (Lazy.force Frame.audio_channels)
| NDI { audio = false; video = true } -> video_type ()
| Flac m -> audio_type m.Flac_format.channels
| Ffmpeg m ->
List.fold_left
Expand Down Expand Up @@ -138,6 +145,7 @@ let string_of_format = function
| AVI w -> Avi_format.to_string w
| Ogg w -> Ogg_format.to_string w
| MP3 w -> Mp3_format.to_string w
| NDI w -> Ndi_format.to_string w
| Shine w -> Shine_format.to_string w
| Flac w -> Flac_format.to_string w
| Ffmpeg w -> Ffmpeg_format.to_string w
Expand Down
1 change: 1 addition & 0 deletions src/core/encoder/encoder.mli
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
type format =
| WAV of Wav_format.t
| AVI of Avi_format.t
| NDI of Ndi_format.t
| Ogg of Ogg_format.t
| MP3 of Mp3_format.t
| Shine of Shine_format.t
Expand Down
43 changes: 43 additions & 0 deletions src/core/encoder/encoders/ndi_encoder.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
(*****************************************************************************
Liquidsoap, a programmable stream generator.
Copyright 2003-2024 Savonet team
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
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details, fully stated in the COPYING
file at the root of the liquidsoap distribution.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*****************************************************************************)

(** NDI encoder. This encoder does nothing and can only by used with `output.ndi` *)

let encoder ~pos _ =
let error () =
Runtime_error.raise
~pos:(match pos with Some p -> [p] | None -> [])
~message:"The NDI encoder can only be used with `output.ndi`!" "invalid"
in
{
Encoder.insert_metadata = (fun _ -> error ());
header = error;
hls = Encoder.dummy_hls (fun _ -> error ());
encode = (fun _ -> error ());
stop = error;
}

let () =
Plug.register Encoder.plug "ndi"
~doc:"NDI encoder. Only used with `output.ndi`." (function
| Encoder.NDI m -> Some (fun ?hls:_ ~pos _ _ -> encoder ~pos m)
| _ -> None)
29 changes: 29 additions & 0 deletions src/core/encoder/formats/ndi_format.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
(*****************************************************************************
Liquidsoap, a programmable stream generator.
Copyright 2003-2024 Savonet team
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
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details, fully stated in the COPYING
file at the root of the liquidsoap distribution.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*****************************************************************************)

type t = { audio : bool; video : bool }

let to_string { audio; video } =
Printf.sprintf "%%ndi(%s)"
(String.concat ","
((if audio then "%audio" else "%audio.none")
:: (if video then ["%video"] else ["%video.none"])))
62 changes: 62 additions & 0 deletions src/core/encoder/lang/lang_ndi.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
(*****************************************************************************
Liquidsoap, a programmable stream generator.
Copyright 2003-2024 Savonet team
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
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details, fully stated in the COPYING
file at the root of the liquidsoap distribution.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*****************************************************************************)

open Ndi_format

let type_of_encoder p =
match
List.fold_left
(fun (audio, video) -> function
| `Encoder ("audio", []) -> (true, video)
| `Encoder ("audio.none", []) -> (false, video)
| `Encoder ("video", []) -> (audio, true)
| `Encoder ("video.none", []) -> (audio, false)
| _ -> (audio, video))
(true, true) p
with
| true, true -> Encoder.audio_video_type ~pcm_kind:Content.Audio.kind 2
| false, true -> Encoder.video_type ()
| true, false -> Encoder.audio_type ~pcm_kind:Content.Audio.kind 2
| _ -> Lang_encoder.raise_error ~pos:None "Invalid %%ndi encoder parameter!"

let make params =
let defaults = { audio = true; video = true } in
let ndi =
List.fold_left
(fun ndi -> function
| `Encoder ("audio", []) -> { ndi with audio = true }
| `Encoder ("audio.none", []) -> { ndi with audio = false }
| `Encoder ("video", []) -> { ndi with video = true }
| `Encoder ("video.none", []) -> { ndi with video = false }
| `Labelled (_, v) ->
Lang_encoder.raise_error ~pos:(Value.pos v) "Invalid parameter!"
| _ ->
Lang_encoder.raise_error ~pos:None
"Invalid %%ndi encoder parameter!")
defaults params
in
if (not ndi.audio) && not ndi.video then
Lang_encoder.raise_error ~pos:None
"%%ndi encoder needs at least one audio or video field!";
Encoder.NDI ndi

let () = Lang_encoder.register "ndi" type_of_encoder make
2 changes: 2 additions & 0 deletions src/core/outputs/icecast2.ml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ module Icecast = struct
samplerate = Some (Lazy.force m.Fdkaac_format.samplerate);
channels = Some m.Fdkaac_format.channels;
}
| Encoder.NDI _ ->
{ quality = None; bitrate = None; samplerate = None; channels = None }
| Encoder.External m ->
{
quality = None;
Expand Down
Loading

0 comments on commit 4631cd7

Please sign in to comment.