| // 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() override; | |
| bool Open() override; | |
| bool Close() override; | |
| int Read(void* out_audio_data, int audio_data_size) 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); | |
| } |