|
@@ -0,0 +1,213 @@
|
|
|
|
|
+/* This file is part of DarkFi (https://dark.fi)
|
|
|
|
|
+ *
|
|
|
|
|
+ * Copyright (C) 2020-2025 Dyne.org foundation
|
|
|
|
|
+ *
|
|
|
|
|
+ * This program is free software: you can redistribute it and/or modify
|
|
|
|
|
+ * it under the terms of the GNU Affero General Public License as
|
|
|
|
|
+ * published by the Free Software Foundation, either version 3 of the
|
|
|
|
|
+ * License, or (at your option) any later version.
|
|
|
|
|
+ *
|
|
|
|
|
+ * This program is distributed in the hope that it will be useful,
|
|
|
|
|
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
+ * GNU Affero General Public License for more details.
|
|
|
|
|
+ *
|
|
|
|
|
+ * You should have received a copy of the GNU Affero General Public License
|
|
|
|
|
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
|
+ */
|
|
|
|
|
+
|
|
|
|
|
+//! Android video decoder JNI functions for MediaCodec integration
|
|
|
|
|
+
|
|
|
|
|
+use miniquad::native::android::{self, ndk_sys, ndk_utils};
|
|
|
|
|
+use parking_lot::Mutex as SyncMutex;
|
|
|
|
|
+use std::{
|
|
|
|
|
+ collections::HashMap,
|
|
|
|
|
+ sync::{mpsc, LazyLock},
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+pub struct DecodedFrame {
|
|
|
|
|
+ pub width: usize,
|
|
|
|
|
+ pub height: usize,
|
|
|
|
|
+ pub y_data: Vec<u8>,
|
|
|
|
|
+ pub u_data: Vec<u8>,
|
|
|
|
|
+ pub v_data: Vec<u8>,
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+struct VideoDecoderGlobals {
|
|
|
|
|
+ senders: HashMap<usize, mpsc::Sender<DecodedFrame>>,
|
|
|
|
|
+ next_id: usize,
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+unsafe impl Send for VideoDecoderGlobals {}
|
|
|
|
|
+unsafe impl Sync for VideoDecoderGlobals {}
|
|
|
|
|
+
|
|
|
|
|
+static VIDEO_DECODER_GLOBALS: LazyLock<SyncMutex<VideoDecoderGlobals>> =
|
|
|
|
|
+ LazyLock::new(|| SyncMutex::new(VideoDecoderGlobals { senders: HashMap::new(), next_id: 0 }));
|
|
|
|
|
+
|
|
|
|
|
+fn send(id: usize, frame: DecodedFrame) {
|
|
|
|
|
+ let globals = &VIDEO_DECODER_GLOBALS.lock();
|
|
|
|
|
+ if let Some(sender) = globals.senders.get(&id) {
|
|
|
|
|
+ let _ = sender.send(frame);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+pub fn register(sender: mpsc::Sender<DecodedFrame>) -> usize {
|
|
|
|
|
+ let mut globals = VIDEO_DECODER_GLOBALS.lock();
|
|
|
|
|
+ let id = globals.next_id;
|
|
|
|
|
+ globals.next_id += 1;
|
|
|
|
|
+ globals.senders.insert(id, sender);
|
|
|
|
|
+ id
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+pub fn unregister(id: usize) {
|
|
|
|
|
+ VIDEO_DECODER_GLOBALS.lock().senders.remove(&id);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+#[no_mangle]
|
|
|
|
|
+pub unsafe extern "C" fn Java_videodecode_VideoDecoder_onFrameDecoded(
|
|
|
|
|
+ env: *mut ndk_sys::JNIEnv,
|
|
|
|
|
+ _: ndk_sys::jobject,
|
|
|
|
|
+ decoder_id: ndk_sys::jint,
|
|
|
|
|
+ y_data: ndk_sys::jbyteArray,
|
|
|
|
|
+ u_data: ndk_sys::jbyteArray,
|
|
|
|
|
+ v_data: ndk_sys::jbyteArray,
|
|
|
|
|
+ width: ndk_sys::jint,
|
|
|
|
|
+ height: ndk_sys::jint,
|
|
|
|
|
+) {
|
|
|
|
|
+ use std::slice;
|
|
|
|
|
+
|
|
|
|
|
+ let get_array_length = (**env).GetArrayLength.unwrap();
|
|
|
|
|
+ let y_len = get_array_length(env, y_data) as usize;
|
|
|
|
|
+ let u_len = get_array_length(env, u_data) as usize;
|
|
|
|
|
+ let v_len = get_array_length(env, v_data) as usize;
|
|
|
|
|
+
|
|
|
|
|
+ let get_byte_array_elements = (**env).GetByteArrayElements.unwrap();
|
|
|
|
|
+ let y_ptr = get_byte_array_elements(env, y_data, std::ptr::null_mut()) as *const u8;
|
|
|
|
|
+ let u_ptr = get_byte_array_elements(env, u_data, std::ptr::null_mut()) as *const u8;
|
|
|
|
|
+ let v_ptr = get_byte_array_elements(env, v_data, std::ptr::null_mut()) as *const u8;
|
|
|
|
|
+
|
|
|
|
|
+ let y_vec = slice::from_raw_parts(y_ptr, y_len).to_vec();
|
|
|
|
|
+ let u_vec = slice::from_raw_parts(u_ptr, u_len).to_vec();
|
|
|
|
|
+ let v_vec = slice::from_raw_parts(v_ptr, v_len).to_vec();
|
|
|
|
|
+
|
|
|
|
|
+ let release_byte_array_elements = (**env).ReleaseByteArrayElements.unwrap();
|
|
|
|
|
+ release_byte_array_elements(env, y_data, y_ptr as *mut i8, 0);
|
|
|
|
|
+ release_byte_array_elements(env, u_data, u_ptr as *mut i8, 0);
|
|
|
|
|
+ release_byte_array_elements(env, v_data, v_ptr as *mut i8, 0);
|
|
|
|
|
+
|
|
|
|
|
+ let frame = DecodedFrame {
|
|
|
|
|
+ width: width as usize,
|
|
|
|
|
+ height: height as usize,
|
|
|
|
|
+ y_data: y_vec,
|
|
|
|
|
+ u_data: u_vec,
|
|
|
|
|
+ v_data: v_vec,
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ send(decoder_id as usize, frame);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+pub struct VideoDecoderHandle {
|
|
|
|
|
+ pub obj: ndk_sys::jobject,
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+impl Drop for VideoDecoderHandle {
|
|
|
|
|
+ fn drop(&mut self) {
|
|
|
|
|
+ unsafe {
|
|
|
|
|
+ let env = android::attach_jni_env();
|
|
|
|
|
+ let delete_local_ref = (**env).DeleteLocalRef.unwrap();
|
|
|
|
|
+ delete_local_ref(env, self.obj);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+pub fn videodecoder_init(path: &str) -> Option<VideoDecoderHandle> {
|
|
|
|
|
+ unsafe {
|
|
|
|
|
+ let env = android::attach_jni_env();
|
|
|
|
|
+
|
|
|
|
|
+ // Call MainActivity.createVideoDecoder() helper method
|
|
|
|
|
+ let activity_class = (**env).GetObjectClass.unwrap()(env, android::ACTIVITY);
|
|
|
|
|
+ let create_method = (**env).GetMethodID.unwrap()(
|
|
|
|
|
+ env,
|
|
|
|
|
+ activity_class,
|
|
|
|
|
+ b"createVideoDecoder\0".as_ptr() as _,
|
|
|
|
|
+ b"()Lvideodecode/VideoDecoder;\0".as_ptr() as _,
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ let decoder_obj = (**env).CallObjectMethod.unwrap()(env, android::ACTIVITY, create_method);
|
|
|
|
|
+
|
|
|
|
|
+ let delete_local_ref = (**env).DeleteLocalRef.unwrap();
|
|
|
|
|
+ delete_local_ref(env, activity_class);
|
|
|
|
|
+
|
|
|
|
|
+ if decoder_obj.is_null() {
|
|
|
|
|
+ error!(target: "android::vid", "Failed to create VideoDecoder object");
|
|
|
|
|
+ return None;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ let cpath = std::ffi::CString::new(path).unwrap();
|
|
|
|
|
+ let jpath = (**env).NewStringUTF.unwrap()(env, cpath.as_ptr());
|
|
|
|
|
+
|
|
|
|
|
+ // Get VideoDecoder class
|
|
|
|
|
+ let decoder_class = (**env).GetObjectClass.unwrap()(env, decoder_obj);
|
|
|
|
|
+
|
|
|
|
|
+ let init_video = (**env).GetMethodID.unwrap()(
|
|
|
|
|
+ env,
|
|
|
|
|
+ decoder_class,
|
|
|
|
|
+ b"init\0".as_ptr() as _,
|
|
|
|
|
+ b"(Ljava/lang/String;)Z\0".as_ptr() as _,
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ let result = (**env).CallBooleanMethod.unwrap()(env, decoder_obj, init_video, jpath);
|
|
|
|
|
+
|
|
|
|
|
+ delete_local_ref(env, jpath);
|
|
|
|
|
+ delete_local_ref(env, decoder_class);
|
|
|
|
|
+
|
|
|
|
|
+ if result == 0 {
|
|
|
|
|
+ error!(target: "android::vid", "VideoDecoder.init() failed");
|
|
|
|
|
+ return None;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Some(VideoDecoderHandle { obj: decoder_obj })
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+pub fn videodecoder_set_id(decoder_obj: ndk_sys::jobject, id: usize) {
|
|
|
|
|
+ unsafe {
|
|
|
|
|
+ let env = android::attach_jni_env();
|
|
|
|
|
+
|
|
|
|
|
+ let class_ptr = (**env).GetObjectClass.unwrap()(env, decoder_obj);
|
|
|
|
|
+
|
|
|
|
|
+ let method_id = (**env).GetMethodID.unwrap()(
|
|
|
|
|
+ env,
|
|
|
|
|
+ class_ptr,
|
|
|
|
|
+ b"setDecoderId\0".as_ptr() as _,
|
|
|
|
|
+ b"(I)V\0".as_ptr() as _,
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ (**env).CallVoidMethod.unwrap()(env, decoder_obj, method_id, id as i32);
|
|
|
|
|
+
|
|
|
|
|
+ let delete_local_ref = (**env).DeleteLocalRef.unwrap();
|
|
|
|
|
+ delete_local_ref(env, class_ptr);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+pub fn videodecoder_decode_all(decoder_obj: ndk_sys::jobject) -> i32 {
|
|
|
|
|
+ unsafe {
|
|
|
|
|
+ let env = android::attach_jni_env();
|
|
|
|
|
+
|
|
|
|
|
+ let class_ptr = (**env).GetObjectClass.unwrap()(env, decoder_obj);
|
|
|
|
|
+
|
|
|
|
|
+ let method_id = (**env).GetMethodID.unwrap()(
|
|
|
|
|
+ env,
|
|
|
|
|
+ class_ptr,
|
|
|
|
|
+ b"decodeAll\0".as_ptr() as _,
|
|
|
|
|
+ b"()I\0".as_ptr() as _,
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ let result = (**env).CallIntMethod.unwrap()(env, decoder_obj, method_id);
|
|
|
|
|
+
|
|
|
|
|
+ let delete_local_ref = (**env).DeleteLocalRef.unwrap();
|
|
|
|
|
+ delete_local_ref(env, class_ptr);
|
|
|
|
|
+
|
|
|
|
|
+ result
|
|
|
|
|
+ }
|
|
|
|
|
+}
|