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