├── .gitignore ├── .idea ├── .name ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── gradle.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── Airspy_Test.apk ├── Readme.md ├── airspy_android.aar ├── airspy_android.iml ├── airspy_android ├── .gitignore ├── airspy_android-airspy_android.iml ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── mantz_it │ │ └── airspy_android │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── mantz_it │ │ │ └── airspy_android │ │ │ ├── Airspy.java │ │ │ ├── AirspyFloatConverter.java │ │ │ └── AirspyInt16Converter.java │ └── res │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── mantz_it │ └── airspy_android │ └── ExampleUnitTest.java ├── airspy_test ├── .gitignore ├── airspy_test.iml ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── mantz_it │ │ └── airspy_test │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── mantz_it │ │ │ └── airspy_test │ │ │ └── MainActivity.java │ └── res │ │ ├── layout │ │ └── activity_main.xml │ │ ├── menu │ │ └── menu_main.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── mantz_it │ └── airspy_test │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | /.idea/workspace.xml 4 | /.idea/libraries 5 | .DS_Store 6 | /build 7 | /captures 8 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | airspy_android -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | 47 | 48 | 49 | 50 | Android API 19 Platform 51 | 52 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 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 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /airspy_android/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /airspy_android/airspy_android-airspy_android.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 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 | -------------------------------------------------------------------------------- /airspy_android/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 21 5 | buildToolsVersion "21.1.1" 6 | 7 | defaultConfig { 8 | minSdkVersion 12 9 | targetSdkVersion 21 10 | versionCode 1 11 | versionName "1.0" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | compile fileTree(dir: 'libs', include: ['*.jar']) 23 | testCompile 'junit:junit:4.12' 24 | compile 'com.android.support:appcompat-v7:21.0.3' 25 | } 26 | -------------------------------------------------------------------------------- /airspy_android/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_android/src/androidTest/java/com/mantz_it/airspy_android/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.mantz_it.airspy_android; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /airspy_android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /airspy_android/src/main/java/com/mantz_it/airspy_android/Airspy.java: -------------------------------------------------------------------------------- 1 | package com.mantz_it.airspy_android; 2 | 3 | import android.app.PendingIntent; 4 | import android.content.BroadcastReceiver; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.content.IntentFilter; 8 | import android.hardware.usb.UsbConstants; 9 | import android.hardware.usb.UsbDevice; 10 | import android.hardware.usb.UsbDeviceConnection; 11 | import android.hardware.usb.UsbEndpoint; 12 | import android.hardware.usb.UsbInterface; 13 | import android.hardware.usb.UsbManager; 14 | import android.hardware.usb.UsbRequest; 15 | import android.util.Log; 16 | import android.widget.Toast; 17 | 18 | import java.nio.ByteBuffer; 19 | import java.util.HashMap; 20 | import java.util.Iterator; 21 | import java.util.concurrent.ArrayBlockingQueue; 22 | import java.util.concurrent.TimeUnit; 23 | 24 | /** 25 | *

Airspy USB Library for Android

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 | 8 | 9 | 10 | 11 | 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 |