fix(client): make H.264 decoder selection startup-safe for previews
This commit is contained in:
parent
22a92c9fe7
commit
9616ba00ea
@ -823,9 +823,22 @@ impl PreviewFeed {
|
||||
session_active_flag,
|
||||
active_bindings_flag,
|
||||
running_flag,
|
||||
shared_state,
|
||||
log_sink,
|
||||
Arc::clone(&shared_state),
|
||||
Arc::clone(&log_sink),
|
||||
) {
|
||||
set_shared_status(
|
||||
&shared_state,
|
||||
&log_sink,
|
||||
monitor_id,
|
||||
"Preview pipeline setup failed. See session log.",
|
||||
true,
|
||||
);
|
||||
log_preview_issue(
|
||||
&shared_state,
|
||||
&log_sink,
|
||||
monitor_id,
|
||||
&format!("Preview feed startup failed: {err:#}"),
|
||||
);
|
||||
warn!(monitor_id, ?err, "launcher preview feed exited");
|
||||
}
|
||||
});
|
||||
@ -981,7 +994,33 @@ fn run_preview_feed(
|
||||
shared: Arc<Mutex<SharedPreviewState>>,
|
||||
log_sink: Arc<Mutex<Option<std::sync::mpsc::Sender<String>>>>,
|
||||
) -> Result<()> {
|
||||
let (pipeline, appsrc, appsink, decoder_name) = build_preview_pipeline(profile)?;
|
||||
let mut startup_error = None;
|
||||
let mut selected = None;
|
||||
for decoder_name in preview_decoder_candidates() {
|
||||
match build_preview_pipeline(profile, &decoder_name) {
|
||||
Ok((pipeline, appsrc, appsink, decoder_label)) => {
|
||||
match pipeline
|
||||
.set_state(gst::State::Playing)
|
||||
.context("starting launcher preview pipeline")
|
||||
{
|
||||
Ok(_) => {
|
||||
selected = Some((pipeline, appsrc, appsink, decoder_label));
|
||||
break;
|
||||
}
|
||||
Err(err) => {
|
||||
let _ = pipeline.set_state(gst::State::Null);
|
||||
startup_error = Some(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
startup_error = Some(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
let (pipeline, appsrc, appsink, decoder_name) = selected.ok_or_else(|| {
|
||||
startup_error.unwrap_or_else(|| anyhow::anyhow!("no usable H.264 decoder"))
|
||||
})?;
|
||||
let parser = pipeline.by_name("preview_parse");
|
||||
let decoder = pipeline.by_name("decoder");
|
||||
if let Ok(mut slot) = shared.lock() {
|
||||
@ -997,10 +1036,6 @@ fn run_preview_feed(
|
||||
}
|
||||
});
|
||||
}
|
||||
pipeline
|
||||
.set_state(gst::State::Playing)
|
||||
.context("starting launcher preview pipeline")?;
|
||||
|
||||
{
|
||||
let shared = Arc::clone(&shared);
|
||||
let appsink = appsink.clone();
|
||||
@ -1410,8 +1445,8 @@ fn looks_like_preview_problem(status: &str) -> bool {
|
||||
#[cfg(not(coverage))]
|
||||
fn build_preview_pipeline(
|
||||
profile: PreviewProfile,
|
||||
decoder_name: &str,
|
||||
) -> Result<(gst::Pipeline, gst_app::AppSrc, gst_app::AppSink, String)> {
|
||||
let decoder_name = pick_h264_decoder();
|
||||
let source_mode = eye_source_mode_for_request(
|
||||
profile.requested_width.max(2) as u32,
|
||||
profile.requested_height.max(2) as u32,
|
||||
@ -1421,10 +1456,11 @@ fn build_preview_pipeline(
|
||||
let desc = format!(
|
||||
"appsrc name=src is-live=true format=time do-timestamp=true block=false ! \
|
||||
queue max-size-buffers=6 max-size-time=0 max-size-bytes=0 leaky=downstream ! \
|
||||
h264parse name=preview_parse disable-passthrough=true ! {decoder_name} name=decoder ! videoconvert ! \
|
||||
h264parse name=preview_parse disable-passthrough=true ! {} name=decoder ! videoconvert ! \
|
||||
videoscale add-borders=false ! \
|
||||
video/x-raw,format=RGBA,width=(int){render_width},height=(int){render_height},pixel-aspect-ratio=1/1 ! \
|
||||
appsink name=sink emit-signals=false sync=false max-buffers=1 drop=true",
|
||||
decoder_name,
|
||||
);
|
||||
let pipeline = gst::parse::launch(&desc)?
|
||||
.downcast::<gst::Pipeline>()
|
||||
@ -1457,7 +1493,7 @@ fn build_preview_pipeline(
|
||||
.build(),
|
||||
));
|
||||
|
||||
Ok((pipeline, appsrc, appsink, decoder_name))
|
||||
Ok((pipeline, appsrc, appsink, decoder_name.to_string()))
|
||||
}
|
||||
|
||||
#[cfg(not(coverage))]
|
||||
@ -1481,6 +1517,40 @@ fn preview_render_size(
|
||||
(render_w.max(2), render_h.max(2))
|
||||
}
|
||||
|
||||
#[cfg(not(coverage))]
|
||||
fn preview_decoder_candidates() -> Vec<String> {
|
||||
let mut candidates = Vec::new();
|
||||
let preferred = pick_h264_decoder();
|
||||
if !preferred.trim().is_empty() {
|
||||
candidates.push(preferred);
|
||||
}
|
||||
for name in [
|
||||
"avdec_h264",
|
||||
"openh264dec",
|
||||
"vah264dec",
|
||||
"vaapih264dec",
|
||||
"v4l2h264dec",
|
||||
"v4l2slh264dec",
|
||||
"nvh264dec",
|
||||
"nvh264sldec",
|
||||
"decodebin",
|
||||
] {
|
||||
if name == "decodebin" || gst::ElementFactory::find(name).is_some() {
|
||||
candidates.push(name.to_string());
|
||||
}
|
||||
}
|
||||
candidates.sort();
|
||||
candidates.dedup();
|
||||
if let Some(pos) = candidates
|
||||
.iter()
|
||||
.position(|name| name == &pick_h264_decoder())
|
||||
{
|
||||
let preferred = candidates.remove(pos);
|
||||
candidates.insert(0, preferred);
|
||||
}
|
||||
candidates
|
||||
}
|
||||
|
||||
#[cfg(not(coverage))]
|
||||
fn push_preview_packet(appsrc: &gst_app::AppSrc, pkt: VideoPacket) {
|
||||
let mut buf = gst::Buffer::from_slice(pkt.data);
|
||||
|
||||
@ -16,22 +16,22 @@ fn pick_h264_decoder() -> String {
|
||||
if name.eq_ignore_ascii_case("decodebin") {
|
||||
return "decodebin".to_string();
|
||||
}
|
||||
if !name.is_empty() && gst::ElementFactory::find(name).is_some() {
|
||||
if !name.is_empty() && buildable_decoder(name) {
|
||||
return name.to_string();
|
||||
}
|
||||
}
|
||||
|
||||
for name in [
|
||||
"avdec_h264",
|
||||
"openh264dec",
|
||||
"nvh264dec",
|
||||
"nvh264sldec",
|
||||
"vah264dec",
|
||||
"vaapih264dec",
|
||||
"v4l2h264dec",
|
||||
"v4l2slh264dec",
|
||||
"openh264dec",
|
||||
"avdec_h264",
|
||||
] {
|
||||
if gst::ElementFactory::find(name).is_some() {
|
||||
if buildable_decoder(name) {
|
||||
return name.to_string();
|
||||
}
|
||||
}
|
||||
@ -39,6 +39,10 @@ fn pick_h264_decoder() -> String {
|
||||
"decodebin".to_string()
|
||||
}
|
||||
|
||||
fn buildable_decoder(name: &str) -> bool {
|
||||
gst::ElementFactory::find(name).is_some() && gst::ElementFactory::make(name).build().is_ok()
|
||||
}
|
||||
|
||||
pub struct MonitorWindow {
|
||||
_pipeline: gst::Pipeline,
|
||||
src: gst_app::AppSrc,
|
||||
|
||||
@ -17,25 +17,29 @@ pub fn pick_h264_decoder() -> String {
|
||||
if name.eq_ignore_ascii_case("decodebin") {
|
||||
return "decodebin".to_string();
|
||||
}
|
||||
if !name.is_empty() && gst::ElementFactory::find(name).is_some() {
|
||||
if !name.is_empty() && buildable_decoder(name) {
|
||||
return name.to_string();
|
||||
}
|
||||
}
|
||||
|
||||
for name in [
|
||||
"avdec_h264",
|
||||
"openh264dec",
|
||||
"nvh264dec",
|
||||
"nvh264sldec",
|
||||
"vah264dec",
|
||||
"vaapih264dec",
|
||||
"v4l2h264dec",
|
||||
"v4l2slh264dec",
|
||||
"openh264dec",
|
||||
"avdec_h264",
|
||||
] {
|
||||
if gst::ElementFactory::find(name).is_some() {
|
||||
if buildable_decoder(name) {
|
||||
return name.to_string();
|
||||
}
|
||||
}
|
||||
|
||||
"decodebin".to_string()
|
||||
}
|
||||
|
||||
fn buildable_decoder(name: &str) -> bool {
|
||||
gst::ElementFactory::find(name).is_some() && gst::ElementFactory::make(name).build().is_ok()
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user