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