• 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 package org.chromium.latency.walt;
18 
19 import android.Manifest;
20 import android.content.BroadcastReceiver;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.pm.PackageManager;
24 import android.graphics.Color;
25 import android.media.AudioManager;
26 import android.os.Bundle;
27 import android.support.v4.app.Fragment;
28 import android.support.v4.content.ContextCompat;
29 import android.text.method.ScrollingMovementMethod;
30 import android.view.LayoutInflater;
31 import android.view.View;
32 import android.view.ViewGroup;
33 import android.widget.ArrayAdapter;
34 import android.widget.Spinner;
35 import android.widget.TextView;
36 
37 import com.github.mikephil.charting.charts.LineChart;
38 import com.github.mikephil.charting.components.Description;
39 import com.github.mikephil.charting.components.LimitLine;
40 import com.github.mikephil.charting.data.Entry;
41 import com.github.mikephil.charting.data.LineData;
42 import com.github.mikephil.charting.data.LineDataSet;
43 
44 import java.util.ArrayList;
45 import java.util.List;
46 import java.util.Locale;
47 
48 import static org.chromium.latency.walt.Utils.getIntPreference;
49 
50 /**
51  * A simple {@link Fragment} subclass.
52  */
53 public class AudioFragment extends Fragment implements View.OnClickListener,
54         BaseTest.TestStateListener {
55 
56     enum AudioTestType {
57         CONTINUOUS_PLAYBACK,
58         CONTINUOUS_RECORDING,
59         COLD_PLAYBACK,
60         COLD_RECORDING,
61         DISPLAY_WAVEFORM
62     }
63 
64     private SimpleLogger logger;
65     private TextView textView;
66     private AudioTest audioTest;
67     private View startButton;
68     private View stopButton;
69     private Spinner modeSpinner;
70     private LineChart chart;
71     private HistogramChart latencyChart;
72     private View chartLayout;
73 
74     private static final int PERMISSION_REQUEST_RECORD_AUDIO = 1;
75 
AudioFragment()76     public AudioFragment() {
77         // Required empty public constructor
78     }
79 
80 
81     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)82     public View onCreateView(LayoutInflater inflater, ViewGroup container,
83                              Bundle savedInstanceState) {
84 
85         logger = SimpleLogger.getInstance(getContext());
86 
87         audioTest = new AudioTest(getActivity());
88         audioTest.setTestStateListener(this);
89 
90         // Inflate the layout for this fragment
91         View view = inflater.inflate(R.layout.fragment_audio, container, false);
92         textView = (TextView) view.findViewById(R.id.txt_box_audio);
93         textView.setMovementMethod(new ScrollingMovementMethod());
94         startButton = view.findViewById(R.id.button_start_audio);
95         stopButton = view.findViewById(R.id.button_stop_audio);
96         chartLayout = view.findViewById(R.id.chart_layout);
97         chart = (LineChart) view.findViewById(R.id.chart);
98         latencyChart = (HistogramChart) view.findViewById(R.id.latency_chart);
99 
100         view.findViewById(R.id.button_close_chart).setOnClickListener(this);
101         enableButtons();
102 
103         // Configure the audio mode spinner
104         modeSpinner = (Spinner) view.findViewById(R.id.spinner_audio_mode);
105         ArrayAdapter<CharSequence> modeAdapter = ArrayAdapter.createFromResource(getContext(),
106                 R.array.audio_mode_array, android.R.layout.simple_spinner_item);
107         modeAdapter.setDropDownViewResource(R.layout.support_simple_spinner_dropdown_item);
108         modeSpinner.setAdapter(modeAdapter);
109 
110         return view;
111     }
112 
113     @Override
onResume()114     public void onResume() {
115         super.onResume();
116 
117         // Register this fragment class as the listener for some button clicks
118         startButton.setOnClickListener(this);
119         stopButton.setOnClickListener(this);
120 
121         textView.setText(logger.getLogText());
122         logger.registerReceiver(logReceiver);
123     }
124 
125     @Override
onPause()126     public void onPause() {
127         logger.unregisterReceiver(logReceiver);
128         super.onPause();
129     }
130 
131     @Override
onDestroy()132     public void onDestroy() {
133         super.onDestroy();
134         audioTest.teardown();
135     }
136 
137     @Override
onClick(View v)138     public void onClick(View v) {
139         switch (v.getId()) {
140             case R.id.button_start_audio:
141                 chartLayout.setVisibility(View.GONE);
142                 disableButtons();
143                 AudioTestType testType = getSelectedTestType();
144                 switch (testType) {
145                     case CONTINUOUS_PLAYBACK:
146                     case CONTINUOUS_RECORDING:
147                     case DISPLAY_WAVEFORM:
148                         audioTest.setAudioMode(AudioTest.AudioMode.CONTINUOUS);
149                         audioTest.setPeriod(AudioTest.CONTINUOUS_TEST_PERIOD);
150                         break;
151                     case COLD_PLAYBACK:
152                     case COLD_RECORDING:
153                         audioTest.setAudioMode(AudioTest.AudioMode.CONTINUOUS);
154                         audioTest.setPeriod(AudioTest.COLD_TEST_PERIOD);
155                         break;
156                 }
157                 if (testType == AudioTestType.DISPLAY_WAVEFORM) {
158                     // Only need to record 1 beep to display wave
159                     audioTest.setRecordingRepetitions(1);
160                 } else {
161                     audioTest.setRecordingRepetitions(
162                             getIntPreference(getContext(), R.string.preference_audio_in_reps, 5));
163                 }
164                 if (testType == AudioTestType.CONTINUOUS_PLAYBACK ||
165                         testType == AudioTestType.COLD_PLAYBACK ||
166                         testType == AudioTestType.CONTINUOUS_RECORDING ||
167                         testType == AudioTestType.COLD_RECORDING) {
168                     latencyChart.setVisibility(View.VISIBLE);
169                     latencyChart.clearData();
170                     latencyChart.setLegendEnabled(false);
171                     final String description =
172                             getResources().getStringArray(R.array.audio_mode_array)[
173                                     modeSpinner.getSelectedItemPosition()] + " [ms]";
174                     latencyChart.setDescription(description);
175                 }
176                 switch (testType) {
177                     case CONTINUOUS_RECORDING:
178                     case COLD_RECORDING:
179                     case DISPLAY_WAVEFORM:
180                         attemptRecordingTest();
181                         break;
182                     case CONTINUOUS_PLAYBACK:
183                     case COLD_PLAYBACK:
184                         // Set media volume to max
185                         AudioManager am = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
186                         am.setStreamVolume(AudioManager.STREAM_MUSIC, am.getStreamMaxVolume(AudioManager.STREAM_MUSIC), 0);
187                         audioTest.beginPlaybackMeasurement();
188                         break;
189                 }
190                 break;
191             case R.id.button_stop_audio:
192                 audioTest.stopTest();
193                 break;
194             case R.id.button_close_chart:
195                 chartLayout.setVisibility(View.GONE);
196                 break;
197         }
198     }
199 
getSelectedTestType()200     private AudioTestType getSelectedTestType() {
201         return AudioTestType.values()[modeSpinner.getSelectedItemPosition()];
202     }
203 
204     private BroadcastReceiver logReceiver = new BroadcastReceiver() {
205         @Override
206         public void onReceive(Context context, Intent intent) {
207             String msg = intent.getStringExtra("message");
208             textView.append(msg + "\n");
209         }
210     };
211 
attemptRecordingTest()212     private void attemptRecordingTest() {
213         // first see if we already have permission to record audio
214         int currentPermission = ContextCompat.checkSelfPermission(this.getContext(),
215                 Manifest.permission.RECORD_AUDIO);
216         if (currentPermission == PackageManager.PERMISSION_GRANTED) {
217             disableButtons();
218             audioTest.beginRecordingMeasurement();
219         } else {
220             requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO},
221                     PERMISSION_REQUEST_RECORD_AUDIO);
222         }
223     }
224 
225     @Override
onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)226     public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
227         switch (requestCode) {
228             case PERMISSION_REQUEST_RECORD_AUDIO:
229                 if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
230                     disableButtons();
231                     audioTest.beginRecordingMeasurement();
232                 } else {
233                     logger.log("Could not get permission to record audio");
234                 }
235                 return;
236         }
237     }
238 
239     @Override
onTestStopped()240     public void onTestStopped() {
241         if (getSelectedTestType() == AudioTestType.DISPLAY_WAVEFORM) {
242             drawWaveformChart();
243         } else {
244             if (!audioTest.deltas_mic.isEmpty()) {
245                 latencyChart.setLegendEnabled(true);
246                 latencyChart.setLabel(String.format(Locale.US, "Median=%.1f ms", Utils.median(audioTest.deltas_mic)));
247             } else if (!audioTest.deltas_queue2wire.isEmpty()) {
248                 latencyChart.setLegendEnabled(true);
249                 latencyChart.setLabel(String.format(Locale.US, "Median=%.1f ms", Utils.median(audioTest.deltas_queue2wire)));
250             }
251         }
252         LogUploader.uploadIfAutoEnabled(getContext());
253         enableButtons();
254     }
255 
256     @Override
onTestStoppedWithError()257     public void onTestStoppedWithError() {
258         enableButtons();
259         latencyChart.setVisibility(View.GONE);
260     }
261 
262     @Override
onTestPartialResult(double value)263     public void onTestPartialResult(double value) {
264         latencyChart.addEntry(value);
265     }
266 
drawWaveformChart()267     private void drawWaveformChart() {
268         final short[] wave = AudioTest.getRecordedWave();
269         List<Entry> entries = new ArrayList<>();
270         int frameRate = audioTest.getOptimalFrameRate();
271         for (int i = 0; i < wave.length; i++) {
272             float timeStamp = (float) i / frameRate * 1000f;
273             entries.add(new Entry(timeStamp, (float) wave[i]));
274         }
275         LineDataSet dataSet = new LineDataSet(entries, "Waveform");
276         dataSet.setColor(Color.BLACK);
277         dataSet.setValueTextColor(Color.BLACK);
278         dataSet.setCircleColor(ContextCompat.getColor(getContext(), R.color.DarkGreen));
279         dataSet.setCircleRadius(1.5f);
280         dataSet.setCircleColorHole(Color.DKGRAY);
281         LineData lineData = new LineData(dataSet);
282         chart.setData(lineData);
283 
284         LimitLine line = new LimitLine(audioTest.getThreshold(), "Threshold");
285         line.setLineColor(Color.RED);
286         line.setLabelPosition(LimitLine.LimitLabelPosition.LEFT_TOP);
287         line.setLineWidth(2f);
288         line.setTextColor(Color.DKGRAY);
289         line.setTextSize(10f);
290         chart.getAxisLeft().addLimitLine(line);
291 
292         final Description desc = new Description();
293         desc.setText("Wave [digital level -32768 to +32767] vs. Time [ms]");
294         desc.setTextSize(12f);
295         chart.setDescription(desc);
296         chart.getLegend().setEnabled(false);
297         chart.invalidate();
298         chartLayout.setVisibility(View.VISIBLE);
299     }
300 
disableButtons()301     private void disableButtons() {
302         startButton.setEnabled(false);
303         stopButton.setEnabled(true);
304     }
305 
enableButtons()306     private void enableButtons() {
307         startButton.setEnabled(true);
308         stopButton.setEnabled(false);
309     }
310 }
311