/*
|
* 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 org.webrtc.audio.JavaAudioDeviceModule;
|
import org.webrtc.audio.JavaAudioDeviceModule.SamplesReadyCallback;
|
|
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;
|
|
/**
|
* 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());
|
}
|
}
|
});
|
}
|
}
|