package de.tu.darmstadt.seemoo.ansian.model.sources; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import android.content.Context; import android.util.Log; import de.tu.darmstadt.seemoo.ansian.model.SamplePacket; import de.tu.darmstadt.seemoo.ansian.tools.IQConverter; import de.tu.darmstadt.seemoo.ansian.tools.Signed8BitIQConverter; import de.tu.darmstadt.seemoo.ansian.tools.Unsigned8BitIQConverter; /** * <h1>AnSiAn - File Source of IQ samples</h1> * * Module: FileIQSource.java Description: Simple source of IQ sampling by * reading from IQ files generated by the HackRF. Just for testing. * * @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 FileIQSource implements IQSourceInterface { private Callback callback = null; private boolean repeat = false; private int sampleRate = 0; private long frequency = 0; private int packetSize = 0; private int sleepTime = 0; // min. time (in ms) between two getPacket() // calls to simulate the sample rate private long lastAccessTime = 0; // timestamp of the last getPacket() call private byte[] buffer = null; private File file = null; private String filename = null; private BufferedInputStream bufferedInputStream = null; private IQConverter iqConverter; private int fileFormat; private static final String LOGTAG = "FileIQSource"; public static final int FILE_FORMAT_8BIT_SIGNED = 0; public static final int FILE_FORMAT_8BIT_UNSIGNED = 1; public FileIQSource(String filename, int sampleRate, long frequency, int packetSize, boolean repeat, int fileFormat) { this.filename = filename; this.file = new File(filename); this.repeat = repeat; this.fileFormat = fileFormat; this.sampleRate = sampleRate; this.frequency = frequency; this.packetSize = packetSize; this.buffer = new byte[packetSize]; this.sleepTime = (int) ((packetSize / 2) / (float) sampleRate * 1000); // note: // half // packet // size // because // of // I // and // Q // samples switch (fileFormat) { case FILE_FORMAT_8BIT_SIGNED: iqConverter = new Signed8BitIQConverter(); break; case FILE_FORMAT_8BIT_UNSIGNED: iqConverter = new Unsigned8BitIQConverter(); break; default: Log.e(LOGTAG, "constructor: Invalid file format: " + fileFormat); break; } iqConverter.setFrequency(frequency); iqConverter.setSampleRate(sampleRate); } private void reportError(String msg) { if (callback != null) callback.onIQSourceError(this, msg); else Log.e(LOGTAG, "Callback is null when reporting Error (" + msg + ")"); } @Override public boolean open(Context context, Callback callback) { this.callback = callback; // open the file try { this.bufferedInputStream = new BufferedInputStream(new FileInputStream(file)); callback.onIQSourceReady(this); return true; } catch (IOException e) { Log.e(LOGTAG, "open: Error while opening file: " + e.getMessage()); reportError("Error while opening file: " + e.getMessage()); return false; } } @Override public boolean isOpen() { if (bufferedInputStream == null) return false; try { if (bufferedInputStream.available() > 0) return true; } catch (IOException e) { } return false; } @Override public boolean close() { // close the file try { if (bufferedInputStream != null) bufferedInputStream.close(); return true; } catch (IOException e) { Log.e(LOGTAG, "stopSampling: Error while closing file: " + e.getMessage()); reportError("Unexpected error while closing file: " + e.getMessage()); return false; } } @Override public String getName() { return "IQ-File: " + file.getName(); } /** * @return the file name of the file */ public String getFilename() { return filename; } /** * @return true if repeat is enabled; false if not */ public boolean isRepeat() { return repeat; } /** * @return the format of the file: FILE_FORMAT_8BIT_SIGNED, ... */ public int getFileFormat() { return fileFormat; } @Override public int getSampleRate() { return sampleRate; } @Override public void setSampleRate(int sampleRate) { // Log.e(LOGTAG, "Setting the sample rate is not supported on a file // source"); } @Override public long getFrequency() { return frequency; } @Override public void setFrequency(long frequency) { // Log.e(LOGTAG, "Setting the frequency is not supported on a file // source"); } @Override public long getMaxFrequency() { return frequency + sampleRate; } @Override public long getMinFrequency() { return frequency - sampleRate; } @Override public int getMaxSampleRate() { return sampleRate; } @Override public int getMinSampleRate() { return sampleRate; } @Override public int getNextHigherOptimalSampleRate(int sampleRate) { return this.sampleRate; } @Override public int getNextLowerOptimalSampleRate(int sampleRate) { return this.sampleRate; } @Override public int[] getSupportedSampleRates() { return new int[] { this.sampleRate }; } @Override public int getPacketSize() { return packetSize; } @Override public byte[] getPacket(int timeout) { if (bufferedInputStream == null) return null; try { // Simulate sample rate of real hardware: int sleep = Math.min(sleepTime - (int) (System.currentTimeMillis() - lastAccessTime), timeout); if (sleep > 0) Thread.sleep(sleep); // Read the samples. if (bufferedInputStream.read(buffer, 0, buffer.length) != buffer.length) { if (repeat) { // rewind and try again: Log.i(LOGTAG, "getPacket: End of File. Rewind!"); bufferedInputStream.close(); this.bufferedInputStream = new BufferedInputStream(new FileInputStream(file)); if (bufferedInputStream.read(buffer, 0, buffer.length) != buffer.length) return null; else { lastAccessTime = System.currentTimeMillis(); return buffer; } } else { Log.i(LOGTAG, "getPacket: End of File"); reportError("End of File"); return null; } } } catch (IOException e) { Log.e(LOGTAG, "getPacket: Error while reading from file: " + e.getMessage()); reportError("Unexpected error while reading file: " + e.getMessage()); return null; } catch (InterruptedException e) { Log.w(LOGTAG, "getPacket: Interrupted while sleeping!"); return null; } lastAccessTime = System.currentTimeMillis(); return buffer; } @Override public void returnPacket(byte[] buffer) { // do nothing } @Override public void startSampling() { // nothing to do here... } @Override public void stopSampling() { // nothing to do here... } @Override public int fillPacketIntoSamplePacket(byte[] packet, SamplePacket samplePacket) { return this.iqConverter.fillPacketIntoSamplePacket(packet, samplePacket); } public int mixPacketIntoSamplePacket(byte[] packet, SamplePacket samplePacket, long channelFrequency) { return this.iqConverter.mixPacketIntoSamplePacket(packet, samplePacket, channelFrequency); } @Override public SourceType getType() { return SourceType.FILE_SOURCE; } @Override public boolean isTunerSettled() { return true; } @Override public boolean allowsScanning() { return false; } }