Browse Source

Add Android JNI transport and independent stream Opus codecs

master
evgeny 1 week ago
parent
commit
6f544b4ca2
  1. 2
      tests/test_stream_media.c
  2. 1
      tools/chatgui-android/app/src/main/cpp/CMakeLists.txt
  3. 108
      tools/chatgui-android/jni_bridge/android_stream_jni.c

2
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();

1
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
$<TARGET_OBJECTS:utun_core>
)

108
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 <jni.h>
#include <stdint.h>
#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
Loading…
Cancel
Save