https://github.com/nieldeokar/WhatsappAudioRecorder/blob/master/app/src/main/java/com/nieldeokar/whatsappaudiorecorder/recorder/AudioRecordThread.javaに基づく
音声認識の作業中に.m4aファイルに録音を保存するための私のソリューション:
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.media.MediaRecorder;
import android.os.Handler;
import android.os.Looper;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.HashSet;
import timber.log.Timber;
public class SpeechRecognizer {
private static final int CHANNELS = 1;
private static final int BIT_RATE = 32000;
private static final int SAMPLE_RATE = 44100;
private static final int SAMPLE_RATE_INDEX = 4;
protected static final String TAG = SpeechRecognizer.class.getSimpleName();
public int bufferSize;
public final Collection<RecognitionListener> listeners = new HashSet();
public final Handler mainHandler = new Handler(Looper.getMainLooper());
public final Recognizer recognizer;
private Thread recognizerThread;
public final AudioRecord recorder;
private SoundAmplitudeCallback soundAmplitudeCallback;
private File recordFile = null;
private boolean isRecordingToFileEnabled = false;
private boolean isRecordingToFilePrepared = false;
private boolean isContinueRecordingToFile = false;
public interface SoundAmplitudeCallback {
void onAmplitude(int amplitude);
}
public void setSoundAmplitudeCallback(SoundAmplitudeCallback callback) {
soundAmplitudeCallback = callback;
}
public SpeechRecognizer(Mabcd model) throws IOException {
this.recognizer = new Recognizer(model, SAMPLE_RATE);
this.bufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
this.recorder = createAudioRecorder(this.bufferSize);
if (this.recorder.getState() == 0) {
this.recorder.release();
throw new IOException("Failed to initialize recorder. Microphone might be already in use.");
}
}
public void addListener(RecognitionListener listener) {
synchronized (this.listeners) {
this.listeners.add(listener);
}
}
public void removeListener(RecognitionListener listener) {
synchronized (this.listeners) {
this.listeners.remove(listener);
}
}
public boolean startListening() {
if (this.recognizerThread != null) {
return false;
}
this.recognizerThread = new RecognizerThread(this);
this.recognizerThread.start();
return true;
}
public boolean startListening(int timeout) {
if (this.recognizerThread != null) {
return false;
}
this.recognizerThread = new RecognizerThread(timeout);
this.recognizerThread.start();
return true;
}
private boolean stopRecognizerThread() {
if (this.recognizerThread == null) {
return false;
}
try {
this.recognizerThread.interrupt();
this.recognizerThread.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
this.recognizerThread = null;
return true;
}
public void startRecordToFile(File fileRecord) {
this.recordFile = fileRecord;
isRecordingToFileEnabled = true;
}
public void resumeRecordToFile(File fileRecord) {
this.recordFile = fileRecord;
isContinueRecordingToFile = true;
isRecordingToFileEnabled = true;
isRecordingToFilePrepared = false;
}
public void stopRecordToFile() {
isRecordingToFileEnabled = false;
isRecordingToFilePrepared = false;
isContinueRecordingToFile = false;
}
public boolean stop() {
boolean result = stopRecognizerThread();
if (result) {
this.mainHandler.post(new ResultEvent(this.recognizer.Rabcd(), true));
}
return result;
}
public boolean cancel() {
boolean result = stopRecognizerThread();
this.recognizer.Rabcd();
return result;
}
public void shutdown() {
this.recorder.release();
}
private final class RecognizerThread extends Thread {
private static final int NO_TIMEOUT = -1;
private int remainingSamples;
private int timeoutSamples;
VoiceRecorder voiceRecorder = null;
public RecognizerThread(int timeout) {
if (timeout != NO_TIMEOUT) {
this.timeoutSamples = (SpeechRecognizer.SAMPLE_RATE * timeout) / 1000;
} else {
this.timeoutSamples = NO_TIMEOUT;
}
this.remainingSamples = this.timeoutSamples;
}
public RecognizerThread(SpeechRecognizer speechRecognizer) {
this(NO_TIMEOUT);
}
public void run() {
voiceRecorder = new VoiceRecorder();
SpeechRecognizer.this.recorder.startRecording();
if (SpeechRecognizer.this.recorder.getRecordingState() == AudioRecord.RECORDSTATE_STOPPED) {
SpeechRecognizer.this.recorder.stop();
SpeechRecognizer.this.mainHandler.post(new OnErrorEvent(new IOException("Failed to start recording. Microphone might be already in use.")));
return;
}
byte[] buffer = new byte[SpeechRecognizer.this.bufferSize];
while (!interrupted() && (this.timeoutSamples == NO_TIMEOUT || this.remainingSamples > 0)) {
int nread = SpeechRecognizer.this.recorder.read(buffer, 0, buffer.length);
if (soundAmplitudeCallback != null) {
int max = 0;
for (short s : buffer) {
if (Math.abs(s) > max) {
max = Math.abs(s);
}
}
soundAmplitudeCallback.onAmplitude(max);
}
if (nread < 0) {
throw new RuntimeException("error reading audio buffer");
}
voiceRecorder.recording(nread, buffer);
if (SpeechRecognizer.this.recognizer.Aabcd(buffer, nread)) {
SpeechRecognizer.this.mainHandler.post(new ResultEvent(SpeechRecognizer.this.recognizer.Rabcd(), true));
} else {
SpeechRecognizer.this.mainHandler.post(new ResultEvent(SpeechRecognizer.this.recognizer.Pabcd(), false));
}
if (this.timeoutSamples != NO_TIMEOUT) {
this.remainingSamples -= nread;
}
}
voiceRecorder.shutdown();
SpeechRecognizer.this.recorder.stop();
SpeechRecognizer.this.mainHandler.removeCallbacksAndMessages((Object) null);
if (this.timeoutSamples != NO_TIMEOUT && this.remainingSamples <= 0) {
SpeechRecognizer.this.mainHandler.post(new TimeoutEvent());
}
}
}
/*
* Voice Recorder to file
* */
private class VoiceRecorder{
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
FileOutputStream fileOutputStream = null;
MediaCodec mediaCodec = null;
void recording(int nread, byte[] buffer){
/*step 1 prepare file*/
if (isRecordingToFileEnabled && !isRecordingToFilePrepared) {
//if we continue recording not create new file
if (recordFile == null) {
throw new IllegalArgumentException("Record file is null");
}
try {
fileOutputStream = new FileOutputStream(recordFile, isContinueRecordingToFile);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
if (mediaCodec == null){
try {
mediaCodec = createMediaCodec(bufferSize);
mediaCodec.start();
Timber.d("mediaCodec.start()");
} catch (IOException e) {
e.printStackTrace();
}
}
isRecordingToFilePrepared = true;
}
/*prepare file*/
/*step 2 recording*/
if (isRecordingToFileEnabled && isRecordingToFilePrepared) {
try {
if (fileOutputStream != null){
boolean success = handleCodecInput(nread, buffer, mediaCodec, Thread.currentThread().isAlive());
if (success)
handleCodecOutput(mediaCodec, bufferInfo, fileOutputStream);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/*recording*/
/*step 3 finish recording and save to file*/
if (!isRecordingToFileEnabled && fileOutputStream != null) {
try {
VoiceRecorder.this.shutdown();
fileOutputStream.flush();
fileOutputStream.close();
fileOutputStream = null;
Timber.d("Finishing file");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/*finish recording and save to file*/
}
void shutdown(){
if (mediaCodec != null){
mediaCodec.stop();
mediaCodec.release();
mediaCodec = null;
}
}
}
private abstract class RecognitionEvent implements Runnable {
public abstract void execute(RecognitionListener recognitionListener);
private RecognitionEvent() {
}
public void run() {
for (RecognitionListener listener : (RecognitionListener[]) SpeechRecognizer.this.listeners.toArray(new RecognitionListener[0])) {
execute(listener);
}
}
}
private class ResultEvent extends RecognitionEvent {
private final boolean finalResult;
protected final String hypothesis;
ResultEvent(String hypothesis2, boolean finalResult2) {
super();
this.hypothesis = hypothesis2;
this.finalResult = finalResult2;
}
public void execute(RecognitionListener listener) {
if (this.finalResult) {
listener.onResult(this.hypothesis);
} else {
listener.onPartialResult(this.hypothesis);
}
}
}
private class OnErrorEvent extends RecognitionEvent {
private final Exception exception;
OnErrorEvent(Exception exception2) {
super();
this.exception = exception2;
}
public void execute(RecognitionListener listener) {
listener.onError(this.exception);
}
}
private class TimeoutEvent extends RecognitionEvent {
private TimeoutEvent() {
super();
}
public void execute(RecognitionListener listener) {
listener.onTimeout();
}
}
private AudioRecord createAudioRecorder(int bufferSize) {
AudioRecord recorder = new AudioRecord(MediaRecorder.AudioSource.VOICE_RECOGNITION, SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize * 10);
if (android.media.audiofx.NoiseSuppressor.isAvailable()) {
android.media.audiofx.NoiseSuppressor noiseSuppressor = android.media.audiofx.NoiseSuppressor
.create(recorder.getAudioSessionId());
if (noiseSuppressor != null) {
noiseSuppressor.setEnabled(true);
}
}
if (android.media.audiofx.AutomaticGainControl.isAvailable()) {
android.media.audiofx.AutomaticGainControl automaticGainControl = android.media.audiofx.AutomaticGainControl
.create(recorder.getAudioSessionId());
if (automaticGainControl != null) {
automaticGainControl.setEnabled(true);
}
}
return recorder;
}
private MediaCodec createMediaCodec(int bufferSize) throws IOException {
MediaCodec mediaCodec = MediaCodec.createEncoderByType("audio/mp4a-latm");
MediaFormat mediaFormat = new MediaFormat();
mediaFormat.setString(MediaFormat.KEY_MIME, "audio/mp4a-latm");
mediaFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, SAMPLE_RATE);
mediaFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, CHANNELS);
mediaFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, bufferSize);
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
mediaFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
try {
mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
} catch (Exception e) {
Timber.tag(TAG).w(e);
mediaCodec.release();
throw new IOException(e);
}
return mediaCodec;
}
private boolean handleCodecInput(int length,
byte[] buffer,
MediaCodec mediaCodec,
boolean running) {
if (length == AudioRecord.ERROR_BAD_VALUE ||
length == AudioRecord.ERROR_INVALID_OPERATION ||
length != bufferSize) {
if (length != bufferSize) {
Timber.tag(TAG).d( "length != BufferSize calling onRecordFailed");
// if (onRecorderFailedListener != null) {
// Log.d(TAG, "length != BufferSize calling onRecordFailed");
// onRecorderFailedListener.onRecorderFailed();
// }
return false;
}
}
int codecInputBufferIndex = mediaCodec.dequeueInputBuffer(10 * 1000);
if (codecInputBufferIndex >= 0) {
ByteBuffer codecBuffer = mediaCodec.getInputBuffer(codecInputBufferIndex);
codecBuffer.clear();
codecBuffer.put(buffer);
mediaCodec.queueInputBuffer(codecInputBufferIndex, 0, length, 0, running ? 0 : MediaCodec.BUFFER_FLAG_END_OF_STREAM);
}
return true;
}
private void handleCodecOutput(MediaCodec mediaCodec,
MediaCodec.BufferInfo bufferInfo,
OutputStream outputStream) throws IOException {
int codecOutputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
while (codecOutputBufferIndex != MediaCodec.INFO_TRY_AGAIN_LATER) {
if (codecOutputBufferIndex >= 0) {
ByteBuffer encoderOutputBuffer = mediaCodec.getOutputBuffer(codecOutputBufferIndex);
encoderOutputBuffer.position(bufferInfo.offset);
encoderOutputBuffer.limit(bufferInfo.offset + bufferInfo.size);
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != MediaCodec.BUFFER_FLAG_CODEC_CONFIG) {
byte[] header = createAdtsHeader(bufferInfo.size - bufferInfo.offset);
outputStream.write(header);
byte[] data = new byte[encoderOutputBuffer.remaining()];
encoderOutputBuffer.get(data);
outputStream.write(data);
}
encoderOutputBuffer.clear();
mediaCodec.releaseOutputBuffer(codecOutputBufferIndex, false);
}
codecOutputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
}
}
private byte[] createAdtsHeader(int length) {
int frameLength = length + 7;
byte[] adtsHeader = new byte[7];
adtsHeader[0] = (byte) 0xFF; // Sync Word
adtsHeader[1] = (byte) 0xF1; // MPEG-4, Layer (0), No CRC
adtsHeader[2] = (byte) ((MediaCodecInfo.CodecProfileLevel.AACObjectLC - 1) << 6);
adtsHeader[2] |= (((byte) SAMPLE_RATE_INDEX) << 2);
adtsHeader[2] |= (((byte) CHANNELS) >> 2);
adtsHeader[3] = (byte) (((CHANNELS & 3) << 6) | ((frameLength >> 11) & 0x03));
adtsHeader[4] = (byte) ((frameLength >> 3) & 0xFF);
adtsHeader[5] = (byte) (((frameLength & 0x07) << 5) | 0x1f);
adtsHeader[6] = (byte) 0xFC;
return adtsHeader;
}
}
java android 音声 認識 録音 ファイル pcm m4a