blob: 71ac6f40491360717acd2d6645941e71cd5346ec [file] [log] [blame]
// Copyright 2018 The Cobalt Authors. All Rights Reserved.
//
// 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.
#include "starboard/shared/starboard/microphone/microphone_internal.h"
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
#include <algorithm>
#include <queue>
#include "starboard/android/shared/jni_env_ext.h"
#include "starboard/common/log.h"
#include "starboard/common/scoped_ptr.h"
#include "starboard/memory.h"
#include "starboard/shared/starboard/thread_checker.h"
using starboard::android::shared::JniEnvExt;
namespace starboard {
namespace android {
namespace shared {
namespace {
const int kSampleRateInHz = 16000;
const int kSampleRateInMillihertz = kSampleRateInHz * 1000;
const int kNumOfOpenSLESBuffers = 2;
const int kSamplesPerBuffer = 128;
const int kBufferSizeInBytes = kSamplesPerBuffer * sizeof(int16_t);
bool CheckReturnValue(SLresult result) {
return result == SL_RESULT_SUCCESS;
}
} // namespace
class SbMicrophoneImpl : public SbMicrophonePrivate {
public:
SbMicrophoneImpl();
~SbMicrophoneImpl() SB_OVERRIDE;
bool Open() SB_OVERRIDE;
bool Close() SB_OVERRIDE;
int Read(void* out_audio_data, int audio_data_size) SB_OVERRIDE;
void SetPermission(bool is_granted);
static bool IsMicrophoneDisconnected();
static bool IsMicrophoneMute();
private:
enum State { kWaitPermission, kPermissionGranted, kOpened, kClosed };
static void SwapAndPublishBuffer(SLAndroidSimpleBufferQueueItf buffer_object,
void* context);
void SwapAndPublishBuffer();
bool CreateAudioRecorder();
void DeleteAudioRecorder();
void ClearBuffer();
bool RequestAudioPermission();
bool StartRecording();
bool StopRecording();
SLObjectItf engine_object_;
SLEngineItf engine_;
SLObjectItf recorder_object_;
SLRecordItf recorder_;
SLAndroidSimpleBufferQueueItf buffer_object_;
SLAndroidConfigurationItf config_object_;
// Keeps track of the microphone's current state.
State state_;
// Audio data that has been delivered to the buffer queue.
std::queue<int16_t*> delivered_queue_;
// Audio data that is ready to be read.
std::queue<int16_t*> ready_queue_;
};
SbMicrophoneImpl::SbMicrophoneImpl()
: engine_object_(nullptr),
engine_(nullptr),
recorder_object_(nullptr),
recorder_(nullptr),
buffer_object_(nullptr),
config_object_(nullptr),
state_(kClosed) {}
SbMicrophoneImpl::~SbMicrophoneImpl() {
Close();
}
bool SbMicrophoneImpl::RequestAudioPermission() {
JniEnvExt* env = JniEnvExt::Get();
jobject j_audio_permission_requester =
static_cast<jobject>(env->CallStarboardObjectMethodOrAbort(
"getAudioPermissionRequester",
"()Ldev/cobalt/coat/AudioPermissionRequester;"));
jboolean j_permission = env->CallBooleanMethodOrAbort(
j_audio_permission_requester, "requestRecordAudioPermission", "(J)Z",
reinterpret_cast<intptr_t>(this));
return j_permission;
}
// static
bool SbMicrophoneImpl::IsMicrophoneDisconnected() {
JniEnvExt* env = JniEnvExt::Get();
jboolean j_microphone =
env->CallStarboardBooleanMethodOrAbort("isMicrophoneDisconnected", "()Z");
return j_microphone;
}
// static
bool SbMicrophoneImpl::IsMicrophoneMute() {
JniEnvExt* env = JniEnvExt::Get();
jboolean j_microphone =
env->CallStarboardBooleanMethodOrAbort("isMicrophoneMute", "()Z");
return j_microphone;
}
bool SbMicrophoneImpl::Open() {
if (state_ == kOpened) {
// The microphone has already been opened; clear the unread buffer. See
// starboard/microphone.h for more info.
ClearBuffer();
return true;
}
if (IsMicrophoneDisconnected()) {
SB_DLOG(WARNING) << "No microphone connected.";
return false;
} else if (!RequestAudioPermission()) {
state_ = kWaitPermission;
SB_DLOG(INFO) << "Waiting for audio permission.";
// The permission is not set; this causes the MicrophoneManager to call
// read() repeatedly and wait for the user's response.
return true;
} else if (!StartRecording()) {
SB_DLOG(WARNING) << "Error starting recording.";
return false;
}
// Successfully opened the microphone and started recording.
state_ = kOpened;
return true;
}
bool SbMicrophoneImpl::StartRecording() {
if (!CreateAudioRecorder()) {
SB_DLOG(WARNING) << "Create audio recorder failed.";
DeleteAudioRecorder();
return false;
}
// Enqueues kNumOfOpenSLESBuffers zero buffers to start.
// Adds buffers to the queue before changing state to ensure that recording
// starts as soon as the state is modified.
for (int i = 0; i < kNumOfOpenSLESBuffers; ++i) {
int16_t* buffer = new int16_t[kSamplesPerBuffer];
SbMemorySet(buffer, 0, kBufferSizeInBytes);
delivered_queue_.push(buffer);
SLresult result =
(*buffer_object_)->Enqueue(buffer_object_, buffer, kBufferSizeInBytes);
if (!CheckReturnValue(result)) {
SB_DLOG(WARNING) << "Error adding buffers to the queue.";
return false;
}
}
// Start the recording by setting the state to |SL_RECORDSTATE_RECORDING|.
// When the object is in the SL_RECORDSTATE_RECORDING state, adding buffers
// will implicitly start the recording process.
SLresult result =
(*recorder_)->SetRecordState(recorder_, SL_RECORDSTATE_RECORDING);
if (!CheckReturnValue(result)) {
return false;
}
return true;
}
bool SbMicrophoneImpl::Close() {
if (state_ == kClosed) {
// The microphone has already been closed.
return true;
}
if (state_ == kOpened && !StopRecording()) {
SB_DLOG(WARNING) << "Error closing the microphone.";
return false;
}
// Successfully closed the microphone and stopped recording.
state_ = kClosed;
return true;
}
bool SbMicrophoneImpl::StopRecording() {
// Stop recording by setting the record state to |SL_RECORDSTATE_STOPPED|.
SLresult result =
(*recorder_)->SetRecordState(recorder_, SL_RECORDSTATE_STOPPED);
if (!CheckReturnValue(result)) {
return false;
}
ClearBuffer();
DeleteAudioRecorder();
return true;
}
int SbMicrophoneImpl::Read(void* out_audio_data, int audio_data_size) {
if (state_ == kClosed || IsMicrophoneMute()) {
// No audio data is read from a stopped or muted microphone; return an
// error.
return -1;
}
if (!out_audio_data || audio_data_size == 0 || state_ == kWaitPermission) {
// No data to be read.
return 0;
}
if (state_ == kPermissionGranted) {
if (StartRecording()) {
state_ = kOpened;
} else {
// Could not start recording; return an error.
state_ = kClosed;
return -1;
}
}
int read_bytes = 0;
scoped_ptr<int16_t> buffer;
// Go through the ready queue, reading and sending audio data.
while (!ready_queue_.empty() &&
audio_data_size - read_bytes >= kBufferSizeInBytes) {
buffer.reset(ready_queue_.front());
SbMemoryCopy(static_cast<uint8_t*>(out_audio_data) + read_bytes,
buffer.get(), kBufferSizeInBytes);
ready_queue_.pop();
read_bytes += kBufferSizeInBytes;
}
buffer.reset();
return read_bytes;
}
void SbMicrophoneImpl::SetPermission(bool is_granted) {
state_ = is_granted ? kPermissionGranted : kClosed;
}
// static
void SbMicrophoneImpl::SwapAndPublishBuffer(
SLAndroidSimpleBufferQueueItf buffer_object,
void* context) {
SbMicrophoneImpl* recorder = static_cast<SbMicrophoneImpl*>(context);
recorder->SwapAndPublishBuffer();
}
void SbMicrophoneImpl::SwapAndPublishBuffer() {
if (!delivered_queue_.empty()) {
// The front item in the delivered queue already has the buffered data, so
// move it from the delivered queue to the ready queue for future reads.
int16_t* buffer = delivered_queue_.front();
delivered_queue_.pop();
ready_queue_.push(buffer);
}
if (state_ == kOpened) {
int16_t* buffer = new int16_t[kSamplesPerBuffer];
SbMemorySet(buffer, 0, kBufferSizeInBytes);
delivered_queue_.push(buffer);
SLresult result =
(*buffer_object_)->Enqueue(buffer_object_, buffer, kBufferSizeInBytes);
CheckReturnValue(result);
}
}
bool SbMicrophoneImpl::CreateAudioRecorder() {
SLresult result;
// Initializes the SL engine object with specific options.
// OpenSL ES for Android is designed for multi-threaded applications and
// is thread-safe.
result = slCreateEngine(&engine_object_, 0, nullptr, 0, nullptr, nullptr);
if (!CheckReturnValue(result)) {
SB_DLOG(WARNING) << "Error creating the SL engine object.";
return false;
}
// Realize the SL engine object in synchronous mode.
result = (*engine_object_)
->Realize(engine_object_, /* async = */ SL_BOOLEAN_FALSE);
if (!CheckReturnValue(result)) {
SB_DLOG(WARNING) << "Error realizing the SL engine object.";
return false;
}
// Get the SL engine interface.
result =
(*engine_object_)->GetInterface(engine_object_, SL_IID_ENGINE, &engine_);
if (!CheckReturnValue(result)) {
SB_DLOG(WARNING) << "Error getting the SL engine interface.";
return false;
}
// Audio source configuration; the audio source is an I/O device data locator.
SLDataLocator_IODevice input_dev_locator = {
SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT,
SL_DEFAULTDEVICEID_AUDIOINPUT, nullptr};
SLDataSource audio_source = {&input_dev_locator, nullptr};
SLDataLocator_AndroidSimpleBufferQueue simple_buffer_queue = {
SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, kNumOfOpenSLESBuffers};
// Audio sink configuration; the audio sink is a simple buffer queue. PCM is
// the only data format allowed with buffer queues.
SLAndroidDataFormat_PCM_EX format = {
SL_ANDROID_DATAFORMAT_PCM_EX, 1 /* numChannels */,
kSampleRateInMillihertz, SL_PCMSAMPLEFORMAT_FIXED_16,
SL_PCMSAMPLEFORMAT_FIXED_16, SL_SPEAKER_FRONT_CENTER,
SL_BYTEORDER_LITTLEENDIAN, SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT};
SLDataSink audio_sink = {&simple_buffer_queue, &format};
const int kCount = 2;
const SLInterfaceID kInterfaceId[kCount] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
SL_IID_ANDROIDCONFIGURATION};
const SLboolean kInterfaceRequired[kCount] = {SL_BOOLEAN_TRUE,
SL_BOOLEAN_TRUE};
// Create the audio recorder.
result = (*engine_)->CreateAudioRecorder(engine_, &recorder_object_,
&audio_source, &audio_sink, kCount,
kInterfaceId, kInterfaceRequired);
if (!CheckReturnValue(result)) {
SB_DLOG(WARNING) << "Error creating the audio recorder.";
return false;
}
// Configure the audio recorder (before it is realized); get the configuration
// interface.
result = (*recorder_object_)
->GetInterface(recorder_object_, SL_IID_ANDROIDCONFIGURATION,
&config_object_);
if (!CheckReturnValue(result)) {
SB_DLOG(WARNING) << "Error getting the audio recorder interface.";
return false;
}
// Use the main microphone tuned for voice recognition.
const SLuint32 kPresetValue = SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION;
result =
(*config_object_)
->SetConfiguration(config_object_, SL_ANDROID_KEY_RECORDING_PRESET,
&kPresetValue, sizeof(SLuint32));
if (!CheckReturnValue(result)) {
SB_DLOG(WARNING) << "Error configuring the audio recorder.";
return false;
}
// Realize the recorder in synchronous mode.
result = (*recorder_object_)
->Realize(recorder_object_, /* async = */ SL_BOOLEAN_FALSE);
if (!CheckReturnValue(result)) {
SB_DLOG(WARNING) << "Error realizing the audio recorder. Double check that "
"the microphone is connected to the device.";
return false;
}
// Get the record interface (an implicit interface).
result = (*recorder_object_)
->GetInterface(recorder_object_, SL_IID_RECORD, &recorder_);
if (!CheckReturnValue(result)) {
SB_DLOG(WARNING) << "Error getting the audio recorder interface.";
return false;
}
// Get the buffer queue interface which was explicitly requested.
result = (*recorder_object_)
->GetInterface(recorder_object_, SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
&buffer_object_);
if (!CheckReturnValue(result)) {
SB_DLOG(WARNING) << "Error getting the buffer queue interface.";
return false;
}
// Setup to receive buffer queue event callbacks for when a buffer in the
// queue is completed.
result =
(*buffer_object_)
->RegisterCallback(buffer_object_,
&SbMicrophoneImpl::SwapAndPublishBuffer, this);
if (!CheckReturnValue(result)) {
SB_DLOG(WARNING) << "Error registering buffer queue callbacks.";
return false;
}
return true;
}
void SbMicrophoneImpl::DeleteAudioRecorder() {
if (recorder_object_) {
(*recorder_object_)->Destroy(recorder_object_);
}
config_object_ = nullptr;
buffer_object_ = nullptr;
recorder_ = nullptr;
recorder_object_ = nullptr;
// Destroy the engine object.
if (engine_object_) {
(*engine_object_)->Destroy(engine_object_);
}
engine_ = nullptr;
engine_object_ = nullptr;
}
void SbMicrophoneImpl::ClearBuffer() {
// Clear the buffer queue to get rid of old data.
if (buffer_object_) {
SLresult result = (*buffer_object_)->Clear(buffer_object_);
if (!CheckReturnValue(result)) {
SB_DLOG(WARNING) << "Error clearing the buffer.";
}
}
while (!delivered_queue_.empty()) {
delete[] delivered_queue_.front();
delivered_queue_.pop();
}
while (!ready_queue_.empty()) {
delete[] ready_queue_.front();
ready_queue_.pop();
}
}
} // namespace shared
} // namespace android
} // namespace starboard
int SbMicrophonePrivate::GetAvailableMicrophones(
SbMicrophoneInfo* out_info_array,
int info_array_size) {
// Note that there is no way of checking for a connected microphone/device
// before API 23, so GetAvailableMicrophones() will assume a microphone is
// connected and always return 1 on APIs < 23.
if (starboard::android::shared::SbMicrophoneImpl::
IsMicrophoneDisconnected()) {
SB_DLOG(WARNING) << "No microphone connected.";
return 0;
}
if (starboard::android::shared::SbMicrophoneImpl::IsMicrophoneMute()) {
SB_DLOG(WARNING) << "Microphone is muted.";
return 0;
}
if (out_info_array && info_array_size > 0) {
// Only support one microphone.
out_info_array[0].id = reinterpret_cast<SbMicrophoneId>(1);
out_info_array[0].type = kSbMicrophoneUnknown;
out_info_array[0].max_sample_rate_hz =
starboard::android::shared::kSampleRateInHz;
out_info_array[0].min_read_size =
starboard::android::shared::kSamplesPerBuffer;
}
return 1;
}
bool SbMicrophonePrivate::IsMicrophoneSampleRateSupported(
SbMicrophoneId id,
int sample_rate_in_hz) {
if (!SbMicrophoneIdIsValid(id)) {
return false;
}
return sample_rate_in_hz == starboard::android::shared::kSampleRateInHz;
}
namespace {
const int kUnusedBufferSize = 32 * 1024;
// Only a single microphone is supported.
SbMicrophone s_microphone = kSbMicrophoneInvalid;
} // namespace
SbMicrophone SbMicrophonePrivate::CreateMicrophone(SbMicrophoneId id,
int sample_rate_in_hz,
int buffer_size_bytes) {
if (!SbMicrophoneIdIsValid(id) ||
!IsMicrophoneSampleRateSupported(id, sample_rate_in_hz) ||
buffer_size_bytes > kUnusedBufferSize || buffer_size_bytes <= 0) {
return kSbMicrophoneInvalid;
}
if (s_microphone != kSbMicrophoneInvalid) {
return kSbMicrophoneInvalid;
}
s_microphone = new starboard::android::shared::SbMicrophoneImpl();
return s_microphone;
}
void SbMicrophonePrivate::DestroyMicrophone(SbMicrophone microphone) {
if (!SbMicrophoneIsValid(microphone)) {
return;
}
SB_DCHECK(s_microphone == microphone);
s_microphone->Close();
delete s_microphone;
s_microphone = kSbMicrophoneInvalid;
}
extern "C" SB_EXPORT_PLATFORM void
Java_dev_cobalt_coat_AudioPermissionRequester_nativeHandlePermission(
JNIEnv* env,
jobject unused_this,
jlong nativeSbMicrophoneImpl,
jboolean is_granted) {
starboard::android::shared::SbMicrophoneImpl* native =
reinterpret_cast<starboard::android::shared::SbMicrophoneImpl*>(
nativeSbMicrophoneImpl);
native->SetPermission(is_granted);
}