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