From ee369e4277e48624bb557f0264644ed19a40dd67 Mon Sep 17 00:00:00 2001 From: henrika Date: Mon, 25 May 2015 10:11:27 +0200 Subject: [PATCH] Refactoring of AudioTrackJni and AudioRecordJni using new JVM/JNI classes BUG=NONE TEST=./webrtc/build/android/test_runner.py gtest -s modules_unittests --gtest_filter=AudioDevice* R=tommi@webrtc.org Review URL: https://webrtc-codereview.appspot.com/51079004 Cr-Commit-Position: refs/heads/master@{#9271} --- .../android/audio_device_template.h | 11 -- .../android/audio_device_unittest.cc | 16 +- .../audio_device/android/audio_manager.h | 5 +- .../audio_device/android/audio_record_jni.cc | 175 +++++------------ .../audio_device/android/audio_record_jni.h | 68 ++++--- .../audio_device/android/audio_track_jni.cc | 185 ++++++------------ .../audio_device/android/audio_track_jni.h | 71 ++++--- .../android/ensure_initialized.cc | 6 - .../webrtc/voiceengine/WebRtcAudioRecord.java | 2 +- .../webrtc/voiceengine/WebRtcAudioTrack.java | 2 +- .../modules/utility/interface/jvm_android.h | 1 + webrtc/modules/utility/source/jvm_android.cc | 19 +- webrtc/voice_engine/voice_engine_impl.cc | 10 - 13 files changed, 224 insertions(+), 347 deletions(-) diff --git a/webrtc/modules/audio_device/android/audio_device_template.h b/webrtc/modules/audio_device/android/audio_device_template.h index 0dd0596629..69ede541c4 100644 --- a/webrtc/modules/audio_device/android/audio_device_template.h +++ b/webrtc/modules/audio_device/android/audio_device_template.h @@ -32,17 +32,6 @@ namespace webrtc { template class AudioDeviceTemplate : public AudioDeviceGeneric { public: - static void SetAndroidAudioDeviceObjects(void* javaVM, - void* context) { - OutputType::SetAndroidAudioDeviceObjects(javaVM, context); - InputType::SetAndroidAudioDeviceObjects(javaVM, context); - } - - static void ClearAndroidAudioDeviceObjects() { - OutputType::ClearAndroidAudioDeviceObjects(); - InputType::ClearAndroidAudioDeviceObjects(); - } - AudioDeviceTemplate(AudioDeviceModule::AudioLayer audio_layer, AudioManager* audio_manager) : audio_layer_(audio_layer), diff --git a/webrtc/modules/audio_device/android/audio_device_unittest.cc b/webrtc/modules/audio_device/android/audio_device_unittest.cc index 4a1fa15ef5..e3d657dbc1 100644 --- a/webrtc/modules/audio_device/android/audio_device_unittest.cc +++ b/webrtc/modules/audio_device/android/audio_device_unittest.cc @@ -8,8 +8,12 @@ * be found in the AUTHORS file in the root of the source tree. */ +#include +#include #include #include +#include +#include #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -117,11 +121,11 @@ class FileAudioStream : public AudioStreamInterface { } // AudioStreamInterface::Write() is not implemented. - virtual void Write(const void* source, int num_frames) override {} + void Write(const void* source, int num_frames) override {} // Read samples from file stored in memory (at construction) and copy // |num_frames| (<=> 10ms) to the |destination| byte buffer. - virtual void Read(void* destination, int num_frames) override { + void Read(void* destination, int num_frames) override { memcpy(destination, static_cast (&file_[file_pos_]), num_frames * sizeof(int16_t)); @@ -169,7 +173,7 @@ class FifoAudioStream : public AudioStreamInterface { // Allocate new memory, copy |num_frames| samples from |source| into memory // and add pointer to the memory location to end of the list. // Increases the size of the FIFO by one element. - virtual void Write(const void* source, int num_frames) override { + void Write(const void* source, int num_frames) override { ASSERT_EQ(num_frames, frames_per_buffer_); PRINTD("+"); if (write_count_++ < kNumIgnoreFirstCallbacks) { @@ -192,7 +196,7 @@ class FifoAudioStream : public AudioStreamInterface { // Read pointer to data buffer from front of list, copy |num_frames| of stored // data into |destination| and delete the utilized memory allocation. // Decreases the size of the FIFO by one element. - virtual void Read(void* destination, int num_frames) override { + void Read(void* destination, int num_frames) override { ASSERT_EQ(num_frames, frames_per_buffer_); PRINTD("-"); rtc::CritScope lock(&lock_); @@ -255,7 +259,7 @@ class LatencyMeasuringAudioStream : public AudioStreamInterface { } // Insert periodic impulses in first two samples of |destination|. - virtual void Read(void* destination, int num_frames) override { + void Read(void* destination, int num_frames) override { ASSERT_EQ(num_frames, frames_per_buffer_); if (play_count_ == 0) { PRINT("["); @@ -277,7 +281,7 @@ class LatencyMeasuringAudioStream : public AudioStreamInterface { // Detect received impulses in |source|, derive time between transmission and // detection and add the calculated delay to list of latencies. - virtual void Write(const void* source, int num_frames) override { + void Write(const void* source, int num_frames) override { ASSERT_EQ(num_frames, frames_per_buffer_); rec_count_++; if (pulse_time_ == 0) { diff --git a/webrtc/modules/audio_device/android/audio_manager.h b/webrtc/modules/audio_device/android/audio_manager.h index 5321f74c09..bc46f03cbc 100644 --- a/webrtc/modules/audio_device/android/audio_manager.h +++ b/webrtc/modules/audio_device/android/audio_manager.h @@ -173,12 +173,13 @@ class AudioManager { // Also ensures that DetachCurrentThread() is called at destruction. AttachCurrentThreadIfNeeded attach_thread_if_needed_; + // Wraps the JNI interface pointer and methods associated with it. rtc::scoped_ptr j_environment_; - // TODO(henrika): add comments... + // Contains factory method for creating the Java object. rtc::scoped_ptr j_native_registration_; - // TODO(henrika): add comments... + // Wraps the Java specific parts of the AudioManager. rtc::scoped_ptr j_audio_manager_; AudioDeviceModule::AudioLayer audio_layer_; diff --git a/webrtc/modules/audio_device/android/audio_record_jni.cc b/webrtc/modules/audio_device/android/audio_record_jni.cc index 957efe5a4e..e829781bfd 100644 --- a/webrtc/modules/audio_device/android/audio_record_jni.cc +++ b/webrtc/modules/audio_device/android/audio_record_jni.cc @@ -25,73 +25,65 @@ namespace webrtc { -static JavaVM* g_jvm = NULL; -static jobject g_context = NULL; -static jclass g_audio_record_class = NULL; - -void AudioRecordJni::SetAndroidAudioDeviceObjects(void* jvm, void* context) { - ALOGD("SetAndroidAudioDeviceObjects%s", GetThreadInfo().c_str()); - - CHECK(jvm); - CHECK(context); - - g_jvm = reinterpret_cast(jvm); - JNIEnv* jni = GetEnv(g_jvm); - CHECK(jni) << "AttachCurrentThread must be called on this tread"; - - // Protect context from being deleted during garbage collection. - g_context = NewGlobalRef(jni, reinterpret_cast(context)); - - // Load the locally-defined WebRtcAudioRecord class and create a new global - // reference to it. - jclass local_class = FindClass( - jni, "org/webrtc/voiceengine/WebRtcAudioRecord"); - g_audio_record_class = reinterpret_cast( - NewGlobalRef(jni, local_class)); - jni->DeleteLocalRef(local_class); - CHECK_EXCEPTION(jni); - - // Register native methods with the WebRtcAudioRecord class. These methods - // are declared private native in WebRtcAudioRecord.java. - JNINativeMethod native_methods[] = { - {"nativeCacheDirectBufferAddress", "(Ljava/nio/ByteBuffer;J)V", - reinterpret_cast( - &webrtc::AudioRecordJni::CacheDirectBufferAddress)}, - {"nativeDataIsRecorded", "(IJ)V", - reinterpret_cast(&webrtc::AudioRecordJni::DataIsRecorded)}}; - jni->RegisterNatives(g_audio_record_class, - native_methods, arraysize(native_methods)); - CHECK_EXCEPTION(jni) << "Error during RegisterNatives"; +// AudioRecordJni::JavaAudioRecord implementation. +AudioRecordJni::JavaAudioRecord::JavaAudioRecord( + NativeRegistration* native_reg, rtc::scoped_ptr audio_record) + : audio_record_(audio_record.Pass()), + init_recording_(native_reg->GetMethodId("InitRecording", "(II)I")), + start_recording_(native_reg->GetMethodId("StartRecording", "()Z")), + stop_recording_(native_reg->GetMethodId("StopRecording", "()Z")), + enable_built_in_aec_(native_reg->GetMethodId( + "EnableBuiltInAEC", "(Z)Z")) { } -void AudioRecordJni::ClearAndroidAudioDeviceObjects() { - ALOGD("ClearAndroidAudioDeviceObjects%s", GetThreadInfo().c_str()); - JNIEnv* jni = GetEnv(g_jvm); - CHECK(jni) << "AttachCurrentThread must be called on this tread"; - jni->UnregisterNatives(g_audio_record_class); - CHECK_EXCEPTION(jni) << "Error during UnregisterNatives"; - DeleteGlobalRef(jni, g_audio_record_class); - g_audio_record_class = NULL; - DeleteGlobalRef(jni, g_context); - g_context = NULL; - g_jvm = NULL; +AudioRecordJni::JavaAudioRecord::~JavaAudioRecord() {} + +int AudioRecordJni::JavaAudioRecord::InitRecording( + int sample_rate, int channels) { + return audio_record_->CallIntMethod(init_recording_, sample_rate, channels); } +bool AudioRecordJni::JavaAudioRecord::StartRecording() { + return audio_record_->CallBooleanMethod(start_recording_); +} + +bool AudioRecordJni::JavaAudioRecord::StopRecording() { + return audio_record_->CallBooleanMethod(stop_recording_); +} + +bool AudioRecordJni::JavaAudioRecord::EnableBuiltInAEC(bool enable) { + return audio_record_->CallBooleanMethod(enable_built_in_aec_, enable); +} + +// AudioRecordJni implementation. AudioRecordJni::AudioRecordJni(AudioManager* audio_manager) - : audio_manager_(audio_manager), + : j_environment_(JVM::GetInstance()->environment()), + audio_manager_(audio_manager), audio_parameters_(audio_manager->GetRecordAudioParameters()), total_delay_in_milliseconds_(0), - j_audio_record_(NULL), - direct_buffer_address_(NULL), + direct_buffer_address_(nullptr), direct_buffer_capacity_in_bytes_(0), frames_per_buffer_(0), initialized_(false), recording_(false), - audio_device_buffer_(NULL) { + audio_device_buffer_(nullptr) { ALOGD("ctor%s", GetThreadInfo().c_str()); DCHECK(audio_parameters_.is_valid()); - CHECK(HasDeviceObjects()); - CreateJavaInstance(); + CHECK(j_environment_); + JNINativeMethod native_methods[] = { + {"nativeCacheDirectBufferAddress", "(Ljava/nio/ByteBuffer;J)V", + reinterpret_cast( + &webrtc::AudioRecordJni::CacheDirectBufferAddress)}, + {"nativeDataIsRecorded", "(IJ)V", + reinterpret_cast(&webrtc::AudioRecordJni::DataIsRecorded)}}; + j_native_registration_ = j_environment_->RegisterNatives( + "org/webrtc/voiceengine/WebRtcAudioRecord", + native_methods, arraysize(native_methods)); + j_audio_record_.reset(new JavaAudioRecord( + j_native_registration_.get(), + j_native_registration_->NewObject( + "", "(Landroid/content/Context;J)V", + JVM::GetInstance()->context(), PointerTojlong(this)))); // Detach from this thread since we want to use the checker to verify calls // from the Java based audio thread. thread_checker_java_.DetachFromThread(); @@ -101,10 +93,6 @@ AudioRecordJni::~AudioRecordJni() { ALOGD("~dtor%s", GetThreadInfo().c_str()); DCHECK(thread_checker_.CalledOnValidThread()); Terminate(); - AttachThreadScoped ats(g_jvm); - JNIEnv* jni = ats.env(); - jni->DeleteGlobalRef(j_audio_record_); - j_audio_record_ = NULL; } int32_t AudioRecordJni::Init() { @@ -125,17 +113,8 @@ int32_t AudioRecordJni::InitRecording() { DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(!initialized_); DCHECK(!recording_); - if (initialized_ || recording_) { - return -1; - } - AttachThreadScoped ats(g_jvm); - JNIEnv* jni = ats.env(); - jmethodID initRecordingID = GetMethodID( - jni, g_audio_record_class, "InitRecording", "(II)I"); - jint frames_per_buffer = jni->CallIntMethod( - j_audio_record_, initRecordingID, audio_parameters_.sample_rate(), - audio_parameters_.channels()); - CHECK_EXCEPTION(jni); + int frames_per_buffer = j_audio_record_->InitRecording( + audio_parameters_.sample_rate(), audio_parameters_.channels()); if (frames_per_buffer < 0) { ALOGE("InitRecording failed!"); return -1; @@ -154,16 +133,7 @@ int32_t AudioRecordJni::StartRecording() { DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(initialized_); DCHECK(!recording_); - if (!initialized_ || recording_) { - return -1; - } - AttachThreadScoped ats(g_jvm); - JNIEnv* jni = ats.env(); - jmethodID startRecordingID = GetMethodID( - jni, g_audio_record_class, "StartRecording", "()Z"); - jboolean res = jni->CallBooleanMethod(j_audio_record_, startRecordingID); - CHECK_EXCEPTION(jni); - if (!res) { + if (!j_audio_record_->StartRecording()) { ALOGE("StartRecording failed!"); return -1; } @@ -177,13 +147,7 @@ int32_t AudioRecordJni::StopRecording() { if (!initialized_ || !recording_) { return 0; } - AttachThreadScoped ats(g_jvm); - JNIEnv* jni = ats.env(); - jmethodID stopRecordingID = GetMethodID( - jni, g_audio_record_class, "StopRecording", "()Z"); - jboolean res = jni->CallBooleanMethod(j_audio_record_, stopRecordingID); - CHECK_EXCEPTION(jni); - if (!res) { + if (!j_audio_record_->StopRecording()) { ALOGE("StopRecording failed!"); return -1; } @@ -214,18 +178,7 @@ void AudioRecordJni::AttachAudioBuffer(AudioDeviceBuffer* audioBuffer) { int32_t AudioRecordJni::EnableBuiltInAEC(bool enable) { ALOGD("EnableBuiltInAEC%s", GetThreadInfo().c_str()); DCHECK(thread_checker_.CalledOnValidThread()); - AttachThreadScoped ats(g_jvm); - JNIEnv* jni = ats.env(); - jmethodID enableBuiltInAEC = GetMethodID( - jni, g_audio_record_class, "EnableBuiltInAEC", "(Z)Z"); - jboolean res = jni->CallBooleanMethod( - j_audio_record_, enableBuiltInAEC, enable); - CHECK_EXCEPTION(jni); - if (!res) { - ALOGE("EnableBuiltInAEC failed!"); - return -1; - } - return 0; + return j_audio_record_->EnableBuiltInAEC(enable) ? 0 : -1; } void JNICALL AudioRecordJni::CacheDirectBufferAddress( @@ -239,6 +192,7 @@ void AudioRecordJni::OnCacheDirectBufferAddress( JNIEnv* env, jobject byte_buffer) { ALOGD("OnCacheDirectBufferAddress"); DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!direct_buffer_address_); direct_buffer_address_ = env->GetDirectBufferAddress(byte_buffer); jlong capacity = env->GetDirectBufferCapacity(byte_buffer); @@ -267,32 +221,11 @@ void AudioRecordJni::OnDataIsRecorded(int length) { // |playDelayMs| parameter only. Components like the AEC only sees the sum // of |playDelayMs| and |recDelayMs|, hence the distributions does not matter. audio_device_buffer_->SetVQEData(total_delay_in_milliseconds_, - 0, // recDelayMs - 0 ); // clockDrift + 0, // recDelayMs + 0); // clockDrift if (audio_device_buffer_->DeliverRecordedData() == -1) { ALOGE("AudioDeviceBuffer::DeliverRecordedData failed!"); } } -bool AudioRecordJni::HasDeviceObjects() { - return (g_jvm && g_context && g_audio_record_class); -} - -void AudioRecordJni::CreateJavaInstance() { - ALOGD("CreateJavaInstance"); - AttachThreadScoped ats(g_jvm); - JNIEnv* jni = ats.env(); - jmethodID constructorID = GetMethodID( - jni, g_audio_record_class, "", "(Landroid/content/Context;J)V"); - j_audio_record_ = jni->NewObject(g_audio_record_class, - constructorID, - g_context, - reinterpret_cast(this)); - CHECK_EXCEPTION(jni) << "Error during NewObject"; - CHECK(j_audio_record_); - j_audio_record_ = jni->NewGlobalRef(j_audio_record_); - CHECK_EXCEPTION(jni) << "Error during NewGlobalRef"; - CHECK(j_audio_record_); -} - } // namespace webrtc diff --git a/webrtc/modules/audio_device/android/audio_record_jni.h b/webrtc/modules/audio_device/android/audio_record_jni.h index 18cb0c8ba7..1a2bd9de22 100644 --- a/webrtc/modules/audio_device/android/audio_record_jni.h +++ b/webrtc/modules/audio_device/android/audio_record_jni.h @@ -18,6 +18,7 @@ #include "webrtc/modules/audio_device/include/audio_device_defines.h" #include "webrtc/modules/audio_device/audio_device_generic.h" #include "webrtc/modules/utility/interface/helpers_android.h" +#include "webrtc/modules/utility/interface/jvm_android.h" namespace webrtc { @@ -35,26 +36,31 @@ namespace webrtc { // An instance must be created and destroyed on one and the same thread. // All public methods must also be called on the same thread. A thread checker // will DCHECK if any method is called on an invalid thread. -// It is possible to call the two static methods (SetAndroidAudioDeviceObjects -// and ClearAndroidAudioDeviceObjects) from a different thread but both will -// CHECK that the calling thread is attached to a Java VM. // -// All methods use AttachThreadScoped to attach to a Java VM if needed and then -// detach when method goes out of scope. We do so because this class does not -// own the thread is is created and called on and other objects on the same -// thread might put us in a detached state at any time. +// This class uses AttachCurrentThreadIfNeeded to attach to a Java VM if needed +// and detach when the object goes out of scope. Additional thread checking +// guarantees that no other (possibly non attached) thread is used. class AudioRecordJni { public: - // Use the invocation API to allow the native application to use the JNI - // interface pointer to access VM features. - // |jvm| denotes the Java VM and |context| corresponds to - // android.content.Context in Java. - // This method also sets a global jclass object, |g_audio_record_class| for - // the "org/webrtc/voiceengine/WebRtcAudioRecord"-class. - static void SetAndroidAudioDeviceObjects(void* jvm, void* context); - // Always call this method after the object has been destructed. It deletes - // existing global references and enables garbage collection. - static void ClearAndroidAudioDeviceObjects(); + // Wraps the Java specific parts of the AudioRecordJni into one helper class. + class JavaAudioRecord { + public: + JavaAudioRecord(NativeRegistration* native_registration, + rtc::scoped_ptr audio_track); + ~JavaAudioRecord(); + + int InitRecording(int sample_rate, int channels); + bool StartRecording(); + bool StopRecording(); + bool EnableBuiltInAEC(bool enable); + + private: + rtc::scoped_ptr audio_record_; + jmethodID init_recording_; + jmethodID start_recording_; + jmethodID stop_recording_; + jmethodID enable_built_in_aec_; + }; explicit AudioRecordJni(AudioManager* audio_manager); ~AudioRecordJni(); @@ -66,7 +72,7 @@ class AudioRecordJni { bool RecordingIsInitialized() const { return initialized_; } int32_t StartRecording(); - int32_t StopRecording (); + int32_t StopRecording(); bool Recording() const { return recording_; } void AttachAudioBuffer(AudioDeviceBuffer* audioBuffer); @@ -93,23 +99,26 @@ class AudioRecordJni { JNIEnv* env, jobject obj, jint length, jlong nativeAudioRecord); void OnDataIsRecorded(int length); - // Returns true if SetAndroidAudioDeviceObjects() has been called - // successfully. - bool HasDeviceObjects(); - - // Called from the constructor. Defines the |j_audio_record_| member. - void CreateJavaInstance(); - // Stores thread ID in constructor. - // We can then use ThreadChecker::CalledOnValidThread() to ensure that - // other methods are called from the same thread. - // Currently only does DCHECK(thread_checker_.CalledOnValidThread()). rtc::ThreadChecker thread_checker_; // Stores thread ID in first call to OnDataIsRecorded() from high-priority // thread in Java. Detached during construction of this object. rtc::ThreadChecker thread_checker_java_; + // Calls AttachCurrentThread() if this thread is not attached at construction. + // Also ensures that DetachCurrentThread() is called at destruction. + AttachCurrentThreadIfNeeded attach_thread_if_needed_; + + // Wraps the JNI interface pointer and methods associated with it. + rtc::scoped_ptr j_environment_; + + // Contains factory method for creating the Java object. + rtc::scoped_ptr j_native_registration_; + + // Wraps the Java specific parts of the AudioRecordJni class. + rtc::scoped_ptr j_audio_record_; + // Raw pointer to the audio manger. const AudioManager* audio_manager_; @@ -122,9 +131,6 @@ class AudioRecordJni { // possible values. See audio_common.h for details. int total_delay_in_milliseconds_; - // The Java WebRtcAudioRecord instance. - jobject j_audio_record_; - // Cached copy of address to direct audio buffer owned by |j_audio_record_|. void* direct_buffer_address_; diff --git a/webrtc/modules/audio_device/android/audio_track_jni.cc b/webrtc/modules/audio_device/android/audio_track_jni.cc index ac8bdeefa3..f9a5d4d069 100644 --- a/webrtc/modules/audio_device/android/audio_track_jni.cc +++ b/webrtc/modules/audio_device/android/audio_track_jni.cc @@ -25,70 +25,72 @@ namespace webrtc { -static JavaVM* g_jvm = NULL; -static jobject g_context = NULL; -static jclass g_audio_track_class = NULL; - -void AudioTrackJni::SetAndroidAudioDeviceObjects(void* jvm, void* context) { - ALOGD("SetAndroidAudioDeviceObjects%s", GetThreadInfo().c_str()); - - CHECK(jvm); - CHECK(context); - - g_jvm = reinterpret_cast(jvm); - JNIEnv* jni = GetEnv(g_jvm); - CHECK(jni) << "AttachCurrentThread must be called on this tread"; - - g_context = NewGlobalRef(jni, reinterpret_cast(context)); - jclass local_class = FindClass( - jni, "org/webrtc/voiceengine/WebRtcAudioTrack"); - g_audio_track_class = reinterpret_cast( - NewGlobalRef(jni, local_class)); - jni->DeleteLocalRef(local_class); - CHECK_EXCEPTION(jni); - - // Register native methods with the WebRtcAudioTrack class. These methods - // are declared private native in WebRtcAudioTrack.java. - JNINativeMethod native_methods[] = { - {"nativeCacheDirectBufferAddress", "(Ljava/nio/ByteBuffer;J)V", - reinterpret_cast( - &webrtc::AudioTrackJni::CacheDirectBufferAddress)}, - {"nativeGetPlayoutData", "(IJ)V", - reinterpret_cast(&webrtc::AudioTrackJni::GetPlayoutData)}}; - jni->RegisterNatives(g_audio_track_class, - native_methods, arraysize(native_methods)); - CHECK_EXCEPTION(jni) << "Error during RegisterNatives"; +// AudioTrackJni::JavaAudioTrack implementation. +AudioTrackJni::JavaAudioTrack::JavaAudioTrack( + NativeRegistration* native_reg, rtc::scoped_ptr audio_track) + : audio_track_(audio_track.Pass()), + init_playout_(native_reg->GetMethodId("InitPlayout", "(II)V")), + start_playout_(native_reg->GetMethodId("StartPlayout", "()Z")), + stop_playout_(native_reg->GetMethodId("StopPlayout", "()Z")), + set_stream_volume_(native_reg->GetMethodId("SetStreamVolume", "(I)Z")), + get_stream_max_volume_(native_reg->GetMethodId( + "GetStreamMaxVolume", "()I")), + get_stream_volume_(native_reg->GetMethodId("GetStreamVolume", "()I")) { } -// TODO(henrika): figure out if it is required to call this method? If so, -// ensure that is is always called as part of the destruction phase. -void AudioTrackJni::ClearAndroidAudioDeviceObjects() { - ALOGD("ClearAndroidAudioDeviceObjects%s", GetThreadInfo().c_str()); - JNIEnv* jni = GetEnv(g_jvm); - CHECK(jni) << "AttachCurrentThread must be called on this tread"; - jni->UnregisterNatives(g_audio_track_class); - CHECK_EXCEPTION(jni) << "Error during UnregisterNatives"; - DeleteGlobalRef(jni, g_audio_track_class); - g_audio_track_class = NULL; - DeleteGlobalRef(jni, g_context); - g_context = NULL; - g_jvm = NULL; +AudioTrackJni::JavaAudioTrack::~JavaAudioTrack() {} + +void AudioTrackJni::JavaAudioTrack::InitPlayout(int sample_rate, int channels) { + audio_track_->CallVoidMethod(init_playout_, sample_rate, channels); +} + +bool AudioTrackJni::JavaAudioTrack::StartPlayout() { + return audio_track_->CallBooleanMethod(start_playout_); +} + +bool AudioTrackJni::JavaAudioTrack::StopPlayout() { + return audio_track_->CallBooleanMethod(stop_playout_); +} + +bool AudioTrackJni::JavaAudioTrack::SetStreamVolume(int volume) { + return audio_track_->CallBooleanMethod(set_stream_volume_, volume); +} + +int AudioTrackJni::JavaAudioTrack::GetStreamMaxVolume() { + return audio_track_->CallIntMethod(get_stream_max_volume_); +} + +int AudioTrackJni::JavaAudioTrack::GetStreamVolume() { + return audio_track_->CallIntMethod(get_stream_volume_); } // TODO(henrika): possible extend usage of AudioManager and add it as member. AudioTrackJni::AudioTrackJni(AudioManager* audio_manager) - : audio_parameters_(audio_manager->GetPlayoutAudioParameters()), - j_audio_track_(NULL), - direct_buffer_address_(NULL), + : j_environment_(JVM::GetInstance()->environment()), + audio_parameters_(audio_manager->GetPlayoutAudioParameters()), + direct_buffer_address_(nullptr), direct_buffer_capacity_in_bytes_(0), frames_per_buffer_(0), initialized_(false), playing_(false), - audio_device_buffer_(NULL) { + audio_device_buffer_(nullptr) { ALOGD("ctor%s", GetThreadInfo().c_str()); DCHECK(audio_parameters_.is_valid()); - CHECK(HasDeviceObjects()); - CreateJavaInstance(); + CHECK(j_environment_); + JNINativeMethod native_methods[] = { + {"nativeCacheDirectBufferAddress", "(Ljava/nio/ByteBuffer;J)V", + reinterpret_cast( + &webrtc::AudioTrackJni::CacheDirectBufferAddress)}, + {"nativeGetPlayoutData", "(IJ)V", + reinterpret_cast(&webrtc::AudioTrackJni::GetPlayoutData)}}; + j_native_registration_ = j_environment_->RegisterNatives( + "org/webrtc/voiceengine/WebRtcAudioTrack", + native_methods, arraysize(native_methods)); + j_audio_track_.reset(new JavaAudioTrack( + j_native_registration_.get(), + j_native_registration_->NewObject( + "", "(Landroid/content/Context;J)V", + JVM::GetInstance()->context(), PointerTojlong(this)))); // Detach from this thread since we want to use the checker to verify calls // from the Java based audio thread. thread_checker_java_.DetachFromThread(); @@ -98,10 +100,6 @@ AudioTrackJni::~AudioTrackJni() { ALOGD("~dtor%s", GetThreadInfo().c_str()); DCHECK(thread_checker_.CalledOnValidThread()); Terminate(); - AttachThreadScoped ats(g_jvm); - JNIEnv* jni = ats.env(); - jni->DeleteGlobalRef(j_audio_track_); - j_audio_track_ = NULL; } int32_t AudioTrackJni::Init() { @@ -122,16 +120,8 @@ int32_t AudioTrackJni::InitPlayout() { DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(!initialized_); DCHECK(!playing_); - if (initialized_ || playing_) { - return -1; - } - AttachThreadScoped ats(g_jvm); - JNIEnv* jni = ats.env(); - jmethodID initPlayoutID = GetMethodID( - jni, g_audio_track_class, "InitPlayout", "(II)V"); - jni->CallVoidMethod(j_audio_track_, initPlayoutID, + j_audio_track_->InitPlayout( audio_parameters_.sample_rate(), audio_parameters_.channels()); - CHECK_EXCEPTION(jni); initialized_ = true; return 0; } @@ -141,16 +131,7 @@ int32_t AudioTrackJni::StartPlayout() { DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(initialized_); DCHECK(!playing_); - if (!initialized_ || playing_) { - return -1; - } - AttachThreadScoped ats(g_jvm); - JNIEnv* jni = ats.env(); - jmethodID startPlayoutID = GetMethodID( - jni, g_audio_track_class, "StartPlayout", "()Z"); - jboolean res = jni->CallBooleanMethod(j_audio_track_, startPlayoutID); - CHECK_EXCEPTION(jni); - if (!res) { + if (!j_audio_track_->StartPlayout()) { ALOGE("StartPlayout failed!"); return -1; } @@ -164,13 +145,7 @@ int32_t AudioTrackJni::StopPlayout() { if (!initialized_ || !playing_) { return 0; } - AttachThreadScoped ats(g_jvm); - JNIEnv* jni = ats.env(); - jmethodID stopPlayoutID = GetMethodID( - jni, g_audio_track_class, "StopPlayout", "()Z"); - jboolean res = jni->CallBooleanMethod(j_audio_track_, stopPlayoutID); - CHECK_EXCEPTION(jni); - if (!res) { + if (!j_audio_track_->StopPlayout()) { ALOGE("StopPlayout failed!"); return -1; } @@ -190,26 +165,13 @@ int AudioTrackJni::SpeakerVolumeIsAvailable(bool& available) { int AudioTrackJni::SetSpeakerVolume(uint32_t volume) { ALOGD("SetSpeakerVolume(%d)%s", volume, GetThreadInfo().c_str()); DCHECK(thread_checker_.CalledOnValidThread()); - AttachThreadScoped ats(g_jvm); - JNIEnv* jni = ats.env(); - jmethodID setStreamVolume = GetMethodID( - jni, g_audio_track_class, "SetStreamVolume", "(I)Z"); - jboolean res = jni->CallBooleanMethod( - j_audio_track_, setStreamVolume, volume); - CHECK_EXCEPTION(jni); - return res ? 0 : -1; + return j_audio_track_->SetStreamVolume(volume) ? 0 : -1; } int AudioTrackJni::MaxSpeakerVolume(uint32_t& max_volume) const { ALOGD("MaxSpeakerVolume%s", GetThreadInfo().c_str()); DCHECK(thread_checker_.CalledOnValidThread()); - AttachThreadScoped ats(g_jvm); - JNIEnv* jni = ats.env(); - jmethodID getStreamMaxVolume = GetMethodID( - jni, g_audio_track_class, "GetStreamMaxVolume", "()I"); - jint max_vol = jni->CallIntMethod(j_audio_track_, getStreamMaxVolume); - CHECK_EXCEPTION(jni); - max_volume = max_vol; + max_volume = j_audio_track_->GetStreamMaxVolume(); return 0; } @@ -223,13 +185,7 @@ int AudioTrackJni::MinSpeakerVolume(uint32_t& min_volume) const { int AudioTrackJni::SpeakerVolume(uint32_t& volume) const { ALOGD("SpeakerVolume%s", GetThreadInfo().c_str()); DCHECK(thread_checker_.CalledOnValidThread()); - AttachThreadScoped ats(g_jvm); - JNIEnv* jni = ats.env(); - jmethodID getStreamVolume = GetMethodID( - jni, g_audio_track_class, "GetStreamVolume", "()I"); - jint stream_volume = jni->CallIntMethod(j_audio_track_, getStreamVolume); - CHECK_EXCEPTION(jni); - volume = stream_volume; + volume = j_audio_track_->GetStreamVolume(); return 0; } @@ -295,25 +251,4 @@ void AudioTrackJni::OnGetPlayoutData(int length) { DCHECK_EQ(length, kBytesPerFrame * samples); } -bool AudioTrackJni::HasDeviceObjects() { - return (g_jvm && g_context && g_audio_track_class); -} - -void AudioTrackJni::CreateJavaInstance() { - ALOGD("CreateJavaInstance"); - AttachThreadScoped ats(g_jvm); - JNIEnv* jni = ats.env(); - jmethodID constructorID = GetMethodID( - jni, g_audio_track_class, "", "(Landroid/content/Context;J)V"); - j_audio_track_ = jni->NewObject(g_audio_track_class, - constructorID, - g_context, - reinterpret_cast(this)); - CHECK_EXCEPTION(jni) << "Error during NewObject"; - CHECK(j_audio_track_); - j_audio_track_ = jni->NewGlobalRef(j_audio_track_); - CHECK_EXCEPTION(jni) << "Error during NewGlobalRef"; - CHECK(j_audio_track_); -} - } // namespace webrtc diff --git a/webrtc/modules/audio_device/android/audio_track_jni.h b/webrtc/modules/audio_device/android/audio_track_jni.h index 82f5d19796..57f7b5137f 100644 --- a/webrtc/modules/audio_device/android/audio_track_jni.h +++ b/webrtc/modules/audio_device/android/audio_track_jni.h @@ -19,6 +19,7 @@ #include "webrtc/modules/audio_device/include/audio_device_defines.h" #include "webrtc/modules/audio_device/audio_device_generic.h" #include "webrtc/modules/utility/interface/helpers_android.h" +#include "webrtc/modules/utility/interface/jvm_android.h" namespace webrtc { @@ -31,28 +32,37 @@ namespace webrtc { // An instance must be created and destroyed on one and the same thread. // All public methods must also be called on the same thread. A thread checker // will DCHECK if any method is called on an invalid thread. -// It is possible to call the two static methods (SetAndroidAudioDeviceObjects -// and ClearAndroidAudioDeviceObjects) from a different thread but both will -// CHECK that the calling thread is attached to a Java VM. // -// All methods use AttachThreadScoped to attach to a Java VM if needed and then -// detach when method goes out of scope. We do so because this class does not -// own the thread is is created and called on and other objects on the same -// thread might put us in a detached state at any time. +// This class uses AttachCurrentThreadIfNeeded to attach to a Java VM if needed +// and detach when the object goes out of scope. Additional thread checking +// guarantees that no other (possibly non attached) thread is used. class AudioTrackJni { public: - // Use the invocation API to allow the native application to use the JNI - // interface pointer to access VM features. - // |jvm| denotes the Java VM and |context| corresponds to - // android.content.Context in Java. - // This method also sets a global jclass object, |g_audio_track_class| for - // the "org/webrtc/voiceengine/WebRtcAudioTrack"-class. - static void SetAndroidAudioDeviceObjects(void* jvm, void* context); - // Always call this method after the object has been destructed. It deletes - // existing global references and enables garbage collection. - static void ClearAndroidAudioDeviceObjects(); + // Wraps the Java specific parts of the AudioTrackJni into one helper class. + class JavaAudioTrack { + public: + JavaAudioTrack(NativeRegistration* native_registration, + rtc::scoped_ptr audio_track); + ~JavaAudioTrack(); - AudioTrackJni(AudioManager* audio_manager); + void InitPlayout(int sample_rate, int channels); + bool StartPlayout(); + bool StopPlayout(); + bool SetStreamVolume(int volume); + int GetStreamMaxVolume(); + int GetStreamVolume(); + + private: + rtc::scoped_ptr audio_track_; + jmethodID init_playout_; + jmethodID start_playout_; + jmethodID stop_playout_; + jmethodID set_stream_volume_; + jmethodID get_stream_max_volume_; + jmethodID get_stream_volume_; + }; + + explicit AudioTrackJni(AudioManager* audio_manager); ~AudioTrackJni(); int32_t Init(); @@ -91,29 +101,30 @@ class AudioTrackJni { JNIEnv* env, jobject obj, jint length, jlong nativeAudioTrack); void OnGetPlayoutData(int length); - // Returns true if SetAndroidAudioDeviceObjects() has been called - // successfully. - bool HasDeviceObjects(); - - // Called from the constructor. Defines the |j_audio_track_| member. - void CreateJavaInstance(); - // Stores thread ID in constructor. - // We can then use ThreadChecker::CalledOnValidThread() to ensure that - // other methods are called from the same thread. rtc::ThreadChecker thread_checker_; // Stores thread ID in first call to OnGetPlayoutData() from high-priority // thread in Java. Detached during construction of this object. rtc::ThreadChecker thread_checker_java_; + // Calls AttachCurrentThread() if this thread is not attached at construction. + // Also ensures that DetachCurrentThread() is called at destruction. + AttachCurrentThreadIfNeeded attach_thread_if_needed_; + + // Wraps the JNI interface pointer and methods associated with it. + rtc::scoped_ptr j_environment_; + + // Contains factory method for creating the Java object. + rtc::scoped_ptr j_native_registration_; + + // Wraps the Java specific parts of the AudioTrackJni class. + rtc::scoped_ptr j_audio_track_; + // Contains audio parameters provided to this class at construction by the // AudioManager. const AudioParameters audio_parameters_; - // The Java WebRtcAudioTrack instance. - jobject j_audio_track_; - // Cached copy of address to direct audio buffer owned by |j_audio_track_|. void* direct_buffer_address_; diff --git a/webrtc/modules/audio_device/android/ensure_initialized.cc b/webrtc/modules/audio_device/android/ensure_initialized.cc index bd28c45a59..a194a5e9c9 100644 --- a/webrtc/modules/audio_device/android/ensure_initialized.cc +++ b/webrtc/modules/audio_device/android/ensure_initialized.cc @@ -33,12 +33,6 @@ void EnsureInitializedOnce() { // Initialize the Java environment (currently only used by the audio manager). webrtc::JVM::Initialize(jvm, context); - // TODO(henrika): remove this call when AudioRecordJni and AudioTrackJni - // are modified to use the same sort of Java initialization as the audio - // manager. - using AudioDeviceJava = AudioDeviceTemplate; - AudioDeviceJava::SetAndroidAudioDeviceObjects(jvm, context); - } void EnsureInitialized() { diff --git a/webrtc/modules/audio_device/android/java/src/org/webrtc/voiceengine/WebRtcAudioRecord.java b/webrtc/modules/audio_device/android/java/src/org/webrtc/voiceengine/WebRtcAudioRecord.java index ac60be201a..f81bab3e93 100644 --- a/webrtc/modules/audio_device/android/java/src/org/webrtc/voiceengine/WebRtcAudioRecord.java +++ b/webrtc/modules/audio_device/android/java/src/org/webrtc/voiceengine/WebRtcAudioRecord.java @@ -147,7 +147,7 @@ class WebRtcAudioRecord { } final int bytesPerFrame = channels * (BITS_PER_SAMPLE / 8); final int framesPerBuffer = sampleRate / BUFFERS_PER_SECOND; - byteBuffer = byteBuffer.allocateDirect(bytesPerFrame * framesPerBuffer); + byteBuffer = ByteBuffer.allocateDirect(bytesPerFrame * framesPerBuffer); Logd("byteBuffer.capacity: " + byteBuffer.capacity()); // Rather than passing the ByteBuffer with every callback (requiring // the potentially expensive GetDirectBufferAddress) we simply have the diff --git a/webrtc/modules/audio_device/android/java/src/org/webrtc/voiceengine/WebRtcAudioTrack.java b/webrtc/modules/audio_device/android/java/src/org/webrtc/voiceengine/WebRtcAudioTrack.java index 276045d53d..da0980e911 100644 --- a/webrtc/modules/audio_device/android/java/src/org/webrtc/voiceengine/WebRtcAudioTrack.java +++ b/webrtc/modules/audio_device/android/java/src/org/webrtc/voiceengine/WebRtcAudioTrack.java @@ -21,7 +21,7 @@ import android.os.Process; import android.util.Log; class WebRtcAudioTrack { - private static final boolean DEBUG = false; + private static final boolean DEBUG = true; private static final String TAG = "WebRtcAudioTrack"; diff --git a/webrtc/modules/utility/interface/jvm_android.h b/webrtc/modules/utility/interface/jvm_android.h index 543ffeaaa4..0744fdbf12 100644 --- a/webrtc/modules/utility/interface/jvm_android.h +++ b/webrtc/modules/utility/interface/jvm_android.h @@ -45,6 +45,7 @@ class GlobalRef { ~GlobalRef(); jboolean CallBooleanMethod(jmethodID methodID, ...); + jint CallIntMethod(jmethodID methodID, ...); void CallVoidMethod(jmethodID methodID, ...); private: diff --git a/webrtc/modules/utility/source/jvm_android.cc b/webrtc/modules/utility/source/jvm_android.cc index 2655195cc6..e5e32116f7 100644 --- a/webrtc/modules/utility/source/jvm_android.cc +++ b/webrtc/modules/utility/source/jvm_android.cc @@ -27,8 +27,10 @@ struct { const char* name; jclass clazz; } loaded_classes[] = { - {"org/webrtc/voiceengine/WebRtcAudioManager", nullptr}, {"org/webrtc/voiceengine/BuildInfo", nullptr}, + {"org/webrtc/voiceengine/WebRtcAudioManager", nullptr}, + {"org/webrtc/voiceengine/WebRtcAudioRecord", nullptr}, + {"org/webrtc/voiceengine/WebRtcAudioTrack", nullptr}, }; // Android's FindClass() is trickier than usual because the app-specific @@ -102,16 +104,27 @@ GlobalRef::~GlobalRef() { jboolean GlobalRef::CallBooleanMethod(jmethodID methodID, ...) { va_list args; va_start(args, methodID); - jboolean res = jni_->CallBooleanMethod(j_object_, methodID, args); + jboolean res = jni_->CallBooleanMethodV(j_object_, methodID, args); CHECK_EXCEPTION(jni_) << "Error during CallBooleanMethod"; + va_end(args); + return res; +} + +jint GlobalRef::CallIntMethod(jmethodID methodID, ...) { + va_list args; + va_start(args, methodID); + jint res = jni_->CallIntMethodV(j_object_, methodID, args); + CHECK_EXCEPTION(jni_) << "Error during CallIntMethod"; + va_end(args); return res; } void GlobalRef::CallVoidMethod(jmethodID methodID, ...) { va_list args; va_start(args, methodID); - jni_->CallVoidMethod(j_object_, methodID, args); + jni_->CallVoidMethodV(j_object_, methodID, args); CHECK_EXCEPTION(jni_) << "Error during CallVoidMethod"; + va_end(args); } // NativeRegistration implementation. diff --git a/webrtc/voice_engine/voice_engine_impl.cc b/webrtc/voice_engine/voice_engine_impl.cc index c626917358..f7f363debe 100644 --- a/webrtc/voice_engine/voice_engine_impl.cc +++ b/webrtc/voice_engine/voice_engine_impl.cc @@ -146,16 +146,6 @@ int VoiceEngine::SetAndroidObjects(void* javaVM, void* context) { #ifdef WEBRTC_ANDROID webrtc::JVM::Initialize(reinterpret_cast(javaVM), reinterpret_cast(context)); - // The Android ADM implementation supports dynamic selection of the audio - // layer in both directions if a default audio layer is selected. Both - // Java-based audio backends are initialized here to ensure that the user - // can switch backend dynamically as well. - typedef AudioDeviceTemplate AudioDevice; - if (javaVM && context) { - AudioDevice::SetAndroidAudioDeviceObjects(javaVM, context); - } else { - AudioDevice::ClearAndroidAudioDeviceObjects(); - } return 0; #else return -1;