1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 18 package com.android.cts.verifier.audio; 19 20 import android.content.Context; 21 import android.os.Handler; 22 import android.os.Message; 23 import android.util.Log; 24 25 /** 26 * A thread that runs a native audio loopback analyzer. 27 */ 28 public class NativeAnalyzerThread { 29 private static final String TAG = "NativeAnalyzerThread"; 30 31 private Context mContext; 32 33 private final int mSecondsToRun = 5; 34 private Handler mMessageHandler; 35 private Thread mThread; 36 private volatile boolean mEnabled = false; 37 private volatile double mLatencyMillis = 0.0; 38 private volatile double mConfidence = 0.0; 39 private volatile int mSampleRate = 0; 40 private volatile boolean mIsLowLatencyStream = false; 41 private volatile boolean mHas24BitHardwareSupport = false; 42 43 private int mInputPreset = 0; 44 45 private int mInputDeviceId; 46 private int mOutputDeviceId; 47 48 static final int NATIVE_AUDIO_THREAD_MESSAGE_REC_STARTED = 892; 49 static final int NATIVE_AUDIO_THREAD_MESSAGE_OPEN_ERROR = 893; 50 static final int NATIVE_AUDIO_THREAD_MESSAGE_REC_ERROR = 894; 51 static final int NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE = 895; 52 static final int NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE_ERRORS = 896; 53 static final int NATIVE_AUDIO_THREAD_MESSAGE_ANALYZING = 897; 54 NativeAnalyzerThread(Context context)55 public NativeAnalyzerThread(Context context) { 56 mContext = context; 57 } 58 setInputPreset(int inputPreset)59 public void setInputPreset(int inputPreset) { 60 mInputPreset = inputPreset; 61 } 62 63 //JNI load 64 static { 65 try { 66 System.loadLibrary("audioloopback_jni"); 67 } catch (UnsatisfiedLinkError e) { 68 log("Error loading loopback JNI library"); 69 log("e: " + e); 70 e.printStackTrace(); 71 } 72 73 /* TODO: gracefully fail/notify if the library can't be loaded */ 74 } 75 76 /** 77 * @return native audio context 78 */ openAudio(int inputDeviceID, int outputDeviceId)79 private native long openAudio(int inputDeviceID, int outputDeviceId); startAudio(long audioContext)80 private native int startAudio(long audioContext); stopAudio(long audioContext)81 private native int stopAudio(long audioContext); closeAudio(long audioContext)82 private native int closeAudio(long audioContext); getError(long audioContext)83 private native int getError(long audioContext); isRecordingComplete(long audioContext)84 private native boolean isRecordingComplete(long audioContext); analyze(long audioContext)85 private native int analyze(long audioContext); getLatencyMillis(long audioContext)86 private native double getLatencyMillis(long audioContext); getConfidence(long audioContext)87 private native double getConfidence(long audioContext); isLowlatency(long audioContext)88 private native boolean isLowlatency(long audioContext); has24BitHardwareSupport(long audioContext)89 private native boolean has24BitHardwareSupport(long audioContext); 90 getSampleRate(long audio_context)91 private native int getSampleRate(long audio_context); 92 getLatencyMillis()93 public double getLatencyMillis() { 94 return mLatencyMillis; 95 } 96 getConfidence()97 public double getConfidence() { 98 return mConfidence; 99 } 100 getSampleRate()101 public int getSampleRate() { return mSampleRate; } 102 isLowLatencyStream()103 public boolean isLowLatencyStream() { return mIsLowLatencyStream; } 104 105 /** 106 * @return whether 24 bit data formats are supported for the hardware 107 */ has24BitHardwareSupport()108 public boolean has24BitHardwareSupport() { 109 return mHas24BitHardwareSupport; 110 } 111 startTest(int inputDeviceId, int outputDeviceId)112 public synchronized void startTest(int inputDeviceId, int outputDeviceId) { 113 mInputDeviceId = inputDeviceId; 114 mOutputDeviceId = outputDeviceId; 115 116 if (mThread == null) { 117 mEnabled = true; 118 mThread = new Thread(mBackGroundTask); 119 mThread.start(); 120 } 121 } 122 stopTest(int millis)123 public synchronized void stopTest(int millis) throws InterruptedException { 124 mEnabled = false; 125 if (mThread != null) { 126 mThread.interrupt(); 127 mThread.join(millis); 128 mThread = null; 129 } 130 } 131 sendMessage(int what)132 private void sendMessage(int what) { 133 if (mMessageHandler != null) { 134 Message msg = Message.obtain(); 135 msg.what = what; 136 mMessageHandler.sendMessage(msg); 137 } 138 } 139 140 private Runnable mBackGroundTask = () -> { 141 mLatencyMillis = 0.0; 142 mConfidence = 0.0; 143 mSampleRate = 0; 144 145 boolean analysisComplete = false; 146 147 log(" Started capture test"); 148 sendMessage(NATIVE_AUDIO_THREAD_MESSAGE_REC_STARTED); 149 150 //TODO - route parameters 151 long audioContext = openAudio(mInputDeviceId, mOutputDeviceId); 152 log(String.format("audioContext = 0x%X",audioContext)); 153 154 if (audioContext == 0 ) { 155 log(" ERROR at JNI initialization"); 156 sendMessage(NATIVE_AUDIO_THREAD_MESSAGE_OPEN_ERROR); 157 } else if (mEnabled) { 158 int result = startAudio(audioContext); 159 if (result < 0) { 160 sendMessage(NATIVE_AUDIO_THREAD_MESSAGE_REC_ERROR); 161 mEnabled = false; 162 } 163 mIsLowLatencyStream = isLowlatency(audioContext); 164 mHas24BitHardwareSupport = has24BitHardwareSupport(audioContext); 165 166 final long timeoutMillis = mSecondsToRun * 1000; 167 final long startedAtMillis = System.currentTimeMillis(); 168 boolean timedOut = false; 169 int loopCounter = 0; 170 while (mEnabled && !timedOut) { 171 result = getError(audioContext); 172 if (result < 0) { 173 sendMessage(NATIVE_AUDIO_THREAD_MESSAGE_REC_ERROR); 174 break; 175 } else if (isRecordingComplete(audioContext)) { 176 stopAudio(audioContext); 177 178 // Analyze the recording and measure latency. 179 mThread.setPriority(Thread.MAX_PRIORITY); 180 sendMessage(NATIVE_AUDIO_THREAD_MESSAGE_ANALYZING); 181 result = analyze(audioContext); 182 if (result < 0) { 183 break; 184 } else { 185 analysisComplete = true; 186 } 187 mLatencyMillis = getLatencyMillis(audioContext); 188 mConfidence = getConfidence(audioContext); 189 mSampleRate = getSampleRate(audioContext); 190 break; 191 } else { 192 try { 193 Thread.sleep(100); 194 } catch (InterruptedException e) { 195 e.printStackTrace(); 196 } 197 } 198 long now = System.currentTimeMillis(); 199 timedOut = (now - startedAtMillis) > timeoutMillis; 200 } 201 log("latency: analyze returns " + result); 202 closeAudio(audioContext); 203 204 int what = (analysisComplete && result == 0) 205 ? NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE 206 : NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE_ERRORS; 207 sendMessage(what); 208 } 209 }; 210 setMessageHandler(Handler messageHandler)211 public void setMessageHandler(Handler messageHandler) { 212 mMessageHandler = messageHandler; 213 } 214 log(String msg)215 private static void log(String msg) { 216 Log.v("Loopback", msg); 217 } 218 219 } //end thread. 220