feat: enhance audio input handling and add message polling functionality

This commit is contained in:
Nedifinita
2025-12-05 18:12:20 +08:00
parent 99bc081583
commit 2b856c1a4a
7 changed files with 467 additions and 22 deletions

View File

@@ -250,7 +250,18 @@ void processBasebandSample(double sample)
}
double sample_val = filt - dc_offset;
double threshold = 0.05;
static double peak_pos = 0.01;
static double peak_neg = -0.01;
if (sample_val > peak_pos) peak_pos = sample_val;
else peak_pos *= 0.9999;
if (sample_val < peak_neg) peak_neg = sample_val;
else peak_neg *= 0.9999;
double threshold = (peak_pos - peak_neg) * 0.15;
if (threshold < 0.005) threshold = 0.005;
if (sample_val > threshold)
{

View File

@@ -98,9 +98,6 @@ Java_org_noxylva_lbjconsole_flutter_AudioInputHandler_nativePushAudio(
for (int i = 0; i < size; i++) {
double sample = (double)samples[i] / 32768.0;
sample *= 5.0;
processBasebandSample(sample);
}
@@ -125,6 +122,35 @@ Java_org_noxylva_lbjconsole_flutter_RtlTcpChannelHandler_getSignalStrength(JNIEn
{
return (jdouble)magsqRaw;
}
extern "C" JNIEXPORT void JNICALL
Java_org_noxylva_lbjconsole_flutter_AudioInputHandler_clearMessageBuffer(JNIEnv *, jobject)
{
std::lock_guard<std::mutex> demodLock(demodDataMutex);
std::lock_guard<std::mutex> msgLock(msgMutex);
messageBuffer.clear();
is_message_ready = false;
numeric_msg.clear();
alpha_msg.clear();
}
extern "C" JNIEXPORT jstring JNICALL
Java_org_noxylva_lbjconsole_flutter_AudioInputHandler_pollMessages(JNIEnv *env, jobject)
{
std::lock_guard<std::mutex> demodLock(demodDataMutex);
std::lock_guard<std::mutex> msgLock(msgMutex);
if (messageBuffer.empty())
{
return env->NewStringUTF("");
}
std::ostringstream ss;
for (auto &msg : messageBuffer)
ss << msg << "\n";
messageBuffer.clear();
return env->NewStringUTF(ss.str().c_str());
}
extern "C" JNIEXPORT jboolean JNICALL
Java_org_noxylva_lbjconsole_flutter_RtlTcpChannelHandler_isConnected(JNIEnv *, jobject)
{

View File

@@ -6,14 +6,18 @@ import android.content.pm.PackageManager
import android.media.AudioFormat
import android.media.AudioRecord
import android.media.MediaRecorder
import android.os.Handler
import android.os.Looper
import android.util.Log
import androidx.core.content.ContextCompat
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.EventChannel
import java.util.concurrent.atomic.AtomicBoolean
import java.nio.charset.Charset
class AudioInputHandler(private val context: Context) : MethodChannel.MethodCallHandler {
class AudioInputHandler(private val context: Context) : MethodChannel.MethodCallHandler, EventChannel.StreamHandler {
private var audioRecord: AudioRecord? = null
private val isRecording = AtomicBoolean(false)
private var recordingThread: Thread? = null
@@ -25,21 +29,35 @@ class AudioInputHandler(private val context: Context) : MethodChannel.MethodCall
AudioFormat.ENCODING_PCM_16BIT
) * 2
private val handler = Handler(Looper.getMainLooper())
private var eventSink: EventChannel.EventSink? = null
companion object {
private const val CHANNEL = "org.noxylva.lbjconsole/audio_input"
private const val METHOD_CHANNEL = "org.noxylva.lbjconsole/audio_input"
private const val EVENT_CHANNEL = "org.noxylva.lbjconsole/audio_input_event"
private const val TAG = "AudioInputHandler"
init {
System.loadLibrary("railwaypagerdemod")
}
fun registerWith(flutterEngine: FlutterEngine, context: Context) {
val channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
channel.setMethodCallHandler(AudioInputHandler(context))
val handler = AudioInputHandler(context)
val methodChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, METHOD_CHANNEL)
methodChannel.setMethodCallHandler(handler)
val eventChannel = EventChannel(flutterEngine.dartExecutor.binaryMessenger, EVENT_CHANNEL)
eventChannel.setStreamHandler(handler)
}
}
private external fun nativePushAudio(data: ShortArray, size: Int)
private external fun pollMessages(): String
private external fun clearMessageBuffer()
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"start" -> {
clearMessageBuffer()
if (startRecording()) {
result.success(null)
} else {
@@ -48,12 +66,63 @@ class AudioInputHandler(private val context: Context) : MethodChannel.MethodCall
}
"stop" -> {
stopRecording()
clearMessageBuffer()
result.success(null)
}
else -> result.notImplemented()
}
}
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
Log.d(TAG, "EventChannel onListen")
this.eventSink = events
startPolling()
}
override fun onCancel(arguments: Any?) {
Log.d(TAG, "EventChannel onCancel")
handler.removeCallbacksAndMessages(null)
this.eventSink = null
}
private fun startPolling() {
handler.post(object : Runnable {
override fun run() {
if (eventSink == null) {
return
}
val recording = isRecording.get()
val logs = pollMessages()
val regex = "\\[MSG\\]\\s*(\\d+)\\|(-?\\d+)\\|(.*)".toRegex()
val statusMap = mutableMapOf<String, Any?>()
statusMap["listening"] = recording
eventSink?.success(statusMap)
if (logs.isNotEmpty()) {
regex.findAll(logs).forEach { match ->
try {
val dataMap = mutableMapOf<String, Any?>()
dataMap["address"] = match.groupValues[1]
dataMap["func"] = match.groupValues[2]
val gbkBytes = match.groupValues[3].toByteArray(Charsets.ISO_8859_1)
val utf8String = String(gbkBytes, Charset.forName("GBK"))
dataMap["numeric"] = utf8String
eventSink?.success(dataMap)
} catch (e: Exception) {
Log.e(TAG, "decode_fail", e)
}
}
}
handler.postDelayed(this, 200)
}
})
}
private fun startRecording(): Boolean {
if (isRecording.get()) return true