11 |
12 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Airspy_Test.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/demantz/airspy_android/7e06c96c086f0a3c27801b371ede86adc260e877/Airspy_Test.apk
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 | Airspy Library for Android
2 | ==========================
3 |
4 | This repository is a ported version of Benjamin Vernoux's and
5 | Youssef Touil's libairspy
6 | ([https://github.com/airspy/host/tree/master/libairspy]
7 | (https://github.com/airspy/host/tree/master/libairspy))
8 | library to work with Android 3.1+.
9 |
10 | See [http://tech.mantz-it.com](http://tech.mantz-it.com) and @dennismantz for updates.
11 |
12 |
13 | Implemented Features
14 | --------------------
15 | * Open Airspy (including the USB permission request)
16 | * Reading Board ID from Airspy
17 | * Reading Version from Airspy
18 | * Reading Part ID and Serial Number from Airspy
19 | * Setting Sample Rate of Airspy
20 | * Setting Frequency of Airspy
21 | * Setting VGA Gain of Airspy
22 | * Setting LNA Gain of Airspy
23 | * Setting Mixer Gain of Airspy
24 | * Receiving from the Airspy using a BlockingQueue
25 | * Get Transmission statistics
26 | * Example App that shows how to use the library
27 |
28 |
29 | Tested Devices
30 | --------------
31 |
32 | | Device | Does it work? | Comments | Tester |
33 | |:------------------:|:-------------:|:-----------------------------------------:|:----------------:|
34 | | Nexus 7 2012 | yes and no | IQ not working (performance issue) | demantz |
35 | | Nexus 5 | yes and no | IQ not working (performance issue) | demantz |
36 |
37 |
38 |
39 | Known Issues
40 | ------------
41 | * packing not working
42 | * performance issues
43 |
44 |
45 | Installation / Usage
46 | --------------------
47 | Build the library and the example app by using Androis Studio:
48 | * Import existing project (root of the git repository)
49 | * Build
50 | * The aar library file will be under airspy_android/build/outputs/aar/airspy_android-release.aar
51 |
52 | If you want to use the library in your own app, just create a new module in your
53 | Android Studio project and select 'Import .JAR/.AAR'. Name it airspy_android.
54 | Then add to the gradle.build file of your main module:
55 |
56 | dependencies {
57 | compile project(':airspy_android')
58 | }
59 |
60 | The airspy_android.aar and the Airspy_Test.apk files are also in this repository
61 | so that they can be used without building them. But they won't be synched to the
62 | latest code base all the time.
63 |
64 | Use the example application to test the library on your device and trouble shoot
65 | any problems. It has the option to show the logcat output!
66 |
67 | License
68 | -------
69 | This library is free software; you can redistribute it and/or
70 | modify it under the terms of the GNU General Public
71 | License as published by the Free Software Foundation; either
72 | version 2 of the License, or (at your option) any later version.
73 | [http://www.gnu.org/licenses/gpl.html](http://www.gnu.org/licenses/gpl.html) GPL version 2 or higher
74 |
75 | principal author: Dennis Mantz
76 |
77 | principal author of libairspy: Benjamin Vernoux and Youssef Touil
78 |
--------------------------------------------------------------------------------
/airspy_android.aar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/demantz/airspy_android/7e06c96c086f0a3c27801b371ede86adc260e877/airspy_android.aar
--------------------------------------------------------------------------------
/airspy_android.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
26 | *
27 | * Module: Airspy.java
28 | * Description: The Airspy class represents the Airspy device and
29 | * acts as abstraction layer that manages the USB
30 | * communication between the device and the application.
31 | *
32 | * @author Dennis Mantz
33 | *
34 | * Copyright (C) 2015 Dennis Mantz
35 | * based on code of libairspy [https://github.com/airspy/host/tree/master/libairspy]:
36 | * Copyright (c) 2013, Michael Ossmann
37 | * Copyright (c) 2012, Jared Boone
38 | * Copyright (c) 2014, Youssef Touil
39 | * Copyright (c) 2014, Benjamin Vernoux
40 | * Copyright (c) 2015, Ian Gilmour
41 | * All rights reserved.
42 | * Redistribution and use in source and binary forms, with or without modification,
43 | * are permitted provided that the following conditions are met:
44 | * - Redistributions of source code must retain the above copyright notice, this list
45 | * of conditions and the following disclaimer.
46 | * - Redistributions in binary form must reproduce the above copyright notice, this
47 | * list of conditions and the following disclaimer in the documentation and/or other
48 | * materials provided with the distribution.
49 | * - Neither the name of Great Scott Gadgets nor the names of its contributors may be
50 | * used to endorse or promote products derived from this software without specific
51 | * prior written permission.
52 | * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
53 | *
54 | * This library is free software; you can redistribute it and/or
55 | * modify it under the terms of the GNU General Public
56 | * License as published by the Free Software Foundation; either
57 | * version 2 of the License, or (at your option) any later version.
58 | *
59 | * This library is distributed in the hope that it will be useful,
60 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
61 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
62 | * General Public License for more details.
63 | *
64 | * You should have received a copy of the GNU General Public
65 | * License along with this library; if not, write to the Free Software
66 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
67 | */
68 | public class Airspy implements Runnable {
69 |
70 | // Sample types:
71 | public static final int AIRSPY_SAMPLE_FLOAT32_IQ = 0; // 2 * 32bit float per sample
72 | public static final int AIRSPY_SAMPLE_FLOAT32_REAL = 1; // 1 * 32bit float per sample
73 | public static final int AIRSPY_SAMPLE_INT16_IQ = 2; // 2 * 16bit int per sample
74 | public static final int AIRSPY_SAMPLE_INT16_REAL = 3; // 1 * 16bit int per sample
75 | public static final int AIRSPY_SAMPLE_UINT16_REAL = 4; // 1 * 16bit unsigned int per sample (raw)
76 |
77 | // Attributes to hold the USB related objects:
78 | private UsbManager usbManager = null;
79 | private UsbDevice usbDevice = null;
80 | private UsbInterface usbInterface = null;
81 | private UsbDeviceConnection usbConnection = null;
82 | private UsbEndpoint usbEndpointIN = null;
83 | private UsbEndpoint usbEndpointOUT = null;
84 |
85 | private int receiverMode = AIRSPY_RECEIVER_MODE_OFF; // current mode of the Airspy
86 | private int sampleType = AIRSPY_SAMPLE_UINT16_REAL; // Type of the samples that should be delivered
87 | private boolean packingEnabled = false; // is packing currently enabled in the Airspy?
88 | private boolean rawMode = false; // if true, the conversion thread is bypassed and the
89 | // user will access the usbQueue directly
90 | private Thread usbThread = null; // hold the receiver Thread if running
91 | private int usbQueueSize = 16; // Size of the usbQueue
92 | private ArrayBlockingQueue usbQueue = null; // queue that buffers samples received from the Airspy
93 | private ArrayBlockingQueue usbBufferPool = null; // queue that holds spare buffers which can be
94 | // reused while receiving samples from the Airspy
95 | private int conversionQueueSize = 20; // Size of the conversionQueue
96 | private ArrayBlockingQueue conversionQueueInt16 = null; // queue that buffers samples that were processed by
97 | // the conversion thread (if sample type is int16)
98 | private ArrayBlockingQueue conversionBufferPoolInt16 = null; // queue that holds spare buffers which can be
99 | // used for conversion processing (if sample type is int16)
100 | private ArrayBlockingQueue conversionQueueFloat = null; // queue that buffers samples that were processed by
101 | // the conversion thread (if sample type is float)
102 | private ArrayBlockingQueue conversionBufferPoolFloat = null; // queue that holds spare buffers which can be
103 | // used for conversion processing (if sample type is float)
104 | private int usbPacketSize = 1024 * 16; // Buffer Size of each UsbRequest
105 | private AirspyInt16Converter int16Converter = null; // Reference to the int16 converter
106 | private AirspyFloatConverter floatConverter = null; // Reference to the float converter
107 |
108 | // startTime (in ms since 1970) and packetCounter for statistics:
109 | private long receiveStartTime = 0;
110 | private long receivePacketCounter = 0;
111 |
112 | // Receiver Modes:
113 | public static final int AIRSPY_RECEIVER_MODE_OFF = 0;
114 | public static final int AIRSPY_RECEIVER_MODE_RECEIVE = 1;
115 |
116 | // USB Vendor Requests (from airspy_commands.h)
117 | private static final int AIRSPY_INVALID = 0;
118 | private static final int AIRSPY_RECEIVER_MODE = 1;
119 | private static final int AIRSPY_SI5351C_WRITE = 2;
120 | private static final int AIRSPY_SI5351C_READ = 3;
121 | private static final int AIRSPY_R820T_WRITE = 4;
122 | private static final int AIRSPY_R820T_READ = 5;
123 | private static final int AIRSPY_SPIFLASH_ERASE = 6;
124 | private static final int AIRSPY_SPIFLASH_WRITE = 7;
125 | private static final int AIRSPY_SPIFLASH_READ = 8;
126 | private static final int AIRSPY_BOARD_ID_READ = 9;
127 | private static final int AIRSPY_VERSION_STRING_READ = 10;
128 | private static final int AIRSPY_BOARD_PARTID_SERIALNO_READ = 11;
129 | private static final int AIRSPY_SET_SAMPLERATE = 12;
130 | private static final int AIRSPY_SET_FREQ = 13;
131 | private static final int AIRSPY_SET_LNA_GAIN = 14;
132 | private static final int AIRSPY_SET_MIXER_GAIN = 15;
133 | private static final int AIRSPY_SET_VGA_GAIN = 16;
134 | private static final int AIRSPY_SET_LNA_AGC = 17;
135 | private static final int AIRSPY_SET_MIXER_AGC = 18;
136 | private static final int AIRSPY_MS_VENDOR_CMD = 19;
137 | private static final int AIRSPY_SET_RF_BIAS_CMD = 20;
138 | private static final int AIRSPY_GPIO_WRITE = 21;
139 | private static final int AIRSPY_GPIO_READ = 22;
140 | private static final int AIRSPY_GPIODIR_WRITE = 23;
141 | private static final int AIRSPY_GPIODIR_READ = 24;
142 | private static final int AIRSPY_GET_SAMPLERATES = 25;
143 | private static final int AIRSPY_SET_PACKING = 26;
144 |
145 | // Some Constants:
146 | private static final String LOGTAG = "airspy_android";
147 | private static final String AIRSPY_USB_PERMISSION = "com.mantz_it.airspy_android.USB_PERMISSION";
148 | private static final int numUsbRequests = 16; // Number of parallel UsbRequests
149 |
150 | /**
151 | * Initializing the Airspy Instance with a USB Device. This will try to request
152 | * the permissions to open the USB device and then create an instance of
153 | * the Airspy class and pass it back via the callbackInterface
154 | *
155 | * @param context Application context. Used to retrieve System Services (USB)
156 | * @param callbackInterface This interface declares two methods that are called if the
157 | * device is ready or if there was an error
158 | * @return false if no Airspy could be found
159 | */
160 | public static boolean initAirspy(Context context, final AirspyCallbackInterface callbackInterface) {
161 | final UsbManager usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
162 | UsbDevice airspyUsbDvice = null;
163 |
164 | if (usbManager == null) {
165 | Log.e(LOGTAG, "initAirspy: Couldn't get an instance of UsbManager!");
166 | return false;
167 | }
168 |
169 | // Get a list of connected devices
170 | HashMap deviceList = usbManager.getDeviceList();
171 |
172 | if (deviceList == null) {
173 | Log.e(LOGTAG, "initAirspy: Couldn't read the USB device list!");
174 | return false;
175 | }
176 |
177 | Log.i(LOGTAG, "initAirspy: Found " + deviceList.size() + " USB devices.");
178 |
179 | // Iterate over the list. Use the first Device that matches an Airspy
180 | Iterator deviceIterator = deviceList.values().iterator();
181 | while (deviceIterator.hasNext()) {
182 | UsbDevice device = deviceIterator.next();
183 |
184 | Log.d(LOGTAG, "initAirspy: deviceList: vendor=" + device.getVendorId() + " product=" + device.getProductId());
185 |
186 | // Airspy (Vendor ID: 7504 [0x1d50]; Product ID: 24737 [0x60a1] )
187 | if (device.getVendorId() == 7504 && device.getProductId() == 24737) {
188 | Log.i(LOGTAG, "initAirspy: Found Airspy at " + device.getDeviceName());
189 | airspyUsbDvice = device;
190 | }
191 | }
192 |
193 | // Check if we found a device:
194 | if (airspyUsbDvice == null) {
195 | Log.e(LOGTAG, "initAirspy: No Airspy Device found.");
196 | return false;
197 | }
198 |
199 | // Requesting Permissions:
200 | // First we define a broadcast receiver that handles the permission_granted intend:
201 | BroadcastReceiver permissionBroadcastReceiver = new BroadcastReceiver() {
202 | public void onReceive(Context context, Intent intent) {
203 | if (AIRSPY_USB_PERMISSION.equals(intent.getAction())) {
204 | UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
205 | if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false) && device != null) {
206 | // We have permissions to open the device! Lets init the airspy instance and
207 | // return it to the calling application.
208 | Log.d(LOGTAG, "initAirspy: Permission granted for device " + device.getDeviceName());
209 | try {
210 | Airspy airspy = new Airspy(usbManager, device);
211 | Toast.makeText(context, "Airspy at " + device.getDeviceName() + " is ready!", Toast.LENGTH_LONG).show();
212 | callbackInterface.onAirspyReady(airspy);
213 | } catch (AirspyUsbException e) {
214 | Log.e(LOGTAG, "initAirspy: Couldn't open device " + device.getDeviceName());
215 | Toast.makeText(context, "Couldn't open Airspy device", Toast.LENGTH_LONG).show();
216 | callbackInterface.onAirspyError("Couldn't open device " + device.getDeviceName());
217 | }
218 | } else {
219 | Log.e(LOGTAG, "initAirspy: Permission denied for device " + device.getDeviceName());
220 | Toast.makeText(context, "Permission denied to open Airspy device", Toast.LENGTH_LONG).show();
221 | callbackInterface.onAirspyError("Permission denied for device " + device.getDeviceName());
222 | }
223 | }
224 |
225 | // unregister the Broadcast Receiver:
226 | context.unregisterReceiver(this);
227 | }
228 | };
229 |
230 | // Now create a intent to request for the permissions and register the broadcast receiver for it:
231 | PendingIntent mPermissionIntent = PendingIntent.getBroadcast(context, 0, new Intent(AIRSPY_USB_PERMISSION), 0);
232 | IntentFilter filter = new IntentFilter(AIRSPY_USB_PERMISSION);
233 | context.registerReceiver(permissionBroadcastReceiver, filter);
234 |
235 | // Fire the request:
236 | usbManager.requestPermission(airspyUsbDvice, mPermissionIntent);
237 | Log.d(LOGTAG, "Permission request for device " + airspyUsbDvice.getDeviceName() + " was send. waiting...");
238 |
239 | return true;
240 | }
241 |
242 | /**
243 | * Initializing the Airspy Instance with a USB Device.
244 | * Note: The application must have reclaimed permissions to
245 | * access the USB Device BEFOR calling this constructor.
246 | *
247 | * @param usbManager Instance of the USB Manager (System Service)
248 | * @param usbDevice Instance of an USB Device representing the Airspy
249 | * @throws AirspyUsbException
250 | */
251 | private Airspy(UsbManager usbManager, UsbDevice usbDevice) throws AirspyUsbException {
252 | // Initialize the class attributes:
253 | this.usbManager = usbManager;
254 | this.usbDevice = usbDevice;
255 |
256 | // For detailed trouble shooting: Read out information of the device:
257 | Log.i(LOGTAG, "constructor: create Airspy instance from " + usbDevice.getDeviceName()
258 | + ". Vendor ID: " + usbDevice.getVendorId() + " Product ID: " + usbDevice.getProductId());
259 | Log.i(LOGTAG, "constructor: device protocol: " + usbDevice.getDeviceProtocol());
260 | Log.i(LOGTAG, "constructor: device class: " + usbDevice.getDeviceClass()
261 | + " subclass: " + usbDevice.getDeviceSubclass());
262 | Log.i(LOGTAG, "constructor: interface count: " + usbDevice.getInterfaceCount());
263 |
264 | try {
265 | // Extract interface from the device:
266 | this.usbInterface = usbDevice.getInterface(0);
267 |
268 | // For detailed trouble shooting: Read out interface information of the device:
269 | Log.i(LOGTAG, "constructor: [interface 0] interface protocol: " + usbInterface.getInterfaceProtocol()
270 | + " subclass: " + usbInterface.getInterfaceSubclass());
271 | Log.i(LOGTAG, "constructor: [interface 0] interface class: " + usbInterface.getInterfaceClass());
272 | Log.i(LOGTAG, "constructor: [interface 0] endpoint count: " + usbInterface.getEndpointCount());
273 |
274 | // Extract the endpoint from the device:
275 | this.usbEndpointIN = usbInterface.getEndpoint(0);
276 | this.usbEndpointOUT = usbInterface.getEndpoint(1);
277 |
278 | // For detailed trouble shooting: Read out endpoint information of the interface:
279 | Log.i(LOGTAG, "constructor: [endpoint 0 (IN)] address: " + usbEndpointIN.getAddress()
280 | + " attributes: " + usbEndpointIN.getAttributes() + " direction: " + usbEndpointIN.getDirection()
281 | + " max_packet_size: " + usbEndpointIN.getMaxPacketSize());
282 | Log.i(LOGTAG, "constructor: [endpoint 1 (OUT)] address: " + usbEndpointOUT.getAddress()
283 | + " attributes: " + usbEndpointOUT.getAttributes() + " direction: " + usbEndpointOUT.getDirection()
284 | + " max_packet_size: " + usbEndpointOUT.getMaxPacketSize());
285 |
286 | // Open the device:
287 | this.usbConnection = usbManager.openDevice(usbDevice);
288 |
289 | if (this.usbConnection == null) {
290 | Log.e(LOGTAG, "constructor: Couldn't open Airspy USB Device: openDevice() returned null!");
291 | throw (new AirspyUsbException("Couldn't open Airspy USB Device! (device is gone)"));
292 | }
293 | } catch (Exception e) {
294 | Log.e(LOGTAG, "constructor: Couldn't open Airspy USB Device: " + e.getMessage());
295 | throw (new AirspyUsbException("Error: Couldn't open Airspy USB Device!"));
296 | }
297 | }
298 |
299 | /**
300 | * This returns the size of the packets that are received by the
301 | * application from the airspy (AFTER unpacking was done!).
302 | * Note that the size is measured in bytes and does not account
303 | * for the type conversion that is done by the Converter classes.
304 | * (i.e. the size is correct for all int16 sample_types but has
305 | * to be multiplyed by 2 to fit the float32 sample_types!)
306 | *
307 | * @return Packet size in Bytes
308 | */
309 | public int getUsbPacketSize() {
310 | if(rawMode || !packingEnabled)
311 | return usbPacketSize;
312 | else
313 | return usbPacketSize * 4 / 3;
314 | }
315 |
316 | /**
317 | * This returns the number of packets (of size getUsbPacketSize()) received since start.
318 | *
319 | * @return Number of packets (of size getUsbPacketSize()) received since start
320 | */
321 | public long getReceiverPacketCounter() {
322 | return this.receivePacketCounter;
323 | }
324 |
325 | /**
326 | * This returns the time in milliseconds since receiving was started.
327 | *
328 | * @return time in milliseconds since receiving was started.
329 | */
330 | public long getReceivingTime() {
331 | if (this.receiveStartTime == 0)
332 | return 0;
333 | return System.currentTimeMillis() - this.receiveStartTime;
334 | }
335 |
336 | /**
337 | * Returns the average rx transfer rate in byte/seconds.
338 | *
339 | * @return average transfer rate in byte/seconds
340 | */
341 | public long getAverageReceiveRate() {
342 | long transTime = this.getReceivingTime() / 1000; // Transfer Time in seconds
343 | if (transTime == 0)
344 | return 0;
345 | return this.getReceiverPacketCounter() * this.getUsbPacketSize() / transTime;
346 | }
347 |
348 | /**
349 | * Returns the current mode of the airspy (off / receiving)
350 | *
351 | * @return AIRSPY_TRANSCEIVER_MODE_OFF or *_RECEIVE
352 | */
353 | public int getReceiverMode() {
354 | return receiverMode;
355 | }
356 |
357 | /**
358 | * Converts a byte array into an integer using little endian byteorder.
359 | *
360 | * @param b byte array (length 4)
361 | * @param offset offset pointing to the first byte in the bytearray that should be used
362 | * @return integer
363 | */
364 | private int byteArrayToInt(byte[] b, int offset) {
365 | return b[offset + 0] & 0xFF | (b[offset + 1] & 0xFF) << 8 |
366 | (b[offset + 2] & 0xFF) << 16 | (b[offset + 3] & 0xFF) << 24;
367 | }
368 |
369 | /**
370 | * Converts a byte array into a long integer using little endian byteorder.
371 | *
372 | * @param b byte array (length 8)
373 | * @param offset offset pointing to the first byte in the bytearray that should be used
374 | * @return long integer
375 | */
376 | private long byteArrayToLong(byte[] b, int offset) {
377 | return b[offset + 0] & 0xFF | (b[offset + 1] & 0xFF) << 8 | (b[offset + 2] & 0xFF) << 16 |
378 | (b[offset + 3] & 0xFF) << 24 | (b[offset + 4] & 0xFF) << 32 | (b[offset + 5] & 0xFF) << 40 |
379 | (b[offset + 6] & 0xFF) << 48 | (b[offset + 7] & 0xFF) << 56;
380 | }
381 |
382 | /**
383 | * Converts an integer into a byte array using little endian byteorder.
384 | *
385 | * @param i integer
386 | * @return byte array (length 4)
387 | */
388 | private byte[] intToByteArray(int i) {
389 | byte[] b = new byte[4];
390 | b[0] = (byte) (i & 0xff);
391 | b[1] = (byte) ((i >> 8) & 0xff);
392 | b[2] = (byte) ((i >> 16) & 0xff);
393 | b[3] = (byte) ((i >> 24) & 0xff);
394 | return b;
395 | }
396 |
397 | /**
398 | * Converts a long integer into a byte array using little endian byteorder.
399 | *
400 | * @param i long integer
401 | * @return byte array (length 8)
402 | */
403 | private byte[] longToByteArray(long i) {
404 | byte[] b = new byte[8];
405 | b[0] = (byte) (i & 0xff);
406 | b[1] = (byte) ((i >> 8) & 0xff);
407 | b[2] = (byte) ((i >> 16) & 0xff);
408 | b[3] = (byte) ((i >> 24) & 0xff);
409 | b[4] = (byte) ((i >> 32) & 0xff);
410 | b[5] = (byte) ((i >> 40) & 0xff);
411 | b[6] = (byte) ((i >> 48) & 0xff);
412 | b[7] = (byte) ((i >> 56) & 0xff);
413 | return b;
414 | }
415 |
416 | /**
417 | * Sets the sample type for the Airspy. This affects which converter is used to process the
418 | * samples after they were received from the Airspy. This setting can only be changed while
419 | * the Airspy is in receiver mode OFF!
420 | *
421 | * @param sampleType AIRSPY_SAMPLE_INT16_REAL, *_INT16_IQ, *FLOAT_REAL, ...
422 | * @return true on success; false if the Airspy is not in receiver mode OFF or invalid sample type
423 | */
424 | public boolean setSampleType(int sampleType) {
425 | if (receiverMode != AIRSPY_RECEIVER_MODE_OFF) {
426 | Log.e(LOGTAG, "setSampleType: Airspy is not in receiver mode OFF. Cannot change sample type!");
427 | return false;
428 | }
429 |
430 | if (sampleType < 0 || sampleType > 4) {
431 | Log.e(LOGTAG, "setSampleType: Not a valid sample type: " + sampleType);
432 | return false;
433 | }
434 |
435 | this.sampleType = sampleType;
436 | return true;
437 | }
438 |
439 | /**
440 | * Enables / Disables raw-mode. It raw mode is enabled, the user can access
441 | * the usbQueue directly (conversion threads are not started / bypassed).
442 | *
443 | * Note that the raw mode can only be changed if the Airspy is currently
444 | * in receiver mode OFF!
445 | *
446 | * @param enabled true to enable raw mode, false to disable raw mode
447 | * @return true on success, false on error
448 | */
449 | public boolean setRawMode(boolean enabled) {
450 | if (receiverMode != AIRSPY_RECEIVER_MODE_OFF) {
451 | Log.e(LOGTAG, "setRawMode: Airspy is not in receiver mode OFF. Cannot change rawMode!");
452 | return false;
453 | }
454 | this.rawMode = enabled;
455 | return true;
456 | }
457 |
458 | /**
459 | * @return true if rawMode is enabled; false if not
460 | */
461 | public boolean getRawMode() {
462 | return rawMode;
463 | }
464 |
465 | /**
466 | * Executes a Request to the USB interface.
467 | *
468 | * Note: This function interacts with the USB Hardware and
469 | * should not be called from a GUI Thread!
470 | *
471 | * @param endpoint USB_DIR_IN or USB_DIR_OUT
472 | * @param request request type (AIRSPY...)
473 | * @param value value to use in the controlTransfer call
474 | * @param index index to use in the controlTransfer call
475 | * @param buffer buffer to use in the controlTransfer call
476 | * @return count of received bytes. Negative on error
477 | * @throws AirspyUsbException
478 | */
479 | private int sendUsbRequest(int endpoint, int request, int value, int index, byte[] buffer) throws AirspyUsbException {
480 | int len = 0;
481 |
482 | // Determine the length of the buffer:
483 | if (buffer != null)
484 | len = buffer.length;
485 |
486 | // Claim the usb interface
487 | if (!this.usbConnection.claimInterface(this.usbInterface, true)) {
488 | Log.e(LOGTAG, "Couldn't claim Airspy USB Interface!");
489 | throw (new AirspyUsbException("Couldn't claim Airspy USB Interface!"));
490 | }
491 |
492 | // Send Board ID Read request
493 | len = this.usbConnection.controlTransfer(
494 | endpoint | UsbConstants.USB_TYPE_VENDOR, // Request Type
495 | request, // Request
496 | value, // Value (unused)
497 | index, // Index (unused)
498 | buffer, // Buffer
499 | len, // Length
500 | 0 // Timeout
501 | );
502 |
503 | // Release usb interface
504 | this.usbConnection.releaseInterface(this.usbInterface);
505 |
506 | return len;
507 | }
508 |
509 | /**
510 | * Returns the Board ID of the Airspy.
511 | *
512 | * Note: This function interacts with the USB Hardware and
513 | * should not be called from a GUI Thread!
514 | *
515 | * @return Airspy Board ID
516 | * @throws AirspyUsbException
517 | */
518 | public byte getBoardID() throws AirspyUsbException {
519 | byte[] buffer = new byte[1];
520 |
521 | if (this.sendUsbRequest(UsbConstants.USB_DIR_IN, AIRSPY_BOARD_ID_READ, 0, 0, buffer) != 1) {
522 | Log.e(LOGTAG, "getBoardID: USB Transfer failed!");
523 | throw (new AirspyUsbException("USB Transfer failed!"));
524 | }
525 |
526 | return buffer[0];
527 | }
528 |
529 | /**
530 | * Converts the Board ID into a human readable String
531 | *
532 | * @param boardID boardID to convert
533 | * @return Board ID interpretation as String
534 | * @throws AirspyUsbException
535 | */
536 | public static String convertBoardIdToString(int boardID) {
537 | switch (boardID) {
538 | case 0:
539 | return "AIRSPY";
540 | default:
541 | return "INVALID BOARD ID";
542 | }
543 | }
544 |
545 | /**
546 | * Returns the Version String of the Airspy.
547 | *
548 | * Note: This function interacts with the USB Hardware and
549 | * should not be called from a GUI Thread!
550 | *
551 | * @return Airspy Version String
552 | * @throws AirspyUsbException
553 | */
554 | public String getVersionString() throws AirspyUsbException {
555 | byte[] buffer = new byte[255];
556 | int len = 0;
557 |
558 | len = this.sendUsbRequest(UsbConstants.USB_DIR_IN, AIRSPY_VERSION_STRING_READ, 0, 0, buffer);
559 |
560 | if (len < 1) {
561 | Log.e(LOGTAG, "getVersionString: USB Transfer failed!");
562 | throw (new AirspyUsbException("USB Transfer failed!"));
563 | }
564 |
565 | return new String(buffer);
566 | }
567 |
568 |
569 | /**
570 | * Returns the Part ID + Serial Number of the Airspy.
571 | *
572 | * Note: This function interacts with the USB Hardware and
573 | * should not be called from a GUI Thread!
574 | *
575 | * @return int[2+6] => int[0-1] is Part ID; int[2-5] is Serial No
576 | * @throws AirspyUsbException
577 | */
578 | public int[] getPartIdAndSerialNo() throws AirspyUsbException {
579 | byte[] buffer = new byte[8 + 16];
580 | int[] ret = new int[2 + 4];
581 |
582 | if (this.sendUsbRequest(UsbConstants.USB_DIR_IN, AIRSPY_BOARD_PARTID_SERIALNO_READ,
583 | 0, 0, buffer) != 8 + 16) {
584 | Log.e(LOGTAG, "getPartIdAndSerialNo: USB Transfer failed!");
585 | throw (new AirspyUsbException("USB Transfer failed!"));
586 | }
587 |
588 | for (int i = 0; i < 6; i++) {
589 | ret[i] = this.byteArrayToInt(buffer, 4 * i);
590 | }
591 |
592 | return ret;
593 | }
594 |
595 | /**
596 | * Returns the supported sample rates of the Airspy.
597 | *
598 | * Note: This function interacts with the USB Hardware and
599 | * should not be called from a GUI Thread!
600 | *
601 | * @return array of supported sample rates (in Sps)
602 | * @throws AirspyUsbException
603 | */
604 | public int[] getSampleRates() throws AirspyUsbException {
605 | byte[] buffer = new byte[4];
606 | int count;
607 | int[] rates;
608 | int len = 0;
609 |
610 | // First read the number of supported sample rates:
611 | len = this.sendUsbRequest(UsbConstants.USB_DIR_IN, AIRSPY_GET_SAMPLERATES, 0, 0, buffer);
612 |
613 | if (len < buffer.length) {
614 | Log.e(LOGTAG, "getSampleRates: USB Transfer failed (reading count)!");
615 | throw (new AirspyUsbException("USB Transfer failed!"));
616 | }
617 | count = byteArrayToInt(buffer, 0);
618 | Log.d(LOGTAG, "getSampleRates: Airspy supports " + count + " different sample rates!");
619 |
620 | // Now read the actual sample rates:
621 | buffer = new byte[count * 4]; // every rate is stored in a 32bit int
622 | rates = new int[count];
623 | len = this.sendUsbRequest(UsbConstants.USB_DIR_IN, AIRSPY_GET_SAMPLERATES, 0, count, buffer);
624 |
625 | if (len < buffer.length) {
626 | Log.e(LOGTAG, "getSampleRates: USB Transfer failed (reading rates)!");
627 | throw (new AirspyUsbException("USB Transfer failed!"));
628 | }
629 |
630 | for (int i = 0; i < rates.length; i++) {
631 | rates[i] = byteArrayToInt(buffer, i * 4);
632 | }
633 |
634 | return rates;
635 | }
636 |
637 | /**
638 | * Sets the Sample Rate of the Airspy.
639 | *
640 | * Note: This function interacts with the USB Hardware and
641 | * should not be called from a GUI Thread!
642 | *
643 | * @return true on success
644 | * @throws AirspyUsbException
645 | * @param sampRateIdx Index (see getSampleRates) of the sample rate that should be set (starting from 0)
646 | */
647 | public boolean setSampleRate(int sampRateIdx) throws AirspyUsbException {
648 | byte[] retVal = new byte[1];
649 |
650 | int len = this.sendUsbRequest(UsbConstants.USB_DIR_IN, AIRSPY_SET_SAMPLERATE, 0, sampRateIdx, retVal);
651 |
652 | if (len != 1) {
653 | Log.e(LOGTAG, "setSampleRate: USB Transfer failed!");
654 | throw (new AirspyUsbException("USB Transfer failed!"));
655 | }
656 |
657 | if (retVal[0] < 0) {
658 | Log.e(LOGTAG, "setSampleRate: Airspy returned with an error!");
659 | return false;
660 | }
661 |
662 | return true;
663 | }
664 |
665 | /**
666 | * Sets the Mixer Gain of the Airspy.
667 | *
668 | * Note: This function interacts with the USB Hardware and
669 | * should not be called from a GUI Thread!
670 | *
671 | * @return true on success
672 | * @throws AirspyUsbException
673 | * @param gain Mixer Gain (0-15)
674 | */
675 | public boolean setMixerGain(int gain) throws AirspyUsbException {
676 | byte[] retVal = new byte[1];
677 |
678 | if (gain > 15 || gain < 0) {
679 | Log.e(LOGTAG, "setMixerGain: Mixer gain must be within 0-15!");
680 | return false;
681 | }
682 |
683 | if (this.sendUsbRequest(UsbConstants.USB_DIR_IN, AIRSPY_SET_MIXER_GAIN, 0, gain, retVal) != 1) {
684 | Log.e(LOGTAG, "setMixerGain: USB Transfer failed!");
685 | throw (new AirspyUsbException("USB Transfer failed!"));
686 | }
687 |
688 | if (retVal[0] < 0) {
689 | Log.e(LOGTAG, "setMixerGain: Airspy returned with an error!");
690 | return false;
691 | }
692 |
693 | return true;
694 | }
695 |
696 | /**
697 | * Sets the VGA Gain of the Airspy.
698 | *
699 | * Note: This function interacts with the USB Hardware and
700 | * should not be called from a GUI Thread!
701 | *
702 | * @return true on success
703 | * @throws AirspyUsbException
704 | * @param gain VGA Gain (0-15)
705 | */
706 | public boolean setVGAGain(int gain) throws AirspyUsbException {
707 | byte[] retVal = new byte[1];
708 |
709 | if (gain > 15 || gain < 0) {
710 | Log.e(LOGTAG, "setVGAGain: VGA gain must be within 0-15!");
711 | return false;
712 | }
713 |
714 | if (this.sendUsbRequest(UsbConstants.USB_DIR_IN, AIRSPY_SET_VGA_GAIN, 0, gain, retVal) != 1) {
715 | Log.e(LOGTAG, "setVGAGain: USB Transfer failed!");
716 | throw (new AirspyUsbException("USB Transfer failed!"));
717 | }
718 |
719 | if (retVal[0] < 0) {
720 | Log.e(LOGTAG, "setVGAGain: Airspy returned with an error!");
721 | return false;
722 | }
723 |
724 | return true;
725 | }
726 |
727 | /**
728 | * Sets the LNA Gain of the Airspy.
729 | *
730 | * Note: This function interacts with the USB Hardware and
731 | * should not be called from a GUI Thread!
732 | *
733 | * @return true on success
734 | * @throws AirspyUsbException
735 | * @param gain LNA Gain (0-14)
736 | */
737 | public boolean setLNAGain(int gain) throws AirspyUsbException {
738 | byte[] retVal = new byte[1];
739 |
740 | if (gain > 14 || gain < 0) {
741 | Log.e(LOGTAG, "setLNAGain: LNA gain must be within 0-14!");
742 | return false;
743 | }
744 |
745 | if (this.sendUsbRequest(UsbConstants.USB_DIR_IN, AIRSPY_SET_LNA_GAIN, 0, gain, retVal) != 1) {
746 | Log.e(LOGTAG, "setLNAGain: USB Transfer failed!");
747 | throw (new AirspyUsbException("USB Transfer failed!"));
748 | }
749 |
750 | if (retVal[0] < 0) {
751 | Log.e(LOGTAG, "setLNAGain: Airspy returned with an error!");
752 | return false;
753 | }
754 |
755 | return true;
756 | }
757 |
758 | /**
759 | * Enables / Disables the LNA automatic gain control of the Airspy.
760 | *
761 | * Note: This function interacts with the USB Hardware and
762 | * should not be called from a GUI Thread!
763 | *
764 | * @return true on success
765 | * @throws AirspyUsbException
766 | * @param enable true for enable; false for disable
767 | */
768 | public boolean setLNAAutomaticGainControl(boolean enable) throws AirspyUsbException {
769 | byte[] retVal = new byte[1];
770 |
771 | if (this.sendUsbRequest(UsbConstants.USB_DIR_IN, AIRSPY_SET_LNA_AGC, 0, enable ? 1 : 0, retVal) != 1) {
772 | Log.e(LOGTAG, "setLNAAutomaticGainControl: USB Transfer failed!");
773 | throw (new AirspyUsbException("USB Transfer failed!"));
774 | }
775 |
776 | if (retVal[0] < 0) {
777 | Log.e(LOGTAG, "setLNAAutomaticGainControl: Airspy returned with an error!");
778 | return false;
779 | }
780 |
781 | return true;
782 | }
783 |
784 | /**
785 | * Enables / Disables the Mixer automatic gain control of the Airspy.
786 | *
787 | * Note: This function interacts with the USB Hardware and
788 | * should not be called from a GUI Thread!
789 | *
790 | * @return true on success
791 | * @throws AirspyUsbException
792 | * @param enable true for enable; false for disable
793 | */
794 | public boolean setMixerAutomaticGainControl(boolean enable) throws AirspyUsbException {
795 | byte[] retVal = new byte[1];
796 |
797 | if (this.sendUsbRequest(UsbConstants.USB_DIR_IN, AIRSPY_SET_MIXER_AGC, 0, enable ? 1 : 0, retVal) != 1) {
798 | Log.e(LOGTAG, "setMixerAutomaticGainControl: USB Transfer failed!");
799 | throw (new AirspyUsbException("USB Transfer failed!"));
800 | }
801 |
802 | if (retVal[0] < 0) {
803 | Log.e(LOGTAG, "setMixerAutomaticGainControl: Airspy returned with an error!");
804 | return false;
805 | }
806 |
807 | return true;
808 | }
809 |
810 | /**
811 | * Enables / Disables packing for the Airspy. This is only possible if the
812 | * receiver mode is currently OFF!
813 | * If packing is enabled, the Airspy will compress the samples before sending
814 | * them via USB. The Converter threads will unpack the samples automatically.
815 | *
816 | * Note: This function interacts with the USB Hardware and
817 | * should not be called from a GUI Thread!
818 | *
819 | * @return true on success
820 | * @throws AirspyUsbException
821 | * @param enable true for enable; false for disable
822 | */
823 | public boolean setPacking(boolean enable) throws AirspyUsbException {
824 | byte[] retVal = new byte[1];
825 |
826 | if (receiverMode != AIRSPY_RECEIVER_MODE_OFF) {
827 | Log.e(LOGTAG, "setPacking: Airspy is not in receiver mode OFF. Cannot change packing setting!");
828 | return false;
829 | }
830 |
831 | if (this.sendUsbRequest(UsbConstants.USB_DIR_IN, AIRSPY_SET_PACKING, 0, enable ? 1 : 0, retVal) != 1) {
832 | Log.e(LOGTAG, "setPacking: USB Transfer failed!");
833 | throw (new AirspyUsbException("USB Transfer failed!"));
834 | }
835 |
836 | if (retVal[0] < 0) {
837 | Log.e(LOGTAG, "setPacking: Airspy returned with an error!");
838 | return false;
839 | }
840 |
841 | this.packingEnabled = enable;
842 |
843 | return true;
844 | }
845 |
846 | /**
847 | * Sets the Frequency of the Airspy.
848 | *
849 | * Note: This function interacts with the USB Hardware and
850 | * should not be called from a GUI Thread!
851 | *
852 | * @return true on success
853 | * @throws AirspyUsbException
854 | * @param frequency Frequency in Hz
855 | */
856 | public boolean setFrequency(int frequency) throws AirspyUsbException {
857 | byte[] freq = intToByteArray(frequency);
858 |
859 | Log.d(LOGTAG, "Tune Airspy to " + frequency + "Hz...");
860 |
861 | if (this.sendUsbRequest(UsbConstants.USB_DIR_OUT, AIRSPY_SET_FREQ, 0, 0, freq) != 4) {
862 | Log.e(LOGTAG, "setFrequency: USB Transfer failed!");
863 | throw (new AirspyUsbException("USB Transfer failed!"));
864 | }
865 |
866 | return true;
867 | }
868 |
869 |
870 | /**
871 | * Sets the Receiver Mode of the Airspy (OFF,RX)
872 | *
873 | * Note: This function interacts with the USB Hardware and
874 | * should not be called from a GUI Thread!
875 | *
876 | * @return true on success
877 | * @throws AirspyUsbException
878 | * @param mode AIRSPY_RECEIVER_MODE_OFF, *_RECEIVE
879 | */
880 | public boolean setReceiverMode(int mode) throws AirspyUsbException {
881 | if (mode != AIRSPY_RECEIVER_MODE_OFF && mode != AIRSPY_RECEIVER_MODE_RECEIVE) {
882 | Log.e(LOGTAG, "Invalid Receiver Mode: " + mode);
883 | return false;
884 | }
885 |
886 | this.receiverMode = mode;
887 |
888 | if (this.sendUsbRequest(UsbConstants.USB_DIR_OUT, AIRSPY_RECEIVER_MODE,
889 | mode, 0, null) != 0) {
890 | Log.e(LOGTAG, "setReceiverMode: USB Transfer failed!");
891 | throw (new AirspyUsbException("USB Transfer failed!"));
892 | }
893 |
894 | return true;
895 | }
896 |
897 | /**
898 | * Starts receiving.
899 | *
900 | * After calling this function the user should also call getFloatQueue()/getInt16Queue()/getRawQueue()
901 | * to read the received samples and getFloatReturnPoolQueue()/getInt16ReturnPoolQueue()/
902 | * getRawReturnPoolQueue() to return used buffers to the Airspy class.
903 | *
904 | * @throws AirspyUsbException
905 | */
906 | public boolean startRX() throws AirspyUsbException {
907 | // Create the usbQueue that holds samples received from the Airspy
908 | this.usbQueue = new ArrayBlockingQueue(usbQueueSize);
909 |
910 | // Create another queue that will be used to collect old buffers for reusing them.
911 | // TODO: maybe this can be optimized: check if the pool already fits the current requirements and don't reallocate it!
912 | this.usbBufferPool = new ArrayBlockingQueue(usbQueueSize);
913 | for (int i = 0; i < usbQueueSize; i++)
914 | this.usbBufferPool.offer(new byte[usbPacketSize]); // Allocate buffers
915 |
916 | // Create queues for the Conversion Thread and start it (if not in rawMode)
917 | if(!rawMode) {
918 | switch (sampleType) {
919 | case AIRSPY_SAMPLE_FLOAT32_IQ:
920 | case AIRSPY_SAMPLE_FLOAT32_REAL:
921 | this.conversionQueueFloat = new ArrayBlockingQueue(conversionQueueSize);
922 | this.conversionBufferPoolFloat = new ArrayBlockingQueue(conversionQueueSize);
923 | for (int i = 0; i < conversionQueueSize; i++)
924 | this.conversionBufferPoolFloat.offer(new float[getUsbPacketSize()/2]); // Allocate buffers
925 | try {
926 | floatConverter = new AirspyFloatConverter(sampleType, packingEnabled, usbQueue, usbBufferPool, conversionQueueFloat, conversionBufferPoolFloat);
927 | floatConverter.start();
928 | } catch (Exception e) {
929 | Log.e(LOGTAG, "startRX: Cannot create float converter: " + e.getMessage());
930 | return false;
931 | }
932 | break;
933 | case AIRSPY_SAMPLE_INT16_IQ:
934 | case AIRSPY_SAMPLE_INT16_REAL:
935 | case AIRSPY_SAMPLE_UINT16_REAL:
936 | this.conversionQueueInt16 = new ArrayBlockingQueue(conversionQueueSize);
937 | this.conversionBufferPoolInt16 = new ArrayBlockingQueue(conversionQueueSize);
938 | for (int i = 0; i < conversionQueueSize; i++)
939 | this.conversionBufferPoolInt16.offer(new short[getUsbPacketSize()/2]); // Allocate buffers
940 | try {
941 | int16Converter = new AirspyInt16Converter(sampleType, packingEnabled, usbQueue, usbBufferPool, conversionQueueInt16, conversionBufferPoolInt16);
942 | int16Converter.start();
943 | } catch (Exception e) {
944 | Log.e(LOGTAG, "startRX: Cannot create int16 converter: " + e.getMessage());
945 | return false;
946 | }
947 | break;
948 | }
949 | }
950 |
951 | // Signal the Airspy Device to start receiving:
952 | this.setReceiverMode(AIRSPY_RECEIVER_MODE_RECEIVE);
953 |
954 | // Start the Thread to queue the received samples:
955 | this.usbThread = new Thread(this);
956 | this.usbThread.start();
957 |
958 | // Reset the packet counter and start time for statistics:
959 | this.receiveStartTime = System.currentTimeMillis();
960 | this.receivePacketCounter = 0;
961 |
962 | return true;
963 | }
964 |
965 | /**
966 | * Stops receiving
967 | *
968 | * @throws AirspyUsbException
969 | */
970 | public void stop() throws AirspyUsbException {
971 | // Signal the Airspy Device to stop receiving:
972 | this.setReceiverMode(AIRSPY_RECEIVER_MODE_OFF);
973 | }
974 |
975 |
976 | public void run() {
977 | if (receiverMode != AIRSPY_RECEIVER_MODE_RECEIVE) {
978 | Log.e(LOGTAG, "run: Invalid receiver mode: " + receiverMode);
979 | return;
980 | }
981 |
982 | receiveLoop();
983 | }
984 |
985 | /**
986 | * This method will be executed in a separate Thread after the Airspy starts receiving
987 | * Samples. It will return as soon as the transceiverMode changes or an error occurs.
988 | */
989 | private void receiveLoop() {
990 | UsbRequest[] usbRequests = new UsbRequest[numUsbRequests];
991 | ByteBuffer buffer;
992 |
993 | try {
994 | // Create, initialize and queue all usb requests:
995 | for (int i = 0; i < numUsbRequests; i++) {
996 | // Get a ByteBuffer for the request from the buffer pool:
997 | try {
998 | byte[] tmpBuffer = usbBufferPool.poll(1, TimeUnit.SECONDS);
999 | if (tmpBuffer == null) {
1000 | // We hit the timeout.
1001 | Log.e(LOGTAG, "receiveLoop: Buffer pool is empty. Stop receiving!");
1002 | this.stop();
1003 | break;
1004 | }
1005 | buffer = ByteBuffer.wrap(tmpBuffer);
1006 | } catch (InterruptedException e) {
1007 | Log.e(LOGTAG, "receiveLoop: Interrupted while waiting on buffers in the pool. Stop receiving!");
1008 | this.stop();
1009 | break;
1010 | }
1011 |
1012 | // Initialize the USB Request:
1013 | usbRequests[i] = new UsbRequest();
1014 | usbRequests[i].initialize(usbConnection, usbEndpointIN);
1015 | usbRequests[i].setClientData(buffer);
1016 |
1017 | // Queue the request
1018 | if (!usbRequests[i].queue(buffer, usbPacketSize)) {
1019 | Log.e(LOGTAG, "receiveLoop: Couldn't queue USB Request.");
1020 | this.stop();
1021 | break;
1022 | }
1023 | }
1024 |
1025 | // Run loop until transceiver mode changes...
1026 | while (this.receiverMode == AIRSPY_RECEIVER_MODE_RECEIVE) {
1027 | // Wait for a request to return. This will block until one of the requests is ready.
1028 | UsbRequest request = usbConnection.requestWait();
1029 |
1030 | if (request == null) {
1031 | Log.e(LOGTAG, "receiveLoop: Didn't receive USB Request.");
1032 | break;
1033 | }
1034 |
1035 | // Make sure we got an UsbRequest for the IN endpoint!
1036 | if (request.getEndpoint() != usbEndpointIN)
1037 | continue;
1038 |
1039 | // Extract the buffer
1040 | buffer = (ByteBuffer) request.getClientData();
1041 |
1042 | // Increment the packetCounter (for statistics)
1043 | this.receivePacketCounter++;
1044 |
1045 | // Put the received samples into the usbQueue, so that they can be read by the
1046 | // conversion thread (or the application if in raw mode)
1047 | try {
1048 | if (!this.usbQueue.offer(buffer.array(), 1000, TimeUnit.MILLISECONDS)) {
1049 | // We hit the timeout.
1050 | Log.e(LOGTAG, "receiveLoop: Queue is full. Stop receiving!");
1051 | break;
1052 | }
1053 | } catch (InterruptedException e) {
1054 | Log.e(LOGTAG, "receiveLoop: Interrupted while putting a buffer in the queue. Stop receiving!");
1055 | break;
1056 | }
1057 |
1058 | // Get a fresh ByteBuffer for the request from the buffer pool:
1059 | try {
1060 | byte[] tmpBuffer = usbBufferPool.poll(10, TimeUnit.SECONDS);
1061 | if (tmpBuffer == null) {
1062 | // We hit the timeout.
1063 | Log.e(LOGTAG, "receiveLoop: Buffer pool is empty. Stop receiving!");
1064 | break;
1065 | }
1066 | buffer = ByteBuffer.wrap(tmpBuffer);
1067 | } catch (InterruptedException e) {
1068 | Log.e(LOGTAG, "receiveLoop: Interrupted while waiting on buffers in the pool. Stop receiving!");
1069 | break;
1070 | }
1071 | request.setClientData(buffer);
1072 |
1073 | // Queue the request again...
1074 | if (!request.queue(buffer, usbPacketSize)) {
1075 | Log.e(LOGTAG, "receiveLoop: Couldn't queue USB Request.");
1076 | break;
1077 | }
1078 | }
1079 | } catch (AirspyUsbException e) {
1080 | Log.e(LOGTAG, "receiveLoop: USB Error!");
1081 | }
1082 |
1083 | // Receiving is done. Cancel and close all usb requests:
1084 | for (UsbRequest request : usbRequests) {
1085 | if (request != null) {
1086 | request.cancel();
1087 | //request.close(); <-- This will cause the VM to crash with a SIGABRT when the next transceive starts?!?
1088 | }
1089 | }
1090 |
1091 | // If the receiverMode is still on RECEIVE, we stop Receiving:
1092 | if (this.receiverMode == AIRSPY_RECEIVER_MODE_RECEIVE) {
1093 | try {
1094 | this.stop();
1095 | } catch (AirspyUsbException e) {
1096 | Log.e(LOGTAG, "receiveLoop: Error while stopping RX!");
1097 | }
1098 | }
1099 |
1100 | // Stop all converters if running:
1101 | if(int16Converter != null)
1102 | int16Converter.requestStop();
1103 | if(floatConverter != null)
1104 | floatConverter.requestStop();
1105 | }
1106 |
1107 | /**
1108 | * Call this after startRX() to get the queue with the received and converted samples (if sample type is int16)
1109 | * Also get a reference to the int16ReturnPoolQueue by calling getInt16ReturnPoolQueue() to return the buffers!
1110 | * @return ArrayBlockingQueue which is filled by the conversion thread (with received and converted int16 samples)
1111 | */
1112 | public ArrayBlockingQueue getInt16Queue() {
1113 | if(receiverMode != AIRSPY_RECEIVER_MODE_RECEIVE)
1114 | return null;
1115 | if(sampleType == AIRSPY_SAMPLE_INT16_IQ || sampleType == AIRSPY_SAMPLE_INT16_REAL || sampleType == AIRSPY_SAMPLE_UINT16_REAL)
1116 | return conversionQueueInt16;
1117 | else
1118 | return null;
1119 | }
1120 |
1121 | /**
1122 | * Call this after startRX() to get a queue to the bufferPool. Return every buffer you got from the int16Queue into
1123 | * the pool after usage (if sample type is int16)
1124 | * @return ArrayBlockingQueue that is used to collect buffers from the int16Queue after usage
1125 | */
1126 | public ArrayBlockingQueue getInt16ReturnPoolQueue() {
1127 | if(receiverMode != AIRSPY_RECEIVER_MODE_RECEIVE)
1128 | return null;
1129 | if(sampleType == AIRSPY_SAMPLE_INT16_IQ || sampleType == AIRSPY_SAMPLE_INT16_REAL || sampleType == AIRSPY_SAMPLE_UINT16_REAL)
1130 | return conversionBufferPoolInt16;
1131 | else
1132 | return null;
1133 | }
1134 |
1135 | /**
1136 | * Call this after startRX() to get the queue with the received and converted samples (if sample type is float)
1137 | * Also get a reference to the floatReturnPoolQueue by calling getFloatReturnPoolQueue() to return the buffers!
1138 | * @return ArrayBlockingQueue which is filled by the conversion thread (with received and converted float samples)
1139 | */
1140 | public ArrayBlockingQueue getFloatQueue() {
1141 | if(receiverMode != AIRSPY_RECEIVER_MODE_RECEIVE)
1142 | return null;
1143 | if(sampleType == AIRSPY_SAMPLE_FLOAT32_IQ || sampleType == AIRSPY_SAMPLE_FLOAT32_REAL)
1144 | return conversionQueueFloat;
1145 | else
1146 | return null;
1147 | }
1148 |
1149 | /**
1150 | * Call this after startRX() to get a queue to the bufferPool. Return every buffer you got from the floatQueue into
1151 | * the pool after usage (if sample type is float)
1152 | * @return ArrayBlockingQueue that is used to collect buffers from the floatQueue after usage
1153 | */
1154 | public ArrayBlockingQueue getFloatReturnPoolQueue() {
1155 | if(receiverMode != AIRSPY_RECEIVER_MODE_RECEIVE)
1156 | return null;
1157 | if(sampleType == AIRSPY_SAMPLE_FLOAT32_IQ || sampleType == AIRSPY_SAMPLE_FLOAT32_REAL)
1158 | return conversionBufferPoolFloat;
1159 | else
1160 | return null;
1161 | }
1162 |
1163 | /**
1164 | * Call this after startRX() to get the queue with the received raw samples (if rawMode is enabled)
1165 | * Also get a reference to the rawReturnPoolQueue by calling getRawReturnPoolQueue() to return the buffers!
1166 | * @return ArrayBlockingQueue which is filled by the conversion thread (with received raw samples)
1167 | */
1168 | public ArrayBlockingQueue getRawQueue() {
1169 | if(receiverMode != AIRSPY_RECEIVER_MODE_RECEIVE)
1170 | return null;
1171 | if(rawMode)
1172 | return usbQueue;
1173 | else
1174 | return null;
1175 | }
1176 |
1177 | /**
1178 | * Call this after startRX() to get a queue to the bufferPool. Return every buffer you got from the rawQueue into
1179 | * the pool after usage (if rawMode is enabled)
1180 | * @return ArrayBlockingQueue that is used to collect buffers from the rawQueue after usage
1181 | */
1182 | public ArrayBlockingQueue getRawReturnPoolQueue() {
1183 | if(receiverMode != AIRSPY_RECEIVER_MODE_RECEIVE)
1184 | return null;
1185 | if(rawMode)
1186 | return usbBufferPool;
1187 | else
1188 | return null;
1189 | }
1190 |
1191 | /**
1192 | * This method is needed if packing is enabled on the Airspy.
1193 | * It will read bytes from the source array, unpack them
1194 | * and write the results to dest. Note the length parameter
1195 | * specifies the number of bytes that should be written to
1196 | * the destination array and not the number of bytes that will
1197 | * be read from the source!
1198 | * @param src source array containing at least length * 3/4 packed bytes
1199 | * @param dest destination array. Size must be greater or equal to length
1200 | * @param length number of bytes that should be written to dest. Must be multiple of 16!
1201 | */
1202 | public static void unpackSamples(byte[] src, byte[] dest, int length) {
1203 | if (length % 16 != 0) {
1204 | Log.e(LOGTAG, "convertSamplesFloat: length has to be multiple of 16!");
1205 | return;
1206 | }
1207 |
1208 | if (src.length < 3 * dest.length / 4 || dest.length < length) {
1209 | Log.e(LOGTAG, "convertSamplesFloat: input buffers have invalid length!");
1210 | return;
1211 | }
1212 |
1213 | for (int i = 0, j = 0; i < length; i += 16, j += 12) {
1214 | /* [3] [2] [1] [0] [7] [6] [5] [4] [11] [10] [9] [8]
1215 | * src [--------][---- ----][--------] [--------][---- ----][--------] [--------][---- ----][--------] [--------][---- ----][--------]
1216 | *
1217 | * [0] [1] [2] [3] [4] [5] [6] [7]
1218 | * samples [0000-------- ----][0000---- --------][0000-------- ----][0000---- --------][0000-------- ----][0000---- --------][0000-------- ----][0000---- --------]
1219 | *
1220 | * [1] [0] [3] [2] [5] [4] [7] [6] [9] [8] [11] [10] [13] [12] [15] [14]
1221 | * dest [0000----|--------][0000----|--------][0000----|--------][0000----|--------][0000----|--------][0000----|--------][0000----|--------][0000----|--------]
1222 | */
1223 | dest[i] = (byte) ((src[j + 3] << 4) & 0xF0 | (src[j + 2] >> 4) & 0x0F);
1224 | dest[i + 1] = (byte) ((src[j + 3] >> 4) & 0x0F);
1225 | dest[i + 2] = src[j + 1];
1226 | dest[i + 3] = (byte) (src[j + 2] & 0x0F);
1227 | dest[i + 4] = (byte) ((src[j] << 4) & 0xF0 | (src[j + 7] >> 4) & 0x0F);
1228 | dest[i + 5] = (byte) ((src[j] >> 4) & 0x0F);
1229 | dest[i + 6] = src[j + 6];
1230 | dest[i + 7] = (byte) (src[j + 7] & 0x0F);
1231 | dest[i + 8] = (byte) ((src[j + 5] << 4) & 0xF0 | (src[j + 4] >> 4) & 0x0F);
1232 | dest[i + 9] = (byte) ((src[j + 5] >> 4) & 0x0F);
1233 | dest[i + 10] = src[j + 11];
1234 | dest[i + 11] = (byte) (src[j + 4] & 0x0F);
1235 | dest[i + 12] = (byte) ((src[j + 10] << 4) & 0xF0 | (src[j + 9] >> 4) & 0x0F);
1236 | dest[i + 13] = (byte) ((src[j + 10] >> 4) & 0x0F);
1237 | dest[i + 14] = src[j + 8];
1238 | dest[i + 15] = (byte) (src[j + 9] & 0x0F);
1239 |
1240 | // from airspy.c:
1241 | // output[j + 0] = (input[i] >> 20) & 0xfff;
1242 | // output[j + 1] = (input[i] >> 8) & 0xfff;
1243 | // output[j + 2] = ((input[i] & 0xff) << 4) | ((input[i + 1] >> 28) & 0xf);
1244 | // output[j + 3] = ((input[i + 1] & 0xfff0000) >> 16);
1245 | // output[j + 4] = ((input[i + 1] & 0xfff0) >> 4);
1246 | // output[j + 5] = ((input[i + 1] & 0xf) << 8) | ((input[i + 2] & 0xff000000) >> 24);
1247 | // output[j + 6] = ((input[i + 2] >> 12) & 0xfff);
1248 | // output[j + 7] = ((input[i + 2] & 0xfff));
1249 | }
1250 | }
1251 |
1252 | /**
1253 | * This Interface declares a callback method to return a Airspy instance to the application after it was opened
1254 | * by the initialization routine (asynchronous process because it includes requesting the USB permissions)
1255 | */
1256 | public interface AirspyCallbackInterface {
1257 | /**
1258 | * Called by initAirspy() after the device is ready to be used.
1259 | *
1260 | * @param airspy Instance of the Airspy that provides access to the device
1261 | */
1262 | public void onAirspyReady(Airspy airspy);
1263 |
1264 | /**
1265 | * Called if there was an error when accessing the device.
1266 | *
1267 | * @param message Reason for the Error
1268 | */
1269 | public void onAirspyError(String message);
1270 | }
1271 |
1272 | /**
1273 | * This Exception will be thrown if an Error with the USB communication occurs.
1274 | */
1275 | public class AirspyUsbException extends Exception {
1276 | private static final long serialVersionUID = 1L;
1277 |
1278 | public AirspyUsbException(String message) {
1279 | super(message);
1280 | }
1281 | }
1282 | }
--------------------------------------------------------------------------------
/airspy_android/src/main/java/com/mantz_it/airspy_android/AirspyFloatConverter.java:
--------------------------------------------------------------------------------
1 | package com.mantz_it.airspy_android;
2 |
3 | import android.util.Log;
4 |
5 | import java.util.concurrent.ArrayBlockingQueue;
6 | import java.util.concurrent.TimeUnit;
7 |
8 | /**
9 | *
Airspy USB Library for Android
10 | *
11 | * Module: AirspyInt16Converter.java
12 | * Description: This class offers methods to convert the samples from the Airspy device
13 | * to various FLOAT32 types.
14 | *
15 | * @author Dennis Mantz
16 | *
17 | * Copyright (C) 2015 Dennis Mantz
18 | * based on code of libairspy [https://github.com/airspy/host/tree/master/libairspy]:
19 | * Copyright (c) 2013, Michael Ossmann
20 | * Copyright (c) 2012, Jared Boone
21 | * Copyright (c) 2014, Youssef Touil
22 | * Copyright (c) 2014, Benjamin Vernoux
23 | * Copyright (c) 2015, Ian Gilmour
24 | * All rights reserved.
25 | * Redistribution and use in source and binary forms, with or without modification,
26 | * are permitted provided that the following conditions are met:
27 | * - Redistributions of source code must retain the above copyright notice, this list
28 | * of conditions and the following disclaimer.
29 | * - Redistributions in binary form must reproduce the above copyright notice, this
30 | * list of conditions and the following disclaimer in the documentation and/or other
31 | * materials provided with the distribution.
32 | * - Neither the name of Great Scott Gadgets nor the names of its contributors may be
33 | * used to endorse or promote products derived from this software without specific
34 | * prior written permission.
35 | * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
36 | *
37 | * This library is free software; you can redistribute it and/or
38 | * modify it under the terms of the GNU General Public
39 | * License as published by the Free Software Foundation; either
40 | * version 2 of the License, or (at your option) any later version.
41 | *
42 | * This library is distributed in the hope that it will be useful,
43 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
44 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
45 | * General Public License for more details.
46 | *
47 | * You should have received a copy of the GNU General Public
48 | * License along with this library; if not, write to the Free Software
49 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
50 | */
51 | public class AirspyFloatConverter extends Thread{
52 |
53 | private static final String LOGTAG = "AirspyFloatConverter";
54 | private boolean stopRequested = false;
55 | private int sampleType = -1;
56 | private boolean packingEnabled = false;
57 | private ArrayBlockingQueue inputQueue; // Queue from which the input samples are taken
58 | private ArrayBlockingQueue inputReturnQueue; // Queue to return the used input buffers to the pool
59 | private ArrayBlockingQueue outputQueue; // Queue to deliver the converted samples
60 | private ArrayBlockingQueue outputPoolQueue; // Queue from which the output buffers are taken
61 | private int len = 0;
62 | private int firIndex = 0;
63 | private int delayIndex = 0;
64 | private float avg;
65 | private float hbc;
66 | private float firQueue[];
67 | private float delayLine[];
68 | private static final int SIZE_FACTOR = 16;
69 | private static final float SCALE = 0.01f;
70 |
71 | // Hilbert Kernel with zeros removed
72 | public static final float HB_KERNEL_FLOAT[] = {
73 | -0.000998606272947510f,
74 | 0.001695637278417295f,
75 | -0.003054430179754289f,
76 | 0.005055504379767936f,
77 | -0.007901319195893647f,
78 | 0.011873357051047719f,
79 | -0.017411159379930066f,
80 | 0.025304817427568772f,
81 | -0.037225225204559217f,
82 | 0.057533286997004301f,
83 | -0.102327462004259350f,
84 | 0.317034472508947400f,
85 | 0.317034472508947400f,
86 | -0.102327462004259350f,
87 | 0.057533286997004301f,
88 | -0.037225225204559217f,
89 | 0.025304817427568772f,
90 | -0.017411159379930066f,
91 | 0.011873357051047719f,
92 | -0.007901319195893647f,
93 | 0.005055504379767936f,
94 | -0.003054430179754289f,
95 | 0.001695637278417295f,
96 | -0.000998606272947510f
97 | };
98 |
99 | /**
100 | * Constructor for the float Converter
101 | * @param sampleType Desired sample type of the output samples (Airspy.AIRSPY_SAMPLE_FLOAT32_IQ or *_FLOAT32_REAL
102 | * @param packingEnabled Indicates if the input samples are packed
103 | * @param inputQueue Queue from which the input samples are taken
104 | * @param inputReturnQueue Queue to return the used input buffers to the pool
105 | * @param outputQueue Queue to deliver the converted samples
106 | * @param outputPoolQueue Queue from which the output buffers are taken
107 | * @throws Exception if the sample type does not match a float based type
108 | */
109 | public AirspyFloatConverter(int sampleType, boolean packingEnabled, ArrayBlockingQueue inputQueue,
110 | ArrayBlockingQueue inputReturnQueue, ArrayBlockingQueue outputQueue,
111 | ArrayBlockingQueue outputPoolQueue) throws Exception {
112 | if(sampleType != Airspy.AIRSPY_SAMPLE_FLOAT32_IQ && sampleType != Airspy.AIRSPY_SAMPLE_FLOAT32_REAL) {
113 | Log.e(LOGTAG, "constructor: Invalid sample type: " + sampleType);
114 | throw new Exception("Invalid sample type: " + sampleType);
115 | }
116 | this.sampleType = sampleType;
117 | this.packingEnabled = packingEnabled;
118 | this.inputQueue = inputQueue;
119 | this.inputReturnQueue = inputReturnQueue;
120 | this.outputQueue = outputQueue;
121 | this.outputPoolQueue = outputPoolQueue;
122 | this.len = HB_KERNEL_FLOAT.length;
123 | this.hbc = 0.5f;
124 | this.delayLine = new float[this.len / 2];
125 | this.firQueue = new float[this.len * SIZE_FACTOR];
126 | }
127 |
128 | /**
129 | * Converts a byte array (little endian, unsigned-12bit-integer) to a float array (signed-32bit-float)
130 | *
131 | * @param src input samples (little endian, unsigned-12bit-integer); min. twice the size of output
132 | * @param dest output samples (signed-32bit-float); min. of size 'count'
133 | * @param count number of samples to process
134 | */
135 | public static void convertSamplesFloat(byte[] src, float[] dest, int count) {
136 | if (src.length < 2 * count || dest.length < count) {
137 | Log.e(LOGTAG, "convertSamplesFloat: input buffers have invalid length: src=" + src.length + " dest=" + dest.length);
138 | return;
139 | }
140 | for (int i = 0; i < count; i++) {
141 | /* src[2i+1] src[2i]
142 | * [--------|--------]
143 | * (xxxx xxxxxxxx) -2048
144 | * * (1/2048) // normalize to [-1;1)
145 | */
146 | dest[i] = ((((src[2 * i + 1] & 0x0F) << 8) + (src[2 * i] & 0xFF)) - 2048) * (1f / 2048f);
147 | }
148 | }
149 |
150 | public void requestStop() {
151 | this.stopRequested = true;
152 | }
153 |
154 | private void firInterleaved(float[] samples) {
155 | float acc;
156 | int idxKernel, idx1, idx2;
157 |
158 | for (int i = 0; i < samples.length; i += 2)
159 | {
160 | firQueue[firIndex] = samples[i];
161 | acc = 0;
162 |
163 | // Convolution
164 | idxKernel = 0;
165 | idx1 = firIndex;
166 | idx2 = firIndex + len - 1;
167 | for(; idxKernel < (len/2)-4; idxKernel+=4, idx1+=4, idx2-=4) {
168 | acc += HB_KERNEL_FLOAT[idxKernel] * (firQueue[idx1] + firQueue[idx2])
169 | + HB_KERNEL_FLOAT[idxKernel+1] * (firQueue[idx1+1] + firQueue[idx2-1])
170 | + HB_KERNEL_FLOAT[idxKernel+2] * (firQueue[idx1+2] + firQueue[idx2-2])
171 | + HB_KERNEL_FLOAT[idxKernel+3] * (firQueue[idx1+3] + firQueue[idx2-3]);
172 | }
173 | // Rest of the convolution: ( if kernel length is not dividable by 2*4 )
174 | for(; idxKernel < len/2; idxKernel++, idx1++, idx2--) {
175 | acc += HB_KERNEL_FLOAT[idxKernel] * (firQueue[idx1] + firQueue[idx2]);
176 | }
177 |
178 | if (--firIndex < 0) {
179 | firIndex = len * (SIZE_FACTOR - 1);
180 | System.arraycopy(firQueue, 0, firQueue, firIndex + 1, len - 1);
181 | }
182 |
183 | samples[i] = acc;
184 | }
185 | }
186 |
187 | private void delayInterleaved(float[] samples, int offset) {
188 | int halfLen = len >> 1;
189 | float res;
190 |
191 | for (int i = offset; i < samples.length; i += 2) {
192 | res = delayLine[delayIndex];
193 | delayLine[delayIndex] = samples[i];
194 | samples[i] = res;
195 |
196 | if (++delayIndex >= halfLen) {
197 | delayIndex = 0;
198 | }
199 | }
200 | }
201 |
202 | private void removeDC(float[] samples) {
203 | for (int i = 0; i < samples.length; i++)
204 | {
205 | samples[i] = samples[i] - avg;
206 | avg += SCALE * samples[i];
207 | }
208 | }
209 |
210 | private void translateFs4(float[] samples) {
211 | for (int i = 0; i < samples.length; i += 4) {
212 | samples[i] = -samples[i];
213 | samples[i + 1] = -samples[i + 1] * hbc;
214 | //samples[i + 2] = samples[i + 2];
215 | samples[i + 3] = samples[i + 3] * hbc;
216 | }
217 |
218 | firInterleaved(samples);
219 | delayInterleaved(samples, 1);
220 | }
221 |
222 | public void processSamplesFloat(float[] samples) {
223 | removeDC(samples);
224 | translateFs4(samples);
225 | }
226 |
227 | public void run() {
228 | byte[] inputBuffer;
229 | byte[] origInputBuffer;
230 | byte[] packingBuffer = null;
231 | float[] outputBuffer = null;
232 |
233 | while (!stopRequested) {
234 | // First we get a fresh set of input and output buffers from the queues:
235 | try {
236 | outputBuffer = outputPoolQueue.poll(1000, TimeUnit.MILLISECONDS);
237 | } catch (InterruptedException e) {
238 | // Note: If the output buffer pool (filled by the user) is empty and the timeout is hit,
239 | // we just wait again. After some time the Airspy class will stop because its usbQueue
240 | // will run full.
241 | Log.e(LOGTAG, "run: Interrupted while waiting for buffers in the output pool. Lets wait for another round...");
242 | continue;
243 | }
244 | if(outputBuffer == null) {
245 | Log.e(LOGTAG, "run: No output buffers available in the pool. Let's query it again...");
246 | continue;
247 | }
248 |
249 | try {
250 | origInputBuffer = inputQueue.poll(1000, TimeUnit.MILLISECONDS);
251 | } catch (InterruptedException e) {
252 | Log.e(LOGTAG, "run: Interrpted while waiting for buffers in the input queue. Stop!");
253 | stopRequested = true;
254 | continue;
255 | }
256 | if(origInputBuffer == null) {
257 | Log.e(LOGTAG, "run: No input buffers available in the queue. Stop!");
258 | stopRequested = true;
259 | continue;
260 | }
261 |
262 | // Maybe unpack the samples first:
263 | if (packingEnabled) {
264 | int unpackedLength = origInputBuffer.length * 4 / 3;
265 | if (packingBuffer == null || packingBuffer.length != unpackedLength)
266 | packingBuffer = new byte[unpackedLength];
267 | Airspy.unpackSamples(origInputBuffer, packingBuffer, unpackedLength);
268 | inputBuffer = packingBuffer;
269 | } else {
270 | inputBuffer = origInputBuffer;
271 | }
272 |
273 | // Next we do the processing for the conversion:
274 | switch (sampleType) {
275 | case Airspy.AIRSPY_SAMPLE_FLOAT32_IQ:
276 | convertSamplesFloat(inputBuffer, outputBuffer, outputBuffer.length);
277 | processSamplesFloat(outputBuffer);
278 | break;
279 |
280 | case Airspy.AIRSPY_SAMPLE_FLOAT32_REAL:
281 | convertSamplesFloat(inputBuffer, outputBuffer, outputBuffer.length);
282 | break;
283 | }
284 |
285 | // Finally we return the buffers to the corresponding queues:
286 | inputReturnQueue.offer(origInputBuffer);
287 | outputQueue.offer(outputBuffer);
288 | }
289 | }
290 | }
291 |
--------------------------------------------------------------------------------
/airspy_android/src/main/java/com/mantz_it/airspy_android/AirspyInt16Converter.java:
--------------------------------------------------------------------------------
1 | package com.mantz_it.airspy_android;
2 |
3 | import android.util.Log;
4 |
5 | import java.util.concurrent.ArrayBlockingQueue;
6 | import java.util.concurrent.TimeUnit;
7 |
8 | /**
9 | *
Airspy USB Library for Android
10 | *
11 | * Module: AirspyInt16Converter.java
12 | * Description: This class offers methods to convert the samples from the Airspy device
13 | * to various INT16 types.
14 | *
15 | * @author Dennis Mantz
16 | *
17 | * Copyright (C) 2015 Dennis Mantz
18 | * based on code of libairspy [https://github.com/airspy/host/tree/master/libairspy]:
19 | * Copyright (c) 2013, Michael Ossmann
20 | * Copyright (c) 2012, Jared Boone
21 | * Copyright (c) 2014, Youssef Touil
22 | * Copyright (c) 2014, Benjamin Vernoux
23 | * Copyright (c) 2015, Ian Gilmour
24 | * All rights reserved.
25 | * Redistribution and use in source and binary forms, with or without modification,
26 | * are permitted provided that the following conditions are met:
27 | * - Redistributions of source code must retain the above copyright notice, this list
28 | * of conditions and the following disclaimer.
29 | * - Redistributions in binary form must reproduce the above copyright notice, this
30 | * list of conditions and the following disclaimer in the documentation and/or other
31 | * materials provided with the distribution.
32 | * - Neither the name of Great Scott Gadgets nor the names of its contributors may be
33 | * used to endorse or promote products derived from this software without specific
34 | * prior written permission.
35 | * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
36 | *
37 | * This library is free software; you can redistribute it and/or
38 | * modify it under the terms of the GNU General Public
39 | * License as published by the Free Software Foundation; either
40 | * version 2 of the License, or (at your option) any later version.
41 | *
42 | * This library is distributed in the hope that it will be useful,
43 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
44 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
45 | * General Public License for more details.
46 | *
47 | * You should have received a copy of the GNU General Public
48 | * License along with this library; if not, write to the Free Software
49 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
50 | */
51 | public class AirspyInt16Converter extends Thread{
52 | private static final String LOGTAG = "AirspyInt16Converter";
53 | private boolean stopRequested = false;
54 | private int sampleType = -1;
55 | private boolean packingEnabled = false;
56 | private ArrayBlockingQueue inputQueue; // Queue from which the input samples are taken
57 | private ArrayBlockingQueue inputReturnQueue; // Queue to return the used input buffers to the pool
58 | private ArrayBlockingQueue outputQueue; // Queue to deliver the converted samples
59 | private ArrayBlockingQueue outputPoolQueue; // Queue from which the output buffers are taken
60 | private int len = 0;
61 | private int firIndex = 0;
62 | private int delayIndex = 0;
63 | private short oldX = 0;
64 | private short oldY = 0;
65 | private int oldE = 0;
66 | private int firQueue[];
67 | private short delayLine[];
68 | private static final int SIZE_FACTOR = 16;
69 |
70 | // Hilbert kernel with zeros removed:
71 | public static final short HB_KERNEL_INT16[] = {
72 | -33,
73 | 56,
74 | -100,
75 | 166,
76 | -259,
77 | 389,
78 | -571,
79 | 829,
80 | -1220,
81 | 1885,
82 | -3353,
83 | 10389,
84 | 10389,
85 | -3353,
86 | 1885,
87 | -1220,
88 | 829,
89 | -571,
90 | 389,
91 | -259,
92 | 166,
93 | -100,
94 | 56,
95 | -33
96 | };
97 |
98 | /**
99 | * Constructor for the int16 Converter
100 | * @param sampleType Desired sample type of the output samples (Airspy.AIRSPY_SAMPLE_INT16_IQ, *_INT16_REAL or *_UINT16_REAL
101 | * @param packingEnabled Indicates if the input samples are packed
102 | * @param inputQueue Queue from which the input samples are taken
103 | * @param inputReturnQueue Queue to return the used input buffers to the pool
104 | * @param outputQueue Queue to deliver the converted samples
105 | * @param outputPoolQueue Queue from which the output buffers are taken
106 | * @throws Exception if the sample type does not match a int16 based type
107 | */
108 | public AirspyInt16Converter(int sampleType, boolean packingEnabled, ArrayBlockingQueue inputQueue,
109 | ArrayBlockingQueue inputReturnQueue, ArrayBlockingQueue outputQueue,
110 | ArrayBlockingQueue outputPoolQueue) throws Exception {
111 | if(sampleType != Airspy.AIRSPY_SAMPLE_INT16_IQ && sampleType != Airspy.AIRSPY_SAMPLE_INT16_REAL && sampleType != Airspy.AIRSPY_SAMPLE_UINT16_REAL) {
112 | Log.e(LOGTAG, "constructor: Invalid sample type: " + sampleType);
113 | throw new Exception("Invalid sample type: " + sampleType);
114 | }
115 | this.sampleType = sampleType;
116 | this.packingEnabled = packingEnabled;
117 | this.inputQueue = inputQueue;
118 | this.inputReturnQueue = inputReturnQueue;
119 | this.outputQueue = outputQueue;
120 | this.outputPoolQueue = outputPoolQueue;
121 | this.len = HB_KERNEL_INT16.length;
122 | this.delayLine = new short[this.len / 2];
123 | this.firQueue = new int[this.len * SIZE_FACTOR];
124 | }
125 |
126 | /**
127 | * Converts a byte array (little endian, unsigned-12bit-integer) to a short array (signed-16bit-integer)
128 | *
129 | * @param src input samples (little endian, unsigned-12bit-integer); min. twice the size of output
130 | * @param dest output samples (signed-16bit-integer); min. of size 'count'
131 | * @param count number of samples to process
132 | */
133 | public static void convertSamplesInt16(byte[] src, short[] dest, int count) {
134 | if (src.length < 2 * count || dest.length < count) {
135 | Log.e(LOGTAG, "convertSamplesInt16: input buffers have invalid length: src=" + src.length + " dest=" + dest.length);
136 | return;
137 | }
138 | for (int i = 0; i < count; i++) {
139 | /* src[2i+1] src[2i]
140 | * [--------|--------]
141 | * (xxxx xxxxxxxx) -2048
142 | * << 4
143 | * [xxxxxxxx|xxxxxxxx] dest[i]
144 | */
145 | dest[i] = (short) (((((src[2 * i + 1] & 0x0F) << 8) + (src[2 * i] & 0xFF)) - 2048) << 4);
146 | }
147 | }
148 |
149 | /**
150 | * Converts a byte array (little endian, unsigned-12bit-integer) to a short array (unsigned-16bit-integer)
151 | *
152 | * @param src input samples (little endian, unsigned-12bit-integer); min. twice the size of output
153 | * @param dest output samples (unsigned-16bit-integer); min. of size 'count'
154 | * @param count number of samples to process
155 | */
156 | public static void convertSamplesUint16(byte[] src, short[] dest, int count) {
157 | if (src.length < 2 * count || dest.length < count) {
158 | Log.e(LOGTAG, "convertSamplesUint16: input buffers have invalid length: src=" + src.length + " dest=" + dest.length);
159 | return;
160 | }
161 | for (int i = 0; i < count; i++) {
162 | /* src[2i+1] src[2i]
163 | * [--------|--------]
164 | * (xxxx xxxxxxxx)
165 | * << 4
166 | * [xxxxxxxx|xxxxxxxx] dest[i]
167 | */
168 | dest[i] = (short) ((((src[2 * i + 1] & 0x0F) << 8) + (src[2 * i] & 0xFF)) << 4);
169 | }
170 | }
171 |
172 | public void requestStop() {
173 | this.stopRequested = true;
174 | }
175 |
176 | private void firInterleaved(short[] samples) {
177 | int acc;
178 |
179 | for (int i = 0; i < samples.length; i += 2) {
180 | firQueue[firIndex] = samples[i];
181 | acc = 0;
182 |
183 | // Auto vectorization works on VS2012, VS2013 and GCC
184 | for (int j = 0; j < len; j++) {
185 | acc += HB_KERNEL_INT16[j] * firQueue[firIndex + j];
186 | }
187 |
188 | if (--firIndex < 0) {
189 | firIndex = len * (SIZE_FACTOR - 1);
190 | System.arraycopy(firQueue, 0, firQueue, firIndex + 1, len - 1);
191 | }
192 |
193 | samples[i] = (short) (acc >> 15);
194 | }
195 | }
196 |
197 | private void delayInterleaved(short[] samples, int offset) {
198 | int halfLen = len >> 1;
199 | short res;
200 |
201 | for (int i = offset; i < samples.length; i += 2) {
202 | res = delayLine[delayIndex];
203 | delayLine[delayIndex] = samples[i];
204 | samples[i] = res;
205 |
206 | if (++delayIndex >= halfLen) {
207 | delayIndex = 0;
208 | }
209 | }
210 | }
211 |
212 | private void removeDC(short[] samples) {
213 | int u;
214 | short x, y, w, s;
215 |
216 | for (int i = 0; i < samples.length; i++) {
217 | x = samples[i];
218 | w = (short) (x - oldX);
219 | u = oldE + oldY * 32100;
220 | s = (short) (u >> 15);
221 | y = (short) (w + s);
222 | oldE = u - (s << 15);
223 | oldX = x;
224 | oldY = y;
225 | samples[i] = y;
226 | }
227 | }
228 |
229 | private void translateFs4(short[] samples) {
230 | for (int i = 0; i < samples.length; i += 4) {
231 | samples[i] = (short) -samples[i];
232 | samples[i + 1] = (short) (-samples[i + 1] >> 1);
233 | //samples[i + 2] = samples[i + 2];
234 | samples[i + 3] = (short) (samples[i + 3] >> 1);
235 | }
236 |
237 | firInterleaved(samples);
238 | delayInterleaved(samples, 1);
239 | }
240 |
241 | public void processSamplesInt16(short[] samples) {
242 | removeDC(samples);
243 | translateFs4(samples);
244 | }
245 |
246 | public void run() {
247 | byte[] inputBuffer;
248 | byte[] origInputBuffer;
249 | byte[] packingBuffer = null;
250 | short[] outputBuffer = null;
251 |
252 | while (!stopRequested) {
253 | // First we get a fresh set of input and output buffers from the queues:
254 | try {
255 | outputBuffer = outputPoolQueue.poll(1000, TimeUnit.MILLISECONDS);
256 | } catch (InterruptedException e) {
257 | // Note: If the output buffer pool (filled by the user) is empty and the timeout is hit,
258 | // we just wait again. After some time the Airspy class will stop because its usbQueue
259 | // will run full.
260 | Log.e(LOGTAG, "run: Interrupted while waiting for buffers in the output pool. Lets wait for another round...");
261 | continue;
262 | }
263 | if(outputBuffer == null) {
264 | Log.e(LOGTAG, "run: No output buffers available in the pool. Let's query it again...");
265 | continue;
266 | }
267 |
268 | try {
269 | origInputBuffer = inputQueue.poll(1000, TimeUnit.MILLISECONDS);
270 | } catch (InterruptedException e) {
271 | Log.e(LOGTAG, "run: Interrpted while waiting for buffers in the input queue. Stop!");
272 | stopRequested = true;
273 | continue;
274 | }
275 | if(origInputBuffer == null) {
276 | Log.e(LOGTAG, "run: No input buffers available in the queue. Stop!");
277 | stopRequested = true;
278 | continue;
279 | }
280 |
281 | // Maybe unpack the samples first:
282 | if (packingEnabled) {
283 | int unpackedLength = origInputBuffer.length * 4 / 3;
284 | if (packingBuffer == null || packingBuffer.length != unpackedLength)
285 | packingBuffer = new byte[unpackedLength];
286 | Airspy.unpackSamples(origInputBuffer, packingBuffer, unpackedLength);
287 | inputBuffer = packingBuffer;
288 | } else {
289 | inputBuffer = origInputBuffer;
290 | }
291 |
292 | // Next we do the processing for the conversion:
293 | switch (sampleType) {
294 | case Airspy.AIRSPY_SAMPLE_INT16_IQ:
295 | convertSamplesInt16(inputBuffer, outputBuffer, outputBuffer.length);
296 | processSamplesInt16(outputBuffer);
297 | break;
298 |
299 | case Airspy.AIRSPY_SAMPLE_INT16_REAL:
300 | convertSamplesInt16(inputBuffer, outputBuffer, outputBuffer.length);
301 | break;
302 |
303 | case Airspy.AIRSPY_SAMPLE_UINT16_REAL:
304 | convertSamplesUint16(inputBuffer, outputBuffer, outputBuffer.length);
305 | break;
306 | }
307 |
308 | // Finally we return the buffers to the corresponding queues:
309 | inputReturnQueue.offer(origInputBuffer);
310 | outputQueue.offer(outputBuffer);
311 | }
312 | }
313 | }
--------------------------------------------------------------------------------
/airspy_android/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | airspy_android
3 |
4 |
--------------------------------------------------------------------------------
/airspy_android/src/test/java/com/mantz_it/airspy_android/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.mantz_it.airspy_android;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * To work on unit tests, switch the Test Artifact in the Build Variants view.
9 | */
10 | public class ExampleUnitTest {
11 | @Test
12 | public void addition_isCorrect() throws Exception {
13 | assertEquals(4, 2 + 2);
14 | }
15 | }
--------------------------------------------------------------------------------
/airspy_test/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/airspy_test/airspy_test.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/airspy_test/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 21
5 | buildToolsVersion "21.1.1"
6 |
7 | defaultConfig {
8 | applicationId "com.mantz_it.airspy_test"
9 | minSdkVersion 14
10 | targetSdkVersion 21
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(include: ['*.jar'], dir: 'libs')
24 | testCompile 'junit:junit:4.12'
25 | compile 'com.android.support:appcompat-v7:21.0.3'
26 | compile project(':airspy_android')
27 | }
28 |
--------------------------------------------------------------------------------
/airspy_test/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /opt/android_sdk/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/airspy_test/src/androidTest/java/com/mantz_it/airspy_test/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.mantz_it.airspy_test;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | import com.mantz_it.airspy_android.Airspy;
7 | import com.mantz_it.airspy_android.AirspyFloatConverter;
8 | import com.mantz_it.airspy_android.AirspyInt16Converter;
9 |
10 | /**
11 | * Testing Fundamentals
12 | */
13 | public class ApplicationTest extends ApplicationTestCase {
14 | public ApplicationTest() {
15 | super(Application.class);
16 | }
17 |
18 | public void testAirspyConvertSamplesInt16() {
19 | byte[] input = {
20 | (byte) 0xFF, (byte) 0xFF, // 0
21 | (byte) 0xFE, (byte) 0xFF, // 1
22 | (byte) 0xFF, (byte) 0x0F, // 2
23 | (byte) 0xFE, (byte) 0x0F, // 3
24 | (byte) 0x00, (byte) 0x00, // 4
25 | (byte) 0x01, (byte) 0x00, // 5
26 | (byte) 0x00, (byte) 0xF0, // 6
27 | (byte) 0x00, (byte) 0x08, // 7
28 | (byte) 0x03, (byte) 0x08}; // 8
29 | short[] expected = {
30 | Short.MAX_VALUE-0x0F, // 0
31 | Short.MAX_VALUE-0x1F, // 1
32 | Short.MAX_VALUE-0x0F, // 2
33 | Short.MAX_VALUE-0x1F, // 3
34 | Short.MIN_VALUE, // 4
35 | Short.MIN_VALUE+0x10, // 5
36 | Short.MIN_VALUE, // 6
37 | 0, // 7
38 | 0x30}; // 8
39 | short[] output = new short[expected.length];
40 | AirspyInt16Converter.convertSamplesInt16(input, output, output.length);
41 | System.out.print("Testing input buffer: ");
42 | printUnsignedArray(input);
43 |
44 | for(int i=0; i expected-0.0001;
302 | }
303 | }
--------------------------------------------------------------------------------
/airspy_test/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
14 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/airspy_test/src/main/java/com/mantz_it/airspy_test/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.mantz_it.airspy_test;
2 |
3 | import android.content.Intent;
4 | import android.content.pm.PackageManager;
5 | import android.net.Uri;
6 | import android.os.Bundle;
7 | import android.os.Environment;
8 | import android.os.Handler;
9 | import android.support.v7.app.ActionBarActivity;
10 | import android.text.Html;
11 | import android.text.method.ScrollingMovementMethod;
12 | import android.util.Log;
13 | import android.view.Menu;
14 | import android.view.MenuItem;
15 | import android.view.View;
16 | import android.widget.ArrayAdapter;
17 | import android.widget.Button;
18 | import android.widget.CheckBox;
19 | import android.widget.EditText;
20 | import android.widget.SeekBar;
21 | import android.widget.Spinner;
22 | import android.widget.TextView;
23 |
24 | import com.mantz_it.airspy_android.Airspy;
25 |
26 | import java.io.BufferedOutputStream;
27 | import java.io.File;
28 | import java.io.FileOutputStream;
29 | import java.io.IOException;
30 | import java.nio.ByteBuffer;
31 | import java.nio.ByteOrder;
32 | import java.nio.FloatBuffer;
33 | import java.nio.ShortBuffer;
34 | import java.util.concurrent.ArrayBlockingQueue;
35 | import java.util.concurrent.TimeUnit;
36 |
37 | public class MainActivity extends ActionBarActivity implements Runnable, Airspy.AirspyCallbackInterface {
38 |
39 | private static final String LOGTAG = "MainActivity";
40 |
41 | // References to the GUI elements:
42 | private Button bt_openAirspy = null;
43 | private Button bt_info = null;
44 | private Button bt_rx = null;
45 | private Button bt_stop = null;
46 | private EditText et_sampRateIndex = null;
47 | private EditText et_freq = null;
48 | private EditText et_filename = null;
49 | private SeekBar sb_vgaGain = null;
50 | private SeekBar sb_lnaGain = null;
51 | private SeekBar sb_mixerGain = null;
52 | private CheckBox cb_packing = null;
53 | private Spinner sp_sampleType = null;
54 | private TextView tv_output = null;
55 |
56 | // Reference to the airspy instance:
57 | private Airspy airspy = null;
58 |
59 | private int sampRateIndex = 0;
60 | private int frequency = 0;
61 | private String filename = null;
62 | private int vgaGain = 0;
63 | private int lnaGain = 0;
64 | private int mixerGain = 0;
65 | private boolean packingEnabled = false;
66 | private int sampleType = 0;
67 |
68 | // The handler is used to access GUI elements from other threads then the GUI thread
69 | private Handler handler = null;
70 |
71 | // This variable is used to select what the thread should do if it is started
72 | private int task = -1;
73 | private static final int PRINT_INFO = 0;
74 | private static final int RECEIVE = 1;
75 |
76 | private boolean stopRequested = false; // Used to stop receive thread
77 |
78 | // Folder name for capture files:
79 | private static final String foldername = "Test_Airspy";
80 |
81 | // logcat process:
82 | Process logcat;
83 | File logfile;
84 |
85 | // This method is called on application startup by the Android System:
86 | @Override
87 | protected void onCreate(Bundle savedInstanceState) {
88 | super.onCreate(savedInstanceState);
89 | setContentView(R.layout.activity_main);
90 |
91 | // Start logging:
92 | try{
93 | logfile = new File(Environment.getExternalStorageDirectory() + "/" + foldername, "log.txt");
94 | logfile.getParentFile().mkdir(); // Create folder
95 | logcat = Runtime.getRuntime().exec("logcat -f " + logfile.getAbsolutePath());
96 | Log.i(LOGTAG, "onCreate: log path: " + logfile.getAbsolutePath());
97 | } catch (Exception e) {
98 | Log.e(LOGTAG, "onCreate: Failed to start logging!");
99 | }
100 |
101 | // Create a Handler instance to use in other threads:
102 | handler = new Handler();
103 |
104 | // Initialize the GUI references:
105 | bt_info = ((Button) this.findViewById(R.id.bt_info));
106 | bt_rx = ((Button) this.findViewById(R.id.bt_rx));
107 | bt_stop = ((Button) this.findViewById(R.id.bt_stop));
108 | bt_openAirspy = ((Button) this.findViewById(R.id.bt_openAirspy));
109 | et_sampRateIndex = (EditText) this.findViewById(R.id.et_sampRateIndex);
110 | et_freq = (EditText) this.findViewById(R.id.et_freq);
111 | et_filename = (EditText) this.findViewById(R.id.et_filename);
112 | sb_vgaGain = (SeekBar) this.findViewById(R.id.sb_vgaGain);
113 | sb_lnaGain = (SeekBar) this.findViewById(R.id.sb_lnaGain);
114 | sb_mixerGain = (SeekBar) this.findViewById(R.id.sb_mixerGain);
115 | cb_packing = (CheckBox) this.findViewById(R.id.cb_packing);
116 | sp_sampleType = (Spinner) this.findViewById(R.id.sp_sampleType);
117 | tv_output = (TextView) findViewById(R.id.tv_output);
118 | tv_output.setMovementMethod(new ScrollingMovementMethod()); // make it scroll!
119 | this.toggleButtonsEnabledIfAirspyReady(false); // Disable all buttons except for 'Open Airspy'
120 |
121 | // Create an ArrayAdapter using the string array and a default spinner layout
122 | ArrayAdapter adapter = ArrayAdapter.createFromResource(this,R.array.sampleTypes, android.R.layout.simple_spinner_item);
123 | adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
124 | sp_sampleType.setAdapter(adapter);
125 | sp_sampleType.setSelection(3);
126 |
127 | // Print Hello
128 | String version = "";
129 | try {
130 | version = getPackageManager().getPackageInfo(getPackageName(), 0).versionName;
131 | } catch (PackageManager.NameNotFoundException e) {}
132 | this.tv_output.setText("Test_Airspy (version " + version + ") by Dennis Mantz\n");
133 | }
134 |
135 | @Override
136 | protected void onDestroy() {
137 | if(logcat != null)
138 | logcat.destroy();
139 | super.onDestroy();
140 | }
141 |
142 | @Override
143 | public boolean onCreateOptionsMenu(Menu menu) {
144 | // Inflate the menu; this adds items to the action bar if it is present.
145 | getMenuInflater().inflate(R.menu.menu_main, menu);
146 | return true;
147 | }
148 |
149 | @Override
150 | public boolean onOptionsItemSelected(MenuItem item) {
151 | // Handle action bar item clicks here. The action bar will
152 | // automatically handle clicks on the Home/Up button, so long
153 | // as you specify a parent activity in AndroidManifest.xml.
154 | int id = item.getItemId();
155 |
156 | if (id == R.id.action_help) {
157 | this.tv_output.setText(Html.fromHtml(getResources().getString(R.string.helpText)));
158 | return true;
159 | }
160 | if (id == R.id.action_showLog) {
161 | Uri uri = Uri.fromFile(logfile);
162 | Intent intent = new Intent(Intent.ACTION_VIEW);
163 | intent.setDataAndType(uri, "text/plain");
164 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
165 | this.startActivity(intent);
166 | return true;
167 | }
168 | if (id == R.id.action_settings) {
169 | return true;
170 | }
171 |
172 | return super.onOptionsItemSelected(item);
173 | }
174 |
175 | /**
176 | * Will append the message to the tv_output TextView. Can be called from
177 | * outside the GUI thread because it uses the handler reference to access
178 | * the TextView.
179 | *
180 | * @param msg Message to print on the screen
181 | */
182 | public void printOnScreen(final String msg)
183 | {
184 | handler.post(new Runnable() {
185 | public void run() {
186 | tv_output.append(msg);
187 | }
188 | });
189 | }
190 |
191 | /**
192 | * Will set all buttons to disabled except for the 'open airspy' button. If
193 | * false is given, the behavior toggles. Can be called from
194 | * outside the GUI thread because it uses the handler reference to access
195 | * the TextView.
196 | *
197 | * @param enable if true: 'Open Airspy' will be enabled, all others disabled
198 | * if false: The other way round
199 | */
200 | public void toggleButtonsEnabledIfAirspyReady(final boolean enable)
201 | {
202 | handler.post(new Runnable() {
203 | public void run() {
204 | bt_info.setEnabled(enable);
205 | bt_rx.setEnabled(enable);
206 | bt_stop.setEnabled(enable);
207 | bt_openAirspy.setEnabled(!enable);
208 | }
209 | });
210 | }
211 |
212 | /**
213 | * Will set 'Info', 'RX' to disabled and 'stop' to enabled (while
214 | * receiving is running). If false is given, the behavior toggles.
215 | * Can be called from outside the GUI thread because it uses the handler
216 | * reference to access the TextView.
217 | *
218 | * @param enable if true: 'Stop' will be enabled, all others ('Info', 'RX') disabled
219 | * if false: The other way round
220 | */
221 | public void toggleButtonsEnabledIfReceiving(final boolean enable)
222 | {
223 | handler.post(new Runnable() {
224 | public void run() {
225 | bt_info.setEnabled(!enable);
226 | bt_rx.setEnabled(!enable);
227 | bt_stop.setEnabled(enable);
228 | }
229 | });
230 | }
231 |
232 | /**
233 | * Will read the values from the GUI elements into the corresponding variables
234 | */
235 | public void readGuiElements()
236 | {
237 | sampRateIndex = Integer.valueOf(et_sampRateIndex.getText().toString());
238 | frequency = Integer.valueOf(et_freq.getText().toString());
239 | filename = et_filename.getText().toString();
240 | vgaGain = sb_vgaGain.getProgress();
241 | lnaGain = sb_lnaGain.getProgress();
242 | mixerGain = sb_mixerGain.getProgress();
243 | packingEnabled = cb_packing.isChecked();
244 | sampleType = sp_sampleType.getSelectedItemPosition();
245 | }
246 |
247 | /**
248 | * Is called if the user presses the 'Open Airspy' Button. Will initialize the
249 | * Airspy device.
250 | *
251 | * @param view Reference to the calling View (in this case bt_openAirspy)
252 | */
253 | public void openAirspy(View view)
254 | {
255 | // Initialize the Airspy (i.e. open the USB device, which requires the user to give permissions)
256 | if (!Airspy.initAirspy(view.getContext(), this))
257 | {
258 | tv_output.append("No Airspy could be found!\n");
259 | }
260 | // initAirspy() is asynchronous. this.onAirspyReady() will be called as soon as the device is ready.
261 | }
262 |
263 | /**
264 | * Is called if the user presses the 'Info' Button. Will start a Thread that
265 | * retrieves the BoardID, Version String, PartID and Serial number from the device
266 | * and then print the information on the screen.
267 | *
268 | * @param view Reference to the calling View (in this case bt_info)
269 | */
270 | public void info(View view)
271 | {
272 | if (airspy != null)
273 | {
274 | this.task = PRINT_INFO;
275 | new Thread(this).start();
276 | }
277 | }
278 |
279 | /**
280 | * Is called if the user presses the 'RX' Button. Will start a Thread that
281 | * sets the Airspy into receiving mode and then save the received samples
282 | * to a file. Will run forever until user presses the 'Stop' button.
283 | *
284 | * @param view Reference to the calling View (in this case bt_rx)
285 | */
286 | public void rx(View view)
287 | {
288 | if (airspy != null)
289 | {
290 | this.readGuiElements();
291 | this.task = RECEIVE;
292 | this.stopRequested = false;
293 | new Thread(this).start();
294 | toggleButtonsEnabledIfReceiving(true);
295 | }
296 | }
297 |
298 | /**
299 | * Is called if the user presses the 'Stop' Button. Will set the stopRequested
300 | * attribute to true, which will cause any running thread to shut down. It will
301 | * then set the transceiver mode of the Airspy to OFF.
302 | *
303 | * @param view Reference to the calling View (in this case bt_stop)
304 | */
305 | public void stop(View view)
306 | {
307 | this.stopRequested = true;
308 | toggleButtonsEnabledIfReceiving(false);
309 |
310 | if(airspy != null)
311 | {
312 | try {
313 | airspy.stop();
314 | } catch (Airspy.AirspyUsbException e) {
315 | printOnScreen("Error (USB)!\n");
316 | toggleButtonsEnabledIfAirspyReady(false);
317 | }
318 | }
319 | }
320 |
321 | /**
322 | * Is called by the airspy_android library after the device is ready.
323 | * Was triggered by the initAirspy() call in openAirspy().
324 | * See also AirspyCallbackInterface.java
325 | *
326 | * @param airspy Instance of the Airspy class that represents the open device
327 | */
328 | @Override
329 | public void onAirspyReady(Airspy airspy) {
330 | tv_output.append("Airspy is ready!\n");
331 |
332 | this.airspy = airspy;
333 | this.toggleButtonsEnabledIfAirspyReady(true);
334 | this.toggleButtonsEnabledIfReceiving(false);
335 | }
336 |
337 | /**
338 | * Is called by the airspy_android library after a error occurred while opening
339 | * the device.
340 | * Was triggered by the initAirspy() call in openAirspy().
341 | * See also AirspyCallbackInterface.java
342 | *
343 | * @param message Short human readable error message
344 | */
345 | @Override
346 | public void onAirspyError(String message) {
347 | tv_output.append("Error while opening Airspy: " + message +"\n");
348 | this.toggleButtonsEnabledIfAirspyReady(false);
349 | }
350 |
351 | /**
352 | * Is called (in a separate Thread) after 'new Thread(this).start()' is
353 | * executed in info(), rx().
354 | * Will run either infoThread() or receiveThread() depending
355 | * on how the task attribute is set.
356 | */
357 | @Override
358 | public void run() {
359 | switch(this.task)
360 | {
361 | case PRINT_INFO: infoThread(); break;
362 | case RECEIVE: receiveThread(); break;
363 | default:
364 | }
365 |
366 | }
367 |
368 | /**
369 | * Will run in a separate thread created in info(). Retrieves the BoardID,
370 | * Version String, PartID and Serial number from the device
371 | * and then print the information on the screen.
372 | */
373 | public void infoThread()
374 | {
375 | // Read out boardID, version, partID and serialNo:
376 | try
377 | {
378 | int boardID = airspy.getBoardID();
379 | printOnScreen("Board ID: " + boardID + " (" + Airspy.convertBoardIdToString(boardID) + ")\n" );
380 | printOnScreen("Version: " + airspy.getVersionString() + "\n");
381 | int[] tmp = airspy.getPartIdAndSerialNo();
382 | printOnScreen("Part ID: 0x" + Integer.toHexString(tmp[0]) +
383 | " 0x" + Integer.toHexString(tmp[1]) +"\n");
384 | printOnScreen("Serial No: 0x" + Integer.toHexString(tmp[2]) +
385 | " 0x" + Integer.toHexString(tmp[3]) +
386 | " 0x" + Integer.toHexString(tmp[4]) +
387 | " 0x" + Integer.toHexString(tmp[5]) +"\n");
388 | int[] rates = airspy.getSampleRates();
389 | printOnScreen("Supported sample rates:\n");
390 | for(int rate: rates) {
391 | printOnScreen("\t" + rate + "\n");
392 | }
393 | printOnScreen("\n");
394 | } catch (Airspy.AirspyUsbException e) {
395 | printOnScreen("Error while reading Board Information!\n");
396 | this.toggleButtonsEnabledIfAirspyReady(false);
397 | }
398 | }
399 |
400 | /**
401 | * Will run in a separate thread created in rx(). Sets the Airspy into receiving
402 | * mode and then save the received samples to a file. Will run forever until user
403 | * presses the 'Stop' button.
404 | */
405 | public void receiveThread()
406 | {
407 | int i;
408 | long lastTransceiverPacketCounter = 0;
409 | long lastTransceivingTime = 0;
410 |
411 | // vgaGain, mixerGain and lnaGain are still values from 0-100; scale them to the right range:
412 | vgaGain = (vgaGain * 15) / 100;
413 | lnaGain = (lnaGain * 14) / 100;
414 | mixerGain = (mixerGain * 15) / 100;
415 |
416 | try {
417 | // First set all parameters:
418 | int[] sampleRates = airspy.getSampleRates();
419 | if(sampRateIndex < 0 || sampRateIndex >= sampleRates.length) {
420 | printOnScreen("Sample Rate index " + sampRateIndex + " is out of bounds. Supported Rates are:\n");
421 | for(i=0; i shortQueue = null;
477 | ArrayBlockingQueue shortReturnPoolQueue = null;
478 | ArrayBlockingQueue floatQueue = null;
479 | ArrayBlockingQueue floatReturnPoolQueue = null;
480 | short[] shortSamples;
481 | float[] floatSamples;
482 |
483 | // Start Receiving:
484 | printOnScreen("Start Receiving... \n");
485 | airspy.startRX();
486 |
487 | switch (sampleType) {
488 | case Airspy.AIRSPY_SAMPLE_FLOAT32_IQ:
489 | case Airspy.AIRSPY_SAMPLE_FLOAT32_REAL:
490 | byteBuffer = ByteBuffer.allocate(airspy.getUsbPacketSize() * 2); // *2 because float has 32bit
491 | byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
492 | floatBuffer = byteBuffer.asFloatBuffer();
493 | floatQueue = airspy.getFloatQueue();
494 | floatReturnPoolQueue = airspy.getFloatReturnPoolQueue();
495 | break;
496 | case Airspy.AIRSPY_SAMPLE_INT16_IQ:
497 | case Airspy.AIRSPY_SAMPLE_INT16_REAL:
498 | case Airspy.AIRSPY_SAMPLE_UINT16_REAL:
499 | byteBuffer = ByteBuffer.allocate(airspy.getUsbPacketSize());
500 | byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
501 | shortBuffer = byteBuffer.asShortBuffer();
502 | shortQueue = airspy.getInt16Queue();
503 | shortReturnPoolQueue = airspy.getInt16ReturnPoolQueue();
504 | break;
505 | }
506 |
507 |
508 | // Run until user hits the 'Stop' button
509 | i = 0;
510 | while(!this.stopRequested)
511 | {
512 | i++; // only for statistics
513 |
514 | // TODO: update this information (Airspy has different sample types):
515 | /* HERE should be the DSP portion of the app. The receivedBytes
516 | * variable now contains a byte array of size airspy.getUsbPacketSize().
517 | * The bytes are interleaved, 16-bit, signed IQ samples (in-phase
518 | * component first, followed by the quadrature component):
519 | *
520 | * [--------- first sample ----------] [-------- second sample --------]
521 | * I Q I Q ...
522 | * receivedBytes[0] receivedBytes[1] receivedBytes[2] ...
523 | *
524 | * Note: Make sure you read from the queue fast enough, because if it runs
525 | * full, the airspy_android library will abort receiving and go back to
526 | * OFF mode.
527 | */
528 |
529 | // Grab one packet from the top of the queue. Will block if queue is
530 | // empty and timeout after one second if the queue stays empty.
531 | if(sampleType==Airspy.AIRSPY_SAMPLE_FLOAT32_IQ || sampleType==Airspy.AIRSPY_SAMPLE_FLOAT32_REAL) {
532 | floatSamples = floatQueue.poll(1000, TimeUnit.MILLISECONDS);
533 | // We just write the whole packet into the file:
534 | if(floatSamples != null)
535 | {
536 | floatBuffer.position(0);
537 | floatBuffer.put(floatSamples);
538 | outputStream.write(byteBuffer.array());
539 |
540 | // IMPORTANT: After we used the receivedBytes buffer and don't need it any more,
541 | // we should return it to the buffer pool of the airspy! This will save a lot of
542 | // allocation time and the garbage collector won't go off every second.
543 | floatReturnPoolQueue.offer(floatSamples);
544 | }
545 | else
546 | {
547 | printOnScreen("Error: Queue is empty! (This happens most often because the queue ran full"
548 | + " which causes the Airspy class to stop receiving. Writing the samples to a file"
549 | + " seems to be working to slowly... try a lower sample rate.)\n");
550 | break;
551 | }
552 | }
553 | else {
554 | shortSamples = shortQueue.poll(1000, TimeUnit.MILLISECONDS);
555 | // We just write the whole packet into the file:
556 | if(shortSamples != null)
557 | {
558 | shortBuffer.position(0);
559 | shortBuffer.put(shortSamples);
560 | outputStream.write(byteBuffer.array());
561 |
562 | // IMPORTANT: After we used the receivedBytes buffer and don't need it any more,
563 | // we should return it to the buffer pool of the airspy! This will save a lot of
564 | // allocation time and the garbage collector won't go off every second.
565 | shortReturnPoolQueue.offer(shortSamples);
566 | }
567 | else
568 | {
569 | printOnScreen("Error: Queue is empty! (This happens most often because the queue ran full"
570 | + " which causes the Airspy class to stop receiving. Writing the samples to a file"
571 | + " seems to be working to slowly... try a lower sample rate.)\n");
572 | break;
573 | }
574 | }
575 |
576 | // print statistics
577 | if(i%1000 == 0)
578 | {
579 | long bytes = (airspy.getReceiverPacketCounter() - lastTransceiverPacketCounter) * airspy.getUsbPacketSize();
580 | double time = (airspy.getReceivingTime() - lastTransceivingTime)/1000.0;
581 | printOnScreen( String.format("Current Transfer Rate: %4.1f MB/s\n",(bytes/time)/1000000.0));
582 | lastTransceiverPacketCounter = airspy.getReceiverPacketCounter();
583 | lastTransceivingTime = airspy.getReceivingTime();
584 | }
585 | }
586 |
587 | // After loop ended: close the file and print more statistics:
588 | outputStream.close();
589 | printOnScreen( String.format("Finished! (Average Transfer Rate: %4.1f MB/s\n",
590 | airspy.getAverageReceiveRate()/1000000.0));
591 | printOnScreen(String.format("Recorded %d packets (each %d Bytes) in %5.3f Seconds.\n\n",
592 | airspy.getReceiverPacketCounter(), airspy.getUsbPacketSize(),
593 | airspy.getReceivingTime()/1000.0));
594 | toggleButtonsEnabledIfReceiving(false);
595 | } catch (Airspy.AirspyUsbException e) {
596 | // This exception is thrown if a USB communication error occurres (e.g. you unplug / reset
597 | // the device while receiving)
598 | printOnScreen("error (USB " + e.getMessage() + ")!\n");
599 | toggleButtonsEnabledIfAirspyReady(false);
600 | } catch (IOException e) {
601 | // This exception is thrown if the file could not be opened or write fails.
602 | printOnScreen("error (File IO: " + e.getMessage() + ")!\n");
603 | toggleButtonsEnabledIfReceiving(false);
604 | } catch (InterruptedException e) {
605 | // This exception is thrown if queue.poll() is interrupted
606 | printOnScreen("error (Queue)!\n");
607 | toggleButtonsEnabledIfReceiving(false);
608 | }
609 | }
610 |
611 | }
612 |
--------------------------------------------------------------------------------
/airspy_test/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
21 |
22 |
29 |
30 |
37 |
38 |
45 |
46 |
54 |
55 |
65 |
66 |
74 |
75 |
85 |
86 |
94 |
95 |
103 |
104 |
111 |
112 |
121 |
122 |
129 |
130 |
139 |
140 |
147 |
148 |
157 |
158 |
165 |
166 |
173 |
174 |
181 |
182 |
193 |
194 |
--------------------------------------------------------------------------------
/airspy_test/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
19 |
--------------------------------------------------------------------------------
/airspy_test/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/demantz/airspy_android/7e06c96c086f0a3c27801b371ede86adc260e877/airspy_test/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/airspy_test/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/demantz/airspy_android/7e06c96c086f0a3c27801b371ede86adc260e877/airspy_test/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/airspy_test/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/demantz/airspy_android/7e06c96c086f0a3c27801b371ede86adc260e877/airspy_test/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/airspy_test/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/demantz/airspy_android/7e06c96c086f0a3c27801b371ede86adc260e877/airspy_test/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/airspy_test/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/demantz/airspy_android/7e06c96c086f0a3c27801b371ede86adc260e877/airspy_test/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/airspy_test/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/airspy_test/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/airspy_test/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Airspy_Test
3 | Settings
4 | Show Log
5 | Help
6 | Open
7 | Info
8 | RX
9 | Sample Rate
10 | Frequency
11 | Stop
12 | Enable Packing
13 | Filename
14 | VGA Gain
15 | LNA Gain
16 | Mixer Gain
17 | Sample Type
18 |
19 | FLOAT32_IQ
20 | FLOAT32_REAL
21 | INT16_IQ
22 | INT16_REAL
23 | UINT16_REAL
24 |
25 |
26 | == HELP ==
28 | This is the example application delivered with the airspy_android library. Its intention
29 | is to demonstrate the functionality of the library and to provide running example code.
30 | To use this App you need a Airspy connected to your device via an OTG USB cable. Press Open,
31 | give the App the permissions to open the USB device and hit Info to read out the Board ID,
32 | Version, Part ID and Serial Number from your device. RX will start receiving samples at the
33 | given sample rate and store them to a file on your SD card. If you leave the filename blank, all
34 | samples will go to /dev/null. With TX you can transmit a previously recorded file.
35 | Get the latest version (binaries and sources) from the Github repository:
36 | https://github.com/demantz/airspy_android