blob: 06943f7eac472dea4f23f2cbcaeff3aa4e19a91b [file] [log] [blame]
/*
* Copyright 2018 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
package org.appspot.apprtc;
import android.media.AudioFormat;
import android.os.Environment;
import android.support.annotation.Nullable;
import android.util.Log;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.concurrent.ExecutorService;
import org.webrtc.audio.JavaAudioDeviceModule;
import org.webrtc.audio.JavaAudioDeviceModule.SamplesReadyCallback;
/**
* Implements the AudioRecordSamplesReadyCallback interface and writes
* recorded raw audio samples to an output file.
*/
public class RecordedAudioToFileController implements SamplesReadyCallback {
private static final String TAG = "RecordedAudioToFile";
private static final long MAX_FILE_SIZE_IN_BYTES = 58348800L;
private final Object lock = new Object();
private final ExecutorService executor;
@Nullable private OutputStream rawAudioFileOutputStream;
private boolean isRunning;
private long fileSizeInBytes;
public RecordedAudioToFileController(ExecutorService executor) {
Log.d(TAG, "ctor");
this.executor = executor;
}
/**
* Should be called on the same executor thread as the one provided at
* construction.
*/
public boolean start() {
Log.d(TAG, "start");
if (!isExternalStorageWritable()) {
Log.e(TAG, "Writing to external media is not possible");
return false;
}
synchronized (lock) {
isRunning = true;
}
return true;
}
/**
* Should be called on the same executor thread as the one provided at
* construction.
*/
public void stop() {
Log.d(TAG, "stop");
synchronized (lock) {
isRunning = false;
if (rawAudioFileOutputStream != null) {
try {
rawAudioFileOutputStream.close();
} catch (IOException e) {
Log.e(TAG, "Failed to close file with saved input audio: " + e);
}
rawAudioFileOutputStream = null;
}
fileSizeInBytes = 0;
}
}
// Checks if external storage is available for read and write.
private boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
return true;
}
return false;
}
// Utilizes audio parameters to create a file name which contains sufficient
// information so that the file can be played using an external file player.
// Example: /sdcard/recorded_audio_16bits_48000Hz_mono.pcm.
private void openRawAudioOutputFile(int sampleRate, int channelCount) {
final String fileName = Environment.getExternalStorageDirectory().getPath() + File.separator
+ "recorded_audio_16bits_" + String.valueOf(sampleRate) + "Hz"
+ ((channelCount == 1) ? "_mono" : "_stereo") + ".pcm";
final File outputFile = new File(fileName);
try {
rawAudioFileOutputStream = new FileOutputStream(outputFile);
} catch (FileNotFoundException e) {
Log.e(TAG, "Failed to open audio output file: " + e.getMessage());
}
Log.d(TAG, "Opened file for recording: " + fileName);
}
// Called when new audio samples are ready.
@Override
public void onWebRtcAudioRecordSamplesReady(JavaAudioDeviceModule.AudioSamples samples) {
// The native audio layer on Android should use 16-bit PCM format.
if (samples.getAudioFormat() != AudioFormat.ENCODING_PCM_16BIT) {
Log.e(TAG, "Invalid audio format");
return;
}
synchronized (lock) {
// Abort early if stop() has been called.
if (!isRunning) {
return;
}
// Open a new file for the first callback only since it allows us to add audio parameters to
// the file name.
if (rawAudioFileOutputStream == null) {
openRawAudioOutputFile(samples.getSampleRate(), samples.getChannelCount());
fileSizeInBytes = 0;
}
}
// Append the recorded 16-bit audio samples to the open output file.
executor.execute(() -> {
if (rawAudioFileOutputStream != null) {
try {
// Set a limit on max file size. 58348800 bytes corresponds to
// approximately 10 minutes of recording in mono at 48kHz.
if (fileSizeInBytes < MAX_FILE_SIZE_IN_BYTES) {
// Writes samples.getData().length bytes to output stream.
rawAudioFileOutputStream.write(samples.getData());
fileSizeInBytes += samples.getData().length;
}
} catch (IOException e) {
Log.e(TAG, "Failed to write audio to file: " + e.getMessage());
}
}
});
}
}