• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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 android.virtualdevice.cts.common;
18 
19 import static android.media.AudioRecord.READ_BLOCKING;
20 import static android.media.AudioRecord.READ_NON_BLOCKING;
21 
22 import android.annotation.IntDef;
23 import android.companion.virtual.audio.AudioCapture;
24 import android.media.AudioRecord;
25 
26 import java.lang.annotation.Retention;
27 import java.lang.annotation.RetentionPolicy;
28 import java.nio.ByteBuffer;
29 import java.nio.ByteOrder;
30 
31 /**
32  * Utility methods for creating and processing audio data.
33  */
34 public final class AudioHelper {
35     /** Tells the activity to play audio for testing. */
36     public static final String ACTION_PLAY_AUDIO = "android.virtualdevice.cts.PLAY_AUDIO";
37 
38     /** Tells the activity to record audio for testing. */
39     public static final String ACTION_RECORD_AUDIO = "android.virtualdevice.cts.RECORD_AUDIO";
40 
41     /** Tells the activity to play or record for which audio data type. */
42     public static final String EXTRA_AUDIO_DATA_TYPE = "audio_data_type";
43 
44     @IntDef({
45             BYTE_BUFFER,
46             BYTE_ARRAY,
47             SHORT_ARRAY,
48             FLOAT_ARRAY,
49     })
50     @Retention(RetentionPolicy.SOURCE)
51     public @interface DataType {}
52 
53     public static final int BYTE_BUFFER = 1;
54     public static final int BYTE_ARRAY = 2;
55     public static final int SHORT_ARRAY = 3;
56     public static final int FLOAT_ARRAY = 4;
57 
58     /** Values for read and write verification. */
59     public static final byte BYTE_VALUE = 8;
60     public static final short SHORT_VALUE = 16;
61     public static final float FLOAT_VALUE = 0.8f;
62 
63     /** Constants of audio config for testing. */
64     public static final int FREQUENCY = 264;
65     public static final int SAMPLE_RATE = 44100;
66     public static final int CHANNEL_COUNT = 1;
67     public static final int AMPLITUDE = 32767;
68     public static final int BUFFER_SIZE_IN_BYTES = 65536;
69     public static final int NUMBER_OF_SAMPLES = computeNumSamples(/* timeMs= */ 1000, SAMPLE_RATE,
70             CHANNEL_COUNT);
71 
72     public static class CapturedAudio {
73         private int mSamplingRate;
74         private int mChannelCount;
75         private ByteBuffer mCapturedData;
76         private byte mByteValue;
77         private short mShortValue;
78         private float mFloatValue;
79 
CapturedAudio(AudioRecord audioRecord)80         public CapturedAudio(AudioRecord audioRecord) {
81             mSamplingRate = audioRecord.getSampleRate();
82             mChannelCount = audioRecord.getChannelCount();
83             ByteBuffer byteBuffer = ByteBuffer.allocateDirect(BUFFER_SIZE_IN_BYTES).order(
84                     ByteOrder.nativeOrder());
85             while (true) {
86                 // Read the first buffer with non-zero data
87                 byteBuffer.clear();
88                 int bytesRead = audioRecord.read(byteBuffer, BUFFER_SIZE_IN_BYTES);
89                 if (bytesRead == 0 || isAllZero(byteBuffer)) {
90                     continue;
91                 }
92                 mCapturedData = byteBuffer;
93                 break;
94             }
95         }
96 
CapturedAudio(AudioCapture audioCapture, ByteBuffer byteBuffer, int readMode)97         public CapturedAudio(AudioCapture audioCapture, ByteBuffer byteBuffer, int readMode) {
98             mSamplingRate = audioCapture.getFormat().getSampleRate();
99             mChannelCount = audioCapture.getFormat().getChannelCount();
100             while (true) {
101                 // Read the first buffer with non-zero data
102                 byteBuffer.clear();
103                 int bytesRead;
104                 if (readMode == READ_BLOCKING || readMode == READ_NON_BLOCKING) {
105                     bytesRead = audioCapture.read(byteBuffer, BUFFER_SIZE_IN_BYTES, readMode);
106                 } else {
107                     bytesRead = audioCapture.read(byteBuffer, BUFFER_SIZE_IN_BYTES);
108                 }
109                 if (bytesRead == 0 || isAllZero(byteBuffer)) {
110                     continue;
111                 }
112                 mCapturedData = byteBuffer;
113                 break;
114             }
115         }
116 
CapturedAudio(AudioCapture audioCapture, byte[] audioData, int readMode)117         public CapturedAudio(AudioCapture audioCapture, byte[] audioData, int readMode) {
118             while (true) {
119                 int bytesRead;
120                 if (readMode == READ_BLOCKING || readMode == READ_NON_BLOCKING) {
121                     bytesRead = audioCapture.read(audioData, 0, audioData.length, readMode);
122                 } else {
123                     bytesRead = audioCapture.read(audioData, 0, audioData.length);
124                 }
125                 if (bytesRead == 0) {
126                     continue;
127                 }
128                 break;
129             }
130             for (int i = 0; i < audioData.length; i++) {
131                 if (audioData[i] != 0) {
132                     mByteValue = audioData[i];
133                     break;
134                 }
135             }
136         }
137 
CapturedAudio(AudioCapture audioCapture, short[] audioData, int readMode)138         public CapturedAudio(AudioCapture audioCapture, short[] audioData, int readMode) {
139             while (true) {
140                 int bytesRead;
141                 if (readMode == READ_BLOCKING || readMode == READ_NON_BLOCKING) {
142                     bytesRead = audioCapture.read(audioData, 0, audioData.length, readMode);
143                 } else {
144                     bytesRead = audioCapture.read(audioData, 0, audioData.length);
145                 }
146                 if (bytesRead == 0) {
147                     continue;
148                 }
149                 break;
150             }
151             for (int i = 0; i < audioData.length; i++) {
152                 if (audioData[i] != 0) {
153                     mShortValue = audioData[i];
154                     break;
155                 }
156             }
157         }
158 
CapturedAudio(AudioCapture audioCapture, float[] audioData, int readMode)159         public CapturedAudio(AudioCapture audioCapture, float[] audioData, int readMode) {
160             while (true) {
161                 int bytesRead = audioCapture.read(audioData, 0, audioData.length, readMode);
162                 if (bytesRead == 0) {
163                     continue;
164                 }
165                 break;
166             }
167             for (int i = 0; i < audioData.length; i++) {
168                 if (audioData[i] != 0) {
169                     mFloatValue = audioData[i];
170                     break;
171                 }
172             }
173         }
174 
getPowerSpectrum(int frequency)175         public double getPowerSpectrum(int frequency) {
176             return getCapturedPowerSpectrum(mSamplingRate, mChannelCount, mCapturedData, frequency);
177         }
178 
getByteValue()179         public byte getByteValue() {
180             return mByteValue;
181         }
182 
getShortValue()183         public short getShortValue() {
184             return mShortValue;
185         }
186 
getFloatValue()187         public float getFloatValue() {
188             return mFloatValue;
189         }
190     }
191 
computeNumSamples(int timeMs, int samplingRate, int channelCount)192     public static int computeNumSamples(int timeMs, int samplingRate, int channelCount) {
193         return (int) ((long) timeMs * samplingRate * channelCount / 1000);
194     }
195 
createAudioData(int samplingRate, int numSamples, int channelCount, double signalFrequencyHz, float amplitude)196     public static ByteBuffer createAudioData(int samplingRate, int numSamples, int channelCount,
197             double signalFrequencyHz, float amplitude) {
198         ByteBuffer playBuffer =
199                 ByteBuffer.allocateDirect(numSamples * 2).order(ByteOrder.nativeOrder());
200         final double multiplier = 2f * Math.PI * signalFrequencyHz / samplingRate;
201         for (int i = 0; i < numSamples; ) {
202             double vDouble = amplitude * Math.sin(multiplier * (i / channelCount));
203             short v = (short) vDouble;
204             for (int c = 0; c < channelCount; c++) {
205                 playBuffer.putShort(i * 2, v);
206                 i++;
207             }
208         }
209         return playBuffer;
210     }
211 
getCapturedPowerSpectrum( int samplingFreq, int channelCount, ByteBuffer capturedData, int expectedSignalFreq)212     public static double getCapturedPowerSpectrum(
213             int samplingFreq, int channelCount, ByteBuffer capturedData,
214             int expectedSignalFreq) {
215         if (capturedData == null) {
216             return 0;
217         }
218         double power = 0;
219         int length = capturedData.remaining() / 2;  // PCM16, so 2 bytes for each
220         for (int i = 0; i < channelCount; i++) {
221             // Get the power in that channel
222             double goertzel = goertzel(
223                     expectedSignalFreq,
224                     samplingFreq,
225                     capturedData,
226                     /* offset= */ i,
227                     length,
228                     channelCount);
229             power += goertzel / channelCount;
230         }
231         return power;
232     }
233 
234     /**
235      * Computes the relative power of a given frequency within a frame of the signal.
236      * See: http://en.wikipedia.org/wiki/Goertzel_algorithm
237      */
goertzel(int signalFreq, int samplingFreq, ByteBuffer samples, int offset, int length, int stride)238     private static double goertzel(int signalFreq, int samplingFreq,
239             ByteBuffer samples, int offset, int length, int stride) {
240         final int n = length / stride;
241         final double coeff = Math.cos(signalFreq * 2 * Math.PI / samplingFreq) * 2;
242         double s1 = 0;
243         double s2 = 0;
244         double rms = 0;
245         for (int i = 0; i < n; i++) {
246             double hamming = 0.54 - 0.46 * Math.cos(2 * Math.PI * i / (n - 1));
247             double x = samples.getShort(i * 2 * stride + offset) * hamming; // apply hamming window
248             double s = x + coeff * s1 - s2;
249             s2 = s1;
250             s1 = s;
251             rms += x * x;
252         }
253         rms = Math.sqrt(rms / n);
254         double magnitude = s2 * s2 + s1 * s1 - coeff * s1 * s2;
255         return Math.sqrt(magnitude) / n / rms;
256     }
257 
isAllZero(ByteBuffer byteBuffer)258     private static boolean isAllZero(ByteBuffer byteBuffer) {
259         int position = byteBuffer.position();
260         int limit = byteBuffer.limit();
261         for (int i = position; i < limit; i += 2) {
262             if (byteBuffer.getShort(i) != 0) {
263                 return false;
264             }
265         }
266         return true;
267     }
268 }
269