diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2008-12-17 18:05:43 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2008-12-17 18:05:43 -0800 |
commit | f013e1afd1e68af5e3b868c26a653bbfb39538f8 (patch) | |
tree | 7ad6c8fd9c7b55f4b4017171dec1cb760bbd26bf /media/jni | |
parent | e70cfafe580c6f2994c4827cd8a534aabf3eb05c (diff) | |
download | frameworks_base-f013e1afd1e68af5e3b868c26a653bbfb39538f8.zip frameworks_base-f013e1afd1e68af5e3b868c26a653bbfb39538f8.tar.gz frameworks_base-f013e1afd1e68af5e3b868c26a653bbfb39538f8.tar.bz2 |
Code drop from //branches/cupcake/...@124589
Diffstat (limited to 'media/jni')
-rw-r--r-- | media/jni/Android.mk | 11 | ||||
-rw-r--r-- | media/jni/android_media_AmrInputStream.cpp | 184 | ||||
-rw-r--r-- | media/jni/android_media_MediaMetadataRetriever.cpp | 229 | ||||
-rw-r--r-- | media/jni/android_media_MediaPlayer.cpp | 18 | ||||
-rw-r--r-- | media/jni/android_media_MediaRecorder.cpp | 136 | ||||
-rw-r--r-- | media/jni/android_media_MediaScanner.cpp | 20 | ||||
-rw-r--r-- | media/jni/soundpool/SoundPool.cpp | 558 | ||||
-rw-r--r-- | media/jni/soundpool/SoundPool.h | 73 |
8 files changed, 847 insertions, 382 deletions
diff --git a/media/jni/Android.mk b/media/jni/Android.mk index 32c8360..cb0fc83 100644 --- a/media/jni/Android.mk +++ b/media/jni/Android.mk @@ -6,7 +6,8 @@ LOCAL_SRC_FILES:= \ android_media_MediaPlayer.cpp \ android_media_MediaRecorder.cpp \ android_media_MediaScanner.cpp \ - android_media_MediaMetadataRetriever.cpp + android_media_MediaMetadataRetriever.cpp \ + android_media_AmrInputStream.cpp LOCAL_SHARED_LIBRARIES := \ libopencoreplayer \ @@ -15,12 +16,15 @@ LOCAL_SHARED_LIBRARIES := \ libnativehelper \ libcutils \ libutils \ - libmedia + libmedia \ + libsgl \ + libui LOCAL_STATIC_LIBRARIES := LOCAL_C_INCLUDES += \ external/tremor/Tremor \ + $(PV_INCLUDES) \ $(JNI_H_INCLUDE) \ $(call include-path-for, corecg graphics) @@ -31,4 +35,7 @@ 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/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 index ff58fba..5b0ecb8 100644 --- a/media/jni/android_media_MediaMetadataRetriever.cpp +++ b/media/jni/android_media_MediaMetadataRetriever.cpp @@ -15,23 +15,31 @@ ** limitations under the License. */ -#ifdef LOG_TAG -#undef LOG_TAG -#define LOG_TAG "MediaMetadataRetriever" -#endif +//#define LOG_NDEBUG 0 +#define LOG_TAG "MediaMetadataRetrieverJNI" #include <assert.h> #include <utils/Log.h> #include <utils/threads.h> #include <graphics/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; -static const char* const kClassPathName = "android/media/MediaMetadataRetriever"; +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) { @@ -50,13 +58,29 @@ static void process_media_retriever_call(JNIEnv *env, status_t opStatus, const c } } -static void android_media_MediaMetadataRetriever_setMode(JNIEnv *env, jobject thiz, jint mode) +static MediaMetadataRetriever* getRetriever(JNIEnv* env, jobject thiz) { - MediaMetadataRetriever::setMode(mode); + // 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; @@ -73,85 +97,195 @@ static void android_media_MediaMetadataRetriever_setDataSource(JNIEnv *env, jobj return; } - process_media_retriever_call(env, MediaMetadataRetriever::setDataSource(pathStr), "java/lang/RuntimeException", "setDataSource failed"); + process_media_retriever_call(env, retriever->setDataSource(pathStr), "java/lang/RuntimeException", "setDataSource failed"); env->ReleaseStringUTFChars(path, pathStr); } -static jobject android_media_MediaMetadataRetriever_extractMetadata(JNIEnv *env, jobject thiz, jint keyCode) +static void android_media_MediaMetadataRetriever_setDataSourceFD(JNIEnv *env, jobject thiz, jobject fileDescriptor, jlong offset, jlong length) { - const char* value = MediaMetadataRetriever::extractMetadata(keyCode); - if (!value) { - LOGV("extractMetadata: Metadata is not found"); - return NULL; + LOGV("setDataSource"); + Mutex::Autolock lock(sLock); + MediaMetadataRetriever* retriever = getRetriever(env, thiz); + if (retriever == 0) { + jniThrowException(env, "java/lang/IllegalStateException", "No retriever available"); + return; } - LOGV("extractMetadata: value (%s) for keyCode(%d)", value, keyCode); - return env->NewStringUTF(value); + 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) { - // Call native MediaMetadataRetriever::captureFrame method - SkBitmap *bitmap = MediaMetadataRetriever::captureFrame(); - if (!bitmap) { + LOGV("captureFrame"); + Mutex::Autolock lock(sLock); + MediaMetadataRetriever* retriever = getRetriever(env, thiz); + if (retriever == 0) { + jniThrowException(env, "java/lang/IllegalStateException", "No retriever available"); return NULL; } - // Create the bitmap by calling into Java! - jclass bitmapClazz = env->FindClass("android/graphics/Bitmap"); - if (!bitmapClazz) { - LOGE("captureFrame: Bitmap class is not found"); + // 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; } - jmethodID constructor = env->GetMethodID(bitmapClazz, "<init>", "(IZ[B)V"); - if (!constructor) { - LOGE("captureFrame: Bitmap constructor is not found"); + + // 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; } - return env->NewObject(bitmapClazz, constructor, (int) bitmap, true, 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) { - MediaAlbumArt* mediaAlbumArt = MediaMetadataRetriever::extractAlbumArt(); - if (!mediaAlbumArt) { + 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->getLength(); - char* data = mediaAlbumArt->getData(); + 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); - memcpy(bytes, data, len); - env->ReleaseByteArrayElements(array, bytes, 0); + if (bytes != NULL) { + memcpy(bytes, data, len); + env->ReleaseByteArrayElements(array, bytes, 0); + } } - delete []data; - delete mediaAlbumArt; + + // 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) { - MediaMetadataRetriever::release(); + 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) { - MediaMetadataRetriever::release(); + 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) { - MediaMetadataRetriever::create(); + 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[] = { - {"setMode", "(I)V", (void *)android_media_MediaMetadataRetriever_setMode}, {"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}, @@ -163,12 +297,31 @@ static JNINativeMethod nativeMethods[] = { // 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 index f9f3646..8083882 100644 --- a/media/jni/android_media_MediaPlayer.cpp +++ b/media/jni/android_media_MediaPlayer.cpp @@ -375,6 +375,17 @@ android_media_MediaPlayer_setLooping(JNIEnv *env, jobject thiz, jboolean looping 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) { @@ -450,6 +461,7 @@ static JNINativeMethod gMethods[] = { {"_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}, @@ -506,6 +518,7 @@ static int register_android_media_MediaPlayer(JNIEnv *env) 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); jint JNI_OnLoad(JavaVM* vm, void* reserved) { @@ -538,6 +551,11 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) goto bail; } + if (register_android_media_AmrInputStream(env) < 0) { + LOGE("ERROR: AmrInputStream native registration failed\n"); + goto bail; + } + /* success -- return valid version number */ result = JNI_VERSION_1_4; diff --git a/media/jni/android_media_MediaRecorder.cpp b/media/jni/android_media_MediaRecorder.cpp index f7adf4e..2810a9c 100644 --- a/media/jni/android_media_MediaRecorder.cpp +++ b/media/jni/android_media_MediaRecorder.cpp @@ -1,23 +1,26 @@ -/* //device/libs/media_jni/android_media_MediaRecorder.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 "MediaRecorder" -#include "utils/Log.h" - +/* + * 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> @@ -37,6 +40,9 @@ 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 fields_t { jfieldID context; jfieldID surface; @@ -49,12 +55,14 @@ static fields_t fields; 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); } static void 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); } else if (opStatus != (status_t)OK) { @@ -63,48 +71,82 @@ static void process_media_recorder_call(JNIEnv *env, status_t opStatus, const ch return; } +static void android_media_MediaRecorder_setCamera(JNIEnv* env, jobject thiz, jobject camera) +{ + sp<Camera> c = get_native_camera(env, camera); + MediaRecorder *mr = (MediaRecorder*)env->GetIntField(thiz, fields.context); + 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; + } MediaRecorder *mr = (MediaRecorder *)env->GetIntField(thiz, fields.context); - process_media_recorder_call(env, mr->setVideoSource((video_source)vs), "java/lang/RuntimeException", "setVideoSource failed."); + 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; + } MediaRecorder *mr = (MediaRecorder *)env->GetIntField(thiz, fields.context); - process_media_recorder_call(env, mr->setAudioSource((audio_source)as), "java/lang/RuntimeException", "setAudioSource failed."); + 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; + } MediaRecorder *mr = (MediaRecorder *)env->GetIntField(thiz, fields.context); - process_media_recorder_call(env, mr->setOutputFormat((output_format)of), "java/lang/RuntimeException", "setOutputFormat failed."); + 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; + } MediaRecorder *mr = (MediaRecorder *)env->GetIntField(thiz, fields.context); - process_media_recorder_call(env, mr->setVideoEncoder((video_encoder)ve), "java/lang/RuntimeException", "setVideoEncoder failed."); + 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; + } MediaRecorder *mr = (MediaRecorder *)env->GetIntField(thiz, fields.context); - process_media_recorder_call(env, mr->setAudioEncoder((audio_encoder)ae), "java/lang/RuntimeException", "setAudioEncoder failed."); + process_media_recorder_call(env, mr->setAudioEncoder(ae), "java/lang/RuntimeException", "setAudioEncoder failed."); } static void android_media_MediaRecorder_setOutputFile(JNIEnv *env, jobject thiz, jstring path) { + LOGV("setOutputFile"); MediaRecorder *mr = (MediaRecorder *)env->GetIntField(thiz, fields.context); if (path == NULL) { - jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + jniThrowException(env, "java/lang/IllegalArgumentException", "Path is a NULL pointer"); return; } const char *pathStr = env->GetStringUTFChars(path, NULL); @@ -113,7 +155,7 @@ android_media_MediaRecorder_setOutputFile(JNIEnv *env, jobject thiz, jstring pat return; } status_t opStatus = mr->setOutputFile(pathStr); - + // Make sure that local ref is released before a potential exception env->ReleaseStringUTFChars(path, pathStr); process_media_recorder_call(env, opStatus, "java/lang/RuntimeException", "setOutputFile failed."); @@ -122,13 +164,24 @@ android_media_MediaRecorder_setOutputFile(JNIEnv *env, jobject thiz, jstring pat static void android_media_MediaRecorder_setVideoSize(JNIEnv *env, jobject thiz, jint width, jint height) { + LOGV("setVideoSize(%d, %d)", width, height); MediaRecorder *mr = (MediaRecorder *)env->GetIntField(thiz, fields.context); + + 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; + } MediaRecorder *mr = (MediaRecorder *)env->GetIntField(thiz, fields.context); process_media_recorder_call(env, mr->setVideoFrameRate(rate), "java/lang/RuntimeException", "setVideoFrameRate failed."); } @@ -136,13 +189,13 @@ android_media_MediaRecorder_setVideoFrameRate(JNIEnv *env, jobject thiz, jint ra static void android_media_MediaRecorder_prepare(JNIEnv *env, jobject thiz) { + LOGV("prepare"); MediaRecorder *mr = (MediaRecorder *)env->GetIntField(thiz, fields.context); 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()); + LOGI("prepare: surface=%p (id=%d)", native_surface.get(), native_surface->ID()); process_media_recorder_call(env, mr->setPreviewSurface(native_surface), "java/lang/RuntimeException", "setPreviewSurface failed."); } process_media_recorder_call(env, mr->prepare(), "java/io/IOException", "prepare failed."); @@ -151,6 +204,7 @@ android_media_MediaRecorder_prepare(JNIEnv *env, jobject thiz) static int android_media_MediaRecorder_native_getMaxAmplitude(JNIEnv *env, jobject thiz) { + LOGV("getMaxAmplitude"); MediaRecorder *mr = (MediaRecorder *)env->GetIntField(thiz, fields.context); int result = 0; process_media_recorder_call(env, mr->getMaxAmplitude(&result), "java/lang/RuntimeException", "getMaxAmplitude failed."); @@ -160,6 +214,7 @@ android_media_MediaRecorder_native_getMaxAmplitude(JNIEnv *env, jobject thiz) static void android_media_MediaRecorder_start(JNIEnv *env, jobject thiz) { + LOGV("start"); MediaRecorder *mr = (MediaRecorder *)env->GetIntField(thiz, fields.context); process_media_recorder_call(env, mr->start(), "java/lang/RuntimeException", "start failed."); } @@ -167,6 +222,7 @@ android_media_MediaRecorder_start(JNIEnv *env, jobject thiz) static void android_media_MediaRecorder_stop(JNIEnv *env, jobject thiz) { + LOGV("stop"); MediaRecorder *mr = (MediaRecorder *)env->GetIntField(thiz, fields.context); process_media_recorder_call(env, mr->stop(), "java/lang/RuntimeException", "stop failed."); } @@ -174,6 +230,7 @@ android_media_MediaRecorder_stop(JNIEnv *env, jobject thiz) static void android_media_MediaRecorder_reset(JNIEnv *env, jobject thiz) { + LOGV("reset"); MediaRecorder *mr = (MediaRecorder *)env->GetIntField(thiz, fields.context); process_media_recorder_call(env, mr->reset(), "java/lang/RuntimeException", "reset failed."); } @@ -181,6 +238,7 @@ android_media_MediaRecorder_reset(JNIEnv *env, jobject thiz) static void android_media_MediaRecorder_release(JNIEnv *env, jobject thiz) { + LOGV("release"); MediaRecorder *mr = (MediaRecorder *)env->GetIntField(thiz, fields.context); env->SetIntField(thiz, fields.context, 0); delete mr; @@ -189,32 +247,28 @@ android_media_MediaRecorder_release(JNIEnv *env, jobject thiz) static void android_media_MediaRecorder_native_setup(JNIEnv *env, jobject thiz) { + LOGV("setup"); MediaRecorder *mr = new MediaRecorder(); - if (mr == NULL) { - jniThrowException(env, "java/lang/RuntimeException", "Out of memory"); - return; + if (mr->initCheck() == NO_ERROR) { + env->SetIntField(thiz, fields.context, (int)mr); + } else { + delete mr; + jniThrowException(env, "java/lang/IOException", "Unable to initialize camera"); } - - process_media_recorder_call(env, mr->init(), "java/lang/RuntimeException", "init failed."); - env->SetIntField(thiz, fields.context, (int)mr); } static void android_media_MediaRecorder_native_finalize(JNIEnv *env, jobject thiz) { + LOGV("finalize"); MediaRecorder *mr = (MediaRecorder *)env->GetIntField(thiz, fields.context); - - //printf("##### android_media_MediaRecorder_native_finalize: ctx=0x%p\n", ctx); - - if (mr == 0) - return; - delete mr; } // ---------------------------------------------------------------------------- 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}, diff --git a/media/jni/android_media_MediaScanner.cpp b/media/jni/android_media_MediaScanner.cpp index 76202d3..8764a70 100644 --- a/media/jni/android_media_MediaScanner.cpp +++ b/media/jni/android_media_MediaScanner.cpp @@ -189,6 +189,25 @@ android_media_MediaScanner_processFile(JNIEnv *env, jobject thiz, jstring path, } } +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) { @@ -254,6 +273,7 @@ static JNINativeMethod gMethods[] = { (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}, diff --git a/media/jni/soundpool/SoundPool.cpp b/media/jni/soundpool/SoundPool.cpp index 8ad73d7..7872a8d 100644 --- a/media/jni/soundpool/SoundPool.cpp +++ b/media/jni/soundpool/SoundPool.cpp @@ -15,9 +15,11 @@ */ //#define LOG_NDEBUG 0 -#define LOG_TAG "SoundPoolRestartThread" +#define LOG_TAG "SoundPool" #include <utils/Log.h> +//#define USE_SHARED_MEM_BUFFER + // XXX needed for timing latency #include <utils/Timers.h> @@ -32,40 +34,80 @@ namespace android { int kDefaultBufferCount = 4; -uint32_t kMaxSampleRate = 44100; +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); -// handles restarting channels that have been stolen -class SoundPoolRestartThread + 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]); + } + + if (AudioSystem::getOutputFrameCount(&mFrameCount) != NO_ERROR) { + mFrameCount = kDefaultFrameCount; + } + if (AudioSystem::getOutputSamplingRate(&mSampleRate) != NO_ERROR) { + mSampleRate = kDefaultSampleRate; + } + + // start decode thread + startThreads(); +} + +SoundPool::~SoundPool() { -public: - SoundPoolRestartThread() : mQuit(false) { createThread(beginThread, this); } - void addToRestartList(SoundChannel* channel); - void quit(); + LOGV("SoundPool destructor"); + mDecodeThread->quit(); + quit(); -private: - static int beginThread(void* arg); - int run(); + Mutex::Autolock lock(&mLock); + mChannels.clear(); + if (mChannelPool) + delete [] mChannelPool; - Mutex mLock; - Condition mCondition; - List<SoundChannel*> mRestart; - bool mQuit; -}; + // clean up samples + LOGV("clear samples"); + mSamples.clear(); -void SoundPoolRestartThread::addToRestartList(SoundChannel* channel) + if (mDecodeThread) + delete mDecodeThread; +} + +void SoundPool::addToRestartList(SoundChannel* channel) { Mutex::Autolock lock(&mLock); mRestart.push_back(channel); mCondition.signal(); } -int SoundPoolRestartThread::beginThread(void* arg) +int SoundPool::beginThread(void* arg) { - SoundPoolRestartThread* thread = (SoundPoolRestartThread*)arg; - return thread->run(); + SoundPool* p = (SoundPool*)arg; + return p->run(); } -int SoundPoolRestartThread::run() +int SoundPool::run() { mLock.lock(); while (!mQuit) { @@ -79,21 +121,7 @@ int SoundPoolRestartThread::run() List<SoundChannel*>::iterator iter = mRestart.begin(); channel = *iter; mRestart.erase(iter); - if (channel) { - SoundEvent* next = channel->nextEvent(); - if (next) { - LOGV("Starting stolen channel %d -> %d", channel->channelID(), next->mChannelID); - channel->play(next->mSample, next->mChannelID, next->mLeftVolume, - next->mRightVolume, next->mPriority, next->mLoop, - next->mRate); - } - else { - LOGW("stolen channel has no event"); - } - } - else { - LOGW("no stolen channel to process"); - } + if (channel) channel->nextEvent(); if (mQuit) break; } } @@ -105,64 +133,22 @@ int SoundPoolRestartThread::run() return 0; } -void SoundPoolRestartThread::quit() +void SoundPool::quit() { mLock.lock(); mQuit = true; mCondition.signal(); mCondition.wait(mLock); LOGV("return from quit"); -} - -#undef LOG_TAG -#define LOG_TAG "SoundPool" - -SoundPool::SoundPool(jobject soundPoolRef, int maxChannels, int streamType, int srcQuality) : - mSoundPoolRef(soundPoolRef), mRestartThread(NULL), mDecodeThread(NULL), - mChannelPool(NULL), mMaxChannels(maxChannels), mStreamType(streamType), - mSrcQuality(srcQuality), mAllocated(0), mNextSampleID(0), mNextChannelID(0) -{ - LOGV("SoundPool constructor: maxChannels=%d, streamType=%d, srcQuality=%d", - maxChannels, streamType, srcQuality); - - 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(); - mRestartThread->quit(); - - Mutex::Autolock lock(&mLock); - mChannels.clear(); - if (mChannelPool) - delete [] mChannelPool; - - // clean up samples - LOGV("clear samples"); - mSamples.clear(); - - if (mDecodeThread) - delete mDecodeThread; - if (mRestartThread) - delete mRestartThread; + mLock.unlock(); } bool SoundPool::startThreads() { + createThread(beginThread, this); if (mDecodeThread == NULL) mDecodeThread = new SoundPoolThread(this); - if (mRestartThread == NULL) - mRestartThread = new SoundPoolRestartThread(); - return (mDecodeThread && mRestartThread); + return mDecodeThread != NULL; } SoundChannel* SoundPool::findChannel(int channelID) @@ -178,7 +164,7 @@ SoundChannel* SoundPool::findChannel(int channelID) SoundChannel* SoundPool::findNextChannel(int channelID) { for (int i = 0; i < mMaxChannels; ++i) { - if (mChannelPool[i].nextEvent()->mChannelID == channelID) { + if (mChannelPool[i].nextChannelID() == channelID) { return &mChannelPool[i]; } } @@ -225,44 +211,37 @@ int SoundPool::play(int sampleID, float leftVolume, float rightVolume, { LOGV("sampleID=%d, leftVolume=%f, rightVolume=%f, priority=%d, loop=%d, rate=%f", sampleID, leftVolume, rightVolume, priority, loop, rate); - Mutex::Autolock lock(&mLock); + 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; + } - // is sample ready? - sp<Sample> sample = findSample(sampleID); - if ((sample == 0) || (sample->state() != Sample::READY)) { - LOGW(" sample %d not READY", sampleID); - return 0; - } + dump(); - dump(); + // allocate a channel + channel = allocateChannel(priority); - // allocate a channel - SoundChannel* channel = allocateChannel(priority); + // no channel allocated - return 0 + if (!channel) { + LOGV("No channel allocated"); + return 0; + } - // no channel allocated - return 0 - if (!channel) { - LOGV("No channel allocated"); - return 0; + channelID = ++mNextChannelID; } - int channelID = ++mNextChannelID; LOGV("channel state = %d", channel->state()); - - // idle: start playback - if (channel->state() == SoundChannel::IDLE) { - LOGV("idle channel - starting playback"); - channel->play(sample, channelID, leftVolume, rightVolume, priority, - loop, rate); - } - - // stolen: stop, save event data, and let service thread restart it - else { - LOGV("channel %d stolen - event queued for channel %d", channel->channelID(), channelID); - channel->stop(); - channel->setNextEvent(new SoundEvent(sample, channelID, leftVolume, - rightVolume, priority, loop, rate)); - } - + channel->play(sample, channelID, leftVolume, rightVolume, priority, loop, rate); return channelID; } @@ -295,12 +274,12 @@ SoundChannel* SoundPool::allocateChannel(int priority) } // move a channel from its current position to the front of the list -void SoundPool::moveToFront(List<SoundChannel*>& list, SoundChannel* channel) +void SoundPool::moveToFront(SoundChannel* channel) { - for (List<SoundChannel*>::iterator iter = list.begin(); iter != list.end(); ++iter) { + for (List<SoundChannel*>::iterator iter = mChannels.begin(); iter != mChannels.end(); ++iter) { if (*iter == channel) { - list.erase(iter); - list.push_front(channel); + mChannels.erase(iter); + mChannels.push_front(channel); break; } } @@ -342,7 +321,6 @@ void SoundPool::stop(int channelID) void SoundPool::setVolume(int channelID, float leftVolume, float rightVolume) { - LOGV("setVolume(%d, %f, %f)", channelID, leftVolume, rightVolume); Mutex::Autolock lock(&mLock); SoundChannel* channel = findChannel(channelID); if (channel) { @@ -380,21 +358,21 @@ void SoundPool::setRate(int channelID, float rate) } } +// call with lock held void SoundPool::done(SoundChannel* channel) { LOGV("done(%d)", channel->channelID()); // if "stolen", play next event - SoundEvent* next = channel->nextEvent(); - if (next) { + if (channel->nextChannelID() != 0) { LOGV("add to restart list"); - mRestartThread->addToRestartList(channel); + addToRestartList(channel); } // return to idle state else { LOGV("move to front"); - moveToFront(mChannels, channel); + moveToFront(channel); } } @@ -405,6 +383,7 @@ void SoundPool::dump() } } + Sample::Sample(int sampleID, const char* url) { init(); @@ -447,39 +426,17 @@ Sample::~Sample() delete mUrl; } -// TODO: Remove after debug is complete -#if 0 -static void _dumpBuffer(void* buffer, size_t bufferSize, size_t dumpSize=10, bool zeroCheck=true) -{ - int16_t* p = static_cast<int16_t*>(buffer); - if (zeroCheck) { - for (size_t i = 0; i < bufferSize / 2; i++) { - if (*p != 0) { - goto Dump; - } - } - LOGV("Sample data is all zeroes"); - return; - } - -Dump: - LOGV("Sample Data"); - while (--dumpSize) { - LOGV(" %04x", *p++); - } -} -#endif - void Sample::doLoad() { uint32_t sampleRate; int numChannels; + int format; sp<IMemory> p; LOGV("Start decode"); if (mUrl) { - p = MediaPlayer::decode(mUrl, &sampleRate, &numChannels); + p = MediaPlayer::decode(mUrl, &sampleRate, &numChannels, &format); } else { - p = MediaPlayer::decode(mFd, mOffset, mLength, &sampleRate, &numChannels); + p = MediaPlayer::decode(mFd, mOffset, mLength, &sampleRate, &numChannels, &format); LOGV("close(%d)", mFd); ::close(mFd); mFd = -1; @@ -509,150 +466,195 @@ void Sample::doLoad() mSize = p->size(); mSampleRate = sampleRate; mNumChannels = numChannels; + mFormat = format; mState = READY; } + void SoundChannel::init(SoundPool* soundPool) { mSoundPool = soundPool; } -void SoundChannel::deleteTrack() { - LOGV("delete track"); - delete mAudioTrack; - mAudioTrack = 0; - mState = IDLE; - return; -} - -void SoundChannel::play(const sp<Sample>& sample, int channelID, float leftVolume, +void SoundChannel::play(const sp<Sample>& sample, int nextChannelID, float leftVolume, float rightVolume, int priority, int loop, float rate) { - Mutex::Autolock lock(&mLock); - mSample = sample; - mChannelID = channelID; - mPriority = priority; - mLoop = loop; - doPlay(leftVolume, rightVolume, rate); -} + AudioTrack* oldTrack; -// must call with mutex held -void SoundChannel::doPlay(float leftVolume, float rightVolume, float rate) -{ - LOGV("SoundChannel::doPlay: sampleID=%d, channelID=%d, leftVolume=%f, rightVolume=%f, priority=%d, loop=%d, rate=%f", - mSample->sampleID(), mChannelID, leftVolume, rightVolume, mPriority, mLoop, rate); - deleteTrack(); - mNumChannels = mSample->numChannels(); - clearNextEvent(); - mPos = 0; + LOGV("play: sampleID=%d, channelID=%d, leftVolume=%f, rightVolume=%f, priority=%d, loop=%d, rate=%f", + 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 - uint32_t sampleRate = uint32_t(float(mSample->sampleRate()) * rate + 0.5); - LOGV("play: channelID=%d, sampleRate=%d\n", mChannelID, sampleRate); // create track + int numChannels = sample->numChannels(); + uint32_t sampleRate = uint32_t(float(sample->sampleRate()) * rate + 0.5); + uint32_t bufferFrames = (mSoundPool->mFrameCount * sampleRate) / mSoundPool->mSampleRate; + uint32_t frameCount = 0; - mAudioTrack = new AudioTrack(mSoundPool->streamType(), sampleRate, AudioSystem::PCM_16_BIT, - mSample->numChannels(), kDefaultBufferCount, 0, callback, this); - if (mAudioTrack->initCheck() != NO_ERROR) { + 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; +#ifdef USE_SHARED_MEM_BUFFER + newTrack = new AudioTrack(mSoundPool->streamType(), sampleRate, sample->format(), + numChannels, sample->getIMemory(), 0, callback, this); +#else + newTrack = new AudioTrack(mSoundPool->streamType(), sampleRate, sample->format(), + numChannels, frameCount, 0, callback, this, bufferFrames); +#endif + if (newTrack->initCheck() != NO_ERROR) { LOGE("Error creating AudioTrack"); - deleteTrack(); + delete newTrack; return; } - mLeftVolume = leftVolume; - mRightVolume = rightVolume; - mAudioTrack->setVolume(leftVolume, rightVolume); + LOGV("setVolume %p", newTrack); + newTrack->setVolume(leftVolume, rightVolume); + newTrack->setLoop(0, frameCount, loop); + + { + Mutex::Autolock lock(&mLock); + 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; - // start playback - mState = PLAYING; - LOGV("play: start track"); - mAudioTrack->start(); + // 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(void* user, const AudioTrack::Buffer& b) +void SoundChannel::callback(int event, void* user, void *info) { SoundChannel* channel = static_cast<SoundChannel*>(user); - channel->process(b); + channel->process(event, info); } -void SoundChannel::process(const AudioTrack::Buffer& b) +void SoundChannel::process(int event, void *info) { //LOGV("process(%d)", mChannelID); - bool more = true; sp<Sample> sample = mSample; - // check for stop state - if (sample != 0) { - - // fill buffer - uint8_t* q = (uint8_t*) b.i8; - uint8_t* p = sample->data() + mPos; - size_t count = sample->size() - mPos; - if (count > b.size) { - //LOGV("fill: q=%p, p=%p, mPos=%u, b.size=%u", q, p, mPos, b.size); - memcpy(q, p, b.size); - mPos += b.size; - } + 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; - // not enough samples to fill buffer - else { - //LOGV("partial: q=%p, p=%p, mPos=%u, count=%u", q, p, mPos, count); - memcpy(q, p, count); - size_t left = b.size - count; - q += count; - - // loop sample - while (left && mLoop) { - if (mLoop > 0) { - mLoop--; + if (mPos < (int)sample->size()) { + uint8_t* p = sample->data() + mPos; + count = sample->size() - mPos; + if (count > b->size) { + count = b->size; } - count = left > sample->size() ? sample->size() : left; - //LOGV("loop: q=%p, p=%p, count=%u, mLoop=%d", p, q, count, mLoop); - memcpy(q, sample->data(), count); - q += count; - mPos = count; - left -= count; - - // done filling buffer? - if ((mLoop == 0) && (count == sample->size())) { - more = false; + 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); } - // end of sample: zero-fill and stop track - if (left) { - //LOGV("zero-fill: q=%p, left=%u", q, left); - memset(q, 0, left); - more = false; - } + mPos += count; + b->size = count; + //LOGV("buffer=%p, [0]=%d", b->i16, b->i16[0]); } - - //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); } +} - // clean up - Mutex::Autolock lock(&mLock); - if (!more || (mState == STOPPING) || (mState == PAUSED)) { - LOGV("stopping track"); + +// call with lock held +void SoundChannel::stop_l() +{ + if (mState != IDLE) { + setVolume_l(0, 0); + LOGV("stop"); mAudioTrack->stop(); - if (more && (mState == PAUSED)) { - LOGV("volume to zero"); - mAudioTrack->setVolume(0,0); - } else { - mSample.clear(); - mState = IDLE; - mPriority = IDLE_PRIORITY; - mSoundPool->done(this); - } + mSample.clear(); + mState = IDLE; + mPriority = IDLE_PRIORITY; } } void SoundChannel::stop() { - Mutex::Autolock lock(&mLock); - if (mState != IDLE) { - setVolume(0, 0); - LOGV("stop"); - mState = STOPPING; + { + Mutex::Autolock lock(&mLock); + stop_l(); } + mSoundPool->done(this); } //FIXME: Pause is a little broken right now @@ -662,6 +664,7 @@ void SoundChannel::pause() if (mState == PLAYING) { LOGV("pause track"); mState = PAUSED; + mAudioTrack->pause(); } } @@ -671,25 +674,43 @@ void SoundChannel::resume() if (mState == PAUSED) { LOGV("resume track"); mState = PLAYING; - mAudioTrack->setVolume(mLeftVolume, mRightVolume); mAudioTrack->start(); } } void SoundChannel::setRate(float rate) { - uint32_t sampleRate = uint32_t(float(mSample->sampleRate()) * rate + 0.5); - mAudioTrack->setSampleRate(sampleRate); + 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; + } } -void SoundChannel::setVolume(float leftVolume, float rightVolume) +// call with lock held +void SoundChannel::setVolume_l(float leftVolume, float rightVolume) { - Mutex::Autolock lock(&mLock); 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"); @@ -702,28 +723,23 @@ SoundChannel::~SoundChannel() mSample.clear(); } -// always call with lock held -void SoundChannel::clearNextEvent() -{ - if (mNextEvent) { - mNextEvent->mSample.clear(); - delete mNextEvent; - mNextEvent = NULL; - } -} - -void SoundChannel::setNextEvent(SoundEvent* nextEvent) -{ - Mutex::Autolock lock(&mLock); - clearNextEvent(); - mNextEvent = nextEvent; -} - 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 index ccd724a..d02ae8b 100644 --- a/media/jni/soundpool/SoundPool.h +++ b/media/jni/soundpool/SoundPool.h @@ -32,7 +32,6 @@ static const int IDLE_PRIORITY = -1; // forward declarations class SoundEvent; -class SoundPoolRestartThread; class SoundPoolThread; class SoundPool; @@ -59,15 +58,17 @@ public: 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, size_t size, sp<IMemory> data ) { - mNumChannels = numChannels; mSampleRate = sampleRate; mSize = size; mData = data; } + 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(); @@ -78,6 +79,7 @@ private: uint16_t mSampleRate; uint8_t mState : 3; uint8_t mNumChannels : 2; + uint8_t mFormat : 2; int mFd; int64_t mOffset; int64_t mLength; @@ -89,11 +91,20 @@ private: class SoundEvent { public: - SoundEvent(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) {} + 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; @@ -104,50 +115,42 @@ public: }; // for channels aka AudioTracks -class SoundChannel { +class SoundChannel : public SoundEvent { public: enum state { IDLE, RESUMING, STOPPING, PAUSED, PLAYING }; - SoundChannel() : mAudioTrack(0), mNextEvent(0), mChannelID(0), mState(IDLE), - mNumChannels(1), mPos(0), mPriority(IDLE_PRIORITY), mLoop(0) {} + SoundChannel() : mAudioTrack(0), mState(IDLE), mNumChannels(1), mPos(0) {} ~SoundChannel(); void init(SoundPool* soundPool); - void deleteTrack(); void play(const sp<Sample>& sample, int channelID, float leftVolume, float rightVolume, int priority, int loop, float rate); - void doPlay(float leftVolume, float rightVolume, 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 channelID() { return mChannelID; } int state() { return mState; } - int priority() { return mPriority; } void setPriority(int priority) { mPriority = priority; } - void setLoop(int loop) { mLoop = loop; } + void setLoop(int loop); int numChannels() { return mNumChannels; } - SoundEvent* nextEvent() { return mNextEvent; } - void clearNextEvent(); - void setNextEvent(SoundEvent* nextEvent); + void clearNextEvent() { mNextEvent.clear(); } + void nextEvent(); + int nextChannelID() { return mNextEvent.channelID(); } void dump(); private: - static void callback(void* user, const AudioTrack::Buffer& info); - void process(const AudioTrack::Buffer& b); + static void callback(int event, void* user, void *info); + void process(int event, void *info); SoundPool* mSoundPool; AudioTrack* mAudioTrack; - sp<Sample> mSample; - SoundEvent* mNextEvent; + SoundEvent mNextEvent; Mutex mLock; - int mChannelID; int mState; int mNumChannels; int mPos; - int mPriority; - int mLoop; - float mLeftVolume; - float mRightVolume; + int mAudioBufferSize; }; // application object for managing a pool of sounds @@ -189,15 +192,22 @@ private: SoundChannel* findChannel (int channelID); SoundChannel* findNextChannel (int channelID); SoundChannel* allocateChannel(int priority); - void moveToFront(List<SoundChannel*>& list, SoundChannel* channel); + 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; - SoundPoolRestartThread* mRestartThread; + Condition mCondition; SoundPoolThread* mDecodeThread; SoundChannel* mChannelPool; List<SoundChannel*> mChannels; + List<SoundChannel*> mRestart; DefaultKeyedVector< int, sp<Sample> > mSamples; int mMaxChannels; int mStreamType; @@ -205,6 +215,9 @@ private: int mAllocated; int mNextSampleID; int mNextChannelID; + int mFrameCount; + int mSampleRate; + bool mQuit; }; } // end namespace android |