diff --git a/app/build.gradle b/app/build.gradle index 631248a..784bb8a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -36,4 +36,5 @@ dependencies { testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' + implementation 'com.jjoe64:graphview:4.2.2' } \ No newline at end of file diff --git a/app/src/main/java/com/example/ueberwachungssystem/MainActivity.java b/app/src/main/java/com/example/ueberwachungssystem/MainActivity.java index ab47b23..ee9c096 100644 --- a/app/src/main/java/com/example/ueberwachungssystem/MainActivity.java +++ b/app/src/main/java/com/example/ueberwachungssystem/MainActivity.java @@ -1,36 +1,32 @@ package com.example.ueberwachungssystem; import androidx.appcompat.app.AppCompatActivity; -import androidx.core.app.ActivityCompat; -import android.Manifest; import android.os.Bundle; import android.view.View; -import android.widget.Button; import android.widget.Switch; import android.widget.TextView; -import android.widget.ToggleButton; -import com.example.ueberwachungssystem.Mikrofon; import com.example.ueberwachungssystem.logger.Logger; +import com.jjoe64.graphview.GraphView; + public class MainActivity extends AppCompatActivity { Logger logger; + GraphView graph; private TextView tv_log; - Mikrofon Mikrofon_1= new Mikrofon(); - MicrophoneDetector Mic; private Switch TglBtn_Mic; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - TextView textView = new TextView(this); tv_log = (TextView) findViewById(R.id.tv_log); //Set textview for showing logged content logger = new Logger(this.getClass().getSimpleName(), tv_log, ""); logger.log(this.getClass().getSimpleName() + ".onCreate"); - Mic = new MicrophoneDetector(this, logger); + graph = (GraphView) findViewById(R.id.graph); + Mic = new MicrophoneDetector(this, logger, graph); setupMic(); } @@ -38,19 +34,16 @@ public class MainActivity extends AppCompatActivity { @Override public void onResume() { super.onResume(); - Mikrofon_1.onResume(); logger.log(this.getClass().getSimpleName() + ".onResume"); } @Override public void onPause() { super.onPause(); - Mikrofon_1.onPause(); logger.log(this.getClass().getSimpleName() + ".onPause"); } private void setupMic() { - Mikrofon_1.onCreate(this, logger); TglBtn_Mic = (Switch) findViewById(R.id.TglBtn_Mic); TglBtn_Mic.setOnClickListener(new View.OnClickListener() { @Override @@ -58,9 +51,10 @@ public class MainActivity extends AppCompatActivity { if (v == TglBtn_Mic) { logger.log("onClick toggleButtonThread " + TglBtn_Mic.isChecked()); if (TglBtn_Mic.isChecked()) { - Mikrofon_1.onResume(); + Mic.startDetection(); } else { - Mikrofon_1.onPause(); + Mic.stopDetection(); + logger.clearLog(); } } } diff --git a/app/src/main/java/com/example/ueberwachungssystem/MicrophoneDetector.java b/app/src/main/java/com/example/ueberwachungssystem/MicrophoneDetector.java index d897131..8cae886 100644 --- a/app/src/main/java/com/example/ueberwachungssystem/MicrophoneDetector.java +++ b/app/src/main/java/com/example/ueberwachungssystem/MicrophoneDetector.java @@ -1,5 +1,7 @@ package com.example.ueberwachungssystem; +import static java.lang.Math.*; + import android.Manifest; import android.app.Activity; import android.content.Context; @@ -12,9 +14,14 @@ import android.os.AsyncTask; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; +import com.example.ueberwachungssystem.Signalverarbeitung.Complex; +import com.example.ueberwachungssystem.Signalverarbeitung.FFT; import com.example.ueberwachungssystem.logger.Logger; +import com.jjoe64.graphview.GraphView; +import com.jjoe64.graphview.series.DataPoint; +import com.jjoe64.graphview.series.LineGraphSeries; -public class MicrophoneDetector extends Detector{ +public class MicrophoneDetector extends Detector { /** * Constructor - takes context of current activity * @@ -24,17 +31,19 @@ public class MicrophoneDetector extends Detector{ private static final int RECHTEANFORDERUNG_MIKROFON = 1; private AufnahmeTask aufnahmeTask; - public boolean Alarm = false; - public int Schwellwert_Alarm = 500; + public boolean armed = false; + public int Schwellwert_Alarm = 100; + GraphView graph; Logger logger; - private Activity MainActivityForClass; - public MicrophoneDetector(Context context, Logger MainLogger) { + + public MicrophoneDetector(Context context, Logger MainLogger, GraphView MainGraph) { super(context); MainActivityForClass = (Activity) context; logger = MainLogger; //Class uses the same logger as the MainActivity logger.log(this.getClass().getSimpleName() + ".onCreate"); + graph = MainGraph; if (!istZugriffAufMikrofonErlaubt()) { zugriffAufMikrofonAnfordern(); @@ -44,6 +53,7 @@ public class MicrophoneDetector extends Detector{ @Override public void startDetection() { logger.log(this.getClass().getSimpleName() + ".startDetection"); + if (!istZugriffAufMikrofonErlaubt()) { zugriffAufMikrofonAnfordern(); } @@ -56,7 +66,7 @@ public class MicrophoneDetector extends Detector{ @Override public void stopDetection() { logger.log(this.getClass().getSimpleName() + ".stopDetection"); - if(aufnahmeTask!=null) { + if (aufnahmeTask != null) { aufnahmeTask.cancel(true); // aufnahmeTask = null; // if aufnahmeTask = null, break in for loop would not work (Nullpointer Exception) } @@ -71,6 +81,7 @@ public class MicrophoneDetector extends Detector{ return true; } } + private void zugriffAufMikrofonAnfordern() { ActivityCompat.requestPermissions(MainActivityForClass, new String[]{Manifest.permission.RECORD_AUDIO}, RECHTEANFORDERUNG_MIKROFON); } @@ -83,10 +94,23 @@ public class MicrophoneDetector extends Detector{ private int minPufferGroesseInBytes; private int pufferGroesseInBytes; private RingPuffer ringPuffer = new RingPuffer(10); + private float kalibierWert; + private DetectionReport detectionReport; AufnahmeTask() { minPufferGroesseInBytes = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat); pufferGroesseInBytes = minPufferGroesseInBytes * 2; + if (ActivityCompat.checkSelfPermission(MainActivityForClass, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { + // TODO: Consider calling + // ActivityCompat#requestPermissions + // here to request the missing permissions, and then overriding + // public void onRequestPermissionsResult(int requestCode, String[] permissions, + // int[] grantResults) + // to handle the case where the user grants the permission. See the documentation + // for ActivityCompat#requestPermissions for more details. + ActivityCompat.requestPermissions(MainActivityForClass, new String[]{Manifest.permission.RECORD_AUDIO}, RECHTEANFORDERUNG_MIKROFON); + } + recorder = new AudioRecord(MediaRecorder.AudioSource.MIC, sampleRateInHz, channelConfig, audioFormat, pufferGroesseInBytes); // textViewMinPufferGroesseInBytes.setText("" + minPufferGroesseInBytes); @@ -152,6 +176,40 @@ public class MicrophoneDetector extends Detector{ int anzahlVerarbeitet = 0; GleitenderMittelwert gleitenderMittelwert = new GleitenderMittelwert(0.3f); + //Kalibrierung + try { + Thread.sleep(3000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + int i = 0; + for (i = 0; i < 20; i++) { + int n = recorder.read(puffer, 0, puffer.length); + Verarbeitungsergebnis kalibrierErgebnis = verarbeiten(puffer, n); + kalibierWert += kalibrierErgebnis.maxAmp; + try { + Thread.sleep(50); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + kalibierWert = kalibierWert/i; + +// Complex[] zeitSignal = new Complex[puffer.length]; +// for (int j = 0; j < puffer.length; j++) { +// zeitSignal[j] = new Complex(puffer[j], 0); +// } +// Complex[] spektrum = FFT.fft(zeitSignal); + double[] spektrum = calculateFFT(puffer); + DataPoint AddPoint; +// LineGraphSeries series = new LineGraphSeries(new DataPoint[]{}); +// for (i = 0; i < spektrum.length; i++) { +// AddPoint = new DataPoint(i, spektrum[i]); +// series.appendData(AddPoint, true, spektrum.length); +// } +// graph.addSeries(series); + // logger.log(spektrum.toString()); + for (; ; ) { if (aufnahmeTask.isCancelled()) { break; @@ -160,6 +218,14 @@ public class MicrophoneDetector extends Detector{ Verarbeitungsergebnis ergebnis = verarbeiten(puffer, n); anzahlVerarbeitet += n; + spektrum = calculateFFT(puffer); + LineGraphSeries newseries = new LineGraphSeries(new DataPoint[]{}); + for (i = 0; i < spektrum.length; i++) { + AddPoint = new DataPoint(i, spektrum[i]); + newseries.appendData(AddPoint, true, spektrum.length); + } + graph.removeAllSeries(); + graph.addSeries(newseries); zaehlerZeitMessung++; if (zaehlerZeitMessung == maxZaehlerZeitMessung) { long time = System.currentTimeMillis(); @@ -171,10 +237,7 @@ public class MicrophoneDetector extends Detector{ lastTime = time; } - float noiseLevel = gleitenderMittelwert.MittelwertPuffer(puffer); - // logger.log("Noise Level:" + noiseLevel); - ergebnis.noiseLevel = noiseLevel; ergebnis.verarbeitungsrate = (int) verarbeitungsrate; publishProgress(ergebnis); @@ -202,14 +265,18 @@ public class MicrophoneDetector extends Detector{ for (int i = 0; i < n; i++) { if (daten[i] > max) { max = daten[i]; + //max = 20 * log10(abs(daten[i]) / 32768); } } ringPuffer.hinzufuegen(max); maxAmp = ringPuffer.maximum(); + if (maxAmp <= Schwellwert_Alarm+kalibierWert) { + armed = true; + } } - return new Verarbeitungsergebnis(status, maxAmp, 0, 0); + return new Verarbeitungsergebnis(status, maxAmp, 0); } @Override @@ -217,26 +284,71 @@ public class MicrophoneDetector extends Detector{ super.onProgressUpdate(progress); // textViewMaxAmp.setText("" + progress[0].maxAmp); // textViewVerarbeitungsrate.setText("" + progress[0].verarbeitungsrate); - logger.overwriteLastlog("VR, Max, NL:" + progress[0].verarbeitungsrate + ", " + progress[0].maxAmp - + ", " + progress[0].noiseLevel); + float maxAmpPrint = round(20*log10(abs(progress[0].maxAmp/1.0))); + float kalibierWertPrint = round(20*log10(abs(kalibierWert))); + logger.overwriteLastlog("VR, Max, Kal:" + progress[0].verarbeitungsrate + ", " + maxAmpPrint + + " dB, " + kalibierWertPrint + " dB"); - if (progress[0].maxAmp >= Schwellwert_Alarm) { - Alarm = true; + if (progress[0].maxAmp >= Schwellwert_Alarm+kalibierWert && armed == true) { + armed = false; + detectionReport = new DetectionReport("Mic1", "Audio", maxAmpPrint); + logger.log(""); + logger.log("Alarm!"); + logger.log(detectionReport.toString()); + logger.log(""); } } } + private double[] calculateFFT(short[] zeitsignal) + { + byte signal[] = new byte[zeitsignal.length]; + // loops through all the values of a Short + for (int i = 0; i < zeitsignal.length-1; i++) { + signal[i] = (byte) (zeitsignal[i]); + signal[i+1] = (byte) (zeitsignal[i] >> 8); + } + + final int mNumberOfFFTPoints =1024; + double mMaxFFTSample; + + double temp; + Complex[] y; + Complex[] complexSignal = new Complex[mNumberOfFFTPoints]; + double[] absSignal = new double[mNumberOfFFTPoints/2]; + + for(int i = 0; i < mNumberOfFFTPoints; i++){ + temp = (double)((signal[2*i] & 0xFF) | (signal[2*i+1] << 8)) / 32768.0F; + complexSignal[i] = new Complex(temp,0.0); + } + + y = FFT.fft(complexSignal); + + mMaxFFTSample = 0.0; + // mPeakPos = 0; + for(int i = 0; i < (mNumberOfFFTPoints/2); i++) + { + absSignal[i] = y[i].abs(); +// absSignal[i] = Math.sqrt(Math.pow(y[i].re(), 2) + Math.pow(y[i].im(), 2)); +// if(absSignal[i] > mMaxFFTSample) +// { +// mMaxFFTSample = absSignal[i]; +// // mPeakPos = i; +// } + } + + return absSignal; + + } + class Verarbeitungsergebnis { String status; short maxAmp; int verarbeitungsrate; - float noiseLevel; - - Verarbeitungsergebnis(String status, short maxAmp, int verarbeitungsrate, float noiseLevel) { + Verarbeitungsergebnis(String status, short maxAmp, int verarbeitungsrate) { this.status = status; this.maxAmp = maxAmp; this.verarbeitungsrate = verarbeitungsrate; - this.noiseLevel = noiseLevel; } } diff --git a/app/src/main/java/com/example/ueberwachungssystem/Mikrofon.java b/app/src/main/java/com/example/ueberwachungssystem/Mikrofon.java deleted file mode 100644 index 2d3c815..0000000 --- a/app/src/main/java/com/example/ueberwachungssystem/Mikrofon.java +++ /dev/null @@ -1,329 +0,0 @@ -package com.example.ueberwachungssystem; - -import android.Manifest; -import android.app.Activity; -import android.content.Context; -import android.content.pm.PackageManager; -import android.media.AudioFormat; -import android.media.AudioRecord; -import android.media.MediaRecorder; -import android.os.AsyncTask; -import android.os.Bundle; -import android.os.Environment; -import android.widget.TextView; - -import androidx.core.app.ActivityCompat; -import androidx.core.content.ContextCompat; - -import com.example.ueberwachungssystem.logger.Logger; - -import java.io.File; - -public class Mikrofon { - - Logger logger; - private static final int RECHTEANFORDERUNG_MIKROFON = 1; - - private Activity MainActivityForClass; - private AufnahmeTask aufnahmeTask; - public boolean Alarm = false; - public int Schwellwert_Alarm = 500; - - public void onCreate(Activity MainActivity, Logger MainLogger) { - MainActivityForClass = MainActivity; - logger = MainLogger; //Class uses the same logger as the MainActivity - logger.log(this.getClass().getSimpleName() + ".onCreate"); - - if (!istZugriffAufMikrofonErlaubt()) { - zugriffAufMikrofonAnfordern(); - } - } - - public void onResume() { - logger.log(this.getClass().getSimpleName() + ".onResume"); - if (!istZugriffAufMikrofonErlaubt()) { - zugriffAufMikrofonAnfordern(); - } - if (istZugriffAufMikrofonErlaubt()) { - aufnahmeTask = new AufnahmeTask(); - aufnahmeTask.execute(); - } - } - - public void onPause() { - logger.log(this.getClass().getSimpleName() + ".onPause"); - if(aufnahmeTask!=null) { - aufnahmeTask.cancel(true); - // aufnahmeTask = null; // if aufnahmeTask = null, break in for loop would not work (Nullpointer Exception) - } - } - - private boolean istZugriffAufMikrofonErlaubt() { - if (ContextCompat.checkSelfPermission(MainActivityForClass, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { - logger.log("Zugriff auf Mikrofon ist verboten."); - return false; - } else { - logger.log("Zugriff auf Mikrofon ist erlaubt."); - return true; - } - } - private void zugriffAufMikrofonAnfordern() { - ActivityCompat.requestPermissions(MainActivityForClass, new String[]{Manifest.permission.RECORD_AUDIO}, RECHTEANFORDERUNG_MIKROFON); - } - - class AufnahmeTask extends AsyncTask { - private AudioRecord recorder; - private final int sampleRateInHz = 44100; - private final int channelConfig = AudioFormat.CHANNEL_IN_MONO; - private final int audioFormat = AudioFormat.ENCODING_PCM_16BIT; - private int minPufferGroesseInBytes; - private int pufferGroesseInBytes; - private RingPuffer ringPuffer = new RingPuffer(10); - - AufnahmeTask() { - minPufferGroesseInBytes = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat); - pufferGroesseInBytes = minPufferGroesseInBytes * 2; - recorder = new AudioRecord(MediaRecorder.AudioSource.MIC, sampleRateInHz, channelConfig, audioFormat, pufferGroesseInBytes); - -// textViewMinPufferGroesseInBytes.setText("" + minPufferGroesseInBytes); -// textViewPufferGroesseInBytes.setText("" + pufferGroesseInBytes); -// textViewAbtastrate.setText("" + recorder.getSampleRate()); -// textViewAnzahlKanaele.setText("" + recorder.getChannelCount()); - - logger.log("Puffergroeße: "+ minPufferGroesseInBytes + " " + pufferGroesseInBytes); - logger.log("Recorder (SR, CH): "+ recorder.getSampleRate() + " " + recorder.getChannelCount()); - - int anzahlBytesProAbtastwert; - String s; - switch (recorder.getAudioFormat()) { - case AudioFormat.ENCODING_PCM_8BIT: - s = "8 Bit PCM "; - anzahlBytesProAbtastwert = 1; - break; - case AudioFormat.ENCODING_PCM_16BIT: - s = "16 Bit PCM"; - anzahlBytesProAbtastwert = 2; - break; - case AudioFormat.ENCODING_PCM_FLOAT: - s = "Float PCM"; - anzahlBytesProAbtastwert = 4; - break; - default: - throw new IllegalArgumentException(); - } -// textViewAudioFormat.setText(s); - - switch (recorder.getChannelConfiguration()) { - case AudioFormat.CHANNEL_IN_MONO: - s = "Mono"; - break; - case AudioFormat.CHANNEL_IN_STEREO: - s = "Stereo"; - anzahlBytesProAbtastwert *= 2; - break; - case AudioFormat.CHANNEL_INVALID: - s = "ungültig"; - break; - default: - throw new IllegalArgumentException(); - } -// textViewKanalKonfiguration.setText(s); - logger.log("Konfiguration: "+ s); - - int pufferGroesseInAnzahlAbtastwerten = pufferGroesseInBytes / anzahlBytesProAbtastwert; - int pufferGroesseInMillisekunden = 1000 * pufferGroesseInAnzahlAbtastwerten / recorder.getSampleRate(); - -// textViewPufferGroesseInAnzahlAbtastwerte.setText("" + pufferGroesseInAnzahlAbtastwerten); -// textViewPufferGroesseInMillisekunden.setText("" + pufferGroesseInMillisekunden); - } - - @Override - protected Void doInBackground(Long... params) { - recorder.startRecording(); - short[] puffer = new short[pufferGroesseInBytes / 2]; - long lastTime = System.currentTimeMillis(); - float verarbeitungsrate = 0; - final int maxZaehlerZeitMessung = 10; - int zaehlerZeitMessung = 0; - int anzahlVerarbeitet = 0; - GleitenderMittelwert gleitenderMittelwert = new GleitenderMittelwert(0.3f); - - for (; ; ) { - if (aufnahmeTask.isCancelled()) { - break; - } else { - int n = recorder.read(puffer, 0, puffer.length); - Verarbeitungsergebnis ergebnis = verarbeiten(puffer, n); - anzahlVerarbeitet += n; - - zaehlerZeitMessung++; - if (zaehlerZeitMessung == maxZaehlerZeitMessung) { - long time = System.currentTimeMillis(); - long deltaTime = time - lastTime; - verarbeitungsrate = 1000.0f * anzahlVerarbeitet / deltaTime; - verarbeitungsrate = gleitenderMittelwert.mittel(verarbeitungsrate); - zaehlerZeitMessung = 0; - anzahlVerarbeitet = 0; - lastTime = time; - } - - float noiseLevel = gleitenderMittelwert.MittelwertPuffer(puffer); - - // logger.log("Noise Level:" + noiseLevel); - ergebnis.noiseLevel = noiseLevel; - ergebnis.verarbeitungsrate = (int) verarbeitungsrate; - publishProgress(ergebnis); - - try { - Thread.sleep(100); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - } - recorder.release(); - return null; - } - - private Verarbeitungsergebnis verarbeiten(short[] daten, int n) { - String status; - short maxAmp = -1; - if (n == AudioRecord.ERROR_INVALID_OPERATION) { - status = "ERROR_INVALID_OPERATION"; - } else if (n == AudioRecord.ERROR_BAD_VALUE) { - status = "ERROR_BAD_VALUE"; - } else { - status = "OK"; - short max = 0; - for (int i = 0; i < n; i++) { - if (daten[i] > max) { - max = daten[i]; - } - } - - ringPuffer.hinzufuegen(max); - maxAmp = ringPuffer.maximum(); - } - - return new Verarbeitungsergebnis(status, maxAmp, 0, 0); - } - - @Override - protected void onProgressUpdate(Verarbeitungsergebnis... progress) { - super.onProgressUpdate(progress); -// textViewMaxAmp.setText("" + progress[0].maxAmp); -// textViewVerarbeitungsrate.setText("" + progress[0].verarbeitungsrate); - logger.overwriteLastlog("VR, Max, NL:" + progress[0].verarbeitungsrate + ", " + progress[0].maxAmp - + ", " + progress[0].noiseLevel); - - if (progress[0].maxAmp >= Schwellwert_Alarm) { - Alarm = true; - } - } - } - - class Verarbeitungsergebnis { - String status; - short maxAmp; - int verarbeitungsrate; - float noiseLevel; - - Verarbeitungsergebnis(String status, short maxAmp, int verarbeitungsrate, float noiseLevel) { - this.status = status; - this.maxAmp = maxAmp; - this.verarbeitungsrate = verarbeitungsrate; - this.noiseLevel = noiseLevel; - } - } - - class RingPuffer { - private short[] puffer; - private final int laenge; - private int anzahlEnthaltenerDaten; - private int position; - - public RingPuffer(int n) { - laenge = n; - anzahlEnthaltenerDaten = 0; - position = 0; - puffer = new short[laenge]; - } - - public void hinzufuegen(short wert) { - puffer[position] = wert; - position++; - if (position >= laenge) { - position = 0; - } - if (anzahlEnthaltenerDaten < laenge) { - anzahlEnthaltenerDaten++; - } - } - - public void hinzufuegen(short[] daten) { - for (short d : daten) { - puffer[position] = d; - position++; - if (position >= laenge) { - position = 0; - } - } - if (anzahlEnthaltenerDaten < laenge) { - anzahlEnthaltenerDaten += daten.length; - if (anzahlEnthaltenerDaten >= laenge) { - anzahlEnthaltenerDaten = laenge; - } - } - } - - public short maximum() { - short max = 0; - for (int i = 0; i < anzahlEnthaltenerDaten; i++) { - if (puffer[i] > max) { - max = puffer[i]; - } - } - return max; - } - - public float mittelwert() { - float summe = 0; - for (int i = 0; i < anzahlEnthaltenerDaten; i++) { - summe += puffer[i]; - } - return summe / anzahlEnthaltenerDaten; - } - } - - class GleitenderMittelwert { - private final float wichtungNeuerWert; - private final float wichtungAlterWert; - private float mittelwert = 0; - private boolean istMittelwertGesetzt = false; - - GleitenderMittelwert(float wichtungNeuerWert) { - this.wichtungNeuerWert = wichtungNeuerWert; - this.wichtungAlterWert = 1 - this.wichtungNeuerWert; - } - - float MittelwertPuffer(short[] puffer) { - - for (int i = 0; i < puffer.length; i++) { - mittelwert = Math.abs(puffer[i]); - } - mittelwert = mittelwert/puffer.length; - - return mittelwert; - } - - float mittel(float wert) { - if (istMittelwertGesetzt) { - mittelwert = wert * wichtungNeuerWert + mittelwert * wichtungAlterWert; - } else { - mittelwert = wert; - istMittelwertGesetzt = true; - } - return mittelwert; - } - } -} diff --git a/app/src/main/java/com/example/ueberwachungssystem/Signalverarbeitung/Complex.java b/app/src/main/java/com/example/ueberwachungssystem/Signalverarbeitung/Complex.java new file mode 100644 index 0000000..26a3c28 --- /dev/null +++ b/app/src/main/java/com/example/ueberwachungssystem/Signalverarbeitung/Complex.java @@ -0,0 +1,148 @@ +package com.example.ueberwachungssystem.Signalverarbeitung; + +import java.util.Objects; + +public class Complex { + private final double re; // the real part + private final double im; // the imaginary part + + // create a new object with the given real and imaginary parts + public Complex(double real, double imag) { + re = real; + im = imag; + } + + // return a string representation of the invoking com.example.ueberwachungssystem.Signalverarbeitung.Complex object + public String toString() { + if (im == 0) return re + ""; + if (re == 0) return im + "i"; + if (im < 0) return re + " - " + (-im) + "i"; + return re + " + " + im + "i"; + } + + // return abs/modulus/magnitude + public double abs() { + return Math.hypot(re, im); + } + + // return angle/phase/argument, normalized to be between -pi and pi + public double phase() { + return Math.atan2(im, re); + } + + // return a new com.example.ueberwachungssystem.Signalverarbeitung.Complex object whose value is (this + b) + public Complex plus(Complex b) { + Complex a = this; // invoking object + double real = a.re + b.re; + double imag = a.im + b.im; + return new Complex(real, imag); + } + + // return a new com.example.ueberwachungssystem.Signalverarbeitung.Complex object whose value is (this - b) + public Complex minus(Complex b) { + Complex a = this; + double real = a.re - b.re; + double imag = a.im - b.im; + return new Complex(real, imag); + } + + // return a new com.example.ueberwachungssystem.Signalverarbeitung.Complex object whose value is (this * b) + public Complex times(Complex b) { + Complex a = this; + double real = a.re * b.re - a.im * b.im; + double imag = a.re * b.im + a.im * b.re; + return new Complex(real, imag); + } + + // return a new object whose value is (this * alpha) + public Complex scale(double alpha) { + return new Complex(alpha * re, alpha * im); + } + + // return a new com.example.ueberwachungssystem.Signalverarbeitung.Complex object whose value is the conjugate of this + public Complex conjugate() { + return new Complex(re, -im); + } + + // return a new com.example.ueberwachungssystem.Signalverarbeitung.Complex object whose value is the reciprocal of this + public Complex reciprocal() { + double scale = re * re + im * im; + return new Complex(re / scale, -im / scale); + } + + // return the real or imaginary part + public double re() { + return re; + } + + public double im() { + return im; + } + + // return a / b + public Complex divides(Complex b) { + Complex a = this; + return a.times(b.reciprocal()); + } + + // return a new com.example.ueberwachungssystem.Signalverarbeitung.Complex object whose value is the complex exponential of this + public Complex exp() { + return new Complex(Math.exp(re) * Math.cos(im), Math.exp(re) * Math.sin(im)); + } + + // return a new com.example.ueberwachungssystem.Signalverarbeitung.Complex object whose value is the complex sine of this + public Complex sin() { + return new Complex(Math.sin(re) * Math.cosh(im), Math.cos(re) * Math.sinh(im)); + } + + // return a new com.example.ueberwachungssystem.Signalverarbeitung.Complex object whose value is the complex cosine of this + public Complex cos() { + return new Complex(Math.cos(re) * Math.cosh(im), -Math.sin(re) * Math.sinh(im)); + } + + // return a new com.example.ueberwachungssystem.Signalverarbeitung.Complex object whose value is the complex tangent of this + public Complex tan() { + return sin().divides(cos()); + } + + // a static version of plus + public static Complex plus(Complex a, Complex b) { + double real = a.re + b.re; + double imag = a.im + b.im; + Complex sum = new Complex(real, imag); + return sum; + } + + // See Section 3.3. + public boolean equals(Object x) { + if (x == null) return false; + if (this.getClass() != x.getClass()) return false; + Complex that = (Complex) x; + return (this.re == that.re) && (this.im == that.im); + } + + // See Section 3.3. + public int hashCode() { + return Objects.hash(re, im); + } + + // sample client for testing + public static void main(String[] args) { + Complex a = new Complex(5.0, 6.0); + Complex b = new Complex(-3.0, 4.0); + + System.out.println("a = " + a); + System.out.println("b = " + b); + System.out.println("Re(a) = " + a.re()); + System.out.println("Im(a) = " + a.im()); + System.out.println("b + a = " + b.plus(a)); + System.out.println("a - b = " + a.minus(b)); + System.out.println("a * b = " + a.times(b)); + System.out.println("b * a = " + b.times(a)); + System.out.println("a / b = " + a.divides(b)); + System.out.println("(a / b) * b = " + a.divides(b).times(b)); + System.out.println("conj(a) = " + a.conjugate()); + System.out.println("|a| = " + a.abs()); + System.out.println("tan(a) = " + a.tan()); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/ueberwachungssystem/Signalverarbeitung/FFT.java b/app/src/main/java/com/example/ueberwachungssystem/Signalverarbeitung/FFT.java new file mode 100644 index 0000000..2543310 --- /dev/null +++ b/app/src/main/java/com/example/ueberwachungssystem/Signalverarbeitung/FFT.java @@ -0,0 +1,248 @@ +package com.example.ueberwachungssystem.Signalverarbeitung; +// Source: https://introcs.cs.princeton.edu/java/97data/FFT.java.html + +import android.util.Log; + +/****************************************************************************** + * Compilation: javac FFT.java + * Execution: java FFT n + * Dependencies: com.example.ueberwachungssystem.Signalverarbeitung.Complex.java + * + * Compute the FFT and inverse FFT of a length n complex sequence + * using the radix 2 Cooley-Tukey algorithm. + * Bare bones implementation that runs in O(n log n) time and O(n) + * space. Our goal is to optimize the clarity of the code, rather + * than performance. + * + * This implementation uses the primitive root of unity w = e^(-2 pi i / n). + * Some resources use w = e^(2 pi i / n). + * + * Reference: https://www.cs.princeton.edu/~wayne/kleinberg-tardos/pdf/05DivideAndConquerII.pdf + * + * Limitations + * ----------- + * - assumes n is a power of 2 + * + * - not the most memory efficient algorithm (because it uses + * an object type for representing complex numbers and because + * it re-allocates memory for the subarray, instead of doing + * in-place or reusing a single temporary array) + * + * For an in-place radix 2 Cooley-Tukey FFT, see + * https://introcs.cs.princeton.edu/java/97data/InplaceFFT.java.html + * + ******************************************************************************/ + +public class FFT { + + // compute the FFT of x[], assuming its length n is a power of 2 + public static Complex[] fft(Complex[] x) { + int n = x.length; + + // base case + if (n == 1) return new Complex[]{x[0]}; + + // radix 2 Cooley-Tukey FFT + if (n % 2 != 0) { + throw new IllegalArgumentException("n is not a power of 2"); + } + + // compute FFT of even terms + Complex[] even = new Complex[n / 2]; + for (int k = 0; k < n / 2; k++) { + even[k] = x[2 * k]; + } + Complex[] evenFFT = fft(even); + + // compute FFT of odd terms + Complex[] odd = even; // reuse the array (to avoid n log n space) + for (int k = 0; k < n / 2; k++) { + odd[k] = x[2 * k + 1]; + } + Complex[] oddFFT = fft(odd); + + // combine + Complex[] y = new Complex[n]; + for (int k = 0; k < n / 2; k++) { + double kth = -2 * k * Math.PI / n; + Complex wk = new Complex(Math.cos(kth), Math.sin(kth)); + y[k] = evenFFT[k].plus(wk.times(oddFFT[k])); + y[k + n / 2] = evenFFT[k].minus(wk.times(oddFFT[k])); + } + return y; + } + + + // compute the inverse FFT of x[], assuming its length n is a power of 2 + public static Complex[] ifft(Complex[] x) { + int n = x.length; + Complex[] y = new Complex[n]; + + // take conjugate + for (int i = 0; i < n; i++) { + y[i] = x[i].conjugate(); + } + + // compute forward FFT + y = fft(y); + + // take conjugate again + for (int i = 0; i < n; i++) { + y[i] = y[i].conjugate(); + } + + // divide by n + for (int i = 0; i < n; i++) { + y[i] = y[i].scale(1.0 / n); + } + + return y; + + } + + // compute the circular convolution of x and y + public static Complex[] cconvolve(Complex[] x, Complex[] y) { + + // should probably pad x and y with 0s so that they have same length + // and are powers of 2 + if (x.length != y.length) { + throw new IllegalArgumentException("Dimensions don't agree"); + } + + int n = x.length; + + // compute FFT of each sequence + Complex[] a = fft(x); + Complex[] b = fft(y); + + // point-wise multiply + Complex[] c = new Complex[n]; + for (int i = 0; i < n; i++) { + c[i] = a[i].times(b[i]); + } + + // compute inverse FFT + return ifft(c); + } + + + // compute the linear convolution of x and y + public static Complex[] convolve(Complex[] x, Complex[] y) { + Complex ZERO = new Complex(0, 0); + + Complex[] a = new Complex[2 * x.length]; + for (int i = 0; i < x.length; i++) a[i] = x[i]; + for (int i = x.length; i < 2 * x.length; i++) a[i] = ZERO; + + Complex[] b = new Complex[2 * y.length]; + for (int i = 0; i < y.length; i++) b[i] = y[i]; + for (int i = y.length; i < 2 * y.length; i++) b[i] = ZERO; + + return cconvolve(a, b); + } + + // compute the DFT of x[] via brute force (n^2 time) + public static Complex[] dft(Complex[] x) { + int n = x.length; + Complex ZERO = new Complex(0, 0); + Complex[] y = new Complex[n]; + for (int k = 0; k < n; k++) { + y[k] = ZERO; + for (int j = 0; j < n; j++) { + int power = (k * j) % n; + double kth = -2 * power * Math.PI / n; + Complex wkj = new Complex(Math.cos(kth), Math.sin(kth)); + y[k] = y[k].plus(x[j].times(wkj)); + } + } + return y; + } + + // display an array of com.example.ueberwachungssystem.Signalverarbeitung.Complex numbers to standard output + public static void show(Complex[] x, String title) { + System.out.println(title); + System.out.println("-------------------"); + for (int i = 0; i < x.length; i++) { + System.out.println(x[i]); + } + System.out.println(); + } + + /*************************************************************************** + * Test client and sample execution + * + * % java FFT 4 + * x + * ------------------- + * -0.03480425839330703 + * 0.07910192950176387 + * 0.7233322451735928 + * 0.1659819820667019 + * + * y = fft(x) + * ------------------- + * 0.9336118983487516 + * -0.7581365035668999 + 0.08688005256493803i + * 0.44344407521182005 + * -0.7581365035668999 - 0.08688005256493803i + * + * z = ifft(y) + * ------------------- + * -0.03480425839330703 + * 0.07910192950176387 + 2.6599344570851287E-18i + * 0.7233322451735928 + * 0.1659819820667019 - 2.6599344570851287E-18i + * + * c = cconvolve(x, x) + * ------------------- + * 0.5506798633981853 + * 0.23461407150576394 - 4.033186818023279E-18i + * -0.016542951108772352 + * 0.10288019294318276 + 4.033186818023279E-18i + * + * d = convolve(x, x) + * ------------------- + * 0.001211336402308083 - 3.122502256758253E-17i + * -0.005506167987577068 - 5.058885073636224E-17i + * -0.044092969479563274 + 2.1934338938072244E-18i + * 0.10288019294318276 - 3.6147323062478115E-17i + * 0.5494685269958772 + 3.122502256758253E-17i + * 0.240120239493341 + 4.655566391833896E-17i + * 0.02755001837079092 - 2.1934338938072244E-18i + * 4.01805098805014E-17i + * + ***************************************************************************/ + + public static void main(String[] args) { + int n = Integer.parseInt(args[0]); + Complex[] x = new Complex[n]; + + // original data + for (int i = 0; i < n; i++) { + x[i] = new Complex(i, 0); + } + show(x, "x"); + + // FFT of original data + Complex[] y = fft(x); + show(y, "y = fft(x)"); + + // FFT of original data + Complex[] y2 = dft(x); + show(y2, "y2 = dft(x)"); + + // take inverse FFT + Complex[] z = ifft(y); + show(z, "z = ifft(y)"); + + // circular convolution of x with itself + Complex[] c = cconvolve(x, x); + show(c, "c = cconvolve(x, x)"); + + // linear convolution of x with itself + Complex[] d = convolve(x, x); + show(d, "d = convolve(x, x)"); + } +} + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index bea580a..edd41b2 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -11,18 +11,27 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Log" - app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" /> + app:layout_constraintTop_toBottomOf="@+id/TglBtn_Mic" /> + + \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 3e927b1..92b9117 100644 --- a/gradle.properties +++ b/gradle.properties @@ -18,4 +18,5 @@ android.useAndroidX=true # Enables namespacing of each library's R class so that its R class includes only the # resources declared in the library itself and none from the library's dependencies, # thereby reducing the size of the R class for that library -android.nonTransitiveRClass=true \ No newline at end of file +android.nonTransitiveRClass=true +android.enableJetifier=true \ No newline at end of file