package com.pedro.encoder.input.audio; import android.media.AudioFormat; import android.media.AudioPlaybackCaptureConfiguration; import android.media.AudioRecord; import android.media.MediaRecorder; import android.media.projection.MediaProjection; import android.os.Build; import android.os.Handler; import android.os.HandlerThread; import android.util.Log; import com.pedro.encoder.Frame; import java.nio.ByteBuffer; /** * Created by pedro on 19/01/17. */ public class MicrophoneManager { private final String TAG = "MicrophoneManager"; private static final int BUFFER_SIZE = 4096; protected AudioRecord audioRecord; private GetMicrophoneData getMicrophoneData; private ByteBuffer pcmBuffer = ByteBuffer.allocateDirect(BUFFER_SIZE); private byte[] pcmBufferMuted = new byte[BUFFER_SIZE]; protected boolean running = false; private boolean created = false; //default parameters for microphone private int sampleRate = 32000; //hz private int audioFormat = AudioFormat.ENCODING_PCM_16BIT; private int channel = AudioFormat.CHANNEL_IN_STEREO; private boolean muted = false; private AudioPostProcessEffect audioPostProcessEffect; HandlerThread handlerThread; private CustomAudioEffect customAudioEffect = new NoAudioEffect(); public MicrophoneManager(GetMicrophoneData getMicrophoneData) { this.getMicrophoneData = getMicrophoneData; } public void setCustomAudioEffect(CustomAudioEffect customAudioEffect) { this.customAudioEffect = customAudioEffect; } /** * Create audio record */ public void createMicrophone() { createMicrophone(sampleRate, true, false, false); Log.i(TAG, "Microphone created, " + sampleRate + "hz, Stereo"); } /** * Create audio record with params and default audio source */ public void createMicrophone(int sampleRate, boolean isStereo, boolean echoCanceler, boolean noiseSuppressor) { createMicrophone(MediaRecorder.AudioSource.DEFAULT, sampleRate, isStereo, echoCanceler, noiseSuppressor); } /** * Create audio record with params and selected audio source * @param audioSource - the recording source. See {@link MediaRecorder.AudioSource} for the recording source definitions. */ public void createMicrophone(int audioSource, int sampleRate, boolean isStereo, boolean echoCanceler, boolean noiseSuppressor) { this.sampleRate = sampleRate; if (!isStereo) channel = AudioFormat.CHANNEL_IN_MONO; audioRecord = new AudioRecord(audioSource, sampleRate, channel, audioFormat, getPcmBufferSize()); audioPostProcessEffect = new AudioPostProcessEffect(audioRecord.getAudioSessionId()); if (echoCanceler) audioPostProcessEffect.enableEchoCanceler(); if (noiseSuppressor) audioPostProcessEffect.enableNoiseSuppressor(); String chl = (isStereo) ? "Stereo" : "Mono"; Log.i(TAG, "Microphone created, " + sampleRate + "hz, " + chl); created = true; } /** * Create audio record with params and AudioPlaybackCaptureConfig used for capturing internal audio * Notice that you should granted {@link android.Manifest.permission#RECORD_AUDIO} before calling this! * * @param config - AudioPlaybackCaptureConfiguration received from {@link android.media.projection.MediaProjection} * * @see AudioPlaybackCaptureConfiguration.Builder#Builder(MediaProjection) * @see "https://developer.android.com/guide/topics/media/playback-capture" * @see "https://medium.com/@debuggingisfun/android-10-audio-capture-77dd8e9070f9" */ public void createInternalMicrophone(AudioPlaybackCaptureConfiguration config, int sampleRate, boolean isStereo) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { this.sampleRate = sampleRate; if (!isStereo) channel = AudioFormat.CHANNEL_IN_MONO; audioRecord = new AudioRecord.Builder() .setAudioPlaybackCaptureConfig(config) .setAudioFormat(new AudioFormat.Builder() .setEncoding(audioFormat) .setSampleRate(sampleRate) .setChannelMask(channel) .build()) .setBufferSizeInBytes(getPcmBufferSize()) .build(); audioPostProcessEffect = new AudioPostProcessEffect(audioRecord.getAudioSessionId()); String chl = (isStereo) ? "Stereo" : "Mono"; Log.i(TAG, "Internal microphone created, " + sampleRate + "hz, " + chl); created = true; } else createMicrophone(sampleRate, isStereo, false, false); } /** * Start record and get data */ public synchronized void start() { init(); handlerThread = new HandlerThread(TAG); handlerThread.start(); Handler handler = new Handler(handlerThread.getLooper()); handler.post(new Runnable() { @Override public void run() { while (running) { Frame frame = read(); if (frame != null) { getMicrophoneData.inputPCMData(frame); } else { running = false; } } } }); } private void init() { if (audioRecord != null) { audioRecord.startRecording(); running = true; Log.i(TAG, "Microphone started"); } else { Log.e(TAG, "Error starting, microphone was stopped or not created, " + "use createMicrophone() before start()"); } } public void mute() { muted = true; } public void unMute() { muted = false; } public boolean isMuted() { return muted; } /** * @return Object with size and PCM buffer data */ private Frame read() { pcmBuffer.rewind(); int size = audioRecord.read(pcmBuffer, pcmBuffer.remaining()); if (size <= 0) { return null; } return new Frame(muted ? pcmBufferMuted : customAudioEffect.process(pcmBuffer.array()), muted ? 0 : pcmBuffer.arrayOffset(), size); } /** * Stop and release microphone */ public synchronized void stop() { running = false; created = false; if (handlerThread != null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { handlerThread.quitSafely(); } else { handlerThread.quit(); } } if (audioRecord != null) { audioRecord.setRecordPositionUpdateListener(null); audioRecord.stop(); audioRecord.release(); audioRecord = null; } if (audioPostProcessEffect != null) { audioPostProcessEffect.releaseEchoCanceler(); audioPostProcessEffect.releaseNoiseSuppressor(); } Log.i(TAG, "Microphone stopped"); } /** * Get PCM buffer size */ private int getPcmBufferSize() { int pcmBufSize = AudioRecord.getMinBufferSize(sampleRate, channel, AudioFormat.ENCODING_PCM_16BIT); return pcmBufSize * 5; } public int getMaxInputSize() { return BUFFER_SIZE; } public int getSampleRate() { return sampleRate; } public void setSampleRate(int sampleRate) { this.sampleRate = sampleRate; } public int getAudioFormat() { return audioFormat; } public int getChannel() { return channel; } public boolean isRunning() { return running; } public boolean isCreated() { return created; } }