1 /*
2  * Copyright 2018 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 package com.google.sample.oboe.manualtest;
18 
19 import android.app.Activity;
20 import android.os.Bundle;
21 import android.view.View;
22 import android.widget.Button;
23 import android.widget.SeekBar;
24 import android.widget.TextView;
25 
26 import java.io.IOException;
27 
28 /**
29  * Activity to capture audio and then send a delayed copy to output.
30  * There is a fader for setting delay time
31  */
32 public class EchoActivity extends TestInputActivity {
33 
34     AudioOutputTester mAudioOutTester;
35 
36     protected TextView mTextDelayTime;
37     protected SeekBar mFaderDelayTime;
38     protected ExponentialTaper mTaperDelayTime;
39     private static final double MIN_DELAY_TIME_SECONDS = 0.0;
40     private static final double MAX_DELAY_TIME_SECONDS = 3.0;
41     private double mDelayTime;
42     private Button mStartButton;
43     private Button mStopButton;
44     private TextView mStatusTextView;
45 
46     private ColdStartSniffer mNativeSniffer = new ColdStartSniffer(this);
47 
48     protected static final int MAX_DELAY_TIME_PROGRESS = 1000;
49 
50     private SeekBar.OnSeekBarChangeListener mDelayListener = new SeekBar.OnSeekBarChangeListener() {
51         @Override
52         public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
53             setDelayTimeByPosition(progress);
54         }
55 
56         @Override
57         public void onStartTrackingTouch(SeekBar seekBar) {
58         }
59 
60         @Override
61         public void onStopTrackingTouch(SeekBar seekBar) {
62         }
63     };
64 
65     // Periodically query for cold start latency from the native code until it is ready.
66     protected class ColdStartSniffer extends NativeSniffer {
67 
68         private int stableCallCount = 0;
69         private static final int STABLE_CALLS_NEEDED = 20;
70         private int mInputLatency;
71         private int mOutputLatency;
72 
ColdStartSniffer(Activity activity)73         public ColdStartSniffer(Activity activity) {
74             super(activity);
75         }
76 
77         @Override
startSniffer()78         public void startSniffer() {
79             stableCallCount = 0;
80             mInputLatency = -1;
81             mOutputLatency = -1;
82             super.startSniffer();
83         }
84 
run()85         public void run() {
86             mInputLatency = getColdStartInputMillis();
87             mOutputLatency = getColdStartOutputMillis();
88             updateStatusText();
89             if (!isComplete()) {
90                 reschedule();
91             }
92         }
93 
isComplete()94         private boolean isComplete() {
95             if (mInputLatency > 0 && mOutputLatency > 0) {
96                 stableCallCount++;
97             }
98             return stableCallCount > STABLE_CALLS_NEEDED;
99         }
100 
getCurrentStatusReport()101         private String getCurrentStatusReport() {
102             StringBuffer message = new StringBuffer();
103             message.append("cold.start.input.msec = " +
104                     ((mInputLatency > 0)
105                             ? mInputLatency
106                             : "?")
107                     + "\n");
108             message.append("cold.start.output.msec = " +
109                     ((mOutputLatency > 0)
110                             ? mOutputLatency
111                             : "?")
112                     + "\n");
113             message.append("stable.call.count = " + stableCallCount +  "\n");
114             return message.toString();
115         }
116 
117         @Override
getShortReport()118         public String getShortReport() {
119             return getCurrentStatusReport();
120         }
121 
122         @Override
updateStatusText()123         public void updateStatusText() {
124             String message = getCurrentStatusReport();
125             mStatusTextView.setText(message);
126         }
127 
128     }
129 
getColdStartInputMillis()130     private native int getColdStartInputMillis();
getColdStartOutputMillis()131     private native int getColdStartOutputMillis();
132 
133     @Override
inflateActivity()134     protected void inflateActivity() {
135         setContentView(R.layout.activity_echo);
136     }
137 
138     @Override
onCreate(Bundle savedInstanceState)139     protected void onCreate(Bundle savedInstanceState) {
140         super.onCreate(savedInstanceState);
141 
142         updateEnabledWidgets();
143 
144         mAudioOutTester = addAudioOutputTester();
145 
146         mStartButton = (Button) findViewById(R.id.button_start_echo);
147         mStopButton = (Button) findViewById(R.id.button_stop_echo);
148         mStopButton.setEnabled(false);
149 
150         mStatusTextView = (TextView) findViewById(R.id.text_status);
151 
152         mTextDelayTime = (TextView) findViewById(R.id.text_delay_time);
153         mFaderDelayTime = (SeekBar) findViewById(R.id.fader_delay_time);
154         mFaderDelayTime.setOnSeekBarChangeListener(mDelayListener);
155         mTaperDelayTime = new ExponentialTaper(
156                 MIN_DELAY_TIME_SECONDS,
157                 MAX_DELAY_TIME_SECONDS,
158                 100.0);
159         mFaderDelayTime.setProgress(MAX_DELAY_TIME_PROGRESS / 2);
160 
161         hideSettingsViews();
162     }
163 
setDelayTimeByPosition(int progress)164     private void setDelayTimeByPosition(int progress) {
165         mDelayTime = mTaperDelayTime.linearToExponential(
166                 ((double)progress)/MAX_DELAY_TIME_PROGRESS);
167         setDelayTime(mDelayTime);
168         mTextDelayTime.setText("DelayLine: " + (int)(mDelayTime * 1000) + " (msec)");
169     }
170 
setDelayTime(double delayTimeSeconds)171     private native void setDelayTime(double delayTimeSeconds);
172 
getActivityType()173     int getActivityType() {
174         return ACTIVITY_ECHO;
175     }
176 
177 
178     @Override
resetConfiguration()179     protected void resetConfiguration() {
180         super.resetConfiguration();
181         mAudioOutTester.reset();
182     }
183 
onStartEcho(View view)184     public void onStartEcho(View view) {
185         try {
186             openAudio();
187             startAudio();
188             setDelayTime(mDelayTime);
189             mStartButton.setEnabled(false);
190             mStopButton.setEnabled(true);
191             keepScreenOn(true);
192             mNativeSniffer.startSniffer();
193         } catch (IOException e) {
194             showErrorToast(e.getMessage());
195         }
196     }
197 
onStopEcho(View view)198     public void onStopEcho(View view) {
199         mNativeSniffer.stopSniffer();
200         stopAudio();
201         closeAudio();
202         mStartButton.setEnabled(true);
203         mStopButton.setEnabled(false);
204         keepScreenOn(false);
205     }
206 
207     @Override
isOutput()208     boolean isOutput() {
209         return false;
210     }
211 
212     @Override
setupEffects(int sessionId)213     public void setupEffects(int sessionId) {
214     }
215 }
216