Compare commits
2 Commits
7c1c9b2e19
...
d5679be3a4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d5679be3a4 | ||
|
|
36f07c92e9 |
23
src/avhw.rs
23
src/avhw.rs
@@ -935,13 +935,22 @@ impl SwEncState {
|
|||||||
let data: &[u8] = unsafe {
|
let data: &[u8] = unsafe {
|
||||||
std::slice::from_raw_parts(raw.data, raw.size as usize)
|
std::slice::from_raw_parts(raw.data, raw.size as usize)
|
||||||
};
|
};
|
||||||
if let Err(e) = tx.send(data.to_vec()) {
|
match tx.try_send(data.to_vec()) {
|
||||||
tracing::warn!(
|
Ok(()) => {}
|
||||||
"WebRTC channel send failed (receiver dropped): {} bytes lost",
|
Err(crossbeam_channel::TrySendError::Full(frame)) => {
|
||||||
e.0.len()
|
tracing::warn!(
|
||||||
);
|
"WebRTC channel full, dropping frame: {} bytes lost",
|
||||||
self.webrtc_disconnected = true;
|
frame.len()
|
||||||
break;
|
);
|
||||||
|
}
|
||||||
|
Err(crossbeam_channel::TrySendError::Disconnected(frame)) => {
|
||||||
|
tracing::warn!(
|
||||||
|
"WebRTC channel disconnected: {} bytes lost",
|
||||||
|
frame.len()
|
||||||
|
);
|
||||||
|
self.webrtc_disconnected = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -164,7 +164,8 @@ impl StatePortal {
|
|||||||
|
|
||||||
// 根据是否启用 WebRTC 选择不同的编码器构造方式
|
// 根据是否启用 WebRTC 选择不同的编码器构造方式
|
||||||
let enc = if let Some(ref tx) = self.webrtc_tx {
|
let enc = if let Some(ref tx) = self.webrtc_tx {
|
||||||
let paused = self.webrtc_paused.as_ref().expect("webrtc_paused must exist when webrtc_tx exists");
|
let paused = self.webrtc_paused.as_ref()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("internal invariant broken: webrtc_paused missing while WebRTC mode is active"))?;
|
||||||
avhw::SwEncState::new_webrtc(
|
avhw::SwEncState::new_webrtc(
|
||||||
&drm_path,
|
&drm_path,
|
||||||
frame.width,
|
frame.width,
|
||||||
@@ -179,9 +180,11 @@ impl StatePortal {
|
|||||||
)?
|
)?
|
||||||
} else {
|
} else {
|
||||||
// MP4 模式:编码输出写入文件
|
// MP4 模式:编码输出写入文件
|
||||||
|
let output_path = self.args.output.as_deref()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("--output is required in MP4 file output mode; use --port > 0 for WebRTC mode"))?;
|
||||||
avhw::SwEncState::new(
|
avhw::SwEncState::new(
|
||||||
&drm_path,
|
&drm_path,
|
||||||
std::path::Path::new(self.args.output.as_deref().expect("output required for MP4 mode")),
|
std::path::Path::new(output_path),
|
||||||
frame.width,
|
frame.width,
|
||||||
frame.height,
|
frame.height,
|
||||||
enc_width,
|
enc_width,
|
||||||
@@ -320,6 +323,9 @@ impl StatePortal {
|
|||||||
///
|
///
|
||||||
/// 使用 `enc.take()` 确保编码器只被 flush 一次,即使多次调用也安全(幂等)。
|
/// 使用 `enc.take()` 确保编码器只被 flush 一次,即使多次调用也安全(幂等)。
|
||||||
pub fn shutdown(&mut self) {
|
pub fn shutdown(&mut self) {
|
||||||
|
// 先 drop receiver,使 flush() 中的 try_send() 立即返回 Disconnected
|
||||||
|
// 而非在满通道上阻塞(修复 issue #8 死锁)
|
||||||
|
self.webrtc_rx = None;
|
||||||
if let Some(mut enc) = self.enc.take() {
|
if let Some(mut enc) = self.enc.take() {
|
||||||
if let Err(e) = enc.flush() {
|
if let Err(e) = enc.flush() {
|
||||||
tracing::error!("Flush error during shutdown: {e}");
|
tracing::error!("Flush error during shutdown: {e}");
|
||||||
@@ -553,4 +559,46 @@ mod tests {
|
|||||||
assert_eq!(desc.layers[0].planes[0].pitch, 3840 * 4);
|
assert_eq!(desc.layers[0].planes[0].pitch, 3840 * 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── issue #8 regression ──
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn try_send_full_channel_returns_full_not_block() {
|
||||||
|
let (tx, rx) = crossbeam_channel::bounded::<Vec<u8>>(2);
|
||||||
|
tx.send(vec![1]).unwrap();
|
||||||
|
tx.send(vec![2]).unwrap();
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
tx.try_send(vec![3]),
|
||||||
|
Err(crossbeam_channel::TrySendError::Full(_))
|
||||||
|
));
|
||||||
|
assert_eq!(rx.len(), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn try_send_after_rx_dropped_returns_disconnected() {
|
||||||
|
let (tx, rx) = crossbeam_channel::bounded::<Vec<u8>>(2);
|
||||||
|
drop(rx);
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
tx.try_send(vec![1]),
|
||||||
|
Err(crossbeam_channel::TrySendError::Disconnected(_))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// given: full bounded channel
|
||||||
|
// when: rx is dropped, then try_send
|
||||||
|
// expect: Disconnected, not blocking
|
||||||
|
#[test]
|
||||||
|
fn shutdown_rx_drop_prevents_deadlock_on_full_channel() {
|
||||||
|
let (tx, rx) = crossbeam_channel::bounded::<Vec<u8>>(2);
|
||||||
|
tx.send(vec![1]).unwrap();
|
||||||
|
tx.send(vec![2]).unwrap();
|
||||||
|
drop(rx);
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
tx.try_send(vec![3]),
|
||||||
|
Err(crossbeam_channel::TrySendError::Disconnected(_))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user