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.AudioTrack.PLAYSTATE_PLAYING; 20 import static android.media.AudioTrack.PLAYSTATE_STOPPED; 21 import static android.media.AudioTrack.STATE_INITIALIZED; 22 import static android.media.AudioTrack.WRITE_BLOCKING; 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.AudioTrack; 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 AudioTrack} that allows for the underlying {@link AudioTrack} to 38 * be swapped out while playout 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 AudioInjection { 46 private static final String TAG = "AudioInjection"; 47 48 private final AudioFormat mAudioFormat; 49 private final Object mLock = new Object(); 50 51 @GuardedBy("mLock") 52 @Nullable 53 private AudioTrack mAudioTrack; 54 @GuardedBy("mLock") 55 private int mPlayState = PLAYSTATE_STOPPED; 56 @GuardedBy("mLock") 57 private boolean mIsSilent; 58 59 /** Sets if the injected microphone sound is silent. */ setSilent(boolean isSilent)60 void setSilent(boolean isSilent) { 61 synchronized (mLock) { 62 mIsSilent = isSilent; 63 } 64 } 65 66 /** 67 * Sets the {@link AudioTrack} to handle audio injection. 68 * 69 * <p>Callers may call this multiple times with different audio tracks to change the underlying 70 * {@link AudioTrack} without stopping and re-starting injection. 71 * 72 * @param audioTrack The underlying {@link AudioTrack} to use for injection, or null if no audio 73 * (i.e. silence) should be injected while still keeping the record in a playing state. 74 */ setAudioTrack(@ullable AudioTrack audioTrack)75 void setAudioTrack(@Nullable AudioTrack audioTrack) { 76 Log.d(TAG, "set AudioTrack with " + audioTrack); 77 synchronized (mLock) { 78 // Sync play state for new reference. 79 if (audioTrack != null) { 80 if (audioTrack.getState() != STATE_INITIALIZED) { 81 throw new IllegalStateException("set an uninitialized AudioTrack."); 82 } 83 84 if (mPlayState == PLAYSTATE_PLAYING 85 && audioTrack.getPlayState() != PLAYSTATE_PLAYING) { 86 audioTrack.play(); 87 } 88 if (mPlayState == PLAYSTATE_STOPPED 89 && audioTrack.getPlayState() != PLAYSTATE_STOPPED) { 90 audioTrack.stop(); 91 } 92 } 93 94 // Release old reference before assigning the new reference. 95 if (mAudioTrack != null) { 96 mAudioTrack.release(); 97 } 98 mAudioTrack = audioTrack; 99 } 100 } 101 AudioInjection(@onNull AudioFormat audioFormat)102 AudioInjection(@NonNull AudioFormat audioFormat) { 103 mAudioFormat = audioFormat; 104 } 105 close()106 void close() { 107 synchronized (mLock) { 108 if (mAudioTrack != null) { 109 mAudioTrack.release(); 110 mAudioTrack = null; 111 } 112 } 113 } 114 115 /** See {@link AudioTrack#getFormat()}. */ getFormat()116 public @NonNull AudioFormat getFormat() { 117 return mAudioFormat; 118 } 119 120 /** See {@link AudioTrack#write(byte[], int, int)}. */ write(@onNull byte[] audioData, int offsetInBytes, int sizeInBytes)121 public int write(@NonNull byte[] audioData, int offsetInBytes, int sizeInBytes) { 122 return write(audioData, offsetInBytes, sizeInBytes, WRITE_BLOCKING); 123 } 124 125 /** See {@link AudioTrack#write(byte[], int, int, int)}. */ write(@onNull byte[] audioData, int offsetInBytes, int sizeInBytes, @AudioTrack.WriteMode int writeMode)126 public int write(@NonNull byte[] audioData, int offsetInBytes, int sizeInBytes, 127 @AudioTrack.WriteMode int writeMode) { 128 final int sizeWrite; 129 synchronized (mLock) { 130 if (mAudioTrack != null && !mIsSilent) { 131 sizeWrite = mAudioTrack.write(audioData, offsetInBytes, sizeInBytes, writeMode); 132 } else { 133 sizeWrite = 0; 134 } 135 } 136 return sizeWrite; 137 } 138 139 /** See {@link AudioTrack#write(ByteBuffer, int, int)}. */ write(@onNull ByteBuffer audioBuffer, int sizeInBytes, int writeMode)140 public int write(@NonNull ByteBuffer audioBuffer, int sizeInBytes, int writeMode) { 141 final int sizeWrite; 142 synchronized (mLock) { 143 if (mAudioTrack != null && !mIsSilent) { 144 sizeWrite = mAudioTrack.write(audioBuffer, sizeInBytes, writeMode); 145 } else { 146 sizeWrite = 0; 147 } 148 } 149 return sizeWrite; 150 } 151 152 /** See {@link AudioTrack#write(ByteBuffer, int, int, long)}. */ write(@onNull ByteBuffer audioBuffer, int sizeInBytes, @AudioTrack.WriteMode int writeMode, long timestamp)153 public int write(@NonNull ByteBuffer audioBuffer, int sizeInBytes, 154 @AudioTrack.WriteMode int writeMode, long timestamp) { 155 final int sizeWrite; 156 synchronized (mLock) { 157 if (mAudioTrack != null && !mIsSilent) { 158 sizeWrite = mAudioTrack.write(audioBuffer, sizeInBytes, writeMode, timestamp); 159 } else { 160 sizeWrite = 0; 161 } 162 } 163 return sizeWrite; 164 } 165 166 /** See {@link AudioTrack#write(float[], int, int, int)}. */ write(@onNull float[] audioData, int offsetInFloats, int sizeInFloats, @AudioTrack.WriteMode int writeMode)167 public int write(@NonNull float[] audioData, int offsetInFloats, int sizeInFloats, 168 @AudioTrack.WriteMode int writeMode) { 169 final int sizeWrite; 170 synchronized (mLock) { 171 if (mAudioTrack != null && !mIsSilent) { 172 sizeWrite = mAudioTrack.write(audioData, offsetInFloats, sizeInFloats, writeMode); 173 } else { 174 sizeWrite = 0; 175 } 176 } 177 return sizeWrite; 178 } 179 180 /** See {@link AudioTrack#write(short[], int, int)}. */ write(@onNull short[] audioData, int offsetInShorts, int sizeInShorts)181 public int write(@NonNull short[] audioData, int offsetInShorts, int sizeInShorts) { 182 return write(audioData, offsetInShorts, sizeInShorts, WRITE_BLOCKING); 183 } 184 185 /** See {@link AudioTrack#write(short[], int, int, int)}. */ write(@onNull short[] audioData, int offsetInShorts, int sizeInShorts, @AudioTrack.WriteMode int writeMode)186 public int write(@NonNull short[] audioData, int offsetInShorts, int sizeInShorts, 187 @AudioTrack.WriteMode int writeMode) { 188 final int sizeWrite; 189 synchronized (mLock) { 190 if (mAudioTrack != null && !mIsSilent) { 191 sizeWrite = mAudioTrack.write(audioData, offsetInShorts, sizeInShorts, writeMode); 192 } else { 193 sizeWrite = 0; 194 } 195 } 196 return sizeWrite; 197 } 198 199 /** See {@link AudioTrack#play()}. */ play()200 public void play() { 201 synchronized (mLock) { 202 mPlayState = PLAYSTATE_PLAYING; 203 if (mAudioTrack != null && mAudioTrack.getPlayState() != PLAYSTATE_PLAYING) { 204 mAudioTrack.play(); 205 } 206 } 207 } 208 209 /** See {@link AudioTrack#stop()}. */ stop()210 public void stop() { 211 synchronized (mLock) { 212 mPlayState = PLAYSTATE_STOPPED; 213 if (mAudioTrack != null && mAudioTrack.getPlayState() != PLAYSTATE_STOPPED) { 214 mAudioTrack.stop(); 215 } 216 } 217 } 218 219 /** See {@link AudioTrack#getPlayState()}. */ getPlayState()220 public int getPlayState() { 221 synchronized (mLock) { 222 return mPlayState; 223 } 224 } 225 } 226