com.ece420.lab3.MainActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.ece420.lab3.MainActivity.java

Source

/*
 * Copyright 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.ece420.lab3;

import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.Timer;
import java.util.TimerTask;

public class MainActivity extends Activity implements ActivityCompat.OnRequestPermissionsResultCallback {
    private static final int AUDIO_ECHO_REQUEST = 0;

    private static final int FFT_SIZE = 2 * 1024;
    private static final int FRAME_SIZE = 1024;

    TextView status_view;
    String nativeSampleRate;
    String nativeSampleBufSize;
    boolean supportRecording;
    Boolean isPlaying;

    ImageView stftView;
    Bitmap bitmap;
    Canvas canvas;
    Paint paint;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        status_view = (TextView) findViewById(R.id.statusView);
        queryNativeAudioParameters();

        // initialize native audio system
        updateNativeAudioUI();
        if (supportRecording) {
            // DARIO
            // 48k and 128
            //            createSLEngine(Integer.parseInt(nativeSampleRate), Integer.parseInt(nativeSampleBufSize));
            createSLEngine(Integer.parseInt(nativeSampleRate), FRAME_SIZE);
        }
        isPlaying = false;

        // STFT portion
        stftView = (ImageView) this.findViewById(R.id.stftView);
        bitmap = Bitmap.createBitmap((int) (FFT_SIZE / 4), (int) 384, Bitmap.Config.ARGB_8888);
        canvas = new Canvas(bitmap);
        canvas.drawColor(Color.BLACK);

        paint = new Paint();
        paint.setColor(Color.GREEN);
        paint.setStyle(Paint.Style.FILL);
        stftView.setImageBitmap(bitmap);

        initializeStftBackgroundThread(10);

        startEcho();
    }

    @Override
    protected void onPause() {
        stopEcho(null);
        super.onPause();
    }

    @Override
    protected void onResume() {
        super.onResume();

        startEcho();
    }

    @Override
    protected void onDestroy() {
        if (supportRecording) {
            if (isPlaying) {
                stopPlay();
            }
            deleteSLEngine();
            isPlaying = false;
        }
        super.onDestroy();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    private void startEcho() {
        if (!supportRecording || isPlaying) {
            return;
        }
        if (!createSLBufferQueueAudioPlayer()) {
            status_view.setText("Failed to create Audio Player");
            return;
        }
        if (!createAudioRecorder()) {
            deleteSLBufferQueueAudioPlayer();
            status_view.setText("Failed to create Audio Recorder");
            return;
        }
        startPlay(); //this must include startRecording()
        isPlaying = true;
        status_view.setText("Engine Echoing ....");
    }

    public void startEcho(View view) {
        if (ActivityCompat.checkSelfPermission(this,
                Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[] { Manifest.permission.RECORD_AUDIO },
                    AUDIO_ECHO_REQUEST);
            status_view.setText("Requesting RECORD_AUDIO Permission...");
            return;
        }
        startEcho();
    }

    public void stopEcho(View view) {
        if (!supportRecording || !isPlaying) {
            return;
        }
        stopPlay(); //this must include stopRecording()
        updateNativeAudioUI();
        deleteSLBufferQueueAudioPlayer();
        deleteAudioRecorder();
        isPlaying = false;
    }

    public void getLowLatencyParameters(View view) {
        updateNativeAudioUI();
        return;
    }

    private void queryNativeAudioParameters() {
        AudioManager myAudioMgr = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
        nativeSampleRate = myAudioMgr.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
        nativeSampleBufSize = myAudioMgr.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER);
        int recBufSize = AudioRecord.getMinBufferSize(Integer.parseInt(nativeSampleRate),
                AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
        supportRecording = true;
        if (recBufSize == AudioRecord.ERROR || recBufSize == AudioRecord.ERROR_BAD_VALUE) {
            supportRecording = false;
        }
    }

    private void updateNativeAudioUI() {
        if (!supportRecording) {
            status_view.setText("Error: Audio recording is not supported");
            return;
        }

        status_view.setText("nativeSampleRate    = " + nativeSampleRate + "\n" + "nativeSampleBufSize = "
                + nativeSampleBufSize + "\n");

    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
            @NonNull int[] grantResults) {
        /*
         * if any permission failed, the sample could not play
         */
        if (AUDIO_ECHO_REQUEST != requestCode) {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
            return;
        }

        if (grantResults.length != 1 || grantResults[0] != PackageManager.PERMISSION_GRANTED) {
            /*
             * When user denied the permission, throw a Toast to prompt that RECORD_AUDIO
             * is necessary; on UI, we display the current status as permission was denied so
             * user know what is going on.
             * This application go back to the original state: it behaves as if the button
             * was not clicked. The assumption is that user will re-click the "start" button
             * (to retry), or shutdown the app in normal way.
             */
            status_view.setText("Error: Permission for RECORD_AUDIO was denied");
            Toast.makeText(getApplicationContext(), getString(R.string.NeedRecordAudioPermission),
                    Toast.LENGTH_SHORT).show();
            return;
        }

        /*
         * When permissions are granted, we prompt the user the status. User would
         * re-try the "start" button to perform the normal operation. This saves us the extra
         * logic in code for async processing of the button listener.
         */
        status_view
                .setText("RECORD_AUDIO permission granted, touch " + getString(R.string.StartEcho) + " to begin");

        // The callback runs on app's thread, so we are safe to resume the action
        startEcho();
    }

    // All this does is calls the UpdateStftTask at a fixed interval
    // http://stackoverflow.com/questions/6531950/how-to-execute-async-task-repeatedly-after-fixed-time-intervals
    public void initializeStftBackgroundThread(int timeInMs) {
        final Handler handler = new Handler();
        Timer timer = new Timer();
        TimerTask doAsynchronousTask = new TimerTask() {
            @Override
            public void run() {
                handler.post(new Runnable() {
                    public void run() {
                        try {
                            UpdateStftTask performStftUiUpdate = new UpdateStftTask();
                            performStftUiUpdate.execute();
                        } catch (Exception e) {
                            // TODO Auto-generated catch block
                        }
                    }
                });
            }
        };
        timer.schedule(doAsynchronousTask, 0, timeInMs); // execute every 50 ms
    }

    // UI update
    private class UpdateStftTask extends AsyncTask<Void, FloatBuffer, Void> {
        @Override
        protected Void doInBackground(Void... params) {

            // Float == 4 bytes
            // Note: We're using FloatBuffer instead of float array because interfacing with JNI
            // with a FloatBuffer allows direct memory sharing, versus having to copy to some
            // intermediate location first.
            // http://stackoverflow.com/questions/10697161/why-floatbuffer-instead-of-float
            FloatBuffer buffer = ByteBuffer.allocateDirect(FFT_SIZE * 4).order(ByteOrder.LITTLE_ENDIAN)
                    .asFloatBuffer();

            getFftBuffer(buffer);

            // Update screen, needs to be done on UI thread
            publishProgress(buffer);

            return null;
        }

        protected void onProgressUpdate(FloatBuffer... newDisplayUpdate) {
            int r, g, b;

            // emulates a scrolling window
            Rect srcRect = new Rect(0, -(-1), bitmap.getWidth(), bitmap.getHeight());
            Rect destRect = new Rect(srcRect);
            destRect.offset(0, -1);
            canvas.drawBitmap(bitmap, srcRect, destRect, null);

            // update latest column with new values which need to be between 0.0 and 1.0
            for (int i = 0; i < newDisplayUpdate[0].capacity(); i++) {
                double val = newDisplayUpdate[0].get();

                // simple linear RYGCB colormap
                if (val <= 0.25) {
                    r = 0;
                    b = 255;
                    g = (int) (4 * val * 255);
                } else if (val <= 0.5) {
                    r = 0;
                    g = 255;
                    b = (int) ((1 - 4 * (val - 0.25)) * 255);
                } else if (val <= 0.75) {
                    g = 255;
                    b = 0;
                    r = (int) (4 * (val - 0.5) * 255);
                } else {
                    r = 255;
                    b = 0;
                    g = (int) ((1 - 4 * (val - 0.75)) * 255);
                }

                // set color with constant alpha
                paint.setColor(Color.argb(255, r, g, b));
                // paint corresponding area
                canvas.drawRect(i, 382, i + 1, 383, paint);
            }

            newDisplayUpdate[0].rewind();
            stftView.invalidate();
        }
    }

    /*
     * Loading our Libs
     */
    static {
        System.loadLibrary("autotune");
    }

    /*
     * jni function implementations...
     */
    public static native void createSLEngine(int rate, int framesPerBuf);

    public static native void deleteSLEngine();

    public static native boolean createSLBufferQueueAudioPlayer();

    public static native void deleteSLBufferQueueAudioPlayer();

    public static native boolean createAudioRecorder();

    public static native void deleteAudioRecorder();

    public static native void startPlay();

    public static native void stopPlay();

    public static native void getFftBuffer(FloatBuffer buffer);
}