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 // Stream IDs 34 // These are the same os the constants in NativeAnalyzer.h and need to be kept in sync. 35 public static final int NUM_STREAM_TYPES = 2; 36 public static final int STREAM_INPUT = 0; 37 public static final int STREAM_OUTPUT = 1; 38 39 private final int mSecondsToRun = 5; 40 private Handler mMessageHandler; 41 private Thread mThread; 42 private volatile boolean mEnabled = false; 43 private volatile double mLatencyMillis = 0.0; 44 private volatile double mConfidence = 0.0; 45 private volatile int mSampleRate = 0; 46 private volatile double mTimestampLatencyMillis = 0.0; 47 private volatile boolean mHas24BitHardwareSupport = false; 48 private volatile int mHardwareFormat = 0; // AAUDIO_FORMAT_UNSPECIFIED 49 50 private volatile boolean[] mIsLowLatencyStream = new boolean[NUM_STREAM_TYPES]; 51 private volatile int[] mBurstFrames = new int[NUM_STREAM_TYPES]; 52 private volatile int[] mCapacityFrames = new int[NUM_STREAM_TYPES]; 53 private volatile boolean[] mIsMMapStream = new boolean[NUM_STREAM_TYPES]; 54 55 private int mInputPreset = 0; 56 57 private int mInputDeviceId; 58 private int mOutputDeviceId; 59 60 static final int NATIVE_AUDIO_THREAD_MESSAGE_REC_STARTED = 892; 61 static final int NATIVE_AUDIO_THREAD_MESSAGE_OPEN_ERROR = 893; 62 static final int NATIVE_AUDIO_THREAD_MESSAGE_REC_ERROR = 894; 63 static final int NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE = 895; 64 static final int NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE_ERRORS = 896; 65 static final int NATIVE_AUDIO_THREAD_MESSAGE_ANALYZING = 897; 66 NativeAnalyzerThread(Context context)67 public NativeAnalyzerThread(Context context) { 68 mContext = context; 69 } 70 setInputPreset(int inputPreset)71 public void setInputPreset(int inputPreset) { 72 mInputPreset = inputPreset; 73 } 74 75 //JNI load 76 static { 77 try { 78 System.loadLibrary("audioloopback_jni"); 79 } catch (UnsatisfiedLinkError e) { 80 log("Error loading loopback JNI library"); 81 log("e: " + e); 82 e.printStackTrace(); 83 } 84 85 /* TODO: gracefully fail/notify if the library can't be loaded */ 86 } 87 88 /** 89 * @return native audio context 90 */ openAudio(int inputDeviceID, int outputDeviceId)91 private native long openAudio(int inputDeviceID, int outputDeviceId); startAudio(long audioContext)92 private native int startAudio(long audioContext); stopAudio(long audioContext)93 private native int stopAudio(long audioContext); closeAudio(long audioContext)94 private native int closeAudio(long audioContext); getError(long audioContext)95 private native int getError(long audioContext); isRecordingComplete(long audioContext)96 private native boolean isRecordingComplete(long audioContext); analyze(long audioContext)97 private native int analyze(long audioContext); getLatencyMillis(long audioContext)98 private native double getLatencyMillis(long audioContext); getConfidence(long audioContext)99 private native double getConfidence(long audioContext); has24BitHardwareSupport(long audioContext)100 private native boolean has24BitHardwareSupport(long audioContext); getHardwareFormat(long audioContext)101 private native int getHardwareFormat(long audioContext); 102 103 // Stream Attributes isLowlatency(long audioContext, int streamId)104 private native boolean isLowlatency(long audioContext, int streamId); getBurstFrames(long audioContext, int streamId)105 private native int getBurstFrames(long audioContext, int streamId); getCapacityFrames(long audioContext, int streamId)106 private native int getCapacityFrames(long audioContext, int streamId); isMMapStream(long audioContext, int streamId)107 private native boolean isMMapStream(long audioContext, int streamId); 108 getSampleRate(long audio_context)109 private native int getSampleRate(long audio_context); 110 measureTimestampLatencyMillis(long audioContext)111 private native double measureTimestampLatencyMillis(long audioContext); 112 getLatencyMillis()113 public double getLatencyMillis() { 114 return mLatencyMillis; 115 } 116 getConfidence()117 public double getConfidence() { 118 return mConfidence; 119 } 120 getSampleRate()121 public int getSampleRate() { return mSampleRate; } 122 123 /** 124 * @return whether 24 bit data formats are supported for the hardware 125 */ has24BitHardwareSupport()126 public boolean has24BitHardwareSupport() { 127 return mHas24BitHardwareSupport; 128 } 129 getHardwareFormat()130 public int getHardwareFormat() { 131 return mHardwareFormat; 132 } 133 134 /** 135 * @param streamId one of STREAM_INPUT or STREAM_OUTPUT 136 * @return true if the specified stream is a low-latency stream. 137 */ isLowLatencyStream(int streamId)138 public boolean isLowLatencyStream(int streamId) { 139 if (streamId != STREAM_INPUT && streamId != STREAM_OUTPUT) { 140 return false; 141 } 142 return mIsLowLatencyStream[streamId]; 143 } 144 145 /** 146 * @param streamId one of STREAM_INPUT or STREAM_OUTPUT 147 * @return the number of burst frames for the specified stream 148 */ getBurstFrames(int streamId)149 public int getBurstFrames(int streamId) { 150 if (streamId != STREAM_INPUT && streamId != STREAM_OUTPUT) { 151 return -1; 152 } 153 return mBurstFrames[streamId]; 154 } 155 156 /** 157 * @param streamId one of STREAM_INPUT or STREAM_OUTPUT 158 * @return the capacity in frames for the specified stream 159 */ getCapacityFrames(int streamId)160 public int getCapacityFrames(int streamId) { 161 if (streamId != STREAM_INPUT && streamId != STREAM_OUTPUT) { 162 return -1; 163 } 164 return mCapacityFrames[streamId]; 165 } 166 167 /** 168 * @param streamId one of STREAM_INPUT or STREAM_OUTPUT 169 * @return true if the specified stream is a MMAP stream. 170 */ isMMapStream(int streamId)171 public boolean isMMapStream(int streamId) { 172 if (streamId != STREAM_INPUT && streamId != STREAM_OUTPUT) { 173 return false; 174 } 175 return mIsMMapStream[streamId]; 176 } 177 getTimestampLatencyMillis()178 public double getTimestampLatencyMillis() { 179 return mTimestampLatencyMillis; 180 } 181 startTest(int inputDeviceId, int outputDeviceId)182 public synchronized void startTest(int inputDeviceId, int outputDeviceId) { 183 mInputDeviceId = inputDeviceId; 184 mOutputDeviceId = outputDeviceId; 185 186 if (mThread == null) { 187 mEnabled = true; 188 mThread = new Thread(mBackGroundTask); 189 mThread.start(); 190 } 191 } 192 stopTest(int millis)193 public synchronized void stopTest(int millis) throws InterruptedException { 194 mEnabled = false; 195 if (mThread != null) { 196 mThread.interrupt(); 197 mThread.join(millis); 198 mThread = null; 199 } 200 } 201 sendMessage(int what)202 private void sendMessage(int what) { 203 if (mMessageHandler != null) { 204 Message msg = Message.obtain(); 205 msg.what = what; 206 mMessageHandler.sendMessage(msg); 207 } 208 } 209 210 private Runnable mBackGroundTask = () -> { 211 mLatencyMillis = 0.0; 212 mConfidence = 0.0; 213 mSampleRate = 0; 214 mTimestampLatencyMillis = 0.0; 215 216 boolean analysisComplete = false; 217 218 log(" Started capture test"); 219 sendMessage(NATIVE_AUDIO_THREAD_MESSAGE_REC_STARTED); 220 221 //TODO - route parameters 222 long audioContext = openAudio(mInputDeviceId, mOutputDeviceId); 223 log(String.format("audioContext = 0x%X",audioContext)); 224 225 if (audioContext == 0 ) { 226 log(" ERROR at JNI initialization"); 227 sendMessage(NATIVE_AUDIO_THREAD_MESSAGE_OPEN_ERROR); 228 } else if (mEnabled) { 229 int result = startAudio(audioContext); 230 if (result < 0) { 231 sendMessage(NATIVE_AUDIO_THREAD_MESSAGE_REC_ERROR); 232 mEnabled = false; 233 } 234 mHas24BitHardwareSupport = has24BitHardwareSupport(audioContext); 235 mHardwareFormat = getHardwareFormat(audioContext); 236 237 mIsLowLatencyStream[STREAM_OUTPUT] = isLowlatency(audioContext, STREAM_OUTPUT); 238 mIsLowLatencyStream[STREAM_INPUT] = isLowlatency(audioContext, STREAM_INPUT); 239 240 mBurstFrames[STREAM_OUTPUT] = getBurstFrames(audioContext, STREAM_OUTPUT); 241 mBurstFrames[STREAM_INPUT] = getBurstFrames(audioContext, STREAM_INPUT); 242 243 mCapacityFrames[STREAM_OUTPUT] = getCapacityFrames(audioContext, STREAM_OUTPUT); 244 mCapacityFrames[STREAM_INPUT] = getCapacityFrames(audioContext, STREAM_INPUT); 245 246 mIsMMapStream[STREAM_OUTPUT] = isMMapStream(audioContext, STREAM_OUTPUT); 247 mIsMMapStream[STREAM_INPUT] = isMMapStream(audioContext, STREAM_INPUT); 248 249 final long timeoutMillis = mSecondsToRun * 1000; 250 final long startedAtMillis = System.currentTimeMillis(); 251 boolean timedOut = false; 252 int loopCounter = 0; 253 while (mEnabled && !timedOut) { 254 result = getError(audioContext); 255 if (result < 0) { 256 sendMessage(NATIVE_AUDIO_THREAD_MESSAGE_REC_ERROR); 257 break; 258 } else if (isRecordingComplete(audioContext)) { 259 mTimestampLatencyMillis = measureTimestampLatencyMillis(audioContext); 260 stopAudio(audioContext); 261 262 // Analyze the recording and measure latency. 263 mThread.setPriority(Thread.MAX_PRIORITY); 264 sendMessage(NATIVE_AUDIO_THREAD_MESSAGE_ANALYZING); 265 result = analyze(audioContext); 266 if (result < 0) { 267 break; 268 } else { 269 analysisComplete = true; 270 } 271 mLatencyMillis = getLatencyMillis(audioContext); 272 mConfidence = getConfidence(audioContext); 273 mSampleRate = getSampleRate(audioContext); 274 break; 275 } else { 276 try { 277 Thread.sleep(100); 278 } catch (InterruptedException e) { 279 e.printStackTrace(); 280 } 281 } 282 long now = System.currentTimeMillis(); 283 timedOut = (now - startedAtMillis) > timeoutMillis; 284 } 285 log("latency: analyze returns " + result); 286 closeAudio(audioContext); 287 288 int what = (analysisComplete && result == 0) 289 ? NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE 290 : NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE_ERRORS; 291 sendMessage(what); 292 } 293 }; 294 setMessageHandler(Handler messageHandler)295 public void setMessageHandler(Handler messageHandler) { 296 mMessageHandler = messageHandler; 297 } 298 log(String msg)299 private static void log(String msg) { 300 Log.v("Loopback", msg); 301 } 302 303 } //end thread. 304