diff --git a/tests/test_stream_media.c b/tests/test_stream_media.c index fed4991c..eda126ce 100644 --- a/tests/test_stream_media.c +++ b/tests/test_stream_media.c @@ -117,7 +117,7 @@ static void test_batches(void) { int main(void) { debug_config_init(); debug_set_level(DEBUG_LEVEL_INFO); /* Усечения проверяются массово; ожидаемые отказы не загромождают диагностический лог. */ - debug_set_category_level(DEBUG_CATEGORY_STREAM, DEBUG_LEVEL_NONE); + debug_set_category_level(DEBUG_CATEGORY_STREAM, DEBUG_LEVEL_DISABLED); size_t before = u_get_allocated_count(); for (unsigned i = 0; i < sizeof(expected_video); i++) expected_video[i] = (uint8_t)(i * 31 + 7); test_media(); test_batches(); diff --git a/tools/chatgui-android/app/src/main/cpp/CMakeLists.txt b/tools/chatgui-android/app/src/main/cpp/CMakeLists.txt index afbd6aee..8cb894c2 100644 --- a/tools/chatgui-android/app/src/main/cpp/CMakeLists.txt +++ b/tools/chatgui-android/app/src/main/cpp/CMakeLists.txt @@ -74,6 +74,7 @@ set_target_properties(utun_core PROPERTIES C_STANDARD 99 C_STANDARD_REQUIRED ON) # JNI bridge + link into shared library add_library(utun SHARED ${JNI_DIR}/android_jni_bridge.c + ${JNI_DIR}/android_stream_jni.c ${JNI_DIR}/android_udp_log.c $ ) diff --git a/tools/chatgui-android/jni_bridge/android_stream_jni.c b/tools/chatgui-android/jni_bridge/android_stream_jni.c new file mode 100644 index 00000000..a9afda68 --- /dev/null +++ b/tools/chatgui-android/jni_bridge/android_stream_jni.c @@ -0,0 +1,108 @@ +/* JNI live media: session IDs для ядра, codec handles принадлежат ровно одному audio worker. */ +#ifdef __ANDROID__ +#include +#include + +#include "../../../lib/debug_config.h" +#include "../../../lib/mem.h" +#include "../../../lib/opus_codec.h" +#include "../libutun_lite/stream_bridge.h" + +struct android_stream_opus { opus_codec_encoder_t *encoder; opus_codec_decoder_t *decoder; }; + +JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeStreamOpen( + JNIEnv *env, jobject obj, jlong id, jlong group, jlong source, jint width, jint height, jint rotation, jboolean audio) { + (void)env; (void)obj; + return android_stream_open((uint64_t)id, (uint64_t)group, (uint64_t)source, width, height, rotation, audio) == 0; +} + +JNIEXPORT jint JNICALL Java_com_utun_chat_data_NativeLib_nativeStreamFeed( + JNIEnv *env, jobject obj, jlong id, jboolean video, jlong pts, jboolean key, jbyteArray data) { + (void)obj; + if (!data || pts < 0) { DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "JNI stream feed missing data/invalid PTS"); return -1; } + jsize length = (*env)->GetArrayLength(env, data); + jbyte *bytes = (*env)->GetByteArrayElements(env, data, NULL); + if (!bytes) { DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "JNI stream feed array access failed"); return -1; } + int result = android_stream_feed((uint64_t)id, video, (uint64_t)pts, key, (const uint8_t *)bytes, (size_t)length); + (*env)->ReleaseByteArrayElements(env, data, bytes, JNI_ABORT); + return result; +} + +JNIEXPORT void JNICALL Java_com_utun_chat_data_NativeLib_nativeStreamStop(JNIEnv *env, jobject obj, jlong id) { + (void)env; (void)obj; android_stream_stop((uint64_t)id); +} + +JNIEXPORT jbyteArray JNICALL Java_com_utun_chat_data_NativeLib_nativeStreamPull(JNIEnv *env, jobject obj, jlong id) { + (void)obj; + size_t length = 0; uint8_t *data = android_stream_pull((uint64_t)id, &length); + if (!data) return NULL; + jbyteArray output = (*env)->NewByteArray(env, (jsize)length); + if (output) (*env)->SetByteArrayRegion(env, output, 0, (jsize)length, (const jbyte *)data); + else DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "JNI stream pull array allocation failed bytes=%zu", length); + u_free(data); return output; +} + +/* Opus state существует только в audio worker; encode/decode не занимают uasync. */ +JNIEXPORT jlong JNICALL Java_com_utun_chat_data_NativeLib_nativeStreamOpusCreate(JNIEnv *env, jobject obj, jboolean encode) { + (void)env; (void)obj; + struct android_stream_opus *codec = u_calloc(1, sizeof(*codec)); + if (!codec) { DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "JNI stream Opus allocation failed"); return 0; } + if (encode) { + codec->encoder = opus_codec_encoder_create(48000, 1); + if (!codec->encoder || opus_codec_encoder_application_set(codec->encoder, OPUS_CODEC_APP_AUDIO) < 0 + || opus_codec_encoder_bitrate_set(codec->encoder, 64000) < 0) goto fail; + } else { + codec->decoder = opus_codec_decoder_create(48000, 1); + if (!codec->decoder) goto fail; + } + DEBUG_INFO(DEBUG_CATEGORY_STREAM, "JNI stream Opus created encode=%d rate=48000 channels=1 frame=960", encode); + return (jlong)(intptr_t)codec; +fail: + DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "JNI stream Opus initialization failed encode=%d", encode); + opus_codec_encoder_destroy(codec->encoder); opus_codec_decoder_destroy(codec->decoder); u_free(codec); return 0; +} + +JNIEXPORT jbyteArray JNICALL Java_com_utun_chat_data_NativeLib_nativeStreamOpusEncode( + JNIEnv *env, jobject obj, jlong handle, jshortArray samples) { + (void)obj; + struct android_stream_opus *codec = (struct android_stream_opus *)(intptr_t)handle; + if (!codec || !codec->encoder || !samples || (*env)->GetArrayLength(env, samples) != 960) { + DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "JNI stream Opus encode invalid handle/frame"); return NULL; + } + int16_t pcm[960]; uint8_t packet[1275]; + (*env)->GetShortArrayRegion(env, samples, 0, 960, pcm); + if ((*env)->ExceptionCheck(env)) { DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "JNI stream PCM read failed"); return NULL; } + int length = opus_codec_encode(codec->encoder, pcm, 960, packet, sizeof(packet)); + if (length < 0) { DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "JNI stream Opus encode failed result=%d", length); return NULL; } + jbyteArray output = (*env)->NewByteArray(env, length); + if (output) (*env)->SetByteArrayRegion(env, output, 0, length, (const jbyte *)packet); + else DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "JNI stream Opus output allocation failed"); + return output; +} + +JNIEXPORT jshortArray JNICALL Java_com_utun_chat_data_NativeLib_nativeStreamOpusDecode( + JNIEnv *env, jobject obj, jlong handle, jbyteArray packet) { + (void)obj; + struct android_stream_opus *codec = (struct android_stream_opus *)(intptr_t)handle; + if (!codec || !codec->decoder || !packet || (*env)->GetArrayLength(env, packet) > 1275) { + DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "JNI stream Opus decode invalid handle/packet"); return NULL; + } + jsize length = (*env)->GetArrayLength(env, packet); uint8_t bytes[1275]; int16_t pcm[960]; + (*env)->GetByteArrayRegion(env, packet, 0, length, (jbyte *)bytes); + if ((*env)->ExceptionCheck(env)) { DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "JNI stream Opus packet read failed"); return NULL; } + int count = opus_codec_decode(codec->decoder, length ? bytes : NULL, length, pcm, 960); + if (count != 960) { DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "JNI stream Opus decode failed count=%d", count); return NULL; } + jshortArray output = (*env)->NewShortArray(env, count); + if (output) (*env)->SetShortArrayRegion(env, output, 0, count, pcm); + else DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "JNI stream Opus PCM allocation failed"); + return output; +} + +JNIEXPORT void JNICALL Java_com_utun_chat_data_NativeLib_nativeStreamOpusClose(JNIEnv *env, jobject obj, jlong handle) { + (void)env; (void)obj; + struct android_stream_opus *codec = (struct android_stream_opus *)(intptr_t)handle; + if (!codec) return; + DEBUG_INFO(DEBUG_CATEGORY_STREAM, "JNI stream Opus release encode=%d", codec->encoder != NULL); + opus_codec_encoder_destroy(codec->encoder); opus_codec_decoder_destroy(codec->decoder); u_free(codec); +} +#endif