1/* 2 * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. 3 * 4 * Use of this source code is governed by a BSD-style license 5 * that can be found in the LICENSE file in the root of the source 6 * tree. An additional intellectual property rights grant can be found 7 * in the file PATENTS. All contributing project authors may 8 * be found in the AUTHORS file in the root of the source tree. 9 */ 10 11/* 12 * Android audio device implementation (JNI/AudioTrack usage) 13 */ 14 15// TODO(xians): Break out attach and detach current thread to JVM to 16// separate functions. 17 18#include "webrtc/modules/audio_device/android/audio_track_jni.h" 19 20#include <android/log.h> 21#include <stdlib.h> 22 23#include "webrtc/modules/audio_device/audio_device_config.h" 24#include "webrtc/modules/audio_device/audio_device_utility.h" 25 26#include "webrtc/system_wrappers/interface/event_wrapper.h" 27#include "webrtc/system_wrappers/interface/thread_wrapper.h" 28#include "webrtc/system_wrappers/interface/trace.h" 29 30namespace webrtc { 31 32JavaVM* AudioTrackJni::globalJvm = NULL; 33JNIEnv* AudioTrackJni::globalJNIEnv = NULL; 34jobject AudioTrackJni::globalContext = NULL; 35jclass AudioTrackJni::globalScClass = NULL; 36 37int32_t AudioTrackJni::SetAndroidAudioDeviceObjects(void* javaVM, void* env, 38 void* context) { 39 assert(env); 40 globalJvm = reinterpret_cast<JavaVM*>(javaVM); 41 globalJNIEnv = reinterpret_cast<JNIEnv*>(env); 42 // Get java class type (note path to class packet). 43 jclass javaScClassLocal = globalJNIEnv->FindClass( 44 "org/webrtc/voiceengine/WebRtcAudioTrack"); 45 if (!javaScClassLocal) { 46 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, -1, 47 "%s: could not find java class", __FUNCTION__); 48 return -1; // exception thrown 49 } 50 51 // Create a global reference to the class (to tell JNI that we are 52 // referencing it after this function has returned). 53 globalScClass = reinterpret_cast<jclass> ( 54 globalJNIEnv->NewGlobalRef(javaScClassLocal)); 55 if (!globalScClass) { 56 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, -1, 57 "%s: could not create reference", __FUNCTION__); 58 return -1; 59 } 60 61 globalContext = globalJNIEnv->NewGlobalRef( 62 reinterpret_cast<jobject>(context)); 63 if (!globalContext) { 64 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, -1, 65 "%s: could not create context reference", __FUNCTION__); 66 return -1; 67 } 68 69 // Delete local class ref, we only use the global ref 70 globalJNIEnv->DeleteLocalRef(javaScClassLocal); 71 return 0; 72} 73 74void AudioTrackJni::ClearAndroidAudioDeviceObjects() { 75 WEBRTC_TRACE(kTraceStateInfo, kTraceAudioDevice, -1, 76 "%s: env is NULL, assuming deinit", __FUNCTION__); 77 78 globalJvm = NULL; 79 if (!globalJNIEnv) { 80 WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, -1, 81 "%s: saved env already NULL", __FUNCTION__); 82 return; 83 } 84 85 globalJNIEnv->DeleteGlobalRef(globalContext); 86 globalContext = reinterpret_cast<jobject>(NULL); 87 88 globalJNIEnv->DeleteGlobalRef(globalScClass); 89 globalScClass = reinterpret_cast<jclass>(NULL); 90 91 globalJNIEnv = reinterpret_cast<JNIEnv*>(NULL); 92} 93 94AudioTrackJni::AudioTrackJni(const int32_t id) 95 : _javaVM(NULL), 96 _jniEnvPlay(NULL), 97 _javaScClass(0), 98 _javaScObj(0), 99 _javaPlayBuffer(0), 100 _javaDirectPlayBuffer(NULL), 101 _javaMidPlayAudio(0), 102 _ptrAudioBuffer(NULL), 103 _critSect(*CriticalSectionWrapper::CreateCriticalSection()), 104 _id(id), 105 _initialized(false), 106 _timeEventPlay(*EventWrapper::Create()), 107 _playStartStopEvent(*EventWrapper::Create()), 108 _ptrThreadPlay(NULL), 109 _playThreadID(0), 110 _playThreadIsInitialized(false), 111 _shutdownPlayThread(false), 112 _playoutDeviceIsSpecified(false), 113 _playing(false), 114 _playIsInitialized(false), 115 _speakerIsInitialized(false), 116 _startPlay(false), 117 _playWarning(0), 118 _playError(0), 119 _delayPlayout(0), 120 _samplingFreqOut((N_PLAY_SAMPLES_PER_SEC/1000)), 121 _maxSpeakerVolume(0) { 122} 123 124AudioTrackJni::~AudioTrackJni() { 125 WEBRTC_TRACE(kTraceMemory, kTraceAudioDevice, _id, 126 "%s destroyed", __FUNCTION__); 127 128 Terminate(); 129 130 delete &_playStartStopEvent; 131 delete &_timeEventPlay; 132 delete &_critSect; 133} 134 135int32_t AudioTrackJni::Init() { 136 CriticalSectionScoped lock(&_critSect); 137 if (_initialized) 138 { 139 return 0; 140 } 141 142 _playWarning = 0; 143 _playError = 0; 144 145 // Init Java member variables 146 // and set up JNI interface to 147 // AudioDeviceAndroid java class 148 if (InitJavaResources() != 0) 149 { 150 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, 151 "%s: Failed to init Java resources", __FUNCTION__); 152 return -1; 153 } 154 155 // Check the sample rate to be used for playback and recording 156 // and the max playout volume 157 if (InitSampleRate() != 0) 158 { 159 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, 160 "%s: Failed to init samplerate", __FUNCTION__); 161 return -1; 162 } 163 164 const char* threadName = "jni_audio_render_thread"; 165 _ptrThreadPlay = ThreadWrapper::CreateThread(PlayThreadFunc, this, 166 kRealtimePriority, threadName); 167 if (_ptrThreadPlay == NULL) 168 { 169 WEBRTC_TRACE(kTraceCritical, kTraceAudioDevice, _id, 170 " failed to create the play audio thread"); 171 return -1; 172 } 173 174 unsigned int threadID = 0; 175 if (!_ptrThreadPlay->Start(threadID)) 176 { 177 WEBRTC_TRACE(kTraceCritical, kTraceAudioDevice, _id, 178 " failed to start the play audio thread"); 179 delete _ptrThreadPlay; 180 _ptrThreadPlay = NULL; 181 return -1; 182 } 183 _playThreadID = threadID; 184 185 _initialized = true; 186 187 return 0; 188} 189 190int32_t AudioTrackJni::Terminate() { 191 CriticalSectionScoped lock(&_critSect); 192 if (!_initialized) 193 { 194 return 0; 195 } 196 197 StopPlayout(); 198 _shutdownPlayThread = true; 199 _timeEventPlay.Set(); // Release rec thread from waiting state 200 if (_ptrThreadPlay) 201 { 202 // First, the thread must detach itself from Java VM 203 _critSect.Leave(); 204 if (kEventSignaled != _playStartStopEvent.Wait(5000)) 205 { 206 WEBRTC_TRACE( 207 kTraceError, 208 kTraceAudioDevice, 209 _id, 210 "%s: Playout thread shutdown timed out, cannot " 211 "terminate thread", 212 __FUNCTION__); 213 // If we close thread anyway, the app will crash 214 return -1; 215 } 216 _playStartStopEvent.Reset(); 217 _critSect.Enter(); 218 219 // Close down play thread 220 ThreadWrapper* tmpThread = _ptrThreadPlay; 221 _ptrThreadPlay = NULL; 222 _critSect.Leave(); 223 tmpThread->SetNotAlive(); 224 _timeEventPlay.Set(); 225 if (tmpThread->Stop()) 226 { 227 delete tmpThread; 228 _jniEnvPlay = NULL; 229 } 230 else 231 { 232 WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, 233 " failed to close down the play audio thread"); 234 } 235 _critSect.Enter(); 236 237 _playThreadIsInitialized = false; 238 } 239 _speakerIsInitialized = false; 240 _playoutDeviceIsSpecified = false; 241 242 // get the JNI env for this thread 243 JNIEnv *env; 244 bool isAttached = false; 245 246 // get the JNI env for this thread 247 if (_javaVM->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) 248 { 249 // try to attach the thread and get the env 250 // Attach this thread to JVM 251 jint res = _javaVM->AttachCurrentThread(&env, NULL); 252 if ((res < 0) || !env) 253 { 254 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, 255 "%s: Could not attach thread to JVM (%d, %p)", 256 __FUNCTION__, res, env); 257 return -1; 258 } 259 isAttached = true; 260 } 261 262 // Make method IDs and buffer pointers unusable 263 _javaMidPlayAudio = 0; 264 _javaDirectPlayBuffer = NULL; 265 266 // Delete the references to the java buffers, this allows the 267 // garbage collector to delete them 268 env->DeleteGlobalRef(_javaPlayBuffer); 269 _javaPlayBuffer = 0; 270 271 // Delete the references to the java object and class, this allows the 272 // garbage collector to delete them 273 env->DeleteGlobalRef(_javaScObj); 274 _javaScObj = 0; 275 _javaScClass = 0; 276 277 // Detach this thread if it was attached 278 if (isAttached) 279 { 280 if (_javaVM->DetachCurrentThread() < 0) 281 { 282 WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, 283 "%s: Could not detach thread from JVM", __FUNCTION__); 284 } 285 } 286 287 _initialized = false; 288 289 return 0; 290} 291 292int32_t AudioTrackJni::PlayoutDeviceName(uint16_t index, 293 char name[kAdmMaxDeviceNameSize], 294 char guid[kAdmMaxGuidSize]) { 295 if (0 != index) 296 { 297 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, 298 " Device index is out of range [0,0]"); 299 return -1; 300 } 301 302 // Return empty string 303 memset(name, 0, kAdmMaxDeviceNameSize); 304 305 if (guid) 306 { 307 memset(guid, 0, kAdmMaxGuidSize); 308 } 309 310 return 0; 311} 312 313int32_t AudioTrackJni::SetPlayoutDevice(uint16_t index) { 314 if (_playIsInitialized) 315 { 316 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, 317 " Playout already initialized"); 318 return -1; 319 } 320 321 if (0 != index) 322 { 323 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, 324 " Device index is out of range [0,0]"); 325 return -1; 326 } 327 328 // Do nothing but set a flag, this is to have consistent behavior 329 // with other platforms 330 _playoutDeviceIsSpecified = true; 331 332 return 0; 333} 334 335int32_t AudioTrackJni::SetPlayoutDevice( 336 AudioDeviceModule::WindowsDeviceType device) { 337 WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, 338 " API call not supported on this platform"); 339 return -1; 340} 341 342 343int32_t AudioTrackJni::PlayoutIsAvailable(bool& available) { // NOLINT 344 available = false; 345 346 // Try to initialize the playout side 347 int32_t res = InitPlayout(); 348 349 // Cancel effect of initialization 350 StopPlayout(); 351 352 if (res != -1) 353 { 354 available = true; 355 } 356 357 return res; 358} 359 360int32_t AudioTrackJni::InitPlayout() { 361 CriticalSectionScoped lock(&_critSect); 362 363 if (!_initialized) 364 { 365 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, 366 " Not initialized"); 367 return -1; 368 } 369 370 if (_playing) 371 { 372 WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, 373 " Playout already started"); 374 return -1; 375 } 376 377 if (!_playoutDeviceIsSpecified) 378 { 379 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, 380 " Playout device is not specified"); 381 return -1; 382 } 383 384 if (_playIsInitialized) 385 { 386 WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, 387 " Playout already initialized"); 388 return 0; 389 } 390 391 // Initialize the speaker 392 if (InitSpeaker() == -1) 393 { 394 WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, 395 " InitSpeaker() failed"); 396 } 397 398 // get the JNI env for this thread 399 JNIEnv *env; 400 bool isAttached = false; 401 402 // get the JNI env for this thread 403 if (_javaVM->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) 404 { 405 WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, 406 "attaching"); 407 408 // try to attach the thread and get the env 409 // Attach this thread to JVM 410 jint res = _javaVM->AttachCurrentThread(&env, NULL); 411 if ((res < 0) || !env) 412 { 413 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, 414 " Could not attach thread to JVM (%d, %p)", res, env); 415 return -1; 416 } 417 isAttached = true; 418 } 419 420 // get the method ID 421 jmethodID initPlaybackID = env->GetMethodID(_javaScClass, "InitPlayback", 422 "(I)I"); 423 424 int samplingFreq = 44100; 425 if (_samplingFreqOut != 44) 426 { 427 samplingFreq = _samplingFreqOut * 1000; 428 } 429 430 int retVal = -1; 431 432 // Call java sc object method 433 jint res = env->CallIntMethod(_javaScObj, initPlaybackID, samplingFreq); 434 if (res < 0) 435 { 436 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, 437 "InitPlayback failed (%d)", res); 438 } 439 else 440 { 441 // Set the audio device buffer sampling rate 442 _ptrAudioBuffer->SetPlayoutSampleRate(_samplingFreqOut * 1000); 443 _playIsInitialized = true; 444 retVal = 0; 445 } 446 447 // Detach this thread if it was attached 448 if (isAttached) 449 { 450 WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, 451 "detaching"); 452 if (_javaVM->DetachCurrentThread() < 0) 453 { 454 WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, 455 " Could not detach thread from JVM"); 456 } 457 } 458 459 return retVal; 460} 461 462int32_t AudioTrackJni::StartPlayout() { 463 CriticalSectionScoped lock(&_critSect); 464 465 if (!_playIsInitialized) 466 { 467 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, 468 " Playout not initialized"); 469 return -1; 470 } 471 472 if (_playing) 473 { 474 WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, 475 " Playout already started"); 476 return 0; 477 } 478 479 // get the JNI env for this thread 480 JNIEnv *env; 481 bool isAttached = false; 482 483 // get the JNI env for this thread 484 if (_javaVM->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) 485 { 486 // try to attach the thread and get the env 487 // Attach this thread to JVM 488 jint res = _javaVM->AttachCurrentThread(&env, NULL); 489 if ((res < 0) || !env) 490 { 491 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, 492 " Could not attach thread to JVM (%d, %p)", res, env); 493 return -1; 494 } 495 isAttached = true; 496 } 497 498 // get the method ID 499 jmethodID startPlaybackID = env->GetMethodID(_javaScClass, "StartPlayback", 500 "()I"); 501 502 // Call java sc object method 503 jint res = env->CallIntMethod(_javaScObj, startPlaybackID); 504 if (res < 0) 505 { 506 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, 507 "StartPlayback failed (%d)", res); 508 return -1; 509 } 510 511 _playWarning = 0; 512 _playError = 0; 513 514 // Signal to playout thread that we want to start 515 _startPlay = true; 516 _timeEventPlay.Set(); // Release thread from waiting state 517 _critSect.Leave(); 518 // Wait for thread to init 519 if (kEventSignaled != _playStartStopEvent.Wait(5000)) 520 { 521 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, 522 " Timeout or error starting"); 523 } 524 _playStartStopEvent.Reset(); 525 _critSect.Enter(); 526 527 // Detach this thread if it was attached 528 if (isAttached) 529 { 530 if (_javaVM->DetachCurrentThread() < 0) 531 { 532 WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, 533 " Could not detach thread from JVM"); 534 } 535 } 536 537 return 0; 538} 539 540int32_t AudioTrackJni::StopPlayout() { 541 CriticalSectionScoped lock(&_critSect); 542 543 if (!_playIsInitialized) 544 { 545 WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, 546 " Playout is not initialized"); 547 return 0; 548 } 549 550 // get the JNI env for this thread 551 JNIEnv *env; 552 bool isAttached = false; 553 554 // get the JNI env for this thread 555 if (_javaVM->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) 556 { 557 // try to attach the thread and get the env 558 // Attach this thread to JVM 559 jint res = _javaVM->AttachCurrentThread(&env, NULL); 560 if ((res < 0) || !env) 561 { 562 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, 563 " Could not attach thread to JVM (%d, %p)", res, env); 564 return -1; 565 } 566 isAttached = true; 567 } 568 569 // get the method ID 570 jmethodID stopPlaybackID = env->GetMethodID(_javaScClass, "StopPlayback", 571 "()I"); 572 573 // Call java sc object method 574 jint res = env->CallIntMethod(_javaScObj, stopPlaybackID); 575 if (res < 0) 576 { 577 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, 578 "StopPlayback failed (%d)", res); 579 } 580 581 _playIsInitialized = false; 582 _playing = false; 583 _playWarning = 0; 584 _playError = 0; 585 586 // Detach this thread if it was attached 587 if (isAttached) 588 { 589 if (_javaVM->DetachCurrentThread() < 0) 590 { 591 WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, 592 " Could not detach thread from JVM"); 593 } 594 } 595 596 return 0; 597 598} 599 600int32_t AudioTrackJni::InitSpeaker() { 601 CriticalSectionScoped lock(&_critSect); 602 603 if (_playing) 604 { 605 WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, 606 " Playout already started"); 607 return -1; 608 } 609 610 if (!_playoutDeviceIsSpecified) 611 { 612 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, 613 " Playout device is not specified"); 614 return -1; 615 } 616 617 // Nothing needs to be done here, we use a flag to have consistent 618 // behavior with other platforms 619 _speakerIsInitialized = true; 620 621 return 0; 622} 623 624int32_t AudioTrackJni::SpeakerVolumeIsAvailable(bool& available) { // NOLINT 625 available = true; // We assume we are always be able to set/get volume 626 return 0; 627} 628 629int32_t AudioTrackJni::SetSpeakerVolume(uint32_t volume) { 630 if (!_speakerIsInitialized) 631 { 632 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, 633 " Speaker not initialized"); 634 return -1; 635 } 636 if (!globalContext) 637 { 638 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, 639 " Context is not set"); 640 return -1; 641 } 642 643 // get the JNI env for this thread 644 JNIEnv *env; 645 bool isAttached = false; 646 647 if (_javaVM->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) 648 { 649 // try to attach the thread and get the env 650 // Attach this thread to JVM 651 jint res = _javaVM->AttachCurrentThread(&env, NULL); 652 if ((res < 0) || !env) 653 { 654 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, 655 " Could not attach thread to JVM (%d, %p)", res, env); 656 return -1; 657 } 658 isAttached = true; 659 } 660 661 // get the method ID 662 jmethodID setPlayoutVolumeID = env->GetMethodID(_javaScClass, 663 "SetPlayoutVolume", "(I)I"); 664 665 // call java sc object method 666 jint res = env->CallIntMethod(_javaScObj, setPlayoutVolumeID, 667 static_cast<int> (volume)); 668 if (res < 0) 669 { 670 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, 671 "SetPlayoutVolume failed (%d)", res); 672 return -1; 673 } 674 675 // Detach this thread if it was attached 676 if (isAttached) 677 { 678 if (_javaVM->DetachCurrentThread() < 0) 679 { 680 WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, 681 " Could not detach thread from JVM"); 682 } 683 } 684 685 return 0; 686} 687 688int32_t AudioTrackJni::SpeakerVolume(uint32_t& volume) const { // NOLINT 689 if (!_speakerIsInitialized) 690 { 691 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, 692 " Speaker not initialized"); 693 return -1; 694 } 695 if (!globalContext) 696 { 697 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, 698 " Context is not set"); 699 return -1; 700 } 701 702 // get the JNI env for this thread 703 JNIEnv *env; 704 bool isAttached = false; 705 706 if (_javaVM->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) 707 { 708 // try to attach the thread and get the env 709 // Attach this thread to JVM 710 jint res = _javaVM->AttachCurrentThread(&env, NULL); 711 if ((res < 0) || !env) 712 { 713 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, 714 " Could not attach thread to JVM (%d, %p)", res, env); 715 return -1; 716 } 717 isAttached = true; 718 } 719 720 // get the method ID 721 jmethodID getPlayoutVolumeID = env->GetMethodID(_javaScClass, 722 "GetPlayoutVolume", "()I"); 723 724 // call java sc object method 725 jint level = env->CallIntMethod(_javaScObj, getPlayoutVolumeID); 726 if (level < 0) 727 { 728 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, 729 "GetPlayoutVolume failed (%d)", level); 730 return -1; 731 } 732 733 // Detach this thread if it was attached 734 if (isAttached) 735 { 736 if (_javaVM->DetachCurrentThread() < 0) 737 { 738 WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, 739 " Could not detach thread from JVM"); 740 } 741 } 742 743 volume = static_cast<uint32_t> (level); 744 745 return 0; 746} 747 748 749int32_t AudioTrackJni::MaxSpeakerVolume(uint32_t& maxVolume) const { // NOLINT 750 if (!_speakerIsInitialized) 751 { 752 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, 753 " Speaker not initialized"); 754 return -1; 755 } 756 757 maxVolume = _maxSpeakerVolume; 758 759 return 0; 760} 761 762int32_t AudioTrackJni::MinSpeakerVolume(uint32_t& minVolume) const { // NOLINT 763 if (!_speakerIsInitialized) 764 { 765 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, 766 " Speaker not initialized"); 767 return -1; 768 } 769 minVolume = 0; 770 return 0; 771} 772 773int32_t AudioTrackJni::SpeakerVolumeStepSize( 774 uint16_t& stepSize) const { // NOLINT 775 if (!_speakerIsInitialized) 776 { 777 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, 778 " Speaker not initialized"); 779 return -1; 780 } 781 782 stepSize = 1; 783 784 return 0; 785} 786 787int32_t AudioTrackJni::SpeakerMuteIsAvailable(bool& available) { // NOLINT 788 available = false; // Speaker mute not supported on Android 789 return 0; 790} 791 792int32_t AudioTrackJni::SetSpeakerMute(bool enable) { 793 WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, 794 " API call not supported on this platform"); 795 return -1; 796} 797 798int32_t AudioTrackJni::SpeakerMute(bool& /*enabled*/) const { 799 800 WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, 801 " API call not supported on this platform"); 802 return -1; 803} 804 805int32_t AudioTrackJni::StereoPlayoutIsAvailable(bool& available) { // NOLINT 806 available = false; // Stereo playout not supported on Android 807 return 0; 808} 809 810int32_t AudioTrackJni::SetStereoPlayout(bool enable) { 811 if (enable) 812 { 813 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, 814 " Enabling not available"); 815 return -1; 816 } 817 818 return 0; 819} 820 821int32_t AudioTrackJni::StereoPlayout(bool& enabled) const { // NOLINT 822 enabled = false; 823 return 0; 824} 825 826int32_t AudioTrackJni::SetPlayoutBuffer( 827 const AudioDeviceModule::BufferType type, 828 uint16_t sizeMS) { 829 WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, 830 " API call not supported on this platform"); 831 return -1; 832} 833 834 835int32_t AudioTrackJni::PlayoutBuffer( 836 AudioDeviceModule::BufferType& type, // NOLINT 837 uint16_t& sizeMS) const { // NOLINT 838 type = AudioDeviceModule::kAdaptiveBufferSize; 839 sizeMS = _delayPlayout; // Set to current playout delay 840 841 return 0; 842} 843 844int32_t AudioTrackJni::PlayoutDelay(uint16_t& delayMS) const { // NOLINT 845 delayMS = _delayPlayout; 846 return 0; 847} 848 849void AudioTrackJni::AttachAudioBuffer(AudioDeviceBuffer* audioBuffer) { 850 CriticalSectionScoped lock(&_critSect); 851 _ptrAudioBuffer = audioBuffer; 852 // inform the AudioBuffer about default settings for this implementation 853 _ptrAudioBuffer->SetPlayoutSampleRate(N_PLAY_SAMPLES_PER_SEC); 854 _ptrAudioBuffer->SetPlayoutChannels(N_PLAY_CHANNELS); 855} 856 857int32_t AudioTrackJni::SetPlayoutSampleRate(const uint32_t samplesPerSec) { 858 if (samplesPerSec > 48000 || samplesPerSec < 8000) 859 { 860 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, 861 " Invalid sample rate"); 862 return -1; 863 } 864 865 // set the playout sample rate to use 866 if (samplesPerSec == 44100) 867 { 868 _samplingFreqOut = 44; 869 } 870 else 871 { 872 _samplingFreqOut = samplesPerSec / 1000; 873 } 874 875 // Update the AudioDeviceBuffer 876 _ptrAudioBuffer->SetPlayoutSampleRate(samplesPerSec); 877 878 return 0; 879} 880 881bool AudioTrackJni::PlayoutWarning() const { 882 return (_playWarning > 0); 883} 884 885bool AudioTrackJni::PlayoutError() const { 886 return (_playError > 0); 887} 888 889void AudioTrackJni::ClearPlayoutWarning() { 890 _playWarning = 0; 891} 892 893void AudioTrackJni::ClearPlayoutError() { 894 _playError = 0; 895} 896 897int32_t AudioTrackJni::SetLoudspeakerStatus(bool enable) { 898 if (!globalContext) 899 { 900 WEBRTC_TRACE(kTraceError, kTraceUtility, -1, 901 " Context is not set"); 902 return -1; 903 } 904 905 // get the JNI env for this thread 906 JNIEnv *env; 907 bool isAttached = false; 908 909 if (_javaVM->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) 910 { 911 // try to attach the thread and get the env 912 // Attach this thread to JVM 913 jint res = _javaVM->AttachCurrentThread(&env, NULL); 914 915 // Get the JNI env for this thread 916 if ((res < 0) || !env) 917 { 918 WEBRTC_TRACE(kTraceError, kTraceUtility, -1, 919 " Could not attach thread to JVM (%d, %p)", res, env); 920 return -1; 921 } 922 isAttached = true; 923 } 924 925 // get the method ID 926 jmethodID setPlayoutSpeakerID = env->GetMethodID(_javaScClass, 927 "SetPlayoutSpeaker", 928 "(Z)I"); 929 930 // call java sc object method 931 jint res = env->CallIntMethod(_javaScObj, setPlayoutSpeakerID, enable); 932 if (res < 0) 933 { 934 WEBRTC_TRACE(kTraceError, kTraceUtility, -1, 935 " SetPlayoutSpeaker failed (%d)", res); 936 return -1; 937 } 938 939 _loudSpeakerOn = enable; 940 941 // Detach this thread if it was attached 942 if (isAttached) 943 { 944 if (_javaVM->DetachCurrentThread() < 0) 945 { 946 WEBRTC_TRACE(kTraceWarning, kTraceUtility, -1, 947 " Could not detach thread from JVM"); 948 } 949 } 950 951 return 0; 952} 953 954int32_t AudioTrackJni::GetLoudspeakerStatus(bool& enabled) const { // NOLINT 955 enabled = _loudSpeakerOn; 956 return 0; 957} 958 959int32_t AudioTrackJni::InitJavaResources() { 960 // todo: Check if we already have created the java object 961 _javaVM = globalJvm; 962 _javaScClass = globalScClass; 963 964 // use the jvm that has been set 965 if (!_javaVM) 966 { 967 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, 968 "%s: Not a valid Java VM pointer", __FUNCTION__); 969 return -1; 970 } 971 972 // get the JNI env for this thread 973 JNIEnv *env; 974 bool isAttached = false; 975 976 // get the JNI env for this thread 977 if (_javaVM->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) 978 { 979 // try to attach the thread and get the env 980 // Attach this thread to JVM 981 jint res = _javaVM->AttachCurrentThread(&env, NULL); 982 if ((res < 0) || !env) 983 { 984 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, 985 "%s: Could not attach thread to JVM (%d, %p)", 986 __FUNCTION__, res, env); 987 return -1; 988 } 989 isAttached = true; 990 } 991 992 WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, 993 "get method id"); 994 995 // get the method ID for the void(void) constructor 996 jmethodID cid = env->GetMethodID(_javaScClass, "<init>", "()V"); 997 if (cid == NULL) 998 { 999 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, 1000 "%s: could not get constructor ID", __FUNCTION__); 1001 return -1; /* exception thrown */ 1002 } 1003 1004 WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, 1005 "construct object", __FUNCTION__); 1006 1007 // construct the object 1008 jobject javaScObjLocal = env->NewObject(_javaScClass, cid); 1009 if (!javaScObjLocal) 1010 { 1011 WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, 1012 "%s: could not create Java sc object", __FUNCTION__); 1013 return -1; 1014 } 1015 1016 // Create a reference to the object (to tell JNI that we are referencing it 1017 // after this function has returned). 1018 _javaScObj = env->NewGlobalRef(javaScObjLocal); 1019 if (!_javaScObj) 1020 { 1021 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, 1022 "%s: could not create Java sc object reference", 1023 __FUNCTION__); 1024 return -1; 1025 } 1026 1027 // Delete local object ref, we only use the global ref. 1028 env->DeleteLocalRef(javaScObjLocal); 1029 1030 ////////////////////// 1031 // AUDIO MANAGEMENT 1032 1033 // This is not mandatory functionality 1034 if (globalContext) { 1035 jfieldID context_id = env->GetFieldID(globalScClass, 1036 "_context", 1037 "Landroid/content/Context;"); 1038 if (!context_id) { 1039 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, 1040 "%s: could not get _context id", __FUNCTION__); 1041 return -1; 1042 } 1043 1044 env->SetObjectField(_javaScObj, context_id, globalContext); 1045 jobject javaContext = env->GetObjectField(_javaScObj, context_id); 1046 if (!javaContext) { 1047 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, 1048 "%s: could not set or get _context", __FUNCTION__); 1049 return -1; 1050 } 1051 } 1052 else { 1053 WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, 1054 "%s: did not set Context - some functionality is not " 1055 "supported", 1056 __FUNCTION__); 1057 } 1058 1059 ///////////// 1060 // PLAYOUT 1061 1062 // Get play buffer field ID. 1063 jfieldID fidPlayBuffer = env->GetFieldID(_javaScClass, "_playBuffer", 1064 "Ljava/nio/ByteBuffer;"); 1065 if (!fidPlayBuffer) 1066 { 1067 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, 1068 "%s: could not get play buffer fid", __FUNCTION__); 1069 return -1; 1070 } 1071 1072 // Get play buffer object. 1073 jobject javaPlayBufferLocal = 1074 env->GetObjectField(_javaScObj, fidPlayBuffer); 1075 if (!javaPlayBufferLocal) 1076 { 1077 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, 1078 "%s: could not get play buffer", __FUNCTION__); 1079 return -1; 1080 } 1081 1082 // Create a global reference to the object (to tell JNI that we are 1083 // referencing it after this function has returned) 1084 // NOTE: we are referencing it only through the direct buffer (see below). 1085 _javaPlayBuffer = env->NewGlobalRef(javaPlayBufferLocal); 1086 if (!_javaPlayBuffer) 1087 { 1088 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, 1089 "%s: could not get play buffer reference", __FUNCTION__); 1090 return -1; 1091 } 1092 1093 // Delete local object ref, we only use the global ref. 1094 env->DeleteLocalRef(javaPlayBufferLocal); 1095 1096 // Get direct buffer. 1097 _javaDirectPlayBuffer = env->GetDirectBufferAddress(_javaPlayBuffer); 1098 if (!_javaDirectPlayBuffer) 1099 { 1100 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, 1101 "%s: could not get direct play buffer", __FUNCTION__); 1102 return -1; 1103 } 1104 1105 // Get the play audio method ID. 1106 _javaMidPlayAudio = env->GetMethodID(_javaScClass, "PlayAudio", "(I)I"); 1107 if (!_javaMidPlayAudio) 1108 { 1109 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, 1110 "%s: could not get play audio mid", __FUNCTION__); 1111 return -1; 1112 } 1113 1114 // Detach this thread if it was attached. 1115 if (isAttached) 1116 { 1117 if (_javaVM->DetachCurrentThread() < 0) 1118 { 1119 WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, 1120 "%s: Could not detach thread from JVM", __FUNCTION__); 1121 } 1122 } 1123 1124 return 0; 1125 1126} 1127 1128int32_t AudioTrackJni::InitSampleRate() { 1129 int samplingFreq = 44100; 1130 jint res = 0; 1131 1132 // get the JNI env for this thread 1133 JNIEnv *env; 1134 bool isAttached = false; 1135 1136 // get the JNI env for this thread 1137 if (_javaVM->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) 1138 { 1139 // try to attach the thread and get the env 1140 // Attach this thread to JVM 1141 jint res = _javaVM->AttachCurrentThread(&env, NULL); 1142 if ((res < 0) || !env) 1143 { 1144 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, 1145 "%s: Could not attach thread to JVM (%d, %p)", 1146 __FUNCTION__, res, env); 1147 return -1; 1148 } 1149 isAttached = true; 1150 } 1151 1152 // get the method ID 1153 jmethodID initPlaybackID = env->GetMethodID(_javaScClass, "InitPlayback", 1154 "(I)I"); 1155 1156 if (_samplingFreqOut > 0) 1157 { 1158 // read the configured sampling rate 1159 samplingFreq = 44100; 1160 if (_samplingFreqOut != 44) 1161 { 1162 samplingFreq = _samplingFreqOut * 1000; 1163 } 1164 WEBRTC_TRACE(kTraceStateInfo, kTraceAudioDevice, _id, 1165 " Trying configured playback sampling rate %d", 1166 samplingFreq); 1167 } 1168 else 1169 { 1170 // set the preferred sampling frequency 1171 if (samplingFreq == 8000) 1172 { 1173 // try 16000 1174 samplingFreq = 16000; 1175 } 1176 // else use same as recording 1177 } 1178 1179 bool keepTrying = true; 1180 while (keepTrying) 1181 { 1182 // call java sc object method 1183 res = env->CallIntMethod(_javaScObj, initPlaybackID, samplingFreq); 1184 if (res < 0) 1185 { 1186 switch (samplingFreq) 1187 { 1188 case 44100: 1189 samplingFreq = 16000; 1190 break; 1191 case 16000: 1192 samplingFreq = 8000; 1193 break; 1194 default: // error 1195 WEBRTC_TRACE(kTraceError, 1196 kTraceAudioDevice, _id, 1197 "InitPlayback failed (%d)", res); 1198 return -1; 1199 } 1200 } 1201 else 1202 { 1203 keepTrying = false; 1204 } 1205 } 1206 1207 // Store max playout volume 1208 _maxSpeakerVolume = static_cast<uint32_t> (res); 1209 if (_maxSpeakerVolume < 1) 1210 { 1211 WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, 1212 " Did not get valid max speaker volume value (%d)", 1213 _maxSpeakerVolume); 1214 } 1215 1216 // set the playback sample rate to use 1217 if (samplingFreq == 44100) 1218 { 1219 _samplingFreqOut = 44; 1220 } 1221 else 1222 { 1223 _samplingFreqOut = samplingFreq / 1000; 1224 } 1225 1226 WEBRTC_TRACE(kTraceStateInfo, kTraceAudioDevice, _id, 1227 "Playback sample rate set to (%d)", _samplingFreqOut); 1228 1229 // get the method ID 1230 jmethodID stopPlaybackID = env->GetMethodID(_javaScClass, "StopPlayback", 1231 "()I"); 1232 1233 // Call java sc object method 1234 res = env->CallIntMethod(_javaScObj, stopPlaybackID); 1235 if (res < 0) 1236 { 1237 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, 1238 "StopPlayback failed (%d)", res); 1239 } 1240 1241 // Detach this thread if it was attached 1242 if (isAttached) 1243 { 1244 if (_javaVM->DetachCurrentThread() < 0) 1245 { 1246 WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, 1247 "%s: Could not detach thread from JVM", __FUNCTION__); 1248 } 1249 } 1250 1251 return 0; 1252 1253} 1254 1255bool AudioTrackJni::PlayThreadFunc(void* pThis) 1256{ 1257 return (static_cast<AudioTrackJni*> (pThis)->PlayThreadProcess()); 1258} 1259 1260bool AudioTrackJni::PlayThreadProcess() 1261{ 1262 if (!_playThreadIsInitialized) 1263 { 1264 // Do once when thread is started 1265 1266 // Attach this thread to JVM and get the JNI env for this thread 1267 jint res = _javaVM->AttachCurrentThread(&_jniEnvPlay, NULL); 1268 if ((res < 0) || !_jniEnvPlay) 1269 { 1270 WEBRTC_TRACE(kTraceCritical, kTraceAudioDevice, 1271 _id, 1272 "Could not attach playout thread to JVM (%d, %p)", 1273 res, _jniEnvPlay); 1274 return false; // Close down thread 1275 } 1276 1277 _playThreadIsInitialized = true; 1278 } 1279 1280 if (!_playing) 1281 { 1282 switch (_timeEventPlay.Wait(1000)) 1283 { 1284 case kEventSignaled: 1285 WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, 1286 _id, "Playout thread event signal"); 1287 _timeEventPlay.Reset(); 1288 break; 1289 case kEventError: 1290 WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, 1291 _id, "Playout thread event error"); 1292 return true; 1293 case kEventTimeout: 1294 WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, 1295 _id, "Playout thread event timeout"); 1296 return true; 1297 } 1298 } 1299 1300 Lock(); 1301 1302 if (_startPlay) 1303 { 1304 WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, 1305 "_startPlay true, performing initial actions"); 1306 _startPlay = false; 1307 _playing = true; 1308 _playWarning = 0; 1309 _playError = 0; 1310 _playStartStopEvent.Set(); 1311 WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, 1312 "Sent signal"); 1313 } 1314 1315 if (_playing) 1316 { 1317 int8_t playBuffer[2 * 480]; // Max 10 ms @ 48 kHz / 16 bit 1318 uint32_t samplesToPlay = _samplingFreqOut * 10; 1319 1320 // ask for new PCM data to be played out using the AudioDeviceBuffer 1321 // ensure that this callback is executed without taking the 1322 // audio-thread lock 1323 UnLock(); 1324 uint32_t nSamples = 1325 _ptrAudioBuffer->RequestPlayoutData(samplesToPlay); 1326 Lock(); 1327 1328 // Check again since play may have stopped during unlocked period 1329 if (!_playing) 1330 { 1331 UnLock(); 1332 return true; 1333 } 1334 1335 nSamples = _ptrAudioBuffer->GetPlayoutData(playBuffer); 1336 if (nSamples != samplesToPlay) 1337 { 1338 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, 1339 " invalid number of output samples(%d)", nSamples); 1340 _playWarning = 1; 1341 } 1342 1343 // Copy data to our direct buffer (held by java sc object) 1344 // todo: Give _javaDirectPlayBuffer directly to VoE? 1345 memcpy(_javaDirectPlayBuffer, playBuffer, nSamples * 2); 1346 1347 UnLock(); 1348 1349 // Call java sc object method to process data in direct buffer 1350 // Will block until data has been put in OS playout buffer 1351 // (see java sc class) 1352 jint res = _jniEnvPlay->CallIntMethod(_javaScObj, _javaMidPlayAudio, 1353 2 * nSamples); 1354 if (res < 0) 1355 { 1356 WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, 1357 "PlayAudio failed (%d)", res); 1358 _playWarning = 1; 1359 } 1360 else if (res > 0) 1361 { 1362 // we are not recording and have got a delay value from playback 1363 _delayPlayout = res / _samplingFreqOut; 1364 } 1365 Lock(); 1366 1367 } // _playing 1368 1369 if (_shutdownPlayThread) 1370 { 1371 WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, 1372 "Detaching thread from Java VM"); 1373 1374 // Detach thread from Java VM 1375 if (_javaVM->DetachCurrentThread() < 0) 1376 { 1377 WEBRTC_TRACE(kTraceCritical, kTraceAudioDevice, 1378 _id, "Could not detach playout thread from JVM"); 1379 _shutdownPlayThread = false; 1380 // If we say OK (i.e. set event) and close thread anyway, 1381 // app will crash 1382 } 1383 else 1384 { 1385 _jniEnvPlay = NULL; 1386 _shutdownPlayThread = false; 1387 _playStartStopEvent.Set(); // Signal to Terminate() that we are done 1388 WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, 1389 "Sent signal"); 1390 } 1391 } 1392 1393 UnLock(); 1394 return true; 1395} 1396 1397} // namespace webrtc 1398