/* * Copyright (C) 2008 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ //#define LOG_NDEBUG 0 #define LOG_TAG "AudioTrack-JNI" #include "android_media_AudioTrack.h" #include #include "core_jni_helpers.h" #include #include #include #include #include #include #include #include "android_media_AudioFormat.h" #include "android_media_AudioErrors.h" #include "android_media_MediaMetricsJNI.h" #include "android_media_PlaybackParams.h" #include "android_media_DeviceCallback.h" #include "android_media_VolumeShaper.h" #include "android_media_AudioAttributes.h" #include // ---------------------------------------------------------------------------- using namespace android; using ::android::media::VolumeShaper; // ---------------------------------------------------------------------------- static const char* const kClassPathName = "android/media/AudioTrack"; struct audio_track_fields_t { // these fields provide access from C++ to the... jmethodID postNativeEventInJava; //... event post callback method jfieldID nativeTrackInJavaObj; // stores in Java the native AudioTrack object jfieldID jniData; // stores in Java additional resources used by the native AudioTrack jfieldID fieldStreamType; // ... mStreamType field in the AudioTrack Java object }; static audio_track_fields_t javaAudioTrackFields; static PlaybackParams::fields_t gPlaybackParamsFields; static VolumeShaperHelper::fields_t gVolumeShaperFields; struct audiotrack_callback_cookie { jclass audioTrack_class; jobject audioTrack_ref; bool busy; Condition cond; bool isOffload; }; // keep these values in sync with AudioTrack.java #define MODE_STATIC 0 #define MODE_STREAM 1 // ---------------------------------------------------------------------------- class AudioTrackJniStorage { public: sp mMemHeap; sp mMemBase; audiotrack_callback_cookie mCallbackData; sp mDeviceCallback; AudioTrackJniStorage() { mCallbackData.audioTrack_class = 0; mCallbackData.audioTrack_ref = 0; mCallbackData.isOffload = false; } ~AudioTrackJniStorage() { mMemBase.clear(); mMemHeap.clear(); } bool allocSharedMem(int sizeInBytes) { mMemHeap = new MemoryHeapBase(sizeInBytes, 0, "AudioTrack Heap Base"); if (mMemHeap->getHeapID() < 0) { return false; } mMemBase = new MemoryBase(mMemHeap, 0, sizeInBytes); return true; } }; static Mutex sLock; static SortedVector sAudioTrackCallBackCookies; // ---------------------------------------------------------------------------- #define DEFAULT_OUTPUT_SAMPLE_RATE 44100 #define AUDIOTRACK_ERROR_SETUP_AUDIOSYSTEM (-16) #define AUDIOTRACK_ERROR_SETUP_INVALIDCHANNELMASK (-17) #define AUDIOTRACK_ERROR_SETUP_INVALIDFORMAT (-18) #define AUDIOTRACK_ERROR_SETUP_INVALIDSTREAMTYPE (-19) #define AUDIOTRACK_ERROR_SETUP_NATIVEINITFAILED (-20) // ---------------------------------------------------------------------------- static void audioCallback(int event, void* user, void *info) { audiotrack_callback_cookie *callbackInfo = (audiotrack_callback_cookie *)user; { Mutex::Autolock l(sLock); if (sAudioTrackCallBackCookies.indexOf(callbackInfo) < 0) { return; } callbackInfo->busy = true; } // used as default argument when event callback doesn't have any, or number of // frames for EVENT_CAN_WRITE_MORE_DATA int arg = 0; bool postEvent = false; switch (event) { // Offload only events case AudioTrack::EVENT_CAN_WRITE_MORE_DATA: // this event will read the info return parameter of the callback: // for JNI offload, use the returned size to indicate: // 1/ no data is returned through callback, as it's all done through write() // 2/ do not wait as AudioTrack does when it receives 0 bytes if (callbackInfo->isOffload) { AudioTrack::Buffer* pBuffer = (AudioTrack::Buffer*) info; const size_t availableForWrite = pBuffer->size; arg = availableForWrite > INT32_MAX ? INT32_MAX : (int) availableForWrite; pBuffer->size = 0; } FALLTHROUGH_INTENDED; case AudioTrack::EVENT_STREAM_END: case AudioTrack::EVENT_NEW_IAUDIOTRACK: // a.k.a. tear down if (callbackInfo->isOffload) { postEvent = true; } break; // PCM and offload events case AudioTrack::EVENT_MARKER: case AudioTrack::EVENT_NEW_POS: postEvent = true; break; default: // event will not be posted break; } if (postEvent) { JNIEnv *env = AndroidRuntime::getJNIEnv(); if (env != NULL) { env->CallStaticVoidMethod( callbackInfo->audioTrack_class, javaAudioTrackFields.postNativeEventInJava, callbackInfo->audioTrack_ref, event, arg, 0, NULL); if (env->ExceptionCheck()) { env->ExceptionDescribe(); env->ExceptionClear(); } } } { Mutex::Autolock l(sLock); callbackInfo->busy = false; callbackInfo->cond.broadcast(); } } // ---------------------------------------------------------------------------- static sp getAudioTrack(JNIEnv* env, jobject thiz) { Mutex::Autolock l(sLock); AudioTrack* const at = (AudioTrack*)env->GetLongField(thiz, javaAudioTrackFields.nativeTrackInJavaObj); return sp(at); } static sp setAudioTrack(JNIEnv* env, jobject thiz, const sp& at) { Mutex::Autolock l(sLock); sp old = (AudioTrack*)env->GetLongField(thiz, javaAudioTrackFields.nativeTrackInJavaObj); if (at.get()) { at->incStrong((void*)setAudioTrack); } if (old != 0) { old->decStrong((void*)setAudioTrack); } env->SetLongField(thiz, javaAudioTrackFields.nativeTrackInJavaObj, (jlong)at.get()); return old; } // ---------------------------------------------------------------------------- sp android_media_AudioTrack_getAudioTrack(JNIEnv* env, jobject audioTrackObj) { return getAudioTrack(env, audioTrackObj); } // ---------------------------------------------------------------------------- static jint android_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject weak_this, jobject jaa, jintArray jSampleRate, jint channelPositionMask, jint channelIndexMask, jint audioFormat, jint buffSizeInBytes, jint memoryMode, jintArray jSession, jlong nativeAudioTrack, jboolean offload) { ALOGV("sampleRates=%p, channel mask=%x, index mask=%x, audioFormat(Java)=%d, buffSize=%d," " nativeAudioTrack=0x%" PRIX64 ", offload=%d", jSampleRate, channelPositionMask, channelIndexMask, audioFormat, buffSizeInBytes, nativeAudioTrack, offload); sp lpTrack = 0; if (jSession == NULL) { ALOGE("Error creating AudioTrack: invalid session ID pointer"); return (jint) AUDIO_JAVA_ERROR; } jint* nSession = (jint *) env->GetPrimitiveArrayCritical(jSession, NULL); if (nSession == NULL) { ALOGE("Error creating AudioTrack: Error retrieving session id pointer"); return (jint) AUDIO_JAVA_ERROR; } audio_session_t sessionId = (audio_session_t) nSession[0]; env->ReleasePrimitiveArrayCritical(jSession, nSession, 0); nSession = NULL; AudioTrackJniStorage* lpJniStorage = NULL; jclass clazz = env->GetObjectClass(thiz); if (clazz == NULL) { ALOGE("Can't find %s when setting up callback.", kClassPathName); return (jint) AUDIOTRACK_ERROR_SETUP_NATIVEINITFAILED; } // if we pass in an existing *Native* AudioTrack, we don't need to create/initialize one. if (nativeAudioTrack == 0) { if (jaa == 0) { ALOGE("Error creating AudioTrack: invalid audio attributes"); return (jint) AUDIO_JAVA_ERROR; } if (jSampleRate == 0) { ALOGE("Error creating AudioTrack: invalid sample rates"); return (jint) AUDIO_JAVA_ERROR; } int* sampleRates = env->GetIntArrayElements(jSampleRate, NULL); int sampleRateInHertz = sampleRates[0]; env->ReleaseIntArrayElements(jSampleRate, sampleRates, JNI_ABORT); // Invalid channel representations are caught by !audio_is_output_channel() below. audio_channel_mask_t nativeChannelMask = nativeChannelMaskFromJavaChannelMasks( channelPositionMask, channelIndexMask); if (!audio_is_output_channel(nativeChannelMask)) { ALOGE("Error creating AudioTrack: invalid native channel mask %#x.", nativeChannelMask); return (jint) AUDIOTRACK_ERROR_SETUP_INVALIDCHANNELMASK; } uint32_t channelCount = audio_channel_count_from_out_mask(nativeChannelMask); // check the format. // This function was called from Java, so we compare the format against the Java constants audio_format_t format = audioFormatToNative(audioFormat); if (format == AUDIO_FORMAT_INVALID) { ALOGE("Error creating AudioTrack: unsupported audio format %d.", audioFormat); return (jint) AUDIOTRACK_ERROR_SETUP_INVALIDFORMAT; } // compute the frame count size_t frameCount; if (audio_has_proportional_frames(format)) { const size_t bytesPerSample = audio_bytes_per_sample(format); frameCount = buffSizeInBytes / (channelCount * bytesPerSample); } else { frameCount = buffSizeInBytes; } // create the native AudioTrack object lpTrack = new AudioTrack(); // read the AudioAttributes values auto paa = JNIAudioAttributeHelper::makeUnique(); jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jaa, paa.get()); if (jStatus != (jint)AUDIO_JAVA_SUCCESS) { return jStatus; } ALOGV("AudioTrack_setup for usage=%d content=%d flags=0x%#x tags=%s", paa->usage, paa->content_type, paa->flags, paa->tags); // initialize the callback information: // this data will be passed with every AudioTrack callback lpJniStorage = new AudioTrackJniStorage(); lpJniStorage->mCallbackData.audioTrack_class = (jclass)env->NewGlobalRef(clazz); // we use a weak reference so the AudioTrack object can be garbage collected. lpJniStorage->mCallbackData.audioTrack_ref = env->NewGlobalRef(weak_this); lpJniStorage->mCallbackData.isOffload = offload; lpJniStorage->mCallbackData.busy = false; audio_offload_info_t offloadInfo; if (offload == JNI_TRUE) { offloadInfo = AUDIO_INFO_INITIALIZER; offloadInfo.format = format; offloadInfo.sample_rate = sampleRateInHertz; offloadInfo.channel_mask = nativeChannelMask; offloadInfo.has_video = false; offloadInfo.stream_type = AUDIO_STREAM_MUSIC; //required for offload } // initialize the native AudioTrack object status_t status = NO_ERROR; switch (memoryMode) { case MODE_STREAM: status = lpTrack->set( AUDIO_STREAM_DEFAULT,// stream type, but more info conveyed in paa (last argument) sampleRateInHertz, format,// word length, PCM nativeChannelMask, offload ? 0 : frameCount, offload ? AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD : AUDIO_OUTPUT_FLAG_NONE, audioCallback, &(lpJniStorage->mCallbackData),//callback, callback data (user) 0,// notificationFrames == 0 since not using EVENT_MORE_DATA to feed the AudioTrack 0,// shared mem true,// thread can call Java sessionId,// audio session ID offload ? AudioTrack::TRANSFER_SYNC_NOTIF_CALLBACK : AudioTrack::TRANSFER_SYNC, offload ? &offloadInfo : NULL, -1, -1, // default uid, pid values paa.get()); break; case MODE_STATIC: // AudioTrack is using shared memory if (!lpJniStorage->allocSharedMem(buffSizeInBytes)) { ALOGE("Error creating AudioTrack in static mode: error creating mem heap base"); goto native_init_failure; } status = lpTrack->set( AUDIO_STREAM_DEFAULT,// stream type, but more info conveyed in paa (last argument) sampleRateInHertz, format,// word length, PCM nativeChannelMask, frameCount, AUDIO_OUTPUT_FLAG_NONE, audioCallback, &(lpJniStorage->mCallbackData),//callback, callback data (user)); 0,// notificationFrames == 0 since not using EVENT_MORE_DATA to feed the AudioTrack lpJniStorage->mMemBase,// shared mem true,// thread can call Java sessionId,// audio session ID AudioTrack::TRANSFER_SHARED, NULL, // default offloadInfo -1, -1, // default uid, pid values paa.get()); break; default: ALOGE("Unknown mode %d", memoryMode); goto native_init_failure; } if (status != NO_ERROR) { ALOGE("Error %d initializing AudioTrack", status); goto native_init_failure; } } else { // end if (nativeAudioTrack == 0) lpTrack = (AudioTrack*)nativeAudioTrack; // TODO: We need to find out which members of the Java AudioTrack might // need to be initialized from the Native AudioTrack // these are directly returned from getters: // mSampleRate // mAudioFormat // mStreamType // mChannelConfiguration // mChannelCount // mState (?) // mPlayState (?) // these may be used internally (Java AudioTrack.audioParamCheck(): // mChannelMask // mChannelIndexMask // mDataLoadMode // initialize the callback information: // this data will be passed with every AudioTrack callback lpJniStorage = new AudioTrackJniStorage(); lpJniStorage->mCallbackData.audioTrack_class = (jclass)env->NewGlobalRef(clazz); // we use a weak reference so the AudioTrack object can be garbage collected. lpJniStorage->mCallbackData.audioTrack_ref = env->NewGlobalRef(weak_this); lpJniStorage->mCallbackData.busy = false; } nSession = (jint *) env->GetPrimitiveArrayCritical(jSession, NULL); if (nSession == NULL) { ALOGE("Error creating AudioTrack: Error retrieving session id pointer"); goto native_init_failure; } // read the audio session ID back from AudioTrack in case we create a new session nSession[0] = lpTrack->getSessionId(); env->ReleasePrimitiveArrayCritical(jSession, nSession, 0); nSession = NULL; { const jint elements[1] = { (jint) lpTrack->getSampleRate() }; env->SetIntArrayRegion(jSampleRate, 0, 1, elements); } { // scope for the lock Mutex::Autolock l(sLock); sAudioTrackCallBackCookies.add(&lpJniStorage->mCallbackData); } // save our newly created C++ AudioTrack in the "nativeTrackInJavaObj" field // of the Java object (in mNativeTrackInJavaObj) setAudioTrack(env, thiz, lpTrack); // save the JNI resources so we can free them later //ALOGV("storing lpJniStorage: %x\n", (long)lpJniStorage); env->SetLongField(thiz, javaAudioTrackFields.jniData, (jlong)lpJniStorage); // since we had audio attributes, the stream type was derived from them during the // creation of the native AudioTrack: push the same value to the Java object env->SetIntField(thiz, javaAudioTrackFields.fieldStreamType, (jint) lpTrack->streamType()); return (jint) AUDIO_JAVA_SUCCESS; // failures: native_init_failure: if (nSession != NULL) { env->ReleasePrimitiveArrayCritical(jSession, nSession, 0); } env->DeleteGlobalRef(lpJniStorage->mCallbackData.audioTrack_class); env->DeleteGlobalRef(lpJniStorage->mCallbackData.audioTrack_ref); delete lpJniStorage; env->SetLongField(thiz, javaAudioTrackFields.jniData, 0); // lpTrack goes out of scope, so reference count drops to zero return (jint) AUDIOTRACK_ERROR_SETUP_NATIVEINITFAILED; } // ---------------------------------------------------------------------------- static jboolean android_media_AudioTrack_is_direct_output_supported(JNIEnv *env, jobject thiz, jint encoding, jint sampleRate, jint channelMask, jint channelIndexMask, jint contentType, jint usage, jint flags) { audio_config_base_t config = {}; audio_attributes_t attributes = {}; config.format = static_cast(audioFormatToNative(encoding)); config.sample_rate = static_cast(sampleRate); config.channel_mask = nativeChannelMaskFromJavaChannelMasks(channelMask, channelIndexMask); attributes.content_type = static_cast(contentType); attributes.usage = static_cast(usage); attributes.flags = static_cast(flags); // ignore source and tags attributes as they don't affect querying whether output is supported return AudioTrack::isDirectOutputSupported(config, attributes); } // ---------------------------------------------------------------------------- static void android_media_AudioTrack_start(JNIEnv *env, jobject thiz) { sp lpTrack = getAudioTrack(env, thiz); if (lpTrack == NULL) { jniThrowException(env, "java/lang/IllegalStateException", "Unable to retrieve AudioTrack pointer for start()"); return; } lpTrack->start(); } // ---------------------------------------------------------------------------- static void android_media_AudioTrack_stop(JNIEnv *env, jobject thiz) { sp lpTrack = getAudioTrack(env, thiz); if (lpTrack == NULL) { jniThrowException(env, "java/lang/IllegalStateException", "Unable to retrieve AudioTrack pointer for stop()"); return; } lpTrack->stop(); } // ---------------------------------------------------------------------------- static void android_media_AudioTrack_pause(JNIEnv *env, jobject thiz) { sp lpTrack = getAudioTrack(env, thiz); if (lpTrack == NULL) { jniThrowException(env, "java/lang/IllegalStateException", "Unable to retrieve AudioTrack pointer for pause()"); return; } lpTrack->pause(); } // ---------------------------------------------------------------------------- static void android_media_AudioTrack_flush(JNIEnv *env, jobject thiz) { sp lpTrack = getAudioTrack(env, thiz); if (lpTrack == NULL) { jniThrowException(env, "java/lang/IllegalStateException", "Unable to retrieve AudioTrack pointer for flush()"); return; } lpTrack->flush(); } // ---------------------------------------------------------------------------- static void android_media_AudioTrack_set_volume(JNIEnv *env, jobject thiz, jfloat leftVol, jfloat rightVol ) { sp lpTrack = getAudioTrack(env, thiz); if (lpTrack == NULL) { jniThrowException(env, "java/lang/IllegalStateException", "Unable to retrieve AudioTrack pointer for setVolume()"); return; } lpTrack->setVolume(leftVol, rightVol); } // ---------------------------------------------------------------------------- #define CALLBACK_COND_WAIT_TIMEOUT_MS 1000 static void android_media_AudioTrack_release(JNIEnv *env, jobject thiz) { sp lpTrack = setAudioTrack(env, thiz, 0); if (lpTrack == NULL) { return; } //ALOGV("deleting lpTrack: %x\n", (int)lpTrack); // delete the JNI data AudioTrackJniStorage* pJniStorage = (AudioTrackJniStorage *)env->GetLongField( thiz, javaAudioTrackFields.jniData); // reset the native resources in the Java object so any attempt to access // them after a call to release fails. env->SetLongField(thiz, javaAudioTrackFields.jniData, 0); if (pJniStorage) { Mutex::Autolock l(sLock); audiotrack_callback_cookie *lpCookie = &pJniStorage->mCallbackData; //ALOGV("deleting pJniStorage: %x\n", (int)pJniStorage); while (lpCookie->busy) { if (lpCookie->cond.waitRelative(sLock, milliseconds(CALLBACK_COND_WAIT_TIMEOUT_MS)) != NO_ERROR) { break; } } sAudioTrackCallBackCookies.remove(lpCookie); // delete global refs created in native_setup env->DeleteGlobalRef(lpCookie->audioTrack_class); env->DeleteGlobalRef(lpCookie->audioTrack_ref); delete pJniStorage; } } // ---------------------------------------------------------------------------- static void android_media_AudioTrack_finalize(JNIEnv *env, jobject thiz) { //ALOGV("android_media_AudioTrack_finalize jobject: %x\n", (int)thiz); android_media_AudioTrack_release(env, thiz); } // overloaded JNI array helper functions (same as in android_media_AudioRecord) static inline jbyte *envGetArrayElements(JNIEnv *env, jbyteArray array, jboolean *isCopy) { return env->GetByteArrayElements(array, isCopy); } static inline void envReleaseArrayElements(JNIEnv *env, jbyteArray array, jbyte *elems, jint mode) { env->ReleaseByteArrayElements(array, elems, mode); } static inline jshort *envGetArrayElements(JNIEnv *env, jshortArray array, jboolean *isCopy) { return env->GetShortArrayElements(array, isCopy); } static inline void envReleaseArrayElements(JNIEnv *env, jshortArray array, jshort *elems, jint mode) { env->ReleaseShortArrayElements(array, elems, mode); } static inline jfloat *envGetArrayElements(JNIEnv *env, jfloatArray array, jboolean *isCopy) { return env->GetFloatArrayElements(array, isCopy); } static inline void envReleaseArrayElements(JNIEnv *env, jfloatArray array, jfloat *elems, jint mode) { env->ReleaseFloatArrayElements(array, elems, mode); } static inline jint interpretWriteSizeError(ssize_t writeSize) { if (writeSize == WOULD_BLOCK) { return (jint)0; } else if (writeSize == NO_INIT) { return AUDIO_JAVA_DEAD_OBJECT; } else { ALOGE("Error %zd during AudioTrack native read", writeSize); return nativeToJavaStatus(writeSize); } } // ---------------------------------------------------------------------------- template static jint writeToTrack(const sp& track, jint audioFormat, const T *data, jint offsetInSamples, jint sizeInSamples, bool blocking) { // give the data to the native AudioTrack object (the data starts at the offset) ssize_t written = 0; // regular write() or copy the data to the AudioTrack's shared memory? size_t sizeInBytes = sizeInSamples * sizeof(T); if (track->sharedBuffer() == 0) { written = track->write(data + offsetInSamples, sizeInBytes, blocking); // for compatibility with earlier behavior of write(), return 0 in this case if (written == (ssize_t) WOULD_BLOCK) { written = 0; } } else { // writing to shared memory, check for capacity if ((size_t)sizeInBytes > track->sharedBuffer()->size()) { sizeInBytes = track->sharedBuffer()->size(); } memcpy(track->sharedBuffer()->pointer(), data + offsetInSamples, sizeInBytes); written = sizeInBytes; } if (written >= 0) { return written / sizeof(T); } return interpretWriteSizeError(written); } // ---------------------------------------------------------------------------- template static jint android_media_AudioTrack_writeArray(JNIEnv *env, jobject thiz, T javaAudioData, jint offsetInSamples, jint sizeInSamples, jint javaAudioFormat, jboolean isWriteBlocking) { //ALOGV("android_media_AudioTrack_writeArray(offset=%d, sizeInSamples=%d) called", // offsetInSamples, sizeInSamples); sp lpTrack = getAudioTrack(env, thiz); if (lpTrack == NULL) { jniThrowException(env, "java/lang/IllegalStateException", "Unable to retrieve AudioTrack pointer for write()"); return (jint)AUDIO_JAVA_INVALID_OPERATION; } if (javaAudioData == NULL) { ALOGE("NULL java array of audio data to play"); return (jint)AUDIO_JAVA_BAD_VALUE; } // NOTE: We may use GetPrimitiveArrayCritical() when the JNI implementation changes in such // a way that it becomes much more efficient. When doing so, we will have to prevent the // AudioSystem callback to be called while in critical section (in case of media server // process crash for instance) // get the pointer for the audio data from the java array auto cAudioData = envGetArrayElements(env, javaAudioData, NULL); if (cAudioData == NULL) { ALOGE("Error retrieving source of audio data to play"); return (jint)AUDIO_JAVA_BAD_VALUE; // out of memory or no data to load } jint samplesWritten = writeToTrack(lpTrack, javaAudioFormat, cAudioData, offsetInSamples, sizeInSamples, isWriteBlocking == JNI_TRUE /* blocking */); envReleaseArrayElements(env, javaAudioData, cAudioData, 0); //ALOGV("write wrote %d (tried %d) samples in the native AudioTrack with offset %d", // (int)samplesWritten, (int)(sizeInSamples), (int)offsetInSamples); return samplesWritten; } // ---------------------------------------------------------------------------- static jint android_media_AudioTrack_write_native_bytes(JNIEnv *env, jobject thiz, jobject javaByteBuffer, jint byteOffset, jint sizeInBytes, jint javaAudioFormat, jboolean isWriteBlocking) { //ALOGV("android_media_AudioTrack_write_native_bytes(offset=%d, sizeInBytes=%d) called", // offsetInBytes, sizeInBytes); sp lpTrack = getAudioTrack(env, thiz); if (lpTrack == NULL) { jniThrowException(env, "java/lang/IllegalStateException", "Unable to retrieve AudioTrack pointer for write()"); return (jint)AUDIO_JAVA_INVALID_OPERATION; } const jbyte* bytes = reinterpret_cast(env->GetDirectBufferAddress(javaByteBuffer)); if (bytes == NULL) { ALOGE("Error retrieving source of audio data to play, can't play"); return (jint)AUDIO_JAVA_BAD_VALUE; } jint written = writeToTrack(lpTrack, javaAudioFormat, bytes, byteOffset, sizeInBytes, isWriteBlocking == JNI_TRUE /* blocking */); return written; } // ---------------------------------------------------------------------------- static jint android_media_AudioTrack_get_buffer_size_frames(JNIEnv *env, jobject thiz) { sp lpTrack = getAudioTrack(env, thiz); if (lpTrack == NULL) { jniThrowException(env, "java/lang/IllegalStateException", "Unable to retrieve AudioTrack pointer for getBufferSizeInFrames()"); return (jint)AUDIO_JAVA_ERROR; } ssize_t result = lpTrack->getBufferSizeInFrames(); if (result < 0) { jniThrowExceptionFmt(env, "java/lang/IllegalStateException", "Internal error detected in getBufferSizeInFrames() = %zd", result); return (jint)AUDIO_JAVA_ERROR; } return (jint)result; } // ---------------------------------------------------------------------------- static jint android_media_AudioTrack_set_buffer_size_frames(JNIEnv *env, jobject thiz, jint bufferSizeInFrames) { sp lpTrack = getAudioTrack(env, thiz); if (lpTrack == NULL) { jniThrowException(env, "java/lang/IllegalStateException", "Unable to retrieve AudioTrack pointer for setBufferSizeInFrames()"); return (jint)AUDIO_JAVA_ERROR; } // Value will be coerced into the valid range. // But internal values are unsigned, size_t, so we need to clip // against zero here where it is signed. if (bufferSizeInFrames < 0) { bufferSizeInFrames = 0; } ssize_t result = lpTrack->setBufferSizeInFrames(bufferSizeInFrames); if (result < 0) { jniThrowExceptionFmt(env, "java/lang/IllegalStateException", "Internal error detected in setBufferSizeInFrames() = %zd", result); return (jint)AUDIO_JAVA_ERROR; } return (jint)result; } // ---------------------------------------------------------------------------- static jint android_media_AudioTrack_get_buffer_capacity_frames(JNIEnv *env, jobject thiz) { sp lpTrack = getAudioTrack(env, thiz); if (lpTrack == NULL) { jniThrowException(env, "java/lang/IllegalStateException", "Unable to retrieve AudioTrack pointer for getBufferCapacityInFrames()"); return (jint)AUDIO_JAVA_ERROR; } return lpTrack->frameCount(); } // ---------------------------------------------------------------------------- static jint android_media_AudioTrack_set_playback_rate(JNIEnv *env, jobject thiz, jint sampleRateInHz) { sp lpTrack = getAudioTrack(env, thiz); if (lpTrack == NULL) { jniThrowException(env, "java/lang/IllegalStateException", "Unable to retrieve AudioTrack pointer for setSampleRate()"); return (jint)AUDIO_JAVA_ERROR; } return nativeToJavaStatus(lpTrack->setSampleRate(sampleRateInHz)); } // ---------------------------------------------------------------------------- static jint android_media_AudioTrack_get_playback_rate(JNIEnv *env, jobject thiz) { sp lpTrack = getAudioTrack(env, thiz); if (lpTrack == NULL) { jniThrowException(env, "java/lang/IllegalStateException", "Unable to retrieve AudioTrack pointer for getSampleRate()"); return (jint)AUDIO_JAVA_ERROR; } return (jint) lpTrack->getSampleRate(); } // ---------------------------------------------------------------------------- static void android_media_AudioTrack_set_playback_params(JNIEnv *env, jobject thiz, jobject params) { sp lpTrack = getAudioTrack(env, thiz); if (lpTrack == NULL) { jniThrowException(env, "java/lang/IllegalStateException", "AudioTrack not initialized"); return; } PlaybackParams pbp; pbp.fillFromJobject(env, gPlaybackParamsFields, params); ALOGV("setPlaybackParams: %d:%f %d:%f %d:%u %d:%u", pbp.speedSet, pbp.audioRate.mSpeed, pbp.pitchSet, pbp.audioRate.mPitch, pbp.audioFallbackModeSet, pbp.audioRate.mFallbackMode, pbp.audioStretchModeSet, pbp.audioRate.mStretchMode); // to simulate partially set params, we do a read-modify-write. // TODO: pass in the valid set mask into AudioTrack. AudioPlaybackRate rate = lpTrack->getPlaybackRate(); bool updatedRate = false; if (pbp.speedSet) { rate.mSpeed = pbp.audioRate.mSpeed; updatedRate = true; } if (pbp.pitchSet) { rate.mPitch = pbp.audioRate.mPitch; updatedRate = true; } if (pbp.audioFallbackModeSet) { rate.mFallbackMode = pbp.audioRate.mFallbackMode; updatedRate = true; } if (pbp.audioStretchModeSet) { rate.mStretchMode = pbp.audioRate.mStretchMode; updatedRate = true; } if (updatedRate) { if (lpTrack->setPlaybackRate(rate) != OK) { jniThrowException(env, "java/lang/IllegalArgumentException", "arguments out of range"); } } } // ---------------------------------------------------------------------------- static jobject android_media_AudioTrack_get_playback_params(JNIEnv *env, jobject thiz, jobject params) { sp lpTrack = getAudioTrack(env, thiz); if (lpTrack == NULL) { jniThrowException(env, "java/lang/IllegalStateException", "AudioTrack not initialized"); return NULL; } PlaybackParams pbs; pbs.audioRate = lpTrack->getPlaybackRate(); pbs.speedSet = true; pbs.pitchSet = true; pbs.audioFallbackModeSet = true; pbs.audioStretchModeSet = true; return pbs.asJobject(env, gPlaybackParamsFields); } // ---------------------------------------------------------------------------- static jint android_media_AudioTrack_set_marker_pos(JNIEnv *env, jobject thiz, jint markerPos) { sp lpTrack = getAudioTrack(env, thiz); if (lpTrack == NULL) { jniThrowException(env, "java/lang/IllegalStateException", "Unable to retrieve AudioTrack pointer for setMarkerPosition()"); return (jint)AUDIO_JAVA_ERROR; } return nativeToJavaStatus( lpTrack->setMarkerPosition(markerPos) ); } // ---------------------------------------------------------------------------- static jint android_media_AudioTrack_get_marker_pos(JNIEnv *env, jobject thiz) { sp lpTrack = getAudioTrack(env, thiz); uint32_t markerPos = 0; if (lpTrack == NULL) { jniThrowException(env, "java/lang/IllegalStateException", "Unable to retrieve AudioTrack pointer for getMarkerPosition()"); return (jint)AUDIO_JAVA_ERROR; } lpTrack->getMarkerPosition(&markerPos); return (jint)markerPos; } // ---------------------------------------------------------------------------- static jint android_media_AudioTrack_set_pos_update_period(JNIEnv *env, jobject thiz, jint period) { sp lpTrack = getAudioTrack(env, thiz); if (lpTrack == NULL) { jniThrowException(env, "java/lang/IllegalStateException", "Unable to retrieve AudioTrack pointer for setPositionUpdatePeriod()"); return (jint)AUDIO_JAVA_ERROR; } return nativeToJavaStatus( lpTrack->setPositionUpdatePeriod(period) ); } // ---------------------------------------------------------------------------- static jint android_media_AudioTrack_get_pos_update_period(JNIEnv *env, jobject thiz) { sp lpTrack = getAudioTrack(env, thiz); uint32_t period = 0; if (lpTrack == NULL) { jniThrowException(env, "java/lang/IllegalStateException", "Unable to retrieve AudioTrack pointer for getPositionUpdatePeriod()"); return (jint)AUDIO_JAVA_ERROR; } lpTrack->getPositionUpdatePeriod(&period); return (jint)period; } // ---------------------------------------------------------------------------- static jint android_media_AudioTrack_set_position(JNIEnv *env, jobject thiz, jint position) { sp lpTrack = getAudioTrack(env, thiz); if (lpTrack == NULL) { jniThrowException(env, "java/lang/IllegalStateException", "Unable to retrieve AudioTrack pointer for setPosition()"); return (jint)AUDIO_JAVA_ERROR; } return nativeToJavaStatus( lpTrack->setPosition(position) ); } // ---------------------------------------------------------------------------- static jint android_media_AudioTrack_get_position(JNIEnv *env, jobject thiz) { sp lpTrack = getAudioTrack(env, thiz); uint32_t position = 0; if (lpTrack == NULL) { jniThrowException(env, "java/lang/IllegalStateException", "Unable to retrieve AudioTrack pointer for getPosition()"); return (jint)AUDIO_JAVA_ERROR; } lpTrack->getPosition(&position); return (jint)position; } // ---------------------------------------------------------------------------- static jint android_media_AudioTrack_get_latency(JNIEnv *env, jobject thiz) { sp lpTrack = getAudioTrack(env, thiz); if (lpTrack == NULL) { jniThrowException(env, "java/lang/IllegalStateException", "Unable to retrieve AudioTrack pointer for latency()"); return (jint)AUDIO_JAVA_ERROR; } return (jint)lpTrack->latency(); } // ---------------------------------------------------------------------------- static jint android_media_AudioTrack_get_underrun_count(JNIEnv *env, jobject thiz) { sp lpTrack = getAudioTrack(env, thiz); if (lpTrack == NULL) { jniThrowException(env, "java/lang/IllegalStateException", "Unable to retrieve AudioTrack pointer for getUnderrunCount()"); return (jint)AUDIO_JAVA_ERROR; } return (jint)lpTrack->getUnderrunCount(); } // ---------------------------------------------------------------------------- static jint android_media_AudioTrack_get_flags(JNIEnv *env, jobject thiz) { sp lpTrack = getAudioTrack(env, thiz); if (lpTrack == NULL) { jniThrowException(env, "java/lang/IllegalStateException", "Unable to retrieve AudioTrack pointer for getFlags()"); return (jint)AUDIO_JAVA_ERROR; } return (jint)lpTrack->getFlags(); } // ---------------------------------------------------------------------------- static jint android_media_AudioTrack_get_timestamp(JNIEnv *env, jobject thiz, jlongArray jTimestamp) { sp lpTrack = getAudioTrack(env, thiz); if (lpTrack == NULL) { ALOGE("Unable to retrieve AudioTrack pointer for getTimestamp()"); return (jint)AUDIO_JAVA_ERROR; } AudioTimestamp timestamp; status_t status = lpTrack->getTimestamp(timestamp); if (status == OK) { jlong* nTimestamp = (jlong *) env->GetPrimitiveArrayCritical(jTimestamp, NULL); if (nTimestamp == NULL) { ALOGE("Unable to get array for getTimestamp()"); return (jint)AUDIO_JAVA_ERROR; } nTimestamp[0] = (jlong) timestamp.mPosition; nTimestamp[1] = (jlong) ((timestamp.mTime.tv_sec * 1000000000LL) + timestamp.mTime.tv_nsec); env->ReleasePrimitiveArrayCritical(jTimestamp, nTimestamp, 0); } return (jint) nativeToJavaStatus(status); } // ---------------------------------------------------------------------------- static jobject android_media_AudioTrack_native_getMetrics(JNIEnv *env, jobject thiz) { ALOGD("android_media_AudioTrack_native_getMetrics"); sp lpTrack = getAudioTrack(env, thiz); if (lpTrack == NULL) { ALOGE("Unable to retrieve AudioTrack pointer for getMetrics()"); jniThrowException(env, "java/lang/IllegalStateException", NULL); return (jobject) NULL; } // get what we have for the metrics from the track MediaAnalyticsItem *item = NULL; status_t err = lpTrack->getMetrics(item); if (err != OK) { ALOGE("getMetrics failed"); jniThrowException(env, "java/lang/IllegalStateException", NULL); return (jobject) NULL; } jobject mybundle = MediaMetricsJNI::writeMetricsToBundle(env, item, NULL /* mybundle */); // housekeeping delete item; item = NULL; return mybundle; } // ---------------------------------------------------------------------------- static jint android_media_AudioTrack_set_loop(JNIEnv *env, jobject thiz, jint loopStart, jint loopEnd, jint loopCount) { sp lpTrack = getAudioTrack(env, thiz); if (lpTrack == NULL) { jniThrowException(env, "java/lang/IllegalStateException", "Unable to retrieve AudioTrack pointer for setLoop()"); return (jint)AUDIO_JAVA_ERROR; } return nativeToJavaStatus( lpTrack->setLoop(loopStart, loopEnd, loopCount) ); } // ---------------------------------------------------------------------------- static jint android_media_AudioTrack_reload(JNIEnv *env, jobject thiz) { sp lpTrack = getAudioTrack(env, thiz); if (lpTrack == NULL) { jniThrowException(env, "java/lang/IllegalStateException", "Unable to retrieve AudioTrack pointer for reload()"); return (jint)AUDIO_JAVA_ERROR; } return nativeToJavaStatus( lpTrack->reload() ); } // ---------------------------------------------------------------------------- static jint android_media_AudioTrack_get_output_sample_rate(JNIEnv *env, jobject thiz, jint javaStreamType) { uint32_t afSamplingRate; // convert the stream type from Java to native value // FIXME: code duplication with android_media_AudioTrack_setup() audio_stream_type_t nativeStreamType; switch (javaStreamType) { case AUDIO_STREAM_VOICE_CALL: case AUDIO_STREAM_SYSTEM: case AUDIO_STREAM_RING: case AUDIO_STREAM_MUSIC: case AUDIO_STREAM_ALARM: case AUDIO_STREAM_NOTIFICATION: case AUDIO_STREAM_BLUETOOTH_SCO: case AUDIO_STREAM_DTMF: nativeStreamType = (audio_stream_type_t) javaStreamType; break; default: nativeStreamType = AUDIO_STREAM_DEFAULT; break; } status_t status = AudioSystem::getOutputSamplingRate(&afSamplingRate, nativeStreamType); if (status != NO_ERROR) { ALOGE("Error %d in AudioSystem::getOutputSamplingRate() for stream type %d " "in AudioTrack JNI", status, nativeStreamType); return DEFAULT_OUTPUT_SAMPLE_RATE; } else { return afSamplingRate; } } // ---------------------------------------------------------------------------- // returns the minimum required size for the successful creation of a streaming AudioTrack // returns -1 if there was an error querying the hardware. static jint android_media_AudioTrack_get_min_buff_size(JNIEnv *env, jobject thiz, jint sampleRateInHertz, jint channelCount, jint audioFormat) { size_t frameCount; const status_t status = AudioTrack::getMinFrameCount(&frameCount, AUDIO_STREAM_DEFAULT, sampleRateInHertz); if (status != NO_ERROR) { ALOGE("AudioTrack::getMinFrameCount() for sample rate %d failed with status %d", sampleRateInHertz, status); return -1; } const audio_format_t format = audioFormatToNative(audioFormat); if (audio_has_proportional_frames(format)) { const size_t bytesPerSample = audio_bytes_per_sample(format); return frameCount * channelCount * bytesPerSample; } else { return frameCount; } } // ---------------------------------------------------------------------------- static jint android_media_AudioTrack_setAuxEffectSendLevel(JNIEnv *env, jobject thiz, jfloat level ) { sp lpTrack = getAudioTrack(env, thiz); if (lpTrack == NULL ) { jniThrowException(env, "java/lang/IllegalStateException", "Unable to retrieve AudioTrack pointer for setAuxEffectSendLevel()"); return -1; } status_t status = lpTrack->setAuxEffectSendLevel(level); if (status != NO_ERROR) { ALOGE("AudioTrack::setAuxEffectSendLevel() for level %g failed with status %d", level, status); } return (jint) status; } // ---------------------------------------------------------------------------- static jint android_media_AudioTrack_attachAuxEffect(JNIEnv *env, jobject thiz, jint effectId) { sp lpTrack = getAudioTrack(env, thiz); if (lpTrack == NULL) { jniThrowException(env, "java/lang/IllegalStateException", "Unable to retrieve AudioTrack pointer for attachAuxEffect()"); return (jint)AUDIO_JAVA_ERROR; } return nativeToJavaStatus( lpTrack->attachAuxEffect(effectId) ); } static jboolean android_media_AudioTrack_setOutputDevice( JNIEnv *env, jobject thiz, jint device_id) { sp lpTrack = getAudioTrack(env, thiz); if (lpTrack == 0) { return false; } return lpTrack->setOutputDevice(device_id) == NO_ERROR; } static jint android_media_AudioTrack_getRoutedDeviceId( JNIEnv *env, jobject thiz) { sp lpTrack = getAudioTrack(env, thiz); if (lpTrack == NULL) { return 0; } return (jint)lpTrack->getRoutedDeviceId(); } static void android_media_AudioTrack_enableDeviceCallback( JNIEnv *env, jobject thiz) { sp lpTrack = getAudioTrack(env, thiz); if (lpTrack == NULL) { return; } AudioTrackJniStorage* pJniStorage = (AudioTrackJniStorage *)env->GetLongField( thiz, javaAudioTrackFields.jniData); if (pJniStorage == NULL || pJniStorage->mDeviceCallback != 0) { return; } pJniStorage->mDeviceCallback = new JNIDeviceCallback(env, thiz, pJniStorage->mCallbackData.audioTrack_ref, javaAudioTrackFields.postNativeEventInJava); lpTrack->addAudioDeviceCallback(pJniStorage->mDeviceCallback); } static void android_media_AudioTrack_disableDeviceCallback( JNIEnv *env, jobject thiz) { sp lpTrack = getAudioTrack(env, thiz); if (lpTrack == NULL) { return; } AudioTrackJniStorage* pJniStorage = (AudioTrackJniStorage *)env->GetLongField( thiz, javaAudioTrackFields.jniData); if (pJniStorage == NULL || pJniStorage->mDeviceCallback == 0) { return; } lpTrack->removeAudioDeviceCallback(pJniStorage->mDeviceCallback); pJniStorage->mDeviceCallback.clear(); } // Pass through the arguments to the AudioFlinger track implementation. static jint android_media_AudioTrack_apply_volume_shaper(JNIEnv *env, jobject thiz, jobject jconfig, jobject joperation) { // NOTE: hard code here to prevent platform issues. Must match VolumeShaper.java const int VOLUME_SHAPER_INVALID_OPERATION = -38; sp lpTrack = getAudioTrack(env, thiz); if (lpTrack == nullptr) { return (jint)VOLUME_SHAPER_INVALID_OPERATION; } sp configuration; sp operation; if (jconfig != nullptr) { configuration = VolumeShaperHelper::convertJobjectToConfiguration( env, gVolumeShaperFields, jconfig); ALOGV("applyVolumeShaper configuration: %s", configuration->toString().c_str()); } if (joperation != nullptr) { operation = VolumeShaperHelper::convertJobjectToOperation( env, gVolumeShaperFields, joperation); ALOGV("applyVolumeShaper operation: %s", operation->toString().c_str()); } VolumeShaper::Status status = lpTrack->applyVolumeShaper(configuration, operation); if (status == INVALID_OPERATION) { status = VOLUME_SHAPER_INVALID_OPERATION; } return (jint)status; // if status < 0 an error, else a VolumeShaper id } // Pass through the arguments to the AudioFlinger track implementation. static jobject android_media_AudioTrack_get_volume_shaper_state(JNIEnv *env, jobject thiz, jint id) { sp lpTrack = getAudioTrack(env, thiz); if (lpTrack == nullptr) { return (jobject)nullptr; } sp state = lpTrack->getVolumeShaperState((int)id); if (state.get() == nullptr) { return (jobject)nullptr; } return VolumeShaperHelper::convertStateToJobject(env, gVolumeShaperFields, state); } static int android_media_AudioTrack_setPresentation( JNIEnv *env, jobject thiz, jint presentationId, jint programId) { sp lpTrack = getAudioTrack(env, thiz); if (lpTrack == NULL) { jniThrowException(env, "java/lang/IllegalStateException", "AudioTrack not initialized"); return (jint)AUDIO_JAVA_ERROR; } return (jint)lpTrack->selectPresentation((int)presentationId, (int)programId); } // ---------------------------------------------------------------------------- static jint android_media_AudioTrack_get_port_id(JNIEnv *env, jobject thiz) { sp lpTrack = getAudioTrack(env, thiz); if (lpTrack == NULL) { jniThrowException(env, "java/lang/IllegalStateException", "AudioTrack not initialized"); return (jint)AUDIO_PORT_HANDLE_NONE; } return (jint)lpTrack->getPortId(); } // ---------------------------------------------------------------------------- static void android_media_AudioTrack_set_delay_padding(JNIEnv *env, jobject thiz, jint delayInFrames, jint paddingInFrames) { sp lpTrack = getAudioTrack(env, thiz); if (lpTrack == NULL) { jniThrowException(env, "java/lang/IllegalStateException", "AudioTrack not initialized"); return; } AudioParameter param = AudioParameter(); param.addInt(String8(AUDIO_OFFLOAD_CODEC_DELAY_SAMPLES), (int) delayInFrames); param.addInt(String8(AUDIO_OFFLOAD_CODEC_PADDING_SAMPLES), (int) paddingInFrames); lpTrack->setParameters(param.toString()); } // ---------------------------------------------------------------------------- // ---------------------------------------------------------------------------- static const JNINativeMethod gMethods[] = { // name, signature, funcPtr {"native_is_direct_output_supported", "(IIIIIII)Z", (void *)android_media_AudioTrack_is_direct_output_supported}, {"native_start", "()V", (void *)android_media_AudioTrack_start}, {"native_stop", "()V", (void *)android_media_AudioTrack_stop}, {"native_pause", "()V", (void *)android_media_AudioTrack_pause}, {"native_flush", "()V", (void *)android_media_AudioTrack_flush}, {"native_setup", "(Ljava/lang/Object;Ljava/lang/Object;[IIIIII[IJZ)I", (void *)android_media_AudioTrack_setup}, {"native_finalize", "()V", (void *)android_media_AudioTrack_finalize}, {"native_release", "()V", (void *)android_media_AudioTrack_release}, {"native_write_byte", "([BIIIZ)I",(void *)android_media_AudioTrack_writeArray}, {"native_write_native_bytes", "(Ljava/nio/ByteBuffer;IIIZ)I", (void *)android_media_AudioTrack_write_native_bytes}, {"native_write_short", "([SIIIZ)I",(void *)android_media_AudioTrack_writeArray}, {"native_write_float", "([FIIIZ)I",(void *)android_media_AudioTrack_writeArray}, {"native_setVolume", "(FF)V", (void *)android_media_AudioTrack_set_volume}, {"native_get_buffer_size_frames", "()I", (void *)android_media_AudioTrack_get_buffer_size_frames}, {"native_set_buffer_size_frames", "(I)I", (void *)android_media_AudioTrack_set_buffer_size_frames}, {"native_get_buffer_capacity_frames", "()I", (void *)android_media_AudioTrack_get_buffer_capacity_frames}, {"native_set_playback_rate", "(I)I", (void *)android_media_AudioTrack_set_playback_rate}, {"native_get_playback_rate", "()I", (void *)android_media_AudioTrack_get_playback_rate}, {"native_set_playback_params", "(Landroid/media/PlaybackParams;)V", (void *)android_media_AudioTrack_set_playback_params}, {"native_get_playback_params", "()Landroid/media/PlaybackParams;", (void *)android_media_AudioTrack_get_playback_params}, {"native_set_marker_pos","(I)I", (void *)android_media_AudioTrack_set_marker_pos}, {"native_get_marker_pos","()I", (void *)android_media_AudioTrack_get_marker_pos}, {"native_set_pos_update_period", "(I)I", (void *)android_media_AudioTrack_set_pos_update_period}, {"native_get_pos_update_period", "()I", (void *)android_media_AudioTrack_get_pos_update_period}, {"native_set_position", "(I)I", (void *)android_media_AudioTrack_set_position}, {"native_get_position", "()I", (void *)android_media_AudioTrack_get_position}, {"native_get_latency", "()I", (void *)android_media_AudioTrack_get_latency}, {"native_get_underrun_count", "()I", (void *)android_media_AudioTrack_get_underrun_count}, {"native_get_flags", "()I", (void *)android_media_AudioTrack_get_flags}, {"native_get_timestamp", "([J)I", (void *)android_media_AudioTrack_get_timestamp}, {"native_getMetrics", "()Landroid/os/PersistableBundle;", (void *)android_media_AudioTrack_native_getMetrics}, {"native_set_loop", "(III)I", (void *)android_media_AudioTrack_set_loop}, {"native_reload_static", "()I", (void *)android_media_AudioTrack_reload}, {"native_get_output_sample_rate", "(I)I", (void *)android_media_AudioTrack_get_output_sample_rate}, {"native_get_min_buff_size", "(III)I", (void *)android_media_AudioTrack_get_min_buff_size}, {"native_setAuxEffectSendLevel", "(F)I", (void *)android_media_AudioTrack_setAuxEffectSendLevel}, {"native_attachAuxEffect", "(I)I", (void *)android_media_AudioTrack_attachAuxEffect}, {"native_setOutputDevice", "(I)Z", (void *)android_media_AudioTrack_setOutputDevice}, {"native_getRoutedDeviceId", "()I", (void *)android_media_AudioTrack_getRoutedDeviceId}, {"native_enableDeviceCallback", "()V", (void *)android_media_AudioTrack_enableDeviceCallback}, {"native_disableDeviceCallback", "()V", (void *)android_media_AudioTrack_disableDeviceCallback}, {"native_applyVolumeShaper", "(Landroid/media/VolumeShaper$Configuration;Landroid/media/VolumeShaper$Operation;)I", (void *)android_media_AudioTrack_apply_volume_shaper}, {"native_getVolumeShaperState", "(I)Landroid/media/VolumeShaper$State;", (void *)android_media_AudioTrack_get_volume_shaper_state}, {"native_setPresentation", "(II)I", (void *)android_media_AudioTrack_setPresentation}, {"native_getPortId", "()I", (void *)android_media_AudioTrack_get_port_id}, {"native_set_delay_padding", "(II)V", (void *)android_media_AudioTrack_set_delay_padding}, }; // field names found in android/media/AudioTrack.java #define JAVA_POSTEVENT_CALLBACK_NAME "postEventFromNative" #define JAVA_NATIVETRACKINJAVAOBJ_FIELD_NAME "mNativeTrackInJavaObj" #define JAVA_JNIDATA_FIELD_NAME "mJniData" #define JAVA_STREAMTYPE_FIELD_NAME "mStreamType" // ---------------------------------------------------------------------------- // preconditions: // theClass is valid bool android_media_getIntConstantFromClass(JNIEnv* pEnv, jclass theClass, const char* className, const char* constName, int* constVal) { jfieldID javaConst = NULL; javaConst = pEnv->GetStaticFieldID(theClass, constName, "I"); if (javaConst != NULL) { *constVal = pEnv->GetStaticIntField(theClass, javaConst); return true; } else { ALOGE("Can't find %s.%s", className, constName); return false; } } // ---------------------------------------------------------------------------- int register_android_media_AudioTrack(JNIEnv *env) { // must be first int res = RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods)); javaAudioTrackFields.nativeTrackInJavaObj = NULL; javaAudioTrackFields.postNativeEventInJava = NULL; // Get the AudioTrack class jclass audioTrackClass = FindClassOrDie(env, kClassPathName); // Get the postEvent method javaAudioTrackFields.postNativeEventInJava = GetStaticMethodIDOrDie(env, audioTrackClass, JAVA_POSTEVENT_CALLBACK_NAME, "(Ljava/lang/Object;IIILjava/lang/Object;)V"); // Get the variables fields // nativeTrackInJavaObj javaAudioTrackFields.nativeTrackInJavaObj = GetFieldIDOrDie(env, audioTrackClass, JAVA_NATIVETRACKINJAVAOBJ_FIELD_NAME, "J"); // jniData javaAudioTrackFields.jniData = GetFieldIDOrDie(env, audioTrackClass, JAVA_JNIDATA_FIELD_NAME, "J"); // fieldStreamType javaAudioTrackFields.fieldStreamType = GetFieldIDOrDie(env, audioTrackClass, JAVA_STREAMTYPE_FIELD_NAME, "I"); env->DeleteLocalRef(audioTrackClass); // initialize PlaybackParams field info gPlaybackParamsFields.init(env); gVolumeShaperFields.init(env); return res; } // ----------------------------------------------------------------------------