|
|
|
|
@ -10,6 +10,8 @@
|
|
|
|
|
#include "../../../lib/audio_compressor.h" |
|
|
|
|
#include "../libutun_lite/stream_bridge.h" |
|
|
|
|
#include "../../../src/stream/stream_media.h" |
|
|
|
|
#include "../../../src/stream/stream_playback_clock.h" |
|
|
|
|
#include "../../../src/stream/stream_playback_audio.h" |
|
|
|
|
|
|
|
|
|
enum { STREAM_AUDIO_CHANNELS = 2, STREAM_AUDIO_FRAMES = 960, STREAM_AUDIO_SAMPLES = 1920 }; |
|
|
|
|
|
|
|
|
|
@ -53,8 +55,10 @@ JNIEXPORT void JNICALL Java_com_utun_chat_data_NativeLib_nativeStreamStop(JNIEnv
|
|
|
|
|
(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) { |
|
|
|
|
JNIEXPORT jbyteArray JNICALL Java_com_utun_chat_data_NativeLib_nativeStreamPull( |
|
|
|
|
JNIEnv *env, jobject obj, jlong id, jlong position, jlong video_ahead, jlong audio_ahead) { |
|
|
|
|
(void)obj; |
|
|
|
|
android_stream_read_request((uint64_t)id, position, video_ahead, audio_ahead); |
|
|
|
|
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); |
|
|
|
|
@ -63,6 +67,110 @@ JNIEXPORT jbyteArray JNICALL Java_com_utun_chat_data_NativeLib_nativeStreamPull(
|
|
|
|
|
u_free(data); return output; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
JNIEXPORT jlongArray JNICALL Java_com_utun_chat_data_NativeLib_nativeStreamStatus(JNIEnv *env, jobject obj, jlong id) { |
|
|
|
|
(void)obj; |
|
|
|
|
struct stream_media_status status; |
|
|
|
|
if (android_stream_get_status((uint64_t)id, &status) < 0) return NULL; |
|
|
|
|
jlong values[] = {status.ready_us, status.video_us, status.audio_us, status.start_us, (jlong)status.generation, |
|
|
|
|
status.initialized | (status.audio_enabled << 1) | (status.finished << 2) | (status.video_ended << 3) | (status.audio_ended << 4)}; |
|
|
|
|
jlongArray result = (*env)->NewLongArray(env, 6); |
|
|
|
|
if (result) (*env)->SetLongArrayRegion(env, result, 0, 6, values); |
|
|
|
|
else DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "JNI media status allocation failed"); |
|
|
|
|
return result; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeStreamResync(JNIEnv *env, jobject obj, jlong id) { |
|
|
|
|
(void)env; (void)obj; return android_stream_resync((uint64_t)id) == 0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
struct android_stream_playback { |
|
|
|
|
struct stream_playback_clock clock; |
|
|
|
|
struct stream_playback_state state; |
|
|
|
|
struct stream_playback_audio *audio; |
|
|
|
|
int64_t position, time_us; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
JNIEXPORT jlong JNICALL Java_com_utun_chat_data_NativeLib_nativeStreamPlaybackCreate( |
|
|
|
|
JNIEnv *env, jobject obj, jlong id, jlong pts, jboolean audio) { |
|
|
|
|
(void)env; (void)obj; |
|
|
|
|
if (pts < 0) { DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "JNI playback invalid start PTS"); return 0; } |
|
|
|
|
struct android_stream_playback *p = u_calloc(1, sizeof(*p)); |
|
|
|
|
if (!p) { DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "JNI playback allocation failed"); return 0; } |
|
|
|
|
stream_playback_clock_reset(&p->clock, (uint64_t)id); p->position = pts; p->time_us = -1; |
|
|
|
|
if (audio && !(p->audio = stream_playback_audio_create(pts))) { u_free(p); return 0; } |
|
|
|
|
return (jlong)(intptr_t)p; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
JNIEXPORT jint JNICALL Java_com_utun_chat_data_NativeLib_nativeStreamPlaybackFeed( |
|
|
|
|
JNIEnv *env, jobject obj, jlong handle, jlong pts, jbyteArray packet) { |
|
|
|
|
(void)obj; struct android_stream_playback *p = (struct android_stream_playback *)(intptr_t)handle; |
|
|
|
|
if (!p || !p->audio || !packet) { DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "JNI playback feed invalid handle/packet"); return -1; } |
|
|
|
|
jsize length = (*env)->GetArrayLength(env, packet); uint8_t bytes[1275]; |
|
|
|
|
if (length <= 0 || length > (jsize)sizeof(bytes)) { DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "JNI playback packet size=%d", length); return -1; } |
|
|
|
|
(*env)->GetByteArrayRegion(env, packet, 0, length, (jbyte *)bytes); |
|
|
|
|
if ((*env)->ExceptionCheck(env)) { DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "JNI playback packet access failed"); return -1; } |
|
|
|
|
return stream_playback_audio_feed(p->audio, pts, bytes, (size_t)length); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
JNIEXPORT jshortArray JNICALL Java_com_utun_chat_data_NativeLib_nativeStreamPlaybackPull( |
|
|
|
|
JNIEnv *env, jobject obj, jlong handle, jint frames, jlong limit) { |
|
|
|
|
(void)obj; struct android_stream_playback *p = (struct android_stream_playback *)(intptr_t)handle; |
|
|
|
|
if (!p || !p->audio || frames <= 0 || frames > 8192) { |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "JNI playback PCM invalid frames=%d", frames); return NULL; |
|
|
|
|
} |
|
|
|
|
if (limit < 0) return NULL; |
|
|
|
|
int16_t pcm[8192 * 2]; |
|
|
|
|
int count = stream_playback_audio_pull(p->audio, pcm, (unsigned)frames, limit); |
|
|
|
|
if (count <= 0) return NULL; |
|
|
|
|
jshortArray output = (*env)->NewShortArray(env, count * 2); |
|
|
|
|
if (output) (*env)->SetShortArrayRegion(env, output, 0, count * 2, pcm); |
|
|
|
|
else DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "JNI playback PCM allocation failed frames=%d", count); |
|
|
|
|
return output; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
JNIEXPORT jlongArray JNICALL Java_com_utun_chat_data_NativeLib_nativeStreamPlaybackUpdate( |
|
|
|
|
JNIEnv *env, jobject obj, jlong handle, jlong now, jlong played, jlong ready, jlong guard, jboolean starved) { |
|
|
|
|
(void)obj; struct android_stream_playback *p = (struct android_stream_playback *)(intptr_t)handle; |
|
|
|
|
if (!p || now < 0 || played < 0 || ready < 0) { DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "JNI playback clock invalid input"); return NULL; } |
|
|
|
|
int64_t pending = 0; |
|
|
|
|
if (p->audio) { |
|
|
|
|
p->position = stream_playback_audio_position(p->audio, (uint64_t)played); |
|
|
|
|
pending = stream_playback_audio_pending_gain(p->audio, (uint64_t)played); |
|
|
|
|
} else if (p->time_us >= 0 && !starved) { |
|
|
|
|
int64_t elapsed = now - p->time_us; |
|
|
|
|
if (elapsed < 0) { DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "JNI playback wall clock regressed"); return NULL; } |
|
|
|
|
int64_t proposed = p->position + elapsed * p->clock.tempo_milli / 1000; |
|
|
|
|
if (proposed > ready) { proposed = ready; starved = JNI_TRUE; } |
|
|
|
|
if (proposed > p->position) p->position = proposed; |
|
|
|
|
} |
|
|
|
|
p->time_us = now; |
|
|
|
|
if (stream_playback_clock_update(&p->clock, now, p->position, ready, guard, pending, starved, &p->state) < 0) return NULL; |
|
|
|
|
if (p->audio) stream_playback_audio_set_tempo(p->audio, p->state.tempo_milli); |
|
|
|
|
jlong values[] = {p->position, p->state.reserve_us, p->state.minimum_us, p->state.tempo_milli, p->state.waiting, |
|
|
|
|
p->audio ? stream_playback_audio_needs_input(p->audio) : 0, |
|
|
|
|
p->audio ? (jlong)stream_playback_audio_written(p->audio) : 0}; |
|
|
|
|
jlongArray output = (*env)->NewLongArray(env, 7); |
|
|
|
|
if (output) (*env)->SetLongArrayRegion(env, output, 0, 7, values); |
|
|
|
|
else DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "JNI playback clock allocation failed"); |
|
|
|
|
return output; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
JNIEXPORT void JNICALL Java_com_utun_chat_data_NativeLib_nativeStreamPlaybackFinish(JNIEnv *env, jobject obj, jlong handle) { |
|
|
|
|
(void)env; (void)obj; struct android_stream_playback *p = (struct android_stream_playback *)(intptr_t)handle; |
|
|
|
|
if (p) stream_playback_audio_finish(p->audio); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeStreamPlaybackEmpty(JNIEnv *env, jobject obj, jlong handle) { |
|
|
|
|
(void)env; (void)obj; struct android_stream_playback *p = (struct android_stream_playback *)(intptr_t)handle; |
|
|
|
|
return !p || stream_playback_audio_empty(p->audio); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
JNIEXPORT void JNICALL Java_com_utun_chat_data_NativeLib_nativeStreamPlaybackClose(JNIEnv *env, jobject obj, jlong handle) { |
|
|
|
|
(void)env; (void)obj; struct android_stream_playback *p = (struct android_stream_playback *)(intptr_t)handle; |
|
|
|
|
if (!p) return; |
|
|
|
|
stream_playback_audio_destroy(p->audio); u_free(p); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* 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; |
|
|
|
|
|