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 130 forks source link

Experiencing skipping when using output.file from an m3u8 of HLS chunks #2948

Closed TheFrankman closed 1 year ago

TheFrankman commented 1 year ago

I am trying to concatenate hls fragments back into a single file, but the file that is produced has skips at the joins. Only slightly but they are audible.

My implementation is a big more complicated than this, as I have docker and docker-in-docker (dind) dynamically generating this stuff. But in essence I have one liquidsoap file that is continuously writing chunks to a centralised file storage, and a liquidsoap script that is responsible for concatenating a subsection of those chunks using an M3U8 file that i generate.

My hls liq looks something like this :

#!/usr/bin/liquidsoap

settings.init.allow_root.set(true)

PRIMARY_STREAM=getenv("PRIMARY_STREAM")
SECONDARY_STREAM=getenv("SECONDARY_STREAM")
PROVIDER=getenv("PROVIDER")
PRIMARY_STREAM_NAME=getenv("PRIMARY_STREAM_NAME")

log.stdout.set(true)

segment_duration=float_of_string(getenv(default="60.", "SEGMENT_DURATION"))

to_record = input.http(PRIMARY_STREAM)
silence = blank(duration=5.)

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

if SECONDARY_STREAM != '' then
 secondary = input.http(SECONDARY_STREAM)
 to_record = fallback(track_sensitive=false, [to_record, secondary, silence])
else
 to_record = fallback(track_sensitive=false, [to_record, silence])
end

output.file.hls(fallible=true,
"/var/www/chunks/#{PROVIDER}/#{PRIMARY_STREAM_NAME}",
[("mp3-low", %mp3(bitrate=192))],
to_record,
segment_duration=60.,
segment_name=segment_name,
segments=4500,
persist_at="/var/www/chunks/#{PROVIDER}/#{PRIMARY_STREAM_NAME}.config",
)

Here is an example M3U8 file that i generate :

#EXT3U
#EXT-X-TARGETDURATION:60
#EXT-X-VERSION:3
#EXT-X-DISCONTINUITY-SEQUENCE:0
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-STREAM-INF:BANDWIDTH=192000,CODECS="mp4a.40.34"
#EXTINF:0.640,
/var/www/chunks/example/example.mp3/1678278038.mp3
#EXTINF:0.640,
/var/www/chunks/example/example.mp3/1678278098.mp3
#EXTINF:0.640,
/var/www/chunks/example/example.mp3/1678278158.mp3
#EXTINF:0.640,
/var/www/chunks/example/example.mp3/1678278218.mp3
#EXTINF:0.640,
/var/www/chunks/example/example.mp31678278278.mp3
#EXTINF:0.640,
/var/www/chunks/example/example.mp3/1678278338.mp3
#EXTINF:0.640,
/var/www/chunks/example/example.mp3/1678278398.mp3
#EXT-X-ENDLIST

and here is my liquidsoap script responsible for concat

#!/usr/bin/liquidsoap
settings.init.allow_root.set(true)
M3U_PATH=getenv("M3U_PATH")
OUTPUT_PATH=getenv("OUTPUT_PATH")

log.important("M3U PATH IS  #{M3U_PATH}")
log.important("OUTPUT PATH IS  #{OUTPUT_PATH}")

playlist_to_concat = playlist(M3U_PATH, loop=false, mode='normal')

clock.assign_new(sync="none",[playlist_to_concat])

output.file(
    %mp3(bitrate=192),
    OUTPUT_PATH,
    playlist_to_concat,
    fallible=true,
    on_stop=shutdown,
    duration=playlist_to_concat.duration()
)

My approach for the concat is that i load the playlist from the m3u8, i play it once, i dsync the clock and output to a file.

Can anyone help me identify where the issue may be here, i feel as though this should work but the skips are there.

Here is concatenate logs :

2023/03/08 16:06:14 >>> LOG START
2023/03/08 16:06:13 [ffmpeg.filter.bitstream:3] No valid mode found for filter pgs_frame_merge!
2023/03/08 16:06:13 [main:3] Liquidsoap 2.1.3
2023/03/08 16:06:13 [main:3] Using: bytes=[distributed with OCaml 4.02 or above] posix-time2=2.0.0 pcre=7.5.0 sedlex=3.0 menhirLib=20220210 curl=0.9.2 uri=4.2.0 memtrace=v0.2.2 mem_usage=0.0.4 dtools=0.4.4 duppy=0.9.3 cry=0.6.8 mm=0.8.2 xmlplaylist=0.1.5 lastfm=0.3.3 ogg=0.7.3 ogg.decoder=0.7.3 vorbis=0.8.1 vorbis.decoder=0.8.1 opus=0.2.3 opus.decoder=0.2.3 speex=0.4.1 speex.decoder=0.4.1 mad=0.5.2 flac=0.3.1 flac.ogg=0.3.1 flac.decoder=0.3.1 dynlink=[distributed with Ocaml] lame=0.3.6 shine=0.2.3 frei0r=0.1.2 fdkaac=0.3.2 theora=0.4.0 theora.decoder=0.4.0 ffmpeg=1.1.6 bjack=0.1.6 alsa=0.3.0 ao=0.2.4 samplerate=0.1.6 taglib=0.3.10 ssl=0.5.12 magic=0.7.3 camomile=1.0.2 inotify=2.0-58-g1beb568 faad=0.5.2 soundtouch=0.1.9 portaudio=0.2.3 pulseaudio=0.1.5 ladspa=0.2.2 tsdl=v0.9.9 tsdl-ttf=0.3.2 tsdl-image=0.3.2 camlimages=4.2.6 camlimages.freetype=5.0.4 cohttp-lwt-unix=5.0.0 prometheus-app=1.2 srt.constants=0.2.2 srt.types=0.2.2 srt.stubs=0.2.2 srt.stubs.locked=0.2.2 srt=0.2.2 lo=0.2.0 gd=1.0a5 irc-client-unix=[unspecified]
2023/03/08 16:06:13 [clock:3] Using native (high-precision) implementation for latency control
2023/03/08 16:06:14 [lang:3] M3U PATH IS  /var/www/recordings/example/example.mp3/1678290755-1678291055.m3u8
2023/03/08 16:06:14 [lang:3] OUTPUT PATH IS  /var/www/recordings/example/example.mp3/1678290755-1678291055.mp3
2023/03/08 16:06:14 [frame:3] Using 44100Hz audio, 25Hz video, 44100Hz main.
2023/03/08 16:06:14 [frame:3] Video frame size set to: 1280x720
2023/03/08 16:06:14 [frame:3] Frame size must be a multiple of 1764 ticks = 1764 audio samples = 1 video samples.
2023/03/08 16:06:14 [frame:3] Targeting 'frame.duration': 0.04s = 1764 audio samples = 1764 ticks.
2023/03/08 16:06:14 [frame:3] Frames last 0.04s = 1764 audio samples = 1 video samples = 1764 ticks.
2023/03/08 16:06:14 [sandbox:3] Sandboxing disabled
2023/03/08 16:06:14 [video.converter:3] Using preferred video converter: ffmpeg.
2023/03/08 16:06:14 [audio.converter:3] Using samplerate converter: libsamplerate.
2023/03/08 16:06:14 [video.text:3] Using camlimages implementation
2023/03/08 16:06:14 [clock.1678290755-1678291055.m3u8:3] Streaming loop starts in no sync mode
2023/03/08 16:06:14 [clock.1678290755-1678291055.m3u8:3] Delegating synchronisation to active sources
2023/03/08 16:06:15 [1678290755-1678291055.m3u8:3] Prepared "/var/www/chunks/example/example.mp3/1678290755.mp3" (RID 1).
2023/03/08 16:06:16 [decoder:2] Decoding "/var/www/chunks/example/example.mp3/1678290755.mp3" ended: Ffmpeg_decoder.End_of_file.
2023/03/08 16:06:16 [1678290755-1678291055.m3u8:3] Prepared "/var/www/chunks/example/example.mp3/1678290815.mp3" (RID 2).
2023/03/08 16:06:18 [decoder:2] Decoding "/var/www/chunks/example/example.mp3/1678290815.mp3" ended: Ffmpeg_decoder.End_of_file.
2023/03/08 16:06:18 [1678290755-1678291055.m3u8:3] Prepared "/var/www/chunks/example/example.mp3/1678290875.mp3" (RID 3).
[mp3 @ 0xffffb3977340] Estimating duration from bitrate, this may be inaccurate
[mp3 @ 0xffffb3977350] Estimating duration from bitrate, this may be inaccurate
[mp3 @ 0xffffb3977330] Estimating duration from bitrate, this may be inaccurate
[mp3 @ 0xffffb3977340] Estimating duration from bitrate, this may be inaccurate
[mp3 @ 0xffffb3d99510] Estimating duration from bitrate, this may be inaccurate
[mp3 @ 0xffffb3d99520] Estimating duration from bitrate, this may be inaccurate
[mp3 @ 0xffffb3977350] Estimating duration from bitrate, this may be inaccurate
[mp3 @ 0xffffb3977320] Estimating duration from bitrate, this may be inaccurate
[mp3 @ 0xffffaf0004f0] Estimating duration from bitrate, this may be inaccurate
[mp3 @ 0xffffae9293e0] Estimating duration from bitrate, this may be inaccurate
[mp3 @ 0xffffb3977330] Estimating duration from bitrate, this may be inaccurate
[mp3 @ 0xffffb3977340] Estimating duration from bitrate, this may be inaccurate
[mp3 @ 0xffffb098a9c0] Estimating duration from bitrate, this may be inaccurate
[mp3 @ 0xffffb098a9d0] Estimating duration from bitrate, this may be inaccurate
[mp3 @ 0xffffb3977350] Estimating duration from bitrate, this may be inaccurate
[mp3 @ 0xffffb3977320] Estimating duration from bitrate, this may be inaccurate
[mp3 @ 0xffffae929680] Estimating duration from bitrate, this may be inaccurate
[mp3 @ 0xffffae929690] Estimating duration from bitrate, this may be inaccurate
[mp3 @ 0xffffb3977330] Estimating duration from bitrate, this may be inaccurate
[mp3 @ 0xffffb3977340] Estimating duration from bitrate, this may be inaccurate
[mp3 @ 0xffffb0cce980] Estimating duration from bitrate, this may be inaccurate
[mp3 @ 0xffffb09ab070] Estimating duration from bitrate, this may be inaccurate
[mp3 @ 0xffffb3977350] Estimating duration from bitrate, this may be inaccurate
[mp3 @ 0xffffb3977320] Estimating duration from bitrate, this may be inaccurate
2023/03/08 16:06:19 [decoder:2] Decoding "/var/www/chunks/example/example.mp3/1678290875.mp3" ended: Ffmpeg_decoder.End_of_file.
2023/03/08 16:06:20 [1678290755-1678291055.m3u8:3] Prepared "/var/www/chunks/example/example.mp3/1678290935.mp3" (RID 4).
2023/03/08 16:06:21 [decoder:2] Decoding "/var/www/chunks/example/example.mp3/1678290935.mp3" ended: Ffmpeg_decoder.End_of_file.
2023/03/08 16:06:21 [1678290755-1678291055.m3u8:3] Prepared "/var/www/chunks/example/example.mp3/1678290995.mp3" (RID 5).
2023/03/08 16:06:24 [decoder:2] Decoding "/var/www/chunks/example/example.mp3/1678290995.mp3" ended: Ffmpeg_decoder.End_of_file.
2023/03/08 16:06:24 [1678290755-1678291055.m3u8:3] Prepared "/var/www/chunks/example/example.mp3/1678291055.mp3" (RID 6).
2023/03/08 16:06:25 [decoder:2] Decoding "/var/www/chunks/example/example.mp3/1678291055.mp3" ended: Ffmpeg_decoder.End_of_file.
2023/03/08 16:06:25 [/var/www/recordings/example/example.mp3/1678290755-1678291055.mp3:3] Source failed (no more tracks) stopping output...
2023/03/08 16:06:25 [main:3] Shutdown started!
2023/03/08 16:06:25 [main:3] Waiting for main threads to terminate...
2023/03/08 16:06:25 [clock.1678290755-1678291055.m3u8:3] Streaming loop stopped.
2023/03/08 16:06:25 [main:3] Main threads terminated.
2023/03/08 16:06:25 [threads:3] Shutting down scheduler...
2023/03/08 16:06:25 [threads:3] Scheduler shut down.
2023/03/08 16:06:25 [main:3] Cleaning downloaded files...
2023/03/08 16:06:25 [main:3] Freeing memory...
TheFrankman commented 1 year ago

I realise that this could be because of the estimated duration warning that's coming from FFMPEG, but i'm not sure how to work around that either.

cdgraff commented 1 year ago

I'm doing something similar, test with my settings.

  mp3 = 
    %ffmpeg(
      format="mp3",
      id3v2_version=0,
      write_xing=0,
      %audio(
        channels=2,
        samplerate=44100,
        codec="libmp3lame",
        b="64k"
      )
    )

  streams = [("mp3",mp3)]