rustdesk/rustdesk · warning · anyhow::Error
no valid frame
Error message
no valid frame
What it means
VpxEncoder::encode_to_message encodes the input AND then calls flush(), collecting all produced frames; if after both steps the list is empty it returns Err('no valid frame'). Because flush() is already drained here (unlike the AOM path), an empty result means the encoder genuinely produced nothing for the input — almost always an invalid input or misconfigured encoder rather than latency.
Source
Thrown at libs/scrap/src/common/vpxcodec.rs:190
}
fn encode_to_message(&mut self, input: EncodeInput, ms: i64) -> ResultType<VideoFrame> {
let mut frames = Vec::new();
for ref frame in self
.encode(ms, input.yuv()?, STRIDE_ALIGN)
.with_context(|| "Failed to encode")?
{
frames.push(VpxEncoder::create_frame(frame));
}
for ref frame in self.flush().with_context(|| "Failed to flush")? {
frames.push(VpxEncoder::create_frame(frame));
}
// to-do: flush periodically, e.g. 1 second
if frames.len() > 0 {
Ok(VpxEncoder::create_video_frame(self.id, frames))
} else {
Err(anyhow!("no valid frame"))
}
}
fn yuvfmt(&self) -> crate::EncodeYuvFormat {
self.yuvfmt.clone()
}
#[cfg(feature = "vram")]
fn input_texture(&self) -> bool {
false
}
fn set_quality(&mut self, ratio: f32) -> ResultType<()> {
let mut c = unsafe { *self.ctx.config.enc.to_owned() };
let (q_min, q_max) = Self::calc_q_values(ratio);
c.rc_min_quantizer = q_min;
c.rc_max_quantizer = q_max;
c.rc_target_bitrate = Self::bitrate(self.width as _, self.height as _, ratio);View on GitHub (pinned to 7aa98d43cf)
Solutions
- Check the capture source: skip encode when the frame is empty or dimensions are 0 (e.g. during display transitions).
- Recreate the encoder whenever the capture width/height changes instead of reusing the old config.
- If it fires on the very first frame only, tolerate and continue — but since flush() ran, treat repeated occurrences as an input bug.
- Log input dimensions vs encoder config at the error site.
Example fix
// before
let vf = enc.encode_to_message(input, ms)?; // empty after encode+flush -> Err
// after: guard the capture side
let Ok(yuv) = input.yuv() else { continue; };
if yuv.dim == (0, 0) { continue; } // display switching, no data yet
let vf = enc.encode_to_message(input, ms)?; Defensive patterns
Strategy: validation
Validate before calling
let (w, h) = (frame.width(), frame.height());
if w == 0 || h == 0 { continue; } // display transitioning / hotplug
if (w, h) != (enc.width as u32, enc.height as u32) { recreate_encoder(w, h)?; } Try / catch
match enc.encode_to_message(input, ms) {
Ok(vf) => send(vf),
Err(e) if e.to_string() == "no valid frame" && first_few => continue,
Err(e) => return Err(e), // flush already ran: persistent empties mean bad input
} Prevention
- Skip encoding for zero-sized capture frames during display transitions.
- Recreate the encoder whenever capture dimensions change.
- Because vpxcodec flushes inside encode_to_message, persistent 'no valid frame' points at the input pipeline — instrument capture.
When it happens
Trigger: input.yuv() returning an empty/zero-sized image; encoder width/height mismatched with the submitted frame so libvpx rejects it silently; encoder already in a failed state from an earlier control call.
Common situations: Screen capture returning a 0x0 frame during mode changes/monitor hotplug; resolution change after encoder creation without recreating the encoder; corrupted capture buffers.
Related errors
AI-assisted analysis of rustdesk/rustdesk@7aa98d43cf (2026-08-16).
Data as JSON: /api/errors/10b86e9150362bff.
Report an issue: GitHub.