package de.tu.darmstadt.seemoo.ansian.model.sources; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.net.UnknownHostException; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.TimeUnit; 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.Unsigned8BitIQConverter; /** * <h1>AnSiAn - rtl-sdr source</h1> * * Module: RtlsdrSource.java Description: Simple source of IQ sampling by * reading from IQ files generated by the HackRF. Just for testing. * * @author Dennis Mantz * * 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 RtlsdrSource implements IQSourceInterface { public static final int RTLSDR_TUNER_UNKNOWN = 0; public static final int RTLSDR_TUNER_E4000 = 1; public static final int RTLSDR_TUNER_FC0012 = 2; public static final int RTLSDR_TUNER_FC0013 = 3; public static final int RTLSDR_TUNER_FC2580 = 4; public static final int RTLSDR_TUNER_R820T = 5; public static final int RTLSDR_TUNER_R828D = 6; public static final String[] TUNER_STRING = { "UNKNOWN", "E4000", "FC0012", "FC0013", "FC2580", "R820T", "R828D" }; public static final int RTL_TCP_COMMAND_SET_FREQUENCY = 0x01; public static final int RTL_TCP_COMMAND_SET_SAMPLERATE = 0x02; public static final int RTL_TCP_COMMAND_SET_GAIN_MODE = 0x03; public static final int RTL_TCP_COMMAND_SET_GAIN = 0x04; public static final int RTL_TCP_COMMAND_SET_FREQ_CORR = 0x05; public static final int RTL_TCP_COMMAND_SET_IFGAIN = 0x06; public static final int RTL_TCP_COMMAND_SET_AGC_MODE = 0x08; public final String[] COMMAND_NAME = { "invalid", "SET_FREQUENY", "SET_SAMPLERATE", "SET_GAIN_MODE", "SET_GAIN", "SET_FREQ_CORR", "SET_IFGAIN", "SET_TEST_MODE", "SET_ADC_MODE" }; private ReceiverThread receiverThread = null; private CommandThread commandThread = null; private Callback callback = null; private Socket socket = null; private InputStream inputStream = null; private OutputStream outputStream = null; private String name = "RTL-SDR"; private String magic = null; private int tuner = RTLSDR_TUNER_UNKNOWN; private String ipAddress = "127.0.0.1"; private int port = 1234; private ArrayBlockingQueue<byte[]> queue = null; private ArrayBlockingQueue<byte[]> returnQueue = null; private long frequency = 0; private int sampleRate = 0; private int gain = 0; private int ifGain = 0; private boolean manualGain = true; // true == manual; false == automatic private int frequencyCorrection = 0; private boolean automaticGainControl = false; private int frequencyShift = 0; // virtually shift the frequency according // to an external up/down-converter private IQConverter iqConverter; private static final String LOGTAG = "RtlsdrSource"; private static final int QUEUE_SIZE = 20; public static final int[] OPTIMAL_SAMPLE_RATES = { 1000000, 1024000, 1800000, 1920000, 2000000, 2048000, 2400000 }; public static final long[] MIN_FREQUENCY = { 0, // invalid 52000000l, // E4000 22000000l, // FC0012 22000000l, // FC0013 146000000l, // FC2580 24000000l, // R820T 24000000l }; // R828D public static final long[] MAX_FREQUENCY = { 0l, // invalid 3000000000l, // E4000 actual max freq: 2200000000l 3000000000l, // FC0012 actual max freq: 948000000l 3000000000l, // FC0013 actual max freq: 1100000000l 3000000000l, // FC2580 actual max freq: 924000000l 3000000000l, // R820T actual max freq: 1766000000l 3000000000l }; // R828D actual max freq: 1766000000l public static final int[][] POSSIBLE_GAIN_VALUES = { // Values from // gr_osmocom // rt_tcp_source_s.cc: { 0 }, // invalid { -10, 15, 40, 65, 90, 115, 140, 165, 190, 215, 240, 290, 340, 420 }, // E4000 { -99, -40, 71, 179, 192 }, // FC0012 { -99, -73, -65, -63, -60, -58, -54, 58, 61, 63, 65, 67, 68, 70, 71, 179, 181, 182, 184, 186, 188, 191, 197 }, // FC0013 { 0 }, // FC2580 { 0, 9, 14, 27, 37, 77, 87, 125, 144, 157, 166, 197, 207, 229, 254, 280, 297, 328, 338, 364, 372, 386, 402, 421, 434, 439, 445, 480, 496 }, // R820T { 0 } // R828D ?? }; public static final int PACKET_SIZE = 16384; private long lastFreqChangeTime = 0; private long freqChangeDelay = 150; public RtlsdrSource(String ip, int port) { this.ipAddress = ip; this.port = port; // Create queues and buffers: queue = new ArrayBlockingQueue<byte[]>(QUEUE_SIZE); returnQueue = new ArrayBlockingQueue<byte[]>(QUEUE_SIZE); for (int i = 0; i < QUEUE_SIZE; i++) returnQueue.offer(new byte[PACKET_SIZE]); this.iqConverter = new Unsigned8BitIQConverter(); } /** * Will forward an error message to the callback object * * @param msg * error message */ private void reportError(String msg) { if (callback != null) callback.onIQSourceError(this, msg); else Log.e(LOGTAG, "reportError: Callback is null. (Error: " + msg + ")"); } /** * This will start the RTL2832U driver app if ip address is loopback and * connect to the rtl_tcp instance * * @param context * not used * @param callback * reference to a class that implements the Callback interface * for notification * @return */ @Override public boolean open(Context context, Callback callback) { this.callback = callback; // Start the command thread (this will perform the "open" procedure: // connecting to the rtl_tcp instance, read information and inform the // callback handler if (commandThread != null) { Log.e(LOGTAG, "open: Command thread is still running"); reportError("Error while opening device"); return false; } commandThread = new CommandThread(); commandThread.start(); return true; } @Override public boolean isOpen() { return (commandThread != null); } @Override public boolean close() { // Stop receving: if (receiverThread != null) stopSampling(); // Stop the command thread: if (commandThread != null) { commandThread.stopCommandThread(); // Join the thread only if the current thread is NOT the // commandThread ^^ if (!Thread.currentThread().getName().equals(commandThread.threadName)) { try { commandThread.join(); } catch (InterruptedException e) { } } commandThread = null; } this.tuner = 0; this.magic = null; this.name = "RTL-SDR"; return true; } @Override public String getName() { return name; } public String getIpAddress() { return ipAddress; } public int getPort() { return port; } @Override public int getSampleRate() { return sampleRate; } @Override public void setSampleRate(int sampleRate) { if (isOpen()) { if (sampleRate < getMinSampleRate() || sampleRate > getMaxSampleRate()) { Log.e(LOGTAG, "setSampleRate: Sample rate out of valid range: " + sampleRate); sampleRate = Math.min(getMaxSampleRate(), Math.max(getMinSampleRate(), sampleRate)); } if (!commandThread.executeCommand(commandToByteArray(RTL_TCP_COMMAND_SET_SAMPLERATE, sampleRate))) { Log.e(LOGTAG, "setSampleRate: failed."); } } // Flush the queue: this.flushQueue(); this.sampleRate = sampleRate; this.iqConverter.setSampleRate(sampleRate); } @Override public long getFrequency() { return frequency + frequencyShift; } @Override public void setFrequency(long frequency) { long actualSourceFrequency = frequency - frequencyShift; if (isOpen()) { if (frequency < getMinFrequency() || frequency > getMaxFrequency()) { Log.e(LOGTAG, "setFrequency: Frequency out of valid range: " + frequency + " (upconverterFrequency=" + frequencyShift + " is subtracted!)"); return; } commandThread.executeFrequencyChangeCommand( commandToByteArray(RTL_TCP_COMMAND_SET_FREQUENCY, (int) actualSourceFrequency)); lastFreqChangeTime = System.currentTimeMillis(); } // Flush the queue: this.flushQueue(); this.frequency = actualSourceFrequency; this.iqConverter.setFrequency(frequency); } @Override public long getMaxFrequency() { return MAX_FREQUENCY[tuner] + frequencyShift; } @Override public long getMinFrequency() { return MIN_FREQUENCY[tuner] + frequencyShift; } @Override public int getMaxSampleRate() { return OPTIMAL_SAMPLE_RATES[OPTIMAL_SAMPLE_RATES.length - 1]; } @Override public int getMinSampleRate() { return OPTIMAL_SAMPLE_RATES[0]; } @Override public int getNextHigherOptimalSampleRate(int sampleRate) { for (int opt : OPTIMAL_SAMPLE_RATES) { if (sampleRate < opt) return opt; } return OPTIMAL_SAMPLE_RATES[OPTIMAL_SAMPLE_RATES.length - 1]; } @Override public int getNextLowerOptimalSampleRate(int sampleRate) { for (int i = 1; i < OPTIMAL_SAMPLE_RATES.length; i++) { if (sampleRate <= OPTIMAL_SAMPLE_RATES[i]) return OPTIMAL_SAMPLE_RATES[i - 1]; } return OPTIMAL_SAMPLE_RATES[OPTIMAL_SAMPLE_RATES.length - 1]; } @Override public int[] getSupportedSampleRates() { return OPTIMAL_SAMPLE_RATES; } public boolean isManualGain() { return manualGain; } public void setManualGain(boolean enable) { if (isOpen()) { if (!commandThread .executeCommand(commandToByteArray(RTL_TCP_COMMAND_SET_GAIN_MODE, (int) (enable ? 0x01 : 0x00)))) { Log.e(LOGTAG, "setManualGain: failed."); } } this.manualGain = enable; setAutomaticGainControl(!enable); } public int getGain() { return gain; } public int[] getPossibleGainValues() { return POSSIBLE_GAIN_VALUES[tuner]; } public void setGain(int gain) { if (isOpen()) { if (!commandThread.executeCommand(commandToByteArray(RTL_TCP_COMMAND_SET_GAIN, gain))) { Log.e(LOGTAG, "setGain: failed."); } } this.gain = gain; } public int getIFGain() { return ifGain; } public int[] getPossibleIFGainValues() { if (tuner == RTLSDR_TUNER_E4000) { int[] ifGainValues = new int[54]; for (int i = 0; i < ifGainValues.length; i++) ifGainValues[i] = i + 3; return ifGainValues; } else { return new int[] { 0 }; } } public void setIFGain(int ifGain) { if (isOpen() && tuner == RTLSDR_TUNER_E4000) { if (!commandThread .executeCommand(commandToByteArray(RTL_TCP_COMMAND_SET_IFGAIN, (short) 0, (short) ifGain))) { Log.e(LOGTAG, "setIFGain: failed."); } } this.ifGain = ifGain; } public int getFrequencyCorrection() { return frequencyCorrection; } public void setFrequencyCorrection(int ppm) { if (isOpen()) { if (!commandThread.executeCommand(commandToByteArray(RTL_TCP_COMMAND_SET_FREQ_CORR, ppm))) { Log.e(LOGTAG, "setFrequencyCorrection: failed."); } } this.frequencyCorrection = ppm; } public boolean isAutomaticGainControl() { return automaticGainControl; } private void setAutomaticGainControl(boolean enable) { if (isOpen()) { if (!commandThread .executeCommand(commandToByteArray(RTL_TCP_COMMAND_SET_AGC_MODE, (int) (enable ? 0x01 : 0x00)))) { Log.e(LOGTAG, "setAutomaticGainControl: failed."); } } this.automaticGainControl = enable; } public int getFrequencyShift() { return frequencyShift; } public void setFrequencyShift(int frequencyShift) { this.frequencyShift = frequencyShift; this.iqConverter.setFrequency(frequency + frequencyShift); } @Override public int getPacketSize() { return PACKET_SIZE; } @Override public byte[] getPacket(int timeout) { if (queue != null) { try { return queue.poll(timeout, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { Log.e(LOGTAG, "getPacket: Interrupted while polling packet from queue: " + e.getMessage()); } } else { Log.e(LOGTAG, "getPacket: Queue is null"); } return null; } @Override public void returnPacket(byte[] buffer) { if (returnQueue != null) { returnQueue.offer(buffer); } else { Log.e(LOGTAG, "returnPacket: Return queue is null"); } } @Override public void startSampling() { if (receiverThread != null) { Log.e(LOGTAG, "startSampling: receiver thread still running."); reportError("Could not start sampling"); return; } if (isOpen()) { // start ReceiverThread: receiverThread = new ReceiverThread(inputStream, returnQueue, queue); receiverThread.start(); } } @Override public void stopSampling() { // stop and join receiver thread: if (receiverThread != null) { receiverThread.stopReceiving(); // Join the thread only if the current thread is NOT the // receiverThread ^^ if (!Thread.currentThread().getName().equals(receiverThread.threadName)) { try { receiverThread.join(); } catch (InterruptedException e) { Log.e(LOGTAG, "stopSampling: Interrupted while joining receiver thread: " + e.getMessage()); } } receiverThread = null; } } @Override public int fillPacketIntoSamplePacket(byte[] packet, SamplePacket samplePacket) { return this.iqConverter.fillPacketIntoSamplePacket(packet, samplePacket); } @Override public int mixPacketIntoSamplePacket(byte[] packet, SamplePacket samplePacket, long channelFrequency) { return this.iqConverter.mixPacketIntoSamplePacket(packet, samplePacket, channelFrequency); } /** * Will empty the queue */ public void flushQueue() { byte[] buffer; for (int i = 0; i < QUEUE_SIZE; i++) { buffer = queue.poll(); if (buffer == null) return; // we are done; the queue is empty. this.returnPacket(buffer); } } /** * Will pack a rtl_tcp command into a byte buffer * * @param command * RTL_TCP_COMMAND_* * @param arg * command argument (see rtl_tcp documentation) * @return command buffer */ private byte[] commandToByteArray(int command, int arg) { byte[] commandArray = new byte[5]; commandArray[0] = (byte) command; commandArray[1] = (byte) ((arg >> 24) & 0xff); commandArray[2] = (byte) ((arg >> 16) & 0xff); commandArray[3] = (byte) ((arg >> 8) & 0xff); commandArray[4] = (byte) (arg & 0xff); return commandArray; } /** * Will pack a rtl_tcp command into a byte buffer * * @param command * RTL_TCP_COMMAND_* * @param arg1 * first command argument (see rtl_tcp documentation) * @param arg2 * second command argument (see rtl_tcp documentation) * @return command buffer */ private byte[] commandToByteArray(int command, short arg1, short arg2) { byte[] commandArray = new byte[5]; commandArray[0] = (byte) command; commandArray[1] = (byte) ((arg1 >> 8) & 0xff); commandArray[2] = (byte) (arg1 & 0xff); commandArray[3] = (byte) ((arg2 >> 8) & 0xff); commandArray[4] = (byte) (arg2 & 0xff); return commandArray; } /** * This thread will read samples from the socket and put them in the queue */ private class ReceiverThread extends Thread { public String threadName = null; // We save the thread name to check // against it in the stopSampling() // method private boolean stopRequested = false; private InputStream inputStream = null; private ArrayBlockingQueue<byte[]> inputQueue = null; private ArrayBlockingQueue<byte[]> outputQueue = null; public ReceiverThread(InputStream inputStream, ArrayBlockingQueue<byte[]> inputQueue, ArrayBlockingQueue<byte[]> outputQueue) { this.inputStream = inputStream; this.inputQueue = inputQueue; this.outputQueue = outputQueue; } public void stopReceiving() { this.stopRequested = true; } public void run() { byte[] buffer = null; int index = 0; int bytesRead = 0; Log.i(LOGTAG, "ReceiverThread started (Thread: " + this.getName() + ")"); threadName = this.getName(); while (!stopRequested) { try { // if buffer is null we request a new buffer from the // inputQueue: if (buffer == null) { buffer = inputQueue.poll(1000, TimeUnit.MILLISECONDS); index = 0; } if (buffer == null) { Log.e(LOGTAG, "ReceiverThread: Couldn't get buffer from input queue. stop."); this.stopRequested = true; break; } // Read into the buffer from the inputStream: bytesRead = inputStream.read(buffer, index, buffer.length - index); if (bytesRead <= 0) { Log.e(LOGTAG, "ReceiverThread: Couldn't read data from input stream. stop."); this.stopRequested = true; break; } index += bytesRead; if (index == buffer.length) { // buffer is full. Send it to the output queue: outputQueue.offer(buffer); buffer = null; } } catch (InterruptedException e) { Log.e(LOGTAG, "ReceiverThread: Interrupted while waiting: " + e.getMessage()); this.stopRequested = true; break; } catch (IOException e) { Log.e(LOGTAG, "ReceiverThread: Error while reading from socket: " + e.getMessage()); reportError("Error while receiving samples."); this.stopRequested = true; break; } catch (NullPointerException e) { Log.e(LOGTAG, "ReceiverThread: Nullpointer! (Probably inputStream): " + e.getStackTrace()); this.stopRequested = true; break; } } // check if we still hold a buffer and return it to the input queue: if (buffer != null) inputQueue.offer(buffer); Log.i(LOGTAG, "ReceiverThread stopped (Thread: " + this.getName() + ")"); } } /** * This thread will initiate the connection to the rtl_tcp instance and then * send commands to it. Commands can be queued for execution by other * threads */ private class CommandThread extends Thread { public String threadName = null; // We save the thread name to check // against it in the close() method private ArrayBlockingQueue<byte[]> commandQueue = null; private static final int COMMAND_QUEUE_SIZE = 20; private ArrayBlockingQueue<byte[]> frequencyChangeCommandQueue = null; // separate // queue // for // frequency // changes // (work-around) private boolean stopRequested = false; public CommandThread() { // Create command queue: this.commandQueue = new ArrayBlockingQueue<byte[]>(COMMAND_QUEUE_SIZE); this.frequencyChangeCommandQueue = new ArrayBlockingQueue<byte[]>(1); // work-around } public void stopCommandThread() { this.stopRequested = true; } /** * Will schedule the command (put it into the command queue * * @param command * 5 byte command array (see rtl_tcp documentation) * @return true if command has been scheduled; */ public boolean executeCommand(byte[] command) { Log.d(LOGTAG, "executeCommand: Queuing command: " + COMMAND_NAME[command[0]]); if (commandQueue.offer(command)) return true; // Queue is full // todo: maybe flush the queue? for now just error: Log.e(LOGTAG, "executeCommand: command queue is full!"); return false; } /** * Work-around: Frequency changes happen very often and if too many of * these commands are sent to the driver it will lag and eventually * crash. To prevent this, we have a separate commandQueue only for * frequency changes. This queue has size 1 and * executeFrequencyChangeCommand() will ensure that it contains always * the latest frequency change command. The command thread will always * sleep 250 ms after executing a frequency change command to prevent a * high rate of commands. * * @param command * 5 byte command array (see rtl_tcp documentation) */ public void executeFrequencyChangeCommand(byte[] command) { // remove any waiting frequency change command from the queue (not // used any more): frequencyChangeCommandQueue.poll(); frequencyChangeCommandQueue.offer(command); // will always work } /** * Called from run(); will setup the connection to the rtl_tcp instance */ private boolean connect(int timeoutMillis) { if (socket != null) { Log.e(LOGTAG, "connect: Socket is still connected"); return false; } // Connect to remote/local rtl_tcp try { long timeoutTime = System.currentTimeMillis() + timeoutMillis; while (!stopRequested && socket == null && System.currentTimeMillis() < timeoutTime) { try { socket = new Socket(ipAddress, port); } catch (IOException e) { // ignore... } sleep(100); } if (socket == null) { if (stopRequested) Log.i(LOGTAG, "CommandThread: (connect) command thread stopped while connecting."); else Log.e(LOGTAG, "CommandThread: (connect) hit timeout"); return false; } // Set socket options: socket.setTcpNoDelay(true); socket.setSoTimeout(1000); inputStream = socket.getInputStream(); outputStream = socket.getOutputStream(); byte[] buffer = new byte[4]; // Read magic value: if (inputStream.read(buffer, 0, buffer.length) != buffer.length) { Log.e(LOGTAG, "CommandThread: (connect) Could not read magic value"); return false; } magic = new String(buffer, "ASCII"); // Read tuner type: if (inputStream.read(buffer, 0, buffer.length) != buffer.length) { Log.e(LOGTAG, "CommandThread: (connect) Could not read tuner type"); return false; } tuner = buffer[3]; if (tuner <= 0 || tuner >= TUNER_STRING.length) { Log.e(LOGTAG, "CommandThread: (connect) Invalid tuner type"); return false; } // Read gain count (only for debugging. value is not used for // now) if (inputStream.read(buffer, 0, buffer.length) != buffer.length) { Log.e(LOGTAG, "CommandThread: (connect) Could not read gain count"); return false; } Log.i(LOGTAG, "CommandThread: (connect) Connected to RTL-SDR (Tuner: " + TUNER_STRING[tuner] + "; magic: " + magic + "; gain count: " + buffer[3] + ") at " + ipAddress + ":" + port); // Update source name with the new information: name = "RTL-SDR (" + TUNER_STRING[tuner] + ") at " + ipAddress + ":" + port; // Check if parameters are in range and correct them: if (frequency > MAX_FREQUENCY[tuner]) { frequency = MAX_FREQUENCY[tuner]; } if (frequency < MIN_FREQUENCY[tuner]) { frequency = MIN_FREQUENCY[tuner]; } iqConverter.setFrequency(frequency + frequencyShift); if (sampleRate > getMaxSampleRate()) sampleRate = getMaxSampleRate(); if (sampleRate < getMinSampleRate()) sampleRate = getMinSampleRate(); for (int gainStep : getPossibleGainValues()) { if (gainStep >= gain) { gain = gainStep; break; } } // Set all parameters: // Frequency: executeCommand(commandToByteArray(RTL_TCP_COMMAND_SET_FREQUENCY, (int) frequency)); // Sample Rate: executeCommand(commandToByteArray(RTL_TCP_COMMAND_SET_SAMPLERATE, sampleRate)); // Gain Mode: executeCommand(commandToByteArray(RTL_TCP_COMMAND_SET_GAIN_MODE, (int) (manualGain ? 0x01 : 0x00))); // Gain: if (manualGain) executeCommand(commandToByteArray(RTL_TCP_COMMAND_SET_GAIN, gain)); // IFGain: if (manualGain && tuner == RTLSDR_TUNER_E4000) executeCommand(commandToByteArray(RTL_TCP_COMMAND_SET_IFGAIN, (short) 0, (short) ifGain)); // Frequency Correction: executeCommand(commandToByteArray(RTL_TCP_COMMAND_SET_FREQ_CORR, frequencyCorrection)); // AGC mode: executeCommand( commandToByteArray(RTL_TCP_COMMAND_SET_AGC_MODE, (int) (automaticGainControl ? 0x01 : 0x00))); return true; } catch (UnknownHostException e) { Log.e(LOGTAG, "CommandThread: (connect) Unknown host: " + ipAddress); reportError("Unknown host: " + ipAddress); } catch (IOException e) { Log.e(LOGTAG, "CommandThread: (connect) Error while connecting to rtlsdr://" + ipAddress + ":" + port + " : " + e.getMessage()); } catch (InterruptedException e) { Log.e(LOGTAG, "CommandThread: (connect) Interrupted."); } return false; } public void run() { Log.i(LOGTAG, "CommandThread started (Thread: " + this.getName() + ")"); threadName = this.getName(); byte[] nextCommand = null; // Perfom "device open". This means connect to the rtl_tcp instance; // get the information if (connect(10000)) { // 10 seconds for the user to accept // permission request // report that the device is ready: callback.onIQSourceReady(RtlsdrSource.this); } else { if (!stopRequested) { Log.e(LOGTAG, "CommandThread: (open) connect reported error."); reportError("Couldn't connect to rtl_tcp instance"); stopRequested = true; } // else: thread was stopped while connecting... } // poll commands from queue and send them over the socket in loop: while (!stopRequested && outputStream != null) { try { nextCommand = commandQueue.poll(100, TimeUnit.MILLISECONDS); // Work-around: // Frequency changes happen very often and if too many of // these commands are sent to the driver // it will lag and eventually crash. To prevent this, we // have a separate commandQueue only for // frequency changes. This queue has size 1 and // executeFrequencyChangeCommand() will ensure that // it contains always the latest frequency change command. // The command thread will always sleep 100 ms // after executing a frequency change command to prevent a // high rate of commands. if (nextCommand == null) nextCommand = frequencyChangeCommandQueue.poll(); // check // for // frequency // change // commands: if (nextCommand == null) continue; outputStream.write(nextCommand); Log.d(LOGTAG, "CommandThread: Command was sent: " + COMMAND_NAME[nextCommand[0]]); } catch (IOException e) { Log.e(LOGTAG, "CommandThread: Error while sending command (" + COMMAND_NAME[nextCommand[0]] + "): " + e.getMessage()); reportError("Error while sending command: " + COMMAND_NAME[nextCommand[0]]); break; } catch (InterruptedException e) { Log.e(LOGTAG, "CommandThread: Interrupted while sending command (" + COMMAND_NAME[nextCommand[0]] + ")"); reportError("Interrupted while sending command: " + COMMAND_NAME[nextCommand[0]]); break; } } // Clean up: if (socket != null) { try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } socket = null; inputStream = null; outputStream = null; RtlsdrSource.this.commandThread = null; // mark this source as // 'closed' Log.i(LOGTAG, "CommandThread stopped (Thread: " + this.getName() + ")"); } } @Override public SourceType getType() { return SourceType.RTLSDR_SOURCE; } @Override public boolean isTunerSettled() { return (System.currentTimeMillis() - lastFreqChangeTime >= freqChangeDelay); } @Override public boolean allowsScanning() { return true; } }