• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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