add OutputMode enum, more work on multi audio tracks, more tests

This commit is contained in:
jb-alvarado 2022-10-13 17:34:14 +02:00
parent 8be260ae20
commit 713233ff1a
15 changed files with 942 additions and 151 deletions

View File

@ -122,7 +122,7 @@ out:
has the options 'desktop', 'hls', 'null', 'stream'. Use 'stream' and adjust
'output_param:' settings when you want to stream to a rtmp/rtsp/srt/... server.
In production don't serve hls playlist with ffpapi, use nginx or another web server!
mode: desktop
mode: hls
output_param: >-
-c:v libx264
-crf 23

View File

@ -21,6 +21,10 @@ Using live ingest to inject a live stream.
The different output modes.
### **[Multi Audio Tracks](/docs/multi_audio.md)**
Output multiple audio tracks.
### **[Custom Filter](/docs/custom_filters.md)**
Apply self defined audio/video filters.

7
docs/multi_audio.md Normal file
View File

@ -0,0 +1,7 @@
## Multiple Audio Tracks
**\* This is a experimental feature, used 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.**
* In your output parameters you need to set the correct mapping.

View File

@ -20,7 +20,7 @@ use ffplayout::{
use ffplayout_lib::utils::{
generate_playlist, import::import_file, init_logging, send_mail, validate_ffmpeg,
PlayerControl, PlayoutStatus, ProcessControl,
OutputMode::*, PlayerControl, PlayoutStatus, ProcessControl,
};
#[cfg(debug_assertions)]
@ -142,9 +142,9 @@ fn main() {
status_file(&config.general.stat_file, &playout_stat);
match config.out.mode.to_lowercase().as_str() {
match config.out.mode {
// write files/playlist to HLS m3u8 playlist
"hls" => write_hls(&config, play_control, playout_stat, proc_control),
HLS => write_hls(&config, play_control, playout_stat, proc_control),
// play on desktop or stream to a remote target
_ => player(&config, play_control, playout_stat, proc_control),
}

View File

@ -80,12 +80,7 @@ fn ingest_to_hls_server(
}
}
let server_cmd = prepare_output_cmd(
server_prefix.clone(),
filters,
config.out.clone().output_cmd.unwrap(),
"hls",
);
let server_cmd = prepare_output_cmd(server_prefix.clone(), filters, &config);
debug!(
"Server CMD: <bright-blue>\"ffmpeg {}\"</>",
@ -201,12 +196,7 @@ pub fn write_hls(
let mut enc_prefix = vec_strings!["-hide_banner", "-nostats", "-v", &ff_log_format];
enc_prefix.append(&mut cmd);
let enc_filter = node.filter.unwrap();
let enc_cmd = prepare_output_cmd(
enc_prefix,
enc_filter,
config.out.clone().output_cmd.unwrap(),
&config.out.mode,
);
let enc_cmd = prepare_output_cmd(enc_prefix, enc_filter, config);
debug!(
"HLS writer CMD: <bright-blue>\"ffmpeg {}\"</>",

View File

@ -18,8 +18,8 @@ pub use hls::write_hls;
use crate::input::{ingest_server, source_generator};
use ffplayout_lib::utils::{
sec_to_time, stderr_reader, Decoder, PlayerControl, PlayoutConfig, PlayoutStatus,
ProcessControl,
sec_to_time, stderr_reader, Decoder, OutputMode::*, PlayerControl, PlayoutConfig,
PlayoutStatus, ProcessControl,
};
use ffplayout_lib::vec_strings;
@ -54,10 +54,10 @@ pub fn player(
);
// get ffmpeg output instance
let mut enc_proc = match config.out.mode.as_str() {
"desktop" => desktop::output(config, &ff_log_format),
"null" => null::output(config, &ff_log_format),
"stream" => stream::output(config, &ff_log_format),
let mut enc_proc = match config.out.mode {
Desktop => desktop::output(config, &ff_log_format),
Null => null::output(config, &ff_log_format),
Stream => stream::output(config, &ff_log_format),
_ => panic!("Output mode doesn't exists!"),
};

View File

@ -47,7 +47,7 @@ pub fn output(config: &PlayoutConfig, log_format: &str) -> process::Child {
enc_cmd.append(&mut output_cmd);
let enc_cmd = prepare_output_cmd(enc_prefix, enc_filter, enc_cmd, &config.out.mode);
let enc_cmd = prepare_output_cmd(enc_prefix, enc_filter, config);
debug!(
"Encoder CMD: <bright-blue>\"ffmpeg {}\"</>",

View File

@ -13,7 +13,7 @@ use simplelog::*;
use ffplayout_lib::utils::{
get_delta, get_filter_from_json, get_sec, sec_to_time, write_status, Ingest, Media,
PlayerControl, PlayoutConfig, PlayoutStatus, ProcessControl,
OutputMode::*, PlayerControl, PlayoutConfig, PlayoutStatus, ProcessControl,
};
use zmq_cmd::zmq_send;
@ -90,7 +90,7 @@ pub fn json_rpc_server(
let mut clips_filter = playout_stat.chain.lock().unwrap();
*clips_filter = vec![filter.clone()];
if config.out.mode == "hls" {
if config.out.mode == HLS {
if proc.server_is_running.load(Ordering::SeqCst) {
let filter_server = format!(
"Parsed_drawtext_{} reinit {filter}",
@ -108,7 +108,7 @@ pub fn json_rpc_server(
}
}
if config.out.mode != "hls" || !proc.server_is_running.load(Ordering::SeqCst) {
if config.out.mode != HLS || !proc.server_is_running.load(Ordering::SeqCst) {
let filter_stream = format!(
"Parsed_drawtext_{} reinit {filter}",
playout_stat.drawtext_stream_index.load(Ordering::SeqCst)

View File

@ -1,5 +1,7 @@
use clap::Parser;
use ffplayout_lib::utils::OutputMode;
#[derive(Parser, Debug, Clone)]
#[clap(version,
about = "ffplayout, Rust based 24/7 playout solution.",
@ -64,8 +66,8 @@ pub struct Args {
#[clap(short, long, help = "Loop playlist infinitely")]
pub infinit: bool,
#[clap(short, long, help = "Set output mode: desktop, hls, stream")]
pub output: Option<String>,
#[clap(short, long, help = "Set output mode: desktop, hls, null, stream")]
pub output: Option<OutputMode>,
#[clap(short, long, help = "Set audio volume")]
pub volume: Option<f64>,

View File

@ -7,7 +7,7 @@ pub mod arg_parse;
pub use arg_parse::Args;
use ffplayout_lib::{
utils::{time_to_sec, PlayoutConfig},
utils::{time_to_sec, OutputMode::*, PlayoutConfig},
vec_strings,
};
@ -91,69 +91,99 @@ pub fn get_config(args: Args) -> PlayoutConfig {
pub fn prepare_output_cmd(
prefix: Vec<String>,
mut filter: Vec<String>,
params: Vec<String>,
mode: &str,
config: &PlayoutConfig,
) -> Vec<String> {
let params_len = params.len();
let mut output_params = params.clone();
let mut output_params = config.out.clone().output_cmd.unwrap();
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 output_count = 1;
let mut out_count = 1;
let mut cmd = prefix;
let params = config.out.clone().output_cmd.unwrap();
let mut new_params = vec![];
if !filter.is_empty() {
output_params.clear();
for (i, p) in params.iter().enumerate() {
let mut param = p.clone();
for (i, p) in params.iter().enumerate() {
let mut param = p.clone();
param = param.replace("[0:v]", "[vout0]");
param = param.replace("[0:a]", "[aout0]");
param = param.replace("[0:v]", "[vout0]");
param = param.replace("[0:a]", "[aout0]");
if param != "-filter_complex" {
output_params.push(param.clone());
}
if i > 0
&& !param.starts_with('-')
&& !params[i - 1].starts_with('-')
&& i < params_len - 1
{
output_count += 1;
let mut a_map = "0:a".to_string();
let v_map = format!("[v_out{output_count}]");
output_v_map.push_str(&v_map);
if mode == "hls" {
a_map = format!("[a_out{output_count}]");
}
output_a_map.push_str(&a_map);
let mut map = vec_strings!["-map", v_map, "-map", a_map];
output_params.append(&mut map);
}
if param != "-filter_complex" {
new_params.push(param.clone());
}
if output_count > 1 && mode == "hls" {
filter[1].push_str(&format!(";[vout0]split={output_count}{output_v_map}"));
filter[1].push_str(&format!(";[aout0]asplit={output_count}{output_a_map}"));
if i > 0 && !param.starts_with('-') && !params[i - 1].starts_with('-') && i < params_len - 1
{
out_count += 1;
let mut a_map = "0:a".to_string();
let v_map = format!("[v_out{out_count}]");
output_v_map.push_str(&v_map);
if config.out.mode == HLS {
a_map = format!("[a_out{out_count}]");
}
output_a_map.push_str(&a_map);
if !output_params.contains(&"-map".to_string()) {
let mut map = vec_strings!["-map", v_map, "-map", a_map];
new_params.append(&mut map);
}
}
}
if !filter.is_empty() {
output_params = new_params;
// Process A/V mapping
//
// Check if there is multiple outputs, and/or multiple audio tracks
// and add the correct mapping for it.
if out_count > 1 && config.processing.audio_tracks == 1 && config.out.mode == HLS {
filter[1].push_str(&format!(";[vout0]split={out_count}{output_v_map}"));
filter[1].push_str(&format!(";[aout0]asplit={out_count}{output_a_map}"));
filter.drain(2..);
cmd.append(&mut filter);
cmd.append(&mut vec_strings!["-map", "[v_out1]", "-map", "[a_out1]"]);
} else if output_count == 1 && mode == "hls" && output_params[0].contains("split") {
} else if out_count == 1
&& config.processing.audio_tracks == 1
&& config.out.mode == HLS
&& output_params[0].contains("split")
{
let out_filter = output_params.remove(0);
filter[1].push_str(&format!(";{out_filter}"));
filter.drain(2..);
cmd.append(&mut filter);
} else if output_count > 1 && mode == "stream" {
filter[1].push_str(&format!(",split={output_count}{output_v_map}"));
} else if out_count > 1 && config.processing.audio_tracks == 1 && config.out.mode == Stream
{
filter[1].push_str(&format!(",split={out_count}{output_v_map}"));
cmd.append(&mut filter);
cmd.append(&mut vec_strings!["-map", "[v_out1]", "-map", "0:a"]);
} else if config.processing.audio_tracks > 1 && config.out.mode == Stream {
filter[1].push_str("[v_out1]");
cmd.append(&mut filter);
output_params = output_params
.iter()
.map(|p| p.replace("0:v", "[v_out1]"))
.collect();
if out_count == 1 {
cmd.append(&mut vec_strings!["-map", "[v_out1]"]);
for i in 0..config.processing.audio_tracks {
cmd.append(&mut vec_strings!["-map", format!("0:a:{i}")]);
}
}
} else {
cmd.append(&mut filter);
}
} else if out_count == 1 && config.processing.audio_tracks > 1 && config.out.mode == Stream {
cmd.append(&mut vec_strings!["-map", "0:v"]);
for i in 0..config.processing.audio_tracks {
cmd.append(&mut vec_strings!["-map", format!("0:a:{i}")]);
}
}
cmd.append(&mut output_params);

@ -1 +1 @@
Subproject commit 0994fd00d16354b3d3059e8e3fae2ed256264460
Subproject commit 0060d40b597a8e595a2dbef6e493821be2e7d12f

View File

@ -12,7 +12,9 @@ pub mod v_drawtext;
// get_delta
use self::custom_filter::custom_filter;
use crate::utils::{fps_calc, get_delta, is_close, Media, MediaProbe, PlayoutConfig};
use crate::utils::{
fps_calc, get_delta, is_close, Media, MediaProbe, OutputMode::*, PlayoutConfig,
};
#[derive(Clone, Debug, Copy, PartialEq)]
enum FilterType {
@ -283,9 +285,7 @@ fn add_text(
config: &PlayoutConfig,
filter_chain: &Arc<Mutex<Vec<String>>>,
) {
if config.text.add_text
&& (config.text.text_from_filename || config.out.mode.to_lowercase() == "hls")
{
if config.text.add_text && (config.text.text_from_filename || config.out.mode == HLS) {
let filter = v_drawtext::filter_node(config, Some(node), filter_chain);
chain.add_filter(&filter, 0, Video);
@ -352,7 +352,7 @@ fn aspect_calc(aspect_string: &Option<String>, config: &PlayoutConfig) -> f64 {
/// This realtime filter is important for HLS output to stay in sync.
fn realtime(node: &mut Media, chain: &mut Filters, config: &PlayoutConfig) {
if config.general.generate.is_none() && &config.out.mode.to_lowercase() == "hls" {
if config.general.generate.is_none() && config.out.mode == HLS {
let mut speed_filter = "realtime=speed=1".to_string();
if let Some(begin) = &node.begin {

View File

@ -3,6 +3,7 @@ use std::{
fs::File,
path::{Path, PathBuf},
process,
str::FromStr,
};
use serde::{Deserialize, Serialize};
@ -24,6 +25,29 @@ pub const FFMPEG_IGNORE_ERRORS: [&str; 3] = [
"Warning MVs not available",
];
#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum OutputMode {
Desktop,
HLS,
Null,
Stream,
}
impl FromStr for OutputMode {
type Err = String;
fn from_str(input: &str) -> Result<Self, Self::Err> {
match input {
"desktop" => Ok(Self::Desktop),
"hls" => Ok(Self::HLS),
"null" => Ok(Self::Null),
"stream" => Ok(Self::Stream),
_ => Err(String::new()),
}
}
}
/// Global Config
///
/// This we init ones, when ffplayout is starting and use them globally in the hole program.
@ -173,7 +197,7 @@ pub struct Text {
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Out {
pub help_text: String,
pub mode: String,
pub mode: OutputMode,
pub output_param: String,
#[serde(skip_serializing, skip_deserializing)]

View File

@ -35,7 +35,9 @@ mod logging;
mod windows;
pub use config::{
self as playout_config, PlayoutConfig, DUMMY_LEN, FFMPEG_IGNORE_ERRORS, IMAGE_FORMAT,
self as playout_config,
OutputMode::{self, *},
PlayoutConfig, DUMMY_LEN, FFMPEG_IGNORE_ERRORS, IMAGE_FORMAT,
};
pub use controller::{PlayerControl, PlayoutStatus, ProcessControl, ProcessUnit::*};
pub use generator::generate_playlist;
@ -597,7 +599,7 @@ pub fn include_file(config: PlayoutConfig, file_path: &Path) -> bool {
}
}
if config.out.mode.to_lowercase() == "hls" {
if config.out.mode == HLS {
if let Some(ts_path) = config
.out
.output_cmd
@ -737,7 +739,7 @@ pub fn validate_ffmpeg(config: &PlayoutConfig) -> Result<(), String> {
is_in_system("ffmpeg")?;
is_in_system("ffprobe")?;
if config.out.mode == "desktop" {
if config.out.mode == Desktop {
is_in_system("ffplay")?;
}

View File

@ -1,6 +1,7 @@
use ffplayout::{input::playlist::gen_source, utils::prepare_output_cmd};
use ffplayout_lib::{
utils::{Media, PlayoutConfig},
filter::v_drawtext,
utils::{Media, OutputMode::*, PlayoutConfig},
vec_strings,
};
use std::sync::{Arc, Mutex};
@ -8,7 +9,7 @@ use std::sync::{Arc, Mutex};
#[test]
fn video_audio_input() {
let mut config = PlayoutConfig::new(Some("../assets/ffplayout.yml".to_string()));
config.out.mode = "stream".to_string();
config.out.mode = Stream;
config.processing.logo = "../assets/logo.png".to_string();
let media_obj = Media::new(0, "assets/with_audio.mp4", true);
@ -32,7 +33,7 @@ fn video_audio_input() {
#[test]
fn dual_audio_aevalsrc_input() {
let mut config = PlayoutConfig::new(Some("../assets/ffplayout.yml".to_string()));
config.out.mode = "stream".to_string();
config.out.mode = Stream;
config.processing.audio_tracks = 2;
config.processing.add_logo = false;
@ -59,7 +60,7 @@ fn dual_audio_aevalsrc_input() {
#[test]
fn dual_audio_input() {
let mut config = PlayoutConfig::new(Some("../assets/ffplayout.yml".to_string()));
config.out.mode = "stream".to_string();
config.out.mode = Stream;
config.processing.audio_tracks = 2;
config.processing.add_logo = false;
@ -82,72 +83,9 @@ fn dual_audio_input() {
}
#[test]
fn test_prepare_output_cmd() {
let enc_prefix = vec_strings![
"-hide_banner",
"-nostats",
"-v",
"level+error",
"-re",
"-i",
"pipe:0"
];
let filter = vec_strings![
"-filter_complex",
"[0:v]null,zmq=b=tcp\\\\://'127.0.0.1\\:5555',drawtext=text=''"
];
let params = vec_strings![
"-c:v",
"libx264",
"-flags",
"+global_header",
"-f",
"flv",
"rtmp://localhost/live/stream",
"-s",
"512x288",
"-c:v",
"libx264",
"-flags",
"+global_header",
"-f",
"flv",
"rtmp://localhost:1937/live/stream"
];
let mut t1_params = enc_prefix.clone();
t1_params.append(&mut params.clone());
let cmd_two_outs =
prepare_output_cmd(enc_prefix.clone(), vec_strings![], params.clone(), "stream");
assert_eq!(cmd_two_outs, t1_params);
let mut test_cmd = enc_prefix.clone();
let mut test_params = params.clone();
let mut t2_filter = filter.clone();
t2_filter[1].push_str(",split=2[v_out1][v_out2]");
test_cmd.append(&mut t2_filter);
test_params.insert(0, "-map".to_string());
test_params.insert(1, "[v_out1]".to_string());
test_params.insert(2, "-map".to_string());
test_params.insert(3, "0:a".to_string());
test_params.insert(11, "-map".to_string());
test_params.insert(12, "[v_out2]".to_string());
test_params.insert(13, "-map".to_string());
test_params.insert(14, "0:a".to_string());
test_cmd.append(&mut test_params);
let cmd_two_outs_with_filter = prepare_output_cmd(enc_prefix, filter, params, "stream");
assert_eq!(cmd_two_outs_with_filter, test_cmd);
}
#[test]
fn video_audio_output() {
fn video_audio_stream() {
let mut config = PlayoutConfig::new(Some("../assets/ffplayout.yml".to_string()));
config.out.mode = "stream".to_string();
config.out.mode = Stream;
config.processing.add_logo = false;
config.out.output_cmd = Some(vec_strings![
"-c:v",
@ -181,7 +119,7 @@ fn video_audio_output() {
enc_cmd.append(&mut output_cmd);
let enc_cmd = prepare_output_cmd(enc_prefix, enc_filter, enc_cmd, &config.out.mode);
let enc_cmd = prepare_output_cmd(enc_prefix, enc_filter, &config);
let test_cmd = vec_strings![
"-hide_banner",
@ -208,3 +146,797 @@ fn video_audio_output() {
assert_eq!(enc_cmd, test_cmd);
}
#[test]
fn video_dual_audio_stream() {
let mut config = PlayoutConfig::new(Some("../assets/ffplayout.yml".to_string()));
config.out.mode = Stream;
config.processing.add_logo = false;
config.processing.audio_tracks = 2;
config.out.output_cmd = Some(vec_strings![
"-c:v",
"libx264",
"-c:a",
"aac",
"-ar",
"44100",
"-b:a",
"128k",
"-flags",
"+global_header",
"-f",
"mpegts",
"srt://127.0.0.1:40051"
]);
let mut enc_cmd = vec![];
let enc_filter = vec![];
let mut output_cmd = config.out.output_cmd.as_ref().unwrap().clone();
let enc_prefix = vec_strings![
"-hide_banner",
"-nostats",
"-v",
"level+error",
"-re",
"-i",
"pipe:0"
];
enc_cmd.append(&mut output_cmd);
let enc_cmd = prepare_output_cmd(enc_prefix, enc_filter, &config);
let test_cmd = vec_strings![
"-hide_banner",
"-nostats",
"-v",
"level+error",
"-re",
"-i",
"pipe:0",
"-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"
];
assert_eq!(enc_cmd, test_cmd);
}
#[test]
fn video_dual_audio_filter_stream() {
let mut config = PlayoutConfig::new(Some("../assets/ffplayout.yml".to_string()));
config.out.mode = Stream;
config.processing.add_logo = false;
config.processing.audio_tracks = 2;
config.out.output_cmd = Some(vec_strings![
"-c:v",
"libx264",
"-c:a",
"aac",
"-ar",
"44100",
"-b:a",
"128k",
"-flags",
"+global_header",
"-f",
"mpegts",
"srt://127.0.0.1:40051"
]);
let mut enc_cmd = vec![];
let enc_prefix = vec_strings![
"-hide_banner",
"-nostats",
"-v",
"level+error",
"-re",
"-i",
"pipe:0"
];
let socket = config
.text
.zmq_stream_socket
.clone()
.unwrap()
.replace(':', "\\:");
let mut filter = "[0:v]null,".to_string();
filter.push_str(v_drawtext::filter_node(&config, None, &Arc::new(Mutex::new(vec![]))).as_str());
let enc_filter = vec!["-filter_complex".to_string(), filter];
let mut output_cmd = config.out.output_cmd.as_ref().unwrap().clone();
enc_cmd.append(&mut output_cmd);
let enc_cmd = prepare_output_cmd(enc_prefix, enc_filter, &config);
let test_cmd = vec_strings![
"-hide_banner",
"-nostats",
"-v",
"level+error",
"-re",
"-i",
"pipe:0",
"-filter_complex",
format!("[0:v]null,zmq=b=tcp\\\\://'{socket}',drawtext=text=''[v_out1]"),
"-map",
"[v_out1]",
"-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"
];
assert_eq!(enc_cmd, test_cmd);
}
#[test]
fn video_audio_multi_stream() {
let mut config = PlayoutConfig::new(Some("../assets/ffplayout.yml".to_string()));
config.out.mode = Stream;
config.processing.add_logo = false;
config.out.output_cmd = Some(vec_strings![
"-c:v",
"libx264",
"-c:a",
"aac",
"-ar",
"44100",
"-b:a",
"128k",
"-flags",
"+global_header",
"-f",
"flv",
"rtmp://localhost/live/stream",
"-s",
"512x288",
"-c:v",
"libx264",
"-c:a",
"aac",
"-ar",
"44100",
"-b:a",
"128k",
"-flags",
"+global_header",
"-f",
"flv",
"rtmp://localhost:1936/live/stream"
]);
let mut enc_cmd = vec![];
let enc_filter = vec![];
let mut output_cmd = config.out.output_cmd.as_ref().unwrap().clone();
let enc_prefix = vec_strings![
"-hide_banner",
"-nostats",
"-v",
"level+error",
"-re",
"-i",
"pipe:0"
];
enc_cmd.append(&mut output_cmd);
let enc_cmd = prepare_output_cmd(enc_prefix, enc_filter, &config);
let test_cmd = vec_strings![
"-hide_banner",
"-nostats",
"-v",
"level+error",
"-re",
"-i",
"pipe:0",
"-c:v",
"libx264",
"-c:a",
"aac",
"-ar",
"44100",
"-b:a",
"128k",
"-flags",
"+global_header",
"-f",
"flv",
"rtmp://localhost/live/stream",
"-s",
"512x288",
"-c:v",
"libx264",
"-c:a",
"aac",
"-ar",
"44100",
"-b:a",
"128k",
"-flags",
"+global_header",
"-f",
"flv",
"rtmp://localhost:1936/live/stream"
];
assert_eq!(enc_cmd, test_cmd);
}
#[test]
fn video_dual_audio_multi_stream() {
let mut config = PlayoutConfig::new(Some("../assets/ffplayout.yml".to_string()));
config.out.mode = Stream;
config.processing.add_logo = false;
config.processing.audio_tracks = 2;
config.out.output_cmd = Some(vec_strings![
"-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"
]);
let mut enc_cmd = vec![];
let enc_filter = vec![];
let mut output_cmd = config.out.output_cmd.as_ref().unwrap().clone();
let enc_prefix = vec_strings![
"-hide_banner",
"-nostats",
"-v",
"level+error",
"-re",
"-i",
"pipe:0"
];
enc_cmd.append(&mut output_cmd);
let enc_cmd = prepare_output_cmd(enc_prefix, enc_filter, &config);
let test_cmd = vec_strings![
"-hide_banner",
"-nostats",
"-v",
"level+error",
"-re",
"-i",
"pipe:0",
"-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"
];
assert_eq!(enc_cmd, test_cmd);
}
#[test]
fn video_dual_audio_multi_filter_stream() {
let mut config = PlayoutConfig::new(Some("../assets/ffplayout.yml".to_string()));
config.out.mode = Stream;
config.processing.add_logo = false;
config.processing.audio_tracks = 2;
config.out.output_cmd = Some(vec_strings![
"-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"
]);
let mut enc_cmd = vec![];
let enc_prefix = vec_strings![
"-hide_banner",
"-nostats",
"-v",
"level+error",
"-re",
"-i",
"pipe:0"
];
let socket = config
.text
.zmq_stream_socket
.clone()
.unwrap()
.replace(':', "\\:");
let mut filter = "[0:v]null,".to_string();
filter.push_str(v_drawtext::filter_node(&config, None, &Arc::new(Mutex::new(vec![]))).as_str());
let enc_filter = vec!["-filter_complex".to_string(), filter];
let mut output_cmd = config.out.output_cmd.as_ref().unwrap().clone();
enc_cmd.append(&mut output_cmd);
let enc_cmd = prepare_output_cmd(enc_prefix, enc_filter, &config);
let test_cmd = vec_strings![
"-hide_banner",
"-nostats",
"-v",
"level+error",
"-re",
"-i",
"pipe:0",
"-filter_complex",
format!("[0:v]null,zmq=b=tcp\\\\://'{socket}',drawtext=text=''[v_out1]"),
"-map",
"[v_out1]",
"-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",
"[v_out1]",
"-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"
];
// println!("{enc_cmd:?}");
assert_eq!(enc_cmd, test_cmd);
}
#[test]
fn video_audio_hls() {
let mut config = PlayoutConfig::new(Some("../assets/ffplayout.yml".to_string()));
config.out.mode = HLS;
config.processing.add_logo = false;
config.text.add_text = false;
config.out.output_cmd = Some(vec_strings![
"-c:v",
"libx264",
"-c:a",
"aac",
"-ar",
"44100",
"-b:a",
"128k",
"-flags",
"+cgop",
"-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-%d.ts",
"/usr/share/ffplayout/public/live/stream.m3u8"
]);
let media_obj = Media::new(0, "assets/with_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/with_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/with_audio.mp4",
"-filter_complex",
"[0:v:0]scale=1024:576,realtime=speed=1[vout0];[0:a:0]anull[aout0]",
"-map",
"[vout0]",
"-map",
"[aout0]",
"-c:v",
"libx264",
"-c:a",
"aac",
"-ar",
"44100",
"-b:a",
"128k",
"-flags",
"+cgop",
"-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-%d.ts",
"/usr/share/ffplayout/public/live/stream.m3u8"
];
assert_eq!(enc_cmd, test_cmd);
}
#[test]
fn 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![
"-c:v",
"libx264",
"-c:a",
"aac",
"-ar",
"44100",
"-b:a",
"128k",
"-flags",
"+cgop",
"-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-%d.ts",
"/usr/share/ffplayout/public/live/stream.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]",
"-map",
"[vout0]",
"-map",
"[aout0]",
"-map",
"[aout1]",
"-c:v",
"libx264",
"-c:a",
"aac",
"-ar",
"44100",
"-b:a",
"128k",
"-flags",
"+cgop",
"-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-%d.ts",
"/usr/share/ffplayout/public/live/stream.m3u8"
];
assert_eq!(enc_cmd, test_cmd);
}
#[test]
fn multi_video_audio_hls() {
let mut config = PlayoutConfig::new(Some("../assets/ffplayout.yml".to_string()));
config.out.mode = HLS;
config.processing.add_logo = false;
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]asplit=2[a1][a2]",
"-map",
"v1_out",
"-map",
"[a1]",
"-c:v",
"libx264",
"-flags",
"+cgop",
"-c:a",
"aac",
"-map",
"[v2_out]",
"-map",
"[a2]",
"-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,name:720p v:1,a:1,name:288p",
"/usr/share/ffplayout/public/live/stream_%v.m3u8"
]);
let media_obj = Media::new(0, "assets/with_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/with_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/with_audio.mp4",
"-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",
"-map",
"[a1]",
"-c:v",
"libx264",
"-flags",
"+cgop",
"-c:a",
"aac",
"-map",
"[v2_out]",
"-map",
"[a2]",
"-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,name:720p v:1,a:1,name:288p",
"/usr/share/ffplayout/public/live/stream_%v.m3u8"
];
assert_eq!(enc_cmd, test_cmd);
}