Dienstag, 7. Oktober 2014

hackrf_android - Using the HackRF with a Android device

Since I received my HackRF last month I wanted to use it with an Android device. I own a Nexus 7 and a Nexus 5 and both are able to act as USB host. I also have an OTG cable to connect other USB devices to them. So I started to port Michael Ossmann's libhackrf library to Android and now I want to present the first alpha version. There is still a lot to do and to implement, but at least it's possible to get some samples out of the air and into the phone/tablet ;)

Receiving at 15 Msps on a Nexus 5
OTG cable (available for ~ 3$)

JNI vs. Pure Java

So the hackrf_android library is entirely written in Java. I thought about using Java Native Interface (JNI) to just reuse the original code from hackrf.c without modifications, but I decided not to do so. The advantage of a pure Java library is, that it is very easy to use (no need to care about NDK and JNI stuff). However, using the JNI approach would have advantages too, e.g. the hackrf.c code is completely tested and it fully implements all features. So maybe in the future I will give it a try.

The hackrf_android library

You can find the library on github: https://github.com/demantz/hackrf_android
The repository contains the library sources and an example application. Binaries of both are also in the rep if you don't want to build them on your own.

Basically the library consists only of the main class: Hackrf. In addition there is an HackrfUsbException class and a HackrfCallbackInterface which both don't contain much code. The Hackrf class has a static method called initHackrf(). This method will try to enumerate and open the HackRF on the USB port. This will require the user to hit OK when he is asked for the permission to do so. Therefore initHackrf() works asynchronously. It takes an instance of HackrfCallbackLibrary as argument and will call the onHackrfReady() method of this instance as soon as the device is ready to use. This call contains an instance of the Hackrf class which is then used for all operations on the device.

There are methods to set and get parameters of the device. Right now it is possible to:

  • Read Board ID from HackRF
  • Read Version from HackRF
  • Read Part ID and Serial Number from HackRF
  • Set Sample Rate of HackRF
  • Set Frequency of HackRF
  • Set Baseband Filter Width of HackRF
  • Compute Baseband Filter Width for given Sample Rate
  • Set VGA Gain (Rx/Tx) of HackRF
  • Set LNA Gain of HackRF
  • Set Amplifier of HackRF
  • Set Antenna Port Power of HackRF
  • Set Transceiver Mode of HackRF

Receiving Samples

The Hackrf class will put the samples into an ArrayBlockingQueue as they arrive over the USB connection. A reference to this queue will be returned to the application when it calls startRx(). The application can then grab the packets from the queue. Each queue element is a byte array containing the samples. The packets (arrays) all have a fixed length, which can be determined by calling getPacketSize(). These packets are the raw packets that are received through the USB connection. Make sure you pass packets you don't need anymore back to the buffer pool of the hackrf library. Otherwise the performance will drop because of the many memory allocations and garbage collection runs. Inside the packets the samples are stored as signed 8-bit values in pairs of two (first the quadrature sample then the in-phase sample - took me some time to find that out^^). Right now I set the packet size to 16KB. This might not be ideal. If it is to low (I started with 512B) the samples aren't getting fast enough from the HackRF to the Android device. If it is to high (I tried 256KB - same as in hackrf.c) then most of the bytes in the packets will be zero. Feel free to experiment with this, all you have to do is change the packetSize attribute in Hackrf.java.

Note that the size of the queue (measured in bytes!) is defined by the application as an argument to initHackrf(). Make sure you choose a large enough queue to buffer the samples between the hackrf library and your application. If the application doesn't pull the samples fast enough out of the queue, it will ran full pretty quickly and the hackrf will stop receiving. What I noticed on the Nexus 7: When writing the samples to a file (using a BufferedOutputStream), you can't set the sample rate to values higher than 2Msps. The write procedure to file will be too slow to get the samples fast enough out of the queue. Can anyone confirm this? It seems strange to me and I don't have this problem with my Nexus 5 (15Msps to a file - works like a charm!).

How to use it - a quick example

The example application I want to show you is also in the git repository. I used Eclipse with the ADT plugin to write it. If you want to do so too, just create two new Android Projects from existing sources and choose the root directory of the repository for the library and the example directory for the example App. Make sure that the example App is either linked against the library project (this should be the default case) or against the hackrf_android.jar file.

The example App shows the user this screen:


After opening the Hackrf the user can show some information about the device (this is equivalent to run the hackrf_info command from the hackrf tools) and he can start receiving. Right now the only parameters that can be adjusted through the GUI is sample rate and frequency. I will extend this as soon as I have time for it. The received samples will be written to the external memory (/storage/sdcard0/Test_HackRF/hackrf_receive.iq). You can view a nice fft of this file by copying it on a linux machine and running baudline with the following settings (Don't forget to adjust the samplerate and baseband frequency):

 cat hackrf_receive.io | baudline -stdin -quadrature -channels 2 -flipcomplex -format s8  -overlap 100 -memory 512 -fftsize 4096 -record -basefrequency 97000000 -samplerate 2000000


Baudline showing the fft of the recorded samples


Now I just want to point out the basic code snippets which are showing how to use the library in your App. Try to compare them to the example App in the repository, but notice, that I stripped off most of the code that is not related to using hackrf_android. So here is a minimal working example of how to receive samples in an App:

public class MainActivity extends Activity implements Runnable,
                                    HackrfCallbackInterface {

private boolean stopRequested = false;

// Called when the button "Open HackRF" is clicked
public void openHackrf(View view) {
    // at 2 Msps we will buffer for 1 second
    int queueSize = 2000000 * 2;    // each sample is 2 bytes
 
    if (!Hackrf.initHackrf(view.getContext(), this, queueSize))
    {
        System.out.println("No HackRF could be found!\n");
    }
}

// Called when the button "RX" is clicked
public void rx(View view) {
    stopRequested = false;
    // Run RX in separate thread to keep GUI responsive:
    new Thread(this).start();
}

// Called by hackrf_android library when device was opened successfully
public void onHackrfReady(Hackrf hackrf) {
    System.out.println("HackRF is ready!\n");
    this.hackrf = hackrf;
}

// Called by hackrf_android library when device could not be opened
public void onHackrfError(String message) {
    System.out.println("Error while opening HackRF: "+message+"\n");
}

// Runs in a separate thread
public void run() {
    try {
        System.out.print("Setting Sample Rate to 2000000 Sps...");
        hackrf.setSampleRate(2000000, 1);
        System.out.print("ok.\nSetting Frequency to 97000000 Hz...");
        hackrf.setFrequency(97000000);
        int bbFilter = Hackrf.computeBasebandFilterBandwidth(
                                            (int)(0.75*2000000));
        System.out.print("ok.\nSet BB Filter to "+bbFilter+" Hz...");
        hackrf.setBasebandFilterBandwidth(basebandFilterWidth);
        System.out.print("ok.\nSetting RX VGA Gain to 20...");
        hackrf.setRxVGAGain(20);
        System.out.print("ok.\nSetting LNA Gain to 8...");
        hackrf.setRxLNAGain(8);
        System.out.print("ok.\nSetting Amplifier to 'off'...");
        hackrf.setAmp(false);
        System.out.print("ok.\nSetting Antenna Power to 'off'...");
        hackrf.setAntennaPower(false);
        System.out.print("ok.\n\n");
     
        File file = new File(Environment.
                getExternalStorageDirectory(), "hackrf_receive.io");
        BufferedOutputStream bufferedOutputStream =
                new BufferedOutputStream(new FileOutputStream(file));
     
        // This starts receiving:
        ArrayBlockingQueue<byte[]> queue = hackrf.startRX();
     
        while(!this.stopRequested)
        {
            // Grab a packet from the queue:
            byte[] packet = queue.poll(1000, TimeUnit.MILLISECONDS);
            bufferedOutputStream.write(packet); // write it to file
            hackrf.returnBufferToBufferPool(receivedBytes);
        }
        bufferedOutputStream.close();
        System.out.print( String.format("Finished! (Avg Rate: " +
            "%4.1f MB/s)\n", hackrf.getAverageTransceiveRate()/1000000.0));
    } catch (HackrfUsbException e) {
        System.out.print("error (USB communication)!\n");
    } catch (IOException e) {
        System.out.print("error (File IO)!\n");
    } catch (InterruptedException e) {
        System.out.print("error (Queue Interrupt)!\n");
    }
}

Porting the hackrf library was fun and easier than I thought. I also learned a lot about USB :) At this point I want to say thank you to Michael Ossmann for creating such a great open source SDR platform. I hope hackrf_android helps to make it even more useful / mobile than it already is!

Let me know if you have issues with the library or if you successfully used it in your own App! Doing DSP on Android devices won't be easy without something like GNU Radio, but I'm curious what you guys will implement ;)

UPDATE:
I made a short video presenting the example application:

Kommentare:

  1. Samsung S3 LTE: works perfect up to 10 Msps. Thanks for the app!

    AntwortenLöschen
  2. Samsung S4 LTE ( GT-I9515L ) : works up to 10 Msps. Thanks !

    AntwortenLöschen
  3. Thanks for testing! I updated the list of supported devices on GitHub!

    AntwortenLöschen
  4. Sony Xperia Z1 (C6906) Android 4.4.4 works in 4M Bsps. Thanks for the app! Great job

    AntwortenLöschen
  5. Danke dass Sie dass für uns testiert haben. Sieht als gute Marke aus. Aus meiner Erfahrung, solche effektive Unternehmen sind immer die, die alle Diele durch sicherer Datenraum realisieren

    AntwortenLöschen
  6. hi bro , i have write error on Samsung Galaxy Note5.. please look :
    Saveing samples to /storage/emulated/0/Test_HackRF/hackrf_android.iq
    error (File IO)!

    AntwortenLöschen
  7. hi bro, i have write error on ULEFONE POWER.. please look :
    Saveing samples to /storage/emulated/0/Test_HackRF/hackrf_android.iq
    error (File IO)!

    AntwortenLöschen
  8. The error was due to the new permission system of Android. Even though the app Manifest requests storage permissions, they are not granted without explicitly asking the user. Version 1.12 of the HackRF_Test app handles this correctly (Now available on GitHub). Alternatively you can go to (Android-)Settings - Apps - HackRF_Test - Permissions - (turn on the Storage Permission)

    Sorry for the late reply, I was busy with exams..

    AntwortenLöschen