Merge "Add more methods for capture and injection" into tm-dev

This commit is contained in:
Yuanjia Hsu
2022-03-03 00:43:31 +00:00
committed by Android (Google) Code Review
4 changed files with 220 additions and 34 deletions

View File

@@ -2849,19 +2849,31 @@ package android.companion.virtual {
package android.companion.virtual.audio {
public final class AudioCapture {
ctor public AudioCapture();
method @NonNull public android.media.AudioFormat getFormat();
method public int getRecordingState();
method public int read(@NonNull byte[], int, int);
method public int read(@NonNull byte[], int, int, int);
method public int read(@NonNull java.nio.ByteBuffer, int);
method public int read(@NonNull java.nio.ByteBuffer, int, int);
method public int read(@NonNull float[], int, int, int);
method public int read(@NonNull short[], int, int);
method public int read(@NonNull short[], int, int, int);
method public void startRecording();
method public void stop();
}
public final class AudioInjection {
ctor public AudioInjection();
method @NonNull public android.media.AudioFormat getFormat();
method public int getPlayState();
method public void play();
method public void stop();
method public int write(@NonNull byte[], int, int);
method public int write(@NonNull byte[], int, int, int);
method public int write(@NonNull java.nio.ByteBuffer, int, int);
method public int write(@NonNull java.nio.ByteBuffer, int, int, long);
method public int write(@NonNull float[], int, int, int);
method public int write(@NonNull short[], int, int);
method public int write(@NonNull short[], int, int, int);
}
public final class VirtualAudioDevice implements java.io.Closeable {

View File

@@ -16,13 +16,16 @@
package android.companion.virtual.audio;
import static android.media.AudioRecord.READ_BLOCKING;
import static android.media.AudioRecord.RECORDSTATE_RECORDING;
import static android.media.AudioRecord.RECORDSTATE_STOPPED;
import static android.media.AudioRecord.STATE_INITIALIZED;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.util.Log;
@@ -42,12 +45,12 @@ import java.nio.ByteBuffer;
public final class AudioCapture {
private static final String TAG = "AudioCapture";
private final AudioFormat mAudioFormat;
private final Object mLock = new Object();
@GuardedBy("mLock")
@Nullable
private AudioRecord mAudioRecord;
@GuardedBy("mLock")
private int mRecordingState = RECORDSTATE_STOPPED;
@@ -63,12 +66,12 @@ public final class AudioCapture {
void setAudioRecord(@Nullable AudioRecord audioRecord) {
Log.d(TAG, "set AudioRecord with " + audioRecord);
synchronized (mLock) {
// Release old reference.
if (mAudioRecord != null) {
mAudioRecord.release();
}
// Sync recording state for new reference.
if (audioRecord != null) {
if (audioRecord.getState() != STATE_INITIALIZED) {
throw new IllegalStateException("set an uninitialized AudioRecord.");
}
if (mRecordingState == RECORDSTATE_RECORDING
&& audioRecord.getRecordingState() != RECORDSTATE_RECORDING) {
audioRecord.startRecording();
@@ -78,16 +81,97 @@ public final class AudioCapture {
audioRecord.stop();
}
}
// Release old reference before assigning the new reference.
if (mAudioRecord != null) {
mAudioRecord.release();
}
mAudioRecord = audioRecord;
}
}
/** See {@link AudioRecord#read(ByteBuffer, int)}. */
public int read(@NonNull ByteBuffer audioBuffer, int sizeInBytes) {
AudioCapture(@NonNull AudioFormat audioFormat) {
mAudioFormat = audioFormat;
}
void close() {
synchronized (mLock) {
if (mAudioRecord != null) {
mAudioRecord.release();
mAudioRecord = null;
}
}
}
/** See {@link AudioRecord#getFormat()} */
public @NonNull AudioFormat getFormat() {
return mAudioFormat;
}
/** See {@link AudioRecord#read(byte[], int, int)} */
public int read(@NonNull byte[] audioData, int offsetInBytes, int sizeInBytes) {
return read(audioData, offsetInBytes, sizeInBytes, READ_BLOCKING);
}
/** See {@link AudioRecord#read(byte[], int, int, int)} */
public int read(@NonNull byte[] audioData, int offsetInBytes, int sizeInBytes,
@AudioRecord.ReadMode int readMode) {
final int sizeRead;
synchronized (mLock) {
if (mAudioRecord != null) {
sizeRead = mAudioRecord.read(audioBuffer, sizeInBytes);
sizeRead = mAudioRecord.read(audioData, offsetInBytes, sizeInBytes, readMode);
} else {
sizeRead = 0;
}
}
return sizeRead;
}
/** See {@link AudioRecord#read(ByteBuffer, int)}. */
public int read(@NonNull ByteBuffer audioBuffer, int sizeInBytes) {
return read(audioBuffer, sizeInBytes, READ_BLOCKING);
}
/** See {@link AudioRecord#read(ByteBuffer, int, int)}. */
public int read(@NonNull ByteBuffer audioBuffer, int sizeInBytes,
@AudioRecord.ReadMode int readMode) {
final int sizeRead;
synchronized (mLock) {
if (mAudioRecord != null) {
sizeRead = mAudioRecord.read(audioBuffer, sizeInBytes, readMode);
} else {
sizeRead = 0;
}
}
return sizeRead;
}
/** See {@link AudioRecord#read(float[], int, int, int)}. */
public int read(@NonNull float[] audioData, int offsetInFloats, int sizeInFloats,
@AudioRecord.ReadMode int readMode) {
final int sizeRead;
synchronized (mLock) {
if (mAudioRecord != null) {
sizeRead = mAudioRecord.read(audioData, offsetInFloats, sizeInFloats, readMode);
} else {
sizeRead = 0;
}
}
return sizeRead;
}
/** See {@link AudioRecord#read(short[], int, int)}. */
public int read(@NonNull short[] audioData, int offsetInShorts, int sizeInShorts) {
return read(audioData, offsetInShorts, sizeInShorts, READ_BLOCKING);
}
/** See {@link AudioRecord#read(short[], int, int, int)}. */
public int read(@NonNull short[] audioData, int offsetInShorts, int sizeInShorts,
@AudioRecord.ReadMode int readMode) {
final int sizeRead;
synchronized (mLock) {
if (mAudioRecord != null) {
sizeRead = mAudioRecord.read(audioData, offsetInShorts, sizeInShorts, readMode);
} else {
sizeRead = 0;
}

View File

@@ -18,11 +18,14 @@ package android.companion.virtual.audio;
import static android.media.AudioTrack.PLAYSTATE_PLAYING;
import static android.media.AudioTrack.PLAYSTATE_STOPPED;
import static android.media.AudioTrack.STATE_INITIALIZED;
import static android.media.AudioTrack.WRITE_BLOCKING;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.media.AudioFormat;
import android.media.AudioTrack;
import android.util.Log;
@@ -42,7 +45,9 @@ import java.nio.ByteBuffer;
public final class AudioInjection {
private static final String TAG = "AudioInjection";
private final AudioFormat mAudioFormat;
private final Object mLock = new Object();
@GuardedBy("mLock")
@Nullable
private AudioTrack mAudioTrack;
@@ -70,12 +75,12 @@ public final class AudioInjection {
void setAudioTrack(@Nullable AudioTrack audioTrack) {
Log.d(TAG, "set AudioTrack with " + audioTrack);
synchronized (mLock) {
// Release old reference.
if (mAudioTrack != null) {
mAudioTrack.release();
}
// Sync play state for new reference.
if (audioTrack != null) {
if (audioTrack.getState() != STATE_INITIALIZED) {
throw new IllegalStateException("set an uninitialized AudioTrack.");
}
if (mPlayState == PLAYSTATE_PLAYING
&& audioTrack.getPlayState() != PLAYSTATE_PLAYING) {
audioTrack.play();
@@ -85,10 +90,52 @@ public final class AudioInjection {
audioTrack.stop();
}
}
// Release old reference before assigning the new reference.
if (mAudioTrack != null) {
mAudioTrack.release();
}
mAudioTrack = audioTrack;
}
}
AudioInjection(@NonNull AudioFormat audioFormat) {
mAudioFormat = audioFormat;
}
void close() {
synchronized (mLock) {
if (mAudioTrack != null) {
mAudioTrack.release();
mAudioTrack = null;
}
}
}
/** See {@link AudioTrack#getFormat()}. */
public @NonNull AudioFormat getFormat() {
return mAudioFormat;
}
/** See {@link AudioTrack#write(byte[], int, int)}. */
public int write(@NonNull byte[] audioData, int offsetInBytes, int sizeInBytes) {
return write(audioData, offsetInBytes, sizeInBytes, WRITE_BLOCKING);
}
/** See {@link AudioTrack#write(byte[], int, int, int)}. */
public int write(@NonNull byte[] audioData, int offsetInBytes, int sizeInBytes,
@AudioTrack.WriteMode int writeMode) {
final int sizeWrite;
synchronized (mLock) {
if (mAudioTrack != null && !mIsSilent) {
sizeWrite = mAudioTrack.write(audioData, offsetInBytes, sizeInBytes, writeMode);
} else {
sizeWrite = 0;
}
}
return sizeWrite;
}
/** See {@link AudioTrack#write(ByteBuffer, int, int)}. */
public int write(@NonNull ByteBuffer audioBuffer, int sizeInBytes, int writeMode) {
final int sizeWrite;
@@ -102,6 +149,53 @@ public final class AudioInjection {
return sizeWrite;
}
/** See {@link AudioTrack#write(ByteBuffer, int, int, long)}. */
public int write(@NonNull ByteBuffer audioBuffer, int sizeInBytes,
@AudioTrack.WriteMode int writeMode, long timestamp) {
final int sizeWrite;
synchronized (mLock) {
if (mAudioTrack != null && !mIsSilent) {
sizeWrite = mAudioTrack.write(audioBuffer, sizeInBytes, writeMode, timestamp);
} else {
sizeWrite = 0;
}
}
return sizeWrite;
}
/** See {@link AudioTrack#write(float[], int, int, int)}. */
public int write(@NonNull float[] audioData, int offsetInFloats, int sizeInFloats,
@AudioTrack.WriteMode int writeMode) {
final int sizeWrite;
synchronized (mLock) {
if (mAudioTrack != null && !mIsSilent) {
sizeWrite = mAudioTrack.write(audioData, offsetInFloats, sizeInFloats, writeMode);
} else {
sizeWrite = 0;
}
}
return sizeWrite;
}
/** See {@link AudioTrack#write(short[], int, int)}. */
public int write(@NonNull short[] audioData, int offsetInShorts, int sizeInShorts) {
return write(audioData, offsetInShorts, sizeInShorts, WRITE_BLOCKING);
}
/** See {@link AudioTrack#write(short[], int, int, int)}. */
public int write(@NonNull short[] audioData, int offsetInShorts, int sizeInShorts,
@AudioTrack.WriteMode int writeMode) {
final int sizeWrite;
synchronized (mLock) {
if (mAudioTrack != null && !mIsSilent) {
sizeWrite = mAudioTrack.write(audioData, offsetInShorts, sizeInShorts, writeMode);
} else {
sizeWrite = 0;
}
}
return sizeWrite;
}
/** See {@link AudioTrack#play()}. */
public void play() {
synchronized (mLock) {

View File

@@ -68,12 +68,6 @@ public final class VirtualAudioSession extends IAudioSessionCallback.Stub implem
private AudioPolicy mAudioPolicy;
@Nullable
@GuardedBy("mLock")
private AudioFormat mCaptureFormat;
@Nullable
@GuardedBy("mLock")
private AudioFormat mInjectionFormat;
@Nullable
@GuardedBy("mLock")
private AudioCapture mAudioCapture;
@Nullable
@GuardedBy("mLock")
@@ -104,8 +98,7 @@ public final class VirtualAudioSession extends IAudioSessionCallback.Stub implem
"Cannot start capture while another capture is ongoing.");
}
mCaptureFormat = captureFormat;
mAudioCapture = new AudioCapture();
mAudioCapture = new AudioCapture(captureFormat);
mAudioCapture.startRecording();
return mAudioCapture;
}
@@ -127,8 +120,7 @@ public final class VirtualAudioSession extends IAudioSessionCallback.Stub implem
"Cannot start injection while injection is already ongoing.");
}
mInjectionFormat = injectionFormat;
mAudioInjection = new AudioInjection();
mAudioInjection = new AudioInjection(injectionFormat);
mAudioInjection.play();
mUserRestrictionsDetector.register(/* callback= */ this);
@@ -179,10 +171,14 @@ public final class VirtualAudioSession extends IAudioSessionCallback.Stub implem
mUserRestrictionsDetector.unregister();
releaseAudioStreams();
synchronized (mLock) {
mAudioCapture = null;
mAudioInjection = null;
mCaptureFormat = null;
mInjectionFormat = null;
if (mAudioCapture != null) {
mAudioCapture.close();
mAudioCapture = null;
}
if (mAudioInjection != null) {
mAudioInjection.close();
mAudioInjection = null;
}
}
}
@@ -198,9 +194,9 @@ public final class VirtualAudioSession extends IAudioSessionCallback.Stub implem
@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
private void createAudioStreams(int[] appUids) {
synchronized (mLock) {
if (mCaptureFormat == null && mInjectionFormat == null) {
if (mAudioCapture == null && mAudioInjection == null) {
throw new IllegalStateException(
"At least one of captureFormat and injectionFormat must be specified.");
"At least one of AudioCapture and AudioInjection must be started.");
}
if (mAudioPolicy != null) {
throw new IllegalStateException(
@@ -218,12 +214,12 @@ public final class VirtualAudioSession extends IAudioSessionCallback.Stub implem
AudioMix audioRecordMix = null;
AudioMix audioTrackMix = null;
AudioPolicy.Builder builder = new AudioPolicy.Builder(mContext);
if (mCaptureFormat != null) {
audioRecordMix = createAudioRecordMix(mCaptureFormat, appUids);
if (mAudioCapture != null) {
audioRecordMix = createAudioRecordMix(mAudioCapture.getFormat(), appUids);
builder.addMix(audioRecordMix);
}
if (mInjectionFormat != null) {
audioTrackMix = createAudioTrackMix(mInjectionFormat, appUids);
if (mAudioInjection != null) {
audioTrackMix = createAudioTrackMix(mAudioInjection.getFormat(), appUids);
builder.addMix(audioTrackMix);
}
mAudioPolicy = builder.build();