diff --git a/.github/scripts/build-posix.sh b/.github/scripts/build-posix.sh index 70f67880ea..e35d81a46c 100755 --- a/.github/scripts/build-posix.sh +++ b/.github/scripts/build-posix.sh @@ -41,6 +41,10 @@ echo "::group::Setting up specific dependencies" opam install -y xml-light +git clone https://github.com/savonet/ocaml-xiph.git +cd ocaml-xiph +opam install -y . + cd /tmp/liquidsoap-full/liquidsoap ./.github/scripts/checkout-deps.sh diff --git a/CHANGES.md b/CHANGES.md index e6bdda0518..2b22641a28 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -26,6 +26,7 @@ Fixed: - Fixed request resolution loop when enabling both `autocue` and `replaygain` metadata resolvers (#4245, fixed in #4246) +- Fixed `flac` encoding segfault (#4286, #4274) - Fixed source `last_metadata` not being properly updated (#4262) - Convert all ICY (icecast) metadata from `input.http` to `utf8`. - Fixed `inotify` unwatching due to GC cleanup (#4275) diff --git a/dune-project b/dune-project index 2a47da480d..dfe24e7bce 100644 --- a/dune-project +++ b/dune-project @@ -117,7 +117,7 @@ (fdkaac (< 0.3.1)) (ffmpeg (< 1.2.0)) (ffmpeg-avutil (< 1.2.0)) - (flac (< 0.3.0)) + (flac (< 1.0.0)) (frei0r (< 0.1.0)) (inotify (< 1.0)) (ladspa (< 0.2.0)) @@ -125,7 +125,7 @@ (lo (< 0.2.0)) (mad (< 0.5.0)) (magic (< 0.6)) - (ogg (< 0.7.4)) + (ogg (< 1.0.0)) (opus (< 0.2.0)) (portaudio (< 0.2.0)) (posix-time2 (< 2.0.2)) @@ -133,13 +133,13 @@ (samplerate (< 0.1.5)) (shine (< 0.2.0)) (soundtouch (< 0.1.9)) - (speex (< 0.4.0)) + (speex (< 1.0.0)) (srt (< 0.3.0)) (ssl (< 0.7.0)) (tls (< 1.0.2)) (sdl-liquidsoap (< 2)) - (theora (< 0.4.0)) - (vorbis (< 0.8.0)) + (theora (< 1.0.0)) + (vorbis (< 1.0.0)) (xmlplaylist (< 0.1.3))) (synopsis "Liquidsoap core library and binary")) diff --git a/liquidsoap-core.opam b/liquidsoap-core.opam index 6a75736720..39767caf15 100644 --- a/liquidsoap-core.opam +++ b/liquidsoap-core.opam @@ -83,7 +83,7 @@ conflicts: [ "fdkaac" {< "0.3.1"} "ffmpeg" {< "1.2.0"} "ffmpeg-avutil" {< "1.2.0"} - "flac" {< "0.3.0"} + "flac" {< "1.0.0"} "frei0r" {< "0.1.0"} "inotify" {< "1.0"} "ladspa" {< "0.2.0"} @@ -91,7 +91,7 @@ conflicts: [ "lo" {< "0.2.0"} "mad" {< "0.5.0"} "magic" {< "0.6"} - "ogg" {< "0.7.4"} + "ogg" {< "1.0.0"} "opus" {< "0.2.0"} "portaudio" {< "0.2.0"} "posix-time2" {< "2.0.2"} @@ -99,13 +99,13 @@ conflicts: [ "samplerate" {< "0.1.5"} "shine" {< "0.2.0"} "soundtouch" {< "0.1.9"} - "speex" {< "0.4.0"} + "speex" {< "1.0.0"} "srt" {< "0.3.0"} "ssl" {< "0.7.0"} "tls" {< "1.0.2"} "sdl-liquidsoap" {< "2"} - "theora" {< "0.4.0"} - "vorbis" {< "0.8.0"} + "theora" {< "1.0.0"} + "vorbis" {< "1.0.0"} "xmlplaylist" {< "0.1.3"} ] build: [ diff --git a/src/core/decoder/liq_flac_decoder.ml b/src/core/decoder/liq_flac_decoder.ml index 5a7ff869a6..506d92dc27 100644 --- a/src/core/decoder/liq_flac_decoder.ml +++ b/src/core/decoder/liq_flac_decoder.ml @@ -45,11 +45,14 @@ let create_decoder input = | Some f -> Some (fun () -> Int64.of_int (f ())) | None -> None in - let dummy_c = - Flac.Decoder.get_callbacks ?seek ?tell ?length read (fun _ -> ()) + let write_ref = ref (fun _ -> ()) in + let write v = + let fn = !write_ref in + fn v + in + let decoder, info, _ = + Flac.Decoder.create ?seek ?tell ?length ~read ~write () in - let decoder = Flac.Decoder.create dummy_c in - let decoder, info, _ = Flac.Decoder.init decoder dummy_c in let samplerate, _ = (info.Flac.Decoder.sample_rate, info.Flac.Decoder.channels) in @@ -60,33 +63,29 @@ let create_decoder input = let duration = Frame.seconds_of_main ticks in let samples = Int64.of_float (duration *. float samplerate) in let pos = Int64.add !processed samples in - let c = - Flac.Decoder.get_callbacks ?seek ?tell ?length read (fun _ -> ()) - in - let ret = Flac.Decoder.seek decoder c pos in + let ret = Flac.Decoder.seek decoder pos in if ret = true then ( processed := pos; ticks) else ( - match Flac.Decoder.state decoder c with + match Flac.Decoder.state decoder with | `Seek_error -> - if Flac.Decoder.flush decoder c then 0 + if Flac.Decoder.flush decoder then 0 else (* Flushing failed, we are in an unknown state.. *) raise End_of_stream | _ -> 0)); decode = (fun buffer -> - let c = - Flac.Decoder.get_callbacks ?seek ?tell ?length read (fun data -> - let len = try Audio.length data with _ -> 0 in - processed := Int64.add !processed (Int64.of_int len); - buffer.Decoder.put_pcm ~samplerate data) - in - match Flac.Decoder.state decoder c with + (write_ref := + fun data -> + let len = try Audio.length data with _ -> 0 in + processed := Int64.add !processed (Int64.of_int len); + buffer.Decoder.put_pcm ~samplerate data); + match Flac.Decoder.state decoder with | `Search_for_metadata | `Read_metadata | `Search_for_frame_sync | `Read_frame -> - Flac.Decoder.process decoder c + Flac.Decoder.process decoder | _ -> raise End_of_stream); eof = (fun _ -> ()); close = (fun _ -> ()); @@ -118,7 +117,7 @@ let file_type filename = ~finally:(fun () -> Unix.close fd) (fun () -> let write _ = () in - let h = Flac.Decoder.File.create_from_fd write fd in + let h = Flac.Decoder.File.create_from_fd ~write fd in let info = h.Flac.Decoder.File.info in let rate, channels = (info.Flac.Decoder.sample_rate, info.Flac.Decoder.channels) @@ -161,7 +160,7 @@ let get_tags ~metadata:_ ~extension ~mime file = ~finally:(fun () -> Unix.close fd) (fun () -> let write _ = () in - let h = Flac.Decoder.File.create_from_fd write fd in + let h = Flac.Decoder.File.create_from_fd ~write fd in match h.Flac.Decoder.File.comments with Some (_, m) -> m | None -> []) let metadata_decoder_priority = @@ -191,7 +190,7 @@ let dresolver ~metadata:_ file = ~finally:(fun () -> Unix.close fd) (fun () -> let write _ = () in - let h = Flac.Decoder.File.create_from_fd write fd in + let h = Flac.Decoder.File.create_from_fd ~write fd in let info = h.Flac.Decoder.File.info in match info.Flac.Decoder.total_samples with | x when x = Int64.zero -> raise Not_found diff --git a/src/core/decoder/ogg_flac_duration.ml b/src/core/decoder/ogg_flac_duration.ml index 339c3d642e..fa14de0d34 100644 --- a/src/core/decoder/ogg_flac_duration.ml +++ b/src/core/decoder/ogg_flac_duration.ml @@ -37,22 +37,14 @@ let dresolver ~metadata:_ file = let serial = Ogg.Page.serialno page in let os = Ogg.Stream.create ~serial () in Ogg.Stream.put_page os page; - let packet = Ogg.Stream.get_packet os in + let packet = Ogg.Stream.peek_packet os in (* Test header. Do not catch anything, first page should be sufficient *) if not (Flac_ogg.Decoder.check_packet packet) then raise Not_found; let fill () = let page = Ogg.Sync.read sync in if Ogg.Page.serialno page = serial then Ogg.Stream.put_page os page in - let callbacks = Flac_ogg.Decoder.get_callbacks os (fun _ -> ()) in - let dec = Flac.Decoder.create callbacks in - let rec info () = - try Flac.Decoder.init dec callbacks - with Ogg.Not_enough_data -> - fill (); - info () - in - info () + Flac_ogg.Decoder.create ~fill ~write:(fun _ -> ()) os in (* Now find a flac stream *) let rec init () = try test_flac () with Not_found -> init () in @@ -64,7 +56,7 @@ let dresolver ~metadata:_ file = samples /. float info.Flac.Decoder.sample_rate) let () = - Plug.register Request.dresolvers "ogg/flac" ~doc:"" + Plug.register Request.dresolvers "ogg_flac" ~doc:"" { dpriority = (fun () -> Liq_ogg_decoder.priority#get); file_extensions = (fun () -> Liq_ogg_decoder.file_extensions#get); diff --git a/src/core/encoder/encoders/flac_encoder.ml b/src/core/encoder/encoders/flac_encoder.ml index 343ca83cd9..855c7ecf40 100644 --- a/src/core/encoder/encoders/flac_encoder.ml +++ b/src/core/encoder/encoders/flac_encoder.ml @@ -44,8 +44,7 @@ let encoder flac meta = in let buf = Strings.Mutable.empty () in let write chunk = Strings.Mutable.add_bytes buf chunk in - let cb = Flac.Encoder.get_callbacks write in - let enc = Flac.Encoder.create ~comments p cb in + let enc = Flac.Encoder.create ~comments ~write p in let enc = ref enc in let encode frame = let b = AFrame.pcm frame in @@ -55,11 +54,11 @@ let encoder flac meta = (dst_freq /. src_freq) b 0 len in let b = Audio.sub b start len in - Flac.Encoder.process !enc cb b; + Flac.Encoder.process !enc b; Strings.Mutable.flush buf in let stop () = - Flac.Encoder.finish !enc cb; + Flac.Encoder.finish !enc; Strings.Mutable.flush buf in { diff --git a/src/core/encoder/encoders/ogg_encoder.ml b/src/core/encoder/encoders/ogg_encoder.ml index 0e1508b812..0e296b6428 100644 --- a/src/core/encoder/encoders/ogg_encoder.ml +++ b/src/core/encoder/encoders/ogg_encoder.ml @@ -132,8 +132,7 @@ let encoder ~pos { Ogg_format.audio; video } = and ogg_stop () = let f track = track.id <- None in List.iter f tracks; - if Ogg_muxer.state ogg_enc = Ogg_muxer.Streaming then - Ogg_muxer.end_of_stream ogg_enc + Ogg_muxer.end_of_stream ogg_enc and stop () = ogg_stop (); Ogg_muxer.get_data ogg_enc diff --git a/src/core/ogg_formats/ogg_flac_encoder.ml b/src/core/ogg_formats/ogg_flac_encoder.ml index 86849ffe55..b8128c5531 100644 --- a/src/core/ogg_formats/ogg_flac_encoder.ml +++ b/src/core/ogg_formats/ogg_flac_encoder.ml @@ -51,7 +51,7 @@ let create_encoder ~flac ~comments () = | None -> let x = Flac_ogg.Encoder.create ~comments ~serialno:(Ogg.Stream.serialno os) - p write_cb + ~write:write_cb p in enc := Some x; x @@ -80,8 +80,8 @@ let create_encoder ~flac ~comments () = (data.Ogg_muxer.data, data.Ogg_muxer.offset, data.Ogg_muxer.length) in let b = Array.map (fun x -> Array.sub x ofs len) b in - let { Flac_ogg.Encoder.encoder; callbacks } = get_enc os in - Flac.Encoder.process encoder callbacks b; + let { Flac_ogg.Encoder.encoder } = get_enc os in + Flac.Encoder.process encoder b; List.iter write_page (flush_pages ()) in let end_of_page p = @@ -90,12 +90,12 @@ let create_encoder ~flac ~comments () = else Ogg_muxer.Time (Int64.to_float granulepos /. float samplerate) in let end_of_stream os = - let { Flac_ogg.Encoder.encoder; callbacks } = get_enc os in + let { Flac_ogg.Encoder.encoder } = get_enc os in (* Assert that at least some data was encoded.. *) if not !started then ( let b = empty_data () in - Flac.Encoder.process encoder callbacks b); - Flac.Encoder.finish encoder callbacks; + Flac.Encoder.process encoder b); + Flac.Encoder.finish encoder; set_stream_eos os; flush_pages () in diff --git a/src/core/ogg_formats/ogg_muxer.ml b/src/core/ogg_formats/ogg_muxer.ml index 759257223a..2f95be87fa 100644 --- a/src/core/ogg_formats/ogg_muxer.ml +++ b/src/core/ogg_formats/ogg_muxer.ml @@ -219,9 +219,7 @@ let register_track ?fill encoder track_encoder = (** Start streams, set state to Streaming. *) let streams_start encoder = - if Hashtbl.length encoder.tracks = 0 then - log#info "%s: Starting stream with no ogg track.." encoder.id; - log#info "%s: Starting all streams" encoder.id; + log#info "%s: Starting %d track(s)" encoder.id (Hashtbl.length encoder.tracks); (* Add skeleton information first. *) begin diff --git a/tests/regression/dune.inc b/tests/regression/dune.inc index dc606c4403..e6a72ef33e 100644 --- a/tests/regression/dune.inc +++ b/tests/regression/dune.inc @@ -975,6 +975,22 @@ (:run_test ../run_test.exe)) (action (run %{run_test} external-encoder.liq liquidsoap %{test_liq} external-encoder.liq))) +(rule + (alias citest) + (package liquidsoap) + (deps + fallible_ogg.liq + ../media/all_media_files + ../../src/bin/liquidsoap.exe + ../streams/file1.png + ../streams/file1.mp3 + ./theora-test.mp4 + (package liquidsoap) + (source_tree ../../src/libs) + (:test_liq ../test.liq) + (:run_test ../run_test.exe)) + (action (run %{run_test} fallible_ogg.liq liquidsoap %{test_liq} fallible_ogg.liq))) + (rule (alias citest) (package liquidsoap) diff --git a/tests/regression/fallible_ogg.liq b/tests/regression/fallible_ogg.liq new file mode 100644 index 0000000000..8db15c5273 --- /dev/null +++ b/tests/regression/fallible_ogg.liq @@ -0,0 +1,9 @@ +port = 9301 + +s = playlist("../media") + +output.icecast(fallible=true, port=port, mount="test.flac", %ogg(%flac), s) + +s = input.harbor("test.flac", buffer=1., port=port) + +output.dummy(on_start=test.pass, fallible=true, s)