AudioRecordInput.java

// SPDX-License-Identifier: Apache-2.0
package com.shengzhiai.yugu.stcompat.internal;

import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.Process;

/** {@link AudioInput} on {@link AudioRecord}: 16 kHz, mono, PCM 16 bit. */
public final class AudioRecordInput implements AudioInput {
    public static final Factory FACTORY = new Factory() {
        @Override
        public AudioInput open(Context context, Integer audioSource) throws AudioInputException {
            return AudioRecordInput.open(context, audioSource);
        }
    };

    private final AudioRecord record;
    private boolean released;

    private AudioRecordInput(AudioRecord record) {
        this.record = record;
    }

    static AudioRecordInput open(Context context, Integer audioSource) throws AudioInputException {
        if (context != null && context.checkPermission(Manifest.permission.RECORD_AUDIO, Process.myPid(),
                Process.myUid()) != PackageManager.PERMISSION_GRANTED) {
            throw new AudioInputException("RECORD_AUDIO permission not granted", null);
        }
        int min = AudioRecord.getMinBufferSize(Wav.SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO,
                AudioFormat.ENCODING_PCM_16BIT);
        int size = Math.max(min > 0 ? min * 2 : 0, Wav.BYTES_PER_SECOND / 5);
        int source = audioSource != null ? audioSource : MediaRecorder.AudioSource.MIC;
        AudioRecord r;
        try {
            r = new AudioRecord(source, Wav.SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO,
                    AudioFormat.ENCODING_PCM_16BIT, size);
        } catch (SecurityException e) {
            throw new AudioInputException("RECORD_AUDIO permission denied", e);
        } catch (IllegalArgumentException e) {
            throw new AudioInputException("unsupported audio source " + source, e);
        }
        if (r.getState() != AudioRecord.STATE_INITIALIZED) {
            r.release();
            throw new AudioInputException("microphone unavailable (AudioRecord not initialized)", null);
        }
        return new AudioRecordInput(r);
    }

    @Override
    public synchronized void start() throws AudioInputException {
        try {
            record.startRecording();
        } catch (IllegalStateException e) {
            throw new AudioInputException("microphone unavailable: " + e.getMessage(), e);
        }
        if (record.getRecordingState() != AudioRecord.RECORDSTATE_RECORDING) {
            throw new AudioInputException("microphone is used by another app", null);
        }
    }

    @Override
    public int read(byte[] buffer, int offset, int length) {
        return record.read(buffer, offset, length);
    }

    @Override
    public synchronized void stop() {
        if (released) {
            return;
        }
        try {
            if (record.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
                record.stop();
            }
        } catch (IllegalStateException ignored) {
            // already stopped
        }
    }

    @Override
    public synchronized void release() {
        if (released) {
            return;
        }
        released = true;
        stop();
        record.release();
    }
}