Skip to content

Commit 3e31a5a

Browse files
committed
[*] use openh264 as video encoder for windows
1 parent 9556a50 commit 3e31a5a

File tree

2 files changed

+201
-7
lines changed

2 files changed

+201
-7
lines changed

lib/recorder/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ env_logger.workspace = true
5252
[features]
5353
default = ["wayland-wlr"]
5454
windows = ["openh264-video-encoder"]
55-
wayland-wlr = ["dep:screen-capture-wayland-wlr", "x264-video-encoder"]
56-
# wayland-wlr = ["dep:screen-capture-wayland-wlr", "openh264-video-encoder"]
55+
# wayland-wlr = ["dep:screen-capture-wayland-wlr", "x264-video-encoder"]
56+
wayland-wlr = ["dep:screen-capture-wayland-wlr", "openh264-video-encoder"]
5757
wayland-portal = ["dep:screen-capture-wayland-portal", "x264-video-encoder"]
5858

5959
x264-video-encoder = ["dep:x264"]

lib/recorder/src/video_encoder/ve_openh264.rs

Lines changed: 199 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use super::EncodedFrame;
22
use crate::{RecorderError, VideoEncoder, VideoEncoderConfig, recorder::ResizedImageBuffer};
3+
use image::{ImageBuffer, Rgb};
34
use openh264::{
45
OpenH264API,
5-
encoder::{Complexity, Encoder, EncoderConfig, FrameRate, Profile, UsageType},
6+
encoder::{Complexity, Encoder, EncoderConfig, FrameRate, Profile, RateControlMode, UsageType},
67
formats::{RgbSliceU8, YUVBuffer},
78
};
89

@@ -11,6 +12,8 @@ pub struct OpenH264VideoEncoder {
1112
height: u32,
1213
frame_index: u64,
1314
encoder: Encoder,
15+
headers_cache: Option<Vec<u8>>,
16+
first_frame_encoded: bool,
1417
}
1518

1619
impl OpenH264VideoEncoder {
@@ -20,9 +23,12 @@ impl OpenH264VideoEncoder {
2023
let encoder_config = EncoderConfig::new()
2124
.max_frame_rate(FrameRate::from_hz(config.fps.to_u32() as f32))
2225
.skip_frames(false)
23-
.usage_type(UsageType::ScreenContentRealTime)
2426
.complexity(Complexity::High)
25-
.profile(Profile::Baseline);
27+
.background_detection(false)
28+
.adaptive_quantization(false)
29+
.profile(Profile::Baseline)
30+
.rate_control_mode(RateControlMode::Off)
31+
.usage_type(UsageType::ScreenContentRealTime);
2632

2733
let encoder = Encoder::with_api_config(OpenH264API::from_source(), encoder_config)
2834
.map_err(|e| {
@@ -36,8 +42,137 @@ impl OpenH264VideoEncoder {
3642
height: config.height,
3743
encoder,
3844
frame_index: 0,
45+
headers_cache: None,
46+
first_frame_encoded: false,
3947
})
4048
}
49+
50+
fn convert_annex_b_to_length_prefixed(&self, annex_b_data: &[u8]) -> Vec<u8> {
51+
let mut result = Vec::new();
52+
let mut i = 0;
53+
54+
// Parse Annex B NAL units and convert to length-prefixed format
55+
while i < annex_b_data.len() {
56+
// Look for NAL start codes (00 00 00 01 or 00 00 01)
57+
let start_code_len = if i + 4 <= annex_b_data.len()
58+
&& annex_b_data[i] == 0
59+
&& annex_b_data[i + 1] == 0
60+
&& annex_b_data[i + 2] == 0
61+
&& annex_b_data[i + 3] == 1
62+
{
63+
4
64+
} else if i + 3 <= annex_b_data.len()
65+
&& annex_b_data[i] == 0
66+
&& annex_b_data[i + 1] == 0
67+
&& annex_b_data[i + 2] == 1
68+
{
69+
3
70+
} else {
71+
i += 1;
72+
continue;
73+
};
74+
75+
// Skip the start code
76+
let nal_start = i + start_code_len;
77+
if nal_start >= annex_b_data.len() {
78+
break;
79+
}
80+
81+
// Find the end of this NAL unit (next start code or end of data)
82+
let mut nal_end = nal_start;
83+
while nal_end + 3 <= annex_b_data.len() {
84+
if (annex_b_data[nal_end] == 0
85+
&& annex_b_data[nal_end + 1] == 0
86+
&& annex_b_data[nal_end + 2] == 0
87+
&& annex_b_data[nal_end + 3] == 1)
88+
|| (annex_b_data[nal_end] == 0
89+
&& annex_b_data[nal_end + 1] == 0
90+
&& annex_b_data[nal_end + 2] == 1)
91+
{
92+
break;
93+
}
94+
nal_end += 1;
95+
}
96+
97+
// If we reached the end without finding another start code, go to the actual end
98+
if nal_end + 3 > annex_b_data.len() {
99+
nal_end = annex_b_data.len();
100+
}
101+
102+
let nal_data = &annex_b_data[nal_start..nal_end];
103+
if !nal_data.is_empty() {
104+
// Add length prefix (4 bytes, big-endian)
105+
result.extend_from_slice(&(nal_data.len() as u32).to_be_bytes());
106+
result.extend_from_slice(nal_data);
107+
}
108+
109+
i = nal_end;
110+
}
111+
112+
result
113+
}
114+
115+
fn extract_sps_pps_from_bitstream(&self, bitstream: &[u8]) -> Option<Vec<u8>> {
116+
let mut sps_data = None;
117+
let mut pps_data = None;
118+
let mut result = Vec::new();
119+
let mut i = 0;
120+
121+
// Parse length-prefixed NAL units (this should be the converted format)
122+
while i + 4 <= bitstream.len() {
123+
// Read NAL unit length (big-endian)
124+
let nal_length = ((bitstream[i] as u32) << 24)
125+
| ((bitstream[i + 1] as u32) << 16)
126+
| ((bitstream[i + 2] as u32) << 8)
127+
| (bitstream[i + 3] as u32);
128+
129+
if i + 4 + nal_length as usize > bitstream.len() {
130+
log::warn!("Invalid NAL length {} at position {}", nal_length, i);
131+
break;
132+
}
133+
134+
let nal_start = i + 4;
135+
let nal_end = nal_start + nal_length as usize;
136+
let nal_data = &bitstream[nal_start..nal_end];
137+
138+
if !nal_data.is_empty() {
139+
let nal_type = nal_data[0] & 0x1F;
140+
match nal_type {
141+
7 => {
142+
sps_data = Some(nal_data);
143+
log::debug!("Found SPS: {} bytes", nal_data.len());
144+
}
145+
8 => {
146+
pps_data = Some(nal_data);
147+
log::debug!("Found PPS: {} bytes", nal_data.len());
148+
}
149+
_ => {}
150+
}
151+
}
152+
153+
i = nal_end;
154+
}
155+
156+
// If we found both SPS and PPS, create length-prefixed header data
157+
if let (Some(sps), Some(pps)) = (sps_data, pps_data) {
158+
// Add length prefix (4 bytes, big-endian) for SPS
159+
result.extend_from_slice(&(sps.len() as u32).to_be_bytes());
160+
result.extend_from_slice(sps);
161+
// Add length prefix (4 bytes, big-endian) for PPS
162+
result.extend_from_slice(&(pps.len() as u32).to_be_bytes());
163+
result.extend_from_slice(pps);
164+
165+
log::debug!(
166+
"Extracted SPS ({} bytes) and PPS ({} bytes) from OpenH264 bitstream",
167+
sps.len(),
168+
pps.len()
169+
);
170+
return Some(result);
171+
}
172+
173+
log::warn!("Could not find both SPS and PPS in bitstream");
174+
None
175+
}
41176
}
42177

43178
impl VideoEncoder for OpenH264VideoEncoder {
@@ -58,13 +193,72 @@ impl VideoEncoder for OpenH264VideoEncoder {
58193
RecorderError::VideoEncodingFailed(format!("OpenH264 encoding failed: {:?}", e))
59194
})?;
60195

61-
let encoded_frame = EncodedFrame::Frame((self.frame_index, bitstream.to_vec()));
62-
self.frame_index += 1;
196+
// Convert OpenH264 Annex B output to length-prefixed format
197+
let annex_b_data = bitstream.to_vec();
198+
let converted_data = self.convert_annex_b_to_length_prefixed(&annex_b_data);
63199

200+
// If this is the first frame and we haven't cached headers yet, try to extract SPS/PPS
201+
if !self.first_frame_encoded && self.headers_cache.is_none() {
202+
if let Some(headers) = self.extract_sps_pps_from_bitstream(&converted_data) {
203+
self.headers_cache = Some(headers);
204+
log::info!("Successfully extracted SPS/PPS headers from first OpenH264 frame");
205+
} else {
206+
log::warn!("Could not extract SPS/PPS from first frame");
207+
}
208+
self.first_frame_encoded = true;
209+
}
210+
211+
let encoded_frame = EncodedFrame::Frame((self.frame_index, converted_data));
212+
self.frame_index += 1;
64213
Ok(encoded_frame)
65214
}
66215

67216
fn headers(&mut self) -> Result<Vec<u8>, RecorderError> {
217+
if let Some(ref headers) = self.headers_cache {
218+
return Ok(headers.clone());
219+
}
220+
221+
log::debug!("Encoding test frame to extract SPS/PPS headers from OpenH264");
222+
223+
let test_frame_data = vec![0u8; (self.width * self.height * 3) as usize];
224+
let test_img =
225+
ImageBuffer::<Rgb<u8>, Vec<u8>>::from_raw(self.width, self.height, test_frame_data)
226+
.ok_or_else(|| {
227+
RecorderError::ImageProcessingFailed(
228+
"Failed to create test frame for SPS/PPS extraction".to_string(),
229+
)
230+
})?;
231+
232+
let rgb_source = RgbSliceU8::new(
233+
test_img.as_raw(),
234+
(self.width as usize, self.height as usize),
235+
);
236+
let yuv_buffer = YUVBuffer::from_rgb8_source(rgb_source);
237+
238+
match self.encoder.encode(&yuv_buffer) {
239+
Ok(bitstream) => {
240+
// Convert Annex B format to length-prefixed format
241+
let annex_b_data = bitstream.to_vec();
242+
let converted_data = self.convert_annex_b_to_length_prefixed(&annex_b_data);
243+
244+
if let Some(headers) = self.extract_sps_pps_from_bitstream(&converted_data) {
245+
self.headers_cache = Some(headers.clone());
246+
log::info!("Successfully extracted SPS/PPS headers from OpenH264 test frame");
247+
return Ok(headers);
248+
} else {
249+
log::warn!(
250+
"Could not extract SPS/PPS from OpenH264 test frame, using empty headers"
251+
);
252+
}
253+
}
254+
Err(e) => {
255+
log::warn!(
256+
"Failed to encode test frame for SPS/PPS extraction: {:?}",
257+
e
258+
);
259+
}
260+
}
261+
68262
Ok(vec![])
69263
}
70264

0 commit comments

Comments
 (0)