Let apps provide a custom data source for extractors
Adds android.media.DataSource, which is modeled after its native namesake,
and a new method on MediaExtractor that lets apps specify their implementation
of a DataSource as the source of data for the extractor.
Change-Id: If1b169bd18d2691ebc4f8996494dfc8ee0894b6c
diff --git a/media/jni/android_media_MediaExtractor.cpp b/media/jni/android_media_MediaExtractor.cpp
index 351ff04..23949fa 100644
--- a/media/jni/android_media_MediaExtractor.cpp
+++ b/media/jni/android_media_MediaExtractor.cpp
@@ -44,6 +44,72 @@
static fields_t gFields;
+class JavaDataSourceBridge : public DataSource {
+ jmethodID mReadMethod;
+ jmethodID mGetSizeMethod;
+ jmethodID mCloseMethod;
+ jobject mDataSource;
+ public:
+ JavaDataSourceBridge(JNIEnv *env, jobject source) {
+ mDataSource = env->NewGlobalRef(source);
+
+ jclass datasourceclass = env->GetObjectClass(mDataSource);
+ CHECK(datasourceclass != NULL);
+
+ mReadMethod = env->GetMethodID(datasourceclass, "readAt", "(J[BI)I");
+ CHECK(mReadMethod != NULL);
+
+ mGetSizeMethod = env->GetMethodID(datasourceclass, "getSize", "()J");
+ CHECK(mGetSizeMethod != NULL);
+
+ mCloseMethod = env->GetMethodID(datasourceclass, "close", "()V");
+ CHECK(mCloseMethod != NULL);
+ }
+
+ ~JavaDataSourceBridge() {
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+ env->CallVoidMethod(mDataSource, mCloseMethod);
+ env->DeleteGlobalRef(mDataSource);
+ }
+
+ virtual status_t initCheck() const {
+ return OK;
+ }
+
+ virtual ssize_t readAt(off64_t offset, void* buffer, size_t size) {
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+
+ // XXX could optimize this by reusing the same array
+ jbyteArray byteArrayObj = env->NewByteArray(size);
+ env->DeleteLocalRef(env->GetObjectClass(mDataSource));
+ env->DeleteLocalRef(env->GetObjectClass(byteArrayObj));
+ ssize_t numread = env->CallIntMethod(mDataSource, mReadMethod, offset, byteArrayObj, size);
+ env->GetByteArrayRegion(byteArrayObj, 0, size, (jbyte*) buffer);
+ env->DeleteLocalRef(byteArrayObj);
+ if (env->ExceptionCheck()) {
+ ALOGW("Exception occurred while reading %d at %lld", size, offset);
+ LOGW_EX(env);
+ env->ExceptionClear();
+ return -1;
+ }
+ return numread;
+ }
+
+ virtual status_t getSize(off64_t *size) {
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+
+ CHECK(size != NULL);
+
+ int64_t len = env->CallLongMethod(mDataSource, mGetSizeMethod);
+ if (len < 0) {
+ *size = ERROR_UNSUPPORTED;
+ } else {
+ *size = len;
+ }
+ return OK;
+ }
+};
+
////////////////////////////////////////////////////////////////////////////////
JMediaExtractor::JMediaExtractor(JNIEnv *env, jobject thiz)
@@ -76,6 +142,10 @@
return mImpl->setDataSource(fd, offset, size);
}
+status_t JMediaExtractor::setDataSource(const sp<DataSource> &datasource) {
+ return mImpl->setDataSource(datasource);
+}
+
size_t JMediaExtractor::countTracks() const {
return mImpl->countTracks();
}
@@ -625,6 +695,33 @@
}
}
+static void android_media_MediaExtractor_setDataSourceCallback(
+ JNIEnv *env, jobject thiz,
+ jobject callbackObj) {
+ sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
+
+ if (extractor == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return;
+ }
+
+ if (callbackObj == NULL) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ return;
+ }
+
+ sp<JavaDataSourceBridge> bridge = new JavaDataSourceBridge(env, callbackObj);
+ status_t err = extractor->setDataSource(bridge);
+
+ if (err != OK) {
+ jniThrowException(
+ env,
+ "java/io/IOException",
+ "Failed to instantiate extractor.");
+ return;
+ }
+}
+
static jlong android_media_MediaExtractor_getCachedDurationUs(
JNIEnv *env, jobject thiz) {
sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
@@ -713,6 +810,9 @@
{ "setDataSource", "(Ljava/io/FileDescriptor;JJ)V",
(void *)android_media_MediaExtractor_setDataSourceFd },
+ { "setDataSource", "(Landroid/media/DataSource;)V",
+ (void *)android_media_MediaExtractor_setDataSourceCallback },
+
{ "getCachedDuration", "()J",
(void *)android_media_MediaExtractor_getCachedDurationUs },