diff options
Diffstat (limited to 'dom/plugins/base/android/ANPAudio.cpp')
-rw-r--r-- | dom/plugins/base/android/ANPAudio.cpp | 390 |
1 files changed, 390 insertions, 0 deletions
diff --git a/dom/plugins/base/android/ANPAudio.cpp b/dom/plugins/base/android/ANPAudio.cpp new file mode 100644 index 000000000..bc47e8999 --- /dev/null +++ b/dom/plugins/base/android/ANPAudio.cpp @@ -0,0 +1,390 @@ +/* -*- 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 <android/log.h> +#include <stdlib.h> +#include <time.h> + +#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, "<init>", "(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 mozilla::Runnable +{ +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<AudioRunnable> runnable = new AudioRunnable(s); + + nsCOMPtr<nsIThread> 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); +} |