fix(android): copy captured frames before the Image is released (#16827)

O FrameRaw guardava apenas o ponteiro do ByteBuffer do Image recebido via
JNI. O Kotlin fecha o Image logo após onVideoFrameUpdate, devolvendo o
buffer ao ImageReader; quando a thread de vídeo chamava take(), o
SurfaceFlinger já podia estar desenhando o quadro seguinte no mesmo
buffer, gerando regiões pretas/parciais na imagem remota em qualquer codec
(visível em Android x86, ex.: Bliss OS 13 em Intel UHD).

Agora update() copia o quadro para um buffer próprio enquanto o Image
ainda é válido, e take() copia desse buffer. O áudio usa a mesma
estrutura e passa a ter a mesma garantia.
This commit is contained in:
Victor Lamers authored and GitHub committed 2026-11-03 08:15:44 +08:00
1 parent a85ad89ffc
commit 41f182e636
1 file changed
+22 -36
+22 -36
View File
@@ -16,7 +16,6 @@ use lazy_static::lazy_static;
use serde::Deserialize;
use std::ops::Not;
use std::os::raw::c_void;
use std::sync::atomic::{AtomicPtr, Ordering::SeqCst};
use std::sync::{Mutex, RwLock};
use std::time::{Duration, Instant};
@@ -38,8 +37,8 @@ const MAX_AUDIO_FRAME_TIMEOUT: Duration = Duration::from_millis(1000);
struct FrameRaw {
name: &'static str,
ptr: AtomicPtr<u8>,
len: usize,
data: Vec<u8>,
ready: bool,
last_update: Instant,
timeout: Duration,
enable: bool,
@@ -49,8 +48,8 @@ impl FrameRaw {
fn new(name: &'static str, timeout: Duration) -> Self {
FrameRaw {
name,
ptr: AtomicPtr::default(),
len: 0,
data: Vec::new(),
ready: false,
last_update: Instant::now(),
timeout,
enable: false,
@@ -59,49 +58,36 @@ impl FrameRaw {
fn set_enable(&mut self, value: bool) {
self.enable = value;
self.ptr.store(std::ptr::null_mut(), SeqCst);
self.len = 0;
self.ready = false;
}
fn update(&mut self, data: *mut u8, len: usize) {
if self.enable.not() {
if self.enable.not() || data.is_null() || len == 0 {
return;
}
self.len = len;
self.ptr.store(data, SeqCst);
let src = unsafe { std::slice::from_raw_parts(data, len) };
self.data.clear();
self.data.extend_from_slice(src);
self.ready = true;
self.last_update = Instant::now();
}
// take inner data as slice
// release when success
fn take<'a>(&mut self, dst: &mut Vec<u8>, last: &mut Vec<u8>) -> Option<()> {
if self.enable.not() {
if self.enable.not() || !self.ready {
return None;
}
let ptr = self.ptr.load(SeqCst);
if ptr.is_null() || self.len == 0 {
None
} else {
if self.last_update.elapsed() > self.timeout {
log::trace!("Failed to take {} raw,timeout!", self.name);
return None;
}
let slice = unsafe { std::slice::from_raw_parts(ptr, self.len) };
self.release();
if last.len() == slice.len() && crate::would_block_if_equal(last, slice).is_err() {
return None;
}
dst.resize(slice.len(), 0);
unsafe {
std::ptr::copy_nonoverlapping(slice.as_ptr(), dst.as_mut_ptr(), slice.len());
}
Some(())
if self.last_update.elapsed() > self.timeout {
log::trace!("Failed to take {} raw,timeout!", self.name);
return None;
}
}
fn release(&mut self) {
self.len = 0;
self.ptr.store(std::ptr::null_mut(), SeqCst);
self.ready = false;
if last.len() == self.data.len() && crate::would_block_if_equal(last, &self.data).is_err()
{
return None;
}
dst.clear();
dst.extend_from_slice(&self.data);
Some(())
}
}