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

`#get_frame didn't add exactly one break!` when using `pipe` #1786

Closed toots closed 3 years ago

toots commented 3 years ago

Issue is described in discussion https://github.com/savonet/liquidsoap/discussions/1782

Reference script:

#!/usr/bin/env liquidsoap

BEET = "/home/pi/.local/bin/beet"

def beets(arg="") =
  fun() -> list.map(fun(item) -> request.create(item),
     process.read.lines(
      "#{BEET} random -f '$path' #{arg}"
    )
  )
end

def transition(a,b)
  # If old or new source is not music, no fade
  if a.metadata["source_tag"] != "music" or a.metadata["source_tag"] != "music" then
    sequence([a.source, b.source])
  else
    # Else, apply the standard smart transition
    cross.smart(a, b)
  end
end

# A function to add a source_tag metadata to a source:
def source_tag(s,tag) =
  def f(_)
    [("source_tag",(tag:string))]
  end
  map_metadata(id=tag,insert_missing=true,f,s)
end

# Tag our sources
default = blank()
music = request.dynamic.list(beets("length:..03:30"))
music = source_tag(music, "music")
music = amplify(1.,override="replaygain_track_gain", music)
music = skip_blank(music, max_blank=5., threshold=-50.)

radio = music
radio = cross(duration=5., transition, radio)

st = pipe(process='/usr/bin/stereo_tool - - -q', radio)

s = fallback(track_sensitive=false, [st, default])
output.alsa(s, device="pcm.liquidsoap")

Log:

[mp3 @ 0x6758b2a0] Estimating duration from bitrate, this may be inaccurate
2021/07/28 19:29:58 [request.dynamic.list_0:3] Prepared "/home/pi/anda/music/jazz/Roland Kirk - Blacknuss - 1972/01 Ain't No Sunshine.mp3" (RID 0).
2021/07/28 19:29:59 [request.dynamic.list_0:2] #get_frame didn't add exactly one break!
2021/07/28 19:29:59 [threads:2] Queue generic queue #1 crashed with exception File "source.ml", line 746, characters 12-18: Assertion failed
pi@radio:~ $ ./radio.liq
no more csLADSPA plugins
2021/07/28 19:36:41 >>> LOG START
2021/07/28 19:36:23 [main:3] Liquidsoap 2.0.0+git@a697602c
2021/07/28 19:36:23 [main:3] Using: bytes=[distributed with OCaml 4.02 or above] pcre=7.5.0 sedlex=2.3 menhirLib=20210419 curl=0.9.1 dtools=0.4.4 duppy=0.9.2 cry=0.6.5 mm=0.7.1 xmlplaylist=0.1.5 ogg=0.7.0 ogg.decoder=0.7.0 vorbis=0.8.0 vorbis.decoder=0.8.0 opus=0.2.0 opus.decoder=0.2.0 mad=0.5.0 dynlink=[distributed with Ocaml] lame=0.3.4 gstreamer=0.3.1 ffmpeg-avutil=97c1fc4 ffmpeg-avcodec=97c1fc4 ffmpeg-avdevice=97c1fc4 ffmpeg-av=97c1fc4 ffmpeg-avfilter=97c1fc4 ffmpeg-swresample=97c1fc4 ffmpeg-swscale=97c1fc4 bjack=0.1.6 alsa=0.3.0 ao=0.2.3 samplerate=0.1.5 taglib=0.3.6 ssl=0.5.9 camomile=1.0.2 faad=0.5.0 ladspa=0.2.0
2021/07/28 19:36:23 [main:3] 
2021/07/28 19:36:23 [main:3] DISCLAIMER: This version of Liquidsoap has been
2021/07/28 19:36:23 [main:3] compiled from a snapshot of the development code.
2021/07/28 19:36:23 [main:3] As such, it should not be used in production
2021/07/28 19:36:23 [main:3] unless you know what you are doing!
2021/07/28 19:36:23 [main:3] 
2021/07/28 19:36:23 [main:3] We are, however, very interested in any feedback
2021/07/28 19:36:23 [main:3] about our development code and committed to fix
2021/07/28 19:36:23 [main:3] issues as soon as possible.
2021/07/28 19:36:23 [main:3] 
2021/07/28 19:36:23 [main:3] If you are interested in collaborating to
2021/07/28 19:36:23 [main:3] the development of Liquidsoap, feel free to
2021/07/28 19:36:23 [main:3] drop us a mail at <savonet-devl@lists.sf.net>
2021/07/28 19:36:23 [main:3] or to join the slack chat at <http://slack.liquidsoap.info>.
2021/07/28 19:36:23 [main:3] 
2021/07/28 19:36:23 [main:3] Please send any bug report or feature request
2021/07/28 19:36:23 [main:3] at <https://github.com/savonet/liquidsoap/issues>.
2021/07/28 19:36:23 [main:3] 
2021/07/28 19:36:23 [main:3] We hope you enjoy this snapshot build of Liquidsoap!
2021/07/28 19:36:23 [main:3] 
2021/07/28 19:36:39 [dynamic.loader:3] Could not find dynamic module for fdkaac encoder.
2021/07/28 19:36:39 [gstreamer.loader:3] Loaded GStreamer 1.14.4 0
2021/07/28 19:36:39 [clock:3] Using builtin (low-precision) implementation for latency control
2021/07/28 19:36:41 [lang.deprecated:2] WARNING: "skip_blank" is deprecated and will be removed in future version. Please use "blank.skip" instead.
2021/07/28 19:36:41 [frame:3] Using 44100Hz audio, 25Hz video, 44100Hz main.
2021/07/28 19:36:41 [frame:3] Video frame size set to: 1280x720
2021/07/28 19:36:41 [frame:3] Frame size must be a multiple of 1764 ticks = 1764 audio samples = 1 video samples.
2021/07/28 19:36:41 [frame:3] Targeting 'frame.duration': 0.04s = 1764 audio samples = 1764 ticks.
2021/07/28 19:36:41 [frame:3] Frames last 0.04s = 1764 audio samples = 1 video samples = 1764 ticks.
2021/07/28 19:36:41 [sandbox:3] Sandboxing disabled
2021/07/28 19:36:41 [video.converter:3] Using preferred video converter: ffmpeg.
2021/07/28 19:36:41 [audio.converter:3] Using samplerate converter: ffmpeg.
2021/07/28 19:36:41 [clock.alsa:3] Streaming loop starts in auto-sync mode
2021/07/28 19:36:41 [clock.alsa:3] Delegating synchronisation to CPU clock
2021/07/28 19:36:41 [switch_0:3] Switch to blank_1.
2021/07/28 19:36:41 [alsa_out(pcm.liquidsoap):3] Using ALSA 1.1.8.
2021/07/28 19:36:41 [alsa_out(pcm.liquidsoap):3] channels=2, samplerate=44100Hz, buffer size=60211B, frame size=4B, periods=2077
2021/07/28 19:36:42 [clock.alsa:3] Delegating synchronisation to active sources
2021/07/28 19:36:52 [request.dynamic.list_0:3] Prepared "/home/pi/anda/music/Various Artists - Strange Breaks & Mr Thing Vol. 2 - 2008/15 Hot City Bump Band - It's Just Begun.mp3" (RID 0).
[mp3float @ 0x67512000] Could not update timestamps for skipped samples.
2021/07/28 19:36:57 [switch_0:3] Switch to pipe_0 with transition.
2021/07/28 19:37:02 [switch_0:3] Switch to blank_1 with transition.
2021/07/28 19:37:04 [switch_0:3] Switch to pipe_0 with transition.
2021/07/28 19:37:06 [switch_0:3] Switch to blank_1 with transition.
2021/07/28 19:37:07 [switch_0:3] Switch to pipe_0 with transition.
2021/07/28 19:37:11 [switch_0:3] Switch to blank_1 with transition.
2021/07/28 19:37:13 [switch_0:3] Switch to pipe_0 with transition.
2021/07/28 19:37:18 [blank.detect_0:2] #get_frame didn't add exactly one break!
2021/07/28 19:37:18 [threads:2] Queue non-blocking queue #2 crashed with exception File "source.ml", line 746, characters 12-18: Assertion failed
2021/07/28 19:37:18 [threads:2] Raised at Process_handler.run.handler in file "tools/process_handler.ml", line 299, characters 16-23
2021/07/28 19:37:18 [threads:2] Called from Duppy.Task.t_of_task.(fun) in file "src/duppy.ml", line 148, characters 50-66
2021/07/28 19:37:18 [threads:2] Called from Duppy.exec in file "src/duppy.ml", line 275, characters 12-21
2021/07/28 19:37:18 [threads:2] Called from Duppy.queue.run in file "src/duppy.ml", line 307, characters 7-24
2021/07/28 19:37:18 [threads:2] Called from Duppy.queue in file "src/duppy.ml", line 357, characters 8-12
2021/07/28 19:37:18 [threads:2] Re-raised at Duppy.queue in file "src/duppy.ml", line 361, characters 8-17
2021/07/28 19:37:18 [threads:2] Called from Tutils.create.(fun).process in file "tools/tutils.ml", line 173, characters 12-15
2021/07/28 19:37:18 [threads:2] 
2021/07/28 19:37:18 [threads:1] PANIC: Liquidsoap has crashed, exiting.,
2021/07/28 19:37:18 [threads:1] Please report at: savonet-users@lists.sf.net
pi@radio:~ $
toots commented 3 years ago

I was able to reproduce with this script without needing to minimize it:

def transition(a,b)
  # If old or new source is not music, no fade
  if a.metadata["source_tag"] != "music" or a.metadata["source_tag"] != "music" then
    sequence([a.source, b.source])
  else
    # Else, apply the standard smart transition
    cross.smart(a, b)
  end
end

# A function to add a source_tag metadata to a source:
def source_tag(s,tag) =
  def f(_)
    [("source_tag",(tag:string))]
  end
  map_metadata(id=tag,insert_missing=true,f,s)
end

def f(_) =
  blank(duration=2.)
end

# Tag our sources
default = blank()
music = request.dynamic.list({[request.create("/tmp/bla.mp3")]})
music = source_tag(music, "music")
music = amplify(1.,override="replaygain_track_gain", music)
music = chop(music)
music = append(music, f)
music = blank.skip(music, max_blank=0.5, threshold=-50.)

radio = music
radio = cross(duration=5., transition, radio)

st = pipe(process='stereo_tool - - -q', radio)

s = fallback(track_sensitive=false, [st, default])
output.ao(s)

Commit 5557ba3 seems to have fixed it. My understanding of the issue is that, with the new Child_support implementation, there was a chance get_to_write and after_output would both call source#get during a single streaming loop, leading to the last one not adding an extra break. I haven't investigated more but, with this fix, we let the piping process drive the child source's clock entirely while it is running and resort to the default Child_support after_output based mechanism only when the process is not active.