Compress stream audio before Opus using radio settings
This commit is contained in:
1 parent
149e4d43e5
commit
6bd14c53b5
3 files changed
+64
-7
No files matched your search
@@ -12,6 +12,7 @@ import androidx.datastore.preferences.preferencesDataStore
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.net.Inet4Address
|
||||
import java.net.Inet6Address
|
||||
@@ -34,6 +35,8 @@ data class VoiceSettings(
|
||||
val targetLevelDb: Int = -3
|
||||
)
|
||||
|
||||
data class RadioCompressorSettings(val enabled: Boolean = true, val maxGainDb: Int = 15, val riseRate: Int = 6)
|
||||
|
||||
class ConfigProvider(private val context: Context) {
|
||||
private val nodePrivateKey = stringPreferencesKey("node_private_key")
|
||||
private val nodePublicKey = stringPreferencesKey("node_public_key")
|
||||
@@ -88,6 +91,12 @@ class ConfigProvider(private val context: Context) {
|
||||
private val radioCompressorEnabledKey = intPreferencesKey("radio_compressor_enabled")
|
||||
private val radioCompressorMaxGainDbKey = intPreferencesKey("radio_compressor_max_gain_db")
|
||||
|
||||
// Трансляция наблюдает те же настройки TX, что и рация; история PCM у каждого владельца своя.
|
||||
val radioCompressorSettings: Flow<RadioCompressorSettings> = context.dataStore.data.map { prefs ->
|
||||
RadioCompressorSettings((prefs[radioCompressorEnabledKey] ?: 1) != 0,
|
||||
prefs[radioCompressorMaxGainDbKey] ?: 15, prefs[chatCompressorRiseRate] ?: 6)
|
||||
}.distinctUntilChanged()
|
||||
|
||||
private fun keysFilePath(): String {
|
||||
val dir = runBlocking { context.dataStore.data.first()[dbPath] } ?: context.filesDir.absolutePath
|
||||
return "$dir/keys.conf"
|
||||
|
||||
@@ -209,7 +209,8 @@ object NativeLib {
|
||||
fun streamStop(id: Long) { nativeStreamStop(id) }
|
||||
fun streamPull(id: Long): ByteArray? = nativeStreamPull(id)
|
||||
fun streamOpusCreate(encode: Boolean): Long = nativeStreamOpusCreate(encode)
|
||||
fun streamOpusEncode(handle: Long, pcm: ShortArray): ByteArray? = nativeStreamOpusEncode(handle, pcm)
|
||||
fun streamOpusEncode(handle: Long, pcm: ShortArray, compressor: RadioCompressorSettings): ByteArray? =
|
||||
nativeStreamOpusEncode(handle, pcm, compressor.enabled, compressor.maxGainDb, compressor.riseRate)
|
||||
fun streamOpusDecode(handle: Long, packet: ByteArray): ShortArray? = nativeStreamOpusDecode(handle, packet)
|
||||
fun streamOpusClose(handle: Long) { nativeStreamOpusClose(handle) }
|
||||
private external fun nativeStreamOpen(id: Long, group: Long, source: Long, width: Int, height: Int, rotation: Int,
|
||||
@@ -220,7 +221,7 @@ object NativeLib {
|
||||
private external fun nativeStreamStop(id: Long)
|
||||
private external fun nativeStreamPull(id: Long): ByteArray?
|
||||
private external fun nativeStreamOpusCreate(encode: Boolean): Long
|
||||
private external fun nativeStreamOpusEncode(handle: Long, pcm: ShortArray): ByteArray?
|
||||
private external fun nativeStreamOpusEncode(handle: Long, pcm: ShortArray, compressor: Boolean, maxGainDb: Int, riseRate: Int): ByteArray?
|
||||
private external fun nativeStreamOpusDecode(handle: Long, packet: ByteArray): ShortArray?
|
||||
private external fun nativeStreamOpusClose(handle: Long)
|
||||
|
||||
|
||||
@@ -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
|
||||
Reference in new issue
Block a user