Skip to content

Commit 3758ca4

Browse files
committed
Add support for splitting on video key frame in output.file.hls
1 parent 70ba1c2 commit 3758ca4

15 files changed

+549
-255
lines changed

src/encoder.ml

+37-16
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,13 @@ let extension = function
157157
| Shine _ -> "mp3"
158158
| Flac _ -> "flac"
159159
| FdkAacEnc _ -> "aac"
160+
(* Todo: add more.. *)
161+
| Ffmpeg { Ffmpeg_format.format = Some "ogg" } -> "ogg"
162+
| Ffmpeg { Ffmpeg_format.format = Some "opus" } -> "opus"
163+
| Ffmpeg { Ffmpeg_format.format = Some "libmp3lame" } -> "mp3"
164+
| Ffmpeg { Ffmpeg_format.format = Some "mpegts" } -> "ts"
165+
| Ffmpeg { Ffmpeg_format.format = Some "mp4" } -> "mp4"
166+
| Ffmpeg { Ffmpeg_format.format = Some "wav" } -> "wav"
160167
| _ -> raise Not_found
161168

162169
(** Mime types *)
@@ -194,32 +201,46 @@ let with_url_output encoder file =
194201
| _ -> failwith "No file output!"
195202

196203
(** An encoder, once initialized, is something that consumes
197-
* frames, insert metadata and that you eventually close
198-
* (triggers flushing).
199-
* Insert metadata is really meant for inline metadata, i.e.
200-
* in most cases, stream sources. Otherwise, metadata are
201-
* passed when creating the encoder. For instance, the mp3
202-
* encoder may accept metadata initally and write them as
203-
* id3 tags but does not support inline metadata.
204-
* Also, the ogg encoder supports inline metadata but restarts
205-
* its stream. This is ok, though, because the ogg container/streams
206-
* is meant to be sequentialized but not the mp3 format.
207-
* header contains data that should be sent first to streaming
208-
* client. *)
204+
frames, insert metadata and that you eventually close
205+
(triggers flushing).
206+
Insert metadata is really meant for inline metadata, i.e.
207+
in most cases, stream sources. Otherwise, metadata are
208+
passed when creating the encoder. For instance, the mp3
209+
encoder may accept metadata initally and write them as
210+
id3 tags but does not support inline metadata.
211+
Also, the ogg encoder supports inline metadata but restarts
212+
its stream. This is ok, though, because the ogg container/streams
213+
is meant to be sequentialized but not the mp3 format.
214+
header contains data that should be sent first to streaming
215+
client. *)
216+
217+
type split_result =
218+
[ (* Returns (flushed, first_bytes_for_next_segment) *)
219+
`Ok of
220+
Strings.t * Strings.t
221+
| `Nope of Strings.t ]
222+
223+
type hls = {
224+
(* Returns (init_segment, first_bytes) *)
225+
init_encode : Frame.t -> int -> int -> Strings.t option * Strings.t;
226+
split_encode : Frame.t -> int -> int -> split_result;
227+
}
228+
209229
type encoder = {
210230
insert_metadata : Meta_format.export_metadata -> unit;
211-
(* Encoder are all called from the main
212-
* thread so there's no need to protect this
213-
* value with a mutex so far.. *)
231+
(* Encoder are all called from the main
232+
thread so there's no need to protect this
233+
value with a mutex so far.. *)
214234
mutable header : Strings.t;
235+
hls : hls;
215236
encode : Frame.t -> int -> int -> Strings.t;
216237
stop : unit -> Strings.t;
217238
}
218239

219240
type factory = string -> Meta_format.export_metadata -> encoder
220241

221242
(** A plugin might or might not accept a given format.
222-
* If it accepts it, it gives a function creating suitable encoders. *)
243+
If it accepts it, it gives a function creating suitable encoders. *)
223244
type plugin = format -> factory option
224245

225246
let plug : plugin Plug.plug =

src/encoder.mli

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
(*****************************************************************************
2+
3+
Liquidsoap, a programmable audio stream generator.
4+
Copyright 2003-2020 Savonet team
5+
6+
This program is free software; you can redistribute it and/or modify
7+
it under the terms of the GNU General Public License as published by
8+
the Free Software Foundation; either version 2 of the License, or
9+
(at your option) any later version.
10+
11+
This program is distributed in the hope that it will be useful,
12+
but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
GNU General Public License for more details, fully stated in the COPYING
15+
file at the root of the liquidsoap distribution.
16+
17+
You should have received a copy of the GNU General Public License
18+
along with this program; if not, write to the Free Software
19+
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20+
21+
*****************************************************************************)
22+
23+
(** Common infrastructure for encoding streams *)
24+
25+
type format =
26+
| WAV of Wav_format.t
27+
| AVI of Avi_format.t
28+
| Ogg of Ogg_format.t
29+
| MP3 of Mp3_format.t
30+
| Shine of Shine_format.t
31+
| Flac of Flac_format.t
32+
| Ffmpeg of Ffmpeg_format.t
33+
| FdkAacEnc of Fdkaac_format.t
34+
| External of External_encoder_format.t
35+
| GStreamer of Gstreamer_format.t
36+
37+
val audio_kind : int -> Frame.kind Frame.fields
38+
val audio_video_kind : int -> Frame.kind Frame.fields
39+
val kind_of_format : format -> Frame.kind Frame.fields
40+
val string_of_format : format -> string
41+
42+
(** ISO Base Media File Format, see RFC 6381 section 3.3. *)
43+
val iso_base_file_media_file_format : format -> string
44+
45+
(** Proposed extension for files. *)
46+
val extension : format -> string
47+
48+
(** Mime types *)
49+
val mime : format -> string
50+
51+
(** Bitrate estimation in bits per second. *)
52+
val bitrate : format -> int
53+
54+
(** Encoders that can output to a file. *)
55+
val file_output : format -> bool
56+
57+
val with_file_output : format -> string -> format
58+
59+
(** Encoders that can output to a arbitrary url. *)
60+
val url_output : format -> bool
61+
62+
val with_url_output : format -> string -> format
63+
64+
(** An encoder, once initialized, is something that consumes
65+
* frames, insert metadata and that you eventually close
66+
* (triggers flushing).
67+
* Insert metadata is really meant for inline metadata, i.e.
68+
* in most cases, stream sources. Otherwise, metadata are
69+
* passed when creating the encoder. For instance, the mp3
70+
* encoder may accept metadata initally and write them as
71+
* id3 tags but does not support inline metadata.
72+
* Also, the ogg encoder supports inline metadata but restarts
73+
* its stream. This is ok, though, because the ogg container/streams
74+
* is meant to be sequentialized but not the mp3 format.
75+
* header contains data that should be sent first to streaming
76+
* client. *)
77+
78+
type split_result =
79+
[ (* Returns (flushed, first_bytes_for_next_segment) *)
80+
`Ok of
81+
Strings.t * Strings.t
82+
| `Nope of Strings.t ]
83+
84+
type hls = {
85+
(* Returns (init_segment, first_bytes) *)
86+
init_encode : Frame.t -> int -> int -> Strings.t option * Strings.t;
87+
split_encode : Frame.t -> int -> int -> split_result;
88+
}
89+
90+
type encoder = {
91+
insert_metadata : Meta_format.export_metadata -> unit;
92+
(* Encoder are all called from the main
93+
* thread so there's no need to protect this
94+
* value with a mutex so far.. *)
95+
mutable header : Strings.t;
96+
hls : hls;
97+
encode : Frame.t -> int -> int -> Strings.t;
98+
stop : unit -> Strings.t;
99+
}
100+
101+
type factory = string -> Meta_format.export_metadata -> encoder
102+
103+
(** A plugin might or might not accept a given format.
104+
* If it accepts it, it gives a function creating suitable encoders. *)
105+
type plugin = format -> factory option
106+
107+
val plug : plugin Plug.plug
108+
109+
(** Return the first available encoder factory for that format. *)
110+
val get_factory : format -> factory

src/encoder/avi_encoder.ml

+7
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,15 @@ let encoder avi =
7878
Strings.dda header ans )
7979
else ans
8080
in
81+
let hls =
82+
{
83+
Encoder.init_encode = (fun f o l -> (None, encode f o l));
84+
split_encode = (fun f o l -> `Ok (Strings.empty, encode f o l));
85+
}
86+
in
8187
{
8288
Encoder.insert_metadata = (fun _ -> ());
89+
hls;
8390
encode;
8491
header = Strings.of_string header;
8592
stop = (fun () -> Strings.empty);

src/encoder/external_encoder.ml

+7
Original file line numberDiff line numberDiff line change
@@ -153,12 +153,19 @@ let encoder id ext =
153153
Condition.wait condition mutex;
154154
Strings.Mutable.flush buf)
155155
in
156+
let hls =
157+
{
158+
Encoder.init_encode = (fun f o l -> (None, encode f o l));
159+
split_encode = (fun f o l -> `Ok (Strings.empty, encode f o l));
160+
}
161+
in
156162
{
157163
Encoder.insert_metadata;
158164
(* External encoders do not support
159165
* headers for now. They will probably
160166
* never do.. *)
161167
header = Strings.empty;
168+
hls;
162169
encode;
163170
stop;
164171
}

src/encoder/fdkaac_encoder.ml

+7
Original file line numberDiff line numberDiff line change
@@ -159,9 +159,16 @@ module Register (Fdkaac : Fdkaac_t) = struct
159159
Strings.Mutable.add rem (Fdkaac.Encoder.flush enc);
160160
Strings.Mutable.to_strings rem
161161
in
162+
let hls =
163+
{
164+
Encoder.init_encode = (fun f o l -> (None, encode f o l));
165+
split_encode = (fun f o l -> `Ok (Strings.empty, encode f o l));
166+
}
167+
in
162168
{
163169
Encoder.insert_metadata = (fun _ -> ());
164170
header = Strings.empty;
171+
hls;
165172
encode;
166173
stop;
167174
}

src/encoder/ffmpeg_copy_encoder.ml

+11-2
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,14 @@ let mk_stream_copy ~get_data output =
5555
| _ -> failwith "Packet missing duration!"
5656
in
5757

58+
let was_keyframe = ref false in
59+
5860
let encode frame start len =
5961
let stop = start + len in
6062
let data = (get_data frame).Ffmpeg_content_base.data in
6163

64+
was_keyframe := false;
65+
6266
List.iter
6367
(fun (pos, { Ffmpeg_copy_content.packet; time_base }) ->
6468
let stream = Option.get !stream in
@@ -76,7 +80,12 @@ let mk_stream_copy ~get_data output =
7680
add ~duration ~time_base next_dts;
7781
add ~duration ~time_base next_pts;
7882

79-
Av.write_packet stream master_time_base packet ))
83+
Av.write_packet stream master_time_base packet;
84+
if List.mem `Keyframe Avcodec.Packet.(get_flags packet) then
85+
was_keyframe := true ))
8086
data
8187
in
82-
{ Ffmpeg_encoder_common.mk_stream; encode }
88+
89+
let was_keyframe () = !was_keyframe in
90+
91+
{ Ffmpeg_encoder_common.mk_stream; was_keyframe; encode }

src/encoder/ffmpeg_encoder_common.ml

+31-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ let log = Ffmpeg_utils.log
2727
type encoder = {
2828
mk_stream : Frame.t -> unit;
2929
encode : Frame.t -> int -> int -> unit;
30+
was_keyframe : unit -> bool;
3031
}
3132

3233
type handler = {
@@ -110,6 +111,34 @@ let encoder ~mk_audio ~mk_video ffmpeg meta =
110111
{ output; audio_stream; video_stream; started = false }
111112
in
112113
let encoder = ref (make ()) in
114+
let init_encode frame start len =
115+
let encoder = !encoder in
116+
match ffmpeg.Ffmpeg_format.format with
117+
| Some "mp4" ->
118+
encode ~encoder frame start len;
119+
Av.flush encoder.output;
120+
let init = Strings.Mutable.flush buf in
121+
(Some init, Strings.empty)
122+
| Some "webm" ->
123+
Av.flush encoder.output;
124+
let init = Strings.Mutable.flush buf in
125+
encode ~encoder frame start len;
126+
(Some init, Strings.Mutable.flush buf)
127+
| _ ->
128+
encode ~encoder frame start len;
129+
(None, Strings.Mutable.flush buf)
130+
in
131+
let split_encode frame start len =
132+
let encoder = !encoder in
133+
Av.flush encoder.output;
134+
let flushed = Strings.Mutable.flush buf in
135+
encode ~encoder frame start len;
136+
let encoded = Strings.Mutable.flush buf in
137+
match encoder.video_stream with
138+
| Some s when s.was_keyframe () -> `Ok (flushed, encoded)
139+
| None -> `Ok (flushed, encoded)
140+
| _ -> `Nope (Strings.append flushed encoded)
141+
in
113142
let encode frame start len =
114143
encode ~encoder:!encoder frame start len;
115144
Strings.Mutable.flush buf
@@ -120,4 +149,5 @@ let encoder ~mk_audio ~mk_video ffmpeg meta =
120149
Av.close !encoder.output;
121150
Strings.Mutable.flush buf
122151
in
123-
{ Encoder.insert_metadata; header = Strings.empty; encode; stop }
152+
let hls = { Encoder.init_encode; split_encode } in
153+
{ Encoder.insert_metadata; header = Strings.empty; hls; encode; stop }

src/encoder/ffmpeg_internal_encoder.ml

+14-4
Original file line numberDiff line numberDiff line change
@@ -200,11 +200,13 @@ let mk_audio ~ffmpeg ~options output =
200200
flush () )
201201
in
202202

203+
let was_keyframe () = false in
204+
203205
let encode frame start len =
204206
List.iter write_frame (converter frame start len)
205207
in
206208

207-
{ Ffmpeg_encoder_common.mk_stream; encode }
209+
{ Ffmpeg_encoder_common.mk_stream; was_keyframe; encode }
208210

209211
let mk_video ~ffmpeg ~options output =
210212
let codec =
@@ -276,6 +278,8 @@ let mk_video ~ffmpeg ~options output =
276278

277279
let stream_time_base = Av.get_time_base stream in
278280

281+
let was_keyframe = ref false in
282+
279283
let fps_converter ~time_base frame =
280284
let converter =
281285
get_converter ~time_base
@@ -291,7 +295,8 @@ let mk_video ~ffmpeg ~options output =
291295
(Avutil.frame_pts frame)
292296
in
293297
Avutil.frame_set_pts frame frame_pts;
294-
Av.write_frame stream frame)
298+
Av.write_frame stream frame;
299+
if Av.was_keyframe stream then was_keyframe := true)
295300
in
296301

297302
let internal_converter cb =
@@ -369,6 +374,11 @@ let mk_video ~ffmpeg ~options output =
369374
| _ -> assert false
370375
in
371376

372-
let encode = converter fps_converter in
377+
let encode =
378+
was_keyframe := false;
379+
converter fps_converter
380+
in
381+
382+
let was_keyframe () = !was_keyframe in
373383

374-
{ Ffmpeg_encoder_common.mk_stream; encode }
384+
{ Ffmpeg_encoder_common.mk_stream; was_keyframe; encode }

src/encoder/flac_encoder.ml

+7
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,18 @@ let encoder flac meta =
6464
Flac.Encoder.finish !enc cb;
6565
Strings.Mutable.flush buf
6666
in
67+
let hls =
68+
{
69+
Encoder.init_encode = (fun f o l -> (None, encode f o l));
70+
split_encode = (fun f o l -> `Ok (Strings.empty, encode f o l));
71+
}
72+
in
6773
{
6874
Encoder.insert_metadata = (fun _ -> ());
6975
(* Flac encoder do not support header
7076
* for now. It will probably never do.. *)
7177
header = Strings.empty;
78+
hls;
7279
encode;
7380
stop;
7481
}

0 commit comments

Comments
 (0)