
This was only used for logging, except on Mac, where the methods are now private. BUG=3132 R=henrika@webrtc.org Review URL: https://webrtc-codereview.appspot.com/10959004 git-svn-id: http://webrtc.googlecode.com/svn/trunk@5831 4adac7df-926f-26a2-2b94-8c16560cd09d
1398 lines
37 KiB
C++
1398 lines
37 KiB
C++
/*
|
|
* 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
|