• 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.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      *
60      * <p>Callers may call this multiple times with different audio records to change the underlying
61      * {@link AudioRecord} without stopping and re-starting recording.
62      *
63      * @param audioRecord The underlying {@link AudioRecord} to use for capture, or null if no audio
64      *   (i.e. silence) should be captured while still keeping the 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