Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8967e85c authored by Yuanjia Hsu's avatar Yuanjia Hsu Committed by Android (Google) Code Review
Browse files

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

parents 6c6f6bd6 5adb0276
Loading
Loading
Loading
Loading
+14 −2
Original line number Diff line number Diff line
@@ -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 {
+90 −6
Original line number Diff line number Diff line
@@ -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;
        }
    }

    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(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(audioBuffer, sizeInBytes);
                sizeRead = mAudioRecord.read(audioData, offsetInShorts, sizeInShorts, readMode);
            } else {
                sizeRead = 0;
            }
+98 −4
Original line number Diff line number Diff line
@@ -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) {
+16 −20
Original line number Diff line number Diff line
@@ -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) {
            if (mAudioCapture != null) {
                mAudioCapture.close();
                mAudioCapture = null;
            }
            if (mAudioInjection != null) {
                mAudioInjection.close();
                mAudioInjection = null;
            mCaptureFormat = null;
            mInjectionFormat = 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();