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 double mTimestampLatencyMillis = 0.0; 41 private volatile boolean mIsLowLatencyStream = false; 42 private volatile boolean mHas24BitHardwareSupport = false; 43 private volatile int mHardwareFormat = 0; // AAUDIO_FORMAT_UNSPECIFIED 44 45 private int mInputPreset = 0; 46 47 private int mInputDeviceId; 48 private int mOutputDeviceId; 49 50 static final int NATIVE_AUDIO_THREAD_MESSAGE_REC_STARTED = 892; 51 static final int NATIVE_AUDIO_THREAD_MESSAGE_OPEN_ERROR = 893; 52 static final int NATIVE_AUDIO_THREAD_MESSAGE_REC_ERROR = 894; 53 static final int NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE = 895; 54 static final int NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE_ERRORS = 896; 55 static final int NATIVE_AUDIO_THREAD_MESSAGE_ANALYZING = 897; 56 NativeAnalyzerThread(Context context)57 public NativeAnalyzerThread(Context context) { 58 mContext = context; 59 } 60 setInputPreset(int inputPreset)61 public void setInputPreset(int inputPreset) { 62 mInputPreset = inputPreset; 63 } 64 65 //JNI load 66 static { 67 try { 68 System.loadLibrary("audioloopback_jni"); 69 } catch (UnsatisfiedLinkError e) { 70 log("Error loading loopback JNI library"); 71 log("e: " + e); 72 e.printStackTrace(); 73 } 74 75 /* TODO: gracefully fail/notify if the library can't be loaded */ 76 } 77 78 /** 79 * @return native audio context 80 */ openAudio(int inputDeviceID, int outputDeviceId)81 private native long openAudio(int inputDeviceID, int outputDeviceId); startAudio(long audioContext)82 private native int startAudio(long audioContext); stopAudio(long audioContext)83 private native int stopAudio(long audioContext); closeAudio(long audioContext)84 private native int closeAudio(long audioContext); getError(long audioContext)85 private native int getError(long audioContext); isRecordingComplete(long audioContext)86 private native boolean isRecordingComplete(long audioContext); analyze(long audioContext)87 private native int analyze(long audioContext); getLatencyMillis(long audioContext)88 private native double getLatencyMillis(long audioContext); getConfidence(long audioContext)89 private native double getConfidence(long audioContext); isLowlatency(long audioContext)90 private native boolean isLowlatency(long audioContext); has24BitHardwareSupport(long audioContext)91 private native boolean has24BitHardwareSupport(long audioContext); getHardwareFormat(long audioContext)92 private native int getHardwareFormat(long audioContext); 93 getSampleRate(long audio_context)94 private native int getSampleRate(long audio_context); 95 measureTimestampLatencyMillis(long audioContext)96 private native double measureTimestampLatencyMillis(long audioContext); 97 getLatencyMillis()98 public double getLatencyMillis() { 99 return mLatencyMillis; 100 } 101 getConfidence()102 public double getConfidence() { 103 return mConfidence; 104 } 105 getSampleRate()106 public int getSampleRate() { return mSampleRate; } 107 isLowLatencyStream()108 public boolean isLowLatencyStream() { return mIsLowLatencyStream; } 109 110 /** 111 * @return whether 24 bit data formats are supported for the hardware 112 */ has24BitHardwareSupport()113 public boolean has24BitHardwareSupport() { 114 return mHas24BitHardwareSupport; 115 } 116 getHardwareFormat()117 public int getHardwareFormat() { 118 return mHardwareFormat; 119 } 120 getTimestampLatencyMillis()121 public double getTimestampLatencyMillis() { 122 return mTimestampLatencyMillis; 123 } 124 startTest(int inputDeviceId, int outputDeviceId)125 public synchronized void startTest(int inputDeviceId, int outputDeviceId) { 126 mInputDeviceId = inputDeviceId; 127 mOutputDeviceId = outputDeviceId; 128 129 if (mThread == null) { 130 mEnabled = true; 131 mThread = new Thread(mBackGroundTask); 132 mThread.start(); 133 } 134 } 135 stopTest(int millis)136 public synchronized void stopTest(int millis) throws InterruptedException { 137 mEnabled = false; 138 if (mThread != null) { 139 mThread.interrupt(); 140 mThread.join(millis); 141 mThread = null; 142 } 143 } 144 sendMessage(int what)145 private void sendMessage(int what) { 146 if (mMessageHandler != null) { 147 Message msg = Message.obtain(); 148 msg.what = what; 149 mMessageHandler.sendMessage(msg); 150 } 151 } 152 153 private Runnable mBackGroundTask = () -> { 154 mLatencyMillis = 0.0; 155 mConfidence = 0.0; 156 mSampleRate = 0; 157 mTimestampLatencyMillis = 0.0; 158 159 boolean analysisComplete = false; 160 161 log(" Started capture test"); 162 sendMessage(NATIVE_AUDIO_THREAD_MESSAGE_REC_STARTED); 163 164 //TODO - route parameters 165 long audioContext = openAudio(mInputDeviceId, mOutputDeviceId); 166 log(String.format("audioContext = 0x%X",audioContext)); 167 168 if (audioContext == 0 ) { 169 log(" ERROR at JNI initialization"); 170 sendMessage(NATIVE_AUDIO_THREAD_MESSAGE_OPEN_ERROR); 171 } else if (mEnabled) { 172 int result = startAudio(audioContext); 173 if (result < 0) { 174 sendMessage(NATIVE_AUDIO_THREAD_MESSAGE_REC_ERROR); 175 mEnabled = false; 176 } 177 mIsLowLatencyStream = isLowlatency(audioContext); 178 mHas24BitHardwareSupport = has24BitHardwareSupport(audioContext); 179 mHardwareFormat = getHardwareFormat(audioContext); 180 181 final long timeoutMillis = mSecondsToRun * 1000; 182 final long startedAtMillis = System.currentTimeMillis(); 183 boolean timedOut = false; 184 int loopCounter = 0; 185 while (mEnabled && !timedOut) { 186 result = getError(audioContext); 187 if (result < 0) { 188 sendMessage(NATIVE_AUDIO_THREAD_MESSAGE_REC_ERROR); 189 break; 190 } else if (isRecordingComplete(audioContext)) { 191 mTimestampLatencyMillis = measureTimestampLatencyMillis(audioContext); 192 stopAudio(audioContext); 193 194 // Analyze the recording and measure latency. 195 mThread.setPriority(Thread.MAX_PRIORITY); 196 sendMessage(NATIVE_AUDIO_THREAD_MESSAGE_ANALYZING); 197 result = analyze(audioContext); 198 if (result < 0) { 199 break; 200 } else { 201 analysisComplete = true; 202 } 203 mLatencyMillis = getLatencyMillis(audioContext); 204 mConfidence = getConfidence(audioContext); 205 mSampleRate = getSampleRate(audioContext); 206 break; 207 } else { 208 try { 209 Thread.sleep(100); 210 } catch (InterruptedException e) { 211 e.printStackTrace(); 212 } 213 } 214 long now = System.currentTimeMillis(); 215 timedOut = (now - startedAtMillis) > timeoutMillis; 216 } 217 log("latency: analyze returns " + result); 218 closeAudio(audioContext); 219 220 int what = (analysisComplete && result == 0) 221 ? NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE 222 : NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE_ERRORS; 223 sendMessage(what); 224 } 225 }; 226 setMessageHandler(Handler messageHandler)227 public void setMessageHandler(Handler messageHandler) { 228 mMessageHandler = messageHandler; 229 } 230 log(String msg)231 private static void log(String msg) { 232 Log.v("Loopback", msg); 233 } 234 235 } //end thread. 236