blob: bea9e5262da4f46cab9bbe148be991627e7782b2 [file] [log] [blame]
// Copyright 2017 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/speech_recognizer/speech_recognizer_internal.h"
#include <android/native_activity.h>
#include <jni.h>
#include <limits>
#include <vector>
#include "starboard/android/shared/jni_env_ext.h"
#include "starboard/android/shared/jni_utils.h"
#include "starboard/mutex.h"
#include "starboard/shared/starboard/thread_checker.h"
#include "starboard/string.h"
namespace starboard {
namespace android {
namespace shared {
namespace {
// Android speech recognizer error.
// Reference:
// https://developer.android.com/reference/android/speech/SpeechRecognizer.html
enum SpeechRecognizerErrorCode {
kErrorNetworkTimeout = 1,
kErrorNetwork = 2,
kErrorAudio = 3,
kErrorService = 4,
kErrorClient = 5,
kErrorSpeechTimeout = 6,
kErrorNoMatch = 7,
kErrorRecognizerBusy = 8,
kErrorInsufficientPermissions = 9,
};
} // namespace
class SbSpeechRecognizerImpl : public SbSpeechRecognizerPrivate {
public:
explicit SbSpeechRecognizerImpl(const SbSpeechRecognizerHandler* handler);
~SbSpeechRecognizerImpl();
// Called from constructor's thread.
bool Start(const SbSpeechConfiguration* configuration) override;
void Stop() override;
void Cancel() override;
// Called from Android's UI thread.
void OnSpeechDetected(bool detected);
void OnError(int error);
void OnResults(const std::vector<std::string>& results,
const std::vector<float>& confidences,
bool is_final);
private:
SbSpeechRecognizerHandler handler_;
jobject j_voice_recognizer_;
bool is_started_;
::starboard::shared::starboard::ThreadChecker thread_checker_;
};
SbSpeechRecognizerImpl::SbSpeechRecognizerImpl(
const SbSpeechRecognizerHandler* handler)
: handler_(*handler), is_started_(false) {
JniEnvExt* env = JniEnvExt::Get();
jobject local_ref = env->CallStarboardObjectMethodOrAbort(
"getVoiceRecognizer", "()Ldev/cobalt/coat/VoiceRecognizer;");
j_voice_recognizer_ = env->ConvertLocalRefToGlobalRef(local_ref);
}
SbSpeechRecognizerImpl::~SbSpeechRecognizerImpl() {
SB_DCHECK(thread_checker_.CalledOnValidThread());
Cancel();
JniEnvExt* env = JniEnvExt::Get();
env->DeleteGlobalRef(j_voice_recognizer_);
}
bool SbSpeechRecognizerImpl::Start(const SbSpeechConfiguration* configuration) {
SB_DCHECK(thread_checker_.CalledOnValidThread());
SB_DCHECK(configuration);
if (is_started_) {
return false;
}
JniEnvExt* env = JniEnvExt::Get();
env->CallVoidMethodOrAbort(
j_voice_recognizer_, "startRecognition", "(ZZIJ)V",
configuration->continuous, configuration->interim_results,
configuration->max_alternatives, reinterpret_cast<jlong>(this));
is_started_ = true;
return true;
}
void SbSpeechRecognizerImpl::Stop() {
SB_DCHECK(thread_checker_.CalledOnValidThread());
if (!is_started_) {
return;
}
JniEnvExt* env = JniEnvExt::Get();
env->CallVoidMethodOrAbort(j_voice_recognizer_, "stopRecognition", "()V");
is_started_ = false;
}
void SbSpeechRecognizerImpl::Cancel() {
Stop();
}
void SbSpeechRecognizerImpl::OnSpeechDetected(bool detected) {
// Called from Android's UI thread instead of constructor's thread.
SB_DCHECK(!thread_checker_.CalledOnValidThread());
handler_.on_speech_detected(handler_.context, detected);
}
void SbSpeechRecognizerImpl::OnError(int error) {
// Called from Android's UI thread instead of constructor's thread.
SB_DCHECK(!thread_checker_.CalledOnValidThread());
SbSpeechRecognizerError recognizer_error;
switch (error) {
case kErrorNetworkTimeout:
recognizer_error = kSbNetworkError;
break;
case kErrorNetwork:
recognizer_error = kSbNetworkError;
break;
case kErrorAudio:
recognizer_error = kSbAudioCaptureError;
break;
case kErrorService:
recognizer_error = kSbNetworkError;
break;
case kErrorClient:
recognizer_error = kSbAborted;
break;
case kErrorSpeechTimeout:
recognizer_error = kSbNoSpeechError;
break;
case kErrorRecognizerBusy:
case kErrorInsufficientPermissions:
recognizer_error = kSbNotAllowed;
break;
case kErrorNoMatch:
// Maybe keep listening until found a match or time-out triggered by
// client.
recognizer_error = kSbNoSpeechError;
break;
}
handler_.on_error(handler_.context, recognizer_error);
}
void SbSpeechRecognizerImpl::OnResults(const std::vector<std::string>& results,
const std::vector<float>& confidences,
bool is_final) {
// Called from Android's UI thread instead of constructor's thread.
SB_DCHECK(!thread_checker_.CalledOnValidThread());
bool has_confidence = (confidences.size() != 0);
if (has_confidence) {
SB_DCHECK(confidences.size() == results.size());
}
int kSpeechResultSize = results.size();
std::vector<SbSpeechResult> speech_results(kSpeechResultSize);
for (int i = 0; i < kSpeechResultSize; ++i) {
// The callback is responsible for freeing the buffer with
// SbMemoryDeallocate.
speech_results[i].transcript = SbStringDuplicate(results[i].c_str());
speech_results[i].confidence =
has_confidence ? confidences[i]
: std::numeric_limits<float>::quiet_NaN();
}
handler_.on_results(handler_.context, speech_results.data(),
kSpeechResultSize, is_final);
}
} // namespace shared
} // namespace android
} // namespace starboard
namespace {
using starboard::android::shared::JniEnvExt;
starboard::Mutex s_speech_recognizer_mutex_;
SbSpeechRecognizer s_speech_recognizer = kSbSpeechRecognizerInvalid;
} // namespace
// static
SbSpeechRecognizer SbSpeechRecognizerPrivate::CreateSpeechRecognizer(
const SbSpeechRecognizerHandler* handler) {
starboard::ScopedLock lock(s_speech_recognizer_mutex_);
SB_DCHECK(!SbSpeechRecognizerIsValid(s_speech_recognizer));
s_speech_recognizer =
new starboard::android::shared::SbSpeechRecognizerImpl(handler);
return s_speech_recognizer;
}
// static
void SbSpeechRecognizerPrivate::DestroySpeechRecognizer(
SbSpeechRecognizer speech_recognizer) {
starboard::ScopedLock lock(s_speech_recognizer_mutex_);
SB_DCHECK(s_speech_recognizer == speech_recognizer);
SB_DCHECK(SbSpeechRecognizerIsValid(s_speech_recognizer));
delete s_speech_recognizer;
s_speech_recognizer = kSbSpeechRecognizerInvalid;
}
extern "C" SB_EXPORT_PLATFORM void
Java_dev_cobalt_coat_VoiceRecognizer_nativeOnSpeechDetected(
JNIEnv* env,
jobject jcaller,
jlong nativeSpeechRecognizerImpl,
jboolean detected) {
starboard::ScopedLock lock(s_speech_recognizer_mutex_);
starboard::android::shared::SbSpeechRecognizerImpl* native =
reinterpret_cast<starboard::android::shared::SbSpeechRecognizerImpl*>(
nativeSpeechRecognizerImpl);
// This is called by the Android UI thread and it is possible that the
// SbSpeechRecognizer is destroyed before this is called.
if (native != s_speech_recognizer) {
SB_DLOG(WARNING) << "The speech recognizer is destroyed.";
return;
}
native->OnSpeechDetected(detected);
}
extern "C" SB_EXPORT_PLATFORM void
Java_dev_cobalt_coat_VoiceRecognizer_nativeOnError(
JNIEnv* env,
jobject jcaller,
jlong nativeSpeechRecognizerImpl,
jint error) {
starboard::ScopedLock lock(s_speech_recognizer_mutex_);
starboard::android::shared::SbSpeechRecognizerImpl* native =
reinterpret_cast<starboard::android::shared::SbSpeechRecognizerImpl*>(
nativeSpeechRecognizerImpl);
// This is called by the Android UI thread and it is possible that the
// SbSpeechRecognizer is destroyed before this is called.
if (native != s_speech_recognizer) {
SB_DLOG(WARNING) << "The speech recognizer is destroyed.";
return;
}
native->OnError(error);
}
extern "C" SB_EXPORT_PLATFORM void
Java_dev_cobalt_coat_VoiceRecognizer_nativeHandlePermission(
JNIEnv* env,
jobject jcaller,
jlong nativeSpeechRecognizerImpl,
jboolean is_granted) {
starboard::ScopedLock lock(s_speech_recognizer_mutex_);
starboard::android::shared::SbSpeechRecognizerImpl* native =
reinterpret_cast<starboard::android::shared::SbSpeechRecognizerImpl*>(
nativeSpeechRecognizerImpl);
// This is called by the Android UI thread and it is possible that the
// SbSpeechRecognizer is destroyed before this is called.
if (native != s_speech_recognizer) {
SB_DLOG(WARNING) << "The speech recognizer is destroyed.";
return;
}
if (!is_granted) {
native->OnError(starboard::android::shared::kErrorInsufficientPermissions);
}
}
extern "C" SB_EXPORT_PLATFORM void
Java_dev_cobalt_coat_VoiceRecognizer_nativeOnResults(
JniEnvExt* env,
jobject unused_this,
jlong nativeSpeechRecognizerImpl,
jobjectArray results,
jfloatArray confidences,
jboolean is_final) {
starboard::ScopedLock lock(s_speech_recognizer_mutex_);
starboard::android::shared::SbSpeechRecognizerImpl* native =
reinterpret_cast<starboard::android::shared::SbSpeechRecognizerImpl*>(
nativeSpeechRecognizerImpl);
// This is called by the Android UI thread and it is possible that the
// SbSpeechRecognizer is destroyed before this is called.
if (native != s_speech_recognizer) {
SB_DLOG(WARNING) << "The speech recognizer is destroyed.";
return;
}
std::vector<std::string> options;
jint argc = env->GetArrayLength(results);
for (jint i = 0; i < argc; i++) {
starboard::android::shared::ScopedLocalJavaRef<jstring> element(
env->GetObjectArrayElement(results, i));
std::string utf_str = env->GetStringStandardUTFOrAbort(element.Get());
options.push_back(utf_str);
}
std::vector<float> scores(options.size(), 0.0);
if (confidences != NULL) {
SB_DCHECK(argc == env->GetArrayLength(results));
float* confidences_array = env->GetFloatArrayElements(confidences, NULL);
std::copy(confidences_array, confidences_array + argc, scores.begin());
env->ReleaseFloatArrayElements(confidences, confidences_array, 0);
}
native->OnResults(options, scores, is_final);
}