├── .gitignore ├── Benchmark.apk ├── COPYING ├── Readme.md ├── android_dsp_lib ├── .gitignore ├── android_dsp_lib-android_dsp_lib.iml ├── build.gradle ├── libs │ └── renderscript-v8.jar ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── mantz_it │ │ └── android_dsp_lib │ │ └── ApplicationTest.java │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── mantz_it │ │ └── android_dsp_lib │ │ ├── AndroidDSPLib.java │ │ ├── BandPassFilter.java │ │ ├── ComplexBandPassFilter.java │ │ ├── FirFilter.java │ │ ├── IQConverter.java │ │ ├── LookupTable_8Bit.java │ │ ├── LowPassFilter.java │ │ ├── Mixer_8Bit.java │ │ ├── QuadratureDemodulator.java │ │ ├── SamplePacket.java │ │ └── WindowFunctions.java │ └── rs │ ├── fir_filter.rs │ ├── lookup_table_8bit.rs │ ├── mixer_8bit.rs │ └── quad_demod.rs ├── androiddsplibbenchmark ├── .gitignore ├── androiddsplibbenchmark.iml ├── build.gradle ├── changelog.txt ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── mantz_it │ │ └── androiddsplibbenchmark │ │ └── ApplicationTest.java │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── mantz_it │ │ └── androiddsplibbenchmark │ │ ├── Benchmark.java │ │ ├── MainActivity.java │ │ └── legacyClasses │ │ ├── ComplexFirFilter.java │ │ ├── FirFilter.java │ │ ├── IQConverter.java │ │ ├── SamplePacket.java │ │ ├── Signed8BitIQConverter.java │ │ └── Unsigned8BitIQConverter.java │ └── res │ ├── drawable-hdpi │ └── ic_launcher.png │ ├── drawable-mdpi │ └── ic_launcher.png │ ├── drawable-xhdpi │ └── ic_launcher.png │ ├── drawable-xxhdpi │ └── ic_launcher.png │ ├── layout │ └── activity_main.xml │ ├── menu │ └── menu_main.xml │ ├── values-v21 │ └── styles.xml │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── benchmark results ├── 1_LGE_Nexus 5_21_etobtbm14kf05vhoiiaumm4i4e.csv ├── 1_motorola_Nexus 6_21_8hj369ee45v6c3mhesk1tt74b4.csv └── 1_samsung_GT-I9305_19_u6ealn0vqbpp7v1pgrqujqfijs.csv ├── build.gradle ├── changelog.txt ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Java class files 2 | *.class 3 | 4 | # generated files 5 | bin/ 6 | gen/ 7 | 8 | # Local configuration file (sdk path, etc) 9 | local.properties 10 | 11 | # Android Studio 12 | *.idea 13 | *.gradle 14 | *.iml 15 | build/ 16 | -------------------------------------------------------------------------------- /Benchmark.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demantz/android_dsp_lib/fac951fe91008043bc2bb8c8377a8687114040c3/Benchmark.apk -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 5 | 51 Franklin Street, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Library General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 19yy 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License 307 | along with this program; if not, write to the Free Software 308 | Foundation, Inc., 51 Franklin Street, Boston, MA 02110-1301 USA 309 | 310 | 311 | Also add information on how to contact you by electronic and paper mail. 312 | 313 | If the program is interactive, make it output a short notice like this 314 | when it starts in an interactive mode: 315 | 316 | Gnomovision version 69, Copyright (C) 19yy name of author 317 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 318 | This is free software, and you are welcome to redistribute it 319 | under certain conditions; type `show c' for details. 320 | 321 | The hypothetical commands `show w' and `show c' should show the appropriate 322 | parts of the General Public License. Of course, the commands you use may 323 | be called something other than `show w' and `show c'; they could even be 324 | mouse-clicks or menu items--whatever suits your program. 325 | 326 | You should also get your employer (if you work as a programmer) or your 327 | school, if any, to sign a "copyright disclaimer" for the program, if 328 | necessary. Here is a sample; alter the names: 329 | 330 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 331 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 332 | 333 | , 1 April 1989 334 | Ty Coon, President of Vice 335 | 336 | This General Public License does not permit incorporating your program into 337 | proprietary programs. If your program is a subroutine library, you may 338 | consider it more useful to permit linking proprietary applications with the 339 | library. If this is what you want to do, use the GNU Library General 340 | Public License instead of this License. 341 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | DSP library for Android 2 | ======================= 3 | 4 | This library contains DSP (digital signal processing) modules for Android 4+. 5 | It uses [RenderScript](http://developer.android.com/guide/topics/renderscript/index.html). 6 | 7 | See [http://tech.mantz-it.com](http://tech.mantz-it.com) and [@dennismantz] 8 | (https://twitter.com/dennismantz) for updates. 9 | 10 | 11 | Implemented Features 12 | -------------------- 13 | * Lookup table for convertion 8-bit interleaved samples to floats 14 | * Lookup table for downmixing 8-bit interleaved samples and converting them to floats 15 | * FIR filter 16 | * Complex FIR filter 17 | 18 | 19 | Testet Devices 20 | -------------- 21 | 22 | This library is in early development right now... 23 | 24 | 25 | License 26 | ------- 27 | This library is free software; you can redistribute it and/or 28 | modify it under the terms of the GNU General Public 29 | License as published by the Free Software Foundation; either 30 | version 2 of the License, or (at your option) any later version. 31 | [http://www.gnu.org/licenses/gpl.html](http://www.gnu.org/licenses/gpl.html) GPL version 2 or higher 32 | 33 | principal author: Dennis Mantz 34 | -------------------------------------------------------------------------------- /android_dsp_lib/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /android_dsp_lib/android_dsp_lib-android_dsp_lib.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /android_dsp_lib/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 21 5 | buildToolsVersion '25.0.0' 6 | 7 | defaultConfig { 8 | minSdkVersion 14 9 | targetSdkVersion 21 10 | versionCode 2 11 | versionName "1.02" 12 | renderscriptTargetApi 18 13 | renderscriptSupportModeEnabled true 14 | } 15 | 16 | buildTypes { 17 | release { 18 | minifyEnabled false 19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 20 | } 21 | } 22 | } 23 | 24 | dependencies { 25 | compile fileTree(dir: 'libs', include: ['*.jar']) 26 | } 27 | -------------------------------------------------------------------------------- /android_dsp_lib/libs/renderscript-v8.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demantz/android_dsp_lib/fac951fe91008043bc2bb8c8377a8687114040c3/android_dsp_lib/libs/renderscript-v8.jar -------------------------------------------------------------------------------- /android_dsp_lib/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 | -------------------------------------------------------------------------------- /android_dsp_lib/src/androidTest/java/com/mantz_it/android_dsp_lib/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.mantz_it.android_dsp_lib; 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 | 14 | @Override 15 | public void setUp() throws Exception { 16 | super.setUp(); 17 | createApplication(); 18 | AndroidDSPLib.init(getContext()); 19 | } 20 | 21 | public void testFillPacketIntoSamplePacket8bitSigned() { 22 | int size = 512; 23 | IQConverter iqConverter = new IQConverter(IQConverter.FORMAT_8BIT_SIGNED, size); 24 | SamplePacket result = new SamplePacket(size/2); 25 | byte[] data = new byte[size]; 26 | for (int i = 0; i < size-1; i+=2) { 27 | data[i] = (byte) (i/2-128); 28 | data[i+1] = data[i]; 29 | } 30 | printArray(data); 31 | iqConverter.fillPacketIntoSamplePacket(data, result); 32 | System.out.println("8-bit signed; Real Result:"); 33 | printArray(result.re()); 34 | System.out.println("8-bit signed; Imag Result:"); 35 | printArray(result.im()); 36 | } 37 | 38 | public void testFillPacketIntoSamplePacket8bitSignedWithOffset() { 39 | int size = 512; 40 | IQConverter iqConverter = new IQConverter(IQConverter.FORMAT_8BIT_SIGNED, size); 41 | SamplePacket result = new SamplePacket(size/2 - 10); // smaller output packet! 42 | result.setSize(10); // start at offset 43 | byte[] data = new byte[size]; 44 | for (int i = 0; i < size-1; i+=2) { 45 | data[i] = (byte) (i/2-128); 46 | data[i+1] = data[i]; 47 | } 48 | printArray(data); 49 | iqConverter.fillPacketIntoSamplePacket(data, result); 50 | System.out.println("8-bit signed; Real Result:"); 51 | printArray(result.re()); 52 | System.out.println("8-bit signed; Imag Result:"); 53 | printArray(result.im()); 54 | } 55 | 56 | public void testFillPacketIntoSamplePacket8bitUnsigned() { 57 | int size = 512; 58 | IQConverter iqConverter = new IQConverter(IQConverter.FORMAT_8BIT_UNSIGNED, size); 59 | SamplePacket result = new SamplePacket(size/2); 60 | byte[] data = new byte[size]; 61 | for (int i = 0; i < size-1; i+=2) { 62 | data[i] = (byte) (i/2); 63 | data[i+1] = data[i]; 64 | } 65 | printUnsignedArray(data); 66 | iqConverter.fillPacketIntoSamplePacket(data, result); 67 | System.out.println("8-bit signed; Real Result:"); 68 | printArray(result.re()); 69 | System.out.println("8-bit signed; Imag Result:"); 70 | printArray(result.im()); 71 | } 72 | 73 | public void testFillPacketIntoSamplePacket8bitUnsignedWithOffset() { 74 | int size = 512; 75 | IQConverter iqConverter = new IQConverter(IQConverter.FORMAT_8BIT_UNSIGNED, size); 76 | SamplePacket result = new SamplePacket(size/2 - 10); // smaller output packet! 77 | result.setSize(10); // start at offset 78 | byte[] data = new byte[size]; 79 | for (int i = 0; i < size-1; i+=2) { 80 | data[i] = (byte) (i/2); 81 | data[i+1] = data[i]; 82 | } 83 | printUnsignedArray(data); 84 | iqConverter.fillPacketIntoSamplePacket(data, result); 85 | System.out.println("8-bit signed; Real Result:"); 86 | printArray(result.re()); 87 | System.out.println("8-bit signed; Imag Result:"); 88 | printArray(result.im()); 89 | } 90 | 91 | public void testMixPacketIntoSamplePacket8bitSigned() { 92 | int size = 10; 93 | IQConverter iqConverter = new IQConverter(IQConverter.FORMAT_8BIT_SIGNED, size); 94 | SamplePacket result = new SamplePacket(size/2); 95 | byte[] data = new byte[size]; 96 | for (int i = 0; i < size; i++) { 97 | data[i] = (byte) (127); 98 | } 99 | iqConverter.setSampleRate(1000000); 100 | iqConverter.setFrequency(1000000); 101 | printArray(data); 102 | iqConverter.mixPacketIntoSamplePacket(data, result, 1250000); 103 | System.out.println("8-bit signed; Real Result [1]:"); 104 | printArray(result.re()); 105 | System.out.println("8-bit signed; Imag Result [1]:"); 106 | printArray(result.im()); 107 | result.setSize(0); 108 | iqConverter.mixPacketIntoSamplePacket(data, result, 1250000); 109 | System.out.println("8-bit signed; Real Result [2]:"); 110 | printArray(result.re()); 111 | System.out.println("8-bit signed; Imag Result [2]:"); 112 | printArray(result.im()); 113 | } 114 | 115 | public void testMixPacketIntoSamplePacket8bitSignedWithOffset() { 116 | int size = 10; 117 | IQConverter iqConverter = new IQConverter(IQConverter.FORMAT_8BIT_SIGNED, size); 118 | SamplePacket result = new SamplePacket(size); // output packet is too big 119 | byte[] data = new byte[size]; 120 | for (int i = 0; i < size; i++) { 121 | data[i] = (byte) (127); 122 | } 123 | iqConverter.setSampleRate(1000000); 124 | iqConverter.setFrequency(1000000); 125 | printArray(data); 126 | iqConverter.mixPacketIntoSamplePacket(data, result, 1250000); 127 | System.out.println("8-bit signed; Real Result [1]:"); 128 | printArray(result.re()); 129 | System.out.println("8-bit signed; Imag Result [1]:"); 130 | printArray(result.im()); 131 | iqConverter.mixPacketIntoSamplePacket(data, result, 1250000); 132 | System.out.println("8-bit signed; Real Result [2]:"); 133 | printArray(result.re()); 134 | System.out.println("8-bit signed; Imag Result [2]:"); 135 | printArray(result.im()); 136 | } 137 | 138 | public void testMixPacketIntoSamplePacket8bitUnsigned() { 139 | int size = 10; 140 | IQConverter iqConverter = new IQConverter(IQConverter.FORMAT_8BIT_UNSIGNED, size); 141 | SamplePacket result = new SamplePacket(size/2); 142 | byte[] data = new byte[size]; 143 | for (int i = 0; i < size; i++) { 144 | data[i] = (byte) (255); 145 | } 146 | iqConverter.setSampleRate(1000000); 147 | iqConverter.setFrequency(1000000); 148 | printUnsignedArray(data); 149 | iqConverter.mixPacketIntoSamplePacket(data, result, 1250000); 150 | System.out.println("8-bit unsigned; Real Result [1]:"); 151 | printArray(result.re()); 152 | System.out.println("8-bit unsigned; Imag Result [1]:"); 153 | printArray(result.im()); 154 | result.setSize(0); 155 | iqConverter.mixPacketIntoSamplePacket(data, result, 1250000); 156 | System.out.println("8-bit unsigned; Real Result [2]:"); 157 | printArray(result.re()); 158 | System.out.println("8-bit unsigned; Imag Result [2]:"); 159 | printArray(result.im()); 160 | } 161 | 162 | public void testMixPacketIntoSamplePacket8bitUnsignedWithOffset() { 163 | int size = 10; 164 | IQConverter iqConverter = new IQConverter(IQConverter.FORMAT_8BIT_UNSIGNED, size); 165 | SamplePacket result = new SamplePacket(size); // output packet is too big 166 | byte[] data = new byte[size]; 167 | for (int i = 0; i < size; i++) { 168 | data[i] = (byte) (255); 169 | } 170 | iqConverter.setSampleRate(1000000); 171 | iqConverter.setFrequency(1000000); 172 | printUnsignedArray(data); 173 | iqConverter.mixPacketIntoSamplePacket(data, result, 1250000); 174 | System.out.println("8-bit unsigned; Real Result [1]:"); 175 | printArray(result.re()); 176 | System.out.println("8-bit unsigned; Imag Result [1]:"); 177 | printArray(result.im()); 178 | iqConverter.mixPacketIntoSamplePacket(data, result, 1250000); 179 | System.out.println("8-bit unsigned; Real Result [2]:"); 180 | printArray(result.re()); 181 | System.out.println("8-bit unsigned; Imag Result [2]:"); 182 | printArray(result.im()); 183 | } 184 | 185 | public void testFirFilter() { 186 | float[] expectedResultReal = {-0.100000f, -0.345000f, -0.827500f, -1.035000f, -1.080000f, -1.020000f, -0.960000f, -0.900000f, -0.840000f, -0.780000f, -0.720000f, -0.660000f, -0.600000f, -0.540000f, -0.480000f, -0.420000f, -0.360000f, -0.300000f, -0.240000f, -0.180000f, -0.120000f, -0.060000f, 0.000000f, 0.060000f, 0.120000f, 0.180000f, 0.240000f, 0.300000f, 0.360000f, 0.420000f, 0.480000f, 0.540000f, 0.600000f, 0.660000f, 0.720000f, 0.780000f, 0.840000f, 0.900000f, 0.960000f, 1.020000f, 1.080000f}; 187 | float[] expectedResultImag = {0.100000f, 0.345000f, 0.827500f, 1.035000f, 1.080000f, 1.020000f, 0.960000f, 0.900000f, 0.840000f, 0.780000f, 0.720000f, 0.660000f, 0.600000f, 0.540000f, 0.480000f, 0.420000f, 0.360000f, 0.300000f, 0.240000f, 0.180000f, 0.120000f, 0.060000f, -0.000000f, -0.060000f, -0.120000f, -0.180000f, -0.240000f, -0.300000f, -0.360000f, -0.420000f, -0.480000f, -0.540000f, -0.600000f, -0.660000f, -0.720000f, -0.780000f, -0.840000f, -0.900000f, -0.960000f, -1.020000f, -1.080000f}; 188 | float[] taps = {0.1f, 0.25f, 0.5f, 0.25f, 0.1f}; 189 | float[] inputReal = {-1.000000f, -0.950000f, -0.900000f, -0.850000f, -0.800000f, -0.750000f, -0.700000f, -0.650000f, -0.600000f, -0.550000f, -0.500000f, -0.450000f, -0.400000f, -0.350000f, -0.300000f, -0.250000f, -0.200000f, -0.150000f, -0.100000f, -0.050000f, 0.000000f, 0.050000f, 0.100000f, 0.150000f, 0.200000f, 0.250000f, 0.300000f, 0.350000f, 0.400000f, 0.450000f, 0.500000f, 0.550000f, 0.600000f, 0.650000f, 0.700000f, 0.750000f, 0.800000f, 0.850000f, 0.900000f, 0.950000f, 1.000000f}; 190 | float[] inputImag = {1.000000f, 0.950000f, 0.900000f, 0.850000f, 0.800000f, 0.750000f, 0.700000f, 0.650000f, 0.600000f, 0.550000f, 0.500000f, 0.450000f, 0.400000f, 0.350000f, 0.300000f, 0.250000f, 0.200000f, 0.150000f, 0.100000f, 0.050000f, 0.000000f, -0.050000f, -0.100000f, -0.150000f, -0.200000f, -0.250000f, -0.300000f, -0.350000f, -0.400000f, -0.450000f, -0.500000f, -0.550000f, -0.600000f, -0.650000f, -0.700000f, -0.750000f, -0.800000f, -0.850000f, -0.900000f, -0.950000f, -1.000000f}; 191 | SamplePacket in = new SamplePacket(inputReal, inputImag, 0, 1000000); 192 | FirFilter firFilter = new FirFilter(taps, null, 1); 193 | 194 | // Filter the first 20 samples: 195 | SamplePacket out = new SamplePacket(inputReal, inputImag, 0, 0); 196 | out.setSize(0); 197 | firFilter.filterComplexSignal(in, out, 0, 20); 198 | assertEquals(20, out.size()); 199 | assertEquals(in.getSampleRate(), out.getSampleRate()); 200 | float[] resultReal = out.re(); 201 | float[] resultImag = out.im(); 202 | for(int i = 0; i < 20; i++) { 203 | assert(floatEquals(expectedResultReal[i], resultReal[i])); 204 | assert(floatEquals(expectedResultImag[i], resultImag[i])); 205 | } 206 | for(int i = 20; i < out.capacity(); i++) { 207 | assert(floatEquals(inputReal[i], resultReal[i])); 208 | assert(floatEquals(inputImag[i], resultImag[i])); 209 | } 210 | 211 | // Filter the next 21 samples: 212 | out = new SamplePacket(21); 213 | firFilter.filterComplexSignal(in, out, 20, in.size()-20); 214 | assertEquals(21, out.size()); 215 | resultReal = out.re(); 216 | resultImag = out.im(); 217 | for(int i = 0; i < 21; i++) { 218 | assert(floatEquals(expectedResultReal[i+20], resultReal[i])); 219 | assert(floatEquals(expectedResultImag[i+20], resultImag[i])); 220 | } 221 | } 222 | 223 | public void testFirFilterWithRealSignals() { 224 | float[] expectedResultReal = {-0.100000f, -0.345000f, -0.827500f, -1.035000f, -1.080000f, -1.020000f, -0.960000f, -0.900000f, -0.840000f, -0.780000f, -0.720000f, -0.660000f, -0.600000f, -0.540000f, -0.480000f, -0.420000f, -0.360000f, -0.300000f, -0.240000f, -0.180000f, -0.120000f, -0.060000f, 0.000000f, 0.060000f, 0.120000f, 0.180000f, 0.240000f, 0.300000f, 0.360000f, 0.420000f, 0.480000f, 0.540000f, 0.600000f, 0.660000f, 0.720000f, 0.780000f, 0.840000f, 0.900000f, 0.960000f, 1.020000f, 1.080000f}; 225 | float[] taps = {0.1f, 0.25f, 0.5f, 0.25f, 0.1f}; 226 | float[] inputReal1 = {-1.000000f, -0.950000f, -0.900000f, -0.850000f, -0.800000f, -0.750000f, -0.700000f, -0.650000f, -0.600000f, -0.550000f, -0.500000f, -0.450000f, -0.400000f, -0.350000f, -0.300000f, -0.250000f, -0.200000f, -0.150000f, -0.100000f, -0.050000f}; 227 | float[] inputReal2 = {0.000000f, 0.050000f, 0.100000f, 0.150000f, 0.200000f, 0.250000f, 0.300000f, 0.350000f, 0.400000f, 0.450000f, 0.500000f, 0.550000f, 0.600000f, 0.650000f, 0.700000f, 0.750000f, 0.800000f, 0.850000f, 0.900000f, 0.950000f, 1.000000f}; 228 | FirFilter firFilter = new FirFilter(taps, null, 1); 229 | 230 | // Filter the first 20 samples: 231 | SamplePacket in = new SamplePacket(20); 232 | in.getReAlloc().copyFrom(inputReal1); 233 | in.setSize(20); 234 | SamplePacket out = new SamplePacket(41); 235 | out.setSize(0); 236 | firFilter.filterRealSignal(in, out, 0, in.size()); 237 | assertEquals(20, out.size()); 238 | float[] resultReal = out.re(); 239 | for(int i = 0; i < 20; i++) { 240 | assert(floatEquals(expectedResultReal[i], resultReal[i])); 241 | } 242 | 243 | // Filter the next 20 samples: 244 | in.getReAlloc().copy1DRangeFrom(0, 20, inputReal2); 245 | firFilter.filterRealSignal(in, out, 0, in.size()); 246 | assertEquals(40, out.size()); 247 | resultReal = out.re(); 248 | for(int i = 0; i < 20; i++) { 249 | assert(floatEquals(expectedResultReal[i+20], resultReal[i])); 250 | } 251 | } 252 | 253 | public void testFirFilterWithComplexTaps() { 254 | float[] expectedResultReal = {-0.190000f, -0.512500f, -1.055000f, -1.330000f, -1.445000f, -1.360000f, -1.275000f, -1.190000f, -1.105000f, -1.020000f, -0.935000f, -0.850000f, -0.765000f, -0.680000f, -0.595000f, -0.510000f, -0.425000f, -0.340000f, -0.255000f, -0.170000f, -0.085000f, 0.000000f, 0.085000f, 0.170000f, 0.255000f, 0.340000f, 0.425000f, 0.510000f, 0.595000f, 0.680000f, 0.765000f, 0.850000f, 0.935000f, 1.020000f, 1.105000f, 1.190000f, 1.275000f, 1.360000f, 1.445000f, 1.530000f, 1.615000f}; 255 | float[] expectedResultImag = {0.000000f, 0.142500f, 0.515000f, 0.630000f, 0.595000f, 0.560000f, 0.525000f, 0.490000f, 0.455000f, 0.420000f, 0.385000f, 0.350000f, 0.315000f, 0.280000f, 0.245000f, 0.210000f, 0.175000f, 0.140000f, 0.105000f, 0.070000f, 0.035000f, -0.000000f, -0.035000f, -0.070000f, -0.105000f, -0.140000f, -0.175000f, -0.210000f, -0.245000f, -0.280000f, -0.315000f, -0.350000f, -0.385000f, -0.420000f, -0.455000f, -0.490000f, -0.525000f, -0.560000f, -0.595000f, -0.630000f, -0.665000f}; 256 | float[] tapsReal = {0.1f, 0.25f, 0.5f, 0.25f, 0.1f}; 257 | float[] tapsImag = {0.1f, 0.1f, 0.1f, 0.1f, 0.1f}; 258 | float[] inputReal = {-0.950000f, -0.900000f, -0.850000f, -0.800000f, -0.750000f, -0.700000f, -0.650000f, -0.600000f, -0.550000f, -0.500000f, -0.450000f, -0.400000f, -0.350000f, -0.300000f, -0.250000f, -0.200000f, -0.150000f, -0.100000f, -0.050000f, 0.000000f, 0.050000f, 0.100000f, 0.150000f, 0.200000f, 0.250000f, 0.300000f, 0.350000f, 0.400000f, 0.450000f, 0.500000f, 0.550000f, 0.600000f, 0.650000f, 0.700000f, 0.750000f, 0.800000f, 0.850000f, 0.900000f, 0.950000f, 1.000000f, 1.050000f}; 259 | float[] inputImag = {0.950000f, 0.900000f, 0.850000f, 0.800000f, 0.750000f, 0.700000f, 0.650000f, 0.600000f, 0.550000f, 0.500000f, 0.450000f, 0.400000f, 0.350000f, 0.300000f, 0.250000f, 0.200000f, 0.150000f, 0.100000f, 0.050000f, 0.000000f, -0.050000f, -0.100000f, -0.150000f, -0.200000f, -0.250000f, -0.300000f, -0.350000f, -0.400000f, -0.450000f, -0.500000f, -0.550000f, -0.600000f, -0.650000f, -0.700000f, -0.750000f, -0.800000f, -0.850000f, -0.900000f, -0.950000f, -1.000000f, -1.050000f}; 260 | SamplePacket in = new SamplePacket(inputReal, inputImag, 0, 1000000); 261 | FirFilter firFilter = new FirFilter(tapsReal, tapsImag, 1); 262 | 263 | // Filter the first 20 samples: 264 | SamplePacket out = new SamplePacket(inputReal, inputImag, 0, 0); 265 | out.setSize(0); 266 | firFilter.filterComplexTaps(in, out, 0, 20); 267 | assertEquals(20, out.size()); 268 | assertEquals(in.getSampleRate(), out.getSampleRate()); 269 | float[] resultReal = out.re(); 270 | float[] resultImag = out.im(); 271 | for(int i = 0; i < 20; i++) { 272 | assert(floatEquals(expectedResultReal[i], resultReal[i])); 273 | assert(floatEquals(expectedResultImag[i], resultImag[i])); 274 | } 275 | for(int i = 20; i < out.capacity(); i++) { 276 | assert(floatEquals(inputReal[i], resultReal[i])); 277 | assert(floatEquals(inputImag[i], resultImag[i])); 278 | } 279 | 280 | // Filter the next 20 samples: 281 | out = new SamplePacket(20); 282 | firFilter.filterComplexTaps(in, out, 20, in.size()-20); 283 | assertEquals(20, out.size()); 284 | resultReal = out.re(); 285 | resultImag = out.im(); 286 | for(int i = 0; i < 20; i++) { 287 | assert(floatEquals(expectedResultReal[i+20], resultReal[i])); 288 | assert(floatEquals(expectedResultImag[i+20], resultImag[i])); 289 | } 290 | } 291 | 292 | public void printArray(byte[] array) { 293 | System.out.print("["); 294 | for (int i = 0; i < array.length; i++) { 295 | System.out.print(" " + array[i]); 296 | } 297 | System.out.println("]"); 298 | } 299 | 300 | public void printUnsignedArray(byte[] array) { 301 | System.out.print("["); 302 | for (int i = 0; i < array.length; i++) { 303 | System.out.print(" " + (array[i] & 0xff)); 304 | } 305 | System.out.println("]"); 306 | } 307 | 308 | public void printArray(float[] array) { 309 | System.out.print("["); 310 | for (int i = 0; i < array.length; i++) { 311 | System.out.print(" " + array[i]); 312 | } 313 | System.out.println("]"); 314 | } 315 | 316 | public boolean floatEquals(float expected, float actual) { 317 | return actual < expected+0.0001 && actual > expected-0.0001; 318 | } 319 | } -------------------------------------------------------------------------------- /android_dsp_lib/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /android_dsp_lib/src/main/java/com/mantz_it/android_dsp_lib/AndroidDSPLib.java: -------------------------------------------------------------------------------- 1 | package com.mantz_it.android_dsp_lib; 2 | 3 | import android.content.Context; 4 | import android.content.res.Resources; 5 | import android.support.v8.renderscript.RenderScript; 6 | import android.util.Log; 7 | 8 | /** 9 | * Android DSP library - Android DSP Library Singleton 10 | * 11 | * Module: AndroidDSPLib.java 12 | * Description: This class is a Singleton holding references to the RenderScript and Resources 13 | * Whenever this library is used. The init() function of this class has to be called first! 14 | * 15 | * @author Dennis Mantz 16 | * 17 | * Copyright (C) 2014 Dennis Mantz 18 | * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher 19 | * 20 | * This library is free software; you can redistribute it and/or 21 | * modify it under the terms of the GNU General Public 22 | * License as published by the Free Software Foundation; either 23 | * version 2 of the License, or (at your option) any later version. 24 | * 25 | * This library is distributed in the hope that it will be useful, 26 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 27 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 28 | * General Public License for more details. 29 | * 30 | * You should have received a copy of the GNU General Public 31 | * License along with this library; if not, write to the Free Software 32 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 33 | */ 34 | public class AndroidDSPLib { 35 | private static Resources resources = null; 36 | private static RenderScript renderScript = null; 37 | private static final String LOGTAG = "ResSingleton"; 38 | 39 | public static void init(Resources res, RenderScript rs) { 40 | resources = res; 41 | renderScript = rs; 42 | } 43 | 44 | public static void init(Context context) { 45 | resources = context.getResources(); 46 | renderScript = RenderScript.create(context); 47 | } 48 | 49 | public static Resources getResources() { 50 | if(resources == null) 51 | Log.e(LOGTAG, "getResources: resources is null!"); 52 | return resources; 53 | } 54 | 55 | public static RenderScript getRenderScript() { 56 | if(renderScript == null) 57 | Log.e(LOGTAG, "getResources: renderScript is null!"); 58 | return renderScript; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /android_dsp_lib/src/main/java/com/mantz_it/android_dsp_lib/BandPassFilter.java: -------------------------------------------------------------------------------- 1 | package com.mantz_it.android_dsp_lib; 2 | 3 | /** 4 | * Android DSP library - Band Pass Filter 5 | * 6 | * Module: BandPassFilter.java 7 | * Description: This class extends the FirFilter class to create a convenient band pass filter with real taps 8 | * 9 | * @author Dennis Mantz 10 | * 11 | * Copyright (C) 2014 Dennis Mantz 12 | * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher 13 | * 14 | * This library is free software; you can redistribute it and/or 15 | * modify it under the terms of the GNU General Public 16 | * License as published by the Free Software Foundation; either 17 | * version 2 of the License, or (at your option) any later version. 18 | * 19 | * This library is distributed in the hope that it will be useful, 20 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 22 | * General Public License for more details. 23 | * 24 | * You should have received a copy of the GNU General Public 25 | * License along with this library; if not, write to the Free Software 26 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 27 | */ 28 | public class BandPassFilter extends FirFilter { 29 | private float gain; 30 | private float sampleRate; 31 | private float lowCutOffFrequency; 32 | private float highCutOffFrequency; 33 | private float transitionWidth; 34 | private float attenuation; 35 | 36 | /** 37 | * Constructor. Creates a new FIR Filter with the given parameters and decimation. 38 | * @param decimation decimation factor 39 | * @param gain filter pass band gain 40 | * @param sampleRate sample rate 41 | * @param lowCutOffFrequency lower cut off frequency (start of pass band) 42 | * @param highCutOffFrequency upper cut off frequency (end of pass band) 43 | * @param transitionWidth width from end of pass band to start stop band 44 | * @param attenuation attenuation of stop band 45 | */ 46 | public BandPassFilter(int decimation, float gain, float sampleRate, float lowCutOffFrequency, 47 | float highCutOffFrequency, float transitionWidth, float attenuation) { 48 | super(designBandPassFilter(gain, sampleRate, lowCutOffFrequency, highCutOffFrequency, transitionWidth, attenuation), null, decimation); 49 | this.gain = gain; 50 | this.sampleRate = sampleRate; 51 | this.lowCutOffFrequency = lowCutOffFrequency; 52 | this.highCutOffFrequency = highCutOffFrequency; 53 | this.transitionWidth = transitionWidth; 54 | this.attenuation = attenuation; 55 | } 56 | 57 | public float getGain() { 58 | return gain; 59 | } 60 | 61 | public float getSampleRate() { 62 | return sampleRate; 63 | } 64 | 65 | public float getLowCutOffFrequency() { 66 | return lowCutOffFrequency; 67 | } 68 | 69 | public float getHighCutOffFrequency() { 70 | return highCutOffFrequency; 71 | } 72 | 73 | public float getTransitionWidth() { 74 | return transitionWidth; 75 | } 76 | 77 | public float getAttenuation() { 78 | return attenuation; 79 | } 80 | 81 | public int filter(SamplePacket in, SamplePacket out, int offset, int length) { 82 | return super.filterComplexSignal(in,out,offset,length); 83 | } 84 | 85 | public int filterReal(SamplePacket in, SamplePacket out, int offset, int length) { 86 | return super.filterRealSignal(in, out, offset, length); 87 | } 88 | 89 | /** 90 | * FROM GNU Radio firdes::band_pass_2: 91 | * Will calculate the tabs for the specified band pass filter 92 | * 93 | * @param gain filter pass band gain 94 | * @param sampleRate sample rate 95 | * @param lowCutOffFrequency cut off frequency (beginning of pass band) 96 | * @param highCutOffFrequency cut off frequency (end of pass band) 97 | * @param transitionWidth width from end of pass band to start stop band 98 | * @param attenuation attenuation of stop band 99 | * @return float[] array containing the filter taps 100 | */ 101 | public static float[] designBandPassFilter(float gain, 102 | float sampleRate, // Hz 103 | float lowCutOffFrequency, // Hz BEGINNING of transition band 104 | float highCutOffFrequency, // Hz END of transition band 105 | float transitionWidth, // Hz width of transition band 106 | float attenuation) // attenuation dB 107 | { 108 | if (sampleRate <= 0.0) { 109 | throw new IllegalArgumentException("firdes check failed: sampling_freq > 0"); 110 | } 111 | 112 | if (lowCutOffFrequency <= 0 || lowCutOffFrequency > sampleRate * 0.5) { 113 | throw new IllegalArgumentException("firdes check failed: 0 < lowCutOffFrequency <= sampling_freq / 2"); 114 | } 115 | 116 | if (highCutOffFrequency <= 0 || highCutOffFrequency > sampleRate * 0.5) { 117 | throw new IllegalArgumentException("firdes check failed: 0 < highCutOffFrequency <= sampling_freq / 2"); 118 | } 119 | 120 | if (lowCutOffFrequency >= highCutOffFrequency) { 121 | throw new IllegalArgumentException("firdes check failed: low_cutoff_freq >= high_cutoff_freq"); 122 | } 123 | 124 | if (transitionWidth <= 0) { 125 | throw new IllegalArgumentException("firdes check failed: transition_width > 0"); 126 | } 127 | 128 | // Calculate number of tabs 129 | // Based on formula from Multirate Signal Processing for 130 | // Communications Systems, fredric j harris 131 | int ntaps = (int)(attenuation*sampleRate/(22.0*transitionWidth)); 132 | if ((ntaps & 1) == 0) // if even... 133 | ntaps++; // ...make odd 134 | 135 | 136 | float[] taps = new float[ntaps]; 137 | float[] w = WindowFunctions.makeBlackmanWindow(ntaps); 138 | 139 | int M = (ntaps - 1) / 2; 140 | float fwT0 = 2 * (float)Math.PI * lowCutOffFrequency / sampleRate; 141 | float fwT1 = 2 * (float)Math.PI * highCutOffFrequency / sampleRate; 142 | for (int n = -M; n <= M; n++) { 143 | if (n == 0) 144 | taps[n + M] = (fwT1 - fwT0) / (float)Math.PI * w[n + M]; 145 | else { 146 | taps[n + M] = taps[n + M] = ((float)Math.sin(n * fwT1) - (float)Math.sin(n * fwT0)) / (n * (float)Math.PI) * w[n + M]; 147 | } 148 | } 149 | 150 | // find the factor to normalize the gain, fmax. 151 | // For band-pass, gain @ zero freq = 1.0 152 | float fmax = taps[0 + M]; 153 | for (int n = 1; n <= M; n++) 154 | fmax += 2 * taps[n + M] * (float)Math.cos(n * (fwT0 + fwT1) * 0.5); 155 | float actualGain = gain/fmax; // normalize 156 | for (int i = 0; i < ntaps; i++) 157 | taps[i] *= actualGain; 158 | 159 | return taps; 160 | } 161 | 162 | } 163 | -------------------------------------------------------------------------------- /android_dsp_lib/src/main/java/com/mantz_it/android_dsp_lib/ComplexBandPassFilter.java: -------------------------------------------------------------------------------- 1 | package com.mantz_it.android_dsp_lib; 2 | 3 | /** 4 | * Android DSP library - Complex Band Pass Filter 5 | * 6 | * Module: ComplexBandPassFilter.java 7 | * Description: This class extends the FirFilter class to create a convenient band pass filter with complex taps 8 | * 9 | * @author Dennis Mantz 10 | * 11 | * Copyright (C) 2014 Dennis Mantz 12 | * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher 13 | * 14 | * This library is free software; you can redistribute it and/or 15 | * modify it under the terms of the GNU General Public 16 | * License as published by the Free Software Foundation; either 17 | * version 2 of the License, or (at your option) any later version. 18 | * 19 | * This library is distributed in the hope that it will be useful, 20 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 22 | * General Public License for more details. 23 | * 24 | * You should have received a copy of the GNU General Public 25 | * License along with this library; if not, write to the Free Software 26 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 27 | */ 28 | public class ComplexBandPassFilter extends FirFilter { 29 | private float gain; 30 | private float sampleRate; 31 | private float lowCutOffFrequency; 32 | private float highCutOffFrequency; 33 | private float transitionWidth; 34 | private float attenuation; 35 | 36 | /** 37 | * Constructor. Creates a new complex FIR Filter with the given parameters and decimation. 38 | * @param decimation decimation factor 39 | * @param gain filter pass band gain 40 | * @param sampleRate sample rate 41 | * @param lowCutOffFrequency lower cut off frequency (start of pass band) 42 | * @param highCutOffFrequency upper cut off frequency (end of pass band) 43 | * @param transitionWidth width from end of pass band to start stop band 44 | * @param attenuation attenuation of stop band 45 | */ 46 | public ComplexBandPassFilter(int decimation, float gain, float sampleRate, float lowCutOffFrequency, 47 | float highCutOffFrequency, float transitionWidth, float attenuation) { 48 | super(designComplexBandPassFilter(gain, sampleRate, lowCutOffFrequency, highCutOffFrequency, transitionWidth, attenuation), decimation); 49 | this.gain = gain; 50 | this.sampleRate = sampleRate; 51 | this.lowCutOffFrequency = lowCutOffFrequency; 52 | this.highCutOffFrequency = highCutOffFrequency; 53 | this.transitionWidth = transitionWidth; 54 | this.attenuation = attenuation; 55 | } 56 | 57 | public float getGain() { 58 | return gain; 59 | } 60 | 61 | public float getSampleRate() { 62 | return sampleRate; 63 | } 64 | 65 | public float getLowCutOffFrequency() { 66 | return lowCutOffFrequency; 67 | } 68 | 69 | public float getHighCutOffFrequency() { 70 | return highCutOffFrequency; 71 | } 72 | 73 | public float getTransitionWidth() { 74 | return transitionWidth; 75 | } 76 | 77 | public float getAttenuation() { 78 | return attenuation; 79 | } 80 | 81 | public int filter(SamplePacket in, SamplePacket out, int offset, int length) { 82 | return super.filterComplexTaps(in,out,offset,length); 83 | } 84 | 85 | /** 86 | * FROM GNU Radio firdes::band_pass_2: 87 | * Will calculate the tabs for the specified complex band pass filter 88 | * 89 | * @param gain filter pass band gain 90 | * @param sampleRate sample rate 91 | * @param lowCutOffFrequency cut off frequency (beginning of pass band) 92 | * @param highCutOffFrequency cut off frequency (end of pass band) 93 | * @param transitionWidth width from end of pass band to start stop band 94 | * @param attenuation attenuation of stop band 95 | * @return float[][] array containing the filter taps: ret[0] are the real taps, ret[1] the imaginary taps 96 | */ 97 | public static float[][] designComplexBandPassFilter(float gain, 98 | float sampleRate, // Hz 99 | float lowCutOffFrequency, // Hz BEGINNING of transition band 100 | float highCutOffFrequency, // Hz END of transition band 101 | float transitionWidth, // Hz width of transition band 102 | float attenuation) // attenuation dB 103 | { 104 | if (sampleRate <= 0.0) { 105 | throw new IllegalArgumentException("firdes check failed: sampling_freq > 0"); 106 | } 107 | 108 | if (lowCutOffFrequency < sampleRate * -0.5 || highCutOffFrequency > sampleRate * 0.5) { 109 | throw new IllegalArgumentException("firdes check failed: -sampling_freq / 2 < fa <= sampling_freq / 2"); 110 | } 111 | 112 | if (lowCutOffFrequency >= highCutOffFrequency) { 113 | throw new IllegalArgumentException("firdes check failed: low_cutoff_freq >= high_cutoff_freq"); 114 | } 115 | 116 | if (transitionWidth <= 0) { 117 | throw new IllegalArgumentException("firdes check failed: transition_width > 0"); 118 | } 119 | 120 | // Calculate number of tabs 121 | // Based on formula from Multirate Signal Processing for 122 | // Communications Systems, fredric j harris 123 | int ntaps = (int)(attenuation*sampleRate/(22.0*transitionWidth)); 124 | if ((ntaps & 1) == 0) // if even... 125 | ntaps++; // ...make odd 126 | 127 | // construct the truncated ideal impulse response 128 | // [sin(x)/x for the low pass case] 129 | // Note: we calculate the real taps for a low pass and shift them 130 | float low_pass_cut_off = (highCutOffFrequency - lowCutOffFrequency)/2f; 131 | float[] tapsLowPass = new float[ntaps]; 132 | float[] w = WindowFunctions.makeBlackmanWindow(ntaps); 133 | 134 | int M = (ntaps - 1) / 2; 135 | float fwT0 = 2 * (float)Math.PI * low_pass_cut_off / sampleRate; 136 | for (int n = -M; n <= M; n++) { 137 | if (n == 0) 138 | tapsLowPass[n + M] = fwT0 / (float)Math.PI * w[n + M]; 139 | else { 140 | // a little algebra gets this into the more familiar sin(x)/x form 141 | tapsLowPass[n + M] = (float)Math.sin(n * fwT0) / (n * (float)Math.PI) * w[n + M]; 142 | } 143 | } 144 | 145 | // find the factor to normalize the gain, fmax. 146 | // For low-pass, gain @ zero freq = 1.0 147 | float fmax = tapsLowPass[0 + M]; 148 | for (int n = 1; n <= M; n++) 149 | fmax += 2 * tapsLowPass[n + M]; 150 | float actualGain = gain/fmax; // normalize 151 | for (int i = 0; i < ntaps; i++) 152 | tapsLowPass[i] *= actualGain; 153 | 154 | // calc the band pass taps: 155 | float[][] taps = new float[2][ntaps]; 156 | float freq = (float)Math.PI * (highCutOffFrequency + lowCutOffFrequency)/sampleRate; 157 | float phase = - freq * ( ntaps/2 ); 158 | 159 | for(int i = 0; i < ntaps; i++) { 160 | taps[0][i] = tapsLowPass[i] * (float)Math.cos(phase); 161 | taps[1][i] = tapsLowPass[i] * (float)Math.sin(phase); 162 | phase += freq; 163 | } 164 | 165 | return taps; 166 | } 167 | 168 | } 169 | -------------------------------------------------------------------------------- /android_dsp_lib/src/main/java/com/mantz_it/android_dsp_lib/FirFilter.java: -------------------------------------------------------------------------------- 1 | package com.mantz_it.android_dsp_lib; 2 | 3 | import android.support.v8.renderscript.Allocation; 4 | import android.support.v8.renderscript.Element; 5 | import android.support.v8.renderscript.RenderScript; 6 | 7 | /** 8 | * Android DSP library - FIR Filter 9 | * 10 | * Module: FirFilter.java 11 | * Description: This class implements a FIR filter with real taps 12 | * 13 | * @author Dennis Mantz 14 | * 15 | * Copyright (C) 2014 Dennis Mantz 16 | * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher 17 | * 18 | * This library is free software; you can redistribute it and/or 19 | * modify it under the terms of the GNU General Public 20 | * License as published by the Free Software Foundation; either 21 | * version 2 of the License, or (at your option) any later version. 22 | * 23 | * This library is distributed in the hope that it will be useful, 24 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 25 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 26 | * General Public License for more details. 27 | * 28 | * You should have received a copy of the GNU General Public 29 | * License along with this library; if not, write to the Free Software 30 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 31 | */ 32 | public class FirFilter { 33 | private RenderScript rs; 34 | private ScriptC_fir_filter script; 35 | private float[] tapsReal; 36 | private float[] tapsImag; 37 | private Allocation tapsRealAlloc; 38 | private Allocation tapsImagAlloc; 39 | private Allocation remainderRealAlloc; 40 | private Allocation remainderImagAlloc; 41 | private int decimation; 42 | 43 | public FirFilter(float[][] taps, int decimation) { 44 | this(taps[0], taps[1], decimation); 45 | } 46 | 47 | public FirFilter(float[] tapsReal, float[] tapsImag, int decimation) { 48 | if(tapsReal == null) 49 | throw new NullPointerException("real taps cannot be null!"); 50 | 51 | this.rs = AndroidDSPLib.getRenderScript(); 52 | this.tapsReal = tapsReal; 53 | this.tapsImag = tapsImag; 54 | this.decimation = decimation; 55 | this.tapsRealAlloc = Allocation.createSized(rs, Element.F32(rs), tapsReal.length); 56 | this.tapsRealAlloc.copyFrom(tapsReal); 57 | if(tapsImag != null) { 58 | if(tapsReal.length != tapsImag.length) 59 | throw new IllegalArgumentException("real taps and imaginary taps have to be of the same length!"); 60 | this.tapsImagAlloc = Allocation.createSized(rs, Element.F32(rs), tapsImag.length); 61 | this.tapsImagAlloc.copyFrom(tapsImag); 62 | } 63 | this.remainderRealAlloc = Allocation.createSized(rs, Element.F32(rs), tapsReal.length - 1); 64 | this.remainderImagAlloc = Allocation.createSized(rs, Element.F32(rs), tapsReal.length - 1); 65 | 66 | script = new ScriptC_fir_filter(rs, AndroidDSPLib.getResources(), R.raw.fir_filter); 67 | script.set_filterOrder(tapsReal.length); 68 | script.set_decimation(decimation); 69 | script.bind_tapsReal(tapsRealAlloc); 70 | if(tapsImag != null) 71 | script.bind_tapsImag(tapsImagAlloc); 72 | script.bind_remainderReal(remainderRealAlloc); 73 | script.bind_remainderImag(remainderImagAlloc); 74 | script.invoke_clearRemainders(); 75 | } 76 | 77 | public int getDecimation() { 78 | return decimation; 79 | } 80 | 81 | public int getNumberOfTaps() { 82 | return tapsReal.length; 83 | } 84 | 85 | /** 86 | * Filters the complex samples from the input sample packet with real taps and appends filter output to the output 87 | * sample packet. Stops automatically if output sample packet is full. 88 | * @param in input sample packet 89 | * @param out output sample packet 90 | * @param offset offset to use as start index for the input packet 91 | * @param length max number of samples processed from the input packet (must be multiple of decimation) 92 | * @return number of samples consumed from the input packet 93 | */ 94 | public int filterComplexSignal(SamplePacket in, SamplePacket out, int offset, int length) { 95 | int outSize = out.size(); 96 | int outputLength = Math.min(outSize + (length / decimation), out.capacity()); 97 | script.set_offsetIn(offset); 98 | script.set_offsetOut(outSize); 99 | script.set_len(outputLength); 100 | script.set_inReal(in.getReAlloc()); 101 | script.set_inImag(in.getImAlloc()); 102 | script.set_outReal(out.getReAlloc()); 103 | script.set_outImag(out.getImAlloc()); 104 | script.forEach_filterAndDecimate(out.getReAlloc()); 105 | out.setSize(outputLength); 106 | out.setSampleRate(in.getSampleRate()/decimation); 107 | out.getReAlloc().syncAll(Allocation.USAGE_SCRIPT); 108 | script.invoke_updateRemainders(); 109 | return (outputLength-outSize) * decimation; 110 | } 111 | 112 | /** 113 | * Filters the real samples from the input sample packet with real taps and appends filter output to the output 114 | * sample packet. Stops automatically if output sample packet is full. 115 | * @param in input sample packet 116 | * @param out output sample packet 117 | * @param offset offset to use as start index for the input packet 118 | * @param length max number of samples processed from the input packet (must be multiple of decimation) 119 | * @return number of samples consumed from the input packet 120 | */ 121 | public int filterRealSignal(SamplePacket in, SamplePacket out, int offset, int length) { 122 | int outSize = out.size(); 123 | int outputLength = Math.min(outSize + (length / decimation), out.capacity()); 124 | script.set_offsetIn(offset); 125 | script.set_offsetOut(outSize); 126 | script.set_len(outputLength); 127 | script.set_inReal(in.getReAlloc()); 128 | script.set_outReal(out.getReAlloc()); 129 | script.forEach_filterRealSignalAndDecimate(out.getReAlloc()); 130 | out.setSize(outputLength); 131 | out.setSampleRate(in.getSampleRate()/decimation); 132 | out.getReAlloc().syncAll(Allocation.USAGE_SCRIPT); 133 | script.invoke_updateRealRemainders(); 134 | return (outputLength-outSize) * decimation; 135 | } 136 | 137 | /** 138 | * Filters the complex samples from the input sample packet with complex taps and appends filter output to the output 139 | * sample packet. Stops automatically if output sample packet is full. 140 | * @param in input sample packet 141 | * @param out output sample packet 142 | * @param offset offset to use as start index for the input packet 143 | * @param length max number of samples processed from the input packet (must be multiple of decimation) 144 | * @return number of samples consumed from the input packet 145 | */ 146 | public int filterComplexTaps(SamplePacket in, SamplePacket out, int offset, int length) { 147 | int outSize = out.size(); 148 | int outputLength = Math.min(outSize + (length / decimation), out.capacity()); 149 | script.set_offsetIn(offset); 150 | script.set_offsetOut(outSize); 151 | script.set_len(outputLength); 152 | script.set_inReal(in.getReAlloc()); 153 | script.set_inImag(in.getImAlloc()); 154 | script.set_outReal(out.getReAlloc()); 155 | script.set_outImag(out.getImAlloc()); 156 | script.forEach_filterComplexTapsAndDecimate(out.getReAlloc()); 157 | out.setSize(outputLength); 158 | out.setSampleRate(in.getSampleRate()/decimation); 159 | out.getReAlloc().syncAll(Allocation.USAGE_SCRIPT); 160 | script.invoke_updateRemainders(); 161 | return (outputLength-outSize) * decimation; 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /android_dsp_lib/src/main/java/com/mantz_it/android_dsp_lib/IQConverter.java: -------------------------------------------------------------------------------- 1 | package com.mantz_it.android_dsp_lib; 2 | 3 | import android.util.Log; 4 | 5 | /** 6 | * Android DSP library - IQ Converter 7 | * 8 | * Module: IQConverter.java 9 | * Description: This class can fill raw interleaved IQ arrays into SamplePackets by using a lookup table. 10 | * It also can perform down-mixing and convertion in one step 11 | * 12 | * @author Dennis Mantz 13 | * 14 | * Copyright (C) 2014 Dennis Mantz 15 | * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher 16 | * 17 | * This library is free software; you can redistribute it and/or 18 | * modify it under the terms of the GNU General Public 19 | * License as published by the Free Software Foundation; either 20 | * version 2 of the License, or (at your option) any later version. 21 | * 22 | * This library is distributed in the hope that it will be useful, 23 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 24 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 25 | * General Public License for more details. 26 | * 27 | * You should have received a copy of the GNU General Public 28 | * License along with this library; if not, write to the Free Software 29 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 30 | */ 31 | public class IQConverter { 32 | public static final int FORMAT_8BIT_SIGNED = 0; 33 | public static final int FORMAT_8BIT_UNSIGNED = 1; 34 | public static final int FORMAT_16BIT_SIGNED = 2; 35 | public static final int FORMAT_16BIT_UNSIGNED = 3; 36 | private static final String LOGTAG = "IQConverter"; 37 | private int format; 38 | private int packetSize; 39 | private long frequency = 0; // Baseband frequency of the converted samples (is put into the SamplePacket) 40 | private int sampleRate = 0; // Sample rate of the converted samples (is put into the SamplePacket) 41 | private boolean mixerLookupTableInvalid = true; // Indicates that the lookup table of the mixer has to be regenerated 42 | private static final int MAX_COSINE_LENGTH = 500; // Max length of the mixer lookup table 43 | private Mixer_8Bit mixer8Bit; 44 | private LookupTable_8Bit lookupTable8Bit; 45 | 46 | public IQConverter(int format, int packetSize) { 47 | this.format = format; 48 | this.packetSize = packetSize; 49 | switch (format) { 50 | case FORMAT_8BIT_SIGNED: 51 | lookupTable8Bit = new LookupTable_8Bit(packetSize, LookupTable_8Bit.createSigned8BitLookupTable()); 52 | mixer8Bit = new Mixer_8Bit(packetSize, MAX_COSINE_LENGTH); 53 | break; 54 | case FORMAT_8BIT_UNSIGNED: 55 | lookupTable8Bit = new LookupTable_8Bit(packetSize, LookupTable_8Bit.createUnsigned8BitLookupTable()); 56 | mixer8Bit = new Mixer_8Bit(packetSize, MAX_COSINE_LENGTH); 57 | break; 58 | default: 59 | Log.e(LOGTAG, "fillPacketIntoSamplePacket: invalid format: " + format); 60 | } 61 | } 62 | 63 | public long getFrequency() { 64 | return frequency; 65 | } 66 | 67 | public void setFrequency(long frequency) { 68 | this.frequency = frequency; 69 | } 70 | 71 | public int getSampleRate() { 72 | return sampleRate; 73 | } 74 | 75 | public void setSampleRate(int sampleRate) { 76 | if(this.sampleRate != sampleRate) { 77 | this.sampleRate = sampleRate; 78 | this.mixerLookupTableInvalid = true; 79 | } 80 | } 81 | 82 | protected int calcOptimalCosineLength(int cosineFrequency) { 83 | // look for the best fitting array size to hold one or more full cosine cycles: 84 | double cycleLength = sampleRate / Math.abs((double)cosineFrequency); 85 | int bestLength = (int) cycleLength; 86 | double bestLengthError = Math.abs(bestLength-cycleLength); 87 | for (int i = 1; i*cycleLength < MAX_COSINE_LENGTH ; i++) { 88 | if(Math.abs(i*cycleLength - (int)(i*cycleLength)) < bestLengthError) { 89 | bestLength = (int)(i*cycleLength); 90 | bestLengthError = Math.abs(bestLength - (i*cycleLength)); 91 | } 92 | } 93 | return bestLength; 94 | } 95 | 96 | public void fillPacketIntoSamplePacket(byte[] packet, SamplePacket samplePacket) { 97 | switch (format) { 98 | case FORMAT_8BIT_SIGNED: 99 | fillPacketIntoSamplePacket_8BitSigned(packet, samplePacket); 100 | break; 101 | case FORMAT_8BIT_UNSIGNED: 102 | fillPacketIntoSamplePacket_8BitUnsigned(packet, samplePacket); 103 | break; 104 | default: 105 | Log.e(LOGTAG, "fillPacketIntoSamplePacket: invalid format: " + format); 106 | break; 107 | } 108 | } 109 | 110 | public void fillPacketIntoSamplePacket_8BitSigned(byte[] packet, SamplePacket samplePacket) { 111 | int size = samplePacket.size(); 112 | int capacity = samplePacket.capacity(); 113 | lookupTable8Bit.convertFromSignedInterleaved8Bit(packet, samplePacket.getReAlloc(), samplePacket.getImAlloc(), size, capacity); 114 | size += packet.length / 2; 115 | if(size > capacity) 116 | size = capacity; 117 | samplePacket.setSize(size); 118 | } 119 | 120 | public void fillPacketIntoSamplePacket_8BitUnsigned(byte[] packet, SamplePacket samplePacket) { 121 | int size = samplePacket.size(); 122 | int capacity = samplePacket.capacity(); 123 | lookupTable8Bit.convertFromUnsignedInterleaved8Bit(packet, samplePacket.getReAlloc(), samplePacket.getImAlloc(), size, capacity); 124 | size += packet.length / 2; 125 | if(size > capacity) 126 | size = capacity; 127 | samplePacket.setSize(size); 128 | } 129 | 130 | public int mixPacketIntoSamplePacket(byte[] packet, SamplePacket samplePacket, long channelFrequency) { 131 | switch (format) { 132 | case FORMAT_8BIT_SIGNED: 133 | return mixPacketIntoSamplePacket_8BitSigned(packet, samplePacket, channelFrequency); 134 | case FORMAT_8BIT_UNSIGNED: 135 | return mixPacketIntoSamplePacket_8BitUnsigned(packet, samplePacket, channelFrequency); 136 | default: 137 | Log.e(LOGTAG, "fillPacketIntoSamplePacket: invalid format: " + format); 138 | return -1; 139 | } 140 | } 141 | 142 | public int mixPacketIntoSamplePacket_8BitSigned(byte[] packet, SamplePacket samplePacket, long channelFrequency) { 143 | // If mix frequency is too low, just add the sample rate (sampled spectrum is periodic): 144 | int mixFrequency = (int) (channelFrequency - frequency); 145 | if(mixFrequency == 0 || (sampleRate / Math.abs(mixFrequency) > MAX_COSINE_LENGTH)) 146 | mixFrequency += sampleRate; 147 | 148 | // Only generate lookupTable if invalid: 149 | if(mixerLookupTableInvalid || mixFrequency != mixer8Bit.getCosineFrequency()) { 150 | int bestLength = calcOptimalCosineLength(mixFrequency); 151 | mixer8Bit.generateLookupTable(sampleRate, mixFrequency, bestLength, true); 152 | mixerLookupTableInvalid = false; 153 | } 154 | 155 | // mix and convert packet: 156 | int size = samplePacket.size(); 157 | int count = mixer8Bit.mixFromSignedInterleaved8Bit(packet, samplePacket.getReAlloc(), samplePacket.getImAlloc(), size, samplePacket.capacity()); 158 | samplePacket.setSize(size + count); 159 | return count; 160 | } 161 | 162 | public int mixPacketIntoSamplePacket_8BitUnsigned(byte[] packet, SamplePacket samplePacket, long channelFrequency) { 163 | // If mix frequency is too low, just add the sample rate (sampled spectrum is periodic): 164 | int mixFrequency = (int) (channelFrequency - frequency); 165 | if(mixFrequency == 0 || (sampleRate / Math.abs(mixFrequency) > MAX_COSINE_LENGTH)) 166 | mixFrequency += sampleRate; 167 | 168 | // Only generate lookupTable if invalid: 169 | if(mixerLookupTableInvalid || mixFrequency != mixer8Bit.getCosineFrequency()) { 170 | int bestLength = calcOptimalCosineLength(mixFrequency); 171 | mixer8Bit.generateLookupTable(sampleRate, mixFrequency, bestLength, false); 172 | mixerLookupTableInvalid = false; 173 | } 174 | 175 | // mix and convert packet: 176 | int size = samplePacket.size(); 177 | int count = mixer8Bit.mixFromUnsignedInterleaved8Bit(packet, samplePacket.getReAlloc(), samplePacket.getImAlloc(), size, samplePacket.capacity()); 178 | samplePacket.setSize(size + count); 179 | return count; 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /android_dsp_lib/src/main/java/com/mantz_it/android_dsp_lib/LookupTable_8Bit.java: -------------------------------------------------------------------------------- 1 | package com.mantz_it.android_dsp_lib; 2 | 3 | import android.support.v8.renderscript.Allocation; 4 | import android.support.v8.renderscript.Element; 5 | import android.support.v8.renderscript.RenderScript; 6 | 7 | /** 8 | * Android DSP library - Lookup Table 8bit 9 | * 10 | * Module: LookupTable_8Bit.java 11 | * Description: This class can do type conversion (e.g. IQ bytes -> floats) by using a lookup table 12 | * 13 | * @author Dennis Mantz 14 | * 15 | * Copyright (C) 2014 Dennis Mantz 16 | * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher 17 | * 18 | * This library is free software; you can redistribute it and/or 19 | * modify it under the terms of the GNU General Public 20 | * License as published by the Free Software Foundation; either 21 | * version 2 of the License, or (at your option) any later version. 22 | * 23 | * This library is distributed in the hope that it will be useful, 24 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 25 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 26 | * General Public License for more details. 27 | * 28 | * You should have received a copy of the GNU General Public 29 | * License along with this library; if not, write to the Free Software 30 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 31 | */ 32 | public class LookupTable_8Bit { 33 | private RenderScript rs; 34 | private ScriptC_lookup_table_8bit script; 35 | private Allocation lut; 36 | private Allocation inAlloc; 37 | 38 | public LookupTable_8Bit(int inputSize, float[] lookupTable) { 39 | if(lookupTable == null || lookupTable.length != 256) 40 | throw new IllegalArgumentException("8-bit lookup table has to have exactly 256 elements!"); 41 | 42 | rs = AndroidDSPLib.getRenderScript(); 43 | 44 | lut = Allocation.createSized(rs, Element.F32(rs), 256); 45 | inAlloc = Allocation.createSized(rs, Element.I8(rs), inputSize); 46 | lut.copyFrom(lookupTable); 47 | 48 | script = new ScriptC_lookup_table_8bit(rs, AndroidDSPLib.getResources(), R.raw.lookup_table_8bit); 49 | script.bind_lut(lut); 50 | } 51 | 52 | public void convertFromSignedInterleaved8Bit(byte[] in, Allocation outReal, Allocation outImag, int offset, int length) { 53 | inAlloc.copyFrom(in); 54 | script.set_outReal(outReal); 55 | script.set_outImag(outImag); 56 | script.set_offset(offset); 57 | script.set_len(length); 58 | script.forEach_convertSignedInterleavedKernel(inAlloc); 59 | } 60 | 61 | public void convertFromUnsignedInterleaved8Bit(byte[] in, Allocation outReal, Allocation outImag, int offset, int length) { 62 | inAlloc.copyFrom(in); 63 | script.set_outReal(outReal); 64 | script.set_outImag(outImag); 65 | script.set_offset(offset); 66 | script.set_len(length); 67 | script.forEach_convertUnsignedInterleavedKernel(inAlloc); 68 | } 69 | 70 | public static float[] createSigned8BitLookupTable() { 71 | float[] lut = new float[256]; 72 | for (int i = 0; i < 256; i++) 73 | lut[i] = (i-128) / 128.0f; 74 | return lut; 75 | } 76 | 77 | public static float[] createUnsigned8BitLookupTable() { 78 | float[] lut = new float[256]; 79 | for (int i = 0; i < 256; i++) 80 | lut[i] = (i-127.4f) / 128.0f; 81 | return lut; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /android_dsp_lib/src/main/java/com/mantz_it/android_dsp_lib/LowPassFilter.java: -------------------------------------------------------------------------------- 1 | package com.mantz_it.android_dsp_lib; 2 | 3 | /** 4 | * Android DSP library - Low Pass Filter 5 | * 6 | * Module: LowPassFilter.java 7 | * Description: This class extends the FirFilter class to create a convenient low pass filter 8 | * 9 | * @author Dennis Mantz 10 | * 11 | * Copyright (C) 2014 Dennis Mantz 12 | * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher 13 | * 14 | * This library is free software; you can redistribute it and/or 15 | * modify it under the terms of the GNU General Public 16 | * License as published by the Free Software Foundation; either 17 | * version 2 of the License, or (at your option) any later version. 18 | * 19 | * This library is distributed in the hope that it will be useful, 20 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 22 | * General Public License for more details. 23 | * 24 | * You should have received a copy of the GNU General Public 25 | * License along with this library; if not, write to the Free Software 26 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 27 | */ 28 | public class LowPassFilter extends FirFilter { 29 | private float gain; 30 | private float sampleRate; 31 | private float cutOffFrequency; 32 | private float transitionWidth; 33 | private float attenuation; 34 | 35 | /** 36 | * Constructor. Creates a new FIR Filter with the given parameters and decimation. 37 | * @param decimation decimation factor 38 | * @param gain filter pass band gain 39 | * @param sampleRate sample rate 40 | * @param cutOffFrequency cut off frequency (end of pass band) 41 | * @param transitionWidth width from end of pass band to start stop band 42 | * @param attenuation attenuation of stop band 43 | */ 44 | public LowPassFilter(int decimation, float gain, float sampleRate, float cutOffFrequency, float transitionWidth, float attenuation) { 45 | super(designLowPassFilter(gain, sampleRate, cutOffFrequency, transitionWidth, attenuation), null, decimation); 46 | this.gain = gain; 47 | this.sampleRate = sampleRate; 48 | this.cutOffFrequency = cutOffFrequency; 49 | this.transitionWidth = transitionWidth; 50 | this.attenuation = attenuation; 51 | } 52 | 53 | public float getGain() { 54 | return gain; 55 | } 56 | 57 | public float getSampleRate() { 58 | return sampleRate; 59 | } 60 | 61 | public float getCutOffFrequency() { 62 | return cutOffFrequency; 63 | } 64 | 65 | public float getTransitionWidth() { 66 | return transitionWidth; 67 | } 68 | 69 | public float getAttenuation() { 70 | return attenuation; 71 | } 72 | 73 | public int filter(SamplePacket in, SamplePacket out, int offset, int length) { 74 | return super.filterComplexSignal(in,out,offset,length); 75 | } 76 | 77 | public int filterReal(SamplePacket in, SamplePacket out, int offset, int length) { 78 | return super.filterRealSignal(in,out,offset,length); 79 | } 80 | 81 | /** 82 | * FROM GNU Radio firdes::low_pass_2: 83 | * Will calculate the tabs for the specified low pass filter 84 | * 85 | * @param gain filter pass band gain 86 | * @param sampleRate sample rate 87 | * @param cutOffFrequency cut off frequency (end of pass band) 88 | * @param transitionWidth width from end of pass band to start stop band 89 | * @param attenuation attenuation of stop band 90 | * @return filter taps 91 | */ 92 | public static float[] designLowPassFilter(float gain, float sampleRate, float cutOffFrequency, float transitionWidth, float attenuation) { 93 | if (sampleRate <= 0.0) { 94 | throw new IllegalArgumentException("firdes check failed: sampling_freq > 0"); 95 | } 96 | 97 | if (cutOffFrequency <= 0.0 || cutOffFrequency > sampleRate / 2) { 98 | throw new IllegalArgumentException("firdes check failed: 0 < fa <= sampling_freq / 2"); 99 | } 100 | 101 | if (transitionWidth <= 0) { 102 | throw new IllegalArgumentException("firdes check failed: transition_width > 0"); 103 | } 104 | 105 | // Calculate number of tabs 106 | // Based on formula from Multirate Signal Processing for 107 | // Communications Systems, fredric j harris 108 | int ntaps = (int)(attenuation*sampleRate/(22.0*transitionWidth)); 109 | if ((ntaps & 1) == 0) // if even... 110 | ntaps++; // ...make odd 111 | 112 | // construct the truncated ideal impulse response 113 | // [sin(x)/x for the low pass case] 114 | 115 | float[] taps = new float[ntaps]; 116 | float[] w = WindowFunctions.makeBlackmanWindow(ntaps); 117 | 118 | int M = (ntaps - 1) / 2; 119 | float fwT0 = 2 * (float)Math.PI * cutOffFrequency / sampleRate; 120 | for (int n = -M; n <= M; n++) { 121 | if (n == 0) 122 | taps[n + M] = fwT0 / (float)Math.PI * w[n + M]; 123 | else { 124 | // a little algebra gets this into the more familiar sin(x)/x form 125 | taps[n + M] = (float)Math.sin(n * fwT0) / (n * (float)Math.PI) * w[n + M]; 126 | } 127 | } 128 | 129 | // find the factor to normalize the gain, fmax. 130 | // For low-pass, gain @ zero freq = 1.0 131 | 132 | float fmax = taps[0 + M]; 133 | for (int n = 1; n <= M; n++) 134 | fmax += 2 * taps[n + M]; 135 | 136 | float actualGain = gain/fmax; // normalize 137 | 138 | for (int i = 0; i < ntaps; i++) 139 | taps[i] *= actualGain; 140 | 141 | return taps; 142 | } 143 | 144 | } 145 | -------------------------------------------------------------------------------- /android_dsp_lib/src/main/java/com/mantz_it/android_dsp_lib/Mixer_8Bit.java: -------------------------------------------------------------------------------- 1 | package com.mantz_it.android_dsp_lib; 2 | 3 | import android.support.v8.renderscript.Allocation; 4 | import android.support.v8.renderscript.Element; 5 | import android.support.v8.renderscript.RenderScript; 6 | 7 | /** 8 | *

Android DSP library - Mixer 8bit

9 | * 10 | * Module: Mixer_8Bit.java 11 | * Description: This class implements methods to do converting (byte->float) and 12 | * down-mixing at the same time. 13 | * 14 | * @author Dennis Mantz 15 | * 16 | * Copyright (C) 2014 Dennis Mantz 17 | * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher 18 | * 19 | * This library is free software; you can redistribute it and/or 20 | * modify it under the terms of the GNU General Public 21 | * License as published by the Free Software Foundation; either 22 | * version 2 of the License, or (at your option) any later version. 23 | * 24 | * This library is distributed in the hope that it will be useful, 25 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 26 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 27 | * General Public License for more details. 28 | * 29 | * You should have received a copy of the GNU General Public 30 | * License along with this library; if not, write to the Free Software 31 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 32 | */ 33 | public class Mixer_8Bit { 34 | private RenderScript rs; 35 | ScriptC_mixer_8bit script; 36 | private Allocation realLutAlloc; 37 | private Allocation imagLutAlloc; 38 | private Allocation inAlloc; 39 | private int cosineLength; 40 | private int cosineFrequency; 41 | 42 | public Mixer_8Bit(int inputSize, int maxCosineLength) { 43 | rs = AndroidDSPLib.getRenderScript(); 44 | realLutAlloc = Allocation.createSized(rs, Element.F32(rs), maxCosineLength*256); 45 | imagLutAlloc = Allocation.createSized(rs, Element.F32(rs), maxCosineLength*256); 46 | inAlloc = Allocation.createSized(rs, Element.I8(rs), inputSize); 47 | script = new ScriptC_mixer_8bit(rs, AndroidDSPLib.getResources(), R.raw.mixer_8bit); 48 | script.bind_lutReal(realLutAlloc); 49 | script.bind_lutImag(imagLutAlloc); 50 | } 51 | 52 | public int getCosineFrequency() { 53 | return cosineFrequency; 54 | } 55 | 56 | public void generateLookupTable(int sampleRate, int mixFrequency, int cosineLength, boolean signed) { 57 | System.out.println("Generating mixer lookup table of length " + cosineLength + " freq=" + mixFrequency); 58 | this.cosineLength = cosineLength; 59 | this.cosineFrequency = mixFrequency; 60 | script.set_cosineLength(cosineLength); 61 | script.set_mixFrequency(mixFrequency); 62 | script.set_sampleRate(sampleRate); 63 | script.set_signedFlag(signed ? (short)1 : (short)0); 64 | script.forEach_updateLut(realLutAlloc); // argument is just a dummy allocation that has the correct length 65 | script.set_baseIndex(0); 66 | realLutAlloc.syncAll(Allocation.USAGE_SCRIPT); 67 | imagLutAlloc.syncAll(Allocation.USAGE_SCRIPT); 68 | } 69 | 70 | public int mixFromSignedInterleaved8Bit(byte[] in, Allocation outReal, Allocation outImag, int offset, int length) { 71 | inAlloc.copyFrom(in); 72 | script.set_outReal(outReal); 73 | script.set_outImag(outImag); 74 | script.set_offset(offset); 75 | script.set_len(length); 76 | long baseIndex = script.get_baseIndex(); 77 | script.forEach_mixSignedInterleavedKernel(inAlloc); 78 | int count = Math.min(in.length/2, length-offset); 79 | script.set_baseIndex((baseIndex+count) % cosineLength); 80 | return count; 81 | } 82 | 83 | public int mixFromUnsignedInterleaved8Bit(byte[] in, Allocation outReal, Allocation outImag, int offset, int length) { 84 | inAlloc.copyFrom(in); 85 | script.set_outReal(outReal); 86 | script.set_outImag(outImag); 87 | script.set_offset(offset); 88 | script.set_len(length); 89 | long baseIndex = script.get_baseIndex(); 90 | script.forEach_mixUnsignedInterleavedKernel(inAlloc); 91 | int count = Math.min(in.length/2, length-offset); 92 | script.set_baseIndex((baseIndex+count) % cosineLength); 93 | return count; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /android_dsp_lib/src/main/java/com/mantz_it/android_dsp_lib/QuadratureDemodulator.java: -------------------------------------------------------------------------------- 1 | package com.mantz_it.android_dsp_lib; 2 | 3 | import android.support.v8.renderscript.RenderScript; 4 | 5 | /** 6 | * Android DSP library - Quadrature Demodulator 7 | * 8 | * Module: QuadratureDemodulator.java 9 | * Description: This class implements a quadrature demodulator 10 | * 11 | * @author Dennis Mantz 12 | * 13 | * Copyright (C) 2014 Dennis Mantz 14 | * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher 15 | * 16 | * This library is free software; you can redistribute it and/or 17 | * modify it under the terms of the GNU General Public 18 | * License as published by the Free Software Foundation; either 19 | * version 2 of the License, or (at your option) any later version. 20 | * 21 | * This library is distributed in the hope that it will be useful, 22 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 23 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 24 | * General Public License for more details. 25 | * 26 | * You should have received a copy of the GNU General Public 27 | * License along with this library; if not, write to the Free Software 28 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 29 | */ 30 | public class QuadratureDemodulator { 31 | private RenderScript rs; 32 | private ScriptC_quad_demod script; 33 | private float gain; 34 | 35 | public QuadratureDemodulator(float gain) { 36 | this.rs = AndroidDSPLib.getRenderScript(); 37 | this.gain = gain; 38 | script = new ScriptC_quad_demod(rs, AndroidDSPLib.getResources(), R.raw.quad_demod); 39 | script.set_gain(gain); 40 | script.set_historyRe(0); 41 | script.set_historyIm(0); 42 | } 43 | 44 | public float getGain() { 45 | return gain; 46 | } 47 | 48 | /** 49 | * Demodulates the complex samples from the input sample packet to the real component of the output 50 | * sample packet. Stops automatically if output sample packet is full. 51 | * @param in input sample packet 52 | * @param out output sample packet 53 | * @param offset offset to use as start index for the input packet 54 | * @param length max number of output samples 55 | * @return number of samples written to the output sample packet 56 | */ 57 | public int demodulate(SamplePacket in, SamplePacket out, int offset, int length) { 58 | int outSize = out.size(); 59 | int outputLength = Math.min(outSize + length, out.capacity()); 60 | script.set_offsetIn(offset); 61 | script.set_offsetOut(outSize); 62 | script.set_len(outputLength); 63 | script.set_inReal(in.getReAlloc()); 64 | script.set_inImag(in.getImAlloc()); 65 | script.set_outReal(out.getReAlloc()); 66 | script.forEach_demod(out.getReAlloc()); 67 | out.setSize(outputLength); 68 | out.setSampleRate(in.getSampleRate()); 69 | script.invoke_saveHistory(); 70 | return (outputLength-outSize); 71 | } 72 | 73 | 74 | } 75 | -------------------------------------------------------------------------------- /android_dsp_lib/src/main/java/com/mantz_it/android_dsp_lib/SamplePacket.java: -------------------------------------------------------------------------------- 1 | package com.mantz_it.android_dsp_lib; 2 | 3 | import android.support.v8.renderscript.Allocation; 4 | import android.support.v8.renderscript.Element; 5 | 6 | /** 7 | * Android DSP library - Sample Packet 8 | * 9 | * Module: SamplePacket.java 10 | * Description: This class encapsulates a packet of complex samples. 11 | * 12 | * @author Dennis Mantz 13 | * 14 | * Copyright (C) 2014 Dennis Mantz 15 | * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher 16 | * 17 | * This library is free software; you can redistribute it and/or 18 | * modify it under the terms of the GNU General Public 19 | * License as published by the Free Software Foundation; either 20 | * version 2 of the License, or (at your option) any later version. 21 | * 22 | * This library is distributed in the hope that it will be useful, 23 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 24 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 25 | * General Public License for more details. 26 | * 27 | * You should have received a copy of the GNU General Public 28 | * License along with this library; if not, write to the Free Software 29 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 30 | */ 31 | public class SamplePacket { 32 | private Allocation reAlloc; // real values 33 | private Allocation imAlloc; // imag values 34 | private long frequency; // center frequency 35 | private int sampleRate; // sample rate 36 | private int size; // number of valid samples in this packet 37 | private int capacity; // max number of samples in this packet 38 | 39 | /** 40 | * Constructor. This constructor wraps existing arrays and set the number of 41 | * samples to the length of the arrays 42 | * 43 | * @param re array of real parts of the sample values 44 | * @param im array of imaginary parts of the sample values 45 | * @param frequency center frequency 46 | * @param sampleRate sample rate 47 | */ 48 | public SamplePacket(float[] re, float im[], long frequency, int sampleRate) { 49 | this(re, im, frequency, sampleRate, re.length); 50 | } 51 | 52 | /** 53 | * Constructor. This constructor wraps existing arrays and allows to set the 54 | * number of samples in this packet to something smaller than the array length 55 | * 56 | * @param re array of real parts of the sample values 57 | * @param im array of imaginary parts of the sample values 58 | * @param frequency center frequency 59 | * @param sampleRate sample rate 60 | * @param size number of samples in this packet ( <= arrays.length ) 61 | */ 62 | public SamplePacket(float[] re, float im[], long frequency, int sampleRate, int size) { 63 | if(re.length != im.length) 64 | throw new IllegalArgumentException("Arrays must be of the same length"); 65 | if(size > re.length) 66 | throw new IllegalArgumentException("Size must be of the smaller or equal the array length"); 67 | 68 | this.capacity = re.length; 69 | this.reAlloc = Allocation.createSized(AndroidDSPLib.getRenderScript(), Element.F32(AndroidDSPLib.getRenderScript()), capacity); 70 | this.imAlloc = Allocation.createSized(AndroidDSPLib.getRenderScript(), Element.F32(AndroidDSPLib.getRenderScript()), capacity); 71 | this.reAlloc.copyFrom(re); 72 | this.imAlloc.copyFrom(im); 73 | this.frequency = frequency; 74 | this.sampleRate = sampleRate; 75 | this.size = size; 76 | } 77 | 78 | /** 79 | * Constructor. This constructor allocates two fresh arrays 80 | * 81 | * @param capacity Number of samples in this packet 82 | */ 83 | public SamplePacket(int capacity) { 84 | this.capacity = capacity; 85 | this.reAlloc = Allocation.createSized(AndroidDSPLib.getRenderScript(), Element.F32(AndroidDSPLib.getRenderScript()), capacity); 86 | this.imAlloc = Allocation.createSized(AndroidDSPLib.getRenderScript(), Element.F32(AndroidDSPLib.getRenderScript()), capacity); 87 | this.frequency = 0; 88 | this.sampleRate = 0; 89 | this.size = 0; 90 | } 91 | 92 | /** 93 | * @return the reference to the imaginary Allocation object 94 | */ 95 | public Allocation getImAlloc() { 96 | return imAlloc; 97 | } 98 | 99 | /** 100 | * @return the reference to the real Allocation object 101 | */ 102 | public Allocation getReAlloc() { 103 | return reAlloc; 104 | } 105 | 106 | /** 107 | * @return the reference to the array of real parts 108 | */ 109 | public float[] re() { 110 | float[] out = new float[capacity]; 111 | reAlloc.copyTo(out); 112 | return out; 113 | } 114 | 115 | /** 116 | * @return the reference to the array of imaginary parts 117 | */ 118 | public float[] im() { 119 | float[] out = new float[capacity]; 120 | imAlloc.copyTo(out); 121 | return out; 122 | } 123 | 124 | /** 125 | * @return the length of the arrays 126 | */ 127 | public int capacity() { 128 | return capacity; 129 | } 130 | 131 | /** 132 | * @return number of samples in this packet 133 | */ 134 | public int size() { 135 | return size; 136 | } 137 | 138 | /** 139 | * Sets a new size (number of samples in this packet) 140 | * @param size number of (valid) samples in this packet 141 | */ 142 | public void setSize(int size) { 143 | this.size = Math.min(size, capacity); 144 | } 145 | 146 | /** 147 | * @return center frequency at which these samples where recorded 148 | */ 149 | public long getFrequency() { 150 | return frequency; 151 | } 152 | 153 | /** 154 | * @return sample rate at which these samples were recorded 155 | */ 156 | public int getSampleRate() { 157 | return sampleRate; 158 | } 159 | 160 | /** 161 | * Sets the center frequency for this sample packet 162 | * @param frequency center frequency at which these samples were recorded 163 | */ 164 | public void setFrequency(long frequency) { 165 | this.frequency = frequency; 166 | } 167 | 168 | /** 169 | * Sets the sample rate for this sample packet 170 | * @param sampleRate sample rate at which these samples were recorded 171 | */ 172 | public void setSampleRate(int sampleRate) { 173 | this.sampleRate = sampleRate; 174 | } 175 | 176 | /** 177 | * Syncs the Allocations in this sample packet. This will block until all current calculations 178 | * (render scripts) on reAlloc and imAlloc are done. This is only necessary when accessing 179 | * the allocations directly - not if accessing through re() and im(). 180 | */ 181 | public void sync() { 182 | reAlloc.syncAll(Allocation.USAGE_SCRIPT); 183 | imAlloc.syncAll(Allocation.USAGE_SCRIPT); 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /android_dsp_lib/src/main/java/com/mantz_it/android_dsp_lib/WindowFunctions.java: -------------------------------------------------------------------------------- 1 | package com.mantz_it.android_dsp_lib; 2 | 3 | /** 4 | * Android DSP library - Window Functions 5 | * 6 | * Module: Window.java 7 | * Description: This class contains static methods to create windows (Blackman, ...). Implementations 8 | * are copied from other sources (referenced in the methods) 9 | * 10 | * @author Dennis Mantz 11 | * 12 | * Copyright (C) 2014 Dennis Mantz 13 | * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher 14 | * 15 | * This library is free software; you can redistribute it and/or 16 | * modify it under the terms of the GNU General Public 17 | * License as published by the Free Software Foundation; either 18 | * version 2 of the License, or (at your option) any later version. 19 | * 20 | * This library is distributed in the hope that it will be useful, 21 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 23 | * General Public License for more details. 24 | * 25 | * You should have received a copy of the GNU General Public 26 | * License along with this library; if not, write to the Free Software 27 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 28 | */ 29 | public class WindowFunctions { 30 | 31 | /** 32 | * Creates a Blackman Window. 33 | * Code from https://www.ee.columbia.edu/~ronw/code/MEAPsoft/doc/html/FFT_8java-source.html 34 | * All credit goes to the original authors! 35 | * 36 | * @param ntabs number of samples 37 | * @return window samples 38 | */ 39 | public static float[] makeBlackmanWindow(int ntabs) { 40 | // Make a blackman window: 41 | // w(n)=0.42-0.5cos{(2*PI*n)/(N-1)}+0.08cos{(4*PI*n)/(N-1)}; 42 | float[] window = new float[ntabs]; 43 | for (int i = 0; i < window.length; i++) 44 | window[i] = 0.42f - 0.5f * (float)Math.cos(2 * Math.PI * i / (ntabs - 1)) 45 | + 0.08f * (float)Math.cos(4 * Math.PI * i / (ntabs - 1)); 46 | return window; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /android_dsp_lib/src/main/rs/fir_filter.rs: -------------------------------------------------------------------------------- 1 | /** 2 | * Android DSP library - FIR Filter 3 | * 4 | * Module: fir_filter.rs 5 | * Description: This kernel implements a FIR filter (real and complex taps possible) 6 | * 7 | * @author Dennis Mantz 8 | * 9 | * Copyright (C) 2014 Dennis Mantz 10 | * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher 11 | * 12 | * This library is free software; you can redistribute it and/or 13 | * modify it under the terms of the GNU General Public 14 | * License as published by the Free Software Foundation; either 15 | * version 2 of the License, or (at your option) any later version. 16 | * 17 | * This library is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 20 | * General Public License for more details. 21 | * 22 | * You should have received a copy of the GNU General Public 23 | * License along with this library; if not, write to the Free Software 24 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 25 | */ 26 | 27 | #pragma version(1) 28 | #pragma rs_fp_relaxed 29 | #pragma rs java_package_name(com.mantz_it.android_dsp_lib) 30 | 31 | float* tapsReal; // Real part of the filter taps 32 | float* tapsImag; // Imaginary part of the filter taps (may not be used) 33 | float* remainderReal; // history samples that are used for the next filter operation 34 | float* remainderImag; 35 | rs_allocation inReal; // Input packet 36 | rs_allocation inImag; 37 | rs_allocation outReal; // Output packet 38 | rs_allocation outImag; 39 | 40 | uint32_t filterOrder; // Length of the taps array 41 | uint32_t decimation; // Decimation factor. set to 1 for no decimation 42 | uint32_t offsetIn; // Start index in the input allocations 43 | uint32_t offsetOut; // Start index in the output allocations (first index that will be written) 44 | uint32_t len; // Length of the output allocations (length-1 is max index that will be written) 45 | 46 | /* 47 | * Will set the remainder samples to zero (initial state) 48 | */ 49 | void clearRemainders() { 50 | uint32_t i; 51 | for(i=0; i < filterOrder-1; i++) { 52 | remainderReal[i] = 0; 53 | remainderImag[i] = 0; 54 | } 55 | } 56 | 57 | /* 58 | * Will copy the remaining samples from the input packet (real and imag parts) to the remainder arrays 59 | */ 60 | void updateRemainders() { 61 | uint32_t i; 62 | uint32_t index = offsetIn + ((len-offsetOut)*decimation) - filterOrder + 1; // length of the input alloc minus length of the remainders 63 | for(i=0; i < filterOrder-1; i++) { 64 | remainderReal[i] = rsGetElementAt_float(inReal, index + i); 65 | remainderImag[i] = rsGetElementAt_float(inImag, index + i); 66 | } 67 | } 68 | 69 | /* 70 | * Will copy the remaining samples from the input packet (only real part) to the remainder array 71 | */ 72 | void updateRealRemainders() { 73 | uint32_t i; 74 | uint32_t index = offsetIn + ((len-offsetOut)*decimation) - filterOrder + 1; // length of the input alloc minus length of the remainders 75 | for(i=0; i < filterOrder-1; i++) { 76 | remainderReal[i] = rsGetElementAt_float(inReal, index + i); 77 | } 78 | } 79 | 80 | /* 81 | * Kernel: filter a complex signal with real filter taps and decimate 82 | * Input is expected in inReal and inImag and output will be stored in outReal and outImag 83 | * @param out dummy argument (is not used, but has to have the same size as outReal and outImag) 84 | * @param x position inside 'out' (given by the runtime) 85 | */ 86 | void filterAndDecimate(float* out, uint32_t x) { 87 | int32_t i; 88 | int32_t inIndex = x * decimation - filterOrder + 1; 89 | float resultReal = 0; 90 | float resultImag = 0; 91 | 92 | if(x + offsetOut >= len) 93 | return; // reached max index 94 | 95 | if(inIndex >= 0) { 96 | // All input values are located in the in-allocations 97 | for(i=0; i= len) 129 | return; // reached max index 130 | 131 | if(inIndex >= 0) { 132 | // All input values are located in the in-allocations 133 | for(i=0; i= len) 160 | return; // reached max index 161 | 162 | if(inIndex >= 0) { 163 | // All input values are located in the in-allocations 164 | for(i=0; i floats) by using a lookup table 7 | * 8 | * @author Dennis Mantz 9 | * 10 | * Copyright (C) 2014 Dennis Mantz 11 | * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher 12 | * 13 | * This library is free software; you can redistribute it and/or 14 | * modify it under the terms of the GNU General Public 15 | * License as published by the Free Software Foundation; either 16 | * version 2 of the License, or (at your option) any later version. 17 | * 18 | * This library is distributed in the hope that it will be useful, 19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 21 | * General Public License for more details. 22 | * 23 | * You should have received a copy of the GNU General Public 24 | * License along with this library; if not, write to the Free Software 25 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 26 | */ 27 | 28 | #pragma version(1) 29 | #pragma rs_fp_relaxed 30 | #pragma rs java_package_name(com.mantz_it.android_dsp_lib) 31 | 32 | float *lut; // lookup table (length 256). must be set by caller. 33 | rs_allocation outReal; // Output Allocation for real array in case of interleaved kernels 34 | rs_allocation outImag; // Output Allocation for imag array in case of interleaved kernels 35 | uint32_t offset; // Start index in the output allocations (first index that will be written) 36 | uint32_t len; // Length of the output allocations (length-1 is max index that will be written) 37 | 38 | void convertSignedInterleavedKernel(const char *in, uint32_t x) { 39 | uint32_t outIndex = x >> 1; 40 | if(outIndex + offset >= len) 41 | return; // reached max index 42 | if(x & 0x01) 43 | rsSetElementAt_float(outImag, *(lut + *in + 128), outIndex + offset); 44 | else 45 | rsSetElementAt_float(outReal, *(lut + *in + 128), outIndex + offset); 46 | } 47 | 48 | void convertUnsignedInterleavedKernel(const char *in, uint32_t x) { 49 | uint32_t outIndex = x >> 1; 50 | if(outIndex + offset >= len) 51 | return; // reached max index 52 | if(x & 0x01) 53 | rsSetElementAt_float(outImag, *(lut + (*in & 0xff)), outIndex + offset); 54 | else 55 | rsSetElementAt_float(outReal, *(lut + (*in & 0xff)), outIndex + offset); 56 | } -------------------------------------------------------------------------------- /android_dsp_lib/src/main/rs/mixer_8bit.rs: -------------------------------------------------------------------------------- 1 | /** 2 | * Android DSP library - Mixer for 8 bit input values 3 | * 4 | * Module: mixer_8bit.rs 5 | * Description: This kernel can do frequency shift and conversion of 8 bit values to float 6 | (e.g. IQ bytes -> floats) by using a lookup table 7 | * 8 | * @author Dennis Mantz 9 | * 10 | * Copyright (C) 2014 Dennis Mantz 11 | * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher 12 | * 13 | * This library is free software; you can redistribute it and/or 14 | * modify it under the terms of the GNU General Public 15 | * License as published by the Free Software Foundation; either 16 | * version 2 of the License, or (at your option) any later version. 17 | * 18 | * This library is distributed in the hope that it will be useful, 19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 21 | * General Public License for more details. 22 | * 23 | * You should have received a copy of the GNU General Public 24 | * License along with this library; if not, write to the Free Software 25 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 26 | */ 27 | 28 | #pragma version(1) 29 | #pragma rs_fp_relaxed 30 | #pragma rs java_package_name(com.mantz_it.android_dsp_lib) 31 | 32 | float *lutReal; // real lookup table (length 256*cosineLength). calculated by updateLut() 33 | float *lutImag; // imag lookup table (length 256*cosineLength). calculated by updateLut() 34 | uint32_t baseIndex; // base index for the lookup table 35 | uint32_t cosineLength; // length of the cosine (lookup table length / 256) 36 | int32_t mixFrequency; // frequency of the cosine that is mixed to the signal 37 | uint32_t sampleRate; // sample rate of the incoming signals 38 | uint8_t signedFlag; // flag that indicates if lookup table should be based on signed (!=0) or unsigned (==0) values 39 | rs_allocation outReal; // Output Allocation for real array 40 | rs_allocation outImag; // Output Allocation for imag array 41 | uint32_t offset; // Start index in the output allocations (first index that will be written) 42 | uint32_t len; // Length of the output allocations (length-1 is max index that will be written) 43 | 44 | void updateLut(const float *in, uint32_t x) { 45 | if(x < cosineLength) { 46 | if( signedFlag != 0 ) { // signed 47 | float cosineAtT = cos(2 * M_PI * mixFrequency * x / sampleRate); 48 | float sineAtT = sin(2 * M_PI * mixFrequency * x / sampleRate); 49 | for (int i = 0; i < 256; i++) { 50 | *(lutReal + x*256 + i) = (i - 128) / 128.0f * cosineAtT; 51 | *(lutImag + x*256 + i) = (i - 128) / 128.0f * sineAtT; 52 | } 53 | } else { // unsigned 54 | float cosineAtT = cos(2 * M_PI * mixFrequency * x / sampleRate); 55 | float sineAtT = sin(2 * M_PI * mixFrequency * x / sampleRate); 56 | for (int i = 0; i < 256; i++) { 57 | *(lutReal + x*256 + i) = (i - 127.4f) / 128.0f * cosineAtT; 58 | *(lutImag + x*256 + i) = (i - 127.4f) / 128.0f * sineAtT; 59 | } 60 | } 61 | } 62 | } 63 | 64 | void mixSignedInterleavedKernel(const char *in, uint32_t x) { 65 | uint32_t outIndex = x >> 1; 66 | if(outIndex + offset >= len) 67 | return; // reached max index 68 | uint32_t lutOffset = (baseIndex + outIndex) % cosineLength; 69 | if(x & 0x01) 70 | rsSetElementAt_float(outImag, *(lutImag + lutOffset*256 + *in + 128), outIndex + offset); 71 | else 72 | rsSetElementAt_float(outReal, *(lutReal + lutOffset*256 + *in + 128), outIndex + offset); 73 | } 74 | 75 | void mixUnsignedInterleavedKernel(const char *in, uint32_t x) { 76 | uint32_t outIndex = x >> 1; 77 | if(outIndex + offset >= len) 78 | return; // reached max index 79 | uint32_t lutOffset = (baseIndex + outIndex) % cosineLength; 80 | float value; 81 | if(x & 0x01) { 82 | // the imaginary component is calculated: im * cos - re * sin (note that *(in-1) is re) 83 | value = *(lutReal + lutOffset*256 + (*in & 0xff)) - *(lutImag + lutOffset*256 + (*(in-1) & 0xff)); 84 | rsSetElementAt_float(outImag, value, outIndex + offset); 85 | } else { 86 | // the real component is calculated: re * cos - im * sin (note that *(in+1) is im) 87 | value = *(lutReal + lutOffset*256 + (*in & 0xff)) + *(lutImag + lutOffset*256 + (*(in+1) & 0xff)); 88 | rsSetElementAt_float(outReal, value, outIndex + offset); 89 | } 90 | } -------------------------------------------------------------------------------- /android_dsp_lib/src/main/rs/quad_demod.rs: -------------------------------------------------------------------------------- 1 | /** 2 | * Android DSP library - Quadrature Demodulator 3 | * 4 | * Module: quad_demod.rs 5 | * Description: This kernel can do quadrature demodulation 6 | * 7 | * @author Dennis Mantz 8 | * 9 | * Copyright (C) 2016 Dennis Mantz 10 | * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher 11 | * 12 | * This library is free software; you can redistribute it and/or 13 | * modify it under the terms of the GNU General Public 14 | * License as published by the Free Software Foundation; either 15 | * version 2 of the License, or (at your option) any later version. 16 | * 17 | * This library is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 20 | * General Public License for more details. 21 | * 22 | * You should have received a copy of the GNU General Public 23 | * License along with this library; if not, write to the Free Software 24 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 25 | */ 26 | 27 | #pragma version(1) 28 | #pragma rs_fp_relaxed 29 | #pragma rs java_package_name(com.mantz_it.android_dsp_lib) 30 | 31 | float gain; // Quadrature gain 32 | float historyRe; // history sample from the last demodulation cycle 33 | float historyIm; 34 | rs_allocation inReal; // Input packet 35 | rs_allocation inImag; 36 | rs_allocation outReal; // Output packet 37 | 38 | uint32_t offsetIn; // Start index in the input allocations 39 | uint32_t offsetOut; // Start index in the output allocations (first index that will be written) 40 | uint32_t len; // Length of the output allocations (len-1 is max index that will be written) 41 | 42 | /* 43 | * Will copy the last sample from the input packet (real and imag parts) to the history variables 44 | */ 45 | void saveHistory() { 46 | historyRe = rsGetElementAt_float(inReal, offsetIn + len -1); 47 | historyIm = rsGetElementAt_float(inImag, offsetIn + len -1); 48 | } 49 | 50 | /* 51 | * Kernel: demodulate a complex signal 52 | * Input is expected in inReal and inImag and output will be stored in outReal 53 | * @param out dummy argument (is not used, but has to have the same size as outReal) 54 | * @param x position inside 'out' (given by the runtime) 55 | */ 56 | void demod(float* out, uint32_t x) { 57 | float imInPrev, imIn, reInPrev, reIn; 58 | float resultReal = 0; 59 | float resultImag = 0; 60 | 61 | if(x + offsetOut >= len) 62 | return; // reached max index 63 | 64 | if(x == 0) { 65 | // Use the history Sample 66 | reIn = rsGetElementAt_float(inReal, offsetIn); 67 | imIn = rsGetElementAt_float(inImag, offsetIn); 68 | resultReal = reIn * historyRe + imIn * historyIm; 69 | resultImag = imIn * historyRe - reIn * historyIm; 70 | } else { 71 | reIn = rsGetElementAt_float(inReal, offsetIn + x); 72 | imIn = rsGetElementAt_float(inImag, offsetIn + x); 73 | reInPrev = rsGetElementAt_float(inReal, offsetIn + x-1); 74 | imInPrev = rsGetElementAt_float(inImag, offsetIn + x-1); 75 | resultReal = reIn * reInPrev + imIn * imInPrev; 76 | resultImag = imIn * reInPrev - reIn * imInPrev; 77 | } 78 | rsSetElementAt_float(outReal, gain * atan2(resultImag, resultReal), offsetOut + x); 79 | } -------------------------------------------------------------------------------- /androiddsplibbenchmark/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /androiddsplibbenchmark/androiddsplibbenchmark.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /androiddsplibbenchmark/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 21 5 | buildToolsVersion '25.0.0' 6 | 7 | defaultConfig { 8 | applicationId "com.mantz_it.androiddsplibbenchmark" 9 | minSdkVersion 14 10 | targetSdkVersion 21 11 | versionCode 3 12 | versionName "1.03" 13 | } 14 | 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 19 | } 20 | } 21 | } 22 | 23 | dependencies { 24 | compile fileTree(include: ['*.jar'], dir: 'libs') 25 | compile project(':android_dsp_lib') 26 | } 27 | -------------------------------------------------------------------------------- /androiddsplibbenchmark/changelog.txt: -------------------------------------------------------------------------------- 1 | CHANGELOG - Android DSP library 2 | 3 | version 1.03: 4 | - Including android_dsp_lib 1.02 5 | 6 | version 1.02: 7 | - Including android_dsp_lib 1.01 8 | 9 | version 1.01: 10 | - Add logging 11 | 12 | version 1.00: 13 | - Initial version. Not stable. Not optimized. Not complete. 14 | -------------------------------------------------------------------------------- /androiddsplibbenchmark/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 | -------------------------------------------------------------------------------- /androiddsplibbenchmark/src/androidTest/java/com/mantz_it/androiddsplibbenchmark/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.mantz_it.androiddsplibbenchmark; 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 | } -------------------------------------------------------------------------------- /androiddsplibbenchmark/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 12 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /androiddsplibbenchmark/src/main/java/com/mantz_it/androiddsplibbenchmark/Benchmark.java: -------------------------------------------------------------------------------- 1 | package com.mantz_it.androiddsplibbenchmark; 2 | 3 | import android.content.Context; 4 | 5 | import com.mantz_it.android_dsp_lib.AndroidDSPLib; 6 | import com.mantz_it.android_dsp_lib.FirFilter; 7 | import com.mantz_it.android_dsp_lib.IQConverter; 8 | import com.mantz_it.android_dsp_lib.LowPassFilter; 9 | import com.mantz_it.android_dsp_lib.SamplePacket; 10 | 11 | /** 12 | * Android DSP library - Benchmark 13 | * 14 | * Module: Benchmark.java 15 | * Description: This class will run benchmarks on the DSP library and the legacy implementations 16 | * from RF Analyzer and compare them. This will run in a separate Thread. 17 | * Results will be stored in a cvs file. 18 | * 19 | * @author Dennis Mantz 20 | * 21 | * Copyright (C) 2014 Dennis Mantz 22 | * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher 23 | * 24 | * This library is free software; you can redistribute it and/or 25 | * modify it under the terms of the GNU General Public 26 | * License as published by the Free Software Foundation; either 27 | * version 2 of the License, or (at your option) any later version. 28 | * 29 | * This library is distributed in the hope that it will be useful, 30 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 31 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 32 | * General Public License for more details. 33 | * 34 | * You should have received a copy of the GNU General Public 35 | * License along with this library; if not, write to the Free Software 36 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 37 | */ 38 | public class Benchmark extends Thread { 39 | private BenchmarkCallback callback; 40 | private boolean stopRequested = true; 41 | private String csvValues = null; 42 | private static final int PACKETSIZE = 8192; 43 | 44 | public Benchmark(BenchmarkCallback callback, Context context) { 45 | this.callback = callback; 46 | AndroidDSPLib.init(context); 47 | } 48 | 49 | public void startBenchmark() { 50 | stopRequested = false; 51 | this.start(); 52 | } 53 | 54 | public void stopBenchmark() { 55 | stopRequested = true; 56 | } 57 | 58 | public boolean isRunning() { 59 | return !stopRequested; 60 | } 61 | 62 | public String getCsvValues() { 63 | return csvValues; 64 | } 65 | 66 | public void run() { 67 | callback.println("Benchmark started. Will take about 2 minutes."); 68 | callback.println("Packet size: " + PACKETSIZE + " samples (IQ)\n"); 69 | int rounds; 70 | int threads = 4; 71 | // Time variables: 72 | long millisFillPacketIntoSamplePacket8BitSigned; 73 | long millisFillPacketIntoSamplePacket8BitSigned_legacy; 74 | long millisMixPacketIntoSamplePacket8BitSigned; 75 | long millisMixPacketIntoSamplePacket8BitSigned_legacy; 76 | long millisLowPassFilter; 77 | long millisLowPassFilter_legacy; 78 | long millisDecimatingLowPassFilter; 79 | long millisDecimatingLowPassFilter_legacy; 80 | long millisLowPassFilterThreaded; 81 | long millisLowPassFilterThreaded_legacy; 82 | long millisLowPassFilter9Taps; 83 | long millisLowPassFilter9Taps_legacy; 84 | 85 | // IQConverter: lookup 86 | rounds = 10000; 87 | callback.println("Measure '8-bit signed lookup table' ("+rounds+" rounds)"); 88 | callback.print("DSP lib ..."); 89 | millisFillPacketIntoSamplePacket8BitSigned = measureFillPacketIntoSamplePacket8BitSigned(rounds); 90 | callback.println("\t: " + millisFillPacketIntoSamplePacket8BitSigned + " ms (" 91 | + rounds*PACKETSIZE*1000l/millisFillPacketIntoSamplePacket8BitSigned + " Sps)"); 92 | callback.print("Legacy ..."); 93 | millisFillPacketIntoSamplePacket8BitSigned_legacy = measureFillPacketIntoSamplePacket8BitSigned_legacy(rounds); 94 | callback.println("\t: " + millisFillPacketIntoSamplePacket8BitSigned_legacy + " ms (" 95 | + rounds*PACKETSIZE*1000l/millisFillPacketIntoSamplePacket8BitSigned_legacy + " Sps)"); 96 | if(stopRequested) { 97 | callback.println("aborted!\n"); 98 | callback.onFinish(false); 99 | return; 100 | } else { 101 | callback.println(String.format("Performance gain is %d%%\n", (int)(100 * ((float)millisFillPacketIntoSamplePacket8BitSigned_legacy/millisFillPacketIntoSamplePacket8BitSigned - 1)))); 102 | } 103 | 104 | // IQConverter: mix 105 | rounds = 10000; 106 | callback.println("Measure '8-bit signed mixing' ("+rounds+" rounds)"); 107 | callback.print("DSP lib ..."); 108 | millisMixPacketIntoSamplePacket8BitSigned = measureMixPacketIntoSamplePacket8BitSigned(rounds); 109 | callback.println("\t: " + millisMixPacketIntoSamplePacket8BitSigned + " ms (" 110 | + rounds*PACKETSIZE*1000l/millisMixPacketIntoSamplePacket8BitSigned + " Sps)"); 111 | callback.print("Legacy ..."); 112 | millisMixPacketIntoSamplePacket8BitSigned_legacy = measureMixPacketIntoSamplePacket8BitSigned_legacy(rounds); 113 | callback.println("\t: " + millisMixPacketIntoSamplePacket8BitSigned_legacy + " ms (" 114 | + rounds*PACKETSIZE*1000l/millisMixPacketIntoSamplePacket8BitSigned_legacy + " Sps)"); 115 | if(stopRequested) { 116 | callback.println("aborted!\n"); 117 | callback.onFinish(false); 118 | return; 119 | }else { 120 | callback.println(String.format("Performance gain is %d%%\n", (int)(100 * ((float)millisMixPacketIntoSamplePacket8BitSigned_legacy/millisMixPacketIntoSamplePacket8BitSigned - 1)))); 121 | } 122 | 123 | // LowPassFilter 124 | rounds = 500; 125 | callback.println("Measure 'LowPassFilter' ("+rounds+" rounds)"); 126 | callback.print("DSP lib ... "); 127 | millisLowPassFilter = measureDecimatingLowPassFilter(rounds, 1); 128 | callback.println("\t: " + millisLowPassFilter + " ms (" 129 | + rounds*PACKETSIZE*1000l/millisLowPassFilter + " Sps)"); 130 | callback.print("Legacy ... "); 131 | millisLowPassFilter_legacy = measureDecimatingLowPassFilter_legacy(rounds, 1); 132 | callback.println("\t: " + millisLowPassFilter_legacy + " ms (" 133 | + rounds*PACKETSIZE*1000l/millisLowPassFilter_legacy + " Sps)"); 134 | if(stopRequested) { 135 | callback.println("aborted!\n"); 136 | callback.onFinish(false); 137 | return; 138 | } else { 139 | callback.println(String.format("Performance gain is %d%%\n", (int)(100 * ((float)millisLowPassFilter_legacy/millisLowPassFilter - 1)))); 140 | } 141 | 142 | // LowPassFilter (decimating by 4) 143 | rounds = 500; 144 | callback.println("Measure 'LowPassFilter' ("+rounds+" rounds)"); 145 | callback.print("DSP lib ... "); 146 | millisDecimatingLowPassFilter = measureDecimatingLowPassFilter(rounds, 4); 147 | callback.println("\t: " + millisDecimatingLowPassFilter + " ms (" 148 | + rounds*PACKETSIZE*1000l/millisDecimatingLowPassFilter + " Sps)"); 149 | callback.print("Legacy ... "); 150 | millisDecimatingLowPassFilter_legacy = measureDecimatingLowPassFilter_legacy(rounds, 4); 151 | callback.println("\t: " + millisDecimatingLowPassFilter_legacy + " ms (" 152 | + rounds*PACKETSIZE*1000l/millisDecimatingLowPassFilter_legacy + " Sps)"); 153 | if(stopRequested) { 154 | callback.println("aborted!\n"); 155 | callback.onFinish(false); 156 | return; 157 | } else { 158 | callback.println(String.format("Performance gain is %d%%\n", (int)(100 * ((float)millisDecimatingLowPassFilter_legacy/millisDecimatingLowPassFilter - 1)))); 159 | } 160 | 161 | // LowPassFilter Threaded 162 | rounds = 500 / threads; 163 | callback.println("Measure 'LowPassFilter' (" + threads + " parallel threads) ("+rounds+" rounds)"); 164 | callback.print("DSP lib ... "); 165 | millisLowPassFilterThreaded = measureLowPassFilterThreaded(rounds, threads); 166 | callback.println("\t: " + millisLowPassFilterThreaded + " ms (" 167 | + rounds*PACKETSIZE*1000l/millisLowPassFilterThreaded + " Sps)"); 168 | callback.print("Legacy ... "); 169 | millisLowPassFilterThreaded_legacy = measureLowPassFilterThreaded_legacy(rounds, threads); 170 | callback.println("\t\t: " + millisLowPassFilterThreaded_legacy + " ms (" 171 | + rounds*PACKETSIZE*1000l/millisLowPassFilterThreaded_legacy + " Sps)"); 172 | if(stopRequested) { 173 | callback.println("aborted!\n"); 174 | callback.onFinish(false); 175 | return; 176 | } else { 177 | callback.println(String.format("Performance gain is %d%%\n", (int)(100 * ((float)millisLowPassFilterThreaded_legacy/millisLowPassFilterThreaded - 1)))); 178 | } 179 | 180 | // LowPassFilter with 9 taps 181 | rounds = 500; 182 | callback.println("Measure 'LowPassFilter' ("+rounds+" rounds)"); 183 | callback.print("DSP lib ... "); 184 | millisLowPassFilter9Taps = measureLowPassFilter9Taps(rounds); 185 | callback.println("\t: " + millisLowPassFilter9Taps + " ms (" 186 | + rounds*PACKETSIZE*1000l/millisLowPassFilter9Taps + " Sps)"); 187 | callback.print("Legacy ... "); 188 | millisLowPassFilter9Taps_legacy = measureLowPassFilter9Taps_legacy(rounds); 189 | callback.println("\t: " + millisLowPassFilter9Taps_legacy + " ms (" 190 | + rounds*PACKETSIZE*1000l/millisLowPassFilter9Taps_legacy + " Sps)"); 191 | if(stopRequested) { 192 | callback.println("aborted!\n"); 193 | callback.onFinish(false); 194 | return; 195 | } else { 196 | callback.println(String.format("Performance gain is %d%%\n", (int)(100 * ((float)millisLowPassFilter9Taps_legacy/millisLowPassFilter9Taps - 1)))); 197 | } 198 | 199 | // prepare the csv string: 200 | csvValues = String.format("%d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d", 201 | millisFillPacketIntoSamplePacket8BitSigned, 202 | millisFillPacketIntoSamplePacket8BitSigned_legacy, 203 | millisMixPacketIntoSamplePacket8BitSigned, 204 | millisMixPacketIntoSamplePacket8BitSigned_legacy, 205 | millisLowPassFilter, 206 | millisLowPassFilter_legacy, 207 | millisDecimatingLowPassFilter, 208 | millisDecimatingLowPassFilter_legacy, 209 | millisLowPassFilterThreaded, 210 | millisLowPassFilterThreaded_legacy, 211 | millisLowPassFilter9Taps, 212 | millisLowPassFilter9Taps_legacy); 213 | 214 | callback.println("Benchmark finished."); 215 | callback.onFinish(true); 216 | } 217 | 218 | public long measureFillPacketIntoSamplePacket8BitSigned(int rounds) { 219 | IQConverter iqConverter8BitSigned = new IQConverter(IQConverter.FORMAT_8BIT_SIGNED, 2 * PACKETSIZE); 220 | SamplePacket samplePacket = new SamplePacket(PACKETSIZE); 221 | byte[] data = new byte[2*PACKETSIZE]; 222 | for (int i = 0; i < data.length; i++) { 223 | data[i] = (byte) i; 224 | } 225 | long startTime = System.currentTimeMillis(); 226 | for (int i = 0; i < rounds && !stopRequested; i++) { 227 | iqConverter8BitSigned.fillPacketIntoSamplePacket_8BitSigned(data, samplePacket); 228 | samplePacket.sync(); 229 | samplePacket.setSize(0); 230 | } 231 | return System.currentTimeMillis() - startTime; 232 | } 233 | 234 | public long measureFillPacketIntoSamplePacket8BitSigned_legacy(int rounds) { 235 | com.mantz_it.androiddsplibbenchmark.legacyClasses.IQConverter iqConverter8BitSigned = 236 | new com.mantz_it.androiddsplibbenchmark.legacyClasses.Signed8BitIQConverter(); 237 | com.mantz_it.androiddsplibbenchmark.legacyClasses.SamplePacket samplePacket = 238 | new com.mantz_it.androiddsplibbenchmark.legacyClasses.SamplePacket(PACKETSIZE); 239 | byte[] data = new byte[2*PACKETSIZE]; 240 | for (int i = 0; i < data.length; i++) { 241 | data[i] = (byte) i; 242 | } 243 | long startTime = System.currentTimeMillis(); 244 | for (int i = 0; i < rounds && !stopRequested; i++) { 245 | iqConverter8BitSigned.fillPacketIntoSamplePacket(data, samplePacket); 246 | samplePacket.setSize(0); 247 | } 248 | return System.currentTimeMillis() - startTime; 249 | } 250 | 251 | public long measureMixPacketIntoSamplePacket8BitSigned(int rounds) { 252 | IQConverter iqConverter8BitSigned = new IQConverter(IQConverter.FORMAT_8BIT_SIGNED, 2 * PACKETSIZE); 253 | iqConverter8BitSigned.setFrequency(97000000); 254 | iqConverter8BitSigned.setSampleRate(1000000); 255 | SamplePacket samplePacket = new SamplePacket(PACKETSIZE); 256 | byte[] data = new byte[2*PACKETSIZE]; 257 | for (int i = 0; i < data.length; i++) { 258 | data[i] = (byte) i; 259 | } 260 | long startTime = System.currentTimeMillis(); 261 | for (int i = 0; i < rounds && !stopRequested; i++) { 262 | iqConverter8BitSigned.mixPacketIntoSamplePacket_8BitSigned(data, samplePacket, 96900000); 263 | samplePacket.sync(); 264 | samplePacket.setSize(0); 265 | } 266 | return System.currentTimeMillis() - startTime; 267 | } 268 | 269 | public long measureMixPacketIntoSamplePacket8BitSigned_legacy(int rounds) { 270 | com.mantz_it.androiddsplibbenchmark.legacyClasses.IQConverter iqConverter8BitSigned = 271 | new com.mantz_it.androiddsplibbenchmark.legacyClasses.Signed8BitIQConverter(); 272 | iqConverter8BitSigned.setFrequency(97000000); 273 | iqConverter8BitSigned.setSampleRate(1000000); 274 | com.mantz_it.androiddsplibbenchmark.legacyClasses.SamplePacket samplePacket = 275 | new com.mantz_it.androiddsplibbenchmark.legacyClasses.SamplePacket(PACKETSIZE); 276 | byte[] data = new byte[2*PACKETSIZE]; 277 | for (int i = 0; i < data.length; i++) { 278 | data[i] = (byte) i; 279 | } 280 | long startTime = System.currentTimeMillis(); 281 | for (int i = 0; i < rounds && !stopRequested; i++) { 282 | iqConverter8BitSigned.mixPacketIntoSamplePacket(data, samplePacket, 96900000); 283 | samplePacket.setSize(0); 284 | } 285 | return System.currentTimeMillis() - startTime; 286 | } 287 | 288 | public long measureDecimatingLowPassFilter(int rounds, int decimation) { 289 | LowPassFilter lowPassFilter = new LowPassFilter(decimation, 1, 1000000, 100000, 10000, 40); 290 | callback.print("("+lowPassFilter.getNumberOfTaps()+" taps; decimate by " + decimation+") "); 291 | float[] data = new float[PACKETSIZE]; 292 | for (int i = 0; i < data.length; i++) { 293 | data[i] = (float) i; 294 | } 295 | SamplePacket in = new SamplePacket(data,data,0,1000000); 296 | SamplePacket out = new SamplePacket(PACKETSIZE); 297 | 298 | long startTime = System.currentTimeMillis(); 299 | for (int i = 0; i < rounds && !stopRequested; i++) { 300 | lowPassFilter.filter(in, out, 0, in.size()); 301 | out.setSize(0); 302 | } 303 | return System.currentTimeMillis() - startTime; 304 | } 305 | 306 | public long measureDecimatingLowPassFilter_legacy(int rounds, int decimation) { 307 | com.mantz_it.androiddsplibbenchmark.legacyClasses.FirFilter lowPassFilter = 308 | com.mantz_it.androiddsplibbenchmark.legacyClasses.FirFilter.createLowPass(decimation, 1, 1000000, 100000, 10000, 40); 309 | callback.print("("+lowPassFilter.getNumberOfTaps()+" taps; decimate by " + decimation+") "); 310 | float[] data = new float[PACKETSIZE]; 311 | for (int i = 0; i < data.length; i++) { 312 | data[i] = (float) i; 313 | } 314 | com.mantz_it.androiddsplibbenchmark.legacyClasses.SamplePacket in = 315 | new com.mantz_it.androiddsplibbenchmark.legacyClasses.SamplePacket(data,data,0,1000000); 316 | com.mantz_it.androiddsplibbenchmark.legacyClasses.SamplePacket out = 317 | new com.mantz_it.androiddsplibbenchmark.legacyClasses.SamplePacket(PACKETSIZE); 318 | 319 | long startTime = System.currentTimeMillis(); 320 | for (int i = 0; i < rounds && !stopRequested; i++) { 321 | lowPassFilter.filter(in, out, 0, in.size()); 322 | out.setSize(0); 323 | } 324 | return System.currentTimeMillis() - startTime; 325 | } 326 | 327 | public long measureLowPassFilterThreaded(final int rounds, int threads) { 328 | LowPassFilter[] lowPassFilter = new LowPassFilter[threads]; 329 | for (int i = 0; i < threads; i++) 330 | lowPassFilter[i] = new LowPassFilter(1, 1, 1000000, 100000, 10000, 40); 331 | callback.print("(" + lowPassFilter[0].getNumberOfTaps() + " taps) "); 332 | final float[] data = new float[PACKETSIZE]; 333 | for (int i = 0; i < data.length; i++) 334 | data[i] = (float) i; 335 | 336 | class WorkerThread extends Thread { 337 | LowPassFilter lowPassFilter; 338 | public WorkerThread(LowPassFilter filter) { 339 | this.lowPassFilter = filter; 340 | } 341 | public void run() { 342 | SamplePacket in = new SamplePacket(data, data, 0, 1000000); 343 | SamplePacket out = new SamplePacket(PACKETSIZE); 344 | for (int i = 0; i < rounds && !stopRequested; i++) { 345 | lowPassFilter.filter(in, out, 0, in.size()); 346 | out.setSize(0); 347 | } 348 | } 349 | } 350 | 351 | WorkerThread[] workerThreads = new WorkerThread[threads]; 352 | long startTime = System.currentTimeMillis(); 353 | for (int i = 0; i < threads; i++) { 354 | workerThreads[i] = new WorkerThread(lowPassFilter[i]); 355 | workerThreads[i].start(); 356 | } 357 | for (int i = 0; i < threads; i++) { 358 | try { 359 | workerThreads[i].join(); 360 | } catch (InterruptedException e) { 361 | e.printStackTrace(); 362 | } 363 | } 364 | return System.currentTimeMillis() - startTime; 365 | } 366 | 367 | public long measureLowPassFilterThreaded_legacy(final int rounds, int threads) { 368 | com.mantz_it.androiddsplibbenchmark.legacyClasses.FirFilter[] lowPassFilter = 369 | new com.mantz_it.androiddsplibbenchmark.legacyClasses.FirFilter[threads]; 370 | for (int i = 0; i < threads; i++) 371 | lowPassFilter[i] = com.mantz_it.androiddsplibbenchmark.legacyClasses.FirFilter.createLowPass(1, 1, 1000000, 100000, 10000, 40); 372 | callback.print("(" + lowPassFilter[0].getNumberOfTaps() + " taps) "); 373 | final float[] data = new float[PACKETSIZE]; 374 | for (int i = 0; i < data.length; i++) 375 | data[i] = (float) i; 376 | 377 | class WorkerThread extends Thread { 378 | com.mantz_it.androiddsplibbenchmark.legacyClasses.FirFilter lowPassFilter; 379 | public WorkerThread(com.mantz_it.androiddsplibbenchmark.legacyClasses.FirFilter filter) { 380 | this.lowPassFilter = filter; 381 | } 382 | public void run() { 383 | com.mantz_it.androiddsplibbenchmark.legacyClasses.SamplePacket in = 384 | new com.mantz_it.androiddsplibbenchmark.legacyClasses.SamplePacket(data,data,0,1000000); 385 | com.mantz_it.androiddsplibbenchmark.legacyClasses.SamplePacket out = 386 | new com.mantz_it.androiddsplibbenchmark.legacyClasses.SamplePacket(PACKETSIZE); 387 | for (int i = 0; i < rounds && !stopRequested; i++) { 388 | lowPassFilter.filter(in, out, 0, in.size()); 389 | out.setSize(0); 390 | } 391 | } 392 | } 393 | 394 | WorkerThread[] workerThreads = new WorkerThread[threads]; 395 | long startTime = System.currentTimeMillis(); 396 | for (int i = 0; i < threads; i++) { 397 | workerThreads[i] = new WorkerThread(lowPassFilter[i]); 398 | workerThreads[i].start(); 399 | } 400 | for (int i = 0; i < threads; i++) { 401 | try { 402 | workerThreads[i].join(); 403 | } catch (InterruptedException e) { 404 | e.printStackTrace(); 405 | } 406 | } 407 | return System.currentTimeMillis() - startTime; 408 | } 409 | 410 | public long measureLowPassFilter9Taps(int rounds) { 411 | LowPassFilter lowPassFilter = new LowPassFilter(1, 1, 1000000, 100000, 100000, 20); 412 | callback.print("("+lowPassFilter.getNumberOfTaps()+" taps) "); 413 | float[] data = new float[PACKETSIZE]; 414 | for (int i = 0; i < data.length; i++) { 415 | data[i] = (float) i; 416 | } 417 | SamplePacket in = new SamplePacket(data,data,0,1000000); 418 | SamplePacket out = new SamplePacket(PACKETSIZE); 419 | 420 | long startTime = System.currentTimeMillis(); 421 | for (int i = 0; i < rounds && !stopRequested; i++) { 422 | lowPassFilter.filter(in, out, 0, in.size()); 423 | out.setSize(0); 424 | } 425 | return System.currentTimeMillis() - startTime; 426 | } 427 | 428 | public long measureLowPassFilter9Taps_legacy(int rounds) { 429 | com.mantz_it.androiddsplibbenchmark.legacyClasses.FirFilter lowPassFilter = 430 | com.mantz_it.androiddsplibbenchmark.legacyClasses.FirFilter.createLowPass(1, 1, 1000000, 100000, 100000, 20); 431 | callback.print("("+lowPassFilter.getNumberOfTaps()+" taps) "); 432 | float[] data = new float[PACKETSIZE]; 433 | for (int i = 0; i < data.length; i++) { 434 | data[i] = (float) i; 435 | } 436 | com.mantz_it.androiddsplibbenchmark.legacyClasses.SamplePacket in = 437 | new com.mantz_it.androiddsplibbenchmark.legacyClasses.SamplePacket(data,data,0,1000000); 438 | com.mantz_it.androiddsplibbenchmark.legacyClasses.SamplePacket out = 439 | new com.mantz_it.androiddsplibbenchmark.legacyClasses.SamplePacket(PACKETSIZE); 440 | 441 | long startTime = System.currentTimeMillis(); 442 | for (int i = 0; i < rounds && !stopRequested; i++) { 443 | lowPassFilter.filter(in, out, 0, in.size()); 444 | out.setSize(0); 445 | } 446 | return System.currentTimeMillis() - startTime; 447 | } 448 | 449 | public interface BenchmarkCallback { 450 | public void print(String msg); 451 | public void println(String msg); 452 | public void outputErr(String msg); 453 | public void onFinish(boolean success); 454 | } 455 | } 456 | -------------------------------------------------------------------------------- /androiddsplibbenchmark/src/main/java/com/mantz_it/androiddsplibbenchmark/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.mantz_it.androiddsplibbenchmark; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.content.SharedPreferences; 7 | import android.content.pm.PackageManager; 8 | import android.net.Uri; 9 | import android.opengl.GLES20; 10 | import android.os.Build; 11 | import android.os.Bundle; 12 | import android.os.Environment; 13 | import android.preference.PreferenceManager; 14 | import android.telephony.TelephonyManager; 15 | import android.text.method.ScrollingMovementMethod; 16 | import android.util.Log; 17 | import android.view.Menu; 18 | import android.view.MenuItem; 19 | import android.view.View; 20 | import android.view.WindowManager; 21 | import android.widget.Button; 22 | import android.widget.TextView; 23 | 24 | import java.io.File; 25 | import java.io.FileWriter; 26 | import java.io.IOException; 27 | import java.io.InputStream; 28 | import java.math.BigInteger; 29 | import java.security.SecureRandom; 30 | 31 | /** 32 | * Android DSP library - Main Activity (Benchmark App) 33 | * 34 | * Module: MainActivity.java 35 | * Description: This is the main activity for the benchmark app 36 | * 37 | * @author Dennis Mantz 38 | * 39 | * Copyright (C) 2014 Dennis Mantz 40 | * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher 41 | * 42 | * This library is free software; you can redistribute it and/or 43 | * modify it under the terms of the GNU General Public 44 | * License as published by the Free Software Foundation; either 45 | * version 2 of the License, or (at your option) any later version. 46 | * 47 | * This library is distributed in the hope that it will be useful, 48 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 49 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 50 | * General Public License for more details. 51 | * 52 | * You should have received a copy of the GNU General Public 53 | * License along with this library; if not, write to the Free Software 54 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 55 | */ 56 | 57 | public class MainActivity extends Activity implements Benchmark.BenchmarkCallback { 58 | private Button bt_start; 59 | private Button bt_stop; 60 | private Button bt_submit; 61 | private TextView tv_output; 62 | private Benchmark benchmark; 63 | private File logfile; 64 | private Process logcat; 65 | private String version = "unknown"; 66 | private String manufacturer = "unknown"; 67 | private String model = "unknown"; 68 | private String uniqueID = "unknown"; 69 | private String apiLevel = "unknown"; 70 | private static final String LOGTAG = "ANDROID_DSP_BENCHMARK"; 71 | 72 | @Override 73 | protected void onCreate(Bundle savedInstanceState) { 74 | super.onCreate(savedInstanceState); 75 | setContentView(R.layout.activity_main); 76 | 77 | // Start logging: 78 | try{ 79 | logfile = new File(Environment.getExternalStorageDirectory(), "android_dsp_lib_benchmark_log.txt"); 80 | logcat = Runtime.getRuntime().exec("logcat -f " + logfile.getAbsolutePath()); 81 | Log.i(LOGTAG, "onCreate: log path: " + logfile.getAbsolutePath()); 82 | } catch (Exception e) { 83 | Log.e(LOGTAG, "onCreate: Failed to start logging!"); 84 | } 85 | 86 | bt_start = (Button) findViewById(R.id.bt_start); 87 | bt_stop = (Button) findViewById(R.id.bt_stop); 88 | bt_submit = (Button) findViewById(R.id.bt_submit); 89 | tv_output = (TextView) findViewById(R.id.tv_output); 90 | bt_start.setEnabled(true); 91 | bt_stop.setEnabled(false); 92 | bt_submit.setEnabled(false); 93 | tv_output.setMovementMethod(new ScrollingMovementMethod()); // make it scroll! 94 | 95 | // Get reference to the shared preferences to get unique id: 96 | SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); 97 | uniqueID = preferences.getString("UNIQUE-ID", "unknown"); 98 | 99 | // if no unique id could be find, generate it: 100 | if(uniqueID.equals("unknown")) { 101 | uniqueID = new BigInteger(130, new SecureRandom()).toString(32); 102 | SharedPreferences.Editor edit = preferences.edit(); 103 | edit.putString("UNIQUE-ID", uniqueID); 104 | edit.apply(); 105 | } 106 | 107 | try { 108 | version = getPackageManager().getPackageInfo(getPackageName(), 0).versionName; 109 | } catch (PackageManager.NameNotFoundException e) { 110 | e.printStackTrace(); 111 | } 112 | manufacturer = Build.MANUFACTURER; 113 | model = Build.MODEL; 114 | apiLevel = "" + android.os.Build.VERSION.SDK_INT; 115 | tv_output.setText("Benchmark for the Android DSP library version " + version + 116 | " by Dennis Mantz.\nDevice Manufacturer: " + manufacturer + "\n" + 117 | "Device Model: " + model + "\nAPI-Level: " + apiLevel + "\n\n"); 118 | Log.i(LOGTAG, tv_output.getText().toString()); 119 | } 120 | 121 | @Override 122 | protected void onDestroy() { 123 | if(logcat != null) 124 | logcat.destroy(); 125 | super.onDestroy(); 126 | } 127 | 128 | @Override 129 | public boolean onCreateOptionsMenu(Menu menu) { 130 | // Inflate the menu; this adds items to the action bar if it is present. 131 | getMenuInflater().inflate(R.menu.menu_main, menu); 132 | return true; 133 | } 134 | 135 | @Override 136 | public boolean onOptionsItemSelected(MenuItem item) { 137 | // Handle action bar item clicks here. The action bar will 138 | // automatically handle clicks on the Home/Up button, so long 139 | // as you specify a parent activity in AndroidManifest.xml. 140 | int id = item.getItemId(); 141 | 142 | if (id == R.id.action_showLog) { 143 | Uri uri = Uri.fromFile(logfile); 144 | Intent intent = new Intent(Intent.ACTION_VIEW); 145 | intent.setDataAndType(uri, "text/plain"); 146 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 147 | this.startActivity(intent); 148 | return true; 149 | } 150 | 151 | if (id == R.id.action_settings) { 152 | return true; 153 | } 154 | 155 | return super.onOptionsItemSelected(item); 156 | } 157 | 158 | @Override 159 | protected void onPause() { 160 | super.onPause(); 161 | onBtStopClick(null); 162 | } 163 | 164 | public void onBtStartClick(View view) { 165 | benchmark = new Benchmark(this, this); 166 | bt_start.setEnabled(false); 167 | bt_stop.setEnabled(true); 168 | bt_submit.setEnabled(false); 169 | benchmark.startBenchmark(); 170 | 171 | // Prevent the screen from turning off: 172 | getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 173 | } 174 | 175 | public void onBtStopClick(View view) { 176 | bt_start.setEnabled(true); 177 | bt_stop.setEnabled(false); 178 | bt_submit.setEnabled(false); 179 | if(benchmark != null) 180 | benchmark.stopBenchmark(); 181 | 182 | // allow screen to turn off again: 183 | this.runOnUiThread(new Runnable() { 184 | @Override 185 | public void run() { 186 | getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 187 | } 188 | }); 189 | } 190 | 191 | public void onBtSubmitClick(View view) { 192 | String filename = "1_" + manufacturer + "_" + model + "_" + apiLevel + "_" + uniqueID + ".csv"; 193 | File csvFile = new File(Environment.getExternalStorageDirectory(), filename); 194 | String csvValues = "1, " + manufacturer + ", " + model + ", " + apiLevel + ", " + uniqueID + ", " + benchmark.getCsvValues(); 195 | 196 | // Create csv file: 197 | try { 198 | FileWriter fileWriter = new FileWriter(csvFile); 199 | fileWriter.write(csvValues); 200 | fileWriter.close(); 201 | } catch (IOException e) { 202 | outputErr(e.getMessage()); 203 | e.printStackTrace(); 204 | } 205 | 206 | // Invoke email app: 207 | Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts("mailto", "dennis.mantz@googlemail.com", null)); 208 | intent.putExtra(Intent.EXTRA_SUBJECT, "ANDROID DSP LIB BENCHMARK V1"); 209 | intent.putExtra(Intent.EXTRA_TEXT, tv_output.getText().toString()); 210 | File root = Environment.getExternalStorageDirectory(); 211 | Uri uri = Uri.fromFile(csvFile); 212 | intent.putExtra(Intent.EXTRA_STREAM, uri); 213 | startActivity(Intent.createChooser(intent, "Send email (careful: not all clients will correctly attach the csv file: "+csvFile.getAbsolutePath()+") ...")); 214 | } 215 | 216 | @Override 217 | public void print(final String msg) { 218 | Log.i(LOGTAG, msg); 219 | runOnUiThread(new Runnable() { 220 | @Override 221 | public void run() { 222 | tv_output.append(msg); 223 | } 224 | }); 225 | } 226 | 227 | @Override 228 | public void println(final String msg) { 229 | Log.i(LOGTAG, msg); 230 | runOnUiThread(new Runnable() { 231 | @Override 232 | public void run() { 233 | tv_output.append(msg + "\n"); 234 | } 235 | }); 236 | } 237 | 238 | @Override 239 | public void outputErr(final String msg) { 240 | Log.e(LOGTAG, msg); 241 | runOnUiThread(new Runnable() { 242 | @Override 243 | public void run() { 244 | tv_output.append("ERROR: " + msg + "\n"); 245 | } 246 | }); 247 | } 248 | 249 | @Override 250 | public void onFinish(final boolean success) { 251 | runOnUiThread(new Runnable() { 252 | @Override 253 | public void run() { 254 | bt_start.setEnabled(true); 255 | bt_stop.setEnabled(false); 256 | bt_submit.setEnabled(success); 257 | 258 | // allow screen to turn off again: 259 | getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 260 | } 261 | }); 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /androiddsplibbenchmark/src/main/java/com/mantz_it/androiddsplibbenchmark/legacyClasses/ComplexFirFilter.java: -------------------------------------------------------------------------------- 1 | package com.mantz_it.androiddsplibbenchmark.legacyClasses; 2 | 3 | import android.util.Log; 4 | 5 | /** 6 | *

RF Analyzer - complex FIR Filter

7 | * 8 | * Module: ComplexFirFilter.java 9 | * Description: This class implements a FIR filter with complex taps. Most of the code is 10 | * copied from the firdes and firfilter module from GNU Radio. 11 | * 12 | * @author Dennis Mantz 13 | * 14 | * Copyright (C) 2014 Dennis Mantz 15 | * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher 16 | * 17 | * This library is free software; you can redistribute it and/or 18 | * modify it under the terms of the GNU General Public 19 | * License as published by the Free Software Foundation; either 20 | * version 2 of the License, or (at your option) any later version. 21 | * 22 | * This library is distributed in the hope that it will be useful, 23 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 24 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 25 | * General Public License for more details. 26 | * 27 | * You should have received a copy of the GNU General Public 28 | * License along with this library; if not, write to the Free Software 29 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 30 | */ 31 | public class ComplexFirFilter { 32 | private int tapCounter = 0; 33 | private float[] tapsReal; 34 | private float[] tapsImag; 35 | private float[] delaysReal; 36 | private float[] delaysImag; 37 | private int decimation; 38 | private int decimationCounter = 1; 39 | private float gain; 40 | private float sampleRate; 41 | private float lowCutOffFrequency; 42 | private float highCutOffFrequency; 43 | private float transitionWidth; 44 | private float attenuation; 45 | private static final String LOGTAG = "ComplexFirFilter"; 46 | 47 | /** 48 | * Private Constructor. Creates a new complex FIR Filter with the given taps and decimation. 49 | * Use create*Filter() to calculate taps and create the filter. 50 | * @param tapsReal filter taps real part 51 | * @param tapsImag filter taps imaginary part 52 | * @param decimation decimation factor 53 | * @param gain filter pass band gain 54 | * @param sampleRate sample rate 55 | * @param lowCutOffFrequency lower cut off frequency (start of pass band) 56 | * @param highCutOffFrequency upper cut off frequency (end of pass band) 57 | * @param transitionWidth width from end of pass band to start stop band 58 | * @param attenuation attenuation of stop band 59 | */ 60 | private ComplexFirFilter(float[] tapsReal, float[] tapsImag, int decimation, float gain, float sampleRate, float lowCutOffFrequency, 61 | float highCutOffFrequency, float transitionWidth, float attenuation) { 62 | if(tapsReal.length != tapsImag.length) 63 | throw new IllegalArgumentException("real and imag filter taps have to be of the same length!"); 64 | this.tapsReal = tapsReal; 65 | this.tapsImag = tapsImag; 66 | this.delaysReal = new float[tapsReal.length]; 67 | this.delaysImag = new float[tapsImag.length]; 68 | this.decimation = decimation; 69 | this.gain = gain; 70 | this.sampleRate = sampleRate; 71 | this.lowCutOffFrequency = lowCutOffFrequency; 72 | this.highCutOffFrequency = highCutOffFrequency; 73 | this.transitionWidth = transitionWidth; 74 | this.attenuation = attenuation; 75 | } 76 | 77 | /** 78 | * @return length of the taps array 79 | */ 80 | public int getNumberOfTaps() { 81 | return tapsReal.length; 82 | } 83 | 84 | public int getDecimation() { 85 | return decimation; 86 | } 87 | 88 | public float getGain() { 89 | return gain; 90 | } 91 | 92 | public float getSampleRate() { 93 | return sampleRate; 94 | } 95 | 96 | public float getLowCutOffFrequency() { 97 | return lowCutOffFrequency; 98 | } 99 | 100 | public float getHighCutOffFrequency() { 101 | return highCutOffFrequency; 102 | } 103 | 104 | public float getTransitionWidth() { 105 | return transitionWidth; 106 | } 107 | 108 | public float getAttenuation() { 109 | return attenuation; 110 | } 111 | 112 | /** 113 | * Filters the samples from the input sample packet and appends filter output to the output 114 | * sample packet. Stops automatically if output sample packet is full. 115 | * @param in input sample packet 116 | * @param out output sample packet 117 | * @param offset offset to use as start index for the input packet 118 | * @param length max number of samples processed from the input packet 119 | * @return number of samples consumed from the input packet 120 | */ 121 | public int filter(SamplePacket in, SamplePacket out, int offset, int length) { 122 | int index; 123 | int indexOut = out.size(); 124 | int outputCapacity = out.capacity(); 125 | float[] reIn = in.re(), imIn = in.im(), reOut = out.re(), imOut = out.im(); 126 | 127 | // insert each input sample into the delay line: 128 | for (int i = 0; i < length; i++) { 129 | delaysReal[tapCounter] = reIn[offset + i]; 130 | delaysImag[tapCounter] = imIn[offset + i]; 131 | 132 | // Calculate the filter output for every Mth element (were M = decimation) 133 | if(decimationCounter == 0) { 134 | // first check if we have enough space in the output buffers: 135 | if(indexOut == outputCapacity) { 136 | out.setSize(indexOut); // update size of output sample packet 137 | out.setSampleRate(in.getSampleRate()/decimation); // update the sample rate of the output sample packet 138 | return i; // We return the number of consumed samples from the input buffers 139 | } 140 | 141 | // Calculate the results: 142 | reOut[indexOut] = 0; 143 | imOut[indexOut] = 0; 144 | index = tapCounter; 145 | for (int j = 0; j < tapsReal.length; j++) { 146 | reOut[indexOut] += tapsReal[j]*delaysReal[index] - tapsImag[j]*delaysImag[index]; 147 | imOut[indexOut] += tapsImag[j]*delaysReal[index] + tapsReal[j]*delaysImag[index]; 148 | index--; 149 | if (index < 0) 150 | index = tapsReal.length - 1; 151 | } 152 | 153 | // increase indexOut: 154 | indexOut++; 155 | } 156 | 157 | // update counters: 158 | decimationCounter++; 159 | if(decimationCounter >= decimation) 160 | decimationCounter = 0; 161 | tapCounter++; 162 | if(tapCounter >= tapsReal.length) 163 | tapCounter = 0; 164 | } 165 | out.setSize(indexOut); // update size of output sample packet 166 | out.setSampleRate(in.getSampleRate()/decimation); // update the sample rate of the output sample packet 167 | return length; // We return the number of consumed samples from the input buffers 168 | } 169 | 170 | /** 171 | * FROM GNU Radio firdes::band_pass_2: 172 | * 173 | * Will calculate the tabs for the specified low pass filter and return a FirFilter instance 174 | * 175 | * @param decimation decimation factor 176 | * @param gain filter pass band gain 177 | * @param sampling_freq sample rate 178 | * @param low_cutoff_freq cut off frequency (beginning of pass band) 179 | * @param high_cutoff_freq cut off frequency (end of pass band) 180 | * @param transition_width width from end of pass band to start stop band 181 | * @param attenuation_dB attenuation of stop band 182 | * @return instance of FirFilter 183 | */ 184 | public static ComplexFirFilter createBandPass(int decimation, 185 | float gain, 186 | float sampling_freq, // Hz 187 | float low_cutoff_freq, // Hz BEGINNING of transition band 188 | float high_cutoff_freq, // Hz END of transition band 189 | float transition_width, // Hz width of transition band 190 | float attenuation_dB) // attenuation dB 191 | { 192 | if (sampling_freq <= 0.0) { 193 | Log.e(LOGTAG, "createBandPass: firdes check failed: sampling_freq > 0"); 194 | return null; 195 | } 196 | 197 | if (low_cutoff_freq < sampling_freq * -0.5 || high_cutoff_freq > sampling_freq * 0.5) { 198 | Log.e(LOGTAG, "createBandPass: firdes check failed: -sampling_freq / 2 < fa <= sampling_freq / 2"); 199 | return null; 200 | } 201 | 202 | if (low_cutoff_freq >= high_cutoff_freq) { 203 | Log.e(LOGTAG,"createBandPass: firdes check failed: low_cutoff_freq >= high_cutoff_freq"); 204 | return null; 205 | } 206 | 207 | if (transition_width <= 0) { 208 | Log.e(LOGTAG,"createBandPass: firdes check failed: transition_width > 0"); 209 | return null; 210 | } 211 | 212 | // Calculate number of tabs 213 | // Based on formula from Multirate Signal Processing for 214 | // Communications Systems, fredric j harris 215 | int ntaps = (int)(attenuation_dB*sampling_freq/(22.0*transition_width)); 216 | if ((ntaps & 1) == 0) // if even... 217 | ntaps++; // ...make odd 218 | 219 | // construct the truncated ideal impulse response 220 | // [sin(x)/x for the low pass case] 221 | // Note: we calculate the real taps for a low pass and shift them 222 | float low_pass_cut_off = (high_cutoff_freq - low_cutoff_freq)/2f; 223 | float[] tapsLowPass = new float[ntaps]; 224 | float[] w = makeWindow(ntaps); 225 | 226 | int M = (ntaps - 1) / 2; 227 | float fwT0 = 2 * (float)Math.PI * low_pass_cut_off / sampling_freq; 228 | for (int n = -M; n <= M; n++) { 229 | if (n == 0) 230 | tapsLowPass[n + M] = fwT0 / (float)Math.PI * w[n + M]; 231 | else { 232 | // a little algebra gets this into the more familiar sin(x)/x form 233 | tapsLowPass[n + M] = (float)Math.sin(n * fwT0) / (n * (float)Math.PI) * w[n + M]; 234 | } 235 | } 236 | 237 | // find the factor to normalize the gain, fmax. 238 | // For low-pass, gain @ zero freq = 1.0 239 | float fmax = tapsLowPass[0 + M]; 240 | for (int n = 1; n <= M; n++) 241 | fmax += 2 * tapsLowPass[n + M]; 242 | float actualGain = gain/fmax; // normalize 243 | for (int i = 0; i < ntaps; i++) 244 | tapsLowPass[i] *= actualGain; 245 | 246 | // calc the band pass taps: 247 | float[] tapsReal = new float[ntaps]; 248 | float[] tapsImag = new float[ntaps]; 249 | float freq = (float)Math.PI * (high_cutoff_freq + low_cutoff_freq)/sampling_freq; 250 | float phase = - freq * ( ntaps/2 ); 251 | 252 | for(int i = 0; i < ntaps; i++) { 253 | tapsReal[i] = tapsLowPass[i] * (float)Math.cos(phase); 254 | tapsImag[i] = tapsLowPass[i] * (float)Math.sin(phase); 255 | phase += freq; 256 | //Log.d(LOGTAG, "createBandPass: Filter Taps [i="+i+"]: " + tapsReal[i] + " " + tapsImag[i]); 257 | } 258 | 259 | return new ComplexFirFilter(tapsReal, tapsImag, decimation, gain, sampling_freq, low_cutoff_freq, high_cutoff_freq, transition_width, attenuation_dB); 260 | } 261 | 262 | /** 263 | * Creates a Blackman Window for a FIR Filter 264 | * 265 | * @param ntabs number of taps of the filter 266 | * @return window samples 267 | */ 268 | private static float[] makeWindow(int ntabs) { 269 | // Make a blackman window: 270 | // w(n)=0.42-0.5cos{(2*PI*n)/(N-1)}+0.08cos{(4*PI*n)/(N-1)}; 271 | float[] window = new float[ntabs]; 272 | for (int i = 0; i < window.length; i++) 273 | window[i] = 0.42f - 0.5f * (float)Math.cos(2 * Math.PI * i / (ntabs - 1)) 274 | + 0.08f * (float)Math.cos(4 * Math.PI * i / (ntabs - 1)); 275 | return window; 276 | } 277 | 278 | } -------------------------------------------------------------------------------- /androiddsplibbenchmark/src/main/java/com/mantz_it/androiddsplibbenchmark/legacyClasses/FirFilter.java: -------------------------------------------------------------------------------- 1 | package com.mantz_it.androiddsplibbenchmark.legacyClasses; 2 | 3 | 4 | import android.util.Log; 5 | 6 | /** 7 | *

RF Analyzer - FIR Filter

8 | * 9 | * Module: FirFilter.java 10 | * Description: This class implements a FIR filter. Most of the code is 11 | * copied from the firdes and firfilter module from GNU Radio. 12 | * 13 | * @author Dennis Mantz 14 | * 15 | * Copyright (C) 2014 Dennis Mantz 16 | * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher 17 | * 18 | * This library is free software; you can redistribute it and/or 19 | * modify it under the terms of the GNU General Public 20 | * License as published by the Free Software Foundation; either 21 | * version 2 of the License, or (at your option) any later version. 22 | * 23 | * This library is distributed in the hope that it will be useful, 24 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 25 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 26 | * General Public License for more details. 27 | * 28 | * You should have received a copy of the GNU General Public 29 | * License along with this library; if not, write to the Free Software 30 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 31 | */ 32 | public class FirFilter { 33 | private int tapCounter = 0; 34 | private float[] taps; 35 | private float[] delaysReal; 36 | private float[] delaysImag; 37 | private int decimation; 38 | private int decimationCounter = 1; 39 | private float gain; 40 | private float sampleRate; 41 | private float cutOffFrequency; 42 | private float transitionWidth; 43 | private float attenuation; 44 | private static final String LOGTAG = "FirFilter"; 45 | 46 | /** 47 | * Private Constructor. Creates a new FIR Filter with the given taps and decimation. 48 | * Use create*Filter() to calculate taps and create the filter. 49 | * @param taps filter taps (double) 50 | * @param decimation decimation factor 51 | * @param gain filter pass band gain 52 | * @param sampleRate sample rate 53 | * @param cutOffFrequency cut off frequency (end of pass band) 54 | * @param transitionWidth width from end of pass band to start stop band 55 | * @param attenuation attenuation of stop band 56 | */ 57 | private FirFilter(float[] taps, int decimation, float gain, float sampleRate, float cutOffFrequency, float transitionWidth, float attenuation) { 58 | this.taps = taps; 59 | this.delaysReal = new float[taps.length]; 60 | this.delaysImag = new float[taps.length]; 61 | this.decimation = decimation; 62 | this.gain = gain; 63 | this.sampleRate = sampleRate; 64 | this.cutOffFrequency = cutOffFrequency; 65 | this.transitionWidth = transitionWidth; 66 | this.attenuation = attenuation; 67 | } 68 | 69 | /** 70 | * @return length of the taps array 71 | */ 72 | public int getNumberOfTaps() { 73 | return taps.length; 74 | } 75 | 76 | public int getDecimation() { 77 | return decimation; 78 | } 79 | 80 | public float getGain() { 81 | return gain; 82 | } 83 | 84 | public float getSampleRate() { 85 | return sampleRate; 86 | } 87 | 88 | public float getCutOffFrequency() { 89 | return cutOffFrequency; 90 | } 91 | 92 | public float getTransitionWidth() { 93 | return transitionWidth; 94 | } 95 | 96 | public float getAttenuation() { 97 | return attenuation; 98 | } 99 | 100 | /** 101 | * Filters the samples from the input sample packet and appends filter output to the output 102 | * sample packet. Stops automatically if output sample packet is full. 103 | * @param in input sample packet 104 | * @param out output sample packet 105 | * @param offset offset to use as start index for the input packet 106 | * @param length max number of samples processed from the input packet 107 | * @return number of samples consumed from the input packet 108 | */ 109 | public int filter(SamplePacket in, SamplePacket out, int offset, int length) { 110 | int index; 111 | int indexOut = out.size(); 112 | int outputCapacity = out.capacity(); 113 | float[] reIn = in.re(), imIn = in.im(), reOut = out.re(), imOut = out.im(); 114 | 115 | // insert each input sample into the delay line: 116 | for (int i = 0; i < length; i++) { 117 | delaysReal[tapCounter] = reIn[offset + i]; 118 | delaysImag[tapCounter] = imIn[offset + i]; 119 | 120 | // Calculate the filter output for every Mth element (were M = decimation) 121 | if(decimationCounter == 0) { 122 | // first check if we have enough space in the output buffers: 123 | if(indexOut == outputCapacity) { 124 | out.setSize(indexOut); // update size of output sample packet 125 | out.setSampleRate(in.getSampleRate()/decimation); // update the sample rate of the output sample packet 126 | return i; // We return the number of consumed samples from the input buffers 127 | } 128 | 129 | // Calculate the results: 130 | reOut[indexOut] = 0; 131 | imOut[indexOut] = 0; 132 | index = tapCounter; 133 | for (float tap : taps) { 134 | reOut[indexOut] += tap * delaysReal[index]; 135 | imOut[indexOut] += tap * delaysImag[index]; 136 | index--; 137 | if (index < 0) 138 | index = taps.length - 1; 139 | } 140 | 141 | // increase indexOut: 142 | indexOut++; 143 | } 144 | 145 | // update counters: 146 | decimationCounter++; 147 | if(decimationCounter >= decimation) 148 | decimationCounter = 0; 149 | tapCounter++; 150 | if(tapCounter >= taps.length) 151 | tapCounter = 0; 152 | } 153 | out.setSize(indexOut); // update size of output sample packet 154 | out.setSampleRate(in.getSampleRate()/decimation); // update the sample rate of the output sample packet 155 | return length; // We return the number of consumed samples from the input buffers 156 | } 157 | 158 | /** 159 | * Filters the real parts of the samples from the input sample packet and appends filter output to the output 160 | * sample packet. Stops automatically if output sample packet is full. 161 | * @param in input sample packet 162 | * @param out output sample packet 163 | * @param offset offset to use as start index for the input packet 164 | * @param length max number of samples processed from the input packet 165 | * @return number of samples consumed from the input packet 166 | */ 167 | public int filterReal(SamplePacket in, SamplePacket out, int offset, int length) { 168 | int index; 169 | int indexOut = out.size(); 170 | int outputCapacity = out.capacity(); 171 | float[] reIn = in.re(), reOut = out.re(); 172 | 173 | // insert each input sample into the delay line: 174 | for (int i = 0; i < length; i++) { 175 | delaysReal[tapCounter] = reIn[offset + i]; 176 | 177 | // Calculate the filter output for every Mth element (were M = decimation) 178 | if(decimationCounter == 0) { 179 | // first check if we have enough space in the output buffers: 180 | if(indexOut == outputCapacity) { 181 | out.setSize(indexOut); // update size of output sample packet 182 | out.setSampleRate(in.getSampleRate()/decimation); // update the sample rate of the output sample packet 183 | return i; // We return the number of consumed samples from the input buffers 184 | } 185 | 186 | // Calculate the results: 187 | reOut[indexOut] = 0; 188 | index = tapCounter; 189 | for (float tap : taps) { 190 | reOut[indexOut] += tap * delaysReal[index]; 191 | index--; 192 | if (index < 0) 193 | index = taps.length - 1; 194 | } 195 | 196 | // increase indexOut: 197 | indexOut++; 198 | } 199 | 200 | // update counters: 201 | decimationCounter++; 202 | if(decimationCounter >= decimation) 203 | decimationCounter = 0; 204 | tapCounter++; 205 | if(tapCounter >= taps.length) 206 | tapCounter = 0; 207 | } 208 | out.setSize(indexOut); // update size of output sample packet 209 | out.setSampleRate(in.getSampleRate()/decimation); // update the sample rate of the output sample packet 210 | return length; // We return the number of consumed samples from the input buffers 211 | } 212 | 213 | /** 214 | * FROM GNU Radio firdes::low_pass_2: 215 | * 216 | * Will calculate the tabs for the specified low pass filter and return a FirFilter instance 217 | * 218 | * @param decimation decimation factor 219 | * @param gain filter pass band gain 220 | * @param sampling_freq sample rate 221 | * @param cutoff_freq cut off frequency (end of pass band) 222 | * @param transition_width width from end of pass band to start stop band 223 | * @param attenuation_dB attenuation of stop band 224 | * @return instance of FirFilter 225 | */ 226 | public static FirFilter createLowPass(int decimation, 227 | float gain, 228 | float sampling_freq, // Hz 229 | float cutoff_freq, // Hz BEGINNING of transition band 230 | float transition_width, // Hz width of transition band 231 | float attenuation_dB) // attenuation dB 232 | { 233 | if (sampling_freq <= 0.0) { 234 | Log.e(LOGTAG,"createLowPass: firdes check failed: sampling_freq > 0"); 235 | return null; 236 | } 237 | 238 | if (cutoff_freq <= 0.0 || cutoff_freq > sampling_freq / 2) { 239 | Log.e(LOGTAG, "createLowPass: firdes check failed: 0 < fa <= sampling_freq / 2"); 240 | return null; 241 | } 242 | 243 | if (transition_width <= 0) { 244 | Log.e(LOGTAG,"createLowPass: firdes check failed: transition_width > 0"); 245 | return null; 246 | } 247 | 248 | // Calculate number of tabs 249 | // Based on formula from Multirate Signal Processing for 250 | // Communications Systems, fredric j harris 251 | int ntaps = (int)(attenuation_dB*sampling_freq/(22.0*transition_width)); 252 | if ((ntaps & 1) == 0) // if even... 253 | ntaps++; // ...make odd 254 | 255 | // construct the truncated ideal impulse response 256 | // [sin(x)/x for the low pass case] 257 | 258 | float[] taps = new float[ntaps]; 259 | float[] w = makeWindow(ntaps); 260 | 261 | int M = (ntaps - 1) / 2; 262 | float fwT0 = 2 * (float)Math.PI * cutoff_freq / sampling_freq; 263 | for (int n = -M; n <= M; n++) { 264 | if (n == 0) 265 | taps[n + M] = fwT0 / (float)Math.PI * w[n + M]; 266 | else { 267 | // a little algebra gets this into the more familiar sin(x)/x form 268 | taps[n + M] = (float)Math.sin(n * fwT0) / (n * (float)Math.PI) * w[n + M]; 269 | } 270 | } 271 | 272 | // find the factor to normalize the gain, fmax. 273 | // For low-pass, gain @ zero freq = 1.0 274 | 275 | float fmax = taps[0 + M]; 276 | for (int n = 1; n <= M; n++) 277 | fmax += 2 * taps[n + M]; 278 | 279 | float actualGain = gain/fmax; // normalize 280 | 281 | for (int i = 0; i < ntaps; i++) 282 | taps[i] *= actualGain; 283 | 284 | return new FirFilter(taps, decimation, gain, sampling_freq, cutoff_freq, transition_width, attenuation_dB); 285 | } 286 | 287 | /** 288 | * Creates a Blackman Window for a FIR Filter 289 | * 290 | * @param ntabs number of taps of the filter 291 | * @return window samples 292 | */ 293 | private static float[] makeWindow(int ntabs) { 294 | // Make a blackman window: 295 | // w(n)=0.42-0.5cos{(2*PI*n)/(N-1)}+0.08cos{(4*PI*n)/(N-1)}; 296 | float[] window = new float[ntabs]; 297 | for (int i = 0; i < window.length; i++) 298 | window[i] = 0.42f - 0.5f * (float)Math.cos(2 * Math.PI * i / (ntabs - 1)) 299 | + 0.08f * (float)Math.cos(4 * Math.PI * i / (ntabs - 1)); 300 | return window; 301 | } 302 | 303 | } -------------------------------------------------------------------------------- /androiddsplibbenchmark/src/main/java/com/mantz_it/androiddsplibbenchmark/legacyClasses/IQConverter.java: -------------------------------------------------------------------------------- 1 | package com.mantz_it.androiddsplibbenchmark.legacyClasses; 2 | 3 | /** 4 | *

RF Analyzer - IQ Converter

5 | * 6 | * Module: IQConverter.java 7 | * Description: This class implements methods to convert the raw input data of IQ sources (bytes) 8 | * to SamplePackets. It has also methods to do converting and down-mixing at the same 9 | * time. 10 | * 11 | * @author Dennis Mantz 12 | * 13 | * Copyright (C) 2014 Dennis Mantz 14 | * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher 15 | * 16 | * This library is free software; you can redistribute it and/or 17 | * modify it under the terms of the GNU General Public 18 | * License as published by the Free Software Foundation; either 19 | * version 2 of the License, or (at your option) any later version. 20 | * 21 | * This library is distributed in the hope that it will be useful, 22 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 23 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 24 | * General Public License for more details. 25 | * 26 | * You should have received a copy of the GNU General Public 27 | * License along with this library; if not, write to the Free Software 28 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 29 | */ 30 | public abstract class IQConverter { 31 | protected long frequency = 0; // Baseband frequency of the converted samples (is put into the SamplePacket) 32 | protected int sampleRate = 0; // Sample rate of the converted samples (is put into the SamplePacket) 33 | protected float[] lookupTable = null; // Lookup table to transform IQ bytes into doubles 34 | protected float[][] cosineRealLookupTable = null; // Lookup table to transform IQ bytes into frequency shifted doubles 35 | protected float[][] cosineImagLookupTable = null; // Lookup table to transform IQ bytes into frequency shifted doubles 36 | protected int cosineFrequency; // Frequency of the cosine that is mixed to the signal 37 | protected int cosineIndex; // current index within the cosine 38 | protected static final int MAX_COSINE_LENGTH = 500; // Max length of the cosine lookup table 39 | 40 | public IQConverter() { 41 | generateLookupTable(); 42 | } 43 | 44 | public long getFrequency() { 45 | return frequency; 46 | } 47 | 48 | public void setFrequency(long frequency) { 49 | this.frequency = frequency; 50 | } 51 | 52 | public int getSampleRate() { 53 | return sampleRate; 54 | } 55 | 56 | public void setSampleRate(int sampleRate) { 57 | if(this.sampleRate != sampleRate) { 58 | this.sampleRate = sampleRate; 59 | generateMixerLookupTable(cosineFrequency); 60 | } 61 | } 62 | 63 | protected int calcOptimalCosineLength() { 64 | // look for the best fitting array size to hold one or more full cosine cycles: 65 | double cycleLength = sampleRate / Math.abs((double)cosineFrequency); 66 | int bestLength = (int) cycleLength; 67 | double bestLengthError = Math.abs(bestLength-cycleLength); 68 | for (int i = 1; i*cycleLength < MAX_COSINE_LENGTH ; i++) { 69 | if(Math.abs(i*cycleLength - (int)(i*cycleLength)) < bestLengthError) { 70 | bestLength = (int)(i*cycleLength); 71 | bestLengthError = Math.abs(bestLength - (i*cycleLength)); 72 | } 73 | } 74 | return bestLength; 75 | } 76 | 77 | public abstract int fillPacketIntoSamplePacket(byte[] packet, SamplePacket samplePacket); 78 | 79 | public abstract int mixPacketIntoSamplePacket(byte[] packet, SamplePacket samplePacket, long channelFrequency); 80 | 81 | protected abstract void generateLookupTable(); 82 | 83 | protected abstract void generateMixerLookupTable(int mixFrequency); 84 | 85 | 86 | } 87 | -------------------------------------------------------------------------------- /androiddsplibbenchmark/src/main/java/com/mantz_it/androiddsplibbenchmark/legacyClasses/SamplePacket.java: -------------------------------------------------------------------------------- 1 | package com.mantz_it.androiddsplibbenchmark.legacyClasses; 2 | 3 | /** 4 | *

RF Analyzer - Sample Packet

5 | * 6 | * Module: SamplePacket.java 7 | * Description: This class encapsulates a packet of complex samples. 8 | * 9 | * @author Dennis Mantz 10 | * 11 | * Copyright (C) 2014 Dennis Mantz 12 | * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher 13 | * 14 | * This library is free software; you can redistribute it and/or 15 | * modify it under the terms of the GNU General Public 16 | * License as published by the Free Software Foundation; either 17 | * version 2 of the License, or (at your option) any later version. 18 | * 19 | * This library is distributed in the hope that it will be useful, 20 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 22 | * General Public License for more details. 23 | * 24 | * You should have received a copy of the GNU General Public 25 | * License along with this library; if not, write to the Free Software 26 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 27 | */ 28 | public class SamplePacket { 29 | private float[] re; // real values 30 | private float[] im; // imag values 31 | private long frequency; // center frequency 32 | private int sampleRate; // sample rate 33 | private int size; // number of samples in this packet 34 | 35 | /** 36 | * Constructor. This constructor wraps existing arrays and set the number of 37 | * samples to the length of the arrays 38 | * 39 | * @param re array of real parts of the sample values 40 | * @param im array of imaginary parts of the sample values 41 | * @param frequency center frequency 42 | * @param sampleRate sample rate 43 | */ 44 | public SamplePacket(float[] re, float im[], long frequency, int sampleRate) { 45 | this(re, im, frequency, sampleRate, re.length); 46 | } 47 | 48 | /** 49 | * Constructor. This constructor wraps existing arrays and allows to set the 50 | * number of samples in this packet to something smaller than the array length 51 | * 52 | * @param re array of real parts of the sample values 53 | * @param im array of imaginary parts of the sample values 54 | * @param frequency center frequency 55 | * @param sampleRate sample rate 56 | * @param size number of samples in this packet ( <= arrays.length ) 57 | */ 58 | public SamplePacket(float[] re, float im[], long frequency, int sampleRate, int size) { 59 | if(re.length != im.length) 60 | throw new IllegalArgumentException("Arrays must be of the same length"); 61 | if(size > re.length) 62 | throw new IllegalArgumentException("Size must be of the smaller or equal the array length"); 63 | this.re = re; 64 | this.im = im; 65 | this.frequency = frequency; 66 | this.sampleRate = sampleRate; 67 | this.size = size; 68 | } 69 | 70 | /** 71 | * Constructor. This constructor allocates two fresh arrays 72 | * 73 | * @param size Number of samples in this packet 74 | */ 75 | public SamplePacket(int size) { 76 | this.re = new float[size]; 77 | this.im = new float[size]; 78 | this.frequency = 0; 79 | this.sampleRate = 0; 80 | this.size = 0; 81 | } 82 | 83 | /** 84 | * @return the reference to the array of real parts 85 | */ 86 | public float[] re() { 87 | return re; 88 | } 89 | 90 | /** 91 | * Returns the real part at the specified index 92 | * 93 | * @param i index 94 | * @return real part of the sample with the given index 95 | */ 96 | public float re(int i) { 97 | return re[i]; 98 | } 99 | 100 | /** 101 | * @return the reference to the array of imaginary parts 102 | */ 103 | public float[] im() { 104 | return im; 105 | } 106 | 107 | /** 108 | * Returns the imaginary part at the specified index 109 | * 110 | * @param i index 111 | * @return imaginary part of the sample with the given index 112 | */ 113 | public float im(int i) { 114 | return im[i]; 115 | } 116 | 117 | /** 118 | * @return the length of the arrays 119 | */ 120 | public int capacity() { 121 | return re.length; 122 | } 123 | 124 | /** 125 | * @return number of samples in this packet 126 | */ 127 | public int size() { 128 | return size; 129 | } 130 | 131 | /** 132 | * Sets a new size (number of samples in this packet) 133 | * @param size number of (valid) samples in this packet 134 | */ 135 | public void setSize(int size) { 136 | this.size = Math.min(size, re.length); 137 | } 138 | 139 | /** 140 | * @return center frequency at which these samples where recorded 141 | */ 142 | public long getFrequency() { 143 | return frequency; 144 | } 145 | 146 | /** 147 | * @return sample rate at which these samples were recorded 148 | */ 149 | public int getSampleRate() { 150 | return sampleRate; 151 | } 152 | 153 | /** 154 | * Sets the center frequency for this sample packet 155 | * @param frequency center frequency at which these samples were recorded 156 | */ 157 | public void setFrequency(long frequency) { 158 | this.frequency = frequency; 159 | } 160 | 161 | /** 162 | * Sets the sample rate for this sample packet 163 | * @param sampleRate sample rate at which these samples were recorded 164 | */ 165 | public void setSampleRate(int sampleRate) { 166 | this.sampleRate = sampleRate; 167 | } 168 | } -------------------------------------------------------------------------------- /androiddsplibbenchmark/src/main/java/com/mantz_it/androiddsplibbenchmark/legacyClasses/Signed8BitIQConverter.java: -------------------------------------------------------------------------------- 1 | package com.mantz_it.androiddsplibbenchmark.legacyClasses; 2 | 3 | /** 4 | *

RF Analyzer - signed 8-bit IQ Converter

5 | * 6 | * Module: Signed8BitIQConverter.java 7 | * Description: This class implements methods to convert the raw input data of IQ sources (8 bit signed) 8 | * to SamplePackets. It has also methods to do converting and down-mixing at the same 9 | * time. 10 | * 11 | * @author Dennis Mantz 12 | * 13 | * Copyright (C) 2014 Dennis Mantz 14 | * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher 15 | * 16 | * This library is free software; you can redistribute it and/or 17 | * modify it under the terms of the GNU General Public 18 | * License as published by the Free Software Foundation; either 19 | * version 2 of the License, or (at your option) any later version. 20 | * 21 | * This library is distributed in the hope that it will be useful, 22 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 23 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 24 | * General Public License for more details. 25 | * 26 | * You should have received a copy of the GNU General Public 27 | * License along with this library; if not, write to the Free Software 28 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 29 | */ 30 | public class Signed8BitIQConverter extends IQConverter { 31 | 32 | public Signed8BitIQConverter() { 33 | super(); 34 | } 35 | 36 | @Override 37 | protected void generateLookupTable() { 38 | /** 39 | * The HackRF delivers samples in the following format: 40 | * The bytes are interleaved, 8-bit, signed IQ samples (in-phase 41 | * component first, followed by the quadrature component): 42 | * 43 | * [--------- first sample ----------] [-------- second sample --------] 44 | * I Q I Q ... 45 | * receivedBytes[0] receivedBytes[1] receivedBytes[2] ... 46 | */ 47 | 48 | lookupTable = new float[256]; 49 | for (int i = 0; i < 256; i++) 50 | lookupTable[i] = (i-128) / 128.0f; 51 | } 52 | 53 | @Override 54 | protected void generateMixerLookupTable(int mixFrequency) { 55 | // If mix frequency is too low, just add the sample rate (sampled spectrum is periodic): 56 | if(mixFrequency == 0 || (sampleRate / Math.abs(mixFrequency) > MAX_COSINE_LENGTH)) 57 | mixFrequency += sampleRate; 58 | 59 | // Only generate lookupTable if null or invalid: 60 | if(cosineRealLookupTable == null || mixFrequency != cosineFrequency) { 61 | cosineFrequency = mixFrequency; 62 | int bestLength = calcOptimalCosineLength(); 63 | cosineRealLookupTable = new float[bestLength][256]; 64 | cosineImagLookupTable = new float[bestLength][256]; 65 | float cosineAtT; 66 | float sineAtT; 67 | for (int t = 0; t < bestLength; t++) { 68 | cosineAtT = (float) Math.cos(2 * Math.PI * cosineFrequency * t / (float) sampleRate); 69 | sineAtT = (float) Math.sin(2 * Math.PI * cosineFrequency * t / (float) sampleRate); 70 | for (int i = 0; i < 256; i++) { 71 | cosineRealLookupTable[t][i] = (i - 128) / 128.0f * cosineAtT; 72 | cosineImagLookupTable[t][i] = (i - 128) / 128.0f * sineAtT; 73 | } 74 | } 75 | cosineIndex = 0; 76 | } 77 | } 78 | 79 | @Override 80 | public int fillPacketIntoSamplePacket(byte[] packet, SamplePacket samplePacket) { 81 | int capacity = samplePacket.capacity(); 82 | int count = 0; 83 | int startIndex = samplePacket.size(); 84 | float[] re = samplePacket.re(); 85 | float[] im = samplePacket.im(); 86 | for (int i = 0; i < packet.length; i+=2) { 87 | re[startIndex+count] = lookupTable[packet[i]+128]; 88 | im[startIndex+count] = lookupTable[packet[i+1]+128]; 89 | count++; 90 | if(startIndex+count >= capacity) 91 | break; 92 | } 93 | samplePacket.setSize(samplePacket.size()+count); // update the size of the sample packet 94 | samplePacket.setSampleRate(sampleRate); // update the sample rate 95 | samplePacket.setFrequency(frequency); // update the frequency 96 | return count; 97 | } 98 | 99 | @Override 100 | public int mixPacketIntoSamplePacket(byte[] packet, SamplePacket samplePacket, long channelFrequency) { 101 | int mixFrequency = (int)(frequency - channelFrequency); 102 | 103 | generateMixerLookupTable(mixFrequency); // will only generate table if really necessary 104 | 105 | // Mix the samples from packet and store the results in the samplePacket 106 | int capacity = samplePacket.capacity(); 107 | int count = 0; 108 | int startIndex = samplePacket.size(); 109 | float[] re = samplePacket.re(); 110 | float[] im = samplePacket.im(); 111 | for (int i = 0; i < packet.length; i+=2) { 112 | re[startIndex+count] = cosineRealLookupTable[cosineIndex][packet[i]+128] - cosineImagLookupTable[cosineIndex][packet[i+1]+128]; 113 | im[startIndex+count] = cosineRealLookupTable[cosineIndex][packet[i+1]+128] + cosineImagLookupTable[cosineIndex][packet[i]+128]; 114 | cosineIndex = (cosineIndex + 1) % cosineRealLookupTable.length; 115 | count++; 116 | if(startIndex+count >= capacity) 117 | break; 118 | } 119 | samplePacket.setSize(samplePacket.size()+count); // update the size of the sample packet 120 | samplePacket.setSampleRate(sampleRate); // update the sample rate 121 | samplePacket.setFrequency(channelFrequency); // update the frequency 122 | return count; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /androiddsplibbenchmark/src/main/java/com/mantz_it/androiddsplibbenchmark/legacyClasses/Unsigned8BitIQConverter.java: -------------------------------------------------------------------------------- 1 | package com.mantz_it.androiddsplibbenchmark.legacyClasses; 2 | 3 | /** 4 | *

RF Analyzer - unsigned 8-bit IQ Converter

5 | * 6 | * Module: Unsigned8BitIQConverter.java 7 | * Description: This class implements methods to convert the raw input data of IQ sources (8 bit unsigned) 8 | * to SamplePackets. It has also methods to do converting and down-mixing at the same 9 | * time. 10 | * 11 | * @author Dennis Mantz 12 | * 13 | * Copyright (C) 2014 Dennis Mantz 14 | * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher 15 | * 16 | * This library is free software; you can redistribute it and/or 17 | * modify it under the terms of the GNU General Public 18 | * License as published by the Free Software Foundation; either 19 | * version 2 of the License, or (at your option) any later version. 20 | * 21 | * This library is distributed in the hope that it will be useful, 22 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 23 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 24 | * General Public License for more details. 25 | * 26 | * You should have received a copy of the GNU General Public 27 | * License along with this library; if not, write to the Free Software 28 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 29 | */ 30 | public class Unsigned8BitIQConverter extends IQConverter { 31 | 32 | public Unsigned8BitIQConverter() { 33 | super(); 34 | } 35 | 36 | @Override 37 | protected void generateLookupTable() { 38 | /** 39 | * The rtl_sdr delivers samples in the following format: 40 | * The bytes are interleaved, 8-bit, unsigned IQ samples (in-phase 41 | * component first, followed by the quadrature component): 42 | * 43 | * [--------- first sample ----------] [-------- second sample --------] 44 | * I Q I Q ... 45 | * receivedBytes[0] receivedBytes[1] receivedBytes[2] ... 46 | */ 47 | 48 | lookupTable = new float[256]; 49 | for (int i = 0; i < 256; i++) 50 | lookupTable[i] = (i-127.4f) / 128.0f; 51 | } 52 | 53 | @Override 54 | protected void generateMixerLookupTable(int mixFrequency) { 55 | // If mix frequency is too low, just add the sample rate (sampled spectrum is periodic): 56 | if(mixFrequency == 0 || (sampleRate / Math.abs(mixFrequency) > MAX_COSINE_LENGTH)) 57 | mixFrequency += sampleRate; 58 | 59 | // Only generate lookupTable if null or invalid: 60 | if(cosineRealLookupTable == null || mixFrequency != cosineFrequency) { 61 | cosineFrequency = mixFrequency; 62 | int bestLength = calcOptimalCosineLength(); 63 | cosineRealLookupTable = new float[bestLength][256]; 64 | cosineImagLookupTable = new float[bestLength][256]; 65 | float cosineAtT; 66 | float sineAtT; 67 | for (int t = 0; t < bestLength; t++) { 68 | cosineAtT = (float) Math.cos(2 * Math.PI * cosineFrequency * t / (float) sampleRate); 69 | sineAtT = (float) Math.sin(2 * Math.PI * cosineFrequency * t / (float) sampleRate); 70 | for (int i = 0; i < 256; i++) { 71 | cosineRealLookupTable[t][i] = (i-127.4f)/128.0f * cosineAtT; 72 | cosineImagLookupTable[t][i] = (i-127.4f)/128.0f * sineAtT; 73 | } 74 | } 75 | cosineIndex = 0; 76 | } 77 | } 78 | 79 | @Override 80 | public int fillPacketIntoSamplePacket(byte[] packet, SamplePacket samplePacket) { 81 | int capacity = samplePacket.capacity(); 82 | int count = 0; 83 | int startIndex = samplePacket.size(); 84 | float[] re = samplePacket.re(); 85 | float[] im = samplePacket.im(); 86 | for (int i = 0; i < packet.length; i+=2) { 87 | re[startIndex+count] = lookupTable[packet[i] & 0xff]; 88 | im[startIndex+count] = lookupTable[packet[i+1] & 0xff]; 89 | count++; 90 | if(startIndex+count >= capacity) 91 | break; 92 | } 93 | samplePacket.setSize(samplePacket.size()+count); // update the size of the sample packet 94 | samplePacket.setSampleRate(sampleRate); // update the sample rate 95 | samplePacket.setFrequency(frequency); // update the frequency 96 | return count; 97 | } 98 | 99 | @Override 100 | public int mixPacketIntoSamplePacket(byte[] packet, SamplePacket samplePacket, long channelFrequency) { 101 | int mixFrequency = (int)(frequency - channelFrequency); 102 | 103 | generateMixerLookupTable(mixFrequency); // will only generate table if really necessary 104 | 105 | // Mix the samples from packet and store the results in the samplePacket 106 | int capacity = samplePacket.capacity(); 107 | int count = 0; 108 | int startIndex = samplePacket.size(); 109 | float[] re = samplePacket.re(); 110 | float[] im = samplePacket.im(); 111 | for (int i = 0; i < packet.length; i+=2) { 112 | re[startIndex+count] = cosineRealLookupTable[cosineIndex][packet[i] & 0xff] - cosineImagLookupTable[cosineIndex][packet[i+1] & 0xff]; 113 | im[startIndex+count] = cosineRealLookupTable[cosineIndex][packet[i+1] & 0xff] + cosineImagLookupTable[cosineIndex][packet[i] & 0xff]; 114 | cosineIndex = (cosineIndex + 1) % cosineRealLookupTable.length; 115 | count++; 116 | if(startIndex+count >= capacity) 117 | break; 118 | } 119 | samplePacket.setSize(samplePacket.size()+count); // update the size of the sample packet 120 | samplePacket.setSampleRate(sampleRate); // update the sample rate 121 | samplePacket.setFrequency(channelFrequency); // update the frequency 122 | return count; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /androiddsplibbenchmark/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demantz/android_dsp_lib/fac951fe91008043bc2bb8c8377a8687114040c3/androiddsplibbenchmark/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /androiddsplibbenchmark/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demantz/android_dsp_lib/fac951fe91008043bc2bb8c8377a8687114040c3/androiddsplibbenchmark/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /androiddsplibbenchmark/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demantz/android_dsp_lib/fac951fe91008043bc2bb8c8377a8687114040c3/androiddsplibbenchmark/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /androiddsplibbenchmark/src/main/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demantz/android_dsp_lib/fac951fe91008043bc2bb8c8377a8687114040c3/androiddsplibbenchmark/src/main/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /androiddsplibbenchmark/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 17 | 18 |