blob: f2100fe0f639cafd6d37175a1d1cfb8dd8bdc4b2 [file] [log] [blame]
/*
* Copyright 2019 The Android Open Source Project
*
* 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.android.tv.settings.system.development.audio;
import android.annotation.Nullable;
import android.content.Context;
import android.media.AudioFormat;
import android.media.AudioTrack;
import android.os.Handler;
import android.util.Log;
import java.nio.ShortBuffer;
/** Manages audio recording, audio metrics, and audio playback for debugging purposes. */
public class AudioDebug {
private static final String TAG = "AudioDebug";
public static final int CHANNELS = 1;
public static final int SAMPLE_RATE = 16000;
public static final int BITRATE = 16;
public static final int ENCODING = AudioFormat.ENCODING_PCM_16BIT;
private final Context mContext;
private final AudioRecordedCallback mAudioRecordedCallback;
private final AudioMetrics mMetrics;
@Nullable
private AudioReader mAudioReader;
@Nullable
private ShortBuffer mAudioBuffer;
@Nullable
private AudioTrack mAudioTrack;
/** Interface for receiving a notification when audio recording finishes. */
public interface AudioRecordedCallback {
/** Callback for receiving a notification when audio recording finishes. */
void onAudioRecorded(boolean successful);
}
/**
* @param context The parent context
* @param audioRecordedCallback Callback for notification on audio recording completion
* @param metricsCallback Callback for metrics updates
*/
public AudioDebug(Context context, AudioRecordedCallback audioRecordedCallback,
AudioMetrics.UpdateMetricsCallback metricsCallback) {
this.mContext = context;
this.mAudioRecordedCallback = audioRecordedCallback;
mMetrics = new AudioMetrics(metricsCallback);
}
/** Starts recording audio. */
public void startRecording() throws AudioReaderException {
if (mAudioReader != null) {
mAudioReader.stop();
}
mMetrics.start();
mAudioReader = new AudioReader(mMetrics);
mAudioReader.addListener((ShortBuffer buffer) -> onAudioRecorded(buffer));
Thread audioReaderThread = new Thread(mAudioReader);
audioReaderThread.setPriority(10);
audioReaderThread.start();
}
/**
* Stores a buffer containing recorded audio in an AudioTrack. Overwrites any previously
* recorded audio.
*
* @param audioBuffer The buffer containing the recorded audio.
*/
private void onAudioRecorded(ShortBuffer audioBuffer) {
if (audioBuffer.position() == 0) {
Log.e(TAG, "Empty buffer recorded");
return;
}
this.mAudioBuffer = audioBuffer;
int numShorts = audioBuffer.position();
int numBytes = numShorts * 2;
Handler mainHandler = new Handler(mContext.getMainLooper());
try {
mAudioTrack =
new AudioTrack.Builder()
.setAudioFormat(
new AudioFormat.Builder()
.setSampleRate(SAMPLE_RATE)
.setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
.setEncoding(ENCODING)
.build()
)
.setTransferMode(AudioTrack.MODE_STATIC)
.setBufferSizeInBytes(numBytes)
.build();
} catch (UnsupportedOperationException | IllegalArgumentException e) {
Log.e(TAG, "Failed to create AudioTrack", e);
mainHandler.post(() -> mAudioRecordedCallback.onAudioRecorded(false));
return;
}
Log.i(TAG, String.format("AudioTrack state: %d", mAudioTrack.getState()));
int writeStatus = mAudioTrack.write(audioBuffer.array(), 0, numShorts,
AudioTrack.WRITE_BLOCKING);
if (writeStatus > 0) {
Log.i(TAG, String.format("Wrote %d bytes to an AudioTrack", numBytes));
mainHandler.post(() -> mAudioRecordedCallback.onAudioRecorded(true));
} else if (writeStatus == 0) {
Log.e(TAG, "Received empty audio buffer");
} else {
Log.e(TAG, String.format("Error calling AudioTrack.write(): %d", writeStatus));
mainHandler.post(() -> mAudioRecordedCallback.onAudioRecorded(false));
}
}
/** Stops recording audio. */
public void stopRecording() {
if (mAudioReader != null) {
mAudioReader.stop();
mAudioReader = null;
}
}
/** Stops recording audio, and discards the recorded audio. */
public void cancelRecording() {
if (mAudioReader != null) {
mAudioReader.cancel();
mAudioReader = null;
}
}
/** Plays the recorded audio. */
public void playAudio() {
if (mAudioTrack == null) {
Log.e(TAG, "No audio track recorded");
} else {
mAudioTrack.stop();
mAudioTrack.reloadStaticData();
mAudioTrack.setPlaybackHeadPosition(0);
mAudioTrack.setVolume(1.0f);
mAudioTrack.play();
}
}
/** Writes the recorded audio to a WAV file. */
public void writeAudioToFile() {
WavWriter.writeToFile(mContext.getExternalFilesDir(null), mAudioBuffer);
}
}