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
experimental
shadowislord 10 years ago
parent 603432790b
commit 5f0c2035c1
  1. BIN
      jme3-android-native/TremorAndroid.zip
  2. 5
      jme3-android-native/build.gradle
  3. 110
      jme3-android-native/decode.gradle
  4. 39
      jme3-android-native/src/native/jme_decode/Android.mk
  5. 3
      jme3-android-native/src/native/jme_decode/Application.mk
  6. 177
      jme3-android-native/src/native/jme_decode/com_jme3_audio_plugins_NativeVorbisFile.c
  7. 4
      jme3-android-native/src/native/jme_decode/com_jme3_texture_plugins_AndroidNativeImageLoader.c
  8. 16
      jme3-android-native/src/native/jme_stbi/Android.mk
  9. 3
      jme3-android-native/src/native/jme_stbi/Application.mk
  10. 123
      jme3-android-native/stb_image.gradle
  11. 37
      jme3-android/src/main/java/com/jme3/audio/plugins/NativeVorbisFile.java
  12. 76
      jme3-android/src/main/java/com/jme3/audio/plugins/NativeVorbisLoader.java

@ -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')

@ -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)
}
}

@ -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)

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

@ -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);
}

@ -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
{

@ -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)

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

@ -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)
}
}

@ -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();
}

@ -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;
}
}
Loading…
Cancel
Save