/* * Copyright (c) 2013 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. */ /* * Android audio device implementation (JNI/AudioTrack usage) */ // TODO(xians): Break out attach and detach current thread to JVM to // separate functions. #include "webrtc/modules/audio_device/android/audio_track_jni.h" #include #include #include "webrtc/modules/audio_device/audio_device_config.h" #include "webrtc/modules/audio_device/audio_device_utility.h" #include "webrtc/system_wrappers/interface/event_wrapper.h" #include "webrtc/system_wrappers/interface/thread_wrapper.h" #include "webrtc/system_wrappers/interface/trace.h" namespace webrtc { JavaVM* AudioTrackJni::globalJvm = NULL; JNIEnv* AudioTrackJni::globalJNIEnv = NULL; jobject AudioTrackJni::globalContext = NULL; jclass AudioTrackJni::globalScClass = NULL; int32_t AudioTrackJni::SetAndroidAudioDeviceObjects(void* javaVM, void* env, void* context) { assert(env); globalJvm = reinterpret_cast(javaVM); globalJNIEnv = reinterpret_cast(env); // Get java class type (note path to class packet). jclass javaScClassLocal = globalJNIEnv->FindClass( "org/webrtc/voiceengine/WebRtcAudioTrack"); if (!javaScClassLocal) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, -1, "%s: could not find java class", __FUNCTION__); return -1; // exception thrown } // Create a global reference to the class (to tell JNI that we are // referencing it after this function has returned). globalScClass = reinterpret_cast ( globalJNIEnv->NewGlobalRef(javaScClassLocal)); if (!globalScClass) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, -1, "%s: could not create reference", __FUNCTION__); return -1; } globalContext = globalJNIEnv->NewGlobalRef( reinterpret_cast(context)); if (!globalContext) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, -1, "%s: could not create context reference", __FUNCTION__); return -1; } // Delete local class ref, we only use the global ref globalJNIEnv->DeleteLocalRef(javaScClassLocal); return 0; } void AudioTrackJni::ClearAndroidAudioDeviceObjects() { WEBRTC_TRACE(kTraceStateInfo, kTraceAudioDevice, -1, "%s: env is NULL, assuming deinit", __FUNCTION__); globalJvm = NULL; if (!globalJNIEnv) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, -1, "%s: saved env already NULL", __FUNCTION__); return; } globalJNIEnv->DeleteGlobalRef(globalContext); globalContext = reinterpret_cast(NULL); globalJNIEnv->DeleteGlobalRef(globalScClass); globalScClass = reinterpret_cast(NULL); globalJNIEnv = reinterpret_cast(NULL); } AudioTrackJni::AudioTrackJni(const int32_t id) : _javaVM(NULL), _jniEnvPlay(NULL), _javaScClass(0), _javaScObj(0), _javaPlayBuffer(0), _javaDirectPlayBuffer(NULL), _javaMidPlayAudio(0), _ptrAudioBuffer(NULL), _critSect(*CriticalSectionWrapper::CreateCriticalSection()), _id(id), _initialized(false), _timeEventPlay(*EventWrapper::Create()), _playStartStopEvent(*EventWrapper::Create()), _ptrThreadPlay(NULL), _playThreadID(0), _playThreadIsInitialized(false), _shutdownPlayThread(false), _playoutDeviceIsSpecified(false), _playing(false), _playIsInitialized(false), _speakerIsInitialized(false), _startPlay(false), _playWarning(0), _playError(0), _delayPlayout(0), _samplingFreqOut((N_PLAY_SAMPLES_PER_SEC/1000)), _maxSpeakerVolume(0) { } AudioTrackJni::~AudioTrackJni() { WEBRTC_TRACE(kTraceMemory, kTraceAudioDevice, _id, "%s destroyed", __FUNCTION__); Terminate(); delete &_playStartStopEvent; delete &_timeEventPlay; delete &_critSect; } int32_t AudioTrackJni::Init() { CriticalSectionScoped lock(&_critSect); if (_initialized) { return 0; } _playWarning = 0; _playError = 0; // Init Java member variables // and set up JNI interface to // AudioDeviceAndroid java class if (InitJavaResources() != 0) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "%s: Failed to init Java resources", __FUNCTION__); return -1; } // Check the sample rate to be used for playback and recording // and the max playout volume if (InitSampleRate() != 0) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "%s: Failed to init samplerate", __FUNCTION__); return -1; } const char* threadName = "jni_audio_render_thread"; _ptrThreadPlay = ThreadWrapper::CreateThread(PlayThreadFunc, this, kRealtimePriority, threadName); if (_ptrThreadPlay == NULL) { WEBRTC_TRACE(kTraceCritical, kTraceAudioDevice, _id, " failed to create the play audio thread"); return -1; } unsigned int threadID = 0; if (!_ptrThreadPlay->Start(threadID)) { WEBRTC_TRACE(kTraceCritical, kTraceAudioDevice, _id, " failed to start the play audio thread"); delete _ptrThreadPlay; _ptrThreadPlay = NULL; return -1; } _playThreadID = threadID; _initialized = true; return 0; } int32_t AudioTrackJni::Terminate() { CriticalSectionScoped lock(&_critSect); if (!_initialized) { return 0; } StopPlayout(); _shutdownPlayThread = true; _timeEventPlay.Set(); // Release rec thread from waiting state if (_ptrThreadPlay) { // First, the thread must detach itself from Java VM _critSect.Leave(); if (kEventSignaled != _playStartStopEvent.Wait(5000)) { WEBRTC_TRACE( kTraceError, kTraceAudioDevice, _id, "%s: Playout thread shutdown timed out, cannot " "terminate thread", __FUNCTION__); // If we close thread anyway, the app will crash return -1; } _playStartStopEvent.Reset(); _critSect.Enter(); // Close down play thread ThreadWrapper* tmpThread = _ptrThreadPlay; _ptrThreadPlay = NULL; _critSect.Leave(); tmpThread->SetNotAlive(); _timeEventPlay.Set(); if (tmpThread->Stop()) { delete tmpThread; _jniEnvPlay = NULL; } else { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " failed to close down the play audio thread"); } _critSect.Enter(); _playThreadIsInitialized = false; } _speakerIsInitialized = false; _playoutDeviceIsSpecified = false; // get the JNI env for this thread JNIEnv *env; bool isAttached = false; // get the JNI env for this thread if (_javaVM->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { // try to attach the thread and get the env // Attach this thread to JVM jint res = _javaVM->AttachCurrentThread(&env, NULL); if ((res < 0) || !env) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "%s: Could not attach thread to JVM (%d, %p)", __FUNCTION__, res, env); return -1; } isAttached = true; } // Make method IDs and buffer pointers unusable _javaMidPlayAudio = 0; _javaDirectPlayBuffer = NULL; // Delete the references to the java buffers, this allows the // garbage collector to delete them env->DeleteGlobalRef(_javaPlayBuffer); _javaPlayBuffer = 0; // Delete the references to the java object and class, this allows the // garbage collector to delete them env->DeleteGlobalRef(_javaScObj); _javaScObj = 0; _javaScClass = 0; // Detach this thread if it was attached if (isAttached) { if (_javaVM->DetachCurrentThread() < 0) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, "%s: Could not detach thread from JVM", __FUNCTION__); } } _initialized = false; return 0; } int32_t AudioTrackJni::PlayoutDeviceName(uint16_t index, char name[kAdmMaxDeviceNameSize], char guid[kAdmMaxGuidSize]) { if (0 != index) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, " Device index is out of range [0,0]"); return -1; } // Return empty string memset(name, 0, kAdmMaxDeviceNameSize); if (guid) { memset(guid, 0, kAdmMaxGuidSize); } return 0; } int32_t AudioTrackJni::SetPlayoutDevice(uint16_t index) { if (_playIsInitialized) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, " Playout already initialized"); return -1; } if (0 != index) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, " Device index is out of range [0,0]"); return -1; } // Do nothing but set a flag, this is to have consistent behavior // with other platforms _playoutDeviceIsSpecified = true; return 0; } int32_t AudioTrackJni::SetPlayoutDevice( AudioDeviceModule::WindowsDeviceType device) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " API call not supported on this platform"); return -1; } int32_t AudioTrackJni::PlayoutIsAvailable(bool& available) { // NOLINT available = false; // Try to initialize the playout side int32_t res = InitPlayout(); // Cancel effect of initialization StopPlayout(); if (res != -1) { available = true; } return res; } int32_t AudioTrackJni::InitPlayout() { CriticalSectionScoped lock(&_critSect); if (!_initialized) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, " Not initialized"); return -1; } if (_playing) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " Playout already started"); return -1; } if (!_playoutDeviceIsSpecified) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, " Playout device is not specified"); return -1; } if (_playIsInitialized) { WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, " Playout already initialized"); return 0; } // Initialize the speaker if (InitSpeaker() == -1) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " InitSpeaker() failed"); } // get the JNI env for this thread JNIEnv *env; bool isAttached = false; // get the JNI env for this thread if (_javaVM->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, "attaching"); // try to attach the thread and get the env // Attach this thread to JVM jint res = _javaVM->AttachCurrentThread(&env, NULL); if ((res < 0) || !env) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, " Could not attach thread to JVM (%d, %p)", res, env); return -1; } isAttached = true; } // get the method ID jmethodID initPlaybackID = env->GetMethodID(_javaScClass, "InitPlayback", "(I)I"); int samplingFreq = 44100; if (_samplingFreqOut != 44) { samplingFreq = _samplingFreqOut * 1000; } int retVal = -1; // Call java sc object method jint res = env->CallIntMethod(_javaScObj, initPlaybackID, samplingFreq); if (res < 0) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "InitPlayback failed (%d)", res); } else { // Set the audio device buffer sampling rate _ptrAudioBuffer->SetPlayoutSampleRate(_samplingFreqOut * 1000); _playIsInitialized = true; retVal = 0; } // Detach this thread if it was attached if (isAttached) { WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, "detaching"); if (_javaVM->DetachCurrentThread() < 0) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " Could not detach thread from JVM"); } } return retVal; } int32_t AudioTrackJni::StartPlayout() { CriticalSectionScoped lock(&_critSect); if (!_playIsInitialized) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, " Playout not initialized"); return -1; } if (_playing) { WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, " Playout already started"); return 0; } // get the JNI env for this thread JNIEnv *env; bool isAttached = false; // get the JNI env for this thread if (_javaVM->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { // try to attach the thread and get the env // Attach this thread to JVM jint res = _javaVM->AttachCurrentThread(&env, NULL); if ((res < 0) || !env) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, " Could not attach thread to JVM (%d, %p)", res, env); return -1; } isAttached = true; } // get the method ID jmethodID startPlaybackID = env->GetMethodID(_javaScClass, "StartPlayback", "()I"); // Call java sc object method jint res = env->CallIntMethod(_javaScObj, startPlaybackID); if (res < 0) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "StartPlayback failed (%d)", res); return -1; } _playWarning = 0; _playError = 0; // Signal to playout thread that we want to start _startPlay = true; _timeEventPlay.Set(); // Release thread from waiting state _critSect.Leave(); // Wait for thread to init if (kEventSignaled != _playStartStopEvent.Wait(5000)) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, " Timeout or error starting"); } _playStartStopEvent.Reset(); _critSect.Enter(); // Detach this thread if it was attached if (isAttached) { if (_javaVM->DetachCurrentThread() < 0) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " Could not detach thread from JVM"); } } return 0; } int32_t AudioTrackJni::StopPlayout() { CriticalSectionScoped lock(&_critSect); if (!_playIsInitialized) { WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, " Playout is not initialized"); return 0; } // get the JNI env for this thread JNIEnv *env; bool isAttached = false; // get the JNI env for this thread if (_javaVM->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { // try to attach the thread and get the env // Attach this thread to JVM jint res = _javaVM->AttachCurrentThread(&env, NULL); if ((res < 0) || !env) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, " Could not attach thread to JVM (%d, %p)", res, env); return -1; } isAttached = true; } // get the method ID jmethodID stopPlaybackID = env->GetMethodID(_javaScClass, "StopPlayback", "()I"); // Call java sc object method jint res = env->CallIntMethod(_javaScObj, stopPlaybackID); if (res < 0) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "StopPlayback failed (%d)", res); } _playIsInitialized = false; _playing = false; _playWarning = 0; _playError = 0; // Detach this thread if it was attached if (isAttached) { if (_javaVM->DetachCurrentThread() < 0) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " Could not detach thread from JVM"); } } return 0; } int32_t AudioTrackJni::InitSpeaker() { CriticalSectionScoped lock(&_critSect); if (_playing) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " Playout already started"); return -1; } if (!_playoutDeviceIsSpecified) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, " Playout device is not specified"); return -1; } // Nothing needs to be done here, we use a flag to have consistent // behavior with other platforms _speakerIsInitialized = true; return 0; } int32_t AudioTrackJni::SpeakerVolumeIsAvailable(bool& available) { // NOLINT available = true; // We assume we are always be able to set/get volume return 0; } int32_t AudioTrackJni::SetSpeakerVolume(uint32_t volume) { if (!_speakerIsInitialized) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, " Speaker not initialized"); return -1; } if (!globalContext) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, " Context is not set"); return -1; } // get the JNI env for this thread JNIEnv *env; bool isAttached = false; if (_javaVM->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { // try to attach the thread and get the env // Attach this thread to JVM jint res = _javaVM->AttachCurrentThread(&env, NULL); if ((res < 0) || !env) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, " Could not attach thread to JVM (%d, %p)", res, env); return -1; } isAttached = true; } // get the method ID jmethodID setPlayoutVolumeID = env->GetMethodID(_javaScClass, "SetPlayoutVolume", "(I)I"); // call java sc object method jint res = env->CallIntMethod(_javaScObj, setPlayoutVolumeID, static_cast (volume)); if (res < 0) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "SetPlayoutVolume failed (%d)", res); return -1; } // Detach this thread if it was attached if (isAttached) { if (_javaVM->DetachCurrentThread() < 0) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " Could not detach thread from JVM"); } } return 0; } int32_t AudioTrackJni::SpeakerVolume(uint32_t& volume) const { // NOLINT if (!_speakerIsInitialized) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, " Speaker not initialized"); return -1; } if (!globalContext) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, " Context is not set"); return -1; } // get the JNI env for this thread JNIEnv *env; bool isAttached = false; if (_javaVM->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { // try to attach the thread and get the env // Attach this thread to JVM jint res = _javaVM->AttachCurrentThread(&env, NULL); if ((res < 0) || !env) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, " Could not attach thread to JVM (%d, %p)", res, env); return -1; } isAttached = true; } // get the method ID jmethodID getPlayoutVolumeID = env->GetMethodID(_javaScClass, "GetPlayoutVolume", "()I"); // call java sc object method jint level = env->CallIntMethod(_javaScObj, getPlayoutVolumeID); if (level < 0) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "GetPlayoutVolume failed (%d)", level); return -1; } // Detach this thread if it was attached if (isAttached) { if (_javaVM->DetachCurrentThread() < 0) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " Could not detach thread from JVM"); } } volume = static_cast (level); return 0; } int32_t AudioTrackJni::MaxSpeakerVolume(uint32_t& maxVolume) const { // NOLINT if (!_speakerIsInitialized) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, " Speaker not initialized"); return -1; } maxVolume = _maxSpeakerVolume; return 0; } int32_t AudioTrackJni::MinSpeakerVolume(uint32_t& minVolume) const { // NOLINT if (!_speakerIsInitialized) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, " Speaker not initialized"); return -1; } minVolume = 0; return 0; } int32_t AudioTrackJni::SpeakerVolumeStepSize( uint16_t& stepSize) const { // NOLINT if (!_speakerIsInitialized) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, " Speaker not initialized"); return -1; } stepSize = 1; return 0; } int32_t AudioTrackJni::SpeakerMuteIsAvailable(bool& available) { // NOLINT available = false; // Speaker mute not supported on Android return 0; } int32_t AudioTrackJni::SetSpeakerMute(bool enable) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " API call not supported on this platform"); return -1; } int32_t AudioTrackJni::SpeakerMute(bool& /*enabled*/) const { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " API call not supported on this platform"); return -1; } int32_t AudioTrackJni::StereoPlayoutIsAvailable(bool& available) { // NOLINT available = false; // Stereo playout not supported on Android return 0; } int32_t AudioTrackJni::SetStereoPlayout(bool enable) { if (enable) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, " Enabling not available"); return -1; } return 0; } int32_t AudioTrackJni::StereoPlayout(bool& enabled) const { // NOLINT enabled = false; return 0; } int32_t AudioTrackJni::SetPlayoutBuffer( const AudioDeviceModule::BufferType type, uint16_t sizeMS) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " API call not supported on this platform"); return -1; } int32_t AudioTrackJni::PlayoutBuffer( AudioDeviceModule::BufferType& type, // NOLINT uint16_t& sizeMS) const { // NOLINT type = AudioDeviceModule::kAdaptiveBufferSize; sizeMS = _delayPlayout; // Set to current playout delay return 0; } int32_t AudioTrackJni::PlayoutDelay(uint16_t& delayMS) const { // NOLINT delayMS = _delayPlayout; return 0; } void AudioTrackJni::AttachAudioBuffer(AudioDeviceBuffer* audioBuffer) { CriticalSectionScoped lock(&_critSect); _ptrAudioBuffer = audioBuffer; // inform the AudioBuffer about default settings for this implementation _ptrAudioBuffer->SetPlayoutSampleRate(N_PLAY_SAMPLES_PER_SEC); _ptrAudioBuffer->SetPlayoutChannels(N_PLAY_CHANNELS); } int32_t AudioTrackJni::SetPlayoutSampleRate(const uint32_t samplesPerSec) { if (samplesPerSec > 48000 || samplesPerSec < 8000) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, " Invalid sample rate"); return -1; } // set the playout sample rate to use if (samplesPerSec == 44100) { _samplingFreqOut = 44; } else { _samplingFreqOut = samplesPerSec / 1000; } // Update the AudioDeviceBuffer _ptrAudioBuffer->SetPlayoutSampleRate(samplesPerSec); return 0; } bool AudioTrackJni::PlayoutWarning() const { return (_playWarning > 0); } bool AudioTrackJni::PlayoutError() const { return (_playError > 0); } void AudioTrackJni::ClearPlayoutWarning() { _playWarning = 0; } void AudioTrackJni::ClearPlayoutError() { _playError = 0; } int32_t AudioTrackJni::SetLoudspeakerStatus(bool enable) { if (!globalContext) { WEBRTC_TRACE(kTraceError, kTraceUtility, -1, " Context is not set"); return -1; } // get the JNI env for this thread JNIEnv *env; bool isAttached = false; if (_javaVM->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { // try to attach the thread and get the env // Attach this thread to JVM jint res = _javaVM->AttachCurrentThread(&env, NULL); // Get the JNI env for this thread if ((res < 0) || !env) { WEBRTC_TRACE(kTraceError, kTraceUtility, -1, " Could not attach thread to JVM (%d, %p)", res, env); return -1; } isAttached = true; } // get the method ID jmethodID setPlayoutSpeakerID = env->GetMethodID(_javaScClass, "SetPlayoutSpeaker", "(Z)I"); // call java sc object method jint res = env->CallIntMethod(_javaScObj, setPlayoutSpeakerID, enable); if (res < 0) { WEBRTC_TRACE(kTraceError, kTraceUtility, -1, " SetPlayoutSpeaker failed (%d)", res); return -1; } _loudSpeakerOn = enable; // Detach this thread if it was attached if (isAttached) { if (_javaVM->DetachCurrentThread() < 0) { WEBRTC_TRACE(kTraceWarning, kTraceUtility, -1, " Could not detach thread from JVM"); } } return 0; } int32_t AudioTrackJni::GetLoudspeakerStatus(bool& enabled) const { // NOLINT enabled = _loudSpeakerOn; return 0; } int32_t AudioTrackJni::InitJavaResources() { // todo: Check if we already have created the java object _javaVM = globalJvm; _javaScClass = globalScClass; // use the jvm that has been set if (!_javaVM) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "%s: Not a valid Java VM pointer", __FUNCTION__); return -1; } // get the JNI env for this thread JNIEnv *env; bool isAttached = false; // get the JNI env for this thread if (_javaVM->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { // try to attach the thread and get the env // Attach this thread to JVM jint res = _javaVM->AttachCurrentThread(&env, NULL); if ((res < 0) || !env) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "%s: Could not attach thread to JVM (%d, %p)", __FUNCTION__, res, env); return -1; } isAttached = true; } WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, "get method id"); // get the method ID for the void(void) constructor jmethodID cid = env->GetMethodID(_javaScClass, "", "()V"); if (cid == NULL) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "%s: could not get constructor ID", __FUNCTION__); return -1; /* exception thrown */ } WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, "construct object", __FUNCTION__); // construct the object jobject javaScObjLocal = env->NewObject(_javaScClass, cid); if (!javaScObjLocal) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, "%s: could not create Java sc object", __FUNCTION__); return -1; } // Create a reference to the object (to tell JNI that we are referencing it // after this function has returned). _javaScObj = env->NewGlobalRef(javaScObjLocal); if (!_javaScObj) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "%s: could not create Java sc object reference", __FUNCTION__); return -1; } // Delete local object ref, we only use the global ref. env->DeleteLocalRef(javaScObjLocal); ////////////////////// // AUDIO MANAGEMENT // This is not mandatory functionality if (globalContext) { jfieldID context_id = env->GetFieldID(globalScClass, "_context", "Landroid/content/Context;"); if (!context_id) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "%s: could not get _context id", __FUNCTION__); return -1; } env->SetObjectField(_javaScObj, context_id, globalContext); jobject javaContext = env->GetObjectField(_javaScObj, context_id); if (!javaContext) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "%s: could not set or get _context", __FUNCTION__); return -1; } } else { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, "%s: did not set Context - some functionality is not " "supported", __FUNCTION__); } ///////////// // PLAYOUT // Get play buffer field ID. jfieldID fidPlayBuffer = env->GetFieldID(_javaScClass, "_playBuffer", "Ljava/nio/ByteBuffer;"); if (!fidPlayBuffer) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "%s: could not get play buffer fid", __FUNCTION__); return -1; } // Get play buffer object. jobject javaPlayBufferLocal = env->GetObjectField(_javaScObj, fidPlayBuffer); if (!javaPlayBufferLocal) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "%s: could not get play buffer", __FUNCTION__); return -1; } // Create a global reference to the object (to tell JNI that we are // referencing it after this function has returned) // NOTE: we are referencing it only through the direct buffer (see below). _javaPlayBuffer = env->NewGlobalRef(javaPlayBufferLocal); if (!_javaPlayBuffer) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "%s: could not get play buffer reference", __FUNCTION__); return -1; } // Delete local object ref, we only use the global ref. env->DeleteLocalRef(javaPlayBufferLocal); // Get direct buffer. _javaDirectPlayBuffer = env->GetDirectBufferAddress(_javaPlayBuffer); if (!_javaDirectPlayBuffer) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "%s: could not get direct play buffer", __FUNCTION__); return -1; } // Get the play audio method ID. _javaMidPlayAudio = env->GetMethodID(_javaScClass, "PlayAudio", "(I)I"); if (!_javaMidPlayAudio) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "%s: could not get play audio mid", __FUNCTION__); return -1; } // Detach this thread if it was attached. if (isAttached) { if (_javaVM->DetachCurrentThread() < 0) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, "%s: Could not detach thread from JVM", __FUNCTION__); } } return 0; } int32_t AudioTrackJni::InitSampleRate() { int samplingFreq = 44100; jint res = 0; // get the JNI env for this thread JNIEnv *env; bool isAttached = false; // get the JNI env for this thread if (_javaVM->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { // try to attach the thread and get the env // Attach this thread to JVM jint res = _javaVM->AttachCurrentThread(&env, NULL); if ((res < 0) || !env) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "%s: Could not attach thread to JVM (%d, %p)", __FUNCTION__, res, env); return -1; } isAttached = true; } // get the method ID jmethodID initPlaybackID = env->GetMethodID(_javaScClass, "InitPlayback", "(I)I"); if (_samplingFreqOut > 0) { // read the configured sampling rate samplingFreq = 44100; if (_samplingFreqOut != 44) { samplingFreq = _samplingFreqOut * 1000; } WEBRTC_TRACE(kTraceStateInfo, kTraceAudioDevice, _id, " Trying configured playback sampling rate %d", samplingFreq); } else { // set the preferred sampling frequency if (samplingFreq == 8000) { // try 16000 samplingFreq = 16000; } // else use same as recording } bool keepTrying = true; while (keepTrying) { // call java sc object method res = env->CallIntMethod(_javaScObj, initPlaybackID, samplingFreq); if (res < 0) { switch (samplingFreq) { case 44100: samplingFreq = 16000; break; case 16000: samplingFreq = 8000; break; default: // error WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "InitPlayback failed (%d)", res); return -1; } } else { keepTrying = false; } } // Store max playout volume _maxSpeakerVolume = static_cast (res); if (_maxSpeakerVolume < 1) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " Did not get valid max speaker volume value (%d)", _maxSpeakerVolume); } // set the playback sample rate to use if (samplingFreq == 44100) { _samplingFreqOut = 44; } else { _samplingFreqOut = samplingFreq / 1000; } WEBRTC_TRACE(kTraceStateInfo, kTraceAudioDevice, _id, "Playback sample rate set to (%d)", _samplingFreqOut); // get the method ID jmethodID stopPlaybackID = env->GetMethodID(_javaScClass, "StopPlayback", "()I"); // Call java sc object method res = env->CallIntMethod(_javaScObj, stopPlaybackID); if (res < 0) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "StopPlayback failed (%d)", res); } // Detach this thread if it was attached if (isAttached) { if (_javaVM->DetachCurrentThread() < 0) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, "%s: Could not detach thread from JVM", __FUNCTION__); } } return 0; } bool AudioTrackJni::PlayThreadFunc(void* pThis) { return (static_cast (pThis)->PlayThreadProcess()); } bool AudioTrackJni::PlayThreadProcess() { if (!_playThreadIsInitialized) { // Do once when thread is started // Attach this thread to JVM and get the JNI env for this thread jint res = _javaVM->AttachCurrentThread(&_jniEnvPlay, NULL); if ((res < 0) || !_jniEnvPlay) { WEBRTC_TRACE(kTraceCritical, kTraceAudioDevice, _id, "Could not attach playout thread to JVM (%d, %p)", res, _jniEnvPlay); return false; // Close down thread } _playThreadIsInitialized = true; } if (!_playing) { switch (_timeEventPlay.Wait(1000)) { case kEventSignaled: WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, "Playout thread event signal"); _timeEventPlay.Reset(); break; case kEventError: WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, "Playout thread event error"); return true; case kEventTimeout: WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, "Playout thread event timeout"); return true; } } Lock(); if (_startPlay) { WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "_startPlay true, performing initial actions"); _startPlay = false; _playing = true; _playWarning = 0; _playError = 0; _playStartStopEvent.Set(); WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, "Sent signal"); } if (_playing) { int8_t playBuffer[2 * 480]; // Max 10 ms @ 48 kHz / 16 bit uint32_t samplesToPlay = _samplingFreqOut * 10; // ask for new PCM data to be played out using the AudioDeviceBuffer // ensure that this callback is executed without taking the // audio-thread lock UnLock(); uint32_t nSamples = _ptrAudioBuffer->RequestPlayoutData(samplesToPlay); Lock(); // Check again since play may have stopped during unlocked period if (!_playing) { UnLock(); return true; } nSamples = _ptrAudioBuffer->GetPlayoutData(playBuffer); if (nSamples != samplesToPlay) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, " invalid number of output samples(%d)", nSamples); _playWarning = 1; } // Copy data to our direct buffer (held by java sc object) // todo: Give _javaDirectPlayBuffer directly to VoE? memcpy(_javaDirectPlayBuffer, playBuffer, nSamples * 2); UnLock(); // Call java sc object method to process data in direct buffer // Will block until data has been put in OS playout buffer // (see java sc class) jint res = _jniEnvPlay->CallIntMethod(_javaScObj, _javaMidPlayAudio, 2 * nSamples); if (res < 0) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "PlayAudio failed (%d)", res); _playWarning = 1; } else if (res > 0) { // we are not recording and have got a delay value from playback _delayPlayout = res / _samplingFreqOut; } Lock(); } // _playing if (_shutdownPlayThread) { WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, "Detaching thread from Java VM"); // Detach thread from Java VM if (_javaVM->DetachCurrentThread() < 0) { WEBRTC_TRACE(kTraceCritical, kTraceAudioDevice, _id, "Could not detach playout thread from JVM"); _shutdownPlayThread = false; // If we say OK (i.e. set event) and close thread anyway, // app will crash } else { _jniEnvPlay = NULL; _shutdownPlayThread = false; _playStartStopEvent.Set(); // Signal to Terminate() that we are done WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, "Sent signal"); } } UnLock(); return true; } } // namespace webrtc