package de.tu.darmstadt.seemoo.ansian.control.threads; import java.util.concurrent.ArrayBlockingQueue; import android.util.Log; import android.widget.Toast; import de.tu.darmstadt.seemoo.ansian.control.SourceControl; import de.tu.darmstadt.seemoo.ansian.control.StateHandler; import de.tu.darmstadt.seemoo.ansian.gui.misc.MyToast; import de.tu.darmstadt.seemoo.ansian.model.AudioSink; import de.tu.darmstadt.seemoo.ansian.model.SamplePacket; import de.tu.darmstadt.seemoo.ansian.model.demodulation.Demodulation; import de.tu.darmstadt.seemoo.ansian.model.demodulation.Demodulation.DemoType; import de.tu.darmstadt.seemoo.ansian.model.filter.FirFilter; import de.tu.darmstadt.seemoo.ansian.model.preferences.MiscPreferences; import de.tu.darmstadt.seemoo.ansian.model.preferences.Preferences; /** * <h1>AnSiAn - Demodulator</h1> * * Module: Demodulator.java Description: This class implements demodulation of * various analog radio modes (FM, AM, SSB). It runs as a separate thread. It * will read raw complex samples from a queue, process them (channel selection, * filtering, demodulating) and forward the to an AudioSink thread. * * @author Dennis Mantz * @author Markus Grau * @author Steffen Kreis * * Copyright (C) 2014 Dennis Mantz License: * http://www.gnu.org/licenses/gpl.html GPL version 2 or higher * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or (at * your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA */ public class Demodulator extends Thread { private static Demodulation demodulation; private boolean stopRequested = true; private static final String LOGTAG = "Demodulator"; public static final int AUDIO_RATE = 31250; // Even though this is not a // proper audio rate, the // Android system can // handle it properly and it // is a integer fraction of // the input rate (1MHz). public static final int INPUT_RATE = 1000000; // Expected rate of the // incoming samples // DECIMATION private static Decimator decimator; // will do INPUT_RATE --> // QUADRATURE_RATE // FILTERING (This is the channel filter controlled by the user) private static final int USER_FILTER_ATTENUATION = 20; private FirFilter userFilter = null; private SamplePacket quadratureSamples; // AUDIO OUTPUT private AudioSink audioSink = null; // Will do QUADRATURE_RATE --> private MiscPreferences preferences; // AUDIO_RATE and audio output public static Demodulation getDemodulation() { return demodulation; } /** * Constructor. Creates a new demodulator block reading its samples from the * given input queue and returning the buffers to the given output queue. * Expects input samples to be at baseband (mixing is done by the scheduler) * * @param inputQueue * Queue that delivers received baseband signals * @param outputQueue * Queue to return used buffers from the inputQueue * @param packetSize * Size of the packets in the input queue * @param ansianService */ public Demodulator(ArrayBlockingQueue<SamplePacket> inputQueue, int packetSize, DemoType type) { preferences = Preferences.MISC_PREFERENCE; demodulation = Demodulation.getDemodulation(type); // Create internal sample buffers: // Note that we create the buffers for the case that there is no // downsampling necessary // All other cases with input decimation > 1 are also possible because // they only need // smaller buffers. this.quadratureSamples = new SamplePacket(packetSize); // Create Audio Sink this.audioSink = new AudioSink(packetSize, AUDIO_RATE); // Create Decimator block // Note that the decimator directly reads from the inputQueue and also // returns processed packets to the // output queue. decimator = new Decimator(demodulation.getQuadratureRate(), packetSize, inputQueue); } /** * Will set the cut off frequency of the user filter * * @param channelWidth * channel width (single side) in Hz * @return true if channel width is valid, false if out of range */ public boolean setChannelWidth(int channelWidth) { if (channelWidth < demodulation.getMinUserFilterWidth() || channelWidth > demodulation.getMaxUserFilterWidth()) return false; demodulation.setUserFilterCutOff(channelWidth); userFilter = null; return true; } /** * @return Current width (cut-off frequency - one sided) of the user filter */ public int getChannelWidth() { return demodulation.getUserFilterCutOff(); } /** * Starts the thread. This thread will start 2 more threads for decimation * and audio output. These threads are managed by the Demodulator and * terminated, when the Demodulator thread terminates. */ @Override public synchronized void start() { stopRequested = false; super.start(); } /** * Stops the thread */ public void stopDemodulator() { stopRequested = true; } @Override public void run() { SamplePacket inputSamples = null; SamplePacket audioBuffer = null; Log.i(LOGTAG, "Demodulator started. (Thread: " + this.getName() + ")"); // Start the audio sink thread: audioSink.start(); // Start decimator thread: decimator.start(); while (!stopRequested) { // Get downsampled packet from the decimator: inputSamples = decimator.getDecimatedPacket(1000); // Verify the input sample packet is not null: if (inputSamples == null) { // Log.d(LOGTAG, // "run: Decimated sample is null. skip this round..."); continue; } // filtering [sample rate is QUADRATURE_RATE] applyUserFilter(inputSamples, quadratureSamples); // The result from // filtering is // stored in // quadratureSamples // get buffer from audio sink // TODO why was it 1000 before? maybe set back audioBuffer = new SamplePacket(quadratureSamples.getRe().length); // audioBuffer = new SamplePacket(1000); // demodulate [sample rate is QUADRATURE_RATE] demodulation.demodulate(quadratureSamples, audioBuffer); // play audio [sample rate is QUADRATURE_RATE] audioSink.enqueuePacket(audioBuffer); } // Stop the audio sink thread: audioSink.stopSink(); // Stop the decimator thread: decimator.stopDecimator(); Log.i(LOGTAG, "Demodulator stopped. (Thread: " + this.getName() + ")"); } /** * Will filter the samples in input according to the user filter settings. * Filtered samples are stored in output. Note: All samples in output will * always be overwritten! * * @param input * incoming (unfiltered) samples * @param output * outgoing (filtered) samples */ private void applyUserFilter(SamplePacket input, SamplePacket output) { // Verify that the filter is still correct configured: if (userFilter == null || ((int) userFilter.getCutOffFrequency()) != demodulation.getUserFilterCutOff()) { // We have to (re-)create the user filter: userFilter = FirFilter.createLowPass(1, 1, input.getSampleRate(), demodulation.getUserFilterCutOff(), input.getSampleRate() * 0.10f, USER_FILTER_ATTENUATION); if (userFilter == null) return; // This may happen if input samples changed rate or // demodulation was turned off. Just skip the filtering. Log.d(LOGTAG, "applyUserFilter: created new user filter with " + userFilter.getNumberOfTaps() + " taps. Decimation=" + userFilter.getDecimation() + " Cut-Off=" + userFilter.getCutOffFrequency() + " transition=" + userFilter.getTransitionWidth()); } output.setSize(0); // mark buffer as empty if (userFilter != null && userFilter.filter(input, output, 0, input.size()) < input.size()) { Log.e(LOGTAG, "applyUserFilter: could not filter all samples from input packet."); } } /** * Will set the modulation mode to the given value. Takes care of adjusting * the scheduler and the demodulator respectively and updates the action bar * menu item. * * @param demod * Demodulator.DEMODULATION_OFF, *_AM, *_NFM, *_WFM */ public void setDemodulationMode(DemoType type) { if (demodulation.getType() == type) return; // if (demodulation.getType()==DemoType.MORSE) // EventBus.getDefault().unregister(demodulation); // set demodulation mode in demodulator: demodulation = Demodulation.getDemodulation(type); decimator.setOutputSampleRate(demodulation.getQuadratureRate()); userFilter = null; if (type != DemoType.OFF) { // adjust sample rate of the source: SourceControl.getSource().setSampleRate(Demodulator.INPUT_RATE); preferences.setSampleRate(INPUT_RATE); // Verify that the source supports the sample rate: if (SourceControl.getSource().getSampleRate() != Demodulator.INPUT_RATE) { Log.e(LOGTAG, "setDemodulationMode: cannot adjust source sample rate!"); MyToast.makeText("Source does not support the sample rate necessary for demodulation (" + INPUT_RATE / 1000000 + " Msps)", Toast.LENGTH_LONG); StateHandler.setDemodulationMode(DemoType.OFF); } } } }