use std::os::raw::c_void;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{
Arc,
mpsc::{self, Receiver, SyncSender},
};
use crate::{
error::{XCapError, XCapResult},
video_recorder::Frame,
};
use super::ffi;
fn ensure_callback_registered(
err: ffi::OH_AVSCREEN_CAPTURE_ErrCode,
callback_name: &str,
) -> XCapResult<()> {
if err == ffi::OH_AVSCREEN_CAPTURE_ErrCode::Ok {
return Ok(());
}
Err(XCapError::new(format!(
"{callback_name} registration failed: {:?}",
err
)))
}
struct RecorderShared {
capture: *mut ffi::OH_AVScreenCapture,
width: u32,
height: u32,
tx: SyncSender<Frame>,
frame_running: AtomicBool,
capture_active: AtomicBool,
}
unsafe impl Send for RecorderShared {}
unsafe impl Sync for RecorderShared {}
impl Drop for RecorderShared {
fn drop(&mut self) {
unsafe {
if self.capture_active.load(Ordering::Acquire) {
ffi::OH_AVScreenCapture_StopScreenCapture(self.capture);
}
ffi::OH_AVScreenCapture_Release(self.capture);
}
}
}
#[derive(Clone)]
pub(crate) struct ImplVideoRecorder {
shared: Arc<RecorderShared>,
}
impl std::fmt::Debug for ImplVideoRecorder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ImplVideoRecorder")
.field("width", &self.shared.width)
.field("height", &self.shared.height)
.finish()
}
}
impl ImplVideoRecorder {
pub fn new(display_id: u64, width: u32, height: u32) -> XCapResult<(Self, Receiver<Frame>)> {
let capture = unsafe { ffi::OH_AVScreenCapture_Create() };
if capture.is_null() {
return Err(XCapError::new("OH_AVScreenCapture_Create returned null"));
}
let mut config: ffi::OH_AVScreenCaptureConfig = unsafe { core::mem::zeroed() };
config.captureMode = ffi::OH_CaptureMode::SpecifiedScreen;
config.dataType = ffi::OH_DataType::OriginalStream;
config.videoInfo.videoCapInfo.displayId = display_id;
config.videoInfo.videoCapInfo.videoFrameWidth = width as i32;
config.videoInfo.videoCapInfo.videoFrameHeight = height as i32;
config.videoInfo.videoCapInfo.videoSource = ffi::OH_VideoSourceType::Rgba;
let err = unsafe { ffi::OH_AVScreenCapture_Init(capture, config) };
if err != ffi::OH_AVSCREEN_CAPTURE_ErrCode::Ok {
unsafe { ffi::OH_AVScreenCapture_Release(capture) };
return Err(XCapError::new(format!(
"OH_AVScreenCapture_Init failed: {:?}",
err
)));
}
let (tx, rx) = mpsc::sync_channel::<Frame>(10);
let shared = Arc::new(RecorderShared {
capture,
width,
height,
tx,
frame_running: AtomicBool::new(false),
capture_active: AtomicBool::new(false),
});
let shared_raw = Arc::as_ptr(&shared) as *mut c_void;
unsafe {
ensure_callback_registered(
ffi::OH_AVScreenCapture_SetStateCallback(
capture,
on_state_change,
core::ptr::null_mut(),
),
"OH_AVScreenCapture_SetStateCallback",
)?;
ensure_callback_registered(
ffi::OH_AVScreenCapture_SetErrorCallback(capture, on_error, core::ptr::null_mut()),
"OH_AVScreenCapture_SetErrorCallback",
)?;
ensure_callback_registered(
ffi::OH_AVScreenCapture_SetDataCallback(capture, on_buffer_recorder, shared_raw),
"OH_AVScreenCapture_SetDataCallback",
)?;
}
Ok((ImplVideoRecorder { shared }, rx))
}
pub fn start(&self) -> XCapResult<()> {
self.shared.frame_running.store(true, Ordering::Release);
if !self.shared.capture_active.load(Ordering::Acquire) {
let err = unsafe { ffi::OH_AVScreenCapture_StartScreenCapture(self.shared.capture) };
if err != ffi::OH_AVSCREEN_CAPTURE_ErrCode::Ok {
self.shared.frame_running.store(false, Ordering::Release);
return Err(XCapError::new(format!(
"OH_AVScreenCapture_StartScreenCapture failed: {:?}",
err
)));
}
self.shared.capture_active.store(true, Ordering::Release);
}
Ok(())
}
pub fn stop(&self) -> XCapResult<()> {
self.shared.frame_running.store(false, Ordering::Release);
if self.shared.capture_active.swap(false, Ordering::AcqRel) {
let err = unsafe { ffi::OH_AVScreenCapture_StopScreenCapture(self.shared.capture) };
if err != ffi::OH_AVSCREEN_CAPTURE_ErrCode::Ok {
return Err(XCapError::new(format!(
"OH_AVScreenCapture_StopScreenCapture failed: {:?}",
err
)));
}
}
Ok(())
}
}
unsafe extern "C" fn on_state_change(
_capture: *mut ffi::OH_AVScreenCapture,
state: ffi::OH_AVScreenCaptureStateCode,
_user_data: *mut c_void,
) {
log::debug!("OH_AVScreenCapture state: {:?}", state);
}
unsafe extern "C" fn on_error(
_capture: *mut ffi::OH_AVScreenCapture,
error_code: i32,
_user_data: *mut c_void,
) {
log::error!("OH_AVScreenCapture error {}", error_code);
}
unsafe extern "C" fn on_buffer_recorder(
_capture: *mut ffi::OH_AVScreenCapture,
buffer: *mut ffi::OH_AVBuffer,
buffer_type: ffi::OH_AVScreenCaptureBufferType,
_timestamp: i64,
user_data: *mut c_void,
) {
if buffer_type != ffi::OH_AVScreenCaptureBufferType::Video {
return;
}
let shared = &*(user_data as *const RecorderShared);
if !shared.frame_running.load(Ordering::Acquire) {
return;
}
let addr = ffi::OH_AVBuffer_GetAddr(buffer);
if addr.is_null() {
return;
}
let capacity = ffi::OH_AVBuffer_GetCapacity(buffer);
if capacity <= 0 {
return;
}
let data = std::slice::from_raw_parts(addr, capacity as usize).to_vec();
let frame = Frame::new(shared.width, shared.height, data);
if let Err(e) = shared.tx.try_send(frame) {
log::warn!("xcap: OHOS frame dropped: {}", e);
}
}