/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; indent-tabs-mode: nil; -*- * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "base/basictypes.h" #include "AndroidBridge.h" #include #include #include #include "assert.h" #include "ANPBase.h" #include "nsIThread.h" #include "nsThreadUtils.h" #include "mozilla/Mutex.h" #define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "GeckoPluginsAudio" , ## args) #define ASSIGN(obj, name) (obj)->name = anp_audio_##name /* android.media.AudioTrack */ struct AudioTrack { jclass at_class; jmethodID constructor; jmethodID flush; jmethodID pause; jmethodID play; jmethodID setvol; jmethodID stop; jmethodID write; jmethodID getpos; jmethodID getstate; jmethodID release; }; enum AudioTrackMode { MODE_STATIC = 0, MODE_STREAM = 1 }; /* android.media.AudioManager */ enum AudioManagerStream { STREAM_VOICE_CALL = 0, STREAM_SYSTEM = 1, STREAM_RING = 2, STREAM_MUSIC = 3, STREAM_ALARM = 4, STREAM_NOTIFICATION = 5, STREAM_DTMF = 8 }; /* android.media.AudioFormat */ enum AudioFormatChannel { CHANNEL_OUT_MONO = 4, CHANNEL_OUT_STEREO = 12 }; enum AudioFormatEncoding { ENCODING_PCM_16BIT = 2, ENCODING_PCM_8BIT = 3 }; enum AudioFormatState { STATE_UNINITIALIZED = 0, STATE_INITIALIZED = 1, STATE_NO_STATIC_DATA = 2 }; static struct AudioTrack at; static jclass init_jni_bindings(JNIEnv *jenv) { jclass jc = (jclass)jenv->NewGlobalRef(jenv->FindClass("android/media/AudioTrack")); at.constructor = jenv->GetMethodID(jc, "", "(IIIIII)V"); at.flush = jenv->GetMethodID(jc, "flush", "()V"); at.pause = jenv->GetMethodID(jc, "pause", "()V"); at.play = jenv->GetMethodID(jc, "play", "()V"); at.setvol = jenv->GetMethodID(jc, "setStereoVolume", "(FF)I"); at.stop = jenv->GetMethodID(jc, "stop", "()V"); at.write = jenv->GetMethodID(jc, "write", "([BII)I"); at.getpos = jenv->GetMethodID(jc, "getPlaybackHeadPosition", "()I"); at.getstate = jenv->GetMethodID(jc, "getState", "()I"); at.release = jenv->GetMethodID(jc, "release", "()V"); return jc; } struct ANPAudioTrack { jobject output_unit; jclass at_class; unsigned int rate; unsigned int channels; unsigned int bufferSize; unsigned int isStopped; unsigned int keepGoing; mozilla::Mutex lock; void* user; ANPAudioCallbackProc proc; ANPSampleFormat format; ANPAudioTrack() : lock("ANPAudioTrack") { } }; class AudioRunnable : public nsRunnable { public: NS_DECL_NSIRUNNABLE AudioRunnable(ANPAudioTrack* aAudioTrack) { mTrack = aAudioTrack; } ANPAudioTrack* mTrack; }; NS_IMETHODIMP AudioRunnable::Run() { PR_SetCurrentThreadName("Android Audio"); JNIEnv* const jenv = mozilla::jni::GetEnvForThread(); mozilla::AutoLocalJNIFrame autoFrame(jenv, 2); jbyteArray bytearray = jenv->NewByteArray(mTrack->bufferSize); if (!bytearray) { LOG("AudioRunnable:: Run. Could not create bytearray"); return NS_ERROR_FAILURE; } jbyte *byte = jenv->GetByteArrayElements(bytearray, nullptr); if (!byte) { LOG("AudioRunnable:: Run. Could not create bytearray"); return NS_ERROR_FAILURE; } ANPAudioBuffer buffer; buffer.channelCount = mTrack->channels; buffer.format = mTrack->format; buffer.bufferData = (void*) byte; while (true) { // reset the buffer size buffer.size = mTrack->bufferSize; { mozilla::MutexAutoLock lock(mTrack->lock); if (!mTrack->keepGoing) break; // Get data from the plugin mTrack->proc(kMoreData_ANPAudioEvent, mTrack->user, &buffer); } if (buffer.size == 0) { LOG("%p - kMoreData_ANPAudioEvent", mTrack); continue; } size_t wroteSoFar = 0; jint retval; do { retval = jenv->CallIntMethod(mTrack->output_unit, at.write, bytearray, wroteSoFar, buffer.size - wroteSoFar); if (retval < 0) { LOG("%p - Write failed %d", mTrack, retval); break; } wroteSoFar += retval; } while(wroteSoFar < buffer.size); } jenv->CallVoidMethod(mTrack->output_unit, at.release); jenv->DeleteGlobalRef(mTrack->output_unit); jenv->DeleteGlobalRef(mTrack->at_class); delete mTrack; jenv->ReleaseByteArrayElements(bytearray, byte, 0); return NS_OK; } ANPAudioTrack* anp_audio_newTrack(uint32_t sampleRate, // sampling rate in Hz ANPSampleFormat format, int channelCount, // MONO=1, STEREO=2 ANPAudioCallbackProc proc, void* user) { ANPAudioTrack *s = new ANPAudioTrack(); if (s == nullptr) { return nullptr; } JNIEnv* const jenv = mozilla::jni::GetEnvForThread(); s->at_class = init_jni_bindings(jenv); s->rate = sampleRate; s->channels = channelCount; s->bufferSize = s->rate * s->channels; s->isStopped = true; s->keepGoing = false; s->user = user; s->proc = proc; s->format = format; int jformat; switch (format) { case kPCM16Bit_ANPSampleFormat: jformat = ENCODING_PCM_16BIT; break; case kPCM8Bit_ANPSampleFormat: jformat = ENCODING_PCM_8BIT; break; default: LOG("Unknown audio format. defaulting to 16bit."); jformat = ENCODING_PCM_16BIT; break; } int jChannels; switch (channelCount) { case 1: jChannels = CHANNEL_OUT_MONO; break; case 2: jChannels = CHANNEL_OUT_STEREO; break; default: LOG("Unknown channel count. defaulting to mono."); jChannels = CHANNEL_OUT_MONO; break; } mozilla::AutoLocalJNIFrame autoFrame(jenv); jobject obj = jenv->NewObject(s->at_class, at.constructor, STREAM_MUSIC, s->rate, jChannels, jformat, s->bufferSize, MODE_STREAM); if (autoFrame.CheckForException() || obj == nullptr) { jenv->DeleteGlobalRef(s->at_class); delete s; return nullptr; } jint state = jenv->CallIntMethod(obj, at.getstate); if (autoFrame.CheckForException() || state == STATE_UNINITIALIZED) { jenv->DeleteGlobalRef(s->at_class); delete s; return nullptr; } s->output_unit = jenv->NewGlobalRef(obj); return s; } void anp_audio_deleteTrack(ANPAudioTrack* s) { if (s == nullptr) { return; } mozilla::MutexAutoLock lock(s->lock); s->keepGoing = false; // deallocation happens in the AudioThread. There is a // potential leak if anp_audio_start is never called, but // we do not see that from flash. } void anp_audio_start(ANPAudioTrack* s) { if (s == nullptr || s->output_unit == nullptr) { return; } if (s->keepGoing) { // we are already playing. Ignore. return; } JNIEnv* const jenv = mozilla::jni::GetEnvForThread(); mozilla::AutoLocalJNIFrame autoFrame(jenv, 0); jenv->CallVoidMethod(s->output_unit, at.play); if (autoFrame.CheckForException()) { jenv->DeleteGlobalRef(s->at_class); delete s; return; } s->isStopped = false; s->keepGoing = true; // AudioRunnable now owns the ANPAudioTrack RefPtr runnable = new AudioRunnable(s); nsCOMPtr thread; NS_NewThread(getter_AddRefs(thread), runnable); } void anp_audio_pause(ANPAudioTrack* s) { if (s == nullptr || s->output_unit == nullptr) { return; } JNIEnv* const jenv = mozilla::jni::GetEnvForThread(); mozilla::AutoLocalJNIFrame autoFrame(jenv, 0); jenv->CallVoidMethod(s->output_unit, at.pause); } void anp_audio_stop(ANPAudioTrack* s) { if (s == nullptr || s->output_unit == nullptr) { return; } s->isStopped = true; JNIEnv* const jenv = mozilla::jni::GetEnvForThread(); mozilla::AutoLocalJNIFrame autoFrame(jenv, 0); jenv->CallVoidMethod(s->output_unit, at.stop); } bool anp_audio_isStopped(ANPAudioTrack* s) { return s->isStopped; } uint32_t anp_audio_trackLatency(ANPAudioTrack* s) { // Hardcode an estimate of the system's audio latency. Flash hardcodes // similar latency estimates for pre-Honeycomb devices that do not support // ANPAudioTrackInterfaceV1's trackLatency(). The Android stock browser // calls android::AudioTrack::latency(), an internal Android API that is // not available in the public NDK: // https://github.com/android/platform_external_webkit/commit/49bf866973cb3b2a6c74c0eab864e9562e4cbab1 return 100; // milliseconds } void InitAudioTrackInterfaceV0(ANPAudioTrackInterfaceV0 *i) { _assert(i->inSize == sizeof(*i)); ASSIGN(i, newTrack); ASSIGN(i, deleteTrack); ASSIGN(i, start); ASSIGN(i, pause); ASSIGN(i, stop); ASSIGN(i, isStopped); } void InitAudioTrackInterfaceV1(ANPAudioTrackInterfaceV1 *i) { _assert(i->inSize == sizeof(*i)); ASSIGN(i, newTrack); ASSIGN(i, deleteTrack); ASSIGN(i, start); ASSIGN(i, pause); ASSIGN(i, stop); ASSIGN(i, isStopped); ASSIGN(i, trackLatency); }