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.companion.virtual.audio; 18 19 import static android.media.AudioRecord.READ_BLOCKING; 20 import static android.media.AudioRecord.RECORDSTATE_RECORDING; 21 import static android.media.AudioRecord.RECORDSTATE_STOPPED; 22 import static android.media.AudioRecord.STATE_INITIALIZED; 23 24 import android.annotation.NonNull; 25 import android.annotation.Nullable; 26 import android.annotation.SuppressLint; 27 import android.annotation.SystemApi; 28 import android.media.AudioFormat; 29 import android.media.AudioRecord; 30 import android.util.Log; 31 32 import com.android.internal.annotations.GuardedBy; 33 34 import java.nio.ByteBuffer; 35 36 /** 37 * Wrapper around {@link AudioRecord} that allows for the underlying {@link AudioRecord} to 38 * be swapped out while recording is ongoing. 39 * 40 * @hide 41 */ 42 // The stop() actually doesn't release resources, so should not force implementing Closeable. 43 @SuppressLint("NotCloseable") 44 @SystemApi 45 public final class AudioCapture { 46 private static final String TAG = "AudioCapture"; 47 48 private final AudioFormat mAudioFormat; 49 private final Object mLock = new Object(); 50 51 @GuardedBy("mLock") 52 @Nullable 53 private AudioRecord mAudioRecord; 54 @GuardedBy("mLock") 55 private int mRecordingState = RECORDSTATE_STOPPED; 56 57 /** 58 * Sets the {@link AudioRecord} to handle audio capturing. 59 * Callers may call this multiple times with different audio records to change 60 * the underlying {@link AudioRecord} without stopping and re-starting recording. 61 * 62 * @param audioRecord The underlying {@link AudioRecord} to use for capture, 63 * or null if no audio (i.e. silence) should be captured while still keeping the 64 * record in a recording state. 65 */ setAudioRecord(@ullable AudioRecord audioRecord)66 void setAudioRecord(@Nullable AudioRecord audioRecord) { 67 Log.d(TAG, "set AudioRecord with " + audioRecord); 68 synchronized (mLock) { 69 // Sync recording state for new reference. 70 if (audioRecord != null) { 71 if (audioRecord.getState() != STATE_INITIALIZED) { 72 throw new IllegalStateException("set an uninitialized AudioRecord."); 73 } 74 75 if (mRecordingState == RECORDSTATE_RECORDING 76 && audioRecord.getRecordingState() != RECORDSTATE_RECORDING) { 77 audioRecord.startRecording(); 78 } 79 if (mRecordingState == RECORDSTATE_STOPPED 80 && audioRecord.getRecordingState() != RECORDSTATE_STOPPED) { 81 audioRecord.stop(); 82 } 83 } 84 85 // Release old reference before assigning the new reference. 86 if (mAudioRecord != null) { 87 mAudioRecord.release(); 88 } 89 mAudioRecord = audioRecord; 90 } 91 } 92 AudioCapture(@onNull AudioFormat audioFormat)93 AudioCapture(@NonNull AudioFormat audioFormat) { 94 mAudioFormat = audioFormat; 95 } 96 close()97 void close() { 98 synchronized (mLock) { 99 if (mAudioRecord != null) { 100 mAudioRecord.release(); 101 mAudioRecord = null; 102 } 103 } 104 } 105 106 /** See {@link AudioRecord#getFormat()} */ getFormat()107 public @NonNull AudioFormat getFormat() { 108 return mAudioFormat; 109 } 110 111 /** See {@link AudioRecord#read(byte[], int, int)} */ read(@onNull byte[] audioData, int offsetInBytes, int sizeInBytes)112 public int read(@NonNull byte[] audioData, int offsetInBytes, int sizeInBytes) { 113 return read(audioData, offsetInBytes, sizeInBytes, READ_BLOCKING); 114 } 115 116 /** See {@link AudioRecord#read(byte[], int, int, int)} */ read(@onNull byte[] audioData, int offsetInBytes, int sizeInBytes, @AudioRecord.ReadMode int readMode)117 public int read(@NonNull byte[] audioData, int offsetInBytes, int sizeInBytes, 118 @AudioRecord.ReadMode int readMode) { 119 final int sizeRead; 120 synchronized (mLock) { 121 if (mAudioRecord != null) { 122 sizeRead = mAudioRecord.read(audioData, offsetInBytes, sizeInBytes, readMode); 123 } else { 124 sizeRead = 0; 125 } 126 } 127 return sizeRead; 128 } 129 130 /** See {@link AudioRecord#read(ByteBuffer, int)}. */ read(@onNull ByteBuffer audioBuffer, int sizeInBytes)131 public int read(@NonNull ByteBuffer audioBuffer, int sizeInBytes) { 132 return read(audioBuffer, sizeInBytes, READ_BLOCKING); 133 } 134 135 /** See {@link AudioRecord#read(ByteBuffer, int, int)}. */ read(@onNull ByteBuffer audioBuffer, int sizeInBytes, @AudioRecord.ReadMode int readMode)136 public int read(@NonNull ByteBuffer audioBuffer, int sizeInBytes, 137 @AudioRecord.ReadMode int readMode) { 138 final int sizeRead; 139 synchronized (mLock) { 140 if (mAudioRecord != null) { 141 sizeRead = mAudioRecord.read(audioBuffer, sizeInBytes, readMode); 142 } else { 143 sizeRead = 0; 144 } 145 } 146 return sizeRead; 147 } 148 149 /** See {@link AudioRecord#read(float[], int, int, int)}. */ read(@onNull float[] audioData, int offsetInFloats, int sizeInFloats, @AudioRecord.ReadMode int readMode)150 public int read(@NonNull float[] audioData, int offsetInFloats, int sizeInFloats, 151 @AudioRecord.ReadMode int readMode) { 152 final int sizeRead; 153 synchronized (mLock) { 154 if (mAudioRecord != null) { 155 sizeRead = mAudioRecord.read(audioData, offsetInFloats, sizeInFloats, readMode); 156 } else { 157 sizeRead = 0; 158 } 159 } 160 return sizeRead; 161 } 162 163 /** See {@link AudioRecord#read(short[], int, int)}. */ read(@onNull short[] audioData, int offsetInShorts, int sizeInShorts)164 public int read(@NonNull short[] audioData, int offsetInShorts, int sizeInShorts) { 165 return read(audioData, offsetInShorts, sizeInShorts, READ_BLOCKING); 166 } 167 168 /** See {@link AudioRecord#read(short[], int, int, int)}. */ read(@onNull short[] audioData, int offsetInShorts, int sizeInShorts, @AudioRecord.ReadMode int readMode)169 public int read(@NonNull short[] audioData, int offsetInShorts, int sizeInShorts, 170 @AudioRecord.ReadMode int readMode) { 171 final int sizeRead; 172 synchronized (mLock) { 173 if (mAudioRecord != null) { 174 sizeRead = mAudioRecord.read(audioData, offsetInShorts, sizeInShorts, readMode); 175 } else { 176 sizeRead = 0; 177 } 178 } 179 return sizeRead; 180 } 181 182 /** See {@link AudioRecord#startRecording()}. */ startRecording()183 public void startRecording() { 184 synchronized (mLock) { 185 mRecordingState = RECORDSTATE_RECORDING; 186 if (mAudioRecord != null && mAudioRecord.getRecordingState() != RECORDSTATE_RECORDING) { 187 mAudioRecord.startRecording(); 188 } 189 } 190 } 191 192 /** See {@link AudioRecord#stop()}. */ stop()193 public void stop() { 194 synchronized (mLock) { 195 mRecordingState = RECORDSTATE_STOPPED; 196 if (mAudioRecord != null && mAudioRecord.getRecordingState() != RECORDSTATE_STOPPED) { 197 mAudioRecord.stop(); 198 } 199 } 200 } 201 202 /** See {@link AudioRecord#getRecordingState()}. */ getRecordingState()203 public int getRecordingState() { 204 synchronized (mLock) { 205 return mRecordingState; 206 } 207 } 208 } 209