com.xzg.fingerprinter.AudioFingerprinter.java Source code

Java tutorial

Introduction

Here is the source code for com.xzg.fingerprinter.AudioFingerprinter.java

Source

/**
 * AudioFingerprinter.java
 * EchoprintLib
 * 
 * Created by Alex Restrepo on 1/22/12.
 * Copyright (C) 2012 Grand Valley State University (http://masl.cis.gvsu.edu/)
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is furnished to do
 * so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package com.xzg.fingerprinter;

import java.io.BufferedReader;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Hashtable;

import net.bluecow.spectro.Clip;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONException;
import org.json.JSONObject;

import android.app.Activity;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.util.Log;

/**
 * Main fingerprinting class<br>
 * This class will record audio from the microphone, generate the fingerprint
 * code using a native library and query the data server for a match
 * 
 * @author Alex Restrepo (MASL)
 * 
 */
public class AudioFingerprinter implements Runnable {
    public final static String META_SCORE_KEY = "meta_score";
    public final static String SCORE_KEY = "score";
    public final static String DELTAT_KEY = "delta_t";
    public final static String NAME_KEY = "song_name";
    public final static String ALBUM_KEY = "release";
    public final static String TITLE_KEY = "track";
    public final static String TRACK_ID_KEY = "track_id";
    public final static String ARTIST_KEY = "artist";

    private final static String[] RESULT_KEYS = { "id", "real_song_hash_match", "real_song_hash_match_time",
            "second_max_num", "second_id", "top25_num", "hash_num", "match_hash_num", "song_name",
            "total_hash_num" };

    private final String SERVER_URL = "http://172.18.184.41:9260/query";

    public final int FREQUENCY = 8000;
    public final int CHANNEL = AudioFormat.CHANNEL_IN_MONO;
    public final int ENCODING = AudioFormat.ENCODING_PCM_16BIT;

    private Thread thread;
    private volatile boolean isRunning = false;
    AudioRecord mRecordInstance = null;

    public byte audioData[];
    private int bufferSize;
    private int secondsToRecord;
    private volatile boolean continuous;

    private AudioFingerprinterListener listener;

    /**
     * Constructor for the class
     * 
     * @param listener
     *            is the AudioFingerprinterListener that will receive the
     *            callbacks
     */
    public AudioFingerprinter(AudioFingerprinterListener listener) {
        this.listener = listener;
    }

    /**
     * Starts the listening / fingerprinting process using the default
     * parameters:<br>
     * A single listening pass of 20 seconds
     */
    public void fingerprint() {
        // set dafault listening time to 20 seconds
        this.fingerprint(20);
    }

    /**
     * Starts a single listening / fingerprinting pass
     * 
     * @param seconds
     *            the seconds of audio to record.
     */
    public void fingerprint(int seconds) {
        // no continuous listening
        this.fingerprint(seconds, false);
    }

    /**
     * Starts the listening / fingerprinting process
     * 
     * @param seconds
     *            the number of seconds to record per pass
     * @param continuous
     *            if true, the class will start a new fingerprinting pass after
     *            each pass
     */
    public void fingerprint(int seconds, boolean continuous) {
        if (this.isRunning)
            return;

        this.continuous = continuous;

        // cap to 30 seconds max, 10 seconds min.
        this.secondsToRecord = Math.max(Math.min(seconds, 30), 1);

        // start the recording thread
        thread = new Thread(this);
        thread.start();
    }

    /**
     * stops the listening / fingerprinting process if there's one in process
     */
    public void stop() {
        this.continuous = false;
        if (mRecordInstance != null)
            mRecordInstance.stop();
    }

    public String fetchServerResult(String code) throws IllegalStateException, IOException {

        StringEntity requestEntity = new StringEntity(code);

        HttpPost post = new HttpPost(SERVER_URL);
        post.setEntity(requestEntity);

        HttpClient client = new DefaultHttpClient();
        HttpResponse response = client.execute(post);
        // Examine the response status
        // Log.d("Fingerprinter", response.getStatusLine().toString());

        // Get hold of the response entity
        HttpEntity entity = response.getEntity();
        // If the response does not enclose an entity, there is no
        // need
        // to worry about connection release

        String result = "";
        if (entity != null) {
            // A Simple JSON Response Read
            InputStream instream = entity.getContent();
            result = convertStreamToString(instream);
            // now you have the string representation of the HTML
            // request
            instream.close();
        }
        return result;
    }

    /**
     * The main thread<br>
     * Records audio and generates the audio fingerprint, then it queries the
     * server for a match and forwards the results to the listener.
     */
    public void run() {
        this.isRunning = true;
        try {
            // create the audio buffer
            // get the minimum buffer size
            int minBufferSize = AudioRecord.getMinBufferSize(FREQUENCY, CHANNEL, ENCODING);

            System.out.println("minBufferSize: " + minBufferSize);
            // and the actual buffer size for the audio to record
            // frequency * seconds to record.
            bufferSize = Math.max(minBufferSize, this.FREQUENCY * this.secondsToRecord);

            System.out.println("BufferSize: " + bufferSize);
            audioData = new byte[bufferSize * 2];

            // start recorder
            mRecordInstance = new AudioRecord(MediaRecorder.AudioSource.MIC, FREQUENCY, CHANNEL, ENCODING,
                    minBufferSize);

            willStartListening();

            mRecordInstance.startRecording();
            boolean firstRun = true;
            do {
                try {
                    willStartListeningPass();

                    long time = System.currentTimeMillis();
                    // fill audio buffer with mic data.
                    int samplesIn = 0;
                    do {
                        samplesIn += mRecordInstance.read(audioData, samplesIn, bufferSize - samplesIn);

                        if (mRecordInstance.getRecordingState() == AudioRecord.RECORDSTATE_STOPPED)
                            break;
                    } while (samplesIn < bufferSize);
                    Log.d("Fingerprinter", "Audio recorded: " + (System.currentTimeMillis() - time) + " millis");

                    // see if the process was stopped.
                    if (mRecordInstance.getRecordingState() == AudioRecord.RECORDSTATE_STOPPED
                            || (!firstRun && !this.continuous))
                        break;

                    Log.d("Fingerprinter", "Recod state: " + mRecordInstance.getRecordingState());
                    byte[] audioDataByteFormat = (audioData);
                    Wave w = new Wave();
                    w.data = audioDataByteFormat;
                    WaveFileManager wm = new WaveFileManager();
                    wm.setWave(w);
                    wm.saveWaveAsFile("/sdcard/xzgrecord.wav");

                    Clip c = Clip.newInstance(audioDataByteFormat, this.FREQUENCY);
                    // create an echoprint codegen wrapper and get the code
                    time = System.currentTimeMillis();
                    Codegen codegen = new Codegen(c);
                    String code = codegen.genCode();
                    // Log.d("Fingerprinter","codegen before");
                    // String code = codegen.generate(audioData, samplesIn);
                    Log.d("Fingerprinter", "codegen after");
                    Log.d("Fingerprinter",
                            "Codegen created in: " + (System.currentTimeMillis() - time) + " millis");
                    //
                    Log.d("Fingerprinter", "code length is " + code.length());
                    if (code.length() == 0) {
                        // no code?
                        // not enough audio data?
                        continue;
                    }

                    // fetch data from echonest
                    long startTime = System.currentTimeMillis();

                    String result = fetchServerResult(code);

                    long endTime = System.currentTimeMillis();
                    long fetchTime = endTime - startTime;
                    Log.d("Fingerprinter", "Results fetched in: " + (fetchTime) + " millis");

                    Log.d("Fingerprinter", "HTTP result: " + result);
                    // parse JSON
                    JSONObject jsonResult = new JSONObject(result);

                    if (jsonResult.has("id"))
                        Log.d("Fingerprinter", "Response code:" + jsonResult.getInt("id"));

                    if (jsonResult.has("id")) {
                        if (jsonResult.getInt("id") >= 0) {
                            Hashtable<String, String> match = parseResult(jsonResult);

                            didFindMatchForCode(match, code);
                        } else
                            didNotFindMatchForCode(code);
                    } else {
                        didFailWithException(new Exception("Unknown error"));
                    }
                    //
                    firstRun = false;

                    didFinishListeningPass();
                } catch (Exception e) {
                    e.printStackTrace();
                    Log.e("Fingerprinter", e.getLocalizedMessage());

                    didFailWithException(e);
                }
            } while (this.continuous);
        } catch (Exception e) {
            e.printStackTrace();
            Log.e("Fingerprinter", e.getLocalizedMessage());

            didFailWithException(e);
        }

        if (mRecordInstance != null) {
            mRecordInstance.stop();
            mRecordInstance.release();
            mRecordInstance = null;
        }
        this.isRunning = false;

        didFinishListening();
    }

    //   private Hashtable<String, String> parseResult(JSONObject jobj)
    //         throws JSONException {
    //      Hashtable<String, String> match = new Hashtable<String, String>();
    //      match.put(TRACK_ID_KEY, jobj.getInt("id") + "");
    //      match.put(SCORE_KEY, jobj.getInt("match_num") + "");
    //      match.put(DELTAT_KEY, jobj.getInt("delta_t") + "");
    //      match.put(NAME_KEY, jobj.getString("song_name")
    //            + "");
    //      return match;
    //   }
    private Hashtable<String, String> parseResult(JSONObject jsonResult) {
        Hashtable<String, String> match = new Hashtable<String, String>();
        try {
            for (int i = 0; i < RESULT_KEYS.length; i++) {
                match.put(RESULT_KEYS[i], jsonResult.getString(RESULT_KEYS[i]));
            }
            /*      match.put("id", jsonResult.getInt("id") + "");
                  match.put("real_snog_hash_match",jsonResult.getInt("real_song_hash_match") + "");
                  match.put("delta_t", jsonResult.getInt("delta_t") + "");
                  match.put("song_name", jsonResult.getString("song_name") + "");
                  match.put("second_max_num", jsonResult.getInt("second_max_num") + "");
                  match.put("second_id", jsonResult.getInt("second_id") + "");
                  match.put("top25_num", jsonResult.getInt("top25_num") + "");
                  match.put("hash_num", jsonResult.getInt("hash_num") + "");
                  match.put("match_hash_num", jsonResult.getInt("match_hash_num") + "");
                  match.put("max_match_hash_num", jsonResult.getInt("max_match_hash_num") + "");*/
        } catch (JSONException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return match;
    }

    public byte[] short2byte(short[] sData) {
        int shortArrsize = sData.length;
        byte[] bytes = new byte[shortArrsize * 2];
        for (int i = 0; i < shortArrsize; i++) {
            bytes[i * 2] = (byte) (sData[i] & 0x00FF);
            bytes[(i * 2) + 1] = (byte) (sData[i] >> 8);
            sData[i] = 0;
        }
        return bytes;

    }

    private static String convertStreamToString(InputStream is) {
        /*
         * To convert the InputStream to String we use the
         * BufferedReader.readLine() method. We iterate until the BufferedReader
         * return null which means there's no more data to read. Each line will
         * appended to a StringBuilder and returned as String.
         */
        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
        StringBuilder sb = new StringBuilder();

        String line = null;
        try {
            while ((line = reader.readLine()) != null) {
                sb.append(line + "\n");
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return sb.toString();
    }

    //   private String messageForCode(int code) {
    //      try {
    //         String codes[] = { "NOT_ENOUGH_CODE", "CANNOT_DECODE",
    //               "SINGLE_BAD_MATCH", "SINGLE_GOOD_MATCH", "NO_RESULTS",
    //               "MULTIPLE_GOOD_MATCH_HISTOGRAM_INCREASED",
    //               "MULTIPLE_GOOD_MATCH_HISTOGRAM_DECREASED",
    //               "MULTIPLE_BAD_HISTOGRAM_MATCH", "MULTIPLE_GOOD_MATCH" };
    //
    //         return codes[code];
    //      } catch (ArrayIndexOutOfBoundsException e) {
    //         return "UNKNOWN";
    //      }
    //   }

    private void didFinishListening() {
        if (listener == null)
            return;

        if (listener instanceof Activity) {
            Activity activity = (Activity) listener;
            activity.runOnUiThread(new Runnable() {
                public void run() {
                    listener.didFinishListening();
                }
            });
        } else
            listener.didFinishListening();
    }

    private void didFinishListeningPass() {
        if (listener == null)
            return;

        if (listener instanceof Activity) {
            Activity activity = (Activity) listener;
            activity.runOnUiThread(new Runnable() {
                public void run() {
                    listener.didFinishListeningPass();
                }
            });
        } else
            listener.didFinishListeningPass();
    }

    private void willStartListening() {
        if (listener == null)
            return;

        if (listener instanceof Activity) {
            Activity activity = (Activity) listener;
            activity.runOnUiThread(new Runnable() {
                public void run() {
                    listener.willStartListening();
                }
            });
        } else
            listener.willStartListening();
    }

    private void willStartListeningPass() {
        if (listener == null)
            return;

        if (listener instanceof Activity) {
            Activity activity = (Activity) listener;
            activity.runOnUiThread(new Runnable() {
                public void run() {
                    listener.willStartListeningPass();
                }
            });
        } else
            listener.willStartListeningPass();
    }

    private void didGenerateFingerprintCode(final String code) {
        if (listener == null)
            return;

        if (listener instanceof Activity) {
            Activity activity = (Activity) listener;
            activity.runOnUiThread(new Runnable() {
                public void run() {
                    listener.didGenerateFingerprintCode(code);
                }
            });
        } else
            listener.didGenerateFingerprintCode(code);
    }

    private void didFindMatchForCode(final Hashtable<String, String> table, final String code) {
        if (listener == null)
            return;

        if (listener instanceof Activity) {
            Activity activity = (Activity) listener;
            activity.runOnUiThread(new Runnable() {
                public void run() {
                    listener.didFindMatchForCode(table, code);
                }
            });
        } else
            listener.didFindMatchForCode(table, code);
    }

    private void didNotFindMatchForCode(final String code) {
        if (listener == null)
            return;

        if (listener instanceof Activity) {
            Activity activity = (Activity) listener;
            activity.runOnUiThread(new Runnable() {
                public void run() {
                    listener.didNotFindMatchForCode(code);
                }
            });
        } else
            listener.didNotFindMatchForCode(code);
    }

    private void didFailWithException(final Exception e) {
        if (listener == null)
            return;

        if (listener instanceof Activity) {
            Activity activity = (Activity) listener;
            activity.runOnUiThread(new Runnable() {
                public void run() {
                    listener.didFailWithException(e);
                }
            });
        } else
            listener.didFailWithException(e);
    }

    /**
     * Interface for the fingerprinter listener<br>
     * Contains the different delegate methods for the fingerprinting process
     * 
     * @author Alex Restrepo
     * 
     */
    public interface AudioFingerprinterListener {
        /**
         * Called when the fingerprinter process loop has finished
         */
        public void didFinishListening();

        /**
         * Called when a single fingerprinter pass has finished
         */
        public void didFinishListeningPass();

        /**
         * Called when the fingerprinter is about to start
         */
        public void willStartListening();

        /**
         * Called when a single listening pass is about to start
         */
        public void willStartListeningPass();

        /**
         * Called when the codegen libary generates a fingerprint code
         * 
         * @param code
         *            the generated fingerprint as a zcompressed, base64 string
         */
        public void didGenerateFingerprintCode(String code);

        /**
         * Called if the server finds a match for the submitted fingerprint code
         * 
         * @param table
         *            a hashtable with the metadata returned from the server
         * @param code
         *            the submited fingerprint code
         */
        public void didFindMatchForCode(Hashtable<String, String> table, String code);

        /**
         * Called if the server DOES NOT find a match for the submitted
         * fingerprint code
         * 
         * @param code
         *            the submited fingerprint code
         */
        public void didNotFindMatchForCode(String code);

        /**
         * Called if there is an error / exception in the fingerprinting process
         * 
         * @param e
         *            an exception with the error
         */
        public void didFailWithException(Exception e);
    }

    public static void main(String[] args) throws IOException {
        String fn = "/home/kevin/Documents/test.wav";
        Wave w = new Wave(fn);
        byte[] data = w.getBytes();
        System.out.println(w.getWaveHeader());
        Clip c = Clip.newInstance(data, w.getWaveHeader().getSampleRate());
        Codegen codegen = new Codegen(c);
        String code = codegen.genCode();
        System.out.println(code);
        System.out.println("ended!");
        // String urlstr = "http://172.18.184.41:5000/query";
        // StringEntity requestEntity = new StringEntity(code);
        // HttpPost post = new HttpPost(urlstr);
        // post.setEntity(requestEntity);
        //
        // HttpClient client = new DefaultHttpClient();
        // HttpResponse response = client.execute(post);
        // Examine the response status
        // Log.d("Fingerprinter",response.getStatusLine().toString());
    }
}