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