summaryrefslogtreecommitdiffstats
path: root/media/jni
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2009-03-03 19:31:44 -0800
committerThe Android Open Source Project <initial-contribution@android.com>2009-03-03 19:31:44 -0800
commit9066cfe9886ac131c34d59ed0e2d287b0e3c0087 (patch)
treed88beb88001f2482911e3d28e43833b50e4b4e97 /media/jni
parentd83a98f4ce9cfa908f5c54bbd70f03eec07e7553 (diff)
downloadframeworks_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.mk42
-rw-r--r--media/jni/MODULE_LICENSE_APACHE20
-rw-r--r--media/jni/NOTICE190
-rw-r--r--media/jni/android_media_AmrInputStream.cpp184
-rw-r--r--media/jni/android_media_MediaMetadataRetriever.cpp327
-rw-r--r--media/jni/android_media_MediaPlayer.cpp573
-rw-r--r--media/jni/android_media_MediaRecorder.cpp416
-rw-r--r--media/jni/android_media_MediaScanner.cpp304
-rw-r--r--media/jni/android_media_ResampleInputStream.cpp143
-rw-r--r--media/jni/soundpool/Android.mk18
-rw-r--r--media/jni/soundpool/SoundPool.cpp764
-rw-r--r--media/jni/soundpool/SoundPool.h224
-rw-r--r--media/jni/soundpool/SoundPoolThread.cpp108
-rw-r--r--media/jni/soundpool/SoundPoolThread.h75
-rw-r--r--media/jni/soundpool/android_media_SoundPool.cpp270
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;
+}