Expose shared playback timing and stereo time stretch to Android
This commit is contained in:
1 parent
541518db81
commit
e6e276aed3
3 files changed
+114
-6
No files matched your search
+3
-3
@@ -676,9 +676,6 @@ int stream_publish_open(struct UTUN_INSTANCE *inst, uint64_t group_id, const str
|
|||||||
struct STREAM_HANDLE **out) {
|
struct STREAM_HANDLE **out) {
|
||||||
int result = stream_open_check(inst, group_id, out);
|
int result = stream_open_check(inst, group_id, out);
|
||||||
if (result != STREAM_OK) return result;
|
if (result != STREAM_OK) return result;
|
||||||
struct stream_session *previous = session_find(inst->stream, group_id, inst->node_id);
|
|
||||||
if (previous && previous->source && previous->end_valid)
|
|
||||||
session_close(previous, STREAM_CLOSE_SOURCE_CHANGED, 0, 1);
|
|
||||||
struct stream_descriptor d = {.group_id = group_id, .source_node_id = inst->node_id,
|
struct stream_descriptor d = {.group_id = group_id, .source_node_id = inst->node_id,
|
||||||
.n = parameters ? parameters->n : STREAM_DEFAULT_N, .r = parameters ? parameters->r : STREAM_DEFAULT_R,
|
.n = parameters ? parameters->n : STREAM_DEFAULT_N, .r = parameters ? parameters->r : STREAM_DEFAULT_R,
|
||||||
.block_size = parameters ? parameters->block_size : STREAM_RS_DEFAULT_SIZE};
|
.block_size = parameters ? parameters->block_size : STREAM_RS_DEFAULT_SIZE};
|
||||||
@@ -688,6 +685,9 @@ int stream_publish_open(struct UTUN_INSTANCE *inst, uint64_t group_id, const str
|
|||||||
}
|
}
|
||||||
} while (!d.source_epoch);
|
} while (!d.source_epoch);
|
||||||
if (stream_descriptor_validate(&d) != STREAM_OK) return STREAM_ERR_ARGUMENT;
|
if (stream_descriptor_validate(&d) != STREAM_OK) return STREAM_ERR_ARGUMENT;
|
||||||
|
struct stream_session *previous = session_find(inst->stream, group_id, inst->node_id);
|
||||||
|
if (previous && previous->source && previous->end_valid)
|
||||||
|
session_close(previous, STREAM_CLOSE_SOURCE_CHANGED, 0, 1);
|
||||||
return session_open(inst, &d, inst->my_ed25519_pubkey, 1, 0, 0, out);
|
return session_open(inst, &d, inst->my_ed25519_pubkey, 1, 0, 0, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ void stream_playback_clock_reset(struct stream_playback_clock *c, uint64_t id) {
|
|||||||
int stream_playback_clock_update(struct stream_playback_clock *c, int64_t now, int64_t position,
|
int stream_playback_clock_update(struct stream_playback_clock *c, int64_t now, int64_t position,
|
||||||
int64_t ready, int64_t guard, int64_t pending_gain,
|
int64_t ready, int64_t guard, int64_t pending_gain,
|
||||||
int starved, struct stream_playback_state *state) {
|
int starved, struct stream_playback_state *state) {
|
||||||
if (!c || !state || now < 0 || position < 0 || ready < 0 || guard < 0 || guard > 1000000
|
if (!c || !state || now < 0 || position < 0 || ready < 0 || guard < 0 || guard > 60000000
|
||||||
|| pending_gain < 0 || pending_gain > 1000000 || (c->initialized && (now < c->last_us || position < c->last_position_us))) {
|
|| pending_gain < 0 || pending_gain > 60000000 || (c->initialized && (now < c->last_us || position < c->last_position_us))) {
|
||||||
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "playback clock invalid id=%llu now=%lld pts=%lld ready=%lld guard=%lld pending=%lld",
|
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "playback clock invalid id=%llu now=%lld pts=%lld ready=%lld guard=%lld pending=%lld",
|
||||||
c ? (unsigned long long)c->id : 0, (long long)now, (long long)position, (long long)ready,
|
c ? (unsigned long long)c->id : 0, (long long)now, (long long)position, (long long)ready,
|
||||||
(long long)guard, (long long)pending_gain);
|
(long long)guard, (long long)pending_gain);
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
#include "../../../lib/audio_compressor.h"
|
#include "../../../lib/audio_compressor.h"
|
||||||
#include "../libutun_lite/stream_bridge.h"
|
#include "../libutun_lite/stream_bridge.h"
|
||||||
#include "../../../src/stream/stream_media.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 };
|
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);
|
(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;
|
(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);
|
size_t length = 0; uint8_t *data = android_stream_pull((uint64_t)id, &length);
|
||||||
if (!data) return NULL;
|
if (!data) return NULL;
|
||||||
jbyteArray output = (*env)->NewByteArray(env, (jsize)length);
|
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;
|
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. */
|
/* Opus state существует только в audio worker; encode/decode не занимают uasync. */
|
||||||
JNIEXPORT jlong JNICALL Java_com_utun_chat_data_NativeLib_nativeStreamOpusCreate(JNIEnv *env, jobject obj, jboolean encode) {
|
JNIEXPORT jlong JNICALL Java_com_utun_chat_data_NativeLib_nativeStreamOpusCreate(JNIEnv *env, jobject obj, jboolean encode) {
|
||||||
(void)env; (void)obj;
|
(void)env; (void)obj;
|
||||||
|
|||||||
Reference in new issue
Block a user