Procházet zdrojové kódy

android native improvements
* rename jme_stbi -> jme_decode, which is a new native library that will handle image and audio decoding in native code
* add a special version of tremor designed to run on android
* adjust the build process to handle these changes

shadowislord před 10 roky
rodič
revize
5f0c2035c1

binární
jme3-android-native/TremorAndroid.zip


+ 3 - 2
jme3-android-native/build.gradle

@@ -44,5 +44,6 @@ ext {
 
 // add each native lib build file
 apply from: file('openalsoft.gradle')
-apply from: file('stb_image.gradle')
-
+// apply from: file('stb_image.gradle')
+// apply from: file('tremor.gradle')
+apply from: file('decode.gradle')

+ 110 - 0
jme3-android-native/decode.gradle

@@ -0,0 +1,110 @@
+String tremorZipFile = "TremorAndroid.zip"
+String stbiUrl = 'https://raw.githubusercontent.com/nothings/stb/master/stb_image.h'
+
+// Working directories for the ndk build.
+String decodeBuildDir = "${buildDir}" + File.separator + 'decode'
+String decodeBuildJniDir = decodeBuildDir + File.separator + 'jni'
+String decodeBuildLibsDir = decodeBuildDir + File.separator + 'libs'
+
+// Pre-compiled libs directory
+String decodePreCompiledLibsDir = 'libs' + File.separator + 'decode'
+
+// jME Android Native source files path
+String decodeSourceDir = 'src/native/jme_decode'
+
+task downloadStbImage(type: MyDownload) {
+    sourceUrl = stbiUrl
+    target = file('stb_image.h')
+}
+
+// Copy stb_image.h to the source directory.
+task copyStbiFiles(type: Copy) {
+    def sourceDir = file('stb_image.h')
+    def outputDir = file(decodeSourceDir + File.separator + "STBI")
+    from sourceDir
+    into outputDir
+}
+copyStbiFiles.dependsOn {
+    def stbiFile = file('stb_image.h')
+    if (!stbiFile.exists()) {
+        downloadStbImage
+    }
+}
+
+// Copy libtremor source to the source directory.
+task copyTremorFiles(type: Copy) {
+    def zipFile = file(tremorZipFile)
+    def outputDir = file(decodeSourceDir + File.separator + "Tremor")
+
+    from (zipTree(zipFile)) {
+        include '*.c'
+        include '*.h'
+    }
+    
+    into outputDir
+}
+
+// Generate headers via javah
+task generateJavahHeaders(type: Exec) {
+    executable org.gradle.internal.jvm.Jvm.current().getExecutable('javah')
+    args '-d', decodeSourceDir
+    args '-classpath', project.projectClassPath
+    args "com.jme3.audio.plugins.NativeVorbisFile"
+    args "com.jme3.texture.plugins.AndroidNativeImageLoader"
+}
+
+// Copy jME Android native files to jni directory
+task copySourceToBuild(type: Copy, dependsOn:[copyTremorFiles, copyStbiFiles, generateJavahHeaders]) {
+    def sourceDir = file(decodeSourceDir)
+    def outputDir = file(decodeBuildJniDir)
+
+    from sourceDir
+    into outputDir
+}
+
+task buildNativeLib(type: Exec, dependsOn: copySourceToBuild) {
+    workingDir decodeBuildDir
+    executable rootProject.ndkCommandPath
+    args '-j8'
+}
+
+task updatePreCompiledLibs(type: Copy, dependsOn: buildNativeLib) {
+    def sourceDir = new File(decodeBuildLibsDir)
+    def outputDir = new File(decodePreCompiledLibsDir)
+
+    from sourceDir
+    into outputDir
+}
+
+// Copy pre-compiled libs to build directory (when not building new libs)
+task copyPreCompiledLibs(type: Copy) {
+    def sourceDir = file(decodePreCompiledLibsDir)
+    def outputDir = file(decodeBuildLibsDir)
+
+    from sourceDir
+    into outputDir
+}
+
+if (rootProject.ndkExists) {
+    // build native libs and update stored pre-compiled libs to commit
+    compileJava.dependsOn { updatePreCompiledLibs }
+} else {
+    // use pre-compiled native libs (not building new ones)
+    compileJava.dependsOn { copyPreCompiledLibs }
+}
+
+jar.into("lib") { from decodeBuildLibsDir }
+
+// Helper class to wrap ant dowload task
+class MyDownload extends DefaultTask {
+    @Input
+    String sourceUrl
+
+    @OutputFile
+    File target
+
+    @TaskAction
+    void download() {
+       ant.get(src: sourceUrl, dest: target)
+    }
+}

+ 39 - 0
jme3-android-native/src/native/jme_decode/Android.mk

@@ -0,0 +1,39 @@
+TARGET_PLATFORM := android-9
+
+LOCAL_PATH := $(call my-dir)
+	
+include $(CLEAR_VARS)
+
+LOCAL_MODULE    := decodejme
+
+LOCAL_C_INCLUDES:= \
+		$(LOCAL_PATH) \
+		$(LOCAL_PATH)/Tremor
+
+LOCAL_CFLAGS := -std=gnu99
+LOCAL_LDLIBS := -lz -llog -Wl,-s
+	
+ifeq ($(TARGET_ARCH),arm)
+LOCAL_CFLAGS+= -D_ARM_ASSEM_
+endif
+
+LOCAL_ARM_MODE := arm
+	
+LOCAL_SRC_FILES := \
+		Tremor/bitwise.c \
+		Tremor/codebook.c \
+		Tremor/dsp.c \
+		Tremor/floor0.c \
+		Tremor/floor1.c \
+		Tremor/floor_lookup.c \
+		Tremor/framing.c \
+		Tremor/info.c \
+		Tremor/mapping0.c \
+		Tremor/mdct.c \
+		Tremor/misc.c \
+		Tremor/res012.c \
+		Tremor/vorbisfile.c \
+		com_jme3_audio_plugins_NativeVorbisFile.c \
+		com_jme3_texture_plugins_AndroidNativeImageLoader.c
+
+include $(BUILD_SHARED_LIBRARY)

+ 3 - 0
jme3-android-native/src/native/jme_decode/Application.mk

@@ -0,0 +1,3 @@
+APP_PLATFORM := android-9
+APP_OPTIM := debug
+APP_ABI := all

+ 177 - 0
jme3-android-native/src/native/jme_decode/com_jme3_audio_plugins_NativeVorbisFile.c

@@ -0,0 +1,177 @@
+#include <unistd.h>
+#include <stdlib.h>
+#include "Tremor/ivorbisfile.h"
+
+#include "com_jme3_audio_plugins_NativeVorbisFile.h"
+
+#ifndef NDEBUG
+#include <android/log.h>
+#include <stdio.h>
+#define LOGI(fmt, ...) __android_log_print(ANDROID_LOG_INFO, \
+                       "NativeVorbisFile", fmt, ##__VA_ARGS__);
+#else
+#error We are building in release mode, arent we?
+#define LOGI(fmt, ...)
+#endif
+
+typedef struct
+{
+    JNIEnv* env;
+    int fd;
+}
+FileDescWrapper;
+
+static size_t FileDesc_read(void *ptr, size_t size, size_t nmemb, void *datasource)
+{
+    FileDescWrapper* wrapper = (FileDescWrapper*)datasource;
+    size_t totalRead = read(wrapper->fd, ptr, size * nmemb);
+    
+    LOGI("read(%zu) = %zu", size * nmemb, totalRead);
+    
+    return totalRead;
+}
+
+// off64_t lseek64(int fd, off64_t offset, int whence); 
+static int FileDesc_seek(void *datasource, ogg_int64_t offset, int whence)
+{
+    FileDescWrapper* wrapper = (FileDescWrapper*)datasource;
+    int result = lseek64(wrapper->fd, offset, whence);
+    
+    char* whenceStr;
+    switch (whence) {
+        case SEEK_CUR: whenceStr = "SEEK_CUR"; break;
+        case SEEK_END: whenceStr = "SEEK_END"; break;
+        case SEEK_SET: whenceStr = "SEEK_SET"; break;
+        default: whenceStr = "unknown"; break;
+    }
+    LOGI("seek(%lld, %s) = %d", offset, whenceStr, result);
+}
+
+static int FileDesc_close(void *datasource)
+{
+    FileDescWrapper* wrapper = (FileDescWrapper*)datasource;
+    
+    LOGI("close");
+    
+    return close(wrapper->fd);
+}
+
+static long FileDesc_tell(void *datasource)
+{
+    FileDescWrapper* wrapper = (FileDescWrapper*)datasource;
+    long result = lseek64(wrapper->fd, 0, SEEK_CUR);
+    
+    LOGI("tell = %ld", result);
+    
+    return result;
+}
+
+static ov_callbacks FileDescCallbacks = {
+    FileDesc_read,
+    FileDesc_seek,
+    FileDesc_close,
+    FileDesc_tell
+};
+
+static void throwIOException(JNIEnv* env, const char* message)
+{
+    jclass ioExClazz = (*env)->FindClass(env, "java/io/IOException");
+    (*env)->ThrowNew(env, ioExClazz, message);
+}
+
+static jfieldID nvf_field_ovf;
+static jfieldID nvf_field_seekable;
+static jfieldID nvf_field_channels;
+static jfieldID nvf_field_sampleRate;
+static jfieldID nvf_field_bitRate;
+static jfieldID nvf_field_totalBytes;
+static jfieldID nvf_field_duration;
+
+JNIEXPORT void JNICALL Java_com_jme3_audio_plugins_NativeVorbisFile_nativeInit
+  (JNIEnv *env, jclass clazz)
+{
+    LOGI("nativeInit");
+    
+    nvf_field_ovf = (*env)->GetFieldID(env, clazz, "ovf", "Ljava/nio/ByteBuffer;");;
+    nvf_field_seekable = (*env)->GetFieldID(env, clazz, "seekable", "Z");
+    nvf_field_channels = (*env)->GetFieldID(env, clazz, "channels", "I");
+    nvf_field_sampleRate = (*env)->GetFieldID(env, clazz, "sampleRate", "I");
+    nvf_field_bitRate = (*env)->GetFieldID(env, clazz, "bitRate", "I");
+    nvf_field_totalBytes = (*env)->GetFieldID(env, clazz, "totalBytes", "I");
+    nvf_field_duration = (*env)->GetFieldID(env, clazz, "duration", "F");
+}
+
+JNIEXPORT void JNICALL Java_com_jme3_audio_plugins_NativeVorbisFile_open
+  (JNIEnv *env, jobject nvf, jint fd)
+{
+    LOGI("open: %d", fd)
+            
+    OggVorbis_File* ovf = (OggVorbis_File*) malloc(sizeof(OggVorbis_File));
+    
+    FileDescWrapper* wrapper = (FileDescWrapper*) malloc(sizeof(FileDescWrapper));
+    wrapper->fd = fd;
+    wrapper->env = env; // NOTE: every java call has to update this
+    
+    int result = ov_open_callbacks((void*)wrapper, ovf, NULL, 0, FileDescCallbacks);
+    
+    if (result != 0)
+    {
+        LOGI("ov_open fail");
+        
+        free(ovf);
+        free(wrapper);
+    
+        char err[512];
+        sprintf(err, "ov_open failed: %d", result);
+        throwIOException(env, err);
+        
+        return;
+    }
+    
+    LOGI("ov_open OK");
+    jobject ovfBuf = (*env)->NewDirectByteBuffer(env, ovf, sizeof(OggVorbis_File));
+    
+    vorbis_info* info = ov_info(ovf, -1);
+    jint total_bytes = ov_pcm_total(ovf, -1);
+    jboolean seekable = ov_seekable(ovf) != 0;
+    jfloat duration = (jfloat) ov_time_total(ovf, -1);
+    
+    (*env)->SetObjectField(env, nvf, nvf_field_ovf, ovfBuf);
+    (*env)->SetBooleanField(env, nvf, nvf_field_seekable, seekable);
+    (*env)->SetIntField(env, nvf, nvf_field_channels, info->channels);
+    (*env)->SetIntField(env, nvf, nvf_field_sampleRate, info->rate);
+    (*env)->SetIntField(env, nvf, nvf_field_bitRate, info->bitrate_nominal);
+    (*env)->SetIntField(env, nvf, nvf_field_totalBytes, total_bytes);
+    (*env)->SetFloatField(env, nvf, nvf_field_duration, duration);
+}
+
+JNIEXPORT void JNICALL Java_com_jme3_audio_plugins_NativeVorbisFile_seekTime
+  (JNIEnv *env, jobject nvf, jdouble time)
+{
+    
+}
+
+JNIEXPORT jint JNICALL Java_com_jme3_audio_plugins_NativeVorbisFile_read
+  (JNIEnv *env, jobject nvf, jbyteArray buf, jint off, jint len)
+{
+    return 0;
+}
+
+JNIEXPORT void JNICALL Java_com_jme3_audio_plugins_NativeVorbisFile_readFully
+  (JNIEnv *env, jobject nvf, jobject buf)
+{
+    
+}
+
+JNIEXPORT void JNICALL Java_com_jme3_audio_plugins_NativeVorbisFile_close
+  (JNIEnv *env, jobject nvf)
+{
+    LOGI("close");
+    
+    jobject ovfBuf = (*env)->GetObjectField(env, nvf, nvf_field_ovf);
+    OggVorbis_File* ovf = (OggVorbis_File*) (*env)->GetDirectBufferAddress(env, ovfBuf);
+    FileDescWrapper* wrapper = (FileDescWrapper*) ovf->datasource;
+    free(wrapper);
+    free(ovf);
+    (*env)->SetObjectField(env, nvf, nvf_field_ovf, NULL);
+}

+ 2 - 2
jme3-android-native/src/native/jme_stbi/com_jme3_texture_plugins_AndroidNativeImageLoader.c → jme3-android-native/src/native/jme_decode/com_jme3_texture_plugins_AndroidNativeImageLoader.c

@@ -1,7 +1,7 @@
 #include "com_jme3_texture_plugins_AndroidNativeImageLoader.h"
 #include <assert.h>
 
-#ifdef DEBUG
+#ifndef NDEBUG
 #include <android/log.h>
 #define LOGI(fmt, ...) __android_log_print(ANDROID_LOG_INFO, \
                        "NativeImageLoader", fmt, ##__VA_ARGS__);
@@ -12,7 +12,7 @@
 #define STB_IMAGE_IMPLEMENTATION
 #define STBI_NO_STDIO
 #define STBI_NO_HDR
-#include "stb_image.h"
+#include "STBI/stb_image.h"
 
 typedef struct 
 {

+ 0 - 16
jme3-android-native/src/native/jme_stbi/Android.mk

@@ -1,16 +0,0 @@
-TARGET_PLATFORM := android-9
-
-LOCAL_PATH := $(call my-dir)
-	
-include $(CLEAR_VARS)
-
-LOCAL_MODULE    := stbijme
-	
-LOCAL_C_INCLUDES  += $(LOCAL_PATH)
-
-LOCAL_CFLAGS := -std=c99
-LOCAL_LDLIBS := -lz -llog -Wl,-s
-	
-LOCAL_SRC_FILES := com_jme3_texture_plugins_AndroidNativeImageLoader.c
-
-include $(BUILD_SHARED_LIBRARY)

+ 0 - 3
jme3-android-native/src/native/jme_stbi/Application.mk

@@ -1,3 +0,0 @@
-APP_PLATFORM := android-9
-APP_OPTIM := release
-APP_ABI := all

+ 0 - 123
jme3-android-native/stb_image.gradle

@@ -1,123 +0,0 @@
-// stb_image url for download
-String stbiUrl = 'https://raw.githubusercontent.com/nothings/stb/master/stb_image.h'
-String stbiDownloadTarget = 'stb_image.h'
-
-// stb_image is not downloaded.  The single source file is included in the repo
-String stbiFolder = 'stb_image'
-
-//Working directories for the ndk build.
-String stbiBuildDir = "${buildDir}" + File.separator + 'stb_image'
-String stbiBuildJniDir = stbiBuildDir + File.separator + 'jni'
-String stbiBuildLibsDir = stbiBuildDir + File.separator + 'libs'
-
-//Pre-compiled libs directory
-String stbiPreCompiledLibsDir = 'libs' + File.separator + 'stb_image'
-
-// jME Android Native source files path
-String stbiJmeAndroidPath = 'src/native/jme_stbi'
-
-// Download external source files if not available
-task downloadStbImage(type: MyDownload) {
-    sourceUrl = stbiUrl
-    target = file(stbiFolder + File.separator + stbiDownloadTarget)
-}
-
-// Copy stb_image.h to the source directory.
-task copyStbiFiles(type: Copy) {
-    def sourceDir = file(stbiFolder)
-    def outputDir = file(stbiJmeAndroidPath)
-//    println "copyStbiFiles sourceDir: " + sourceDir
-//    println "copyStbiFiles outputDir: " + outputDir
-
-    from sourceDir
-    into outputDir
-}
-copyStbiFiles.dependsOn {
-    def stbiFilePath = project.projectDir.absolutePath + stbiFolder + File.separator + stbiDownloadTarget
-    def stbiFile = new File(stbiFilePath)
-//    println "zipFile path: " + zipFile.absolutePath
-//    println "zipFile exists: " + zipFile.exists()
-    if (!stbiFile.exists()) {
-        downloadStbImage
-    }
-}
-
-task generateStbiHeaders(type: Exec) {
-    String destDirPath = stbiJmeAndroidPath
-    String classes = ""
-            .concat("com.jme3.texture.plugins.AndroidNativeImageLoader, ")
-
-//    println "stb_image classes = " + classes
-//    println "stb_image destDir = " + destDir
-//    println "stb_image classpath = " + project.projectClassPath
-
-    executable org.gradle.internal.jvm.Jvm.current().getExecutable('javah')
-    args '-d', destDirPath
-    args '-classpath', project.projectClassPath
-    args "com.jme3.texture.plugins.AndroidNativeImageLoader"
-}
-
-// Copy jME Android native files to jni directory
-task copyStbiJmeFiles(type: Copy, dependsOn:[copyStbiFiles, generateStbiHeaders]) {
-    def sourceDir = file(stbiJmeAndroidPath)
-    def outputDir = file(stbiBuildJniDir)
-//    println "copyStbiJmeFiles sourceDir: " + sourceDir
-//    println "copyStbiJmeFiles outputDir: " + outputDir
-
-    from sourceDir
-    into outputDir
-}
-
-task buildStbiNativeLib(type: Exec, dependsOn: copyStbiJmeFiles) {
-//    println "stb_image build dir: " + stbiBuildDir
-//    println "ndkCommandPath: " + rootProject.ndkCommandPath
-    
-    workingDir stbiBuildDir
-    executable rootProject.ndkCommandPath
-    args '-j8'
-}
-
-task updatePreCompiledStbiLibs(type: Copy, dependsOn: buildStbiNativeLib) {
-    def sourceDir = new File(stbiBuildLibsDir)
-    def outputDir = new File(stbiPreCompiledLibsDir)
-//    println "updatePreCompiledStbiLibs sourceDir: " + sourceDir
-//    println "updatePreCompiledStbiLibs outputDir: " + outputDir
-
-    from sourceDir
-    into outputDir
-}
-
-// Copy pre-compiled libs to build directory (when not building new libs)
-task copyPreCompiledStbiLibs(type: Copy) {
-    def sourceDir = file(stbiPreCompiledLibsDir)
-    def outputDir = file(stbiBuildLibsDir)
-//    println "copyStbiJmeFiles sourceDir: " + sourceDir
-//    println "copyStbiJmeFiles outputDir: " + outputDir
-
-    from sourceDir
-    into outputDir
-}
-
-if (rootProject.ndkExists) {
-    // build native libs and update stored pre-compiled libs to commit
-    compileJava.dependsOn { updatePreCompiledStbiLibs }
-} else {
-    // use pre-compiled native libs (not building new ones)
-    compileJava.dependsOn { copyPreCompiledStbiLibs }
-}
-
-jar.into("lib") { from stbiBuildLibsDir }
-
-// Helper class to wrap ant dowload task
-class MyDownload extends DefaultTask {
-    @Input
-    String sourceUrl
-
-    @OutputFile
-    File target
-
-    @TaskAction
-    void download() {
-       ant.get(src: sourceUrl, dest: target)
-    }
-}

+ 37 - 0
jme3-android/src/main/java/com/jme3/audio/plugins/NativeVorbisFile.java

@@ -0,0 +1,37 @@
+package com.jme3.audio.plugins;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+public class NativeVorbisFile {
+    
+    public int fd;
+    public ByteBuffer ovf;
+    public boolean seekable;
+    public int channels;
+    public int sampleRate;
+    public int bitRate;
+    public int totalBytes;
+    public float duration;
+    
+    static {
+        System.loadLibrary("decodejme");
+        nativeInit();
+    }
+    
+    public NativeVorbisFile(int fd) throws IOException {
+        open(fd);
+    }
+    
+    private native void open(int fd) throws IOException;
+    
+    public native void seekTime(double time) throws IOException;
+    
+    public native int read(byte[] buf, int off, int len) throws IOException;
+    
+    public native void readFully(ByteBuffer out) throws IOException;
+    
+    public native void close();
+    
+    public static native void nativeInit();
+}

+ 76 - 0
jme3-android/src/main/java/com/jme3/audio/plugins/NativeVorbisLoader.java

@@ -0,0 +1,76 @@
+package com.jme3.audio.plugins;
+
+import android.content.res.AssetFileDescriptor;
+import com.jme3.asset.AssetInfo;
+import com.jme3.asset.AssetLoader;
+import com.jme3.asset.plugins.AndroidLocator;
+import com.jme3.asset.plugins.AndroidLocator.AndroidAssetInfo;
+import com.jme3.audio.AudioBuffer;
+import com.jme3.audio.AudioKey;
+import com.jme3.audio.SeekableStream;
+import com.jme3.util.BufferUtils;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+
+public class NativeVorbisLoader implements AssetLoader {
+    
+    private static class VorbisInputStream extends InputStream implements SeekableStream {
+
+        private final NativeVorbisFile file;
+        
+        public VorbisInputStream(NativeVorbisFile file) {
+            this.file = file;
+        }
+        
+        @Override
+        public int read() throws IOException {
+            return 0;
+        }
+        
+        @Override
+        public int read(byte[] buf) throws IOException {
+            return file.read(buf, 0, buf.length);
+        }
+        
+        @Override
+        public int read(byte[] buf, int off, int len) throws IOException {
+            return file.read(buf, off, len);
+        }
+        
+        @Override
+        public long skip(long n) throws IOException {
+            throw new IOException("Not supported for audio streams");
+        }
+
+        public void setTime(float time) {
+            try {
+                file.seekTime(time);
+            } catch (IOException ex) {
+                throw new RuntimeException(ex);
+            }
+        }
+    }
+    
+    @Override
+    public Object load(AssetInfo assetInfo) throws IOException {
+        AudioKey key = (AudioKey) assetInfo.getKey();
+        if (!(assetInfo instanceof AndroidLocator.AndroidAssetInfo)) {
+            throw new UnsupportedOperationException("Cannot load audio files from classpath." + 
+                                                    "Place your audio files in " +
+                                                    "Android's assets directory");
+        }
+        
+        AndroidAssetInfo aai = (AndroidAssetInfo) assetInfo;
+        AssetFileDescriptor afd = aai.openFileDescriptor();
+        int fd = afd.getParcelFileDescriptor().getFd();
+        
+        NativeVorbisFile file = new NativeVorbisFile(fd);
+        ByteBuffer data = BufferUtils.createByteBuffer(file.totalBytes);
+        file.readFully(data);
+        AudioBuffer ab = new AudioBuffer();
+        ab.setupFormat(file.channels, 16, file.sampleRate);
+        ab.updateData(data);
+        return ab;
+    }
+}