diff --git a/src/api/config/encoder.rs b/src/api/config/encoder.rs index a94e99f55f..85992cf263 100644 --- a/src/api/config/encoder.rs +++ b/src/api/config/encoder.rs @@ -31,6 +31,13 @@ pub struct EncoderConfig { pub height: usize, /// Sample aspect ratio (for anamorphic video). pub sample_aspect_ratio: Rational, + /// Maximum width of the frames in pixels (for seq header) + /// 0 means to use the width setting instead. + /// Used for multiple renditions when switch frames are in use. + /// Set all renditions to have identical max_width / max_height. + pub max_width: usize, + /// Maximum height of the frames in pixels (for seq header) + pub max_height: usize, /// Video time base. pub time_base: Rational, @@ -133,7 +140,8 @@ impl EncoderConfig { height: 480, sample_aspect_ratio: Rational { num: 1, den: 1 }, time_base: Rational { num: 1, den: 30 }, - + max_width: 0, + max_height: 0, bit_depth: 8, chroma_sampling: ChromaSampling::Cs420, chroma_sample_position: ChromaSamplePosition::Unknown, diff --git a/src/api/config/mod.rs b/src/api/config/mod.rs index 230e35c624..d4f0263411 100644 --- a/src/api/config/mod.rs +++ b/src/api/config/mod.rs @@ -32,11 +32,21 @@ pub use crate::tiling::TilingInfo; #[non_exhaustive] pub enum InvalidConfig { /// The width is invalid. - #[error("invalid width {0} (expected >= 16, <= 65535)")] - InvalidWidth(usize), + #[error("invalid width {actual} (expected >= 16, <= {max})")] + InvalidWidth { + /// The actual value. + actual: usize, + /// The maximal supported value. + max: usize, + }, /// The height is invalid. - #[error("invalid height {0} (expected >= 16, <= 65535)")] - InvalidHeight(usize), + #[error("invalid height {actual} (expected >= 16, <= {max})")] + InvalidHeight { + /// The actual value. + actual: usize, + /// The maximal supported value. + max: usize, + }, /// Aspect ratio numerator is invalid. #[error("invalid aspect ratio numerator {0} (expected > 0)")] InvalidAspectRatioNum(usize), @@ -285,14 +295,30 @@ impl Config { if (config.still_picture && config.width < 1) || (!config.still_picture && config.width < 16) || config.width > u16::max_value() as usize + || (config.max_width != 0 && config.width > config.max_width) { - return Err(InvalidWidth(config.width)); + return Err(InvalidWidth { + actual: config.width, + max: if config.max_width != 0 { + config.max_width + } else { + u16::max_value() as usize + }, + }); } if (config.still_picture && config.height < 1) || (!config.still_picture && config.height < 16) || config.height > u16::max_value() as usize + || (config.max_height != 0 && config.height > config.max_height) { - return Err(InvalidHeight(config.height)); + return Err(InvalidHeight { + actual: config.height, + max: if config.max_height != 0 { + config.max_height + } else { + u16::max_value() as usize + }, + }); } if config.sample_aspect_ratio.num == 0 { diff --git a/src/api/test.rs b/src/api/test.rs index cf4dc865e9..fe638f6d6c 100644 --- a/src/api/test.rs +++ b/src/api/test.rs @@ -1902,6 +1902,8 @@ fn log_q_exp_overflow() { width: 16, height: 16, sample_aspect_ratio: Rational::new(1, 1), + max_width: 0, + max_height: 0, bit_depth: 8, chroma_sampling: ChromaSampling::Cs420, chroma_sample_position: ChromaSamplePosition::Unknown, @@ -1967,6 +1969,8 @@ fn guess_frame_subtypes_assert() { width: 16, height: 16, sample_aspect_ratio: Rational::new(1, 1), + max_width: 0, + max_height: 0, bit_depth: 8, chroma_sampling: ChromaSampling::Cs420, chroma_sample_position: ChromaSamplePosition::Unknown, diff --git a/src/bin/common.rs b/src/bin/common.rs index c41de23af1..0655d79a60 100644 --- a/src/bin/common.rs +++ b/src/bin/common.rs @@ -258,6 +258,20 @@ pub fn parse_cli() -> Result { .takes_value(true) .default_value("0") ) + .arg( + Arg::with_name("MAX_WIDTH") + .help("Maximum width coded in the sequence header. 0 uses the input video width.") + .long("max-width") + .takes_value(true) + .default_value("0") + ) + .arg( + Arg::with_name("MAX_HEIGHT") + .help("Maximum height coded in the sequence header. 0 uses the input video width.") + .long("max-height") + .takes_value(true) + .default_value("0") + ) .arg( Arg::with_name("TILES") .help("Number of tiles. Tile-cols and tile-rows are overridden\n\ @@ -740,6 +754,9 @@ fn parse_config(matches: &ArgMatches<'_>) -> Result { cfg.tile_cols = matches.value_of("TILE_COLS").unwrap().parse().unwrap(); cfg.tile_rows = matches.value_of("TILE_ROWS").unwrap().parse().unwrap(); + cfg.max_width = matches.value_of("MAX_WIDTH").unwrap().parse().unwrap(); + cfg.max_height = matches.value_of("MAX_HEIGHT").unwrap().parse().unwrap(); + cfg.tiles = matches.value_of("TILES").unwrap().parse().unwrap(); if cfg.tile_cols > 64 || cfg.tile_rows > 64 { diff --git a/src/capi.rs b/src/capi.rs index cda43d8d40..4edcf2b68c 100644 --- a/src/capi.rs +++ b/src/capi.rs @@ -609,6 +609,8 @@ unsafe fn option_match( match key { "width" => enc.width = value.parse().map_err(|_| ())?, "height" => enc.height = value.parse().map_err(|_| ())?, + "max_width" => enc.max_width = value.parse().map_err(|_| ())?, + "max_height" => enc.max_height = value.parse().map_err(|_| ())?, "speed" => { enc.speed_settings = rav1e::SpeedSettings::from_preset(value.parse().map_err(|_| ())?) diff --git a/src/encoder.rs b/src/encoder.rs index 43d696e9b7..9bf6853d21 100644 --- a/src/encoder.rs +++ b/src/encoder.rs @@ -187,8 +187,12 @@ pub struct Sequence { impl Sequence { pub fn new(config: &EncoderConfig) -> Sequence { - let width_bits = 32 - (config.width as u32).leading_zeros(); - let height_bits = 32 - (config.height as u32).leading_zeros(); + let max_width = + if config.max_width > 0 { config.max_width } else { config.width }; + let max_height = + if config.max_height > 0 { config.max_height } else { config.height }; + let width_bits = 32 - (max_width as u32).leading_zeros(); + let height_bits = 32 - (max_height as u32).leading_zeros(); assert!(width_bits <= 16); assert!(height_bits <= 16); @@ -277,8 +281,8 @@ impl Sequence { color_description: config.color_description, mastering_display: config.mastering_display, content_light: config.content_light, - max_frame_width: config.width as u32, - max_frame_height: config.height as u32, + max_frame_width: max_width as u32, + max_frame_height: max_height as u32, frame_id_numbers_present_flag: false, frame_id_length: FRAME_ID_LENGTH, delta_frame_id_length: DELTA_FRAME_ID_LENGTH, diff --git a/src/fuzzing.rs b/src/fuzzing.rs index 8362783689..f8b403da1e 100644 --- a/src/fuzzing.rs +++ b/src/fuzzing.rs @@ -221,6 +221,8 @@ impl Arbitrary for ArbitraryEncoder { speed_settings: SpeedSettings::from_preset(u.int_in_range(0..=10)?), width: u.int_in_range(1..=256)?, height: u.int_in_range(1..=256)?, + max_width: 0, + max_height: 0, still_picture: Arbitrary::arbitrary(u)?, time_base: arbitrary_rational(u)?, min_key_frame_interval: u.int_in_range(0..=3)?, diff --git a/src/header.rs b/src/header.rs index 56d9e2d5af..67e291b5d3 100644 --- a/src/header.rs +++ b/src/header.rs @@ -837,12 +837,10 @@ impl UncompressedHeader for BitWriter { fn write_max_frame_size( &mut self, fi: &FrameInvariants, ) -> io::Result<()> { - // width_bits and height_bits will have to be moved to the sequence header OBU - // when we add support for it. - let width = fi.width - 1; - let height = fi.height - 1; - let width_bits = log_in_base_2(width as u32) as u32 + 1; - let height_bits = log_in_base_2(height as u32) as u32 + 1; + let width = fi.sequence.max_frame_width - 1; + let height = fi.sequence.max_frame_height - 1; + let width_bits = fi.sequence.num_bits_width; + let height_bits = fi.sequence.num_bits_height; assert!(width_bits <= 16); assert!(height_bits <= 16); self.write(4, width_bits - 1)?; @@ -858,14 +856,12 @@ impl UncompressedHeader for BitWriter { // width_bits and height_bits will have to be moved to the sequence header OBU // when we add support for it. if fi.frame_size_override_flag { - let width = fi.width - 1; - let height = fi.height - 1; - let width_bits = log_in_base_2(width as u32) as u32 + 1; - let height_bits = log_in_base_2(height as u32) as u32 + 1; + let width_bits = fi.sequence.num_bits_width; + let height_bits = fi.sequence.num_bits_height; assert!(width_bits <= 16); assert!(height_bits <= 16); - self.write(width_bits, width as u16)?; - self.write(height_bits, height as u16)?; + self.write(width_bits, (fi.width - 1) as u16)?; + self.write(height_bits, (fi.height - 1) as u16)?; } if fi.sequence.enable_superres { unimplemented!();