1
0
Fork 0

Fix audio setup.

- Use Icecast for transmitting NDI audio to Liquidsoap.
- Update liquidsoap to 2.0.4-preview (mem optims)
- Add new API endpoints for starting/stopping outputs.
liquidsoap-2.2
Icedream 2022-03-08 08:56:59 +01:00
parent 29ffc5f4c9
commit b6473d1c1d
Signed by: icedream
GPG Key ID: 468BBEEBB9EC6AEA
11 changed files with 186 additions and 62 deletions

View File

@ -55,3 +55,15 @@ services:
limits:
cpus: "2"
memory: 128M
icecast:
image: icedream/icecast
build: images/icecast-2.4.4
restart: always
volumes:
- "/share/VM_Disks/Docker/IcedreamLive/config/icecast/icecast.xml:/icecast.xml:ro"
network_mode: host
deploy:
resources:
limits:
cpus: "2"
memory: 128M

View File

@ -0,0 +1,4 @@
Dockerfile
.docker*
.git

View File

@ -0,0 +1,70 @@
FROM alpine:3.15 AS share
RUN apk add --no-cache git
WORKDIR /icecast/share/
#COPY share/ .
RUN git clone --depth=1 --recursive https://github.com/logue/icecast2-bootstrap-theme . && rm -rf .git
RUN chown 9999:0 .
RUN chmod -R a-rwx,a+rX .
###
FROM alpine:3.15 AS icecast-download
RUN apk add --no-cache curl ca-certificates
WORKDIR /usr/src/
ARG ICECAST_VERSION=2.4.4
RUN curl -L http://downloads.xiph.org/releases/icecast/icecast-${ICECAST_VERSION}.tar.gz | tar xz -v
###
FROM alpine:3.15 AS icecast
RUN apk add --no-cache \
build-base file openssl-dev libxslt-dev \
libvorbis-dev opus-dev libogg-dev speex-dev \
libtheora-dev curl-dev
ARG ICECAST_VERSION=2.4.4
WORKDIR /usr/src/icecast-${ICECAST_VERSION}
COPY --from=icecast-download /usr/src/icecast-${ICECAST_VERSION}/ .
RUN ./configure
RUN make
RUN make install
###
FROM alpine:3.15
# add runtime deps
RUN \
apk add --no-cache file libssl1.1 libxslt libvorbis \
opus libogg speex libtheora \
libtheora curl && \
rm -rf /tmp/* /var/cache/apk/*
# add icecast user
RUN \
addgroup -g 950 icecast &&\
adduser -S -D -H -u 9999 -G icecast -s /bin/false icecast
# add mime.types file
RUN apk add --no-cache mailcap && cp /etc/mime.types /etc/mime.types.keep && apk del --no-cache mailcap && mv /etc/mime.types.keep /etc/mime.types
# add dumb-init
ARG DUMB_INIT_VERSION=1.2.5
ADD https://github.com/Yelp/dumb-init/releases/download/v${DUMB_INIT_VERSION}/dumb-init_${DUMB_INIT_VERSION}_x86_64 /usr/local/bin/dumb-init
RUN chmod +x /usr/local/bin/dumb-init
# install icecast bins
COPY --from=icecast /usr/local/ /usr/local/
# install share files
COPY --from=share /icecast/share/ /icecast/share/
#USER 9999
USER root
#VOLUME [ "/data" ]
ENTRYPOINT [ "dumb-init" ]
CMD [ "icecast", "-c", "/icecast.xml" ]
EXPOSE 8000

View File

@ -1,5 +1,5 @@
ARG IMAGE=savonet/liquidsoap:v1.4.4
# ARG IMAGE=savonet/liquidsoap:master
ARG IMAGE=savonet/liquidsoap-ci-build:v2.0.4-preview_alpine_amd64
# FROM $IMAGE

View File

@ -7,11 +7,9 @@
def setup_harbor_metadata_api(~metadata_api_port=21338, s) =
# [insert_metadata_func, source_with_new_metadata]
result = insert_metadata(s)
insert_metadata_func = fst(result)
s = snd(result)
# Only Liquidsoap 2.x+
# s = insert_metadata(s)
s = insert_metadata(s)
# Handler for receiving metadata`
def on_http_metadata(~protocol, ~data, ~headers, uri) =
@ -20,7 +18,7 @@ def setup_harbor_metadata_api(~metadata_api_port=21338, s) =
], data)
m = list.assoc(default=[], "data", data)
insert_metadata_func(m)
s.insert_metadata(m)
http_response(protocol=protocol, code=200, headers=[
("allow","POST"),
("access-control-allow-origin","*"),
@ -29,8 +27,6 @@ def setup_harbor_metadata_api(~metadata_api_port=21338, s) =
("access-control-allow-headers","Origin,X-Requested-With,Content-Type,Accept,Authorization,access-control-allow-headers,access-control-allow-origin"),
("content-type","application/json"),
], data=json_of(data))
# Only Liquidsoap 2.x+
#s.insert_metadata(m)
# http.response(protocol=protocol, code=200, headers=[
# ("allow","POST"),
# ("access-control-allow-origin","*"),
@ -66,4 +62,4 @@ def setup_harbor_metadata_api(~metadata_api_port=21338, s) =
harbor.http.register(port=metadata_api_port, method="OPTIONS", "/", on_http_metadata_cors)
s
end
end

View File

@ -1,9 +1,6 @@
# Silent fallback stream that is still loud enough that it forces Vorbis/OPUS codecs to continue broadcasting data.
# Some Icecast-compatible software on the server-side tends to freak out over us not sending data for extended amount of times, for example during technical difficulties.
def mksafe_soft(s) =
blank_s = blank()
blank_v = drop_audio(blank_s)
silent_a = amplify(0.000075, noise())
silent_s = mux_video(video=blank_v, silent_a)
mksafe(fallback(track_sensitive=false, [s, silent_s]))
fallback(track_sensitive=false, [s, silent_a])
end

View File

@ -10,43 +10,50 @@ set("server.telnet.port", 21337)
set("init.allow_root",true)
set("frame.video.width", 1920)
set("frame.video.height", 1080)
set("audio.converter.samplerate.libsamplerate.quality", "best")
set("audio.converter.samplerate.native.quality","linear")
set("sandbox", "disabled")
%include "settings.liq"
%include "metadata_api.liq"
%include "stream_api.liq"
%include "silent_fallback.liq"
s = input.srt(id="input_srt_main", max=3., port=9000)
output.dummy(fallible=true, s)
s = input.http(id="input_ice_main", max_buffer=4., "http://127.0.0.1:61120/main")
# Split audio off to be handled specially
a = drop_video(s)
a = mksafe_soft(a)
a = setup_harbor_metadata_api(a)
output.dummy(a)
output.harbor(
id="out_a_harbor",
%ogg(%flac),
# fallible=true,
port=8050,
# encoded lossless stream
a_flac = ffmpeg.encode.audio(
%ffmpeg(%audio(codec="flac")),
a)
internal_icecast=output.icecast(
fallible=true,
port=61120,
host="127.0.0.1",
name=stream_name())
setup_harbor_stream_api(internal_icecast(
id="out_a_int",
%ffmpeg(format="ogg", %audio.copy),
mount="/outa/flac",
a)
output.harbor(
id="out_a_harbor",
%mp3(bitrate=320),
# fallible=true,
port=8050,
mount="/outa/mp3",
a)
output.icecast(
a_flac))
# REKT.fm
setup_harbor_stream_api(output.icecast(
id="out_a_rekt",
%mp3(bitrate=320),
# fallible=true,
%ffmpeg(format="ogg", %audio.copy),
fallible=true,
mount="rekt",
port=60000,
#host="stream.rekt.network",
host="stream.rekt.fm",
user="icedream",
name=stream_name,
name=stream_name(),
password="***REMOVED***",
start=false,
a)
a_flac))

View File

@ -0,0 +1,39 @@
stream_api_port=21336
interactive.harbor(port=stream_api_port, uri="/interactive") # expose through stream API port
def setup_harbor_stream_api(s) =
def on_start(~protocol, ~data, ~headers, uri) =
s.start()
http.response(protocol=protocol, code=200, headers=[
("content-type","application/json"),
], data=json.stringify([]))
end
def on_stop(~protocol, ~data, ~headers, uri) =
s.stop()
http.response(protocol=protocol, code=200, headers=[
("content-type","application/json"),
], data=json.stringify([]))
end
def on_info(~protocol, ~data, ~headers, uri) =
data = [
("id", s.id()),
("last_metadata", json.stringify(s.last_metadata())),
("is_up", json.stringify(s.is_up())),
("is_started", json.stringify(s.is_started())),
("is_ready", json.stringify(s.is_ready())),
("is_active", json.stringify(s.is_active())),
]
http.response(protocol=protocol, code=200, headers=[
("content-type","application/json"),
], data=json.stringify(data))
end
harbor.http.register(port=stream_api_port, method="POST", "/streams/#{s.id()}/start", on_start)
harbor.http.register(port=stream_api_port, method="POST", "/streams/#{s.id()}/stop", on_stop)
harbor.http.register(port=stream_api_port, method="GET", "/streams/#{s.id()}", on_info)
s
end

View File

@ -20,7 +20,7 @@ COPY --from=0 /target/ /
WORKDIR /library
VOLUME ["/library"]
RUN addgroup -S -g 950 app
RUN adduser -S -k /dev/empty -g "App user" -h /library -u 950 -G app app
USER 950
#RUN addgroup -S -g 950 app
#RUN adduser -S -k /dev/empty -g "App user" -h /library -u 950 -G app app
#USER 950
CMD ["metacollectord"]

View File

@ -20,6 +20,7 @@ USER app
RUN git clone --recursive https://aur.archlinux.org/yay.git yay/
RUN cd yay && makepkg --noconfirm -si && cd .. && rm -r yay
RUN yay --noconfirm -S pod2man && sudo rm -r ~/.cache /var/cache/pacman/*
RUN yay --noconfirm -S ndi-advanced-sdk && sudo rm -r ~/.cache /var/cache/pacman/*
RUN yay --noconfirm -S ffmpeg-ndi && sudo rm -r ~/.cache /var/cache/pacman/*
COPY --from=0 /target/ /

View File

@ -1,7 +1,7 @@
#!/bin/bash -e
target_url="${1:-srt://127.0.0.1:9000}"
ffmpeg_pid=
target_url="${1:-icecast://source:source@127.0.0.1:61120/main}"
ffmpeg_pids=()
call_ffmpeg() {
command ffmpeg -hide_banner "$@"
@ -9,23 +9,19 @@ call_ffmpeg() {
daemon_ffmpeg() {
call_ffmpeg "$@" &
ffmpeg_pid=$!
ffmpeg_pids+=($!)
}
shutdown_ffmpeg() {
if is_ffmpeg_running
then
if is_ffmpeg_running; then
kill "$ffmpeg_pid" || true
for t in $(seq 0 10)
do
if ! kill -0 "$ffmpeg_pid"
then
for t in $(seq 0 10); do
if ! kill -0 "$ffmpeg_pid"; then
break
fi
sleep 1
done
if kill -0 "$ffmpeg_pid"
then
if kill -0 "$ffmpeg_pid"; then
kill -9 "$ffmpeg_pid" || true
fi
fi
@ -43,15 +39,13 @@ trap on_exit EXIT
offline=0
while true
do
while true; do
found_audio_source=""
while read -r line
do
declare -a "found_source=($(sed -e 's/"/\\"/g' -e "s/'/\"/g" -e 's/[][`~!@#$%^&*():;<>.,?/\|{}=+-]/\\&/g' <<< "$line"))"
found_source[0]=$(sed -e 's/\\\([`~!@#$%^&*():;<>.,?/\|{}=+-]\)/\1/g' <<< "${found_source[0]}")
found_source[1]=$(sed -e 's/\\\([`~!@#$%^&*():;<>.,?/\|{}=+-]\)/\1/g' <<< "${found_source[1]}")
while read -r line; do
declare -a "found_source=($(sed -e 's/"/\\"/g' -e "s/'/\"/g" -e 's/[][`~!@#$%^&*():;<>.,?/\|{}=+-]/\\&/g' <<<"$line"))"
found_source[0]=$(sed -e 's/\\\([`~!@#$%^&*():;<>.,?/\|{}=+-]\)/\1/g' <<<"${found_source[0]}")
found_source[1]=$(sed -e 's/\\\([`~!@#$%^&*():;<>.,?/\|{}=+-]\)/\1/g' <<<"${found_source[1]}")
case "${found_source[0]}" in
*\(IDHPC\ Main\ Audio\))
found_audio_source="${found_source[0]}"
@ -59,23 +53,27 @@ do
esac
done < <(call_ffmpeg -loglevel info -extra_ips 192.168.188.21 -find_sources true -f libndi_newtek -i "dummy" 2>&1 | grep -Po "'(.+)'\s+'(.+)" | tee)
if [ -z "$found_audio_source" ]
then
offline=$(( offline + 1 ))
if [ -z "$found_audio_source" ]; then
offline=$((offline + 1))
else
offline=0
fi
if ! is_ffmpeg_running && [ -n "$found_audio_source" ]
then
if ! is_ffmpeg_running && [ -n "$found_audio_source" ]; then
echo "starting ffmpeg with audio source: $found_audio_source" >&2
call_ffmpeg -loglevel warning \
-analyzeduration 1 -f libndi_newtek -extra_ips 192.168.188.21 -i "$found_audio_source" \
-map a -c:a pcm_s16le -ar 48000 -ac 2 -f s16le - |
call_ffmpeg -loglevel warning \
-ar 48000 -channels 2 -f s16le -i - \
-map a -c:a flac -f ogg -content_type application/ogg "${target_url}" || true
# HACK - can't use the standard mpegts here, but liquidsoap will happily accept anything ffmpeg can parse (by default)… so let's just use nut here even though it feels super duper wrong
daemon_ffmpeg -loglevel warning -extra_ips 192.168.188.21 -f libndi_newtek -i "$found_audio_source" -c copy -f nut -write_index false "${target_url}"
elif is_ffmpeg_running && [ -z "$found_audio_source" ] && [ "$offline" -gt 0 ]
then
elif is_ffmpeg_running && [ -z "$found_audio_source" ] && [ "$offline" -gt 0 ]; then
echo "shutting down ffmpeg since no source has been found" >&2
shutdown_ffmpeg # it won't shut down by itself unfortunately
fi
sleep 1
done