1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/plugins/base/android/ANPAudio.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,390 @@ 1.4 +/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; indent-tabs-mode: nil; -*- 1.5 + * This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "base/basictypes.h" 1.10 +#include "AndroidBridge.h" 1.11 + 1.12 +#include <android/log.h> 1.13 +#include <stdlib.h> 1.14 +#include <time.h> 1.15 + 1.16 +#include "assert.h" 1.17 +#include "ANPBase.h" 1.18 +#include "nsIThread.h" 1.19 +#include "nsThreadUtils.h" 1.20 +#include "mozilla/Mutex.h" 1.21 + 1.22 +#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "GeckoPluginsAudio" , ## args) 1.23 +#define ASSIGN(obj, name) (obj)->name = anp_audio_##name 1.24 + 1.25 +/* android.media.AudioTrack */ 1.26 +struct AudioTrack { 1.27 + jclass at_class; 1.28 + jmethodID constructor; 1.29 + jmethodID flush; 1.30 + jmethodID pause; 1.31 + jmethodID play; 1.32 + jmethodID setvol; 1.33 + jmethodID stop; 1.34 + jmethodID write; 1.35 + jmethodID getpos; 1.36 + jmethodID getstate; 1.37 + jmethodID release; 1.38 +}; 1.39 + 1.40 +enum AudioTrackMode { 1.41 + MODE_STATIC = 0, 1.42 + MODE_STREAM = 1 1.43 +}; 1.44 + 1.45 +/* android.media.AudioManager */ 1.46 +enum AudioManagerStream { 1.47 + STREAM_VOICE_CALL = 0, 1.48 + STREAM_SYSTEM = 1, 1.49 + STREAM_RING = 2, 1.50 + STREAM_MUSIC = 3, 1.51 + STREAM_ALARM = 4, 1.52 + STREAM_NOTIFICATION = 5, 1.53 + STREAM_DTMF = 8 1.54 +}; 1.55 + 1.56 +/* android.media.AudioFormat */ 1.57 +enum AudioFormatChannel { 1.58 + CHANNEL_OUT_MONO = 4, 1.59 + CHANNEL_OUT_STEREO = 12 1.60 +}; 1.61 + 1.62 +enum AudioFormatEncoding { 1.63 + ENCODING_PCM_16BIT = 2, 1.64 + ENCODING_PCM_8BIT = 3 1.65 +}; 1.66 + 1.67 +enum AudioFormatState { 1.68 + STATE_UNINITIALIZED = 0, 1.69 + STATE_INITIALIZED = 1, 1.70 + STATE_NO_STATIC_DATA = 2 1.71 +}; 1.72 + 1.73 +static struct AudioTrack at; 1.74 + 1.75 +static jclass 1.76 +init_jni_bindings(JNIEnv *jenv) { 1.77 + jclass jc = 1.78 + (jclass)jenv->NewGlobalRef(jenv->FindClass("android/media/AudioTrack")); 1.79 + 1.80 + at.constructor = jenv->GetMethodID(jc, "<init>", "(IIIIII)V"); 1.81 + at.flush = jenv->GetMethodID(jc, "flush", "()V"); 1.82 + at.pause = jenv->GetMethodID(jc, "pause", "()V"); 1.83 + at.play = jenv->GetMethodID(jc, "play", "()V"); 1.84 + at.setvol = jenv->GetMethodID(jc, "setStereoVolume", "(FF)I"); 1.85 + at.stop = jenv->GetMethodID(jc, "stop", "()V"); 1.86 + at.write = jenv->GetMethodID(jc, "write", "([BII)I"); 1.87 + at.getpos = jenv->GetMethodID(jc, "getPlaybackHeadPosition", "()I"); 1.88 + at.getstate = jenv->GetMethodID(jc, "getState", "()I"); 1.89 + at.release = jenv->GetMethodID(jc, "release", "()V"); 1.90 + 1.91 + return jc; 1.92 +} 1.93 + 1.94 +struct ANPAudioTrack { 1.95 + jobject output_unit; 1.96 + jclass at_class; 1.97 + 1.98 + unsigned int rate; 1.99 + unsigned int channels; 1.100 + unsigned int bufferSize; 1.101 + unsigned int isStopped; 1.102 + unsigned int keepGoing; 1.103 + 1.104 + mozilla::Mutex lock; 1.105 + 1.106 + void* user; 1.107 + ANPAudioCallbackProc proc; 1.108 + ANPSampleFormat format; 1.109 + 1.110 + ANPAudioTrack() : lock("ANPAudioTrack") { } 1.111 +}; 1.112 + 1.113 +class AudioRunnable : public nsRunnable 1.114 +{ 1.115 +public: 1.116 + NS_DECL_NSIRUNNABLE 1.117 + 1.118 + AudioRunnable(ANPAudioTrack* aAudioTrack) { 1.119 + mTrack = aAudioTrack; 1.120 + } 1.121 + 1.122 + ANPAudioTrack* mTrack; 1.123 +}; 1.124 + 1.125 +NS_IMETHODIMP 1.126 +AudioRunnable::Run() 1.127 +{ 1.128 + PR_SetCurrentThreadName("Android Audio"); 1.129 + 1.130 + JNIEnv* jenv = GetJNIForThread(); 1.131 + 1.132 + mozilla::AutoLocalJNIFrame autoFrame(jenv, 2); 1.133 + 1.134 + jbyteArray bytearray = jenv->NewByteArray(mTrack->bufferSize); 1.135 + if (!bytearray) { 1.136 + LOG("AudioRunnable:: Run. Could not create bytearray"); 1.137 + return NS_ERROR_FAILURE; 1.138 + } 1.139 + 1.140 + jbyte *byte = jenv->GetByteArrayElements(bytearray, nullptr); 1.141 + if (!byte) { 1.142 + LOG("AudioRunnable:: Run. Could not create bytearray"); 1.143 + return NS_ERROR_FAILURE; 1.144 + } 1.145 + 1.146 + ANPAudioBuffer buffer; 1.147 + buffer.channelCount = mTrack->channels; 1.148 + buffer.format = mTrack->format; 1.149 + buffer.bufferData = (void*) byte; 1.150 + 1.151 + while (true) 1.152 + { 1.153 + // reset the buffer size 1.154 + buffer.size = mTrack->bufferSize; 1.155 + 1.156 + { 1.157 + mozilla::MutexAutoLock lock(mTrack->lock); 1.158 + 1.159 + if (!mTrack->keepGoing) 1.160 + break; 1.161 + 1.162 + // Get data from the plugin 1.163 + mTrack->proc(kMoreData_ANPAudioEvent, mTrack->user, &buffer); 1.164 + } 1.165 + 1.166 + if (buffer.size == 0) { 1.167 + LOG("%p - kMoreData_ANPAudioEvent", mTrack); 1.168 + continue; 1.169 + } 1.170 + 1.171 + size_t wroteSoFar = 0; 1.172 + jint retval; 1.173 + do { 1.174 + retval = jenv->CallIntMethod(mTrack->output_unit, 1.175 + at.write, 1.176 + bytearray, 1.177 + wroteSoFar, 1.178 + buffer.size - wroteSoFar); 1.179 + if (retval < 0) { 1.180 + LOG("%p - Write failed %d", mTrack, retval); 1.181 + break; 1.182 + } 1.183 + 1.184 + wroteSoFar += retval; 1.185 + 1.186 + } while(wroteSoFar < buffer.size); 1.187 + } 1.188 + 1.189 + jenv->CallVoidMethod(mTrack->output_unit, at.release); 1.190 + 1.191 + jenv->DeleteGlobalRef(mTrack->output_unit); 1.192 + jenv->DeleteGlobalRef(mTrack->at_class); 1.193 + 1.194 + delete mTrack; 1.195 + 1.196 + jenv->ReleaseByteArrayElements(bytearray, byte, 0); 1.197 + 1.198 + return NS_OK; 1.199 +} 1.200 + 1.201 +ANPAudioTrack* 1.202 +anp_audio_newTrack(uint32_t sampleRate, // sampling rate in Hz 1.203 + ANPSampleFormat format, 1.204 + int channelCount, // MONO=1, STEREO=2 1.205 + ANPAudioCallbackProc proc, 1.206 + void* user) 1.207 +{ 1.208 + ANPAudioTrack *s = new ANPAudioTrack(); 1.209 + if (s == nullptr) { 1.210 + return nullptr; 1.211 + } 1.212 + 1.213 + JNIEnv *jenv = GetJNIForThread(); 1.214 + 1.215 + s->at_class = init_jni_bindings(jenv); 1.216 + s->rate = sampleRate; 1.217 + s->channels = channelCount; 1.218 + s->bufferSize = s->rate * s->channels; 1.219 + s->isStopped = true; 1.220 + s->keepGoing = false; 1.221 + s->user = user; 1.222 + s->proc = proc; 1.223 + s->format = format; 1.224 + 1.225 + int jformat; 1.226 + switch (format) { 1.227 + case kPCM16Bit_ANPSampleFormat: 1.228 + jformat = ENCODING_PCM_16BIT; 1.229 + break; 1.230 + case kPCM8Bit_ANPSampleFormat: 1.231 + jformat = ENCODING_PCM_8BIT; 1.232 + break; 1.233 + default: 1.234 + LOG("Unknown audio format. defaulting to 16bit."); 1.235 + jformat = ENCODING_PCM_16BIT; 1.236 + break; 1.237 + } 1.238 + 1.239 + int jChannels; 1.240 + switch (channelCount) { 1.241 + case 1: 1.242 + jChannels = CHANNEL_OUT_MONO; 1.243 + break; 1.244 + case 2: 1.245 + jChannels = CHANNEL_OUT_STEREO; 1.246 + break; 1.247 + default: 1.248 + LOG("Unknown channel count. defaulting to mono."); 1.249 + jChannels = CHANNEL_OUT_MONO; 1.250 + break; 1.251 + } 1.252 + 1.253 + mozilla::AutoLocalJNIFrame autoFrame(jenv); 1.254 + 1.255 + jobject obj = jenv->NewObject(s->at_class, 1.256 + at.constructor, 1.257 + STREAM_MUSIC, 1.258 + s->rate, 1.259 + jChannels, 1.260 + jformat, 1.261 + s->bufferSize, 1.262 + MODE_STREAM); 1.263 + 1.264 + if (autoFrame.CheckForException() || obj == nullptr) { 1.265 + jenv->DeleteGlobalRef(s->at_class); 1.266 + free(s); 1.267 + return nullptr; 1.268 + } 1.269 + 1.270 + jint state = jenv->CallIntMethod(obj, at.getstate); 1.271 + 1.272 + if (autoFrame.CheckForException() || state == STATE_UNINITIALIZED) { 1.273 + jenv->DeleteGlobalRef(s->at_class); 1.274 + free(s); 1.275 + return nullptr; 1.276 + } 1.277 + 1.278 + s->output_unit = jenv->NewGlobalRef(obj); 1.279 + return s; 1.280 +} 1.281 + 1.282 +void 1.283 +anp_audio_deleteTrack(ANPAudioTrack* s) 1.284 +{ 1.285 + if (s == nullptr) { 1.286 + return; 1.287 + } 1.288 + 1.289 + mozilla::MutexAutoLock lock(s->lock); 1.290 + s->keepGoing = false; 1.291 + 1.292 + // deallocation happens in the AudioThread. There is a 1.293 + // potential leak if anp_audio_start is never called, but 1.294 + // we do not see that from flash. 1.295 +} 1.296 + 1.297 +void 1.298 +anp_audio_start(ANPAudioTrack* s) 1.299 +{ 1.300 + if (s == nullptr || s->output_unit == nullptr) { 1.301 + return; 1.302 + } 1.303 + 1.304 + if (s->keepGoing) { 1.305 + // we are already playing. Ignore. 1.306 + return; 1.307 + } 1.308 + 1.309 + JNIEnv *jenv = GetJNIForThread(); 1.310 + 1.311 + mozilla::AutoLocalJNIFrame autoFrame(jenv, 0); 1.312 + jenv->CallVoidMethod(s->output_unit, at.play); 1.313 + 1.314 + if (autoFrame.CheckForException()) { 1.315 + jenv->DeleteGlobalRef(s->at_class); 1.316 + free(s); 1.317 + return; 1.318 + } 1.319 + 1.320 + s->isStopped = false; 1.321 + s->keepGoing = true; 1.322 + 1.323 + // AudioRunnable now owns the ANPAudioTrack 1.324 + nsRefPtr<AudioRunnable> runnable = new AudioRunnable(s); 1.325 + 1.326 + nsCOMPtr<nsIThread> thread; 1.327 + NS_NewThread(getter_AddRefs(thread), runnable); 1.328 +} 1.329 + 1.330 +void 1.331 +anp_audio_pause(ANPAudioTrack* s) 1.332 +{ 1.333 + if (s == nullptr || s->output_unit == nullptr) { 1.334 + return; 1.335 + } 1.336 + 1.337 + JNIEnv *jenv = GetJNIForThread(); 1.338 + 1.339 + mozilla::AutoLocalJNIFrame autoFrame(jenv, 0); 1.340 + jenv->CallVoidMethod(s->output_unit, at.pause); 1.341 +} 1.342 + 1.343 +void 1.344 +anp_audio_stop(ANPAudioTrack* s) 1.345 +{ 1.346 + if (s == nullptr || s->output_unit == nullptr) { 1.347 + return; 1.348 + } 1.349 + 1.350 + s->isStopped = true; 1.351 + JNIEnv *jenv = GetJNIForThread(); 1.352 + 1.353 + mozilla::AutoLocalJNIFrame autoFrame(jenv, 0); 1.354 + jenv->CallVoidMethod(s->output_unit, at.stop); 1.355 +} 1.356 + 1.357 +bool 1.358 +anp_audio_isStopped(ANPAudioTrack* s) 1.359 +{ 1.360 + return s->isStopped; 1.361 +} 1.362 + 1.363 +uint32_t 1.364 +anp_audio_trackLatency(ANPAudioTrack* s) { 1.365 + // Hardcode an estimate of the system's audio latency. Flash hardcodes 1.366 + // similar latency estimates for pre-Honeycomb devices that do not support 1.367 + // ANPAudioTrackInterfaceV1's trackLatency(). The Android stock browser 1.368 + // calls android::AudioTrack::latency(), an internal Android API that is 1.369 + // not available in the public NDK: 1.370 + // https://github.com/android/platform_external_webkit/commit/49bf866973cb3b2a6c74c0eab864e9562e4cbab1 1.371 + return 100; // milliseconds 1.372 +} 1.373 + 1.374 +void InitAudioTrackInterfaceV0(ANPAudioTrackInterfaceV0 *i) { 1.375 + _assert(i->inSize == sizeof(*i)); 1.376 + ASSIGN(i, newTrack); 1.377 + ASSIGN(i, deleteTrack); 1.378 + ASSIGN(i, start); 1.379 + ASSIGN(i, pause); 1.380 + ASSIGN(i, stop); 1.381 + ASSIGN(i, isStopped); 1.382 +} 1.383 + 1.384 +void InitAudioTrackInterfaceV1(ANPAudioTrackInterfaceV1 *i) { 1.385 + _assert(i->inSize == sizeof(*i)); 1.386 + ASSIGN(i, newTrack); 1.387 + ASSIGN(i, deleteTrack); 1.388 + ASSIGN(i, start); 1.389 + ASSIGN(i, pause); 1.390 + ASSIGN(i, stop); 1.391 + ASSIGN(i, isStopped); 1.392 + ASSIGN(i, trackLatency); 1.393 +}