Browse Source

Expose shared video negotiation and bounded queues to Android JNI

stream-multiview
evgeny 1 week ago
parent
commit
c986221cb6
  1. 1
      tools/chatgui-android/app/src/main/cpp/CMakeLists.txt
  2. 26
      tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallVideoNative.kt
  3. 99
      tools/chatgui-android/jni_bridge/android_call_video_jni.c

1
tools/chatgui-android/app/src/main/cpp/CMakeLists.txt

@ -75,6 +75,7 @@ set_target_properties(utun_core PROPERTIES C_STANDARD 99 C_STANDARD_REQUIRED ON)
add_library(utun SHARED
${JNI_DIR}/android_jni_bridge.c
${JNI_DIR}/android_stream_jni.c
${JNI_DIR}/android_call_video_jni.c
${JNI_DIR}/android_udp_log.c
$<TARGET_OBJECTS:utun_core>
)

26
tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallVideoNative.kt

@ -0,0 +1,26 @@
package com.utun.chat.data
/** Тот же C port, что у desktop: один TX mailbox, ограниченный RX, идентификатор звонка в каждой операции. */
internal object CallVideoNative {
external fun open(id: Long, caps: ByteArray): Boolean
external fun close(id: Long)
external fun peer(id: Long): ByteArray?
external fun status(id: Long): LongArray?
external fun choose(local: ByteArray, peer: ByteArray, desired: LongArray): LongArray?
external fun format(id: Long, format: LongArray, bps: Int): Boolean
external fun applied(id: Long, generation: Long, request: Long, bps: Int)
external fun send(id: Long, meta: LongArray, bytes: ByteArray): Int
external fun pull(id: Long, meta: LongArray): ByteArray?
external fun requestKey(id: Long)
external fun range(id: Long, minimum: Int, maximum: Int): Boolean
}
internal data class CallVideoFormat(val generation: Long, val width: Int, val height: Int, val fps: Int = 30,
val rotation: Int = 0, val codec: Int = 0, val level: Int = 0, val maximum: Int = 4_000_000, val vbv: Int = 1_000_000) {
fun array() = longArrayOf(generation, width.toLong(), height.toLong(), fps.toLong(), rotation.toLong(), codec.toLong(),
level.toLong(), maximum.toLong(), vbv.toLong())
companion object {
fun from(a: LongArray) = CallVideoFormat(a[0], a[1].toInt(), a[2].toInt(), a[3].toInt(), a[4].toInt(),
a[5].toInt(), a[6].toInt(), a[7].toInt(), a[8].toInt())
}
}

99
tools/chatgui-android/jni_bridge/android_call_video_jni.c

@ -0,0 +1,99 @@
#ifdef __ANDROID__
#include <jni.h>
#include <string.h>
#include "../../../src/call/call_video_port.h"
#include "../../../lib/debug_config.h"
#include "../../../lib/mem.h"
/* LongArray format: generation,width,height,fps,rotation,codec,level,maximum_bps,vbv_bits.
* Frame metadata добавляет frame_id,pts_us,flags. JNI не хранит заимствованные массивы. */
static int read_format(JNIEnv *e, jlongArray array, struct call_video_format *f, jlong values[12], int frame) {
if (!array || (*e)->GetArrayLength(e, array) != (frame ? 12 : 9)) goto invalid;
(*e)->GetLongArrayRegion(e, array, 0, frame ? 12 : 9, values);
if ((*e)->ExceptionCheck(e)) return -1;
if (values[0] <= 0 || values[1] < 16 || values[1] > 4096 || values[2] < 16 || values[2] > 4096
|| values[3] < 1 || values[3] > 60 || values[4] < 0 || values[4] > 270 || values[4] % 90
|| values[5] < 0 || values[5] > 2 || values[6] < 0 || values[6] > 255
|| values[7] < 300000 || values[7] > 4000000 || values[8] < 1 || values[8] > 16000000) goto invalid;
*f = (struct call_video_format){values[0], values[1], values[2], values[3], values[4], values[5], values[6], values[7], values[8]};
return 0;
invalid:
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "call video JNI invalid format array"); return -1;
}
static int read_caps(JNIEnv *e, jbyteArray bytes, struct call_video_caps *caps) {
if (!bytes || (*e)->GetArrayLength(e, bytes) != CALL_VIDEO_CAPS_BYTES) {
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "call video JNI invalid CAPS array"); return -1;
}
uint8_t data[CALL_VIDEO_CAPS_BYTES]; (*e)->GetByteArrayRegion(e, bytes, 0, sizeof(data), (jbyte *)data);
return (*e)->ExceptionCheck(e) ? -1 : call_video_caps_parse(data, sizeof(data), caps);
}
JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_CallVideoNative_open(JNIEnv *e, jobject o, jlong id, jbyteArray bytes) {
(void)o; struct call_video_caps caps;
return !read_caps(e, bytes, &caps) && !call_video_port_open(id, &caps);
}
JNIEXPORT void JNICALL Java_com_utun_chat_data_CallVideoNative_close(JNIEnv *e, jobject o, jlong id) {
(void)e; (void)o; call_video_port_close(id);
}
JNIEXPORT jbyteArray JNICALL Java_com_utun_chat_data_CallVideoNative_peer(JNIEnv *e, jobject o, jlong id) {
(void)o; struct call_video_port_status status; uint8_t bytes[CALL_VIDEO_CAPS_BYTES];
if (call_video_port_status(id, &status) || !status.have_peer || call_video_caps_encode(&status.peer, bytes)) return NULL;
jbyteArray out = (*e)->NewByteArray(e, sizeof(bytes));
if (out) (*e)->SetByteArrayRegion(e, out, 0, sizeof(bytes), (jbyte *)bytes);
return out;
}
JNIEXPORT jlongArray JNICALL Java_com_utun_chat_data_CallVideoNative_status(JNIEnv *e, jobject o, jlong id) {
(void)o; struct call_video_port_status s;
if (call_video_port_status(id, &s)) return NULL;
jlong values[] = {s.key_serial, s.rate_generation, s.rate_request, s.rate_bps, s.configured_generation,
s.error, s.minimum_bps, s.maximum_bps};
jlongArray out = (*e)->NewLongArray(e, 8); if (out) (*e)->SetLongArrayRegion(e, out, 0, 8, values); return out;
}
JNIEXPORT jlongArray JNICALL Java_com_utun_chat_data_CallVideoNative_choose(
JNIEnv *e, jobject o, jbyteArray local, jbyteArray remote, jlongArray desired) {
(void)o; struct call_video_caps sender, receiver; struct call_video_format f; jlong values[12];
if (read_caps(e, local, &sender) || read_caps(e, remote, &receiver) || read_format(e, desired, &f, values, 0)
|| call_video_choose(&sender, &receiver, &f) <= 0) return NULL;
values[5] = f.codec; values[6] = f.level;
jlongArray out = (*e)->NewLongArray(e, 9); if (out) (*e)->SetLongArrayRegion(e, out, 0, 9, values); return out;
}
JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_CallVideoNative_format(
JNIEnv *e, jobject o, jlong id, jlongArray array, jint bps) {
(void)o; struct call_video_format f; jlong values[12];
return !read_format(e, array, &f, values, 0) && !call_video_port_format(id, &f, bps);
}
JNIEXPORT void JNICALL Java_com_utun_chat_data_CallVideoNative_applied(
JNIEnv *e, jobject o, jlong id, jlong generation, jlong request, jint bps) {
(void)e; (void)o; call_video_port_applied(id, generation, request, bps);
}
JNIEXPORT jint JNICALL Java_com_utun_chat_data_CallVideoNative_send(
JNIEnv *e, jobject o, jlong id, jlongArray meta, jbyteArray bytes) {
(void)o; struct call_video_frame f = {0}; jlong values[12];
if (read_format(e, meta, &f.format, values, 1) || !bytes) return -1;
jsize size = (*e)->GetArrayLength(e, bytes);
if (size <= 0 || size > CALL_VIDEO_MAX_FRAME || values[9] <= 0 || values[10] < 0 || values[11] < 0 || values[11] > 1) {
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "call video JNI invalid frame metadata/size=%d", size); return -1;
}
jbyte *data = (*e)->GetByteArrayElements(e, bytes, NULL); if (!data) return -1;
f.call_id = id; f.frame_id = values[9]; f.pts_us = values[10]; f.flags = values[11];
f.size = f.length = size; f.data = (uint8_t *)data;
int result = call_video_port_send(&f); (*e)->ReleaseByteArrayElements(e, bytes, data, JNI_ABORT); return result;
}
JNIEXPORT jbyteArray JNICALL Java_com_utun_chat_data_CallVideoNative_pull(JNIEnv *e, jobject o, jlong id, jlongArray meta) {
(void)o;
if (!meta || (*e)->GetArrayLength(e, meta) != 12) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "call video JNI invalid pull metadata"); return NULL; }
struct call_video_frame f;
if (call_video_port_pull(id, &f) != 1) return NULL;
jlong values[] = {f.format.generation, f.format.width, f.format.height, f.format.fps, f.format.rotation,
f.format.codec, f.format.level, f.format.maximum_bps, f.format.vbv_bits, f.frame_id, f.pts_us, f.flags};
jbyteArray out = (*e)->NewByteArray(e, f.size);
if (out) { (*e)->SetByteArrayRegion(e, out, 0, f.size, (jbyte *)f.data); (*e)->SetLongArrayRegion(e, meta, 0, 12, values); }
else call_video_port_request_key(id);
u_free((void *)f.data); return out;
}
JNIEXPORT void JNICALL Java_com_utun_chat_data_CallVideoNative_requestKey(JNIEnv *e, jobject o, jlong id) {
(void)e; (void)o; call_video_port_request_key(id);
}
JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_CallVideoNative_range(JNIEnv *e, jobject o, jlong id, jint minimum, jint maximum) {
(void)e; (void)o; return !call_video_port_range(id, minimum, maximum);
}
#endif
Loading…
Cancel
Save