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