1 /*
2  * Copyright 2019 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.tv.settings.system.development.audio;
18 
19 import android.os.Handler;
20 import android.os.Looper;
21 import android.util.Log;
22 
23 import java.util.Optional;
24 
25 /** Stores audio recording metrics. Sends metrics updates through a callback. */
26 public class AudioMetrics {
27 
28     private static final String TAG = "AudioMetrics";
29 
30     private Optional<Long> mStartTs = Optional.empty();
31 
32     private Data mData = new Data();
33 
34     private final UpdateMetricsCallback mCallback;
35 
36     /** Contains data to be exposed via the callback. */
37     public static class Data {
38 
39         public Optional<Long> timeToStartReadMs = Optional.empty();
40         public Optional<Long> timeToValidAudioMs = Optional.empty();
41         public Optional<Long> emptyAudioDurationMs = Optional.empty();
42 
Data()43         public Data() {
44         }
45 
Data(Data data)46         public Data(Data data) {
47             this.timeToStartReadMs = data.timeToStartReadMs;
48             this.timeToValidAudioMs = data.timeToValidAudioMs;
49             this.emptyAudioDurationMs = data.emptyAudioDurationMs;
50         }
51     }
52 
53     /** Interface for receiving metrics updates. */
54     public interface UpdateMetricsCallback {
55         /** Callback for receiving metrics updates. */
onUpdateMetrics(AudioMetrics.Data data)56         void onUpdateMetrics(AudioMetrics.Data data);
57     }
58 
59     /**
60      * @param callback Callback for updates.
61      */
AudioMetrics(UpdateMetricsCallback callback)62     public AudioMetrics(UpdateMetricsCallback callback) {
63         this.mCallback = callback;
64     }
65 
66     /** Records the beginning of the audio recording process. */
start()67     public void start() {
68         mData = new Data();
69         mStartTs = Optional.of(System.currentTimeMillis());
70         updateMetrics();
71     }
72 
73     /** Records that we have started monitoring a buffer for incoming audio data. */
startedReading()74     public void startedReading() throws IllegalStateException {
75         long startTs = this.mStartTs
76                 .orElseThrow(() -> new IllegalStateException(
77                         "Started reading before recording started"));
78         long currentTs = System.currentTimeMillis();
79         mData = new Data(mData);
80         mData.timeToStartReadMs = Optional.of(currentTs - startTs);
81         updateMetrics();
82     }
83 
84     /** Records that we have started receiving non-zero audio data */
receivedValidAudio()85     public void receivedValidAudio() throws IllegalStateException {
86         long startTs = this.mStartTs
87                 .orElseThrow(() -> new IllegalStateException(
88                         "Received audio data before recording started"));
89         long currentTs = System.currentTimeMillis();
90         mData = new Data(mData);
91         mData.timeToValidAudioMs = Optional.of(currentTs - startTs);
92         updateMetrics();
93     }
94 
95     /** Records the duration of empty audio received before valid audio */
setEmptyAudioDurationMs(long emptyAudioMs)96     public void setEmptyAudioDurationMs(long emptyAudioMs) {
97         mData = new Data(mData);
98         mData.emptyAudioDurationMs = Optional.of(emptyAudioMs);
99         updateMetrics();
100     }
101 
102     /** Sends updated data through the callback */
updateMetrics()103     private void updateMetrics() {
104         Handler mainHandler = new Handler(Looper.getMainLooper());
105         mainHandler.post(() -> mCallback.onUpdateMetrics(mData));
106         Log.i(TAG, String.format("Time to start reading: %s",
107                 msTimestampToString(mData.timeToStartReadMs)));
108         Log.i(TAG, String.format("Time to valid audio data: %s",
109                 msTimestampToString(mData.timeToValidAudioMs)));
110         Log.i(TAG, String.format("Empty audio duration: %s",
111                 msTimestampToString(mData.emptyAudioDurationMs)));
112     }
113 
114     /** Converts a possible timestamp in milliseconds to its string representation. */
msTimestampToString(Optional<Long> optL)115     public static String msTimestampToString(Optional<Long> optL) {
116         return optL.map((Long l) -> String.format("%s ms", l)).orElse("");
117     }
118 }
119 
120