diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:31:44 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:31:44 -0800 |
commit | 9066cfe9886ac131c34d59ed0e2d287b0e3c0087 (patch) | |
tree | d88beb88001f2482911e3d28e43833b50e4b4e97 /media/jni | |
parent | d83a98f4ce9cfa908f5c54bbd70f03eec07e7553 (diff) | |
download | frameworks_base-9066cfe9886ac131c34d59ed0e2d287b0e3c0087.zip frameworks_base-9066cfe9886ac131c34d59ed0e2d287b0e3c0087.tar.gz frameworks_base-9066cfe9886ac131c34d59ed0e2d287b0e3c0087.tar.bz2 |
auto import from //depot/cupcake/@135843
Diffstat (limited to 'media/jni')
-rw-r--r-- | media/jni/Android.mk | 42 | ||||
-rw-r--r-- | media/jni/MODULE_LICENSE_APACHE2 | 0 | ||||
-rw-r--r-- | media/jni/NOTICE | 190 | ||||
-rw-r--r-- | media/jni/android_media_AmrInputStream.cpp | 184 | ||||
-rw-r--r-- | media/jni/android_media_MediaMetadataRetriever.cpp | 327 | ||||
-rw-r--r-- | media/jni/android_media_MediaPlayer.cpp | 573 | ||||
-rw-r--r-- | media/jni/android_media_MediaRecorder.cpp | 416 | ||||
-rw-r--r-- | media/jni/android_media_MediaScanner.cpp | 304 | ||||
-rw-r--r-- | media/jni/android_media_ResampleInputStream.cpp | 143 | ||||
-rw-r--r-- | media/jni/soundpool/Android.mk | 18 | ||||
-rw-r--r-- | media/jni/soundpool/SoundPool.cpp | 764 | ||||
-rw-r--r-- | media/jni/soundpool/SoundPool.h | 224 | ||||
-rw-r--r-- | media/jni/soundpool/SoundPoolThread.cpp | 108 | ||||
-rw-r--r-- | media/jni/soundpool/SoundPoolThread.h | 75 | ||||
-rw-r--r-- | media/jni/soundpool/android_media_SoundPool.cpp | 270 |
15 files changed, 3638 insertions, 0 deletions
diff --git a/media/jni/Android.mk b/media/jni/Android.mk new file mode 100644 index 0000000..3620494 --- /dev/null +++ b/media/jni/Android.mk @@ -0,0 +1,42 @@ +ifneq ($(BUILD_WITHOUT_PV),true) +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + android_media_MediaPlayer.cpp \ + android_media_MediaRecorder.cpp \ + android_media_MediaScanner.cpp \ + android_media_MediaMetadataRetriever.cpp \ + android_media_AmrInputStream.cpp \ + android_media_ResampleInputStream.cpp + +LOCAL_SHARED_LIBRARIES := \ + libopencoreplayer \ + libopencoreauthor \ + libandroid_runtime \ + libnativehelper \ + libcutils \ + libutils \ + libmedia \ + libsgl \ + libui + +LOCAL_STATIC_LIBRARIES := + +LOCAL_C_INCLUDES += \ + external/tremor/Tremor \ + $(PV_INCLUDES) \ + $(JNI_H_INCLUDE) \ + $(call include-path-for, corecg graphics) + +LOCAL_CFLAGS += + +LOCAL_LDLIBS := -lpthread + +LOCAL_MODULE:= libmedia_jni + +include $(BUILD_SHARED_LIBRARY) + +# build libsoundpool.so +include $(LOCAL_PATH)/soundpool/Android.mk +endif diff --git a/media/jni/MODULE_LICENSE_APACHE2 b/media/jni/MODULE_LICENSE_APACHE2 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/media/jni/MODULE_LICENSE_APACHE2 diff --git a/media/jni/NOTICE b/media/jni/NOTICE new file mode 100644 index 0000000..c5b1efa --- /dev/null +++ b/media/jni/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2005-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. + + 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. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/media/jni/android_media_AmrInputStream.cpp b/media/jni/android_media_AmrInputStream.cpp new file mode 100644 index 0000000..978c110 --- /dev/null +++ b/media/jni/android_media_AmrInputStream.cpp @@ -0,0 +1,184 @@ +/* +** +** Copyright 2007, 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_TAG "AmrInputStream" +#include "utils/Log.h" + +#include <media/mediarecorder.h> +#include <stdio.h> +#include <assert.h> +#include <limits.h> +#include <unistd.h> +#include <fcntl.h> +#include <utils/threads.h> + +#include "jni.h" +#include "JNIHelp.h" +#include "android_runtime/AndroidRuntime.h" +#include "gsmamr_encoder_wrapper.h" + + +// ---------------------------------------------------------------------------- + +using namespace android; + +// Corresponds to max bit rate of 12.2 kbps. +static const int aMaxOutputBufferSize = 32; + +static const int SAMPLES_PER_FRAME = 8000 * 20 / 1000; + + +// +// helper function to throw an exception +// +static void throwException(JNIEnv *env, const char* ex, const char* fmt, int data) { + if (jclass cls = env->FindClass(ex)) { + char msg[1000]; + sprintf(msg, fmt, data); + env->ThrowNew(cls, msg); + env->DeleteLocalRef(cls); + } +} + +static jint android_media_AmrInputStream_GsmAmrEncoderNew + (JNIEnv *env, jclass clazz) { + CPvGsmAmrEncoder* gae = new CPvGsmAmrEncoder(); + if (gae == NULL) { + throwException(env, "java/lang/IllegalStateException", + "new CPvGsmAmrEncoder() failed", 0); + } + return (jint)gae; +} + +static void android_media_AmrInputStream_GsmAmrEncoderInitialize + (JNIEnv *env, jclass clazz, jint gae) { + // set input parameters + TEncodeProperties encodeProps; + encodeProps.iInBitsPerSample = 16; + encodeProps.iInSamplingRate = 8000; + encodeProps.iInClockRate = 1000; + encodeProps.iInNumChannels = 1; + encodeProps.iInInterleaveMode = TEncodeProperties::EINTERLEAVE_LR; + encodeProps.iMode = CPvGsmAmrEncoder::GSM_AMR_12_2; + encodeProps.iBitStreamFormatIf2 = false; + encodeProps.iAudioObjectType = 0; + encodeProps.iOutSamplingRate = encodeProps.iInSamplingRate; + encodeProps.iOutNumChannels = encodeProps.iInNumChannels; + encodeProps.iOutClockRate = encodeProps.iInClockRate; + + if (int rtn = ((CPvGsmAmrEncoder*)gae)-> + InitializeEncoder(aMaxOutputBufferSize, &encodeProps)) { + throwException(env, "java/lang/IllegalArgumentException", + "CPvGsmAmrEncoder::InitializeEncoder failed %d", rtn); + } +} + +static jint android_media_AmrInputStream_GsmAmrEncoderEncode + (JNIEnv *env, jclass clazz, + jint gae, jbyteArray pcm, jint pcmOffset, jbyteArray amr, jint amrOffset) { + + // set up input stream + jbyte inBuf[SAMPLES_PER_FRAME*2]; + TInputAudioStream in; + in.iSampleBuffer = (uint8*)inBuf; + env->GetByteArrayRegion(pcm, pcmOffset, sizeof(inBuf), inBuf); + in.iSampleLength = sizeof(inBuf); + in.iMode = CPvGsmAmrEncoder::GSM_AMR_12_2; + in.iStartTime = 0; + in.iStopTime = 0; + + // set up output stream + jbyte outBuf[aMaxOutputBufferSize]; + int32 sampleFrameSize[1] = { 0 }; + TOutputAudioStream out; + out.iBitStreamBuffer = (uint8*)outBuf; + out.iNumSampleFrames = 0; + out.iSampleFrameSize = sampleFrameSize; + out.iStartTime = 0; + out.iStopTime = 0; + + // encode + if (int rtn = ((CPvGsmAmrEncoder*)gae)->Encode(in, out)) { + throwException(env, "java/io/IOException", "CPvGsmAmrEncoder::Encode failed %d", rtn); + return -1; + } + + // validate one-frame assumption + if (out.iNumSampleFrames != 1) { + throwException(env, "java/io/IOException", + "CPvGsmAmrEncoder::Encode more than one frame returned %d", out.iNumSampleFrames); + return 0; + } + + // copy result + int length = out.iSampleFrameSize[0]; + + // The 1st byte of PV AMR frames are WMF (Wireless Multimedia Forum) + // bitpacked, i.e.; + // [P(4) + FT(4)]. Q=1 for good frame, P=padding bit, 0 + // Here we are converting the header to be as specified in Section 5.3 of + // RFC 3267 (AMR storage format) i.e. + // [P(1) + FT(4) + Q(1) + P(2)]. + if (length > 0) { + outBuf[0] = (outBuf[0] << 3) | 0x4; + } + + env->SetByteArrayRegion(amr, amrOffset, length, outBuf); + + return length; +} + +static void android_media_AmrInputStream_GsmAmrEncoderCleanup + (JNIEnv *env, jclass clazz, jint gae) { + if (int rtn = ((CPvGsmAmrEncoder*)gae)->CleanupEncoder()) { + throwException(env, "java/lang/IllegalStateException", + "CPvGsmAmrEncoder::CleanupEncoder failed %d", rtn); + } +} + +static void android_media_AmrInputStream_GsmAmrEncoderDelete + (JNIEnv *env, jclass clazz, jint gae) { + delete (CPvGsmAmrEncoder*)gae; +} + +// ---------------------------------------------------------------------------- + +static JNINativeMethod gMethods[] = { + {"GsmAmrEncoderNew", "()I", (void*)android_media_AmrInputStream_GsmAmrEncoderNew}, + {"GsmAmrEncoderInitialize", "(I)V", (void*)android_media_AmrInputStream_GsmAmrEncoderInitialize}, + {"GsmAmrEncoderEncode", "(I[BI[BI)I", (void*)android_media_AmrInputStream_GsmAmrEncoderEncode}, + {"GsmAmrEncoderCleanup", "(I)V", (void*)android_media_AmrInputStream_GsmAmrEncoderCleanup}, + {"GsmAmrEncoderDelete", "(I)V", (void*)android_media_AmrInputStream_GsmAmrEncoderDelete}, +}; + + +int register_android_media_AmrInputStream(JNIEnv *env) +{ + const char* const kClassPathName = "android/media/AmrInputStream"; + jclass clazz; + + clazz = env->FindClass(kClassPathName); + if (clazz == NULL) { + LOGE("Can't find %s", kClassPathName); + return -1; + } + + return AndroidRuntime::registerNativeMethods(env, + kClassPathName, gMethods, NELEM(gMethods)); +} + + diff --git a/media/jni/android_media_MediaMetadataRetriever.cpp b/media/jni/android_media_MediaMetadataRetriever.cpp new file mode 100644 index 0000000..4624a18 --- /dev/null +++ b/media/jni/android_media_MediaMetadataRetriever.cpp @@ -0,0 +1,327 @@ +/* +** +** Copyright 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 "MediaMetadataRetrieverJNI" + +#include <assert.h> +#include <utils/Log.h> +#include <utils/threads.h> +#include <core/SkBitmap.h> +#include <media/mediametadataretriever.h> +#include <private/media/VideoFrame.h> + +#include "jni.h" +#include "JNIHelp.h" +#include "android_runtime/AndroidRuntime.h" + + +using namespace android; + +struct fields_t { + jfieldID context; + jclass bitmapClazz; + jmethodID bitmapConstructor; +}; + +static fields_t fields; +static Mutex sLock; + +static void process_media_retriever_call(JNIEnv *env, status_t opStatus, const char* exception, const char *message) +{ + if (opStatus == (status_t) INVALID_OPERATION) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + } else if (opStatus != (status_t) OK) { + if (strlen(message) > 230) { + // If the message is too long, don't bother displaying the status code. + jniThrowException( env, exception, message); + } else { + char msg[256]; + // Append the status code to the message. + sprintf(msg, "%s: status = 0x%X", message, opStatus); + jniThrowException( env, exception, msg); + } + } +} + +static MediaMetadataRetriever* getRetriever(JNIEnv* env, jobject thiz) +{ + // No lock is needed, since it is called internally by other methods that are protected + MediaMetadataRetriever* retriever = (MediaMetadataRetriever*) env->GetIntField(thiz, fields.context); + return retriever; +} + +static void setRetriever(JNIEnv* env, jobject thiz, int retriever) +{ + // No lock is needed, since it is called internally by other methods that are protected + MediaMetadataRetriever *old = (MediaMetadataRetriever*) env->GetIntField(thiz, fields.context); + env->SetIntField(thiz, fields.context, retriever); +} + +static void android_media_MediaMetadataRetriever_setDataSource(JNIEnv *env, jobject thiz, jstring path) +{ + LOGV("setDataSource"); + Mutex::Autolock lock(sLock); + MediaMetadataRetriever* retriever = getRetriever(env, thiz); + if (retriever == 0) { + jniThrowException(env, "java/lang/IllegalStateException", "No retriever available"); + return; + } + if (!path) { + jniThrowException(env, "java/lang/IllegalArgumentException", "Null pointer"); + return; + } + + const char *pathStr = env->GetStringUTFChars(path, NULL); + if (!pathStr) { // OutOfMemoryError exception already thrown + return; + } + + // Don't let somebody trick us in to reading some random block of memory + if (strncmp("mem://", pathStr, 6) == 0) { + jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid pathname"); + return; + } + + process_media_retriever_call(env, retriever->setDataSource(pathStr), "java/lang/RuntimeException", "setDataSource failed"); + env->ReleaseStringUTFChars(path, pathStr); +} + +static void android_media_MediaMetadataRetriever_setDataSourceFD(JNIEnv *env, jobject thiz, jobject fileDescriptor, jlong offset, jlong length) +{ + LOGV("setDataSource"); + Mutex::Autolock lock(sLock); + MediaMetadataRetriever* retriever = getRetriever(env, thiz); + if (retriever == 0) { + jniThrowException(env, "java/lang/IllegalStateException", "No retriever available"); + return; + } + if (!fileDescriptor) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return; + } + int fd = getParcelFileDescriptorFD(env, fileDescriptor); + if (offset < 0 || length < 0 || fd < 0) { + if (offset < 0) { + LOGE("negative offset (%lld)", offset); + } + if (length < 0) { + LOGE("negative length (%lld)", length); + } + if (fd < 0) { + LOGE("invalid file descriptor"); + } + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return; + } + process_media_retriever_call(env, retriever->setDataSource(fd, offset, length), "java/lang/RuntimeException", "setDataSource failed"); +} + +static void android_media_MediaMetadataRetriever_setMode(JNIEnv *env, jobject thiz, jint mode) +{ + LOGV("setMode"); + Mutex::Autolock lock(sLock); + MediaMetadataRetriever* retriever = getRetriever(env, thiz); + if (retriever == 0) { + jniThrowException(env, "java/lang/IllegalStateException", "No retriever available"); + return; + } + process_media_retriever_call(env, retriever->setMode(mode), "java/lang/RuntimeException", "setMode failed"); +} + +static int android_media_MediaMetadataRetriever_getMode(JNIEnv *env, jobject thiz) +{ + LOGV("getMode"); + Mutex::Autolock lock(sLock); + MediaMetadataRetriever* retriever = getRetriever(env, thiz); + if (retriever == 0) { + jniThrowException(env, "java/lang/IllegalStateException", "No retriever available"); + return -1; // Error + } + int mode = -1; + retriever->getMode(&mode); + return mode; +} + +static jobject android_media_MediaMetadataRetriever_captureFrame(JNIEnv *env, jobject thiz) +{ + LOGV("captureFrame"); + Mutex::Autolock lock(sLock); + MediaMetadataRetriever* retriever = getRetriever(env, thiz); + if (retriever == 0) { + jniThrowException(env, "java/lang/IllegalStateException", "No retriever available"); + return NULL; + } + + // Call native method to retrieve a video frame + VideoFrame *videoFrame = NULL; + sp<IMemory> frameMemory = retriever->captureFrame(); + if (frameMemory != 0) { // cast the shared structure to a VideoFrame object + videoFrame = static_cast<VideoFrame *>(frameMemory->pointer()); + } + if (videoFrame == NULL) { + LOGE("captureFrame: videoFrame is a NULL pointer"); + return NULL; + } + + // Create a SkBitmap to hold the pixels + SkBitmap *bitmap = new SkBitmap(); + if (bitmap == NULL) { + LOGE("captureFrame: cannot instantiate a SkBitmap object."); + return NULL; + } + bitmap->setConfig(SkBitmap::kRGB_565_Config, videoFrame->mDisplayWidth, videoFrame->mDisplayHeight); + if (!bitmap->allocPixels()) { + delete bitmap; + LOGE("failed to allocate pixel buffer"); + return NULL; + } + memcpy((uint8_t*)bitmap->getPixels(), (uint8_t*)videoFrame + sizeof(VideoFrame), videoFrame->mSize); + + // Since internally SkBitmap uses reference count to manage the reference to + // its pixels, it is important that the pixels (along with SkBitmap) be + // available after creating the Bitmap is returned to Java app. + return env->NewObject(fields.bitmapClazz, fields.bitmapConstructor, (int) bitmap, true, NULL); +} + +static jbyteArray android_media_MediaMetadataRetriever_extractAlbumArt(JNIEnv *env, jobject thiz) +{ + LOGV("extractAlbumArt"); + Mutex::Autolock lock(sLock); + MediaMetadataRetriever* retriever = getRetriever(env, thiz); + if (retriever == 0) { + jniThrowException(env, "java/lang/IllegalStateException", "No retriever available"); + return NULL; + } + MediaAlbumArt* mediaAlbumArt = NULL; + sp<IMemory> albumArtMemory = retriever->extractAlbumArt(); + if (albumArtMemory != 0) { // cast the shared structure to a MediaAlbumArt object + mediaAlbumArt = static_cast<MediaAlbumArt *>(albumArtMemory->pointer()); + } + if (mediaAlbumArt == NULL) { + LOGE("extractAlbumArt: Call to extractAlbumArt failed."); + return NULL; + } + + unsigned int len = mediaAlbumArt->mSize; + char* data = (char*) mediaAlbumArt + sizeof(MediaAlbumArt); + jbyteArray array = env->NewByteArray(len); + if (!array) { // OutOfMemoryError exception has already been thrown. + LOGE("extractAlbumArt: OutOfMemoryError is thrown."); + } else { + jbyte* bytes = env->GetByteArrayElements(array, NULL); + if (bytes != NULL) { + memcpy(bytes, data, len); + env->ReleaseByteArrayElements(array, bytes, 0); + } + } + + // No need to delete mediaAlbumArt here + return array; +} + +static jobject android_media_MediaMetadataRetriever_extractMetadata(JNIEnv *env, jobject thiz, jint keyCode) +{ + LOGV("extractMetadata"); + Mutex::Autolock lock(sLock); + MediaMetadataRetriever* retriever = getRetriever(env, thiz); + if (retriever == 0) { + jniThrowException(env, "java/lang/IllegalStateException", "No retriever available"); + return NULL; + } + const char* value = retriever->extractMetadata(keyCode); + if (!value) { + LOGV("extractMetadata: Metadata is not found"); + return NULL; + } + LOGV("extractMetadata: value (%s) for keyCode(%d)", value, keyCode); + return env->NewStringUTF(value); +} + +static void android_media_MediaMetadataRetriever_release(JNIEnv *env, jobject thiz) +{ + LOGV("release"); + Mutex::Autolock lock(sLock); + MediaMetadataRetriever* retriever = getRetriever(env, thiz); + delete retriever; + setRetriever(env, thiz, 0); +} + +static void android_media_MediaMetadataRetriever_native_finalize(JNIEnv *env, jobject thiz) +{ + LOGV("native_finalize"); + + // No lock is needed, since android_media_MediaMetadataRetriever_release() is protected + android_media_MediaMetadataRetriever_release(env, thiz); +} + +static void android_media_MediaMetadataRetriever_native_setup(JNIEnv *env, jobject thiz) +{ + LOGV("native_setup"); + MediaMetadataRetriever* retriever = new MediaMetadataRetriever(); + if (retriever == 0) { + jniThrowException(env, "java/lang/RuntimeException", "Out of memory"); + return; + } + setRetriever(env, thiz, (int)retriever); +} + +// JNI mapping between Java methods and native methods +static JNINativeMethod nativeMethods[] = { + {"setDataSource", "(Ljava/lang/String;)V", (void *)android_media_MediaMetadataRetriever_setDataSource}, + {"setDataSource", "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaMetadataRetriever_setDataSourceFD}, + {"setMode", "(I)V", (void *)android_media_MediaMetadataRetriever_setMode}, + {"getMode", "()I", (void *)android_media_MediaMetadataRetriever_getMode}, + {"captureFrame", "()Landroid/graphics/Bitmap;", (void *)android_media_MediaMetadataRetriever_captureFrame}, + {"extractMetadata", "(I)Ljava/lang/String;", (void *)android_media_MediaMetadataRetriever_extractMetadata}, + {"extractAlbumArt", "()[B", (void *)android_media_MediaMetadataRetriever_extractAlbumArt}, + {"release", "()V", (void *)android_media_MediaMetadataRetriever_release}, + {"native_finalize", "()V", (void *)android_media_MediaMetadataRetriever_native_finalize}, + {"native_setup", "()V", (void *)android_media_MediaMetadataRetriever_native_setup}, +}; + +// Register native mehtods with Android runtime environment +int register_android_media_MediaMetadataRetriever(JNIEnv *env) +{ + static const char* const kClassPathName = "android/media/MediaMetadataRetriever"; + jclass clazz = env->FindClass(kClassPathName); + if (clazz == NULL) { + LOGE("Can't find class: %s", kClassPathName); + return -1; + } + + fields.context = env->GetFieldID(clazz, "mNativeContext", "I"); + if (fields.context == NULL) { + LOGE("Can't find MediaMetadataRetriever.mNativeContext"); + return -1; + } + + fields.bitmapClazz = env->FindClass("android/graphics/Bitmap"); + if (fields.bitmapClazz == NULL) { + LOGE("Bitmap class is not found"); + return -1; + } + + fields.bitmapConstructor = env->GetMethodID(fields.bitmapClazz, "<init>", "(IZ[B)V"); + if (fields.bitmapConstructor == NULL) { + LOGE("Bitmap constructor is not found"); + return -1; + } + + return AndroidRuntime::registerNativeMethods + (env, kClassPathName, nativeMethods, NELEM(nativeMethods)); +} diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp new file mode 100644 index 0000000..5562254 --- /dev/null +++ b/media/jni/android_media_MediaPlayer.cpp @@ -0,0 +1,573 @@ +/* //device/libs/android_runtime/android_media_MediaPlayer.cpp +** +** Copyright 2007, 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 "MediaPlayer-JNI" +#include "utils/Log.h" + +#include <media/mediaplayer.h> +#include <stdio.h> +#include <assert.h> +#include <limits.h> +#include <unistd.h> +#include <fcntl.h> +#include <utils/threads.h> +#include "jni.h" +#include "JNIHelp.h" +#include "android_runtime/AndroidRuntime.h" + + +// ---------------------------------------------------------------------------- + +using namespace android; + +// ---------------------------------------------------------------------------- + +struct fields_t { + jfieldID context; + jfieldID surface; + /* actually in android.view.Surface XXX */ + jfieldID surface_native; + + jmethodID post_event; +}; +static fields_t fields; + +static Mutex sLock; + +// ---------------------------------------------------------------------------- +// ref-counted object for callbacks +class JNIMediaPlayerListener: public MediaPlayerListener +{ +public: + JNIMediaPlayerListener(JNIEnv* env, jobject thiz, jobject weak_thiz); + ~JNIMediaPlayerListener(); + void notify(int msg, int ext1, int ext2); +private: + JNIMediaPlayerListener(); + jclass mClass; // Reference to MediaPlayer class + jobject mObject; // Weak ref to MediaPlayer Java object to call on +}; + +JNIMediaPlayerListener::JNIMediaPlayerListener(JNIEnv* env, jobject thiz, jobject weak_thiz) +{ + + // Hold onto the MediaPlayer class for use in calling the static method + // that posts events to the application thread. + jclass clazz = env->GetObjectClass(thiz); + if (clazz == NULL) { + LOGE("Can't find android/media/MediaPlayer"); + jniThrowException(env, "java/lang/Exception", NULL); + return; + } + mClass = (jclass)env->NewGlobalRef(clazz); + + // We use a weak reference so the MediaPlayer object can be garbage collected. + // The reference is only used as a proxy for callbacks. + mObject = env->NewGlobalRef(weak_thiz); +} + +JNIMediaPlayerListener::~JNIMediaPlayerListener() +{ + // remove global references + JNIEnv *env = AndroidRuntime::getJNIEnv(); + env->DeleteGlobalRef(mObject); + env->DeleteGlobalRef(mClass); +} + +void JNIMediaPlayerListener::notify(int msg, int ext1, int ext2) +{ + JNIEnv *env = AndroidRuntime::getJNIEnv(); + env->CallStaticVoidMethod(mClass, fields.post_event, mObject, msg, ext1, ext2, 0); +} + +// ---------------------------------------------------------------------------- + +static sp<Surface> get_surface(JNIEnv* env, jobject clazz) +{ + Surface* const p = (Surface*)env->GetIntField(clazz, fields.surface_native); + return sp<Surface>(p); +} + +static sp<MediaPlayer> getMediaPlayer(JNIEnv* env, jobject thiz) +{ + Mutex::Autolock l(sLock); + MediaPlayer* const p = (MediaPlayer*)env->GetIntField(thiz, fields.context); + return sp<MediaPlayer>(p); +} + +static sp<MediaPlayer> setMediaPlayer(JNIEnv* env, jobject thiz, const sp<MediaPlayer>& player) +{ + Mutex::Autolock l(sLock); + sp<MediaPlayer> old = (MediaPlayer*)env->GetIntField(thiz, fields.context); + if (player.get()) { + player->incStrong(thiz); + } + if (old != 0) { + old->decStrong(thiz); + } + env->SetIntField(thiz, fields.context, (int)player.get()); + return old; +} + +// If exception is NULL and opStatus is not OK, this method sends an error +// event to the client application; otherwise, if exception is not NULL and +// opStatus is not OK, this method throws the given exception to the client +// application. +static void process_media_player_call(JNIEnv *env, jobject thiz, status_t opStatus, const char* exception, const char *message) +{ + if (exception == NULL) { // Don't throw exception. Instead, send an event. + if (opStatus != (status_t) OK) { + sp<MediaPlayer> mp = getMediaPlayer(env, thiz); + if (mp != 0) mp->notify(MEDIA_ERROR, opStatus, 0); + } + } else { // Throw exception! + if ( opStatus == (status_t) INVALID_OPERATION ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + } else if ( opStatus != (status_t) OK ) { + if (strlen(message) > 230) { + // if the message is too long, don't bother displaying the status code + jniThrowException( env, exception, message); + } else { + char msg[256]; + // append the status code to the message + sprintf(msg, "%s: status=0x%X", message, opStatus); + jniThrowException( env, exception, msg); + } + } + } +} + +static void +android_media_MediaPlayer_setDataSource(JNIEnv *env, jobject thiz, jstring path) +{ + sp<MediaPlayer> mp = getMediaPlayer(env, thiz); + if (mp == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + + if (path == NULL) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return; + } + + const char *pathStr = env->GetStringUTFChars(path, NULL); + if (pathStr == NULL) { // Out of memory + jniThrowException(env, "java/lang/RuntimeException", "Out of memory"); + return; + } + status_t opStatus = mp->setDataSource(pathStr); + + // Make sure that local ref is released before a potential exception + env->ReleaseStringUTFChars(path, pathStr); + process_media_player_call( env, thiz, opStatus, "java/io/IOException", "setDataSource failed." ); +} + +static void +android_media_MediaPlayer_setDataSourceFD(JNIEnv *env, jobject thiz, jobject fileDescriptor, jlong offset, jlong length) +{ + sp<MediaPlayer> mp = getMediaPlayer(env, thiz); + if (mp == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + + if (fileDescriptor == NULL) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return; + } + int fd = getParcelFileDescriptorFD(env, fileDescriptor); + process_media_player_call( env, thiz, mp->setDataSource(fd, offset, length), "java/io/IOException", "setDataSourceFD failed." ); +} + + +static void +android_media_MediaPlayer_prepare(JNIEnv *env, jobject thiz) +{ + sp<MediaPlayer> mp = getMediaPlayer(env, thiz); + if (mp == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + jobject surface = env->GetObjectField(thiz, fields.surface); + if (surface != NULL) { + const sp<Surface>& native_surface = get_surface(env, surface); + //LOGI("prepare: surface=%p (id=%d)", + // native_surface.get(), native_surface->ID()); + mp->setVideoSurface(native_surface); + } + process_media_player_call( env, thiz, mp->prepare(), "java/io/IOException", "Prepare failed." ); +} + +static void +android_media_MediaPlayer_prepareAsync(JNIEnv *env, jobject thiz) +{ + sp<MediaPlayer> mp = getMediaPlayer(env, thiz); + if (mp == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + jobject surface = env->GetObjectField(thiz, fields.surface); + if (surface != NULL) { + const sp<Surface>& native_surface = get_surface(env, surface); + LOGI("prepareAsync: surface=%p (id=%d)", + native_surface.get(), native_surface->ID()); + mp->setVideoSurface(native_surface); + } + process_media_player_call( env, thiz, mp->prepareAsync(), "java/io/IOException", "Prepare Async failed." ); +} + +static void +android_media_MediaPlayer_start(JNIEnv *env, jobject thiz) +{ + sp<MediaPlayer> mp = getMediaPlayer(env, thiz); + if (mp == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + process_media_player_call( env, thiz, mp->start(), NULL, NULL ); +} + +static void +android_media_MediaPlayer_stop(JNIEnv *env, jobject thiz) +{ + sp<MediaPlayer> mp = getMediaPlayer(env, thiz); + if (mp == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + process_media_player_call( env, thiz, mp->stop(), NULL, NULL ); +} + +static void +android_media_MediaPlayer_pause(JNIEnv *env, jobject thiz) +{ + sp<MediaPlayer> mp = getMediaPlayer(env, thiz); + if (mp == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + process_media_player_call( env, thiz, mp->pause(), NULL, NULL ); +} + +static jboolean +android_media_MediaPlayer_isPlaying(JNIEnv *env, jobject thiz) +{ + sp<MediaPlayer> mp = getMediaPlayer(env, thiz); + if (mp == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return false; + } + return mp->isPlaying(); +} + +static void +android_media_MediaPlayer_seekTo(JNIEnv *env, jobject thiz, int msec) +{ + sp<MediaPlayer> mp = getMediaPlayer(env, thiz); + if (mp == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + process_media_player_call( env, thiz, mp->seekTo(msec), NULL, NULL ); +} + +static int +android_media_MediaPlayer_getVideoWidth(JNIEnv *env, jobject thiz) +{ + sp<MediaPlayer> mp = getMediaPlayer(env, thiz); + if (mp == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return 0; + } + int w; + if (0 == mp->getVideoWidth(&w)) + return w; + return 0; +} + +static int +android_media_MediaPlayer_getVideoHeight(JNIEnv *env, jobject thiz) +{ + sp<MediaPlayer> mp = getMediaPlayer(env, thiz); + if (mp == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return 0; + } + int h; + if (0 == mp->getVideoHeight(&h)) + return h; + return 0; +} + + +static int +android_media_MediaPlayer_getCurrentPosition(JNIEnv *env, jobject thiz) +{ + sp<MediaPlayer> mp = getMediaPlayer(env, thiz); + if (mp == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return 0; + } + int msec; + process_media_player_call( env, thiz, mp->getCurrentPosition(&msec), NULL, NULL ); + return msec; +} + +static int +android_media_MediaPlayer_getDuration(JNIEnv *env, jobject thiz) +{ + sp<MediaPlayer> mp = getMediaPlayer(env, thiz); + if (mp == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return 0; + } + int msec; + process_media_player_call( env, thiz, mp->getDuration(&msec), NULL, NULL ); + return msec; +} + +static void +android_media_MediaPlayer_reset(JNIEnv *env, jobject thiz) +{ + sp<MediaPlayer> mp = getMediaPlayer(env, thiz); + if (mp == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + process_media_player_call( env, thiz, mp->reset(), NULL, NULL ); +} + +static void +android_media_MediaPlayer_setAudioStreamType(JNIEnv *env, jobject thiz, int streamtype) +{ + sp<MediaPlayer> mp = getMediaPlayer(env, thiz); + if (mp == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + process_media_player_call( env, thiz, mp->setAudioStreamType(streamtype) , NULL, NULL ); +} + +static void +android_media_MediaPlayer_setLooping(JNIEnv *env, jobject thiz, jboolean looping) +{ + sp<MediaPlayer> mp = getMediaPlayer(env, thiz); + if (mp == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + process_media_player_call( env, thiz, mp->setLooping(looping), NULL, NULL ); +} + +static jboolean +android_media_MediaPlayer_isLooping(JNIEnv *env, jobject thiz) +{ + sp<MediaPlayer> mp = getMediaPlayer(env, thiz); + if (mp == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return false; + } + return mp->isLooping(); +} + +static void +android_media_MediaPlayer_setVolume(JNIEnv *env, jobject thiz, float leftVolume, float rightVolume) +{ + sp<MediaPlayer> mp = getMediaPlayer(env, thiz); + if (mp == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + process_media_player_call( env, thiz, mp->setVolume(leftVolume, rightVolume), NULL, NULL ); +} + +// FIXME: deprecated +static jobject +android_media_MediaPlayer_getFrameAt(JNIEnv *env, jobject thiz, jint msec) +{ + return NULL; +} + +static void +android_media_MediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this) +{ + LOGV("native_setup"); + sp<MediaPlayer> mp = new MediaPlayer(); + if (mp == NULL) { + jniThrowException(env, "java/lang/RuntimeException", "Out of memory"); + return; + } + + // create new listener and give it to MediaPlayer + sp<JNIMediaPlayerListener> listener = new JNIMediaPlayerListener(env, thiz, weak_this); + mp->setListener(listener); + + // Stow our new C++ MediaPlayer in an opaque field in the Java object. + setMediaPlayer(env, thiz, mp); +} + +static void +android_media_MediaPlayer_release(JNIEnv *env, jobject thiz) +{ + LOGV("release"); + sp<MediaPlayer> mp = setMediaPlayer(env, thiz, 0); + if (mp != NULL) { + // this prevents native callbacks after the object is released + mp->setListener(0); + mp->disconnect(); + } +} + +static void +android_media_MediaPlayer_native_finalize(JNIEnv *env, jobject thiz) +{ + LOGV("native_finalize"); + android_media_MediaPlayer_release(env, thiz); +} + +// ---------------------------------------------------------------------------- + +static JNINativeMethod gMethods[] = { + {"setDataSource", "(Ljava/lang/String;)V", (void *)android_media_MediaPlayer_setDataSource}, + {"setDataSource", "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaPlayer_setDataSourceFD}, + {"prepare", "()V", (void *)android_media_MediaPlayer_prepare}, + {"prepareAsync", "()V", (void *)android_media_MediaPlayer_prepareAsync}, + {"_start", "()V", (void *)android_media_MediaPlayer_start}, + {"_stop", "()V", (void *)android_media_MediaPlayer_stop}, + {"getVideoWidth", "()I", (void *)android_media_MediaPlayer_getVideoWidth}, + {"getVideoHeight", "()I", (void *)android_media_MediaPlayer_getVideoHeight}, + {"seekTo", "(I)V", (void *)android_media_MediaPlayer_seekTo}, + {"_pause", "()V", (void *)android_media_MediaPlayer_pause}, + {"isPlaying", "()Z", (void *)android_media_MediaPlayer_isPlaying}, + {"getCurrentPosition", "()I", (void *)android_media_MediaPlayer_getCurrentPosition}, + {"getDuration", "()I", (void *)android_media_MediaPlayer_getDuration}, + {"_release", "()V", (void *)android_media_MediaPlayer_release}, + {"_reset", "()V", (void *)android_media_MediaPlayer_reset}, + {"setAudioStreamType", "(I)V", (void *)android_media_MediaPlayer_setAudioStreamType}, + {"setLooping", "(Z)V", (void *)android_media_MediaPlayer_setLooping}, + {"isLooping", "()Z", (void *)android_media_MediaPlayer_isLooping}, + {"setVolume", "(FF)V", (void *)android_media_MediaPlayer_setVolume}, + {"getFrameAt", "(I)Landroid/graphics/Bitmap;", (void *)android_media_MediaPlayer_getFrameAt}, + {"native_setup", "(Ljava/lang/Object;)V", (void *)android_media_MediaPlayer_native_setup}, + {"native_finalize", "()V", (void *)android_media_MediaPlayer_native_finalize}, +}; + +static const char* const kClassPathName = "android/media/MediaPlayer"; + +static int register_android_media_MediaPlayer(JNIEnv *env) +{ + jclass clazz; + + clazz = env->FindClass("android/media/MediaPlayer"); + if (clazz == NULL) { + LOGE("Can't find android/media/MediaPlayer"); + return -1; + } + + fields.context = env->GetFieldID(clazz, "mNativeContext", "I"); + if (fields.context == NULL) { + LOGE("Can't find MediaPlayer.mNativeContext"); + return -1; + } + + fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative", + "(Ljava/lang/Object;IIILjava/lang/Object;)V"); + if (fields.post_event == NULL) { + LOGE("Can't find MediaPlayer.postEventFromNative"); + return -1; + } + + fields.surface = env->GetFieldID(clazz, "mSurface", "Landroid/view/Surface;"); + if (fields.surface == NULL) { + LOGE("Can't find MediaPlayer.mSurface"); + return -1; + } + + jclass surface = env->FindClass("android/view/Surface"); + if (surface == NULL) { + LOGE("Can't find android/view/Surface"); + return -1; + } + + fields.surface_native = env->GetFieldID(surface, "mSurface", "I"); + if (fields.surface_native == NULL) { + LOGE("Can't find Surface fields"); + return -1; + } + + return AndroidRuntime::registerNativeMethods(env, + "android/media/MediaPlayer", gMethods, NELEM(gMethods)); +} + +extern int register_android_media_MediaRecorder(JNIEnv *env); +extern int register_android_media_MediaScanner(JNIEnv *env); +extern int register_android_media_MediaMetadataRetriever(JNIEnv *env); +extern int register_android_media_AmrInputStream(JNIEnv *env); +extern int register_android_media_ResampleInputStream(JNIEnv *env); + +jint JNI_OnLoad(JavaVM* vm, void* reserved) +{ + JNIEnv* env = NULL; + jint result = -1; + + if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { + LOGE("ERROR: GetEnv failed\n"); + goto bail; + } + assert(env != NULL); + + if (register_android_media_MediaPlayer(env) < 0) { + LOGE("ERROR: MediaPlayer native registration failed\n"); + goto bail; + } + + if (register_android_media_MediaRecorder(env) < 0) { + LOGE("ERROR: MediaRecorder native registration failed\n"); + goto bail; + } + + if (register_android_media_MediaScanner(env) < 0) { + LOGE("ERROR: MediaScanner native registration failed\n"); + goto bail; + } + + if (register_android_media_MediaMetadataRetriever(env) < 0) { + LOGE("ERROR: MediaMetadataRetriever native registration failed\n"); + goto bail; + } + + if (register_android_media_AmrInputStream(env) < 0) { + LOGE("ERROR: AmrInputStream native registration failed\n"); + goto bail; + } + + if (register_android_media_ResampleInputStream(env) < 0) { + LOGE("ERROR: ResampleInputStream native registration failed\n"); + goto bail; + } + + /* success -- return valid version number */ + result = JNI_VERSION_1_4; + +bail: + return result; +} + +// KTHXBYE + diff --git a/media/jni/android_media_MediaRecorder.cpp b/media/jni/android_media_MediaRecorder.cpp new file mode 100644 index 0000000..44f875c --- /dev/null +++ b/media/jni/android_media_MediaRecorder.cpp @@ -0,0 +1,416 @@ +/* + * 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 "MediaRecorderJNI" +#include <utils/Log.h> + +#include <ui/SurfaceComposerClient.h> +#include <ui/ICameraService.h> +#include <ui/Camera.h> +#include <media/mediarecorder.h> +#include <stdio.h> +#include <assert.h> +#include <limits.h> +#include <unistd.h> +#include <fcntl.h> +#include <utils/threads.h> + +#include "jni.h" +#include "JNIHelp.h" +#include "android_runtime/AndroidRuntime.h" + + +// ---------------------------------------------------------------------------- + +using namespace android; + +// ---------------------------------------------------------------------------- + +// helper function to extract a native Camera object from a Camera Java object +extern sp<Camera> get_native_camera(JNIEnv *env, jobject thiz, struct camera_context_t** context); + +struct fields_t { + jfieldID context; + jfieldID surface; + /* actually in android.view.Surface XXX */ + jfieldID surface_native; + + jmethodID post_event; +}; +static fields_t fields; + +static Mutex sLock; + +// ---------------------------------------------------------------------------- +// ref-counted object for callbacks +class JNIMediaRecorderListener: public MediaRecorderListener +{ +public: + JNIMediaRecorderListener(JNIEnv* env, jobject thiz, jobject weak_thiz); + ~JNIMediaRecorderListener(); + void notify(int msg, int ext1, int ext2); +private: + JNIMediaRecorderListener(); + jclass mClass; // Reference to MediaRecorder class + jobject mObject; // Weak ref to MediaRecorder Java object to call on +}; + +JNIMediaRecorderListener::JNIMediaRecorderListener(JNIEnv* env, jobject thiz, jobject weak_thiz) +{ + + // Hold onto the MediaRecorder class for use in calling the static method + // that posts events to the application thread. + jclass clazz = env->GetObjectClass(thiz); + if (clazz == NULL) { + LOGE("Can't find android/media/MediaRecorder"); + jniThrowException(env, "java/lang/Exception", NULL); + return; + } + mClass = (jclass)env->NewGlobalRef(clazz); + + // We use a weak reference so the MediaRecorder object can be garbage collected. + // The reference is only used as a proxy for callbacks. + mObject = env->NewGlobalRef(weak_thiz); +} + +JNIMediaRecorderListener::~JNIMediaRecorderListener() +{ + // remove global references + JNIEnv *env = AndroidRuntime::getJNIEnv(); + env->DeleteGlobalRef(mObject); + env->DeleteGlobalRef(mClass); +} + +void JNIMediaRecorderListener::notify(int msg, int ext1, int ext2) +{ + LOGV("JNIMediaRecorderListener::notify"); + + JNIEnv *env = AndroidRuntime::getJNIEnv(); + env->CallStaticVoidMethod(mClass, fields.post_event, mObject, msg, ext1, ext2, 0); +} + +// ---------------------------------------------------------------------------- + +static sp<Surface> get_surface(JNIEnv* env, jobject clazz) +{ + LOGV("get_surface"); + Surface* const p = (Surface*)env->GetIntField(clazz, fields.surface_native); + return sp<Surface>(p); +} + +// Returns true if it throws an exception. +static bool process_media_recorder_call(JNIEnv *env, status_t opStatus, const char* exception, const char* message) +{ + LOGV("process_media_recorder_call"); + if (opStatus == (status_t)INVALID_OPERATION) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return true; + } else if (opStatus != (status_t)OK) { + jniThrowException(env, exception, message); + return true; + } + return false; +} + +static sp<MediaRecorder> getMediaRecorder(JNIEnv* env, jobject thiz) +{ + Mutex::Autolock l(sLock); + MediaRecorder* const p = (MediaRecorder*)env->GetIntField(thiz, fields.context); + return sp<MediaRecorder>(p); +} + +static sp<MediaRecorder> setMediaRecorder(JNIEnv* env, jobject thiz, const sp<MediaRecorder>& recorder) +{ + Mutex::Autolock l(sLock); + sp<MediaRecorder> old = (MediaRecorder*)env->GetIntField(thiz, fields.context); + if (recorder.get()) { + recorder->incStrong(thiz); + } + if (old != 0) { + old->decStrong(thiz); + } + env->SetIntField(thiz, fields.context, (int)recorder.get()); + return old; +} + + +static void android_media_MediaRecorder_setCamera(JNIEnv* env, jobject thiz, jobject camera) +{ + sp<Camera> c = get_native_camera(env, camera, NULL); + sp<MediaRecorder> mr = getMediaRecorder(env, thiz); + process_media_recorder_call(env, mr->setCamera(c->remote()), + "java/lang/RuntimeException", "setCamera failed."); +} + +static void +android_media_MediaRecorder_setVideoSource(JNIEnv *env, jobject thiz, jint vs) +{ + LOGV("setVideoSource(%d)", vs); + if (vs < VIDEO_SOURCE_DEFAULT || vs > VIDEO_SOURCE_CAMERA) { + jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid video source"); + return; + } + sp<MediaRecorder> mr = getMediaRecorder(env, thiz); + process_media_recorder_call(env, mr->setVideoSource(vs), "java/lang/RuntimeException", "setVideoSource failed."); +} + +static void +android_media_MediaRecorder_setAudioSource(JNIEnv *env, jobject thiz, jint as) +{ + LOGV("setAudioSource(%d)", as); + if (as < AUDIO_SOURCE_DEFAULT || as > AUDIO_SOURCE_MIC) { + jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid audio source"); + return; + } + sp<MediaRecorder> mr = getMediaRecorder(env, thiz); + process_media_recorder_call(env, mr->setAudioSource(as), "java/lang/RuntimeException", "setAudioSource failed."); +} + +static void +android_media_MediaRecorder_setOutputFormat(JNIEnv *env, jobject thiz, jint of) +{ + LOGV("setOutputFormat(%d)", of); + if (of < OUTPUT_FORMAT_DEFAULT || of >= OUTPUT_FORMAT_LIST_END) { + jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid output format"); + return; + } + sp<MediaRecorder> mr = getMediaRecorder(env, thiz); + process_media_recorder_call(env, mr->setOutputFormat(of), "java/lang/RuntimeException", "setOutputFormat failed."); +} + +static void +android_media_MediaRecorder_setVideoEncoder(JNIEnv *env, jobject thiz, jint ve) +{ + LOGV("setVideoEncoder(%d)", ve); + if (ve < VIDEO_ENCODER_DEFAULT || ve > VIDEO_ENCODER_MPEG_4_SP) { + jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid video encoder"); + return; + } + sp<MediaRecorder> mr = getMediaRecorder(env, thiz); + process_media_recorder_call(env, mr->setVideoEncoder(ve), "java/lang/RuntimeException", "setVideoEncoder failed."); +} + +static void +android_media_MediaRecorder_setAudioEncoder(JNIEnv *env, jobject thiz, jint ae) +{ + LOGV("setAudioEncoder(%d)", ae); + if (ae < AUDIO_ENCODER_DEFAULT || ae > AUDIO_ENCODER_AMR_NB) { + jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid audio encoder"); + return; + } + sp<MediaRecorder> mr = getMediaRecorder(env, thiz); + process_media_recorder_call(env, mr->setAudioEncoder(ae), "java/lang/RuntimeException", "setAudioEncoder failed."); +} + +static void +android_media_MediaRecorder_setOutputFileFD(JNIEnv *env, jobject thiz, jobject fileDescriptor, jlong offset, jlong length) +{ + LOGV("setOutputFile"); + if (fileDescriptor == NULL) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return; + } + int fd = getParcelFileDescriptorFD(env, fileDescriptor); + sp<MediaRecorder> mr = getMediaRecorder(env, thiz); + status_t opStatus = mr->setOutputFile(fd, offset, length); + process_media_recorder_call(env, opStatus, "java/io/IOException", "setOutputFile failed."); +} + +static void +android_media_MediaRecorder_setVideoSize(JNIEnv *env, jobject thiz, jint width, jint height) +{ + LOGV("setVideoSize(%d, %d)", width, height); + sp<MediaRecorder> mr = getMediaRecorder(env, thiz); + + if (width <= 0 || height <= 0) { + jniThrowException(env, "java/lang/IllegalArgumentException", "invalid video size"); + return; + } + process_media_recorder_call(env, mr->setVideoSize(width, height), "java/lang/RuntimeException", "setVideoSize failed."); +} + +static void +android_media_MediaRecorder_setVideoFrameRate(JNIEnv *env, jobject thiz, jint rate) +{ + LOGV("setVideoFrameRate(%d)", rate); + if (rate <= 0 || rate > MEDIA_RECORDER_MAX_FRAME_RATE) { + jniThrowException(env, "java/lang/IllegalArgumentException", "invalid frame rate"); + return; + } + sp<MediaRecorder> mr = getMediaRecorder(env, thiz); + process_media_recorder_call(env, mr->setVideoFrameRate(rate), "java/lang/RuntimeException", "setVideoFrameRate failed."); +} + +static void +android_media_MediaRecorder_prepare(JNIEnv *env, jobject thiz) +{ + LOGV("prepare"); + sp<MediaRecorder> mr = getMediaRecorder(env, thiz); + + jobject surface = env->GetObjectField(thiz, fields.surface); + if (surface != NULL) { + const sp<Surface>& native_surface = get_surface(env, surface); + LOGI("prepare: surface=%p (id=%d)", native_surface.get(), native_surface->ID()); + if (process_media_recorder_call(env, mr->setPreviewSurface(native_surface), "java/lang/RuntimeException", "setPreviewSurface failed.")) { + return; + } + } + process_media_recorder_call(env, mr->prepare(), "java/io/IOException", "prepare failed."); +} + +static int +android_media_MediaRecorder_native_getMaxAmplitude(JNIEnv *env, jobject thiz) +{ + LOGV("getMaxAmplitude"); + sp<MediaRecorder> mr = getMediaRecorder(env, thiz); + int result = 0; + process_media_recorder_call(env, mr->getMaxAmplitude(&result), "java/lang/RuntimeException", "getMaxAmplitude failed."); + return result; +} + +static void +android_media_MediaRecorder_start(JNIEnv *env, jobject thiz) +{ + LOGV("start"); + sp<MediaRecorder> mr = getMediaRecorder(env, thiz); + process_media_recorder_call(env, mr->start(), "java/lang/RuntimeException", "start failed."); +} + +static void +android_media_MediaRecorder_stop(JNIEnv *env, jobject thiz) +{ + LOGV("stop"); + sp<MediaRecorder> mr = getMediaRecorder(env, thiz); + process_media_recorder_call(env, mr->stop(), "java/lang/RuntimeException", "stop failed."); +} + +static void +android_media_MediaRecorder_native_reset(JNIEnv *env, jobject thiz) +{ + LOGV("native_reset"); + sp<MediaRecorder> mr = getMediaRecorder(env, thiz); + process_media_recorder_call(env, mr->reset(), "java/lang/RuntimeException", "native_reset failed."); +} + +static void +android_media_MediaRecorder_release(JNIEnv *env, jobject thiz) +{ + LOGV("release"); + sp<MediaRecorder> mr = setMediaRecorder(env, thiz, 0); + if (mr != NULL) { + mr->setListener(NULL); + } +} + +static void +android_media_MediaRecorder_native_setup(JNIEnv *env, jobject thiz, jobject weak_this) +{ + LOGV("setup"); + sp<MediaRecorder> mr = new MediaRecorder(); + if (mr == NULL) { + jniThrowException(env, "java/lang/RuntimeException", "Out of memory"); + return; + } + if (mr->initCheck() != NO_ERROR) { + jniThrowException(env, "java/lang/IOException", "Unable to initialize camera"); + return; + } + + // create new listener and give it to MediaRecorder + sp<JNIMediaRecorderListener> listener = new JNIMediaRecorderListener(env, thiz, weak_this); + mr->setListener(listener); + + setMediaRecorder(env, thiz, mr); +} + +static void +android_media_MediaRecorder_native_finalize(JNIEnv *env, jobject thiz) +{ + LOGV("finalize"); + android_media_MediaRecorder_release(env, thiz); +} + +// ---------------------------------------------------------------------------- + +static JNINativeMethod gMethods[] = { + {"setCamera", "(Landroid/hardware/Camera;)V", (void *)android_media_MediaRecorder_setCamera}, + {"setVideoSource", "(I)V", (void *)android_media_MediaRecorder_setVideoSource}, + {"setAudioSource", "(I)V", (void *)android_media_MediaRecorder_setAudioSource}, + {"setOutputFormat", "(I)V", (void *)android_media_MediaRecorder_setOutputFormat}, + {"setVideoEncoder", "(I)V", (void *)android_media_MediaRecorder_setVideoEncoder}, + {"setAudioEncoder", "(I)V", (void *)android_media_MediaRecorder_setAudioEncoder}, + {"_setOutputFile", "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaRecorder_setOutputFileFD}, + {"setVideoSize", "(II)V", (void *)android_media_MediaRecorder_setVideoSize}, + {"setVideoFrameRate", "(I)V", (void *)android_media_MediaRecorder_setVideoFrameRate}, + {"_prepare", "()V", (void *)android_media_MediaRecorder_prepare}, + {"getMaxAmplitude", "()I", (void *)android_media_MediaRecorder_native_getMaxAmplitude}, + {"start", "()V", (void *)android_media_MediaRecorder_start}, + {"stop", "()V", (void *)android_media_MediaRecorder_stop}, + {"native_reset", "()V", (void *)android_media_MediaRecorder_native_reset}, + {"release", "()V", (void *)android_media_MediaRecorder_release}, + {"native_setup", "(Ljava/lang/Object;)V", (void *)android_media_MediaRecorder_native_setup}, + {"native_finalize", "()V", (void *)android_media_MediaRecorder_native_finalize}, +}; + +static const char* const kClassPathName = "android/media/MediaRecorder"; + +int register_android_media_MediaRecorder(JNIEnv *env) +{ + jclass clazz; + + clazz = env->FindClass("android/media/MediaRecorder"); + if (clazz == NULL) { + LOGE("Can't find android/media/MediaRecorder"); + return -1; + } + + fields.context = env->GetFieldID(clazz, "mNativeContext", "I"); + if (fields.context == NULL) { + LOGE("Can't find MediaRecorder.mNativeContext"); + return -1; + } + + fields.surface = env->GetFieldID(clazz, "mSurface", "Landroid/view/Surface;"); + if (fields.surface == NULL) { + LOGE("Can't find MediaRecorder.mSurface"); + return -1; + } + + jclass surface = env->FindClass("android/view/Surface"); + if (surface == NULL) { + LOGE("Can't find android/view/Surface"); + return -1; + } + + fields.surface_native = env->GetFieldID(surface, "mSurface", "I"); + if (fields.surface_native == NULL) { + LOGE("Can't find Surface fields"); + return -1; + } + + fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative", + "(Ljava/lang/Object;IIILjava/lang/Object;)V"); + if (fields.post_event == NULL) { + LOGE("Can't find MediaRecorder.postEventFromNative"); + return -1; + } + + return AndroidRuntime::registerNativeMethods(env, + "android/media/MediaRecorder", gMethods, NELEM(gMethods)); +} + + diff --git a/media/jni/android_media_MediaScanner.cpp b/media/jni/android_media_MediaScanner.cpp new file mode 100644 index 0000000..8764a70 --- /dev/null +++ b/media/jni/android_media_MediaScanner.cpp @@ -0,0 +1,304 @@ +/* //device/libs/media_jni/MediaScanner.cpp +** +** Copyright 2007, 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_TAG "MediaScanner" +#include "utils/Log.h" + +#include <media/mediascanner.h> +#include <stdio.h> +#include <assert.h> +#include <limits.h> +#include <unistd.h> +#include <fcntl.h> +#include <utils/threads.h> + +#include "jni.h" +#include "JNIHelp.h" +#include "android_runtime/AndroidRuntime.h" + + +// ---------------------------------------------------------------------------- + +using namespace android; + +// ---------------------------------------------------------------------------- + +struct fields_t { + jfieldID context; +}; +static fields_t fields; + +// ---------------------------------------------------------------------------- + +class MyMediaScannerClient : public MediaScannerClient +{ +public: + MyMediaScannerClient(JNIEnv *env, jobject client) + : mEnv(env), + mClient(env->NewGlobalRef(client)), + mScanFileMethodID(0), + mHandleStringTagMethodID(0), + mSetMimeTypeMethodID(0) + { + jclass mediaScannerClientInterface = env->FindClass("android/media/MediaScannerClient"); + if (mediaScannerClientInterface == NULL) { + fprintf(stderr, "android/media/MediaScannerClient not found\n"); + } + else { + mScanFileMethodID = env->GetMethodID(mediaScannerClientInterface, "scanFile", + "(Ljava/lang/String;JJ)V"); + mHandleStringTagMethodID = env->GetMethodID(mediaScannerClientInterface, "handleStringTag", + "(Ljava/lang/String;Ljava/lang/String;)V"); + mSetMimeTypeMethodID = env->GetMethodID(mediaScannerClientInterface, "setMimeType", + "(Ljava/lang/String;)V"); + } + } + + virtual ~MyMediaScannerClient() + { + mEnv->DeleteGlobalRef(mClient); + } + + // returns true if it succeeded, false if an exception occured in the Java code + virtual bool scanFile(const char* path, long long lastModified, long long fileSize) + { + jstring pathStr; + if ((pathStr = mEnv->NewStringUTF(path)) == NULL) return false; + + mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified, fileSize); + + mEnv->DeleteLocalRef(pathStr); + return (!mEnv->ExceptionCheck()); + } + + // returns true if it succeeded, false if an exception occured in the Java code + virtual bool handleStringTag(const char* name, const char* value) + { + jstring nameStr, valueStr; + if ((nameStr = mEnv->NewStringUTF(name)) == NULL) return false; + if ((valueStr = mEnv->NewStringUTF(value)) == NULL) return false; + + mEnv->CallVoidMethod(mClient, mHandleStringTagMethodID, nameStr, valueStr); + + mEnv->DeleteLocalRef(nameStr); + mEnv->DeleteLocalRef(valueStr); + return (!mEnv->ExceptionCheck()); + } + + // returns true if it succeeded, false if an exception occured in the Java code + virtual bool setMimeType(const char* mimeType) + { + jstring mimeTypeStr; + if ((mimeTypeStr = mEnv->NewStringUTF(mimeType)) == NULL) return false; + + mEnv->CallVoidMethod(mClient, mSetMimeTypeMethodID, mimeTypeStr); + + mEnv->DeleteLocalRef(mimeTypeStr); + return (!mEnv->ExceptionCheck()); + } + +private: + JNIEnv *mEnv; + jobject mClient; + jmethodID mScanFileMethodID; + jmethodID mHandleStringTagMethodID; + jmethodID mSetMimeTypeMethodID; +}; + + +// ---------------------------------------------------------------------------- + +static bool ExceptionCheck(void* env) +{ + return ((JNIEnv *)env)->ExceptionCheck(); +} + +static void +android_media_MediaScanner_processDirectory(JNIEnv *env, jobject thiz, jstring path, jstring extensions, jobject client) +{ + MediaScanner *mp = (MediaScanner *)env->GetIntField(thiz, fields.context); + + if (path == NULL) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return; + } + if (extensions == NULL) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return; + } + + const char *pathStr = env->GetStringUTFChars(path, NULL); + if (pathStr == NULL) { // Out of memory + jniThrowException(env, "java/lang/RuntimeException", "Out of memory"); + return; + } + const char *extensionsStr = env->GetStringUTFChars(extensions, NULL); + if (extensionsStr == NULL) { // Out of memory + env->ReleaseStringUTFChars(path, pathStr); + jniThrowException(env, "java/lang/RuntimeException", "Out of memory"); + return; + } + + MyMediaScannerClient myClient(env, client); + mp->processDirectory(pathStr, extensionsStr, myClient, ExceptionCheck, env); + env->ReleaseStringUTFChars(path, pathStr); + env->ReleaseStringUTFChars(extensions, extensionsStr); +} + +static void +android_media_MediaScanner_processFile(JNIEnv *env, jobject thiz, jstring path, jstring mimeType, jobject client) +{ + MediaScanner *mp = (MediaScanner *)env->GetIntField(thiz, fields.context); + + if (path == NULL) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return; + } + + const char *pathStr = env->GetStringUTFChars(path, NULL); + if (pathStr == NULL) { // Out of memory + jniThrowException(env, "java/lang/RuntimeException", "Out of memory"); + return; + } + const char *mimeTypeStr = (mimeType ? env->GetStringUTFChars(mimeType, NULL) : NULL); + if (mimeType && mimeTypeStr == NULL) { // Out of memory + env->ReleaseStringUTFChars(path, pathStr); + jniThrowException(env, "java/lang/RuntimeException", "Out of memory"); + return; + } + + MyMediaScannerClient myClient(env, client); + mp->processFile(pathStr, mimeTypeStr, myClient); + env->ReleaseStringUTFChars(path, pathStr); + if (mimeType) { + env->ReleaseStringUTFChars(mimeType, mimeTypeStr); + } +} + +static void +android_media_MediaScanner_setLocale(JNIEnv *env, jobject thiz, jstring locale) +{ + MediaScanner *mp = (MediaScanner *)env->GetIntField(thiz, fields.context); + + if (locale == NULL) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return; + } + const char *localeStr = env->GetStringUTFChars(locale, NULL); + if (localeStr == NULL) { // Out of memory + jniThrowException(env, "java/lang/RuntimeException", "Out of memory"); + return; + } + mp->setLocale(localeStr); + + env->ReleaseStringUTFChars(locale, localeStr); +} + +static jbyteArray +android_media_MediaScanner_extractAlbumArt(JNIEnv *env, jobject thiz, jobject fileDescriptor) +{ + MediaScanner *mp = (MediaScanner *)env->GetIntField(thiz, fields.context); + + if (fileDescriptor == NULL) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return NULL; + } + + int fd = getParcelFileDescriptorFD(env, fileDescriptor); + char* data = mp->extractAlbumArt(fd); + if (!data) { + return NULL; + } + long len = *((long*)data); + + jbyteArray array = env->NewByteArray(len); + if (array != NULL) { + jbyte* bytes = env->GetByteArrayElements(array, NULL); + memcpy(bytes, data + 4, len); + env->ReleaseByteArrayElements(array, bytes, 0); + } + +done: + free(data); + // if NewByteArray() returned NULL, an out-of-memory + // exception will have been raised. I just want to + // return null in that case. + env->ExceptionClear(); + return array; +} + +static void +android_media_MediaScanner_native_setup(JNIEnv *env, jobject thiz) +{ + MediaScanner *mp = new MediaScanner(); + if (mp == NULL) { + jniThrowException(env, "java/lang/RuntimeException", "Out of memory"); + return; + } + + env->SetIntField(thiz, fields.context, (int)mp); +} + +static void +android_media_MediaScanner_native_finalize(JNIEnv *env, jobject thiz) +{ + MediaScanner *mp = (MediaScanner *)env->GetIntField(thiz, fields.context); + + //printf("##### android_media_MediaScanner_native_finalize: ctx=0x%p\n", ctx); + + if (mp == 0) + return; + + delete mp; +} + +// ---------------------------------------------------------------------------- + +static JNINativeMethod gMethods[] = { + {"processDirectory", "(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V", + (void *)android_media_MediaScanner_processDirectory}, + {"processFile", "(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V", + (void *)android_media_MediaScanner_processFile}, + {"setLocale", "(Ljava/lang/String;)V", (void *)android_media_MediaScanner_setLocale}, + {"extractAlbumArt", "(Ljava/io/FileDescriptor;)[B", (void *)android_media_MediaScanner_extractAlbumArt}, + {"native_setup", "()V", (void *)android_media_MediaScanner_native_setup}, + {"native_finalize", "()V", (void *)android_media_MediaScanner_native_finalize}, +}; + +static const char* const kClassPathName = "android/media/MediaScanner"; + +int register_android_media_MediaScanner(JNIEnv *env) +{ + jclass clazz; + + clazz = env->FindClass("android/media/MediaScanner"); + if (clazz == NULL) { + LOGE("Can't find android/media/MediaScanner"); + return -1; + } + + fields.context = env->GetFieldID(clazz, "mNativeContext", "I"); + if (fields.context == NULL) { + LOGE("Can't find MediaScanner.mNativeContext"); + return -1; + } + + return AndroidRuntime::registerNativeMethods(env, + "android/media/MediaScanner", gMethods, NELEM(gMethods)); +} + + diff --git a/media/jni/android_media_ResampleInputStream.cpp b/media/jni/android_media_ResampleInputStream.cpp new file mode 100644 index 0000000..0247cdb --- /dev/null +++ b/media/jni/android_media_ResampleInputStream.cpp @@ -0,0 +1,143 @@ +/* +** +** Copyright 2007, 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_TAG "ResampleInputStream" +#include "utils/Log.h" + +#include <media/mediarecorder.h> +#include <stdio.h> +#include <assert.h> +#include <limits.h> +#include <unistd.h> +#include <fcntl.h> +#include <utils/threads.h> + +#include "jni.h" +#include "JNIHelp.h" +#include "android_runtime/AndroidRuntime.h" + + +// ---------------------------------------------------------------------------- + +using namespace android; + + +// +// helper function to throw an exception +// +static void throwException(JNIEnv *env, const char* ex, const char* fmt, int data) { + if (jclass cls = env->FindClass(ex)) { + char msg[1000]; + sprintf(msg, fmt, data); + env->ThrowNew(cls, msg); + env->DeleteLocalRef(cls); + } +} + + +#define FIR_COEF(coef) (short)(0x10000 * coef) +static const short fir21[] = { + FIR_COEF(-0.006965742326), + FIR_COEF(-0.008428945737), + FIR_COEF( 0.004241280174), + FIR_COEF( 0.022141096893), + FIR_COEF( 0.018765669437), + FIR_COEF(-0.009871891152), + FIR_COEF(-0.024842433247), + FIR_COEF( 0.006121772058), + FIR_COEF( 0.045890841611), + FIR_COEF( 0.021573503509), + FIR_COEF(-0.059681984668), + FIR_COEF(-0.076036275138), + FIR_COEF( 0.072405390275), + FIR_COEF( 0.308255674582), + FIR_COEF( 0.424321210495), + FIR_COEF( 0.308255674582), + FIR_COEF( 0.072405390275), + FIR_COEF(-0.076036275138), + FIR_COEF(-0.059681984668), + FIR_COEF( 0.021573503509), + FIR_COEF( 0.045890841611), + FIR_COEF( 0.006121772058), + FIR_COEF(-0.024842433247), + FIR_COEF(-0.009871891152), + FIR_COEF( 0.018765669437), + FIR_COEF( 0.022141096893), + FIR_COEF( 0.004241280174), + FIR_COEF(-0.008428945737), + FIR_COEF(-0.006965742326) +}; +static const int nFir21 = sizeof(fir21) / sizeof(fir21[0]); + +static const int BUF_SIZE = 2048; + + +static void android_media_ResampleInputStream_fir21(JNIEnv *env, jclass clazz, + jbyteArray jIn, jint jInOffset, + jbyteArray jOut, jint jOutOffset, + jint jNpoints) { + + // safety first! + if (nFir21 + jNpoints > BUF_SIZE) { + throwException(env, "java/lang/IllegalArgumentException", + "FIR+data too long %d", nFir21 + jNpoints); + return; + } + + // get input data + short in[BUF_SIZE]; + env->GetByteArrayRegion(jIn, jInOffset, (jNpoints * 2 + nFir21 - 1) * 2, (jbyte*)in); + + // compute filter + short out[BUF_SIZE]; + for (int i = 0; i < jNpoints; i++) { + long sum = 0; + const short* firp = &fir21[0]; + const short* inp = &in[i * 2]; + for (int n = nFir21; --n >= 0; ) { + sum += ((long)*firp++) * ((long)*inp++); + } + out[i] = (short)(sum >> 16); + } + + // save new values + env->SetByteArrayRegion(jOut, jOutOffset, jNpoints * 2, (jbyte*)out); +} + +// ---------------------------------------------------------------------------- + +static JNINativeMethod gMethods[] = { + {"fir21", "([BI[BII)V", (void*)android_media_ResampleInputStream_fir21}, +}; + + +int register_android_media_ResampleInputStream(JNIEnv *env) +{ + const char* const kClassPathName = "android/media/ResampleInputStream"; + jclass clazz; + + clazz = env->FindClass(kClassPathName); + if (clazz == NULL) { + LOGE("Can't find %s", kClassPathName); + return -1; + } + + return AndroidRuntime::registerNativeMethods(env, + kClassPathName, gMethods, NELEM(gMethods)); +} + + diff --git a/media/jni/soundpool/Android.mk b/media/jni/soundpool/Android.mk new file mode 100644 index 0000000..374ddeb --- /dev/null +++ b/media/jni/soundpool/Android.mk @@ -0,0 +1,18 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + android_media_SoundPool.cpp \ + SoundPool.cpp \ + SoundPoolThread.cpp + +LOCAL_SHARED_LIBRARIES := \ + libcutils \ + libutils \ + libandroid_runtime \ + libnativehelper \ + libmedia + +LOCAL_MODULE:= libsoundpool + +include $(BUILD_SHARED_LIBRARY) diff --git a/media/jni/soundpool/SoundPool.cpp b/media/jni/soundpool/SoundPool.cpp new file mode 100644 index 0000000..02731825 --- /dev/null +++ b/media/jni/soundpool/SoundPool.cpp @@ -0,0 +1,764 @@ +/* + * Copyright (C) 2007 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 "SoundPool" +#include <utils/Log.h> + +//#define USE_SHARED_MEM_BUFFER + +// XXX needed for timing latency +#include <utils/Timers.h> + +#include <sys/resource.h> +#include <media/AudioTrack.h> +#include <media/mediaplayer.h> + +#include "SoundPool.h" +#include "SoundPoolThread.h" + +namespace android +{ + +int kDefaultBufferCount = 4; +uint32_t kMaxSampleRate = 48000; +uint32_t kDefaultSampleRate = 44100; +uint32_t kDefaultFrameCount = 1200; + +SoundPool::SoundPool(jobject soundPoolRef, int maxChannels, int streamType, int srcQuality) +{ + LOGV("SoundPool constructor: maxChannels=%d, streamType=%d, srcQuality=%d", + maxChannels, streamType, srcQuality); + + if (maxChannels > 32) { + LOGW("App requested %d channels, capped at 32", maxChannels); + maxChannels = 32; + } + + mQuit = false; + mSoundPoolRef = soundPoolRef; + mDecodeThread = 0; + mMaxChannels = maxChannels; + mStreamType = streamType; + mSrcQuality = srcQuality; + mAllocated = 0; + mNextSampleID = 0; + mNextChannelID = 0; + + mChannelPool = new SoundChannel[maxChannels]; + for (int i = 0; i < maxChannels; ++i) { + mChannelPool[i].init(this); + mChannels.push_back(&mChannelPool[i]); + } + + // start decode thread + startThreads(); +} + +SoundPool::~SoundPool() +{ + LOGV("SoundPool destructor"); + mDecodeThread->quit(); + quit(); + + Mutex::Autolock lock(&mLock); + mChannels.clear(); + if (mChannelPool) + delete [] mChannelPool; + + // clean up samples + LOGV("clear samples"); + mSamples.clear(); + + if (mDecodeThread) + delete mDecodeThread; +} + +void SoundPool::addToRestartList(SoundChannel* channel) +{ + Mutex::Autolock lock(&mLock); + mRestart.push_back(channel); + mCondition.signal(); +} + +int SoundPool::beginThread(void* arg) +{ + SoundPool* p = (SoundPool*)arg; + return p->run(); +} + +int SoundPool::run() +{ + mLock.lock(); + while (!mQuit) { + mCondition.wait(mLock); + LOGV("awake"); + if (mQuit) break; + + while (!mRestart.empty()) { + SoundChannel* channel; + LOGV("Getting channel from list"); + List<SoundChannel*>::iterator iter = mRestart.begin(); + channel = *iter; + mRestart.erase(iter); + if (channel) channel->nextEvent(); + if (mQuit) break; + } + } + + mRestart.clear(); + mCondition.signal(); + mLock.unlock(); + LOGV("goodbye"); + return 0; +} + +void SoundPool::quit() +{ + mLock.lock(); + mQuit = true; + mCondition.signal(); + mCondition.wait(mLock); + LOGV("return from quit"); + mLock.unlock(); +} + +bool SoundPool::startThreads() +{ + createThread(beginThread, this); + if (mDecodeThread == NULL) + mDecodeThread = new SoundPoolThread(this); + return mDecodeThread != NULL; +} + +SoundChannel* SoundPool::findChannel(int channelID) +{ + for (int i = 0; i < mMaxChannels; ++i) { + if (mChannelPool[i].channelID() == channelID) { + return &mChannelPool[i]; + } + } + return NULL; +} + +SoundChannel* SoundPool::findNextChannel(int channelID) +{ + for (int i = 0; i < mMaxChannels; ++i) { + if (mChannelPool[i].nextChannelID() == channelID) { + return &mChannelPool[i]; + } + } + return NULL; +} + +int SoundPool::load(const char* path, int priority) +{ + LOGV("load: path=%s, priority=%d", path, priority); + Mutex::Autolock lock(&mLock); + sp<Sample> sample = new Sample(++mNextSampleID, path); + mSamples.add(sample->sampleID(), sample); + doLoad(sample); + return sample->sampleID(); +} + +int SoundPool::load(int fd, int64_t offset, int64_t length, int priority) +{ + LOGV("load: fd=%d, offset=%lld, length=%lld, priority=%d", + fd, offset, length, priority); + Mutex::Autolock lock(&mLock); + sp<Sample> sample = new Sample(++mNextSampleID, fd, offset, length); + mSamples.add(sample->sampleID(), sample); + doLoad(sample); + return sample->sampleID(); +} + +void SoundPool::doLoad(sp<Sample>& sample) +{ + LOGV("doLoad: loading sample sampleID=%d", sample->sampleID()); + sample->startLoad(); + mDecodeThread->loadSample(sample->sampleID()); +} + +bool SoundPool::unload(int sampleID) +{ + LOGV("unload: sampleID=%d", sampleID); + Mutex::Autolock lock(&mLock); + return mSamples.removeItem(sampleID); +} + +int SoundPool::play(int sampleID, float leftVolume, float rightVolume, + int priority, int loop, float rate) +{ + LOGV("sampleID=%d, leftVolume=%f, rightVolume=%f, priority=%d, loop=%d, rate=%f", + sampleID, leftVolume, rightVolume, priority, loop, rate); + sp<Sample> sample; + SoundChannel* channel; + int channelID; + + // scope for lock + { + Mutex::Autolock lock(&mLock); + + // is sample ready? + sample = findSample(sampleID); + if ((sample == 0) || (sample->state() != Sample::READY)) { + LOGW(" sample %d not READY", sampleID); + return 0; + } + + dump(); + + // allocate a channel + channel = allocateChannel(priority); + + // no channel allocated - return 0 + if (!channel) { + LOGV("No channel allocated"); + return 0; + } + + channelID = ++mNextChannelID; + } + + LOGV("channel state = %d", channel->state()); + channel->play(sample, channelID, leftVolume, rightVolume, priority, loop, rate); + return channelID; +} + +SoundChannel* SoundPool::allocateChannel(int priority) +{ + List<SoundChannel*>::iterator iter; + SoundChannel* channel = NULL; + + // allocate a channel + if (!mChannels.empty()) { + iter = mChannels.begin(); + if (priority >= (*iter)->priority()) { + channel = *iter; + mChannels.erase(iter); + LOGV("Allocated active channel"); + } + } + + // update priority and put it back in the list + if (channel) { + channel->setPriority(priority); + for (iter = mChannels.begin(); iter != mChannels.end(); ++iter) { + if (priority < (*iter)->priority()) { + break; + } + } + mChannels.insert(iter, channel); + } + return channel; +} + +// move a channel from its current position to the front of the list +void SoundPool::moveToFront(SoundChannel* channel) +{ + for (List<SoundChannel*>::iterator iter = mChannels.begin(); iter != mChannels.end(); ++iter) { + if (*iter == channel) { + mChannels.erase(iter); + mChannels.push_front(channel); + break; + } + } +} + +void SoundPool::pause(int channelID) +{ + LOGV("pause(%d)", channelID); + Mutex::Autolock lock(&mLock); + SoundChannel* channel = findChannel(channelID); + if (channel) { + channel->pause(); + } +} + +void SoundPool::resume(int channelID) +{ + LOGV("resume(%d)", channelID); + Mutex::Autolock lock(&mLock); + SoundChannel* channel = findChannel(channelID); + if (channel) { + channel->resume(); + } +} + +void SoundPool::stop(int channelID) +{ + LOGV("stop(%d)", channelID); + Mutex::Autolock lock(&mLock); + SoundChannel* channel = findChannel(channelID); + if (channel) { + channel->stop(); + } else { + channel = findNextChannel(channelID); + if (channel) + channel->clearNextEvent(); + } +} + +void SoundPool::setVolume(int channelID, float leftVolume, float rightVolume) +{ + Mutex::Autolock lock(&mLock); + SoundChannel* channel = findChannel(channelID); + if (channel) { + channel->setVolume(leftVolume, rightVolume); + } +} + +void SoundPool::setPriority(int channelID, int priority) +{ + LOGV("setPriority(%d, %d)", channelID, priority); + Mutex::Autolock lock(&mLock); + SoundChannel* channel = findChannel(channelID); + if (channel) { + channel->setPriority(priority); + } +} + +void SoundPool::setLoop(int channelID, int loop) +{ + LOGV("setLoop(%d, %d)", channelID, loop); + Mutex::Autolock lock(&mLock); + SoundChannel* channel = findChannel(channelID); + if (channel) { + channel->setLoop(loop); + } +} + +void SoundPool::setRate(int channelID, float rate) +{ + LOGV("setRate(%d, %f)", channelID, rate); + Mutex::Autolock lock(&mLock); + SoundChannel* channel = findChannel(channelID); + if (channel) { + channel->setRate(rate); + } +} + +// call with lock held +void SoundPool::done(SoundChannel* channel) +{ + LOGV("done(%d)", channel->channelID()); + + // if "stolen", play next event + if (channel->nextChannelID() != 0) { + LOGV("add to restart list"); + addToRestartList(channel); + } + + // return to idle state + else { + LOGV("move to front"); + moveToFront(channel); + } +} + +void SoundPool::dump() +{ + for (int i = 0; i < mMaxChannels; ++i) { + mChannelPool[i].dump(); + } +} + + +Sample::Sample(int sampleID, const char* url) +{ + init(); + mSampleID = sampleID; + mUrl = strdup(url); + LOGV("create sampleID=%d, url=%s", mSampleID, mUrl); +} + +Sample::Sample(int sampleID, int fd, int64_t offset, int64_t length) +{ + init(); + mSampleID = sampleID; + mFd = dup(fd); + mOffset = offset; + mLength = length; + LOGV("create sampleID=%d, fd=%d, offset=%lld, length=%lld", mSampleID, mFd, mLength, mOffset); +} + +void Sample::init() +{ + mData = 0; + mSize = 0; + mRefCount = 0; + mSampleID = 0; + mState = UNLOADED; + mFd = -1; + mOffset = 0; + mLength = 0; + mUrl = 0; +} + +Sample::~Sample() +{ + LOGV("Sample::destructor sampleID=%d, fd=%d", mSampleID, mFd); + if (mFd > 0) { + LOGV("close(%d)", mFd); + ::close(mFd); + } + mData.clear(); + delete mUrl; +} + +void Sample::doLoad() +{ + uint32_t sampleRate; + int numChannels; + int format; + sp<IMemory> p; + LOGV("Start decode"); + if (mUrl) { + p = MediaPlayer::decode(mUrl, &sampleRate, &numChannels, &format); + } else { + p = MediaPlayer::decode(mFd, mOffset, mLength, &sampleRate, &numChannels, &format); + LOGV("close(%d)", mFd); + ::close(mFd); + mFd = -1; + } + if (p == 0) { + LOGE("Unable to load sample: %s", mUrl); + return; + } + LOGV("pointer = %p, size = %u, sampleRate = %u, numChannels = %d", + p->pointer(), p->size(), sampleRate, numChannels); + + if (sampleRate > kMaxSampleRate) { + LOGE("Sample rate (%u) out of range", sampleRate); + return; + } + + if ((numChannels < 1) || (numChannels > 2)) { + LOGE("Sample channel count (%d) out of range", numChannels); + return; + } + + //_dumpBuffer(p->pointer(), p->size()); + uint8_t* q = static_cast<uint8_t*>(p->pointer()) + p->size() - 10; + //_dumpBuffer(q, 10, 10, false); + + mData = p; + mSize = p->size(); + mSampleRate = sampleRate; + mNumChannels = numChannels; + mFormat = format; + mState = READY; +} + + +void SoundChannel::init(SoundPool* soundPool) +{ + mSoundPool = soundPool; +} + +void SoundChannel::play(const sp<Sample>& sample, int nextChannelID, float leftVolume, + float rightVolume, int priority, int loop, float rate) +{ + AudioTrack* oldTrack; + + LOGV("play %p: sampleID=%d, channelID=%d, leftVolume=%f, rightVolume=%f, priority=%d, loop=%d, rate=%f", + this, sample->sampleID(), nextChannelID, leftVolume, rightVolume, priority, loop, rate); + + // if not idle, this voice is being stolen + if (mState != IDLE) { + LOGV("channel %d stolen - event queued for channel %d", channelID(), nextChannelID); + stop_l(); + mNextEvent.set(sample, nextChannelID, leftVolume, rightVolume, priority, loop, rate); +#ifdef USE_SHARED_MEM_BUFFER + mSoundPool->done(this); +#endif + return; + } + + // initialize track + int afFrameCount; + int afSampleRate; + int streamType = mSoundPool->streamType(); + if (AudioSystem::getOutputFrameCount(&afFrameCount, streamType) != NO_ERROR) { + afFrameCount = kDefaultFrameCount; + } + if (AudioSystem::getOutputSamplingRate(&afSampleRate, streamType) != NO_ERROR) { + afSampleRate = kDefaultSampleRate; + } + int numChannels = sample->numChannels(); + uint32_t sampleRate = uint32_t(float(sample->sampleRate()) * rate + 0.5); + uint32_t bufferFrames = (afFrameCount * sampleRate) / afSampleRate; + uint32_t frameCount = 0; + + if (loop) { + frameCount = sample->size()/numChannels/((sample->format() == AudioSystem::PCM_16_BIT) ? sizeof(int16_t) : sizeof(uint8_t)); + } + + // Ensure minimum audio buffer size in case of short looped sample + if(frameCount < kDefaultBufferCount * bufferFrames) { + frameCount = kDefaultBufferCount * bufferFrames; + } + + AudioTrack* newTrack; + + // mToggle toggles each time a track is started on a given channel. + // The toggle is concatenated with the SoundChannel address and passed to AudioTrack + // as callback user data. This enables the detection of callbacks received from the old + // audio track while the new one is being started and avoids processing them with + // wrong audio audio buffer size (mAudioBufferSize) + unsigned long toggle = mToggle ^ 1; + void *userData = (void *)((unsigned long)this | toggle); + +#ifdef USE_SHARED_MEM_BUFFER + newTrack = new AudioTrack(streamType, sampleRate, sample->format(), + numChannels, sample->getIMemory(), 0, callback, userData); +#else + newTrack = new AudioTrack(streamType, sampleRate, sample->format(), + numChannels, frameCount, 0, callback, userData, bufferFrames); +#endif + if (newTrack->initCheck() != NO_ERROR) { + LOGE("Error creating AudioTrack"); + delete newTrack; + return; + } + LOGV("setVolume %p", newTrack); + newTrack->setVolume(leftVolume, rightVolume); + newTrack->setLoop(0, frameCount, loop); + + { + Mutex::Autolock lock(&mLock); + // From now on, AudioTrack callbacks recevieved with previous toggle value will be ignored. + mToggle = toggle; + oldTrack = mAudioTrack; + mAudioTrack = newTrack; + mPos = 0; + mSample = sample; + mChannelID = nextChannelID; + mPriority = priority; + mLoop = loop; + mLeftVolume = leftVolume; + mRightVolume = rightVolume; + mNumChannels = numChannels; + mRate = rate; + clearNextEvent(); + mState = PLAYING; + mAudioTrack->start(); + mAudioBufferSize = newTrack->frameCount()*newTrack->frameSize(); + } + + LOGV("delete oldTrack %p", oldTrack); + delete oldTrack; +} + +void SoundChannel::nextEvent() +{ + sp<Sample> sample; + int nextChannelID; + float leftVolume; + float rightVolume; + int priority; + int loop; + float rate; + + // check for valid event + { + Mutex::Autolock lock(&mLock); + nextChannelID = mNextEvent.channelID(); + if (nextChannelID == 0) { + LOGV("stolen channel has no event"); + return; + } + + sample = mNextEvent.sample(); + leftVolume = mNextEvent.leftVolume(); + rightVolume = mNextEvent.rightVolume(); + priority = mNextEvent.priority(); + loop = mNextEvent.loop(); + rate = mNextEvent.rate(); + } + + LOGV("Starting stolen channel %d -> %d", channelID(), nextChannelID); + play(sample, nextChannelID, leftVolume, rightVolume, priority, loop, rate); +} + +void SoundChannel::callback(int event, void* user, void *info) +{ + unsigned long toggle = (unsigned long)user & 1; + SoundChannel* channel = static_cast<SoundChannel*>((void *)((unsigned long)user & ~1)); + + if (channel->mToggle != toggle) { + LOGV("callback with wrong toggle"); + return; + } + channel->process(event, info); +} + +void SoundChannel::process(int event, void *info) +{ + //LOGV("process(%d)", mChannelID); + sp<Sample> sample = mSample; + +// LOGV("SoundChannel::process event %d", event); + + if (event == AudioTrack::EVENT_MORE_DATA) { + AudioTrack::Buffer* b = static_cast<AudioTrack::Buffer *>(info); + + // check for stop state + if (b->size == 0) return; + + if (sample != 0) { + // fill buffer + uint8_t* q = (uint8_t*) b->i8; + size_t count = 0; + + if (mPos < (int)sample->size()) { + uint8_t* p = sample->data() + mPos; + count = sample->size() - mPos; + if (count > b->size) { + count = b->size; + } + memcpy(q, p, count); + LOGV("fill: q=%p, p=%p, mPos=%u, b->size=%u, count=%d", q, p, mPos, b->size, count); + } else if (mPos < mAudioBufferSize) { + count = mAudioBufferSize - mPos; + if (count > b->size) { + count = b->size; + } + memset(q, 0, count); + LOGV("fill extra: q=%p, mPos=%u, b->size=%u, count=%d", q, mPos, b->size, count); + } + + mPos += count; + b->size = count; + //LOGV("buffer=%p, [0]=%d", b->i16, b->i16[0]); + } + } else if (event == AudioTrack::EVENT_UNDERRUN) { + LOGV("stopping track"); + stop(); + } else if (event == AudioTrack::EVENT_LOOP_END) { + LOGV("End loop: %d", *(int *)info); + } +} + + +// call with lock held +void SoundChannel::stop_l() +{ + if (mState != IDLE) { + setVolume_l(0, 0); + LOGV("stop"); + mAudioTrack->stop(); + mSample.clear(); + mState = IDLE; + mPriority = IDLE_PRIORITY; + } +} + +void SoundChannel::stop() +{ + { + Mutex::Autolock lock(&mLock); + stop_l(); + } + mSoundPool->done(this); +} + +//FIXME: Pause is a little broken right now +void SoundChannel::pause() +{ + Mutex::Autolock lock(&mLock); + if (mState == PLAYING) { + LOGV("pause track"); + mState = PAUSED; + mAudioTrack->pause(); + } +} + +void SoundChannel::resume() +{ + Mutex::Autolock lock(&mLock); + if (mState == PAUSED) { + LOGV("resume track"); + mState = PLAYING; + mAudioTrack->start(); + } +} + +void SoundChannel::setRate(float rate) +{ + Mutex::Autolock lock(&mLock); + if (mAudioTrack != 0 && mSample.get() != 0) { + uint32_t sampleRate = uint32_t(float(mSample->sampleRate()) * rate + 0.5); + mAudioTrack->setSampleRate(sampleRate); + mRate = rate; + } +} + +// call with lock held +void SoundChannel::setVolume_l(float leftVolume, float rightVolume) +{ + mLeftVolume = leftVolume; + mRightVolume = rightVolume; + if (mAudioTrack != 0) mAudioTrack->setVolume(leftVolume, rightVolume); +} + +void SoundChannel::setVolume(float leftVolume, float rightVolume) +{ + Mutex::Autolock lock(&mLock); + setVolume_l(leftVolume, rightVolume); +} + +void SoundChannel::setLoop(int loop) +{ + Mutex::Autolock lock(&mLock); + if (mAudioTrack != 0 && mSample.get() != 0) { + mAudioTrack->setLoop(0, mSample->size()/mNumChannels/((mSample->format() == AudioSystem::PCM_16_BIT) ? sizeof(int16_t) : sizeof(uint8_t)), loop); + mLoop = loop; + } +} + +SoundChannel::~SoundChannel() +{ + LOGV("SoundChannel destructor"); + if (mAudioTrack) { + LOGV("stop track"); + mAudioTrack->stop(); + delete mAudioTrack; + } + clearNextEvent(); + mSample.clear(); +} + +void SoundChannel::dump() +{ + LOGV("mState = %d mChannelID=%d, mNumChannels=%d, mPos = %d, mPriority=%d, mLoop=%d", + mState, mChannelID, mNumChannels, mPos, mPriority, mLoop); +} + +void SoundEvent::set(const sp<Sample>& sample, int channelID, float leftVolume, + float rightVolume, int priority, int loop, float rate) +{ + mSample =sample; + mChannelID = channelID; + mLeftVolume = leftVolume; + mRightVolume = rightVolume; + mPriority = priority; + mLoop = loop; + mRate =rate; +} + +} // end namespace android + diff --git a/media/jni/soundpool/SoundPool.h b/media/jni/soundpool/SoundPool.h new file mode 100644 index 0000000..7802781 --- /dev/null +++ b/media/jni/soundpool/SoundPool.h @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2007 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. + */ + +#ifndef SOUNDPOOL_H_ +#define SOUNDPOOL_H_ + +#include <utils/threads.h> +#include <utils/List.h> +#include <utils/Vector.h> +#include <utils/KeyedVector.h> +#include <media/AudioTrack.h> +#include <cutils/atomic.h> + +#include <nativehelper/jni.h> + +namespace android { + +static const int IDLE_PRIORITY = -1; + +// forward declarations +class SoundEvent; +class SoundPoolThread; +class SoundPool; + +// for queued events +class SoundPoolEvent { +public: + SoundPoolEvent(int msg, int arg1=0, int arg2=0) : + mMsg(msg), mArg1(arg1), mArg2(arg2) {} + int mMsg; + int mArg1; + int mArg2; +}; + +// JNI for calling back Java SoundPool object +extern void android_soundpool_SoundPool_notify(jobject ref, const SoundPoolEvent *event); + +// tracks samples used by application +class Sample : public RefBase { +public: + enum sample_state { UNLOADED, LOADING, READY, UNLOADING }; + Sample(int sampleID, const char* url); + Sample(int sampleID, int fd, int64_t offset, int64_t length); + ~Sample(); + int sampleID() { return mSampleID; } + int numChannels() { return mNumChannels; } + int sampleRate() { return mSampleRate; } + int format() { return mFormat; } + size_t size() { return mSize; } + int state() { return mState; } + uint8_t* data() { return static_cast<uint8_t*>(mData->pointer()); } + void doLoad(); + void startLoad() { mState = LOADING; } + sp<IMemory> getIMemory() { return mData; } + + // hack + void init(int numChannels, int sampleRate, int format, size_t size, sp<IMemory> data ) { + mNumChannels = numChannels; mSampleRate = sampleRate; mFormat = format; mSize = size; mData = data; } + +private: + void init(); + + size_t mSize; + volatile int32_t mRefCount; + uint16_t mSampleID; + uint16_t mSampleRate; + uint8_t mState : 3; + uint8_t mNumChannels : 2; + uint8_t mFormat : 2; + int mFd; + int64_t mOffset; + int64_t mLength; + char* mUrl; + sp<IMemory> mData; +}; + +// stores pending events for stolen channels +class SoundEvent +{ +public: + SoundEvent() : mChannelID(0), mLeftVolume(0), mRightVolume(0), + mPriority(IDLE_PRIORITY), mLoop(0), mRate(0) {} + void set(const sp<Sample>& sample, int channelID, float leftVolume, + float rightVolume, int priority, int loop, float rate); + sp<Sample> sample() { return mSample; } + int channelID() { return mChannelID; } + float leftVolume() { return mLeftVolume; } + float rightVolume() { return mRightVolume; } + int priority() { return mPriority; } + int loop() { return mLoop; } + float rate() { return mRate; } + void clear() { mChannelID = 0; mSample.clear(); } + +protected: + sp<Sample> mSample; + int mChannelID; + float mLeftVolume; + float mRightVolume; + int mPriority; + int mLoop; + float mRate; +}; + +// for channels aka AudioTracks +class SoundChannel : public SoundEvent { +public: + enum state { IDLE, RESUMING, STOPPING, PAUSED, PLAYING }; + SoundChannel() : mAudioTrack(0), mState(IDLE), mNumChannels(1), mPos(0), mToggle(0) {} + ~SoundChannel(); + void init(SoundPool* soundPool); + void play(const sp<Sample>& sample, int channelID, float leftVolume, float rightVolume, + int priority, int loop, float rate); + void setVolume_l(float leftVolume, float rightVolume); + void setVolume(float leftVolume, float rightVolume); + void stop_l(); + void stop(); + void pause(); + void resume(); + void setRate(float rate); + int state() { return mState; } + void setPriority(int priority) { mPriority = priority; } + void setLoop(int loop); + int numChannels() { return mNumChannels; } + void clearNextEvent() { mNextEvent.clear(); } + void nextEvent(); + int nextChannelID() { return mNextEvent.channelID(); } + void dump(); + +private: + static void callback(int event, void* user, void *info); + void process(int event, void *info); + + SoundPool* mSoundPool; + AudioTrack* mAudioTrack; + SoundEvent mNextEvent; + Mutex mLock; + int mState; + int mNumChannels; + int mPos; + int mAudioBufferSize; + unsigned long mToggle; +}; + +// application object for managing a pool of sounds +class SoundPool { + friend class SoundPoolThread; + friend class SoundChannel; +public: + SoundPool(jobject soundPoolRef, int maxChannels, int streamType, int srcQuality); + ~SoundPool(); + int load(const char* url, int priority); + int load(int fd, int64_t offset, int64_t length, int priority); + bool unload(int sampleID); + int play(int sampleID, float leftVolume, float rightVolume, int priority, + int loop, float rate); + void pause(int channelID); + void resume(int channelID); + void stop(int channelID); + void setVolume(int channelID, float leftVolume, float rightVolume); + void setPriority(int channelID, int priority); + void setLoop(int channelID, int loop); + void setRate(int channelID, float rate); + int streamType() const { return mStreamType; } + int srcQuality() const { return mSrcQuality; } + + // called from SoundPoolThread + void sampleLoaded(int sampleID); + + // called from AudioTrack thread + void done(SoundChannel* channel); + +private: + SoundPool() {} // no default constructor + bool startThreads(); + void doLoad(sp<Sample>& sample); + inline void notify(const SoundPoolEvent* event) { + android_soundpool_SoundPool_notify(mSoundPoolRef, event); + } + sp<Sample> findSample(int sampleID) { return mSamples.valueFor(sampleID); } + SoundChannel* findChannel (int channelID); + SoundChannel* findNextChannel (int channelID); + SoundChannel* allocateChannel(int priority); + void moveToFront(SoundChannel* channel); + void dump(); + + // restart thread + void addToRestartList(SoundChannel* channel); + static int beginThread(void* arg); + int run(); + void quit(); + + jobject mSoundPoolRef; + Mutex mLock; + Condition mCondition; + SoundPoolThread* mDecodeThread; + SoundChannel* mChannelPool; + List<SoundChannel*> mChannels; + List<SoundChannel*> mRestart; + DefaultKeyedVector< int, sp<Sample> > mSamples; + int mMaxChannels; + int mStreamType; + int mSrcQuality; + int mAllocated; + int mNextSampleID; + int mNextChannelID; + bool mQuit; +}; + +} // end namespace android + +#endif /*SOUNDPOOL_H_*/ diff --git a/media/jni/soundpool/SoundPoolThread.cpp b/media/jni/soundpool/SoundPoolThread.cpp new file mode 100644 index 0000000..4e6798d --- /dev/null +++ b/media/jni/soundpool/SoundPoolThread.cpp @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2007 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 "SoundPoolThread" +#include "utils/Log.h" + +#include "SoundPoolThread.h" + +namespace android { + +void SoundPoolThread::MessageQueue::write(SoundPoolMsg msg) { + LOGV("MessageQueue::write - acquiring lock\n"); + Mutex::Autolock lock(&mLock); + while (mQueue.size() >= maxMessages) { + LOGV("MessageQueue::write - wait\n"); + mCondition.wait(mLock); + } + LOGV("MessageQueue::write - push message\n"); + mQueue.push(msg); + mCondition.signal(); +} + +const SoundPoolMsg SoundPoolThread::MessageQueue::read() { + LOGV("MessageQueue::read - acquiring lock\n"); + Mutex::Autolock lock(&mLock); + while (mQueue.size() == 0) { + LOGV("MessageQueue::read - wait\n"); + mCondition.wait(mLock); + } + SoundPoolMsg msg = mQueue[0]; + LOGV("MessageQueue::read - retrieve message\n"); + mQueue.removeAt(0); + mCondition.signal(); + return msg; +} + +void SoundPoolThread::MessageQueue::quit() { + Mutex::Autolock lock(&mLock); + mQueue.clear(); + mQueue.push(SoundPoolMsg(SoundPoolMsg::KILL, 0)); + mCondition.signal(); + mCondition.wait(mLock); + LOGV("return from quit"); +} + +SoundPoolThread::SoundPoolThread(SoundPool* soundPool) : + mSoundPool(soundPool) +{ + mMessages.setCapacity(maxMessages); + createThread(beginThread, this); +} + +SoundPoolThread::~SoundPoolThread() +{ +} + +int SoundPoolThread::beginThread(void* arg) { + LOGV("beginThread"); + SoundPoolThread* soundPoolThread = (SoundPoolThread*)arg; + return soundPoolThread->run(); +} + +int SoundPoolThread::run() { + LOGV("run"); + for (;;) { + SoundPoolMsg msg = mMessages.read(); + LOGV("Got message m=%d, mData=%d", msg.mMessageType, msg.mData); + switch (msg.mMessageType) { + case SoundPoolMsg::KILL: + LOGV("goodbye"); + return NO_ERROR; + case SoundPoolMsg::LOAD_SAMPLE: + doLoadSample(msg.mData); + break; + default: + LOGW("run: Unrecognized message %d\n", + msg.mMessageType); + break; + } + } +} + +void SoundPoolThread::loadSample(int sampleID) { + mMessages.write(SoundPoolMsg(SoundPoolMsg::LOAD_SAMPLE, sampleID)); +} + +void SoundPoolThread::doLoadSample(int sampleID) { + sp <Sample> sample = mSoundPool->findSample(sampleID); + if (sample != 0) { + sample->doLoad(); + } +} + +} // end namespace android diff --git a/media/jni/soundpool/SoundPoolThread.h b/media/jni/soundpool/SoundPoolThread.h new file mode 100644 index 0000000..459a764 --- /dev/null +++ b/media/jni/soundpool/SoundPoolThread.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2007 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. + */ + +#ifndef SOUNDPOOLTHREAD_H_ +#define SOUNDPOOLTHREAD_H_ + +#include <utils/threads.h> +#include <utils/Vector.h> +#include <media/AudioTrack.h> + +#include "SoundPool.h" + +namespace android { + +class SoundPoolMsg { +public: + enum MessageType { INVALID, KILL, LOAD_SAMPLE, PLAY_SAMPLE, SAMPLE_DONE }; + SoundPoolMsg() : mMessageType(INVALID), mData(0) {} + SoundPoolMsg(MessageType MessageType, int data) : + mMessageType(MessageType), mData(data) {} + uint8_t mMessageType; + uint8_t mData; + uint8_t mData2; + uint8_t mData3; +}; + +/* + * This class handles background requests from the SoundPool + */ +class SoundPoolThread { +public: + SoundPoolThread(SoundPool* SoundPool); + ~SoundPoolThread(); + void loadSample(int sampleID); + void quit() { mMessages.quit(); } + +private: + static const size_t maxMessages = 5; + + class MessageQueue { + public: + void write(SoundPoolMsg msg); + const SoundPoolMsg read(); + void setCapacity(size_t size) { mQueue.setCapacity(size); } + void quit(); + private: + Vector<SoundPoolMsg> mQueue; + Mutex mLock; + Condition mCondition; + }; + + static int beginThread(void* arg); + int run(); + void doLoadSample(int sampleID); + + SoundPool* mSoundPool; + MessageQueue mMessages; +}; + +} // end namespace android + +#endif /*SOUNDPOOLTHREAD_H_*/ diff --git a/media/jni/soundpool/android_media_SoundPool.cpp b/media/jni/soundpool/android_media_SoundPool.cpp new file mode 100644 index 0000000..0ce2d6f --- /dev/null +++ b/media/jni/soundpool/android_media_SoundPool.cpp @@ -0,0 +1,270 @@ +/* + * 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. + */ + +#include <stdio.h> + +//#define LOG_NDEBUG 0 +#define LOG_TAG "SoundPool" + +#include <utils/Log.h> +#include <nativehelper/jni.h> +#include <nativehelper/JNIHelp.h> +#include <android_runtime/AndroidRuntime.h> +#include "SoundPool.h" + +using namespace android; + +static struct fields_t { + jfieldID mNativeContext; + jclass mSoundPoolClass; +} fields; + +static inline SoundPool* MusterSoundPool(JNIEnv *env, jobject thiz) { + return (SoundPool*)env->GetIntField(thiz, fields.mNativeContext); +} + +// ---------------------------------------------------------------------------- +static int +android_media_SoundPool_load_URL(JNIEnv *env, jobject thiz, jstring path, jint priority) +{ + LOGV("android_media_SoundPool_load_URL"); + SoundPool *ap = MusterSoundPool(env, thiz); + if (path == NULL) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return 0; + } + const char* s = env->GetStringUTFChars(path, NULL); + int id = ap->load(s, priority); + env->ReleaseStringUTFChars(path, s); + return id; +} + +static int +android_media_SoundPool_load_FD(JNIEnv *env, jobject thiz, jobject fileDescriptor, + jlong offset, jlong length, jint priority) +{ + LOGV("android_media_SoundPool_load_FD"); + SoundPool *ap = MusterSoundPool(env, thiz); + if (ap == NULL) return 0; + return ap->load(getParcelFileDescriptorFD(env, fileDescriptor), + int64_t(offset), int64_t(length), int(priority)); +} + +static bool +android_media_SoundPool_unload(JNIEnv *env, jobject thiz, jint sampleID) { + LOGV("android_media_SoundPool_unload\n"); + SoundPool *ap = MusterSoundPool(env, thiz); + if (ap == NULL) return 0; + return ap->unload(sampleID); +} + +static int +android_media_SoundPool_play(JNIEnv *env, jobject thiz, jint sampleID, + jfloat leftVolume, jfloat rightVolume, jint priority, jint loop, + jfloat rate) +{ + LOGV("android_media_SoundPool_play\n"); + SoundPool *ap = MusterSoundPool(env, thiz); + if (ap == NULL) return 0; + return ap->play(sampleID, leftVolume, rightVolume, priority, loop, rate); +} + +static void +android_media_SoundPool_pause(JNIEnv *env, jobject thiz, jint channelID) +{ + LOGV("android_media_SoundPool_pause"); + SoundPool *ap = MusterSoundPool(env, thiz); + if (ap == NULL) return; + ap->pause(channelID); +} + +static void +android_media_SoundPool_resume(JNIEnv *env, jobject thiz, jint channelID) +{ + LOGV("android_media_SoundPool_resume"); + SoundPool *ap = MusterSoundPool(env, thiz); + if (ap == NULL) return; + ap->resume(channelID); +} + +static void +android_media_SoundPool_stop(JNIEnv *env, jobject thiz, jint channelID) +{ + LOGV("android_media_SoundPool_stop"); + SoundPool *ap = MusterSoundPool(env, thiz); + if (ap == NULL) return; + ap->stop(channelID); +} + +static void +android_media_SoundPool_setVolume(JNIEnv *env, jobject thiz, jint channelID, + float leftVolume, float rightVolume) +{ + LOGV("android_media_SoundPool_setVolume"); + SoundPool *ap = MusterSoundPool(env, thiz); + if (ap == NULL) return; + ap->setVolume(channelID, leftVolume, rightVolume); +} + +static void +android_media_SoundPool_setPriority(JNIEnv *env, jobject thiz, jint channelID, + int priority) +{ + LOGV("android_media_SoundPool_setPriority"); + SoundPool *ap = MusterSoundPool(env, thiz); + if (ap == NULL) return; + ap->setPriority(channelID, priority); +} + +static void +android_media_SoundPool_setLoop(JNIEnv *env, jobject thiz, jint channelID, + int loop) +{ + LOGV("android_media_SoundPool_setLoop"); + SoundPool *ap = MusterSoundPool(env, thiz); + if (ap == NULL) return; + ap->setLoop(channelID, loop); +} + +static void +android_media_SoundPool_setRate(JNIEnv *env, jobject thiz, jint channelID, + float rate) +{ + LOGV("android_media_SoundPool_setRate"); + SoundPool *ap = MusterSoundPool(env, thiz); + if (ap == NULL) return; + ap->setRate(channelID, rate); +} + +static void +android_media_SoundPool_native_setup(JNIEnv *env, jobject thiz, + jobject weak_this, jint maxChannels, jint streamType, jint srcQuality) +{ + LOGV("android_media_SoundPool_native_setup"); + SoundPool *ap = new SoundPool(weak_this, maxChannels, streamType, srcQuality); + if (ap == NULL) { + jniThrowException(env, "java/lang/RuntimeException", "Out of memory"); + return; + } + + // save pointer to SoundPool C++ object in opaque field in Java object + env->SetIntField(thiz, fields.mNativeContext, (int)ap); +} + +static void +android_media_SoundPool_release(JNIEnv *env, jobject thiz) +{ + LOGV("android_media_SoundPool_release"); + SoundPool *ap = MusterSoundPool(env, thiz); + if (ap != NULL) { + env->SetIntField(thiz, fields.mNativeContext, 0); + delete ap; + } +} + +// ---------------------------------------------------------------------------- + +// Dalvik VM type signatures +static JNINativeMethod gMethods[] = { + { "_load", + "(Ljava/lang/String;I)I", + (void *)android_media_SoundPool_load_URL + }, + { "_load", + "(Ljava/io/FileDescriptor;JJI)I", + (void *)android_media_SoundPool_load_FD + }, + { "unload", + "(I)Z", + (void *)android_media_SoundPool_unload + }, + { "play", + "(IFFIIF)I", + (void *)android_media_SoundPool_play + }, + { "pause", + "(I)V", + (void *)android_media_SoundPool_pause + }, + { "resume", + "(I)V", + (void *)android_media_SoundPool_resume + }, + { "stop", + "(I)V", + (void *)android_media_SoundPool_stop + }, + { "setVolume", + "(IFF)V", + (void *)android_media_SoundPool_setVolume + }, + { "setPriority", + "(II)V", + (void *)android_media_SoundPool_setPriority + }, + { "setLoop", + "(II)V", + (void *)android_media_SoundPool_setLoop + }, + { "setRate", + "(IF)V", + (void *)android_media_SoundPool_setRate + }, + { "native_setup", + "(Ljava/lang/Object;III)V", + (void*)android_media_SoundPool_native_setup + }, + { "release", + "()V", + (void*)android_media_SoundPool_release + } +}; + +static const char* const kClassPathName = "android/media/SoundPool"; + +jint JNI_OnLoad(JavaVM* vm, void* reserved) +{ + JNIEnv* env = NULL; + jint result = -1; + jclass clazz; + + if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { + LOGE("ERROR: GetEnv failed\n"); + goto bail; + } + assert(env != NULL); + + clazz = env->FindClass(kClassPathName); + if (clazz == NULL) { + LOGE("Can't find %s", kClassPathName); + goto bail; + } + + fields.mNativeContext = env->GetFieldID(clazz, "mNativeContext", "I"); + if (fields.mNativeContext == NULL) { + LOGE("Can't find SoundPool.mNativeContext"); + goto bail; + } + + if (AndroidRuntime::registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods)) < 0) + goto bail; + + /* success -- return valid version number */ + result = JNI_VERSION_1_4; + +bail: + return result; +} |