blob: f425df0c808afbf06e0360df55b324712f2c2194 [file] [log] [blame]
/*
* 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 <android/log.h>
#include <stdlib.h>
#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*>(javaVM);
globalJNIEnv = reinterpret_cast<JNIEnv*>(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<jclass> (
globalJNIEnv->NewGlobalRef(javaScClassLocal));
if (!globalScClass) {
WEBRTC_TRACE(kTraceError, kTraceAudioDevice, -1,
"%s: could not create reference", __FUNCTION__);
return -1;
}
globalContext = globalJNIEnv->NewGlobalRef(
reinterpret_cast<jobject>(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<jobject>(NULL);
globalJNIEnv->DeleteGlobalRef(globalScClass);
globalScClass = reinterpret_cast<jclass>(NULL);
globalJNIEnv = reinterpret_cast<JNIEnv*>(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<int> (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<uint32_t> (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, "<init>", "()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<uint32_t> (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<AudioTrackJni*> (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