savonet / liquidsoap

Liquidsoap is a statically typed scripting general-purpose language with dedicated operators and backend for all thing media, streaming, file generation, automation, HTTP backend and more.
http://liquidsoap.info
GNU General Public License v2.0
1.4k stars 128 forks source link

HLS stream will not change audio quality #3908

Closed K1oma closed 5 months ago

K1oma commented 5 months ago

Describe the bug

I've made an HLS stream that streams audio quality in 320kbps, 192kbps, 96kbps. When I play the stream in VLC or SMPlayer the stream starts at 96kbps for 3-4 seconds and then goes to 320kpbs, which is great. Then I throttle my network to test if it will go to the lower quality with one question in mind, what if someone that listens to my radio station and then their internet got worse? I want their stream to go down from 320kbps to 192kbps or 96kpbs. But in the reality, when I throttle my network, the stream stops instead of going down in quality. I tried numerous players that support ABR either on PC and iPhone, and they all give the same results.

To Reproduce

settings.log.file.set(false) enable_replaygain_metadata()

input = cue_cut(cue_in_metadata="liq_cue_in",playlist(mode="normal",reload=1,reload_mode="rounds","plejlista-processed.m3u8")) input = crossfade(fade_out=0.01, fade_in=0., default=(fun(a,b)->add(normalize=false,([b, a]))), conservative=true, input) aac_lofi = %ffmpeg(format="mpegts", %audio( codec="aac", channels=2, ar=44100, b="96k" )) aac_midfi = %ffmpeg(format="mpegts", %audio( codec="aac", channels=2, ar=44100, b="192k" )) aac_hifi = %ffmpeg(format="mpegts", %audio( codec="aac", channels=2, ar=44100, b="320k" )) streams = [("aac_lofi",aac_lofi), ("aac_midfi", aac_midfi), ("aac_hifi", aac_hifi)]

def segment_name(~position,~extname,stream_name) = timestamp = int_of_float(time()) duration = 2 "#{stream_name}_#{duration}_#{timestamp}_#{position}.#{extname}" end

input = mksafe(normalize(input, gain_max = 3.0, gain_min = 1., target = -13.0, threshold = -65.0)) output.harbor.hls(port=8088, path="stream/",segment_duration=6.,segments=10, streams, input)

Expected behavior

I expect my stream to go from 320kbps to 192kbps/96kbps without interruption if the client has bad internet or internet stoppage. Also, the stream needs to be without any pauses, silence or anything like that.

Version details

Install method

  1. Did you install via opam? Yes
  2. Using your distribution's packages? Yes
  3. From source? Yes
gAlleb commented 5 months ago

Hi! I think it's not LS issue at all. Such players as VLC should work great with HLS - some don't. iOS specifically has very interesting problem: it starts from lo-fi and never goes up ;) so I had to set quality of the streams explicitly. Also I've tested LS HLS streams on latest Android and VLC - they work normally.

To sum up: it all depends on the software you are using to listen to HLS streams.

And you should definitely upgrade your LS because of major HLS improvements that have been made in newer versions! Maybe some of your current problems would disappear. Because 2.1.4 is rather old.

K1oma commented 5 months ago

Thanks for the tips, gAlleb!

I have updated it on 2.2.5+dev Now I have a different problem with config. It seems I cant find a new config on thei internet, do you have any links or configs laying around for HLS?

The problem:

At liquid.conf, line 30 char 11 - line 36 char 22: aac_lofi = %ffmpeg(format="mpegts", %audio( ... b="96k" ))

Error 12: Unsupported encoder: %ffmpeg(format="mpegts", %audio(codec="aac", channels=2, ar=44100, b="96k")). You must be missing an optional dependency.

gAlleb commented 5 months ago

Don't forget to upgrade/install ffmpeg. It should have been upgraded automatically. opam install ffmpeg

HLS config is the same. You could also add {id3_version = 4} something like this - %ffmpeg(format="mpegts",%audio( codec="aac",channels=2, ar=44100,b="96k")).{id3_version = 4}

HLS now supports metadata so you can see it in your player (usually http protocol only).

Normal setup could be with ocaml compiler 4.14.2 or 4.14.1

Also cue_cut is not an option anymore. This operator has been removed. Annotations like "liq_cue_in" are now readable within playlist operator itself -> https://www.liquidsoap.info/doc-2.2.4/migrating.html

Also instead of normalizing and enable_replay_gain you can now use autocue! Just use enable_autocue_metadata and this would calculate cue_in, cue_out, fade_in, fade_out, etc.. all by itself and normalize loudness as well (just add input = amplify(1.,override="liq_amplify",input)). Or you can enable cue_file autocue, which is better in my opinion in a way that it works a bit faster and has more settings - https://github.com/Moonbase59/autocue

if using LS 2.2.5, the best bet is to switch to the "integrate-with-liquidsoap" branch https://github.com/Moonbase59/autocue/tree/integrate-with-liquidsoap, get cue_file from that, and use the autocue.cue_file.liq in your setup. .

Here are the settings to be applied after inserting autoecue.cue_file.liq in your config:

# settings.autocue.cue_file.path := "cue_file"
# settings.autocue.cue_file.fade_in := 0.1
# settings.autocue.cue_file.fade_out := 2.5
# settings.autocue.cue_file.timeout := 60.0
# settings.autocue.cue_file.target := -18.0
# settings.autocue.cue_file.silence := -42.0
# settings.autocue.cue_file.overlay := -8.0
# settings.autocue.cue_file.longtail := 15.0
# settings.autocue.cue_file.overlay_longtail := -15.0
settings.autocue.cue_file.noclip := true  # clipping prevention
# settings.autocue.cue_file.blankskip := false
# settings.autocue.cue_file.unify_loudness_correction := true
# settings.autocue.cue_file.write_tags := false
# settings.autocue.cue_file.force_analysis := false
settings.autocue.cue_file.nice := true  # Linux only!

# `enable_autocue_metadata()` will autocue ALL files Liquidsoap processes.
# You can disable it for selected sources using 'annotate:liq_cue_file=false'.

enable_autocue_metadata()

The commented-out lines just show the possible settings.

These two:

# settings.autocue.cue_file.fade_in := 0.1
# settings.autocue.cue_file.fade_out := 2.5

should correspond to any custom values in custom crossfade code, like so:

        add(normalize=false,
          [fade.in(initial_metadata=new.metadata, duration=.1, new.source),
           fade.out(initial_metadata=old.metadata, duration=2.5, old.source)]
        )

or so:

        cross.simple(
          old.source,
          new.source,
          fade_in=0.1,
          fade_out=2.5,
          initial_fade_in_metadata=new.metadata,
          initial_fade_out_metadata=old.metadata
        )

followed by:

radio = cross(duration=2.5, crossfade_function, radio)

Check out test_autocue.cue_file.liq for a complete example - https://github.com/Moonbase59/autocue/tree/integrate-with-liquidsoap

Made by @Moonbase59


So instead of

input = cue_cut(cue_in_metadata="liq_cue_in",playlist(mode="normal",reload=1,reload_mode="rounds","plejlista-processed.m3u8"))
input = crossfade(fade_out=0.01, fade_in=0., default=(fun(a,b)->add(normalize=false,([b, a]))), conservative=true, input)

# and

input = mksafe(normalize(input, gain_max = 3.0, gain_min = 1., target = -13.0, threshold = -65.0)) 

You can switch to:

settings.log.file.set(false)
# Remember to put `cue_file` somewhere into `/usr/local/bin` or other place and make it executable.
# include your autocue.cue_file.liq. Specify the exact path.
%include "/home/radio/liquidsoap/autocue-integrate-with-liquidsoap/autocue.cue_file.liq"
# settings.autocue.cue_file.path := "cue_file"
# settings.autocue.cue_file.fade_in := 0.1
# settings.autocue.cue_file.fade_out := 2.5
# settings.autocue.cue_file.timeout := 60.0
# settings.autocue.cue_file.target := -18.0
# settings.autocue.cue_file.silence := -42.0
# settings.autocue.cue_file.overlay := -8.0
# settings.autocue.cue_file.longtail := 15.0
# settings.autocue.cue_file.overlay_longtail := -15.0
 settings.autocue.cue_file.noclip := true  # clipping prevention
# settings.autocue.cue_file.blankskip := false
# settings.autocue.cue_file.unify_loudness_correction := false
# settings.autocue.cue_file.write_tags := false
# settings.autocue.cue_file.force_analysis := false
 settings.autocue.cue_file.nice := true  # Linux only!

# `enable_autocue_metadata()` will autocue ALL files Liquidsoap processes.
# You can disable it for selected sources using 'annotate:liq_cue_file=false'.

enable_autocue_metadata()

input = playlist(mode="normal",reload=1,reload_mode="rounds","plejlista-processed.m3u8")

input = amplify(1.,override="liq_amplify",input)

def crossfade_function(old, new) =

        cross.simple(
          old.source,
          new.source,
          fade_in=0.1,
          fade_out=2.5,
          initial_fade_in_metadata=new.metadata,
          initial_fade_out_metadata=old.metadata
        )

end
input = cross(duration=2.5, crossfade_function, input)
aac_lofi = %ffmpeg(format="mpegts", %audio( codec="aac", channels=2, ar=44100, b="96k" )).{id3_version = 4}
aac_midfi = %ffmpeg(format="mpegts", %audio( codec="aac", channels=2, ar=44100, b="192k" )).{id3_version = 4}
aac_hifi = %ffmpeg(format="mpegts", %audio( codec="aac", channels=2, ar=44100, b="320k" )).{id3_version = 4}
streams = [("aac_lofi",aac_lofi), ("aac_midfi", aac_midfi), ("aac_hifi", aac_hifi)]

def segment_name(~position,~extname,stream_name) =
  timestamp = int_of_float(time())
  duration = 2
  "#{stream_name}_#{duration}_#{timestamp}_#{position}.#{extname}"
end

input = mksafe(input) 
output.harbor.hls(port=8088, path="stream/",
segment_duration=2.,
segments=10, 
segments_overhead=10, 
segment_name=segment_name, 
streams, 
input)
K1oma commented 5 months ago

Thank you gAlleb, I really can't thank you enough. Thank you for your time and efforts!