Skip to content

Commit

Permalink
Add streams_info to HLS output, make it mandatory when they cannot be
Browse files Browse the repository at this point in the history
inferred from the encoder.
  • Loading branch information
toots committed Aug 31, 2019
1 parent fff33ed commit db8f333
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 38 deletions.
14 changes: 8 additions & 6 deletions libs/hls.liq
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def input.hls(~id="",~reload=10.,uri)
end

# @flag hidden
def output.harbor.hls.base(~id,~fallible,~on_start,~on_stop,
def output.harbor.hls.base(~id,~fallible,~on_start,~on_stop,~streams_info,
~playlist,~segment_name,~segment_duration,
~segments,~segments_per_playlist,~encode_metadata,
~on_file_change,~start,~port,~path,serve,formats,s)
Expand All @@ -114,7 +114,7 @@ def output.harbor.hls.base(~id,~fallible,~on_start,~on_stop,

serve(port=port,path=path,content_type=content_type,tmp_dir)

output.file.hls(id=id,fallible=fallible,on_start=on_start,on_stop=on_stop,
output.file.hls(id=id,fallible=fallible,on_start=on_start,on_stop=on_stop,streams_info=streams_info,
playlist=playlist,start=start,segment_name=segment_name,encode_metadata=encode_metadata,
segment_duration=segment_duration,segments=segments,on_file_change=on_file_change,
segments_per_playlist=segments_per_playlist,tmp_dir,formats,s)
Expand All @@ -134,12 +134,13 @@ end
# @param ~segments Number of segments to keep.
# @param ~segments_per_playlist Number of segments per playlist.
# @param ~start Automatically start outputting whenever possible. If true, an infallible (normal) output will start outputting as soon as it is created, and a fallible output will (re)start as soon as its source becomes available for streaming.
# @param ~streams_info Additional information about the streams. Should be a list of the form: `[(stream_name, (bandwidth, codec, extname)]`. See RFC 6381 for info about codec. Stream info are required when they cannot be inferred from the encoder.
# @param ~port Port for incoming harbor (http) connections.
# @param ~headers Default response headers.
# @param ~path Base path for hls URIs.
# @param formats List of specifications for each stream: (name, format).
def output.harbor.hls(~id="",~fallible=false,~on_start={()},~on_stop={()},~encode_metadata=false,
~segment_duration=10.,~segments=15,~segments_per_playlist=10,
~segment_duration=10.,~segments=15,~segments_per_playlist=10,~streams_info=[],
~segment_name=(fun (~position,~extname,stream_name) -> "#{stream_name}_#{position}.#{extname}"),
~start=true,~playlist="stream.m3u8",~port=8000,~path="/",~on_file_change=(fun (~state=_,_) -> ()),
~headers=[("Access-Control-Allow-Origin","*")],formats,s)
Expand All @@ -149,7 +150,7 @@ def output.harbor.hls(~id="",~fallible=false,~on_start={()},~on_stop={()},~encod
output.harbor.hls.base(id=id,fallible=fallible,on_start=on_start,on_stop=on_stop,encode_metadata=encode_metadata,
segment_duration=segment_duration,segments=segments,on_file_change=on_file_change,
segments_per_playlist=segments_per_playlist,segment_name=segment_name,
start=start,port=port,path=path,playlist=playlist,
start=start,port=port,path=path,playlist=playlist,streams_info=streams_info,
serve,formats,s)
end

Expand All @@ -168,12 +169,13 @@ end
# @param ~segments Number of segments to keep.
# @param ~segments_per_playlist Number of segments per playlist.
# @param ~start Automatically start outputting whenever possible. If true, an infallible (normal) output will start outputting as soon as it is created, and a fallible output will (re)start as soon as its source becomes available for streaming.
# @param ~streams_info Additional information about the streams. Should be a list of the form: `[(stream_name, (bandwidth, codec, extname)]`. See RFC 6381 for info about codec. Stream info are required when they cannot be inferred from the encoder.
# @param ~port Port for incoming harbor (https) connections.
# @param ~headers Default response headers.
# @param ~path Base path for hls URIs.
# @param formats List of specifications for each stream: (name, format).
def output.harbor.hls.https(~id="",~fallible=false,~on_start={()},~on_stop={()},~encode_metadata=false,
~segment_duration=10.,~segments=15,~segments_per_playlist=10,
~segment_duration=10.,~segments=15,~segments_per_playlist=10,streams_info=[],
~segment_name=(fun (~position,~extname,stream_name) -> "#{stream_name}_#{position}.#{extname}"),
~port=8000,~path="/",~headers=[("Access-Control-Allow-Origin","*")],
~on_file_change=(fun (~state=_,_) -> ()),~start=true,~playlist="stream.m3u8",formats,s)
Expand All @@ -183,7 +185,7 @@ def output.harbor.hls.https(~id="",~fallible=false,~on_start={()},~on_stop={()},
output.harbor.hls.base(id=id,fallible=fallible,on_start=on_start,on_stop=on_stop,
segment_duration=segment_duration,segments=segments,encode_metadata=encode_metadata,
segments_per_playlist=segments_per_playlist,segment_name=segment_name,
start=start,port=port,path=path,playlist=playlist,
start=start,port=port,path=path,playlist=playlist,streams_info=streams_info,
on_file_change=on_file_change,serve,formats,s)
end
%endif
4 changes: 2 additions & 2 deletions src/encoder.ml
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ let extension = function
| Shine _ -> "mp3"
| Flac _ -> "flac"
| FdkAacEnc _ -> "aac"
| _ -> "audio"
| _ -> raise Not_found

(** Mime types *)
let mime = function
Expand All @@ -135,7 +135,7 @@ let mime = function
| Shine _ -> "audio/mpeg"
| Flac _ -> "audio/flex"
| FdkAacEnc _ -> "audio/aac"
| _ -> "audio"
| _ -> "application/octet-stream"

(** Bitrate estimation in bits per second. *)
let bitrate = function
Expand Down
89 changes: 59 additions & 30 deletions src/outputs/hls_output.ml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ let hls_proto kind =
let sname = Lang.to_string (List.assoc "" p) in
Lang.string (Printf.sprintf "%s_%d.%s" sname position extname))
in
let stream_info_t =
Lang.product_t
Lang.string_t (Lang.tuple_t [Lang.int_t;Lang.string_t;Lang.string_t])
in
(Output.proto @ [
"playlist",
Lang.string_t,
Expand Down Expand Up @@ -93,6 +97,13 @@ let hls_proto kind =
file path. Typical use: upload files to a CDN when done writting (`\"close\"` \
state and remove when `\"deleted\"`.";

"streams_info",
Lang.list_t (stream_info_t),
Some (Lang.list ~t:stream_info_t []),
Some "Additional information about the streams. Should be a list of the form: \
`[(stream_name, (bandwidth, codec, extname)]`. See RFC 6381 for info about \
codec. Stream info are required when they cannot be inferred from the encoder.";

"",
Lang.string_t,
None,
Expand All @@ -118,11 +129,12 @@ type segment =
(** A stream in the HLS (which typically contains many, with different qualities). *)
type hls_stream_desc =
{
hls_name : string; (** name of the stream *)
hls_format : Encoder.format;
hls_encoder : Encoder.encoder;
hls_bandwidth : int option;
hls_codec : string option; (** codec (see RFC 6381) *)
hls_name : string; (** name of the stream *)
hls_format : Encoder.format;
hls_encoder : Encoder.encoder;
hls_bandwidth : int;
hls_codec : string; (** codec (see RFC 6381) *)
hls_extname : string;
mutable hls_oc : (string*out_channel) option; (** currently encoded file *)
}

Expand Down Expand Up @@ -162,6 +174,18 @@ class hls_output p =
if not (Sys.file_exists directory) || not (Sys.is_directory directory) then
raise (Lang_errors.Invalid_value (Lang.assoc "" 1 p, "The target directory does not exist"))
in
let streams_info =
let streams_info = List.assoc "streams_info" p in
let l = Lang.to_list streams_info in
List.map (fun el ->
let (name, specs) = Lang.to_product el in
let (bandwidth, codec, extname) =
match Lang.to_tuple specs with
| bandwidth::codec::extname::[] -> bandwidth, codec, extname
| _ -> assert false
in
(Lang.to_string name, (Lang.to_int bandwidth, Lang.to_string codec, Lang.to_string extname))) l
in
let streams =
let streams = Lang.assoc "" 2 p in
let l = Lang.to_list streams in
Expand All @@ -173,31 +197,46 @@ class hls_output p =
let name, fmt = Lang.to_product s in
let hls_name = Lang.to_string name in
let hls_format = Lang.to_format fmt in
let hls_bandwidth =
try
Some (Encoder.bitrate hls_format)
with Not_found -> None
in
let hls_encoder_factory =
try Encoder.get_factory hls_format
with Not_found -> raise (Lang_errors.Invalid_value (fmt, "Unsupported format"))
in
let hls_encoder =
hls_encoder_factory hls_name Meta_format.empty_metadata
in
let hls_codec =
try
Some (Encoder.iso_base_file_media_file_format hls_format)
with Not_found ->
log#important "Unknown ISO Base Media File Format, none will be output in the playlist.";
None
let hls_bandwidth, hls_codec, hls_extname =
try List.assoc hls_name streams_info with
Not_found ->
let hls_bandwidth =
try
Encoder.bitrate hls_format
with Not_found ->
raise (Lang_errors.Invalid_value (fmt, "Bandwidth cannot be inferred from codec, \
please specify it in `streams_info`"))
in
let hls_codec =
try
Encoder.iso_base_file_media_file_format hls_format
with Not_found ->
raise (Lang_errors.Invalid_value (fmt, "Codec cannot be inferred from codec, \
please specify it in `streams_info`"))
in
let hls_extname =
try
Encoder.extension hls_format
with Not_found ->
raise (Lang_errors.Invalid_value (fmt, "File extension cannot be inferred from codec, \
please specify it in `streams_info`"))
in
hls_bandwidth, hls_codec, hls_extname
in
{
hls_name;
hls_format;
hls_encoder;
hls_bandwidth;
hls_codec;
hls_extname;
hls_oc = None;
}
in
Expand Down Expand Up @@ -272,10 +311,10 @@ class hls_output p =
| `Streaming, _ -> state <- `Streaming

method private segment_names id =
List.map (fun {hls_name;hls_format} ->
List.map (fun {hls_name;hls_extname} ->
let fname =
segment_name ~position:id
~extname:(Encoder.extension hls_format)
~extname:hls_extname
hls_name
in
hls_name,fname) streams
Expand Down Expand Up @@ -422,19 +461,9 @@ class hls_output p =
output_string oc (Printf.sprintf "#EXT-X-VERSION:%d\r\n" x_version);
List.iter (fun s ->
let line =
let bandwidth =
match s.hls_bandwidth with
| Some b -> Printf.sprintf "AVERAGE-BANDWIDTH=%d,BANDWIDTH=%d" b b
| None -> ""
in
let codecs =
match s.hls_codec with
| Some codec -> Printf.sprintf "CODECS=\"%s\"" codec
| None -> ""
in
Printf.sprintf
"#EXT-X-STREAM-INF:%s%s%s\n"
bandwidth (if bandwidth = "" then "" else ",") codecs
"#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=%d,BANDWIDTH=%d,CODECS=%S\r\n"
s.hls_bandwidth s.hls_bandwidth s.hls_codec
in
output_string oc line;
output_string oc (s.hls_name^".m3u8\r\n")
Expand Down

0 comments on commit db8f333

Please sign in to comment.