diff --git a/jme3-android-native/TremorAndroid.zip b/jme3-android-native/TremorAndroid.zip new file mode 100644 index 000000000..85f4e734b Binary files /dev/null and b/jme3-android-native/TremorAndroid.zip differ diff --git a/jme3-android-native/build.gradle b/jme3-android-native/build.gradle index 380783bcd..0ca99df77 100644 --- a/jme3-android-native/build.gradle +++ b/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') \ No newline at end of file diff --git a/jme3-android-native/decode.gradle b/jme3-android-native/decode.gradle new file mode 100644 index 000000000..e3e84555a --- /dev/null +++ b/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) + } +} diff --git a/jme3-android-native/src/native/jme_decode/Android.mk b/jme3-android-native/src/native/jme_decode/Android.mk new file mode 100644 index 000000000..04286bcfd --- /dev/null +++ b/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) diff --git a/jme3-android-native/src/native/jme_decode/Application.mk b/jme3-android-native/src/native/jme_decode/Application.mk new file mode 100644 index 000000000..448e7bc90 --- /dev/null +++ b/jme3-android-native/src/native/jme_decode/Application.mk @@ -0,0 +1,3 @@ +APP_PLATFORM := android-9 +APP_OPTIM := debug +APP_ABI := all \ No newline at end of file diff --git a/jme3-android-native/src/native/jme_decode/com_jme3_audio_plugins_NativeVorbisFile.c b/jme3-android-native/src/native/jme_decode/com_jme3_audio_plugins_NativeVorbisFile.c new file mode 100644 index 000000000..958ac2b69 --- /dev/null +++ b/jme3-android-native/src/native/jme_decode/com_jme3_audio_plugins_NativeVorbisFile.c @@ -0,0 +1,177 @@ +#include +#include +#include "Tremor/ivorbisfile.h" + +#include "com_jme3_audio_plugins_NativeVorbisFile.h" + +#ifndef NDEBUG +#include +#include +#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); +} \ No newline at end of file diff --git a/jme3-android-native/src/native/jme_stbi/com_jme3_texture_plugins_AndroidNativeImageLoader.c b/jme3-android-native/src/native/jme_decode/com_jme3_texture_plugins_AndroidNativeImageLoader.c similarity index 96% rename from jme3-android-native/src/native/jme_stbi/com_jme3_texture_plugins_AndroidNativeImageLoader.c rename to jme3-android-native/src/native/jme_decode/com_jme3_texture_plugins_AndroidNativeImageLoader.c index 6c7697880..b03dd8e7d 100644 --- a/jme3-android-native/src/native/jme_stbi/com_jme3_texture_plugins_AndroidNativeImageLoader.c +++ b/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 -#ifdef DEBUG +#ifndef NDEBUG #include #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 { diff --git a/jme3-android-native/src/native/jme_stbi/Android.mk b/jme3-android-native/src/native/jme_stbi/Android.mk deleted file mode 100644 index 1eddbaeee..000000000 --- a/jme3-android-native/src/native/jme_stbi/Android.mk +++ /dev/null @@ -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) diff --git a/jme3-android-native/src/native/jme_stbi/Application.mk b/jme3-android-native/src/native/jme_stbi/Application.mk deleted file mode 100644 index fbc028f84..000000000 --- a/jme3-android-native/src/native/jme_stbi/Application.mk +++ /dev/null @@ -1,3 +0,0 @@ -APP_PLATFORM := android-9 -APP_OPTIM := release -APP_ABI := all \ No newline at end of file diff --git a/jme3-android-native/stb_image.gradle b/jme3-android-native/stb_image.gradle deleted file mode 100644 index c8ef98b62..000000000 --- a/jme3-android-native/stb_image.gradle +++ /dev/null @@ -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) - } -} diff --git a/jme3-android/src/main/java/com/jme3/audio/plugins/NativeVorbisFile.java b/jme3-android/src/main/java/com/jme3/audio/plugins/NativeVorbisFile.java new file mode 100644 index 000000000..784781917 --- /dev/null +++ b/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(); +} diff --git a/jme3-android/src/main/java/com/jme3/audio/plugins/NativeVorbisLoader.java b/jme3-android/src/main/java/com/jme3/audio/plugins/NativeVorbisLoader.java new file mode 100644 index 000000000..f0218a41f --- /dev/null +++ b/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; + } +} \ No newline at end of file