|
|
|
|
@ -2,14 +2,23 @@
|
|
|
|
|
#ifdef __ANDROID__ |
|
|
|
|
#include <jni.h> |
|
|
|
|
#include <stdint.h> |
|
|
|
|
#include <stdlib.h> |
|
|
|
|
|
|
|
|
|
#include "../../../lib/debug_config.h" |
|
|
|
|
#include "../../../lib/mem.h" |
|
|
|
|
#include "../../../lib/opus_codec.h" |
|
|
|
|
#include "../../../lib/audio_compressor.h" |
|
|
|
|
#include "../libutun_lite/stream_bridge.h" |
|
|
|
|
#include "../../../src/stream/stream_media.h" |
|
|
|
|
|
|
|
|
|
struct android_stream_opus { opus_codec_encoder_t *encoder; opus_codec_decoder_t *decoder; }; |
|
|
|
|
struct android_stream_opus { |
|
|
|
|
opus_codec_encoder_t *encoder; |
|
|
|
|
opus_codec_decoder_t *decoder; |
|
|
|
|
struct audio_compressor *compressor; |
|
|
|
|
int compressor_enabled, max_gain_db, rise_rate; |
|
|
|
|
uint64_t frames; |
|
|
|
|
uint32_t reported_clips; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
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, jlong epoch) { |
|
|
|
|
@ -61,29 +70,65 @@ JNIEXPORT jlong JNICALL Java_com_utun_chat_data_NativeLib_nativeStreamOpusCreate
|
|
|
|
|
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; |
|
|
|
|
codec->compressor = audio_compressor_create(); |
|
|
|
|
if (!codec->compressor) 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); |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_STREAM, "JNI stream Opus created codec=%p encode=%d rate=48000 channels=1 frame=960", (void *)codec, encode); |
|
|
|
|
return (jlong)(intptr_t)codec; |
|
|
|
|
fail: |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "JNI stream Opus initialization failed encode=%d", encode); |
|
|
|
|
audio_compressor_destroy(codec->compressor); |
|
|
|
|
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) { |
|
|
|
|
JNIEnv *env, jobject obj, jlong handle, jshortArray samples, jboolean enabled, jint max_gain_db, jint rise_rate) { |
|
|
|
|
(void)obj; |
|
|
|
|
struct android_stream_opus *codec = (struct android_stream_opus *)(intptr_t)handle; |
|
|
|
|
if (!codec || !codec->encoder || !samples || (*env)->GetArrayLength(env, samples) != 960) { |
|
|
|
|
if (!codec || !codec->encoder || !codec->compressor || !samples || (*env)->GetArrayLength(env, samples) != 960) { |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "JNI stream Opus encode invalid handle/frame"); return NULL; |
|
|
|
|
} |
|
|
|
|
if (max_gain_db < 1 || max_gain_db > 60 || rise_rate < 1 || rise_rate > 100) { |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "JNI stream compressor invalid max_gain=%d rise=%d", max_gain_db, rise_rate); return NULL; |
|
|
|
|
} |
|
|
|
|
if (codec->compressor_enabled != !!enabled || codec->max_gain_db != max_gain_db || codec->rise_rate != rise_rate) { |
|
|
|
|
audio_compressor_config_t config = {0}; |
|
|
|
|
config.sample_rate = 48000; config.channels = 1; config.block_duration_ms = 20; |
|
|
|
|
config.lookback_ms = 200; config.lookahead_ms = 0; config.target_level = 0.25f; |
|
|
|
|
config.max_gain_db = max_gain_db; config.rise_rate_per_sec = rise_rate; config.release_rate_per_sec = rise_rate * 5.0f; |
|
|
|
|
if (audio_compressor_configure(codec->compressor, &config) != 0) { |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "JNI stream compressor configuration failed codec=%p", (void *)codec); return NULL; |
|
|
|
|
} |
|
|
|
|
audio_compressor_set_enabled(codec->compressor, enabled); |
|
|
|
|
codec->compressor_enabled = !!enabled; codec->max_gain_db = max_gain_db; codec->rise_rate = rise_rate; codec->reported_clips = 0; |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_STREAM, |
|
|
|
|
"JNI stream compressor applied codec=%p enabled=%d max_gain=%ddB rise=%d release=%d target=0.25 history=200ms delay=0ms", |
|
|
|
|
(void *)codec, !!enabled, max_gain_db, rise_rate, rise_rate * 5); |
|
|
|
|
} |
|
|
|
|
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 report = codec->frames % 50 == 0, input_peak = 0, output_peak = 0; |
|
|
|
|
if (report) for (unsigned i = 0; i < 960; i++) if (abs((int)pcm[i]) > input_peak) input_peak = abs((int)pcm[i]); |
|
|
|
|
if (audio_compressor_process_frame(codec->compressor, pcm, pcm, 960) != 0) { |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "JNI stream compressor processing failed codec=%p", (void *)codec); 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; } |
|
|
|
|
codec->frames++; |
|
|
|
|
if (report) { |
|
|
|
|
for (unsigned i = 0; i < 960; i++) if (abs((int)pcm[i]) > output_peak) output_peak = abs((int)pcm[i]); |
|
|
|
|
uint32_t clips = audio_compressor_clip_count(codec->compressor); |
|
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_STREAM, "JNI stream audio codec=%p frames=%llu enabled=%d gain=%.1fdB peaks=%d->%d clips=%u packet=%d", |
|
|
|
|
(void *)codec, (unsigned long long)codec->frames, codec->compressor_enabled, |
|
|
|
|
audio_compressor_gain_db(codec->compressor), input_peak, output_peak, clips, length); |
|
|
|
|
if (clips != codec->reported_clips) DEBUG_WARN(DEBUG_CATEGORY_STREAM, "JNI stream audio clipped codec=%p samples=%u gain=%.1fdB", |
|
|
|
|
(void *)codec, clips - codec->reported_clips, audio_compressor_gain_db(codec->compressor)); |
|
|
|
|
codec->reported_clips = clips; |
|
|
|
|
} |
|
|
|
|
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"); |
|
|
|
|
@ -112,7 +157,9 @@ JNIEXPORT void JNICALL Java_com_utun_chat_data_NativeLib_nativeStreamOpusClose(J
|
|
|
|
|
(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); |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_STREAM, "JNI stream Opus release codec=%p encode=%d frames=%llu", (void *)codec, |
|
|
|
|
codec->encoder != NULL, (unsigned long long)codec->frames); |
|
|
|
|
audio_compressor_destroy(codec->compressor); |
|
|
|
|
opus_codec_encoder_destroy(codec->encoder); opus_codec_decoder_destroy(codec->decoder); u_free(codec); |
|
|
|
|
} |
|
|
|
|
#endif |
|
|
|
|
|