Skip to content

Commit

Permalink
Add option to provide raw FFmpeg options (#466)
Browse files Browse the repository at this point in the history
  • Loading branch information
wkozyra95 authored Mar 28, 2024
1 parent 1eb9591 commit 70a6fbf
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 33 deletions.
88 changes: 55 additions & 33 deletions compositor_pipeline/src/pipeline/encoder/ffmpeg_h264.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ pub struct Options {
pub preset: EncoderPreset,
pub resolution: Resolution,
pub output_id: OutputId,
pub raw_options: Vec<(String, String)>,
}

pub struct LibavH264Encoder {
Expand Down Expand Up @@ -158,39 +159,40 @@ fn run_encoder_thread(
encoder.set_width(options.resolution.width as u32);
encoder.set_height(options.resolution.height as u32);

let mut encoder = encoder.open_as_with(
codec,
// TODO: audit settings bellow
// Those values are copied from somewhere, they have to be set because libx264
// is throwing an error if it detects default ffmpeg settings.
Dictionary::from_iter([
("preset", options.preset.to_str()),
// Quality-based VBR (0-51)
("crf", "23"),
// Override ffmpeg defaults from https://github.com/mirror/x264/blob/eaa68fad9e5d201d42fde51665f2d137ae96baf0/encoder/encoder.c#L674
// QP curve compression - libx264 defaults to 0.6 (in case of tune=grain to 0.8)
("qcomp", "0.6"),
// Maximum motion vector search range - libx264 defaults to 16 (in case of placebo
// or veryslow preset to 24)
("me_range", "16"),
// Max QP step - libx264 defaults to 4
("qdiff", "4"),
// Min QP - libx264 defaults to 0
("qmin", "0"),
// Max QP - libx264 defaults to QP_MAX = 69
("qmax", "69"),
// Maximum GOP (Group of Pictures) size - libx264 defaults to 250
("g", "250"),
// QP factor between I and P frames - libx264 defaults to 1.4 (in case of tune=grain to 1.1)
("i_qfactor", "1.4"),
// QP factor between P and B frames - libx264 defaults to 1.4 (in case of tune=grain to 1.1)
("f_pb_factor", "1.3"),
// A comma-separated list of partitions to consider. Possible values: p8x8, p4x4, b8x8, i8x8, i4x4, none, all
("partitions", options.preset.default_partitions()),
// Subpixel motion estimation and mode decision (decision quality: 1=fast, 11=best)
("subq", options.preset.default_subq_mode()),
]),
)?;
// TODO: audit settings bellow
// Those values are copied from somewhere, they have to be set because libx264
// is throwing an error if it detects default ffmpeg settings.
let defaults = [
("preset", options.preset.to_str()),
// Quality-based VBR (0-51)
("crf", "23"),
// Override ffmpeg defaults from https://github.com/mirror/x264/blob/eaa68fad9e5d201d42fde51665f2d137ae96baf0/encoder/encoder.c#L674
// QP curve compression - libx264 defaults to 0.6 (in case of tune=grain to 0.8)
("qcomp", "0.6"),
// Maximum motion vector search range - libx264 defaults to 16 (in case of placebo
// or veryslow preset to 24)
("me_range", "16"),
// Max QP step - libx264 defaults to 4
("qdiff", "4"),
// Min QP - libx264 defaults to 0
("qmin", "0"),
// Max QP - libx264 defaults to QP_MAX = 69
("qmax", "69"),
// Maximum GOP (Group of Pictures) size - libx264 defaults to 250
("g", "250"),
// QP factor between I and P frames - libx264 defaults to 1.4 (in case of tune=grain to 1.1)
("i_qfactor", "1.4"),
// QP factor between P and B frames - libx264 defaults to 1.4 (in case of tune=grain to 1.1)
("f_pb_factor", "1.3"),
// A comma-separated list of partitions to consider. Possible values: p8x8, p4x4, b8x8, i8x8, i4x4, none, all
("partitions", options.preset.default_partitions()),
// Subpixel motion estimation and mode decision (decision quality: 1=fast, 11=best)
("subq", options.preset.default_subq_mode()),
];

let encoder_opts_iter = merge_options_with_defaults(&defaults, &options.raw_options);

let mut encoder = encoder.open_as_with(codec, Dictionary::from_iter(encoder_opts_iter))?;

result_sender.send(Ok(())).unwrap();

Expand Down Expand Up @@ -308,3 +310,23 @@ fn write_plane_to_av(frame: &mut frame::Video, plane: usize, data: &[u8]) {
.zip(frame.data_mut(plane).chunks_mut(stride))
.for_each(|(data, target)| target[..width].copy_from_slice(data));
}

fn merge_options_with_defaults<'a>(
defaults: &'a [(&str, &str)],
overrides: &'a [(String, String)],
) -> impl Iterator<Item = (&'a str, &'a str)> {
defaults
.iter()
.copied()
.filter(|(key, _value)| {
// filter out any defaults that are in overrides
!overrides
.iter()
.any(|(override_key, _)| key == override_key)
})
.chain(
overrides
.iter()
.map(|(key, value)| (key.as_str(), value.as_str())),
)
}
2 changes: 2 additions & 0 deletions docs/pages/api/outputs/rtp.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Register a new RTP output stream.
type Video = {
resolution: { width: number; height: number };
encoder_preset?: VideoEncoderPreset;
ffmpeg_options?: Map<String, String>;
send_eos_when?: EosCondition;
initial: Component;
}
Expand All @@ -48,6 +49,7 @@ type VideoEncoderPreset =

- `resolution` - Output resolution in pixels.
- `encoder_preset` - (**default=`"fast"`**) Preset for an encoder. See `FFmpeg` [docs](https://trac.ffmpeg.org/wiki/Encode/H.264#Preset) to learn more.
- `ffmepg_options` - Raw FFmpeg encoder options. See [docs](https://ffmpeg.org/ffmpeg-codecs.html) for more.
- `send_eos_when` - Defines when output stream should end if some of the input streams are finished. If output includes both audio and video streams, then EOS needs to be sent on both.
- `initial` - Root of a component tree/scene that should be rendered for the output. Use [`update_output` request](../routes.md#update-output) to update this value after registration. [Learn more](../../concept/component.md).

Expand Down
10 changes: 10 additions & 0 deletions schemas/register.schema.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/types/from_register_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ impl TryFrom<RegisterOutputRequest> for pipeline::RegisterOutputOptions {
preset: v.encoder_preset.into(),
resolution: v.resolution.into(),
output_id: output_id.clone().into(),
raw_options: v.ffmpeg_options.unwrap_or_default().into_iter().collect(),
}),
end_condition: v.send_eos_when.unwrap_or_default().try_into()?,
})
Expand Down
3 changes: 3 additions & 0 deletions src/types/register_request.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use core::f64;
use std::collections::HashMap;
use std::sync::Arc;

use schemars::JsonSchema;
Expand Down Expand Up @@ -124,6 +125,8 @@ pub enum Port {
pub struct OutputVideoOptions {
pub resolution: Resolution,
pub encoder_preset: VideoEncoderPreset,
/// Raw FFmpeg encoder options. See [docs](https://ffmpeg.org/ffmpeg-codecs.html) for more.
pub ffmpeg_options: Option<HashMap<String, String>>,
pub initial: Component,
/// Condition for termination of output stream based on the input streams states.
pub send_eos_when: Option<OutputEndCondition>,
Expand Down

0 comments on commit 70a6fbf

Please sign in to comment.