Home | History | Annotate | Download | only in audio
      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 com.android.cts.verifier.audio;
     18 
     19 import com.android.cts.verifier.PassFailButtons;
     20 import com.android.cts.verifier.R;
     21 import com.android.compatibility.common.util.ReportLog;
     22 import com.android.compatibility.common.util.ResultType;
     23 import com.android.compatibility.common.util.ResultUnit;
     24 import android.content.Context;
     25 
     26 import android.media.AudioDeviceCallback;
     27 import android.media.AudioDeviceInfo;
     28 import android.media.AudioManager;
     29 import android.media.AudioTrack;
     30 
     31 import android.os.Bundle;
     32 import android.os.Handler;
     33 import android.os.Message;
     34 
     35 import android.util.Log;
     36 
     37 import android.view.View;
     38 import android.view.View.OnClickListener;
     39 
     40 import android.widget.Button;
     41 import android.widget.TextView;
     42 import android.widget.SeekBar;
     43 import android.widget.LinearLayout;
     44 import android.widget.ProgressBar;
     45 
     46 /**
     47  * Tests Audio Device roundtrip latency by using a loopback plug.
     48  */
     49 public class AudioLoopbackActivity extends PassFailButtons.Activity {
     50     private static final String TAG = "AudioLoopbackActivity";
     51 
     52     public static final int BYTES_PER_FRAME = 2;
     53 
     54     NativeAudioThread nativeAudioThread = null;
     55 
     56     private int mSamplingRate = 44100;
     57     private int mMinBufferSizeInFrames = 0;
     58     private static final double CONFIDENCE_THRESHOLD = 0.6;
     59     private Correlation mCorrelation = new Correlation();
     60 
     61     // TODO: remove this variable
     62     private int mNumFramesToIgnore = 0;
     63 
     64     OnBtnClickListener mBtnClickListener = new OnBtnClickListener();
     65     Context mContext;
     66 
     67     Button mHeadsetPortYes;
     68     Button mHeadsetPortNo;
     69 
     70     Button mLoopbackPlugReady;
     71     TextView mAudioLevelText;
     72     SeekBar mAudioLevelSeekbar;
     73     LinearLayout mLinearLayout;
     74     Button mTestButton;
     75     TextView mResultText;
     76     ProgressBar mProgressBar;
     77 
     78     int mMaxLevel;
     79     private class OnBtnClickListener implements OnClickListener {
     80         @Override
     81         public void onClick(View v) {
     82             switch (v.getId()) {
     83                 case R.id.audio_loopback_plug_ready_btn:
     84                     Log.i(TAG, "audio loopback plug ready");
     85                     //enable all the other views.
     86                     enableLayout(true);
     87                     break;
     88                 case R.id.audio_loopback_test_btn:
     89                     Log.i(TAG, "audio loopback test");
     90                     startAudioTest();
     91                     break;
     92                 case R.id.audio_general_headset_yes:
     93                     Log.i(TAG, "User confirms Headset Port existence");
     94                     mLoopbackPlugReady.setEnabled(true);
     95                     recordHeasetPortFound(true);
     96                     mHeadsetPortYes.setEnabled(false);
     97                     mHeadsetPortNo.setEnabled(false);
     98                     break;
     99                 case R.id.audio_general_headset_no:
    100                     Log.i(TAG, "User denies Headset Port existence");
    101                     recordHeasetPortFound(false);
    102                     getPassButton().setEnabled(true);
    103                     mHeadsetPortYes.setEnabled(false);
    104                     mHeadsetPortNo.setEnabled(false);
    105                     break;
    106             }
    107         }
    108     }
    109 
    110     @Override
    111     protected void onCreate(Bundle savedInstanceState) {
    112         super.onCreate(savedInstanceState);
    113         setContentView(R.layout.audio_loopback_activity);
    114 
    115         mContext = this;
    116 
    117         mHeadsetPortYes = (Button)findViewById(R.id.audio_general_headset_yes);
    118         mHeadsetPortYes.setOnClickListener(mBtnClickListener);
    119         mHeadsetPortNo = (Button)findViewById(R.id.audio_general_headset_no);
    120         mHeadsetPortNo.setOnClickListener(mBtnClickListener);
    121 
    122         mLoopbackPlugReady = (Button)findViewById(R.id.audio_loopback_plug_ready_btn);
    123         mLoopbackPlugReady.setOnClickListener(mBtnClickListener);
    124         mLoopbackPlugReady.setEnabled(false);
    125         mLinearLayout = (LinearLayout)findViewById(R.id.audio_loopback_layout);
    126         mAudioLevelText = (TextView)findViewById(R.id.audio_loopback_level_text);
    127         mAudioLevelSeekbar = (SeekBar)findViewById(R.id.audio_loopback_level_seekbar);
    128         mTestButton =(Button)findViewById(R.id.audio_loopback_test_btn);
    129         mTestButton.setOnClickListener(mBtnClickListener);
    130         mResultText = (TextView)findViewById(R.id.audio_loopback_results_text);
    131         mProgressBar = (ProgressBar)findViewById(R.id.audio_loopback_progress_bar);
    132         showWait(false);
    133 
    134         enableLayout(false);         //disabled all content
    135         AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
    136         mMaxLevel = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
    137         mAudioLevelSeekbar.setMax(mMaxLevel);
    138         am.setStreamVolume(AudioManager.STREAM_MUSIC, (int)(0.7 * mMaxLevel), 0);
    139         refreshLevel();
    140 
    141         mAudioLevelSeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
    142             @Override
    143             public void onStopTrackingTouch(SeekBar seekBar) {
    144             }
    145 
    146             @Override
    147             public void onStartTrackingTouch(SeekBar seekBar) {
    148             }
    149 
    150             @Override
    151             public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
    152 
    153                 AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
    154                 am.setStreamVolume(AudioManager.STREAM_MUSIC,
    155                         progress, 0);
    156                 refreshLevel();
    157                 Log.i(TAG,"Changed stream volume to: " + progress);
    158             }
    159         });
    160 
    161         setPassFailButtonClickListeners();
    162         getPassButton().setEnabled(false);
    163         setInfoResources(R.string.audio_loopback_test, R.string.audio_loopback_info, -1);
    164     }
    165 
    166     /**
    167      * refresh Audio Level seekbar and text
    168      */
    169     private void refreshLevel() {
    170         AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
    171 
    172         int currentLevel = am.getStreamVolume(AudioManager.STREAM_MUSIC);
    173         mAudioLevelSeekbar.setProgress(currentLevel);
    174 
    175         String levelText = String.format("%s: %d/%d",
    176                 getResources().getString(R.string.audio_loopback_level_text),
    177                 currentLevel, mMaxLevel);
    178         mAudioLevelText.setText(levelText);
    179     }
    180 
    181     /**
    182      * enable test ui elements
    183      */
    184     private void enableLayout(boolean enable) {
    185         for (int i = 0; i<mLinearLayout.getChildCount(); i++) {
    186             View view = mLinearLayout.getChildAt(i);
    187             view.setEnabled(enable);
    188         }
    189     }
    190 
    191     /**
    192      * show active progress bar
    193      */
    194     private void showWait(boolean show) {
    195         if (show) {
    196             mProgressBar.setVisibility(View.VISIBLE) ;
    197         } else {
    198             mProgressBar.setVisibility(View.INVISIBLE) ;
    199         }
    200     }
    201 
    202     /**
    203      *  Start the loopback audio test
    204      */
    205     private void startAudioTest() {
    206         getPassButton().setEnabled(false);
    207 
    208         //get system defaults for sampling rate, buffers.
    209         AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
    210         String value = am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER);
    211         mMinBufferSizeInFrames = Integer.parseInt(value);
    212 
    213         int minBufferSizeInBytes = BYTES_PER_FRAME * mMinBufferSizeInFrames;
    214 
    215         mSamplingRate = AudioTrack.getNativeOutputSampleRate(AudioManager.STREAM_MUSIC);
    216 
    217         Log.i(TAG, String.format("startAudioTest sr:%d , buffer:%d frames",
    218                 mSamplingRate, mMinBufferSizeInFrames));
    219 
    220         nativeAudioThread = new NativeAudioThread();
    221         if (nativeAudioThread != null) {
    222             nativeAudioThread.setMessageHandler(mMessageHandler);
    223             nativeAudioThread.mSessionId = 0;
    224             nativeAudioThread.setParams(mSamplingRate,
    225                     minBufferSizeInBytes,
    226                     minBufferSizeInBytes,
    227                     0x03 /*voice recognition*/,
    228                     mNumFramesToIgnore);
    229             nativeAudioThread.start();
    230 
    231             try {
    232                 Thread.sleep(200);
    233             } catch (InterruptedException e) {
    234                 e.printStackTrace();
    235             }
    236 
    237             nativeAudioThread.runTest();
    238 
    239         }
    240     }
    241 
    242     /**
    243      * handler for messages from audio thread
    244      */
    245     private Handler mMessageHandler = new Handler() {
    246         public void handleMessage(Message msg) {
    247             super.handleMessage(msg);
    248             switch(msg.what) {
    249                 case NativeAudioThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_STARTED:
    250                     Log.v(TAG,"got message native rec started!!");
    251                     showWait(true);
    252                     mResultText.setText("Test Running...");
    253                     break;
    254                 case NativeAudioThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_ERROR:
    255                     Log.v(TAG,"got message native rec can't start!!");
    256                     showWait(false);
    257                     mResultText.setText("Test Error.");
    258                     break;
    259                 case NativeAudioThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE:
    260                 case NativeAudioThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE_ERRORS:
    261                     if (nativeAudioThread != null) {
    262                         Log.v(TAG,"Finished recording.");
    263                         double [] waveData = nativeAudioThread.getWaveData();
    264                         mCorrelation.computeCorrelation(waveData, mSamplingRate);
    265                         mResultText.setText(String.format(
    266                                 "Test Finished\nLatency:%.2f ms\nConfidence: %.2f",
    267                                 mCorrelation.mEstimatedLatencyMs,
    268                                 mCorrelation.mEstimatedLatencyConfidence));
    269 
    270                         recordTestResults();
    271                         if (mCorrelation.mEstimatedLatencyConfidence >= CONFIDENCE_THRESHOLD) {
    272                             getPassButton().setEnabled(true);
    273                         }
    274 
    275                         //close
    276                         if (nativeAudioThread != null) {
    277                             nativeAudioThread.isRunning = false;
    278                             try {
    279                                 nativeAudioThread.finish();
    280                                 nativeAudioThread.join();
    281                             } catch (InterruptedException e) {
    282                                 e.printStackTrace();
    283                             }
    284                             nativeAudioThread = null;
    285                         }
    286                         showWait(false);
    287                     }
    288                     break;
    289                 default:
    290                     break;
    291             }
    292         }
    293     };
    294 
    295     /**
    296      * Store test results in log
    297      */
    298     private void recordTestResults() {
    299 
    300         getReportLog().addValue(
    301                 "Estimated Latency",
    302                 mCorrelation.mEstimatedLatencyMs,
    303                 ResultType.LOWER_BETTER,
    304                 ResultUnit.MS);
    305 
    306         getReportLog().addValue(
    307                 "Confidence",
    308                 mCorrelation.mEstimatedLatencyConfidence,
    309                 ResultType.HIGHER_BETTER,
    310                 ResultUnit.NONE);
    311 
    312         int audioLevel = mAudioLevelSeekbar.getProgress();
    313         getReportLog().addValue(
    314                 "Audio Level",
    315                 audioLevel,
    316                 ResultType.NEUTRAL,
    317                 ResultUnit.NONE);
    318 
    319         getReportLog().addValue(
    320                 "Frames Buffer Size",
    321                 mMinBufferSizeInFrames,
    322                 ResultType.NEUTRAL,
    323                 ResultUnit.NONE);
    324 
    325         getReportLog().addValue(
    326                 "Sampling Rate",
    327                 mSamplingRate,
    328                 ResultType.NEUTRAL,
    329                 ResultUnit.NONE);
    330 
    331         Log.v(TAG,"Results Recorded");
    332     }
    333 
    334     private void recordHeasetPortFound(boolean found) {
    335         getReportLog().addValue(
    336                 "User Reported Headset Port",
    337                 found ? 1.0 : 0,
    338                 ResultType.NEUTRAL,
    339                 ResultUnit.NONE);
    340     }
    341 }
    342