support multi A/V HLS
This commit is contained in:
parent
61f57e2f9e
commit
06b5d6a227
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -940,6 +940,7 @@ dependencies = [
|
||||
"jsonrpc-http-server",
|
||||
"notify",
|
||||
"openssl",
|
||||
"regex",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
```
|
||||
|
@ -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"
|
||||
|
@ -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(¶m, "[aout$num]").to_string();
|
||||
|
||||
// Skip filter command, to concat existing filters with new ones.
|
||||
if param != "-filter_complex" {
|
||||
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
|
||||
|
@ -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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user