#ifdef __ANDROID__ #include #include #include "../../../src/call/call_video_port.h" #include "../../../src/call/call_video_playout.h" #include "../../../src/call/call_video_bitstream.h" #include "../../../lib/debug_config.h" #include "../../../lib/mem.h" /* LongArray format: generation,width,height,fps,rotation,codec,level,maximum_bps,profile. * TX добавляет frame_id,pts_us,flags; RX также received_us (локальный MONOTONIC). * 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] < 0 || values[8] > 2) 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, s.decoded_generation, s.decoded_accepted}; jlongArray out = (*e)->NewLongArray(e, 10); if (out) (*e)->SetLongArrayRegion(e, out, 0, 10, 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; values[8] = f.profile; 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) != 13) { 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.profile, f.frame_id, f.pts_us, f.flags, f.received_us}; jbyteArray out = (*e)->NewByteArray(e, f.size); if (out) { (*e)->SetByteArrayRegion(e, out, 0, f.size, (jbyte *)f.data); (*e)->SetLongArrayRegion(e, meta, 0, 13, values); } else call_video_port_request_key(id); u_free((void *)f.data); return out; } /* Handle принадлежит только RX worker, включая reset/destroy. */ JNIEXPORT jlong JNICALL Java_com_utun_chat_data_CallVideoNative_playoutCreate(JNIEnv *e, jobject o, jlong id) { (void)e; (void)o; struct call_video_playout *p = u_malloc(sizeof(*p)); if (!p) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "video playout allocation failed"); return 0; } call_video_playout_init(p, id); return (jlong)(uintptr_t)p; } JNIEXPORT void JNICALL Java_com_utun_chat_data_CallVideoNative_playoutDestroy(JNIEnv *e, jobject o, jlong handle) { (void)e; (void)o; u_free((void *)(uintptr_t)handle); } JNIEXPORT void JNICALL Java_com_utun_chat_data_CallVideoNative_playoutReset(JNIEnv *e, jobject o, jlong handle) { (void)e; (void)o; struct call_video_playout *p = (void *)(uintptr_t)handle; if (!p) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "video playout reset without handle"); return; } call_video_playout_init(p, p->call_id); } JNIEXPORT jlong JNICALL Java_com_utun_chat_data_CallVideoNative_playoutStep(JNIEnv *e, jobject o, jlong handle, jlong pts, jlong received, jlong next_pts, jlong next_received, jlong now, jboolean valid, jlong audio) { (void)e; (void)o; struct call_video_playout *p = (void *)(uintptr_t)handle; if (!p) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "video playout poll without handle"); return 5000; } struct call_video_playout_frame f = {pts, received}, next = {next_pts, next_received}; int action = call_video_playout_poll(p, pts >= 0 ? &f : NULL, next_pts >= 0 ? &next : NULL, now, valid, audio); if (action == CALL_VIDEO_PLAYOUT_SHOW) return 0; if (action != CALL_VIDEO_PLAYOUT_WAIT) return -action; int64_t wait = p->deadline_us - now; return wait < 100 ? 100 : wait > 5000 ? 5000 : wait; } 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); } JNIEXPORT void JNICALL Java_com_utun_chat_data_CallVideoNative_decoded(JNIEnv *e, jobject o, jlong id, jlong generation, jboolean accepted) { (void)e; (void)o; call_video_port_decoded(id, generation, accepted); } JNIEXPORT jlongArray JNICALL Java_com_utun_chat_data_CallVideoNative_inspect(JNIEnv *e, jobject o, jint codec, jbyteArray bytes) { (void)o; if (!bytes) return NULL; jsize size = (*e)->GetArrayLength(e, bytes); if (size < 9 || size > CALL_VIDEO_MAX_FRAME) { DEBUG_WARN(DEBUG_CATEGORY_CALL, "video inspect JNI size=%d", size); return NULL; } jbyte *data = (*e)->GetByteArrayElements(e, bytes, NULL); if (!data) return NULL; struct call_video_bitstream s; int result = call_video_inspect_key(codec, (uint8_t *)data, size, &s); (*e)->ReleaseByteArrayElements(e, bytes, data, JNI_ABORT); if (result) return NULL; jlong values[] = {s.width, s.height, s.level, s.profile, s.dpb}; jlongArray out = (*e)->NewLongArray(e, 5); if (out) (*e)->SetLongArrayRegion(e, out, 0, 5, values); return out; } JNIEXPORT jbyteArray JNICALL Java_com_utun_chat_data_CallVideoNative_probeKey(JNIEnv *e, jobject o, jint codec, jint profile) { (void)o; uint8_t bytes[8192]; int size = call_video_probe_key(codec, profile, bytes, sizeof(bytes)); if (size < 0) return NULL; jbyteArray out = (*e)->NewByteArray(e, size); if (out) (*e)->SetByteArrayRegion(e, out, 0, size, (jbyte *)bytes); return out; } JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_CallVideoNative_supported( JNIEnv *e, jobject o, jbyteArray local, jbyteArray remote, jlongArray array) { (void)o; struct call_video_caps sender, receiver; struct call_video_format f; jlong values[12]; return !read_caps(e, local, &sender) && !read_caps(e, remote, &receiver) && !read_format(e, array, &f, values, 0) && call_video_format_supported(&sender, &receiver, &f); } #endif