Skip to content

Commit a1e6437

Browse files
committed
Add output size support to H264 encoder
1 parent d2f8ed3 commit a1e6437

File tree

2 files changed

+106
-38
lines changed
  • crates

2 files changed

+106
-38
lines changed

crates/enc-ffmpeg/src/video/h264.rs

Lines changed: 94 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ pub struct H264EncoderBuilder {
1616
bpp: f32,
1717
input_config: VideoInfo,
1818
preset: H264Preset,
19+
output_size: Option<(u32, u32)>,
1920
}
2021

2122
#[derive(Clone, Copy)]
@@ -43,6 +44,7 @@ impl H264EncoderBuilder {
4344
input_config,
4445
bpp: Self::QUALITY_BPP,
4546
preset: H264Preset::Ultrafast,
47+
output_size: None,
4648
}
4749
}
4850

@@ -56,45 +58,95 @@ impl H264EncoderBuilder {
5658
self
5759
}
5860

61+
pub fn with_output_size(mut self, width: u32, height: u32) -> Self {
62+
if width == 0 || height == 0 {
63+
self.output_size = None;
64+
} else {
65+
self.output_size = Some((width, height));
66+
}
67+
self
68+
}
69+
5970
pub fn build(
6071
self,
6172
output: &mut format::context::Output,
6273
) -> Result<H264Encoder, H264EncoderError> {
63-
let input_config = &self.input_config;
64-
let (codec, encoder_options) = get_codec_and_options(input_config, self.preset)
74+
let input_config = self.input_config;
75+
let (codec, encoder_options) = get_codec_and_options(&input_config, self.preset)
6576
.ok_or(H264EncoderError::CodecNotFound)?;
6677

67-
let (format, converter) = if !codec
68-
.video()
69-
.unwrap()
78+
let (mut output_width, mut output_height) = self
79+
.output_size
80+
.unwrap_or((input_config.width, input_config.height));
81+
82+
if output_width == 0 || output_height == 0 {
83+
output_width = input_config.width;
84+
output_height = input_config.height;
85+
}
86+
87+
let codec_video = codec.video().unwrap();
88+
let encoder_supports_input_format = codec_video
7089
.formats()
7190
.unwrap()
72-
.any(|f| f == input_config.pixel_format)
73-
{
91+
.any(|f| f == input_config.pixel_format);
92+
93+
let mut needs_pixel_conversion = false;
94+
95+
let output_format = if encoder_supports_input_format {
96+
input_config.pixel_format
97+
} else {
98+
needs_pixel_conversion = true;
7499
let format = ffmpeg::format::Pixel::NV12;
75100
debug!(
76101
"Converting from {:?} to {:?} for H264 encoding",
77102
input_config.pixel_format, format
78103
);
79-
(
80-
format,
81-
Some(
82-
ffmpeg::software::converter(
83-
(input_config.width, input_config.height),
84-
input_config.pixel_format,
85-
format,
86-
)
87-
.map_err(|e| {
104+
format
105+
};
106+
107+
let needs_scaling =
108+
output_width != input_config.width || output_height != input_config.height;
109+
110+
if needs_scaling {
111+
debug!(
112+
"Scaling video frames for H264 encoding from {}x{} to {}x{}",
113+
input_config.width, input_config.height, output_width, output_height
114+
);
115+
}
116+
117+
let converter = if needs_pixel_conversion || needs_scaling {
118+
let flags = if needs_scaling {
119+
ffmpeg::software::scaling::flag::Flags::BICUBIC
120+
} else {
121+
ffmpeg::software::scaling::flag::Flags::FAST_BILINEAR
122+
};
123+
124+
match ffmpeg::software::scaling::Context::get(
125+
input_config.pixel_format,
126+
input_config.width,
127+
input_config.height,
128+
output_format,
129+
output_width,
130+
output_height,
131+
flags,
132+
) {
133+
Ok(context) => Some(context),
134+
Err(e) => {
135+
if needs_pixel_conversion {
88136
error!(
89-
"Failed to create converter from {:?} to NV12: {:?}",
90-
input_config.pixel_format, e
137+
"Failed to create converter from {:?} to {:?}: {:?}",
138+
input_config.pixel_format, output_format, e
91139
);
92-
H264EncoderError::PixFmtNotSupported(input_config.pixel_format)
93-
})?,
94-
),
95-
)
140+
return Err(H264EncoderError::PixFmtNotSupported(
141+
input_config.pixel_format,
142+
));
143+
}
144+
145+
return Err(H264EncoderError::FFmpeg(e));
146+
}
147+
}
96148
} else {
97-
(input_config.pixel_format, None)
149+
None
98150
};
99151

100152
let mut encoder_ctx = context::Context::new_with_codec(codec);
@@ -105,16 +157,16 @@ impl H264EncoderBuilder {
105157
encoder_ctx.set_threading(Config::count(thread_count));
106158
let mut encoder = encoder_ctx.encoder().video()?;
107159

108-
encoder.set_width(input_config.width);
109-
encoder.set_height(input_config.height);
110-
encoder.set_format(format);
160+
encoder.set_width(output_width);
161+
encoder.set_height(output_height);
162+
encoder.set_format(output_format);
111163
encoder.set_time_base(input_config.time_base);
112164
encoder.set_frame_rate(Some(input_config.frame_rate));
113165

114166
// let target_bitrate = compression.bitrate();
115167
let bitrate = get_bitrate(
116-
input_config.width,
117-
input_config.height,
168+
output_width,
169+
output_height,
118170
input_config.frame_rate.0 as f32 / input_config.frame_rate.1 as f32,
119171
self.bpp,
120172
);
@@ -133,17 +185,21 @@ impl H264EncoderBuilder {
133185
Ok(H264Encoder {
134186
base: EncoderBase::new(stream_index),
135187
encoder,
136-
config: self.input_config,
137188
converter,
189+
output_format,
190+
output_width,
191+
output_height,
138192
})
139193
}
140194
}
141195

142196
pub struct H264Encoder {
143197
base: EncoderBase,
144198
encoder: encoder::Video,
145-
config: VideoInfo,
146199
converter: Option<ffmpeg::software::scaling::Context>,
200+
output_format: format::Pixel,
201+
output_width: u32,
202+
output_height: u32,
147203
}
148204

149205
#[derive(thiserror::Error, Debug)]
@@ -170,16 +226,16 @@ impl H264Encoder {
170226
self.base
171227
.update_pts(&mut frame, timestamp, &mut self.encoder);
172228

173-
let frame = if let Some(converter) = &mut self.converter {
174-
let mut new_frame = frame::Video::empty();
229+
if let Some(converter) = &mut self.converter {
230+
let pts = frame.pts();
231+
let mut converted =
232+
frame::Video::new(self.output_format, self.output_width, self.output_height);
175233
converter
176-
.run(&frame, &mut new_frame)
234+
.run(&frame, &mut converted)
177235
.map_err(QueueFrameError::Converter)?;
178-
new_frame.set_pts(frame.pts());
179-
new_frame
180-
} else {
181-
frame
182-
};
236+
converted.set_pts(pts);
237+
frame = converted;
238+
}
183239

184240
self.base
185241
.send_frame(&frame, output, &mut self.encoder)

crates/recording/src/output_pipeline/win.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,19 @@ impl Muxer for WindowsMuxer {
103103
error!("Failed to create native encoder: {e}");
104104
info!("Falling back to software H264 encoder");
105105

106+
let fallback_width = if output_size.Width > 0 {
107+
output_size.Width as u32
108+
} else {
109+
video_config.width
110+
};
111+
let fallback_height = if output_size.Height > 0 {
112+
output_size.Height as u32
113+
} else {
114+
video_config.height
115+
};
116+
106117
cap_enc_ffmpeg::H264Encoder::builder(video_config)
118+
.with_output_size(fallback_width, fallback_height)
107119
.build(&mut output)
108120
.map(either::Right)
109121
.map_err(|e| anyhow!("ScreenSoftwareEncoder/{e}"))

0 commit comments

Comments
 (0)