Audio codec libfdk_aac
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
kieran 2024-03-27 16:33:43 +00:00
parent 298bfd994b
commit 9c4969cf95
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
16 changed files with 549 additions and 127 deletions

320
Cargo.lock generated
View File

@ -140,6 +140,12 @@ dependencies = [
"generic-array", "generic-array",
] ]
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.5.0" version = "1.5.0"
@ -277,6 +283,12 @@ dependencies = [
"cipher", "cipher",
] ]
[[package]]
name = "data-encoding"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5"
[[package]] [[package]]
name = "derive_more" name = "derive_more"
version = "0.99.17" version = "0.99.17"
@ -316,6 +328,15 @@ version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
[[package]]
name = "encoding_rs"
version = "0.8.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "env_logger" name = "env_logger"
version = "0.10.2" version = "0.10.2"
@ -474,6 +495,25 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "h2"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fbd2820c5e49886948654ab546d0688ff24530286bdcf8fca3cefb16d4618eb"
dependencies = [
"bytes",
"fnv",
"futures-core",
"futures-sink",
"futures-util",
"http",
"indexmap",
"slab",
"tokio",
"tokio-util",
"tracing",
]
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.13.2" version = "0.13.2"
@ -486,6 +526,30 @@ version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
[[package]]
name = "headers"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270"
dependencies = [
"base64",
"bytes",
"headers-core",
"http",
"httpdate",
"mime",
"sha1",
]
[[package]]
name = "headers-core"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429"
dependencies = [
"http",
]
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
version = "0.3.9" version = "0.3.9"
@ -509,9 +573,9 @@ dependencies = [
[[package]] [[package]]
name = "http" name = "http"
version = "1.1.0" version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1"
dependencies = [ dependencies = [
"bytes", "bytes",
"fnv", "fnv",
@ -520,14 +584,27 @@ dependencies = [
[[package]] [[package]]
name = "http-body" name = "http-body"
version = "1.0.0" version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
dependencies = [ dependencies = [
"bytes", "bytes",
"http", "http",
"pin-project-lite",
] ]
[[package]]
name = "httparse"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
[[package]]
name = "httpdate"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]] [[package]]
name = "humantime" name = "humantime"
version = "2.1.0" version = "2.1.0"
@ -536,14 +613,26 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]] [[package]]
name = "hyper" name = "hyper"
version = "1.2.0" version = "0.14.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "186548d73ac615b32a73aafe38fb4f56c0d340e110e5a200bcadbaf2e199263a" checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-channel",
"futures-core",
"futures-util",
"h2",
"http", "http",
"http-body", "http-body",
"httparse",
"httpdate",
"itoa",
"pin-project-lite",
"socket2",
"tokio", "tokio",
"tower-service",
"tracing",
"want",
] ]
[[package]] [[package]]
@ -667,6 +756,22 @@ version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
[[package]]
name = "mime"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "mime_guess"
version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef"
dependencies = [
"mime",
"unicase",
]
[[package]] [[package]]
name = "minimal-lexical" name = "minimal-lexical"
version = "0.2.1" version = "0.2.1"
@ -693,6 +798,24 @@ dependencies = [
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
[[package]]
name = "multer"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2"
dependencies = [
"bytes",
"encoding_rs",
"futures-util",
"http",
"httparse",
"log",
"memchr",
"mime",
"spin",
"version_check",
]
[[package]] [[package]]
name = "nom" name = "nom"
version = "7.1.3" version = "7.1.3"
@ -819,6 +942,26 @@ dependencies = [
"sha2", "sha2",
] ]
[[package]]
name = "pin-project"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.52",
]
[[package]] [[package]]
name = "pin-project-lite" name = "pin-project-lite"
version = "0.2.13" version = "0.2.13"
@ -979,12 +1122,27 @@ dependencies = [
"semver", "semver",
] ]
[[package]]
name = "rustls-pemfile"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
dependencies = [
"base64",
]
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.17" version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
[[package]]
name = "scoped-tls"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
[[package]] [[package]]
name = "semver" name = "semver"
version = "1.0.22" version = "1.0.22"
@ -1031,6 +1189,18 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
dependencies = [
"form_urlencoded",
"itoa",
"ryu",
"serde",
]
[[package]] [[package]]
name = "sha-1" name = "sha-1"
version = "0.10.1" version = "0.10.1"
@ -1042,6 +1212,17 @@ dependencies = [
"digest", "digest",
] ]
[[package]]
name = "sha1"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]] [[package]]
name = "sha2" name = "sha2"
version = "0.10.8" version = "0.10.8"
@ -1078,6 +1259,12 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
[[package]] [[package]]
name = "srt-protocol" name = "srt-protocol"
version = "0.4.3" version = "0.4.3"
@ -1132,7 +1319,6 @@ dependencies = [
"config", "config",
"ffmpeg-sys-next", "ffmpeg-sys-next",
"futures-util", "futures-util",
"hyper",
"itertools", "itertools",
"libc", "libc",
"log", "log",
@ -1144,6 +1330,7 @@ dependencies = [
"tokio-stream", "tokio-stream",
"url", "url",
"uuid", "uuid",
"warp",
] ]
[[package]] [[package]]
@ -1282,6 +1469,18 @@ dependencies = [
"tokio-util", "tokio-util",
] ]
[[package]]
name = "tokio-tungstenite"
version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c"
dependencies = [
"futures-util",
"log",
"tokio",
"tungstenite",
]
[[package]] [[package]]
name = "tokio-util" name = "tokio-util"
version = "0.7.10" version = "0.7.10"
@ -1293,6 +1492,7 @@ dependencies = [
"futures-sink", "futures-sink",
"pin-project-lite", "pin-project-lite",
"tokio", "tokio",
"tracing",
] ]
[[package]] [[package]]
@ -1329,6 +1529,57 @@ dependencies = [
"winnow", "winnow",
] ]
[[package]]
name = "tower-service"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
[[package]]
name = "tracing"
version = "0.1.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
dependencies = [
"log",
"pin-project-lite",
"tracing-core",
]
[[package]]
name = "tracing-core"
version = "0.1.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
dependencies = [
"once_cell",
]
[[package]]
name = "try-lock"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
name = "tungstenite"
version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9"
dependencies = [
"byteorder",
"bytes",
"data-encoding",
"http",
"httparse",
"log",
"rand",
"sha1",
"thiserror",
"url",
"utf-8",
]
[[package]] [[package]]
name = "typenum" name = "typenum"
version = "1.17.0" version = "1.17.0"
@ -1341,6 +1592,15 @@ version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9"
[[package]]
name = "unicase"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89"
dependencies = [
"version_check",
]
[[package]] [[package]]
name = "unicode-bidi" name = "unicode-bidi"
version = "0.3.15" version = "0.3.15"
@ -1379,6 +1639,12 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "utf-8"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.8.0" version = "1.8.0"
@ -1401,6 +1667,46 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "want"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
dependencies = [
"try-lock",
]
[[package]]
name = "warp"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1e92e22e03ff1230c03a1a8ee37d2f89cd489e2e541b7550d6afad96faed169"
dependencies = [
"bytes",
"futures-channel",
"futures-util",
"headers",
"http",
"hyper",
"log",
"mime",
"mime_guess",
"multer",
"percent-encoding",
"pin-project",
"rustls-pemfile",
"scoped-tls",
"serde",
"serde_json",
"serde_urlencoded",
"tokio",
"tokio-stream",
"tokio-tungstenite",
"tokio-util",
"tower-service",
"tracing",
]
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.11.0+wasi-snapshot-preview1" version = "0.11.0+wasi-snapshot-preview1"

View File

@ -21,4 +21,4 @@ serde = { version = "1.0.197", features = ["derive"] }
config = { version = "0.14.0", features = ["toml"] } config = { version = "0.14.0", features = ["toml"] }
url = "2.5.0" url = "2.5.0"
itertools = "0.12.1" itertools = "0.12.1"
hyper = "1.2.0" warp = "0.3.6"

View File

@ -7,6 +7,7 @@ use ffmpeg_sys_next::{
avcodec_find_decoder, avcodec_free_context, avcodec_open2, avcodec_parameters_to_context, avcodec_find_decoder, avcodec_free_context, avcodec_open2, avcodec_parameters_to_context,
avcodec_receive_frame, avcodec_send_packet, AVCodecContext, AVERROR, AVERROR_EOF, AVPacket, AVStream, avcodec_receive_frame, avcodec_send_packet, AVCodecContext, AVERROR, AVERROR_EOF, AVPacket, AVStream,
}; };
use ffmpeg_sys_next::AVPictureType::AV_PICTURE_TYPE_NONE;
use tokio::sync::broadcast; use tokio::sync::broadcast;
use tokio::sync::mpsc::UnboundedReceiver; use tokio::sync::mpsc::UnboundedReceiver;
@ -99,7 +100,9 @@ impl Decoder {
} }
return Err(Error::msg(format!("Failed to decode {}", ret))); return Err(Error::msg(format!("Failed to decode {}", ret)));
} }
(*frame).time_base = (*stream).time_base; // reset picture type, not to confuse the encoder
(*frame).pict_type = AV_PICTURE_TYPE_NONE;
(*frame).opaque = stream as *mut libc::c_void;
self.chan_out.send(PipelinePayload::AvFrame( self.chan_out.send(PipelinePayload::AvFrame(
"Decoder frame".to_owned(), "Decoder frame".to_owned(),
frame, frame,

View File

@ -4,7 +4,7 @@ use std::mem::transmute;
use std::ptr; use std::ptr;
use anyhow::Error; use anyhow::Error;
use ffmpeg_sys_next::{AV_CH_LAYOUT_STEREO, av_dump_format, av_get_sample_fmt, av_interleaved_write_frame, av_opt_set, AVChannelLayout, AVChannelLayout__bindgen_ty_1, avcodec_parameters_from_context, AVCodecContext, avformat_alloc_output_context2, avformat_free_context, avformat_new_stream, avformat_write_header, AVFormatContext, AVPacket, AVRational}; use ffmpeg_sys_next::{AV_CH_LAYOUT_STEREO, av_dump_format, av_get_sample_fmt, av_interleaved_write_frame, av_opt_set, AVChannelLayout, AVChannelLayout__bindgen_ty_1, avcodec_find_encoder, avcodec_parameters_from_context, AVCodecContext, avformat_alloc_output_context2, avformat_free_context, avformat_new_stream, avformat_write_header, AVFormatContext, AVPacket, AVRational};
use ffmpeg_sys_next::AVChannelOrder::AV_CHANNEL_ORDER_NATIVE; use ffmpeg_sys_next::AVChannelOrder::AV_CHANNEL_ORDER_NATIVE;
use ffmpeg_sys_next::AVColorSpace::AVCOL_SPC_BT709; use ffmpeg_sys_next::AVColorSpace::AVCOL_SPC_BT709;
use ffmpeg_sys_next::AVMediaType::{AVMEDIA_TYPE_AUDIO, AVMEDIA_TYPE_VIDEO}; use ffmpeg_sys_next::AVMediaType::{AVMEDIA_TYPE_AUDIO, AVMEDIA_TYPE_VIDEO};
@ -145,7 +145,6 @@ impl HlsEgress {
); );
for var in &mut self.config.variants { for var in &mut self.config.variants {
let tb = var.time_base();
match var { match var {
VariantStream::Video(vs) => { VariantStream::Video(vs) => {
let stream = avformat_new_stream(ctx, ptr::null()); let stream = avformat_new_stream(ctx, ptr::null());
@ -155,22 +154,8 @@ impl HlsEgress {
// overwrite dst_index to match output stream // overwrite dst_index to match output stream
vs.dst_index = (*stream).index as usize; vs.dst_index = (*stream).index as usize;
(*stream).time_base = tb; vs.to_stream(stream);
vs.to_codec_params((*stream).codecpar);
let params = (*stream).codecpar;
(*params).height = vs.height as libc::c_int;
(*params).width = vs.width as libc::c_int;
(*params).codec_id = transmute(vs.codec as i32);
(*params).codec_type = AVMEDIA_TYPE_VIDEO;
(*params).format = AV_PIX_FMT_YUV420P as i32;
(*params).framerate = AVRational {
num: 1,
den: vs.fps as libc::c_int,
};
(*params).bit_rate = vs.bitrate as i64;
(*params).color_space = AVCOL_SPC_BT709;
(*params).level = vs.level as libc::c_int;
(*params).profile = vs.profile as libc::c_int;
} }
VariantStream::Audio(va) => { VariantStream::Audio(va) => {
let stream = avformat_new_stream(ctx, ptr::null()); let stream = avformat_new_stream(ctx, ptr::null());
@ -180,25 +165,8 @@ impl HlsEgress {
// overwrite dst_index to match output stream // overwrite dst_index to match output stream
va.dst_index = (*stream).index as usize; va.dst_index = (*stream).index as usize;
(*stream).time_base = tb; va.to_stream(stream);
va.to_codec_params((*stream).codecpar);
let params = (*stream).codecpar;
(*params).codec_id = transmute(va.codec as i32);
(*params).codec_type = AVMEDIA_TYPE_AUDIO;
(*params).format = av_get_sample_fmt(
format!("{}\0", va.sample_fmt).as_ptr() as *const libc::c_char
) as libc::c_int;
(*params).bit_rate = va.bitrate as i64;
(*params).sample_rate = va.sample_rate as libc::c_int;
(*params).ch_layout = AVChannelLayout {
order: AV_CHANNEL_ORDER_NATIVE,
nb_channels: 2,
u: AVChannelLayout__bindgen_ty_1 {
mask: AV_CH_LAYOUT_STEREO,
},
opaque: ptr::null_mut(),
};
} }
} }
} }

18
src/egress/http.rs Normal file
View File

@ -0,0 +1,18 @@
use std::net::SocketAddr;
use anyhow::Error;
use warp::{cors, Filter};
use crate::settings::Settings;
pub async fn listen_out_dir(addr: String, settings: Settings) -> Result<(), Error> {
let addr: SocketAddr = addr.parse()?;
let cors = cors().allow_any_origin().allow_methods(vec!["GET"]);
let warp_out = warp::get()
.and(warp::fs::dir(settings.output_dir.clone()))
.with(cors);
warp::serve(warp_out).run(addr).await;
Ok(())
}

View File

@ -1 +1,2 @@
pub mod hls; pub mod hls;
pub mod http;

View File

@ -5,18 +5,16 @@ use anyhow::Error;
use ffmpeg_sys_next::{ use ffmpeg_sys_next::{
av_audio_fifo_alloc, av_audio_fifo_free, av_audio_fifo_read, av_audio_fifo_realloc, av_audio_fifo_alloc, av_audio_fifo_free, av_audio_fifo_read, av_audio_fifo_realloc,
av_audio_fifo_size, av_audio_fifo_write, av_buffer_ref, av_buffer_unref, av_audio_fifo_size, av_audio_fifo_write, av_buffer_ref, av_buffer_unref,
AV_CH_LAYOUT_STEREO, av_channel_layout_copy, av_frame_alloc, av_frame_free, av_frame_get_buffer, av_channel_layout_copy, av_frame_alloc, av_frame_free, av_frame_get_buffer, av_freep,
av_freep, av_get_sample_fmt, av_packet_alloc, av_packet_free, av_packet_alloc, av_packet_free, av_samples_alloc_array_and_samples, AVAudioFifo,
av_packet_rescale_ts, av_samples_alloc_array_and_samples, AVAudioFifo, AVBufferRef, AVCodec, avcodec_alloc_context3, avcodec_free_context,
AVBufferRef, AVChannelLayout, AVChannelLayout__bindgen_ty_1, AVCodec, avcodec_open2, avcodec_receive_packet, avcodec_send_frame, AVCodecContext, AVERROR, AVFrame, swr_alloc_set_opts2,
avcodec_alloc_context3, avcodec_find_encoder, avcodec_free_context, avcodec_open2, avcodec_receive_packet, avcodec_send_frame, swr_convert, swr_free, swr_init, SwrContext,
AVCodecContext, AVERROR, AVFrame, swr_alloc_set_opts2, swr_convert, swr_free,
swr_init, SwrContext,
}; };
use ffmpeg_sys_next::AVChannelOrder::AV_CHANNEL_ORDER_NATIVE;
use libc::EAGAIN; use libc::EAGAIN;
use tokio::sync::mpsc::UnboundedSender; use tokio::sync::mpsc::UnboundedSender;
use crate::encode::set_encoded_pkt_timing;
use crate::ipc::Rx; use crate::ipc::Rx;
use crate::pipeline::{PipelinePayload, PipelineProcessor}; use crate::pipeline::{PipelinePayload, PipelineProcessor};
use crate::utils::{audio_variant_id_ref, get_ffmpeg_error_msg, id_ref_to_uuid}; use crate::utils::{audio_variant_id_ref, get_ffmpeg_error_msg, id_ref_to_uuid};
@ -49,8 +47,8 @@ impl<T> Drop for AudioEncoder<T> {
} }
impl<TRecv> AudioEncoder<TRecv> impl<TRecv> AudioEncoder<TRecv>
where where
TRecv: Rx<PipelinePayload>, TRecv: Rx<PipelinePayload>,
{ {
pub fn new( pub fn new(
chan_in: TRecv, chan_in: TRecv,
@ -72,8 +70,7 @@ impl<TRecv> AudioEncoder<TRecv>
unsafe fn setup_encoder(&mut self, frame: *mut AVFrame) -> Result<(), Error> { unsafe fn setup_encoder(&mut self, frame: *mut AVFrame) -> Result<(), Error> {
if self.ctx.is_null() { if self.ctx.is_null() {
let codec = self.variant.codec; let encoder = self.variant.get_codec();
let encoder = avcodec_find_encoder(transmute(codec as i32));
if encoder.is_null() { if encoder.is_null() {
return Err(Error::msg("Encoder not found")); return Err(Error::msg("Encoder not found"));
} }
@ -83,20 +80,7 @@ impl<TRecv> AudioEncoder<TRecv>
return Err(Error::msg("Failed to allocate encoder context")); return Err(Error::msg("Failed to allocate encoder context"));
} }
(*ctx).time_base = self.variant.time_base(); self.variant.to_codec_context(ctx);
(*ctx).sample_fmt = av_get_sample_fmt(
format!("{}\0", self.variant.sample_fmt).as_ptr() as *const libc::c_char,
);
(*ctx).bit_rate = self.variant.bitrate as i64;
(*ctx).sample_rate = self.variant.sample_rate as libc::c_int;
(*ctx).ch_layout = AVChannelLayout {
order: AV_CHANNEL_ORDER_NATIVE,
nb_channels: 2,
u: AVChannelLayout__bindgen_ty_1 {
mask: AV_CH_LAYOUT_STEREO,
},
opaque: ptr::null_mut(),
};
// setup audio FIFO // setup audio FIFO
let fifo = av_audio_fifo_alloc((*ctx).sample_fmt, 2, 1); let fifo = av_audio_fifo_alloc((*ctx).sample_fmt, 2, 1);
@ -220,13 +204,17 @@ impl<TRecv> AudioEncoder<TRecv>
assert_eq!(var_id, self.variant.id); assert_eq!(var_id, self.variant.id);
self.setup_encoder(frame)?; self.setup_encoder(frame)?;
if !self.process_audio_frame(frame)? { if !self.process_audio_frame(frame)? {
return Ok(()); return Ok(());
} }
// read audio from FIFO // read audio from FIFO
let frame = self.get_fifo_frame()?; let fifo_frame = self.get_fifo_frame()?;
// copy pointer to input stream
(*fifo_frame).opaque = (*frame).opaque;
let frame = fifo_frame;
let mut ret = avcodec_send_frame(self.ctx, frame); let mut ret = avcodec_send_frame(self.ctx, frame);
if ret < 0 && ret != AVERROR(EAGAIN) { if ret < 0 && ret != AVERROR(EAGAIN) {
return Err(Error::msg(get_ffmpeg_error_msg(ret))); return Err(Error::msg(get_ffmpeg_error_msg(ret)));
@ -243,9 +231,7 @@ impl<TRecv> AudioEncoder<TRecv>
return Err(Error::msg(get_ffmpeg_error_msg(ret))); return Err(Error::msg(get_ffmpeg_error_msg(ret)));
} }
(*pkt).time_base = (*self.ctx).time_base; set_encoded_pkt_timing(self.ctx, pkt, frame);
(*pkt).duration = (*frame).duration;
av_packet_rescale_ts(pkt, (*frame).time_base, (*self.ctx).time_base);
(*pkt).opaque = self.ctx as *mut libc::c_void; (*pkt).opaque = self.ctx as *mut libc::c_void;
(*pkt).opaque_ref = av_buffer_ref(self.var_id_ref); (*pkt).opaque_ref = av_buffer_ref(self.var_id_ref);
self.chan_out self.chan_out
@ -257,8 +243,8 @@ impl<TRecv> AudioEncoder<TRecv>
} }
impl<TRecv> PipelineProcessor for AudioEncoder<TRecv> impl<TRecv> PipelineProcessor for AudioEncoder<TRecv>
where where
TRecv: Rx<PipelinePayload>, TRecv: Rx<PipelinePayload>,
{ {
fn process(&mut self) -> Result<(), Error> { fn process(&mut self) -> Result<(), Error> {
while let Ok(pkg) = self.chan_in.try_recv_next() { while let Ok(pkg) = self.chan_in.try_recv_next() {

View File

@ -1,2 +1,22 @@
use ffmpeg_sys_next::{av_packet_rescale_ts, AVCodecContext, AVFrame, AVPacket, AVStream};
use ffmpeg_sys_next::AVMediaType::AVMEDIA_TYPE_VIDEO;
pub mod audio; pub mod audio;
pub mod video; pub mod video;
/// Set packet details based on decoded frame
pub unsafe fn set_encoded_pkt_timing(
ctx: *mut AVCodecContext,
pkt: *mut AVPacket,
in_frame: *mut AVFrame,
) {
assert!(!(*in_frame).opaque.is_null());
let in_stream = (*in_frame).opaque as *mut AVStream;
let tb = (*ctx).time_base;
(*pkt).stream_index = (*in_stream).index;
if (*ctx).codec_type == AVMEDIA_TYPE_VIDEO {
(*pkt).duration = tb.den as i64 / tb.num as i64 / (*in_stream).avg_frame_rate.num as i64
* (*in_stream).avg_frame_rate.den as i64;
}
av_packet_rescale_ts(pkt, (*in_stream).time_base, (*ctx).time_base);
}

View File

@ -3,14 +3,13 @@ use std::ptr;
use anyhow::Error; use anyhow::Error;
use ffmpeg_sys_next::{ use ffmpeg_sys_next::{
av_buffer_ref, av_opt_set, av_packet_alloc, av_packet_free, av_packet_rescale_ts, av_buffer_ref, av_packet_alloc, av_packet_free, av_packet_rescale_ts, avcodec_alloc_context3,
AVBufferRef, AVCodec, avcodec_alloc_context3, avcodec_find_encoder, avcodec_find_encoder, avcodec_open2, avcodec_receive_packet, avcodec_send_frame, AVBufferRef,
avcodec_open2, avcodec_receive_packet, avcodec_send_frame, AVCodecContext, AVERROR, AVFrame, AVRational, AVCodec, AVCodecContext, AVFrame, AVStream, AVERROR,
}; };
use ffmpeg_sys_next::AVColorSpace::AVCOL_SPC_BT709;
use ffmpeg_sys_next::AVPixelFormat::AV_PIX_FMT_YUV420P;
use libc::EAGAIN; use libc::EAGAIN;
use tokio::sync::mpsc::UnboundedSender; use tokio::sync::mpsc::UnboundedSender;
use crate::encode::set_encoded_pkt_timing;
use crate::ipc::Rx; use crate::ipc::Rx;
use crate::pipeline::{PipelinePayload, PipelineProcessor}; use crate::pipeline::{PipelinePayload, PipelineProcessor};
@ -31,8 +30,8 @@ unsafe impl<T> Send for VideoEncoder<T> {}
unsafe impl<T> Sync for VideoEncoder<T> {} unsafe impl<T> Sync for VideoEncoder<T> {}
impl<TRecv> VideoEncoder<TRecv> impl<TRecv> VideoEncoder<TRecv>
where where
TRecv: Rx<PipelinePayload>, TRecv: Rx<PipelinePayload>,
{ {
pub fn new( pub fn new(
chan_in: TRecv, chan_in: TRecv,
@ -63,28 +62,7 @@ impl<TRecv> VideoEncoder<TRecv>
return Err(Error::msg("Failed to allocate encoder context")); return Err(Error::msg("Failed to allocate encoder context"));
} }
(*ctx).time_base = self.variant.time_base(); self.variant.to_codec_context(ctx);
(*ctx).bit_rate = self.variant.bitrate as i64;
(*ctx).width = (*frame).width;
(*ctx).height = (*frame).height;
(*ctx).level = self.variant.level as libc::c_int;
(*ctx).profile = self.variant.profile as libc::c_int;
(*ctx).framerate = AVRational {
num: 1,
den: self.variant.fps as libc::c_int,
};
let key_frames = self.variant.fps * self.variant.keyframe_interval;
(*ctx).gop_size = key_frames as libc::c_int;
(*ctx).max_b_frames = 1;
(*ctx).pix_fmt = AV_PIX_FMT_YUV420P;
(*ctx).colorspace = AVCOL_SPC_BT709;
av_opt_set(
(*ctx).priv_data,
"preset\0".as_ptr() as *const libc::c_char,
"fast\0".as_ptr() as *const libc::c_char,
0,
);
let ret = avcodec_open2(ctx, encoder, ptr::null_mut()); let ret = avcodec_open2(ctx, encoder, ptr::null_mut());
if ret < 0 { if ret < 0 {
@ -119,9 +97,7 @@ impl<TRecv> VideoEncoder<TRecv>
return Err(Error::msg(get_ffmpeg_error_msg(ret))); return Err(Error::msg(get_ffmpeg_error_msg(ret)));
} }
(*pkt).time_base = (*self.ctx).time_base; set_encoded_pkt_timing(self.ctx, pkt, frame);
(*pkt).duration = (*frame).duration;
av_packet_rescale_ts(pkt, (*frame).time_base, (*self.ctx).time_base);
(*pkt).opaque = self.ctx as *mut libc::c_void; (*pkt).opaque = self.ctx as *mut libc::c_void;
(*pkt).opaque_ref = av_buffer_ref(self.var_id_ref); (*pkt).opaque_ref = av_buffer_ref(self.var_id_ref);
self.chan_out self.chan_out
@ -133,8 +109,8 @@ impl<TRecv> VideoEncoder<TRecv>
} }
impl<TRecv> PipelineProcessor for VideoEncoder<TRecv> impl<TRecv> PipelineProcessor for VideoEncoder<TRecv>
where where
TRecv: Rx<PipelinePayload>, TRecv: Rx<PipelinePayload>,
{ {
fn process(&mut self) -> Result<(), Error> { fn process(&mut self) -> Result<(), Error> {
while let Ok(pkg) = self.chan_in.try_recv_next() { while let Ok(pkg) = self.chan_in.try_recv_next() {

View File

@ -4,6 +4,7 @@ use config::Config;
use log::{error, info}; use log::{error, info};
use url::Url; use url::Url;
use crate::egress::http::listen_out_dir;
use crate::pipeline::builder::PipelineBuilder; use crate::pipeline::builder::PipelineBuilder;
use crate::settings::Settings; use crate::settings::Settings;
use crate::webhook::Webhook; use crate::webhook::Webhook;
@ -14,14 +15,14 @@ mod egress;
mod encode; mod encode;
mod fraction; mod fraction;
mod ingress; mod ingress;
mod ipc;
mod pipeline; mod pipeline;
mod scale; mod scale;
mod settings; mod settings;
mod tag_frame;
mod utils; mod utils;
mod variant; mod variant;
mod webhook; mod webhook;
mod ipc;
mod tag_frame;
/// Test: ffmpeg -re -f lavfi -i testsrc -g 2 -r 30 -pix_fmt yuv420p -s 1280x720 -c:v h264 -b:v 2000k -f mpegts srt://localhost:3333 /// Test: ffmpeg -re -f lavfi -i testsrc -g 2 -r 30 -pix_fmt yuv420p -s 1280x720 -c:v h264 -b:v 2000k -f mpegts srt://localhost:3333
#[tokio::main] #[tokio::main]
@ -48,7 +49,7 @@ async fn main() -> anyhow::Result<()> {
let webhook = Webhook::new(settings.clone()); let webhook = Webhook::new(settings.clone());
let builder = PipelineBuilder::new(webhook); let builder = PipelineBuilder::new(webhook);
let mut listeners = vec![]; let mut listeners = vec![];
for e in settings.endpoints { for e in &settings.endpoints {
let u: Url = e.parse()?; let u: Url = e.parse()?;
let addr = format!("{}:{}", u.host_str().unwrap(), u.port().unwrap()); let addr = format!("{}:{}", u.host_str().unwrap(), u.port().unwrap());
match u.scheme() { match u.scheme() {
@ -59,6 +60,11 @@ async fn main() -> anyhow::Result<()> {
} }
} }
} }
listeners.push(tokio::spawn(listen_out_dir(
"0.0.0.0:8080".to_owned(),
settings.clone(),
)));
for handle in listeners { for handle in listeners {
if let Err(e) = handle.await { if let Err(e) = handle.await {
error!("{e}"); error!("{e}");

View File

@ -2,7 +2,7 @@ use std::ops::Add;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use anyhow::Error; use anyhow::Error;
use log::info; use log::{info, warn};
use tokio::sync::broadcast; use tokio::sync::broadcast;
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver}; use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver};
@ -59,7 +59,7 @@ impl PipelineRunner {
} }
pub fn run(&mut self) -> Result<(), Error> { pub fn run(&mut self) -> Result<(), Error> {
if let Some(info) = &self.stream_info { /*if let Some(info) = &self.stream_info {
if let Some(v_stream) = info if let Some(v_stream) = info
.channels .channels
.iter() .iter()
@ -73,7 +73,7 @@ impl PipelineRunner {
std::thread::sleep(poll_sleep); std::thread::sleep(poll_sleep);
} }
} }
} }*/
if let Some(cfg) = self.demuxer.process()? { if let Some(cfg) = self.demuxer.process()? {
self.configure_pipeline(cfg)?; self.configure_pipeline(cfg)?;
} }

View File

@ -57,7 +57,7 @@ impl Scaler {
let ctx = sws_getContext( let ctx = sws_getContext(
(*frame).width, (*frame).width,
(*frame).height, (*frame).height,
dst_fmt, transmute((*frame).format),
self.variant.width as libc::c_int, self.variant.width as libc::c_int,
self.variant.height as libc::c_int, self.variant.height as libc::c_int,
dst_fmt, dst_fmt,
@ -83,6 +83,7 @@ impl Scaler {
return Err(Error::msg(get_ffmpeg_error_msg(ret))); return Err(Error::msg(get_ffmpeg_error_msg(ret)));
} }
(*dst_frame).opaque = (*frame).opaque;
(*dst_frame).opaque_ref = av_buffer_ref(self.var_id_ref); (*dst_frame).opaque_ref = av_buffer_ref(self.var_id_ref);
self.chan_out.send(PipelinePayload::AvFrame( self.chan_out.send(PipelinePayload::AvFrame(

View File

@ -48,6 +48,7 @@ impl<TRecv> PipelineProcessor for TagFrame<TRecv>
if idx == self.variant.src_index() { if idx == self.variant.src_index() {
let new_frame = av_frame_clone(frm); let new_frame = av_frame_clone(frm);
av_frame_copy_props(new_frame, frm); av_frame_copy_props(new_frame, frm);
(*new_frame).opaque = (*frm).opaque;
(*new_frame).opaque_ref = av_buffer_ref(self.var_id_ref); (*new_frame).opaque_ref = av_buffer_ref(self.var_id_ref);
self.chan_out self.chan_out
.send(PipelinePayload::AvFrame(tag.clone(), new_frame, idx))?; .send(PipelinePayload::AvFrame(tag.clone(), new_frame, idx))?;

View File

@ -1,8 +1,18 @@
use std::ffi::CStr; use std::ffi::CStr;
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
use std::mem::transmute; use std::mem::transmute;
use std::ptr;
use ffmpeg_sys_next::{avcodec_get_name, AVRational}; use ffmpeg_sys_next::AVChannelOrder::AV_CHANNEL_ORDER_NATIVE;
use ffmpeg_sys_next::AVCodecID::{AV_CODEC_ID_AAC, AV_CODEC_ID_H264};
use ffmpeg_sys_next::AVColorSpace::AVCOL_SPC_BT709;
use ffmpeg_sys_next::AVPixelFormat::AV_PIX_FMT_YUV420P;
use ffmpeg_sys_next::{
av_get_sample_fmt, av_opt_set, avcodec_find_encoder, avcodec_find_encoder_by_name,
avcodec_get_name, AVChannelLayout, AVChannelLayout__bindgen_ty_1, AVCodec, AVCodecContext,
AVCodecParameters, AVRational, AVStream, AV_CH_LAYOUT_STEREO,
};
use ffmpeg_sys_next::AVColorRange::{AVCOL_RANGE_JPEG, AVCOL_RANGE_MPEG};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use uuid::Uuid; use uuid::Uuid;
@ -162,6 +172,77 @@ impl VideoVariant {
den: 90_000, den: 90_000,
} }
} }
pub fn get_codec(&self) -> *const AVCodec {
unsafe { avcodec_find_encoder(transmute(self.codec as u32)) }
}
pub unsafe fn to_codec_context(&self, ctx: *mut AVCodecContext) {
let codec = self.get_codec();
(*ctx).codec_id = (*codec).id;
(*ctx).codec_type = (*codec).type_;
(*ctx).time_base = self.time_base();
(*ctx).bit_rate = self.bitrate as i64;
(*ctx).width = self.width as libc::c_int;
(*ctx).height = self.height as libc::c_int;
(*ctx).level = self.level as libc::c_int;
(*ctx).profile = self.profile as libc::c_int;
(*ctx).framerate = AVRational {
num: self.fps as libc::c_int,
den: 1,
};
let key_frames = self.fps * self.keyframe_interval;
(*ctx).gop_size = key_frames as libc::c_int;
(*ctx).keyint_min = key_frames as libc::c_int;
(*ctx).max_b_frames = 1;
(*ctx).pix_fmt = AV_PIX_FMT_YUV420P;
(*ctx).colorspace = AVCOL_SPC_BT709;
(*ctx).color_range = AVCOL_RANGE_MPEG;
if (*codec).id == AV_CODEC_ID_H264 {
av_opt_set(
(*ctx).priv_data,
"preset\0".as_ptr() as *const libc::c_char,
"fast\0".as_ptr() as *const libc::c_char,
0,
);
av_opt_set(
(*ctx).priv_data,
"tune\0".as_ptr() as *const libc::c_char,
"zerolatency\0".as_ptr() as *const libc::c_char,
0,
);
}
}
pub unsafe fn to_codec_params(&self, params: *mut AVCodecParameters) {
let codec = self.get_codec();
(*params).codec_id = (*codec).id;
(*params).codec_type = (*codec).type_;
(*params).height = self.height as libc::c_int;
(*params).width = self.width as libc::c_int;
(*params).format = AV_PIX_FMT_YUV420P as i32;
(*params).framerate = AVRational {
num: self.fps as libc::c_int,
den: 1,
};
(*params).bit_rate = self.bitrate as i64;
(*params).color_space = AVCOL_SPC_BT709;
(*params).level = self.level as libc::c_int;
(*params).profile = self.profile as libc::c_int;
}
pub unsafe fn to_stream(&self, stream: *mut AVStream) {
(*stream).time_base = self.time_base();
(*stream).avg_frame_rate = AVRational {
num: self.fps as libc::c_int,
den: 1,
};
(*stream).r_frame_rate = AVRational {
num: self.fps as libc::c_int,
den: 1,
};
}
} }
impl AudioVariant { impl AudioVariant {
@ -171,4 +252,57 @@ impl AudioVariant {
den: self.sample_rate as libc::c_int, den: self.sample_rate as libc::c_int,
} }
} }
pub fn get_codec(&self) -> *const AVCodec {
unsafe {
if self.codec == AV_CODEC_ID_AAC as usize {
avcodec_find_encoder_by_name("libfdk_aac\0".as_ptr() as *const libc::c_char)
} else {
avcodec_find_encoder(transmute(self.codec as u32))
}
}
}
pub unsafe fn to_codec_context(&self, ctx: *mut AVCodecContext) {
let codec = self.get_codec();
(*ctx).codec_id = (*codec).id;
(*ctx).codec_type = (*codec).type_;
(*ctx).time_base = self.time_base();
(*ctx).sample_fmt =
av_get_sample_fmt(format!("{}\0", self.sample_fmt).as_ptr() as *const libc::c_char);
(*ctx).bit_rate = self.bitrate as i64;
(*ctx).sample_rate = self.sample_rate as libc::c_int;
(*ctx).ch_layout = self.channel_layout();
}
pub unsafe fn to_codec_params(&self, params: *mut AVCodecParameters) {
let codec = self.get_codec();
(*params).codec_id = (*codec).id;
(*params).codec_type = (*codec).type_;
(*params).format =
av_get_sample_fmt(format!("{}\0", self.sample_fmt).as_ptr() as *const libc::c_char)
as libc::c_int;
(*params).bit_rate = self.bitrate as i64;
(*params).sample_rate = self.sample_rate as libc::c_int;
(*params).ch_layout = self.channel_layout();
}
pub unsafe fn to_stream(&self, stream: *mut AVStream) {
(*stream).time_base = self.time_base();
(*stream).r_frame_rate = AVRational {
num: (*stream).time_base.den,
den: (*stream).time_base.num,
};
}
pub fn channel_layout(&self) -> AVChannelLayout {
AVChannelLayout {
order: AV_CHANNEL_ORDER_NATIVE,
nb_channels: 2,
u: AVChannelLayout__bindgen_ty_1 {
mask: AV_CH_LAYOUT_STEREO,
},
opaque: ptr::null_mut(),
}
}
} }

View File

@ -1,3 +1,4 @@
use ffmpeg_sys_next::AVCodecID::{AV_CODEC_ID_AAC, AV_CODEC_ID_AAC_LATM};
use uuid::Uuid; use uuid::Uuid;
use crate::demux::info::{DemuxStreamInfo, StreamChannelType}; use crate::demux::info::{DemuxStreamInfo, StreamChannelType};
@ -62,7 +63,7 @@ impl Webhook {
codec: 86018, codec: 86018,
channels: 2, channels: 2,
sample_rate: 44_100, sample_rate: 44_100,
sample_fmt: "fltp".to_owned(), sample_fmt: "s16".to_owned(),
})); }));
vars.push(VariantStream::Audio(AudioVariant { vars.push(VariantStream::Audio(AudioVariant {
id: Uuid::new_v4(), id: Uuid::new_v4(),
@ -72,7 +73,7 @@ impl Webhook {
codec: 86018, codec: 86018,
channels: 2, channels: 2,
sample_rate: 44_100, sample_rate: 44_100,
sample_fmt: "fltp".to_owned(), sample_fmt: "s16".to_owned(),
})); }));
} }

View File

@ -1,5 +1,6 @@
#!/bin/bash #!/bin/bash
ffmpeg \ ffmpeg \
-re -f lavfi -i testsrc -g 300 -r 60 -pix_fmt yuv420p -s 1280x720 -c:v h264 -b:v 2000k -c:a aac -b:a 192k \ -f lavfi -i "sine=frequency=1000:sample_rate=48000" \
-f mpegts srt://localhost:3333 -re -f lavfi -i testsrc -g 300 -r 60 -pix_fmt yuv420p -s 1280x720 \
-c:v h264 -b:v 2000k -c:a aac -b:a 192k $@ -f mpegts srt://localhost:3333