358 lines
10 KiB
Rust
358 lines
10 KiB
Rust
use jni::objects::JByteBuffer;
|
|
use jni::objects::JString;
|
|
use jni::objects::JValue;
|
|
use jni::sys::jboolean;
|
|
use jni::JNIEnv;
|
|
use jni::{
|
|
objects::{GlobalRef, JClass, JObject},
|
|
JavaVM,
|
|
};
|
|
|
|
use jni::errors::{Error as JniError, Result as JniResult};
|
|
use lazy_static::lazy_static;
|
|
use serde::Deserialize;
|
|
use std::ops::Not;
|
|
use std::sync::atomic::{AtomicPtr, Ordering::SeqCst};
|
|
use std::sync::{Mutex, RwLock};
|
|
use std::time::{Duration, Instant};
|
|
lazy_static! {
|
|
static ref JVM: RwLock<Option<JavaVM>> = RwLock::new(None);
|
|
static ref MAIN_SERVICE_CTX: RwLock<Option<GlobalRef>> = RwLock::new(None); // MainService -> video service / audio service / info
|
|
static ref VIDEO_RAW: Mutex<FrameRaw> = Mutex::new(FrameRaw::new("video", MAX_VIDEO_FRAME_TIMEOUT));
|
|
static ref AUDIO_RAW: Mutex<FrameRaw> = Mutex::new(FrameRaw::new("audio", MAX_AUDIO_FRAME_TIMEOUT));
|
|
static ref NDK_CONTEXT_INITED: Mutex<bool> = Default::default();
|
|
static ref MEDIA_CODEC_INFOS: RwLock<Option<MediaCodecInfos>> = RwLock::new(None);
|
|
}
|
|
|
|
const MAX_VIDEO_FRAME_TIMEOUT: Duration = Duration::from_millis(100);
|
|
const MAX_AUDIO_FRAME_TIMEOUT: Duration = Duration::from_millis(1000);
|
|
|
|
struct FrameRaw {
|
|
name: &'static str,
|
|
ptr: AtomicPtr<u8>,
|
|
len: usize,
|
|
last_update: Instant,
|
|
timeout: Duration,
|
|
enable: bool,
|
|
}
|
|
|
|
impl FrameRaw {
|
|
fn new(name: &'static str, timeout: Duration) -> Self {
|
|
FrameRaw {
|
|
name,
|
|
ptr: AtomicPtr::default(),
|
|
len: 0,
|
|
last_update: Instant::now(),
|
|
timeout,
|
|
enable: false,
|
|
}
|
|
}
|
|
|
|
fn set_enable(&mut self, value: bool) {
|
|
self.enable = value;
|
|
self.ptr.store(std::ptr::null_mut(), SeqCst);
|
|
self.len = 0;
|
|
}
|
|
|
|
fn update(&mut self, data: *mut u8, len: usize) {
|
|
if self.enable.not() {
|
|
return;
|
|
}
|
|
self.len = len;
|
|
self.ptr.store(data, SeqCst);
|
|
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() {
|
|
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(())
|
|
}
|
|
}
|
|
|
|
fn release(&mut self) {
|
|
self.len = 0;
|
|
self.ptr.store(std::ptr::null_mut(), SeqCst);
|
|
}
|
|
}
|
|
|
|
pub fn get_video_raw<'a>(dst: &mut Vec<u8>, last: &mut Vec<u8>) -> Option<()> {
|
|
VIDEO_RAW.lock().ok()?.take(dst, last)
|
|
}
|
|
|
|
pub fn get_audio_raw<'a>(dst: &mut Vec<u8>, last: &mut Vec<u8>) -> Option<()> {
|
|
AUDIO_RAW.lock().ok()?.take(dst, last)
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "system" fn Java_ffi_FFI_onVideoFrameUpdate(
|
|
env: JNIEnv,
|
|
_class: JClass,
|
|
buffer: JObject,
|
|
) {
|
|
let jb = JByteBuffer::from(buffer);
|
|
if let Ok(data) = env.get_direct_buffer_address(&jb) {
|
|
if let Ok(len) = env.get_direct_buffer_capacity(&jb) {
|
|
VIDEO_RAW.lock().unwrap().update(data, len);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "system" fn Java_ffi_FFI_onAudioFrameUpdate(
|
|
env: JNIEnv,
|
|
_class: JClass,
|
|
buffer: JObject,
|
|
) {
|
|
let jb = JByteBuffer::from(buffer);
|
|
if let Ok(data) = env.get_direct_buffer_address(&jb) {
|
|
if let Ok(len) = env.get_direct_buffer_capacity(&jb) {
|
|
AUDIO_RAW.lock().unwrap().update(data, len);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "system" fn Java_ffi_FFI_setFrameRawEnable(
|
|
env: JNIEnv,
|
|
_class: JClass,
|
|
name: JString,
|
|
value: jboolean,
|
|
) {
|
|
let mut env = env;
|
|
if let Ok(name) = env.get_string(&name) {
|
|
let name: String = name.into();
|
|
let value = value.eq(&1);
|
|
if name.eq("video") {
|
|
VIDEO_RAW.lock().unwrap().set_enable(value);
|
|
} else if name.eq("audio") {
|
|
AUDIO_RAW.lock().unwrap().set_enable(value);
|
|
}
|
|
};
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "system" fn Java_ffi_FFI_init(env: JNIEnv, _class: JClass, ctx: JObject) {
|
|
log::debug!("MainService init from java");
|
|
if let Ok(jvm) = env.get_java_vm() {
|
|
*JVM.write().unwrap() = Some(jvm);
|
|
if let Ok(context) = env.new_global_ref(ctx) {
|
|
*MAIN_SERVICE_CTX.write().unwrap() = Some(context);
|
|
init_ndk_context().ok();
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, Clone)]
|
|
pub struct MediaCodecInfo {
|
|
pub name: String,
|
|
pub is_encoder: bool,
|
|
#[serde(default)]
|
|
pub hw: Option<bool>, // api 29+
|
|
pub mime_type: String,
|
|
pub surface: bool,
|
|
pub nv12: bool,
|
|
#[serde(default)]
|
|
pub low_latency: Option<bool>, // api 30+, decoder
|
|
pub min_bitrate: u32,
|
|
pub max_bitrate: u32,
|
|
pub min_width: usize,
|
|
pub max_width: usize,
|
|
pub min_height: usize,
|
|
pub max_height: usize,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, Clone)]
|
|
pub struct MediaCodecInfos {
|
|
pub version: usize,
|
|
pub w: usize, // aligned
|
|
pub h: usize, // aligned
|
|
pub codecs: Vec<MediaCodecInfo>,
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "system" fn Java_ffi_FFI_setCodecInfo(env: JNIEnv, _class: JClass, info: JString) {
|
|
let mut env = env;
|
|
if let Ok(info) = env.get_string(&info) {
|
|
let info: String = info.into();
|
|
if let Ok(infos) = serde_json::from_str::<MediaCodecInfos>(&info) {
|
|
*MEDIA_CODEC_INFOS.write().unwrap() = Some(infos);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn get_codec_info() -> Option<MediaCodecInfos> {
|
|
MEDIA_CODEC_INFOS.read().unwrap().as_ref().cloned()
|
|
}
|
|
|
|
pub fn clear_codec_info() {
|
|
*MEDIA_CODEC_INFOS.write().unwrap() = None;
|
|
}
|
|
|
|
// another way to fix "reference table overflow" error caused by new_string and call_main_service_pointer_input frequently calld
|
|
// is below, but here I change kind from string to int for performance
|
|
/*
|
|
env.with_local_frame(10, || {
|
|
let kind = env.new_string(kind)?;
|
|
env.call_method(
|
|
ctx,
|
|
"rustPointerInput",
|
|
"(Ljava/lang/String;III)V",
|
|
&[
|
|
JValue::Object(&JObject::from(kind)),
|
|
JValue::Int(mask),
|
|
JValue::Int(x),
|
|
JValue::Int(y),
|
|
],
|
|
)?;
|
|
Ok(JObject::null())
|
|
})?;
|
|
*/
|
|
pub fn call_main_service_pointer_input(kind: &str, mask: i32, x: i32, y: i32) -> JniResult<()> {
|
|
if let (Some(jvm), Some(ctx)) = (
|
|
JVM.read().unwrap().as_ref(),
|
|
MAIN_SERVICE_CTX.read().unwrap().as_ref(),
|
|
) {
|
|
let mut env = jvm.attach_current_thread_as_daemon()?;
|
|
let kind = if kind == "touch" { 0 } else { 1 };
|
|
env.call_method(
|
|
ctx,
|
|
"rustPointerInput",
|
|
"(IIII)V",
|
|
&[
|
|
JValue::Int(kind),
|
|
JValue::Int(mask),
|
|
JValue::Int(x),
|
|
JValue::Int(y),
|
|
],
|
|
)?;
|
|
return Ok(());
|
|
} else {
|
|
return Err(JniError::ThrowFailed(-1));
|
|
}
|
|
}
|
|
|
|
pub fn call_main_service_key_event(data: &[u8]) -> JniResult<()> {
|
|
if let (Some(jvm), Some(ctx)) = (
|
|
JVM.read().unwrap().as_ref(),
|
|
MAIN_SERVICE_CTX.read().unwrap().as_ref(),
|
|
) {
|
|
let mut env = jvm.attach_current_thread_as_daemon()?;
|
|
let data = env.byte_array_from_slice(data)?;
|
|
|
|
env.call_method(
|
|
ctx,
|
|
"rustKeyEventInput",
|
|
"([B)V",
|
|
&[JValue::Object(&JObject::from(data))],
|
|
)?;
|
|
return Ok(());
|
|
} else {
|
|
return Err(JniError::ThrowFailed(-1));
|
|
}
|
|
}
|
|
|
|
pub fn call_main_service_get_by_name(name: &str) -> JniResult<String> {
|
|
if let (Some(jvm), Some(ctx)) = (
|
|
JVM.read().unwrap().as_ref(),
|
|
MAIN_SERVICE_CTX.read().unwrap().as_ref(),
|
|
) {
|
|
let mut env = jvm.attach_current_thread_as_daemon()?;
|
|
let res = env.with_local_frame(10, |env| -> JniResult<String> {
|
|
let name = env.new_string(name)?;
|
|
let res = env
|
|
.call_method(
|
|
ctx,
|
|
"rustGetByName",
|
|
"(Ljava/lang/String;)Ljava/lang/String;",
|
|
&[JValue::Object(&JObject::from(name))],
|
|
)?
|
|
.l()?;
|
|
let res = JString::from(res);
|
|
let res = env.get_string(&res)?;
|
|
let res = res.to_string_lossy().to_string();
|
|
Ok(res)
|
|
})?;
|
|
Ok(res)
|
|
} else {
|
|
return Err(JniError::ThrowFailed(-1));
|
|
}
|
|
}
|
|
|
|
pub fn call_main_service_set_by_name(
|
|
name: &str,
|
|
arg1: Option<&str>,
|
|
arg2: Option<&str>,
|
|
) -> JniResult<()> {
|
|
if let (Some(jvm), Some(ctx)) = (
|
|
JVM.read().unwrap().as_ref(),
|
|
MAIN_SERVICE_CTX.read().unwrap().as_ref(),
|
|
) {
|
|
let mut env = jvm.attach_current_thread_as_daemon()?;
|
|
env.with_local_frame(10, |env| -> JniResult<()> {
|
|
let name = env.new_string(name)?;
|
|
let arg1 = env.new_string(arg1.unwrap_or(""))?;
|
|
let arg2 = env.new_string(arg2.unwrap_or(""))?;
|
|
|
|
env.call_method(
|
|
ctx,
|
|
"rustSetByName",
|
|
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V",
|
|
&[
|
|
JValue::Object(&JObject::from(name)),
|
|
JValue::Object(&JObject::from(arg1)),
|
|
JValue::Object(&JObject::from(arg2)),
|
|
],
|
|
)?;
|
|
Ok(())
|
|
})?;
|
|
return Ok(());
|
|
} else {
|
|
return Err(JniError::ThrowFailed(-1));
|
|
}
|
|
}
|
|
|
|
fn init_ndk_context() -> JniResult<()> {
|
|
let mut lock = NDK_CONTEXT_INITED.lock().unwrap();
|
|
if *lock {
|
|
unsafe {
|
|
ndk_context::release_android_context();
|
|
}
|
|
*lock = false;
|
|
}
|
|
if let (Some(jvm), Some(ctx)) = (
|
|
JVM.read().unwrap().as_ref(),
|
|
MAIN_SERVICE_CTX.read().unwrap().as_ref(),
|
|
) {
|
|
unsafe {
|
|
ndk_context::initialize_android_context(
|
|
jvm.get_java_vm_pointer() as _,
|
|
ctx.as_obj().as_raw() as _,
|
|
);
|
|
}
|
|
*lock = true;
|
|
return Ok(());
|
|
}
|
|
Err(JniError::ThrowFailed(-1))
|
|
}
|