peertube-live-streaming/encoder/src/main/java/com/pedro/encoder/input/audio/MicrophoneManager.java

272 lines
8.6 KiB
Java

/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.audio;
import android.annotation.SuppressLint;
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.
*/
@SuppressLint("MissingPermission")
public class MicrophoneManager {
private final String TAG = "MicrophoneManager";
private int BUFFER_SIZE = 0;
protected AudioRecord audioRecord;
private final GetMicrophoneData getMicrophoneData;
protected byte[] pcmBuffer = new byte[BUFFER_SIZE];
protected byte[] pcmBufferMuted = new byte[BUFFER_SIZE];
protected boolean running = false;
private boolean created = false;
//default parameters for microphone
private int sampleRate = 32000; //hz
private final int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
private int channel = AudioFormat.CHANNEL_IN_STEREO;
protected boolean muted = false;
private AudioPostProcessEffect audioPostProcessEffect;
protected HandlerThread handlerThread;
protected CustomAudioEffect customAudioEffect = new NoAudioEffect();
public MicrophoneManager(GetMicrophoneData getMicrophoneData) {
this.getMicrophoneData = getMicrophoneData;
getPcmBufferSize();
}
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 boolean createMicrophone(int sampleRate, boolean isStereo, boolean echoCanceler,
boolean noiseSuppressor) {
return 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 boolean createMicrophone(int audioSource, int sampleRate, boolean isStereo,
boolean echoCanceler, boolean noiseSuppressor) {
try {
this.sampleRate = sampleRate;
channel = isStereo ? AudioFormat.CHANNEL_IN_STEREO : AudioFormat.CHANNEL_IN_MONO;
audioRecord = new AudioRecord(audioSource, sampleRate, channel, audioFormat, getMaxInputSize() * 5);
audioPostProcessEffect = new AudioPostProcessEffect(audioRecord.getAudioSessionId());
if (echoCanceler) audioPostProcessEffect.enableEchoCanceler();
if (noiseSuppressor) audioPostProcessEffect.enableNoiseSuppressor();
String chl = (isStereo) ? "Stereo" : "Mono";
if (audioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
throw new IllegalArgumentException("Some parameters specified is not valid");
}
Log.i(TAG, "Microphone created, " + sampleRate + "hz, " + chl);
created = true;
} catch (IllegalArgumentException e) {
Log.e(TAG, "create microphone error", e);
}
return created;
}
/**
* 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 boolean createInternalMicrophone(AudioPlaybackCaptureConfiguration config, int sampleRate,
boolean isStereo, boolean echoCanceler, boolean noiseSuppressor) {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
this.sampleRate = sampleRate;
channel = isStereo ? AudioFormat.CHANNEL_IN_STEREO : AudioFormat.CHANNEL_IN_MONO;
audioRecord = new AudioRecord.Builder().setAudioPlaybackCaptureConfig(config)
.setAudioFormat(new AudioFormat.Builder().setEncoding(audioFormat)
.setSampleRate(sampleRate)
.setChannelMask(channel)
.build())
.setBufferSizeInBytes(getMaxInputSize() * 5)
.build();
audioPostProcessEffect = new AudioPostProcessEffect(audioRecord.getAudioSessionId());
if (echoCanceler) audioPostProcessEffect.enableEchoCanceler();
if (noiseSuppressor) audioPostProcessEffect.enableNoiseSuppressor();
String chl = (isStereo) ? "Stereo" : "Mono";
if (audioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
throw new IllegalArgumentException("Some parameters specified is not valid");
}
Log.i(TAG, "Internal microphone created, " + sampleRate + "hz, " + chl);
created = true;
} else {
return createMicrophone(sampleRate, isStereo, echoCanceler, noiseSuppressor);
}
} catch (IllegalArgumentException e) {
Log.e(TAG, "create microphone error", e);
}
return created;
}
public boolean createInternalMicrophone(AudioPlaybackCaptureConfiguration config, int sampleRate,
boolean isStereo) {
return createInternalMicrophone(config, 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);
}
}
}
});
}
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
*/
protected Frame read() {
int size = audioRecord.read(pcmBuffer, 0, pcmBuffer.length);
if (size < 0) return null;
return new Frame(muted ? pcmBufferMuted : customAudioEffect.process(pcmBuffer), 0, 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.release();
}
Log.i(TAG, "Microphone stopped");
}
/**
* Get PCM buffer size
*/
private void getPcmBufferSize() {
BUFFER_SIZE = AudioRecord.getMinBufferSize(sampleRate, channel, audioFormat);
pcmBuffer = new byte[BUFFER_SIZE];
pcmBufferMuted = new byte[BUFFER_SIZE];
}
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;
}
}