Java tutorial
/* * 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); }