support multi A/V HLS

This commit is contained in:
jb-alvarado 2022-10-14 10:40:35 +02:00
parent 61f57e2f9e
commit 06b5d6a227
6 changed files with 227 additions and 13 deletions

1
Cargo.lock generated
View File

@ -940,6 +940,7 @@ dependencies = [
"jsonrpc-http-server",
"notify",
"openssl",
"regex",
"reqwest",
"serde",
"serde_json",

View File

@ -46,13 +46,15 @@ Check the [releases](https://github.com/ffplayout/ffplayout/releases/latest) for
- JSON RPC server, for getting infos about current playing and controlling
- [live ingest](/docs/live_ingest.md)
- image source (will loop until out duration is reached)
- extra audio source (experimental) (has priority over audio from video source)
- multiple audio tracks (experimental)
- extra audio source (experimental *) (has priority over audio from video source)
- [multiple audio tracks](/docs/multi_audio.md) (experimental *)
- [custom filter](/docs/custom_filters.md) globally in config, or in playlist for specific clips
- import playlist from text or m3u file, with CLI or frontend
For preview stream, read: [/docs/preview_stream.md](/docs/preview_stream.md)
**\* Experimental functions do not guarantee the same stability, also they can fail in unusual circumstances. The code and configuration options may change in the future.**
## **ffplayout-api (ffpapi)**
ffpapi serves the [frontend](https://github.com/ffplayout/ffplayout-frontend) and it acts as a [REST API](/ffplayout-api/README.md) for controlling the engine, manipulate playlists, add settings etc.

View File

@ -1,7 +1,73 @@
## Multiple Audio Tracks
**\* This is a experimental feature, used it with caution!**
**\* This is an experimental feature and more intended for advanced users. Use it with caution!**
With _ffplayout_ you can output streams with multiple audio tracks, with some limitations:
* **Not all formats support multiple audio tracks. For example _flv/rtmp_ doesn't support it.**
* Not all formats support multiple audio tracks. For example _flv/rtmp_ doesn't support it.
* In your output parameters you need to set the correct mapping.
ffmpeg filter usage and encoding parameters can become very complex, so it can happen that not every combination works out of the box.
To get e better idea of what works, you can examine [engin_cmd](../tests/src/engine_cmd.rs).
If you just output a single video stream with multiple audio tracks, let's say with `srt://` protocol, you only need to set in you config under `processing:` the correct `audio_tracks:` count.
For multiple video resolutions and multiple audio tracks, the parameters could look like:
```YAML
out:
...
mode: stream
output_param: >-
-map 0:v
-map 0:a:0
-map 0:a:1
-c:v libx264
-c:a aac
-ar 44100
-b:a 128k
-flags +global_header
-f mpegts
srt://127.0.0.1:40051
-map 0:v
-map 0:a:0
-map 0:a:1
-s 512x288
-c:v libx264
-c:a aac
-ar 44100
-b:a 128k
-flags +global_header
-f mpegts
srt://127.0.0.1:40052
```
If you need HLS output with multiple resolutions and audio tracks, you can try something like:
```YAML
out:
...
mode: hls
output_param: >-
-filter_complex [0:v]split=2[v1_out][v2];[v2]scale=w=512:h=288[v2_out];[0:a:0]asplit=2[a_0_1][a_0_2];[0:a:1]asplit=2[a_1_1][a_1_2]
-map [v1_out]
-map [a_0_1]
-map [a_1_1]
-c:v libx264
-flags +cgop
-c:a aac
-map [v2_out]
-map [a_0_2]
-map [a_1_2]
-c:v:1 libx264
-flags +cgop
-c:a:1 aac
-f hls
-hls_time 6
-hls_list_size 600
-hls_flags append_list+delete_segments+omit_endlist
-hls_segment_filename /usr/share/ffplayout/public/live/stream_%v-%d.ts
-master_pl_name master.m3u8
-var_stream_map "v:0,a:0,a:1,name:720p v:1,a:2,a:3,name:288p"
/usr/share/ffplayout/public/live/stream_%v.m3u8
```

View File

@ -16,6 +16,7 @@ crossbeam-channel = "0.5"
futures = "0.3"
jsonrpc-http-server = "18.0"
notify = "4.0"
regex = "1"
reqwest = { version = "0.11", features = ["blocking", "json"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

View File

@ -3,6 +3,8 @@ use std::{
process::exit,
};
use regex::Regex;
pub mod arg_parse;
pub use arg_parse::Args;
@ -89,30 +91,47 @@ pub fn get_config(args: Args) -> PlayoutConfig {
///
/// seek for multiple outputs and add mapping for it
pub fn prepare_output_cmd(
prefix: Vec<String>,
mut cmd: Vec<String>,
mut filter: Vec<String>,
config: &PlayoutConfig,
) -> Vec<String> {
let mut output_params = config.out.clone().output_cmd.unwrap();
let mut new_params = vec![];
let params_len = output_params.len();
let mut output_a_map = "[a_out1]".to_string();
let mut output_v_map = "[v_out1]".to_string();
let mut out_count = 1;
let mut cmd = prefix;
let params = config.out.clone().output_cmd.unwrap();
let mut new_params = vec![];
let mut output_filter = String::new();
let mut next_is_filter = false;
let re_audio_map = Regex::new(r"\[0:a:(?P<num>[0-9]+)\]").unwrap();
for (i, p) in params.iter().enumerate() {
// Loop over output parameters
//
// Check if it contains a filtergraph, count its outputs and set correct mapping values.
for (i, p) in output_params.iter().enumerate() {
let mut param = p.clone();
param = param.replace("[0:v]", "[vout0]");
param = param.replace("[0:a]", "[aout0]");
param = re_audio_map.replace_all(&param, "[aout$num]").to_string();
// Skip filter command, to concat existing filters with new ones.
if param != "-filter_complex" {
new_params.push(param.clone());
if next_is_filter {
output_filter = param.clone();
next_is_filter = false;
} else {
new_params.push(param.clone());
}
} else {
next_is_filter = true;
}
if i > 0 && !param.starts_with('-') && !params[i - 1].starts_with('-') && i < params_len - 1
// Check if parameter is a output
if i > 0
&& !param.starts_with('-')
&& !output_params[i - 1].starts_with('-')
&& i < params_len - 1
{
out_count += 1;
let mut a_map = "0:a".to_string();
@ -145,6 +164,10 @@ pub fn prepare_output_cmd(
filter.drain(2..);
cmd.append(&mut filter);
cmd.append(&mut vec_strings!["-map", "[v_out1]", "-map", "[a_out1]"]);
} else if !output_filter.is_empty() && config.out.mode == HLS {
filter[1].push_str(&format!(";{output_filter}"));
filter.drain(2..);
cmd.append(&mut filter);
} else if out_count == 1
&& config.processing.audio_tracks == 1
&& config.out.mode == HLS

View File

@ -839,7 +839,7 @@ fn multi_video_audio_hls() {
"-filter_complex",
"[0:v]split=2[v1_out][v2];[v2]scale=w=512:h=288[v2_out];[0:a]asplit=2[a1][a2]",
"-map",
"v1_out",
"[v1_out]",
"-map",
"[a1]",
"-c:v",
@ -902,7 +902,7 @@ fn multi_video_audio_hls() {
"-filter_complex",
"[0:v:0]scale=1024:576,realtime=speed=1[vout0];[0:a:0]anull[aout0];[vout0]split=2[v1_out][v2];[v2]scale=w=512:h=288[v2_out];[aout0]asplit=2[a1][a2]",
"-map",
"v1_out",
"[v1_out]",
"-map",
"[a1]",
"-c:v",
@ -940,3 +940,124 @@ fn multi_video_audio_hls() {
assert_eq!(enc_cmd, test_cmd);
}
#[test]
fn multi_video_multi_audio_hls() {
let mut config = PlayoutConfig::new(Some("../assets/ffplayout.yml".to_string()));
config.out.mode = HLS;
config.processing.add_logo = false;
config.processing.audio_tracks = 2;
config.text.add_text = false;
config.out.output_cmd = Some(vec_strings![
"-filter_complex",
"[0:v]split=2[v1_out][v2];[v2]scale=w=512:h=288[v2_out];[0:a:0]asplit=2[a_0_1][a_0_2];[0:a:1]asplit=2[a_1_1][a_1_2]",
"-map",
"[v1_out]",
"-map",
"[a_0_1]",
"-map",
"[a_1_1]",
"-c:v",
"libx264",
"-flags",
"+cgop",
"-c:a",
"aac",
"-map",
"[v2_out]",
"-map",
"[a_0_2]",
"-map",
"[a_1_2]",
"-c:v:1",
"libx264",
"-flags",
"+cgop",
"-c:a:1",
"aac",
"-f",
"hls",
"-hls_time",
"6",
"-hls_list_size",
"600",
"-hls_flags",
"append_list+delete_segments+omit_endlist",
"-hls_segment_filename",
"/usr/share/ffplayout/public/live/stream_%v-%d.ts",
"-master_pl_name",
"master.m3u8",
"-var_stream_map",
"v:0,a:0,a:1,name:720p v:1,a:2,a:3,name:288p",
"/usr/share/ffplayout/public/live/stream_%v.m3u8"
]);
let media_obj = Media::new(0, "assets/dual_audio.mp4", true);
let media = gen_source(&config, media_obj, &Arc::new(Mutex::new(vec![])));
let enc_filter = media.filter.unwrap();
let enc_prefix = vec_strings![
"-hide_banner",
"-nostats",
"-v",
"level+error",
"-re",
"-i",
"assets/dual_audio.mp4"
];
let enc_cmd = prepare_output_cmd(enc_prefix, enc_filter, &config);
let test_cmd = vec_strings![
"-hide_banner",
"-nostats",
"-v",
"level+error",
"-re",
"-i",
"assets/dual_audio.mp4",
"-filter_complex",
"[0:v:0]scale=1024:576,realtime=speed=1[vout0];[0:a:0]anull[aout0];[0:a:1]anull[aout1];[vout0]split=2[v1_out][v2];[v2]scale=w=512:h=288[v2_out];[aout0]asplit=2[a_0_1][a_0_2];[aout1]asplit=2[a_1_1][a_1_2]",
"-map",
"[v1_out]",
"-map",
"[a_0_1]",
"-map",
"[a_1_1]",
"-c:v",
"libx264",
"-flags",
"+cgop",
"-c:a",
"aac",
"-map",
"[v2_out]",
"-map",
"[a_0_2]",
"-map",
"[a_1_2]",
"-c:v:1",
"libx264",
"-flags",
"+cgop",
"-c:a:1",
"aac",
"-f",
"hls",
"-hls_time",
"6",
"-hls_list_size",
"600",
"-hls_flags",
"append_list+delete_segments+omit_endlist",
"-hls_segment_filename",
"/usr/share/ffplayout/public/live/stream_%v-%d.ts",
"-master_pl_name",
"master.m3u8",
"-var_stream_map",
"v:0,a:0,a:2,name:720p v:1,a:1,a:3,name:288p",
"/usr/share/ffplayout/public/live/stream_%v.m3u8"
];
assert_eq!(enc_cmd, test_cmd);
}