From bc8002fd590628a6d1f2466526de560586601c5d Mon Sep 17 00:00:00 2001 From: David Madl Date: Tue, 3 Mar 2026 12:04:17 +0100 Subject: [PATCH] feat: audio resources --- app/src/main/cpp/PlaybackEngine.cpp | 2 +- app/src/main/cpp/PlaybackEngine.h | 4 +- app/src/main/cpp/jni_bridge.cpp | 3 + app/src/main/cpp/lockstep.cpp | 15 +++-- .../java/at/lockstep/pb/AudioResources.java | 60 ++++++++++++++++++ .../java/at/lockstep/pb/PlaybackEngine.java | 34 +++++++++- app/src/main/res/raw/track_beat.mp3 | Bin 0 -> 5736 bytes 7 files changed, 109 insertions(+), 9 deletions(-) create mode 100644 app/src/main/java/at/lockstep/pb/AudioResources.java create mode 100644 app/src/main/res/raw/track_beat.mp3 diff --git a/app/src/main/cpp/PlaybackEngine.cpp b/app/src/main/cpp/PlaybackEngine.cpp index 7cd48b3..4b6dcb1 100644 --- a/app/src/main/cpp/PlaybackEngine.cpp +++ b/app/src/main/cpp/PlaybackEngine.cpp @@ -7,7 +7,7 @@ #include "PlaybackEngine.h" #include "logging.h" -PlaybackEngine::PlaybackEngine() { +PlaybackEngine::PlaybackEngine(std::string filesDir): mFilesDir(filesDir) { LOGI("PlaybackEngine()"); LOGI("NDK LOG_LEVEL=%d", LOG_LEVEL); // NDK LOG_LEVEL=3 (DEBUG) diff --git a/app/src/main/cpp/PlaybackEngine.h b/app/src/main/cpp/PlaybackEngine.h index 53286c6..73b0883 100644 --- a/app/src/main/cpp/PlaybackEngine.h +++ b/app/src/main/cpp/PlaybackEngine.h @@ -6,13 +6,15 @@ #define LOCKSTEP_PLAYBACKENGINE_H #include "OboeSinePlayer.h" +#include class PlaybackEngine { public: - PlaybackEngine(); + PlaybackEngine(std::string filesDir); virtual ~PlaybackEngine(); private: OboeSinePlayer *mPlayer; + std::string mFilesDir; }; #endif //LOCKSTEP_PLAYBACKENGINE_H diff --git a/app/src/main/cpp/jni_bridge.cpp b/app/src/main/cpp/jni_bridge.cpp index 8462477..0c56e22 100644 --- a/app/src/main/cpp/jni_bridge.cpp +++ b/app/src/main/cpp/jni_bridge.cpp @@ -4,9 +4,12 @@ #include #include "mpg123.h" +#include extern "C" { +// nice-to: merge with lockstep.cpp + JNIEXPORT jint JNICALL Java_at_lockstep_pb_PlaybackEngine_native_1mpg123_1init (JNIEnv *env, jclass) { diff --git a/app/src/main/cpp/lockstep.cpp b/app/src/main/cpp/lockstep.cpp index 5145ea3..504b95b 100644 --- a/app/src/main/cpp/lockstep.cpp +++ b/app/src/main/cpp/lockstep.cpp @@ -30,15 +30,13 @@ extern "C" { JNIEXPORT jlong JNICALL Java_at_lockstep_pb_PlaybackEngine_native_1createEngine( JNIEnv *env, - jclass /*unused*/) { - /* + jclass /*unused*/, jstring filesDir) { const char* filesDirTemp = env->GetStringUTFChars(filesDir, NULL); std::string filesDirString(filesDirTemp); env->ReleaseStringUTFChars(filesDir, filesDirTemp); - */ // We use std::nothrow so `new` returns a nullptr if the engine creation fails - auto *engine = new(std::nothrow) PlaybackEngine(); + auto *engine = new(std::nothrow) PlaybackEngine(filesDirString); return reinterpret_cast(engine); } @@ -51,4 +49,13 @@ jlong engineHandle) { delete reinterpret_cast(engineHandle); } +JNIEXPORT void JNICALL +Java_at_lockstep_pb_PlaybackEngine_native_1setDefaultStreamValues(JNIEnv *env, + jclass type, + jint sampleRate, + jint framesPerBurst) { + oboe::DefaultStreamValues::SampleRate = (int32_t) sampleRate; + oboe::DefaultStreamValues::FramesPerBurst = (int32_t) framesPerBurst; +} + } // extern "C" diff --git a/app/src/main/java/at/lockstep/pb/AudioResources.java b/app/src/main/java/at/lockstep/pb/AudioResources.java new file mode 100644 index 0000000..94eaed1 --- /dev/null +++ b/app/src/main/java/at/lockstep/pb/AudioResources.java @@ -0,0 +1,60 @@ +package at.lockstep.pb; + +import android.content.Context; +import android.os.SystemClock; +import android.util.Log; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Field; + +import at.lockstep.R; + +/** + * Resource helpers. + * + * @author David Madl (git@abanbytes.eu) + * @date 2020-05-16 + */ +public class AudioResources { + /** + * Copy raw tracks to filesystem: + * otherwise, packaged resources cannot directly be accessed from C code. + */ + public static void copyRawTracksToFilesystem(Context context) throws IOException { + Field[] fields = R.raw.class.getFields(); + long before = SystemClock.uptimeMillis(); + try { + for (int i = 0; i < fields.length; i++) { + String assetName = fields[i].getName(); + if (assetName.startsWith("track_")) { + int resourceId = fields[i].getInt(null); + copyFileUsingStream(context.getResources().openRawResource(resourceId), new File(context.getFilesDir() + "/" + resourceId + ".mp3")); + } + } + } catch(IllegalAccessException iae) { + // reflection should always work on R.raw + iae.printStackTrace(); + } + long after = SystemClock.uptimeMillis(); + Log.i("LoadTracks", String.format("copyRawTracksToFilesystem() took %.3f s", ((float)(after-before))/1e3f)); + } + + private static void copyFileUsingStream(InputStream is, File dest) throws IOException { + OutputStream os = null; + try { + os = new FileOutputStream(dest); + byte[] buffer = new byte[1024]; + int length; + while ((length = is.read(buffer)) > 0) { + os.write(buffer, 0, length); + } + } finally { + is.close(); + os.close(); + } + } +} diff --git a/app/src/main/java/at/lockstep/pb/PlaybackEngine.java b/app/src/main/java/at/lockstep/pb/PlaybackEngine.java index 1f5dcc2..68b3878 100644 --- a/app/src/main/java/at/lockstep/pb/PlaybackEngine.java +++ b/app/src/main/java/at/lockstep/pb/PlaybackEngine.java @@ -1,11 +1,16 @@ package at.lockstep.pb; import android.content.Context; +import android.media.AudioManager; +import android.os.Build; import android.util.Log; +import java.io.IOException; + public class PlaybackEngine { static long mEngineHandle = 0; static final int MPG123_OK = 0; + static boolean mFilesystemInitialized = false; static { System.loadLibrary("lockstep-native"); @@ -15,14 +20,36 @@ public class PlaybackEngine { } public static boolean create(Context context) { + try { + if(!mFilesystemInitialized) { + AudioResources.copyRawTracksToFilesystem(context); + mFilesystemInitialized = true; + } + } catch (IOException e) { + e.printStackTrace(); + return false; + } + if (mEngineHandle == 0) { - //setDefaultStreamValues(context); // TODO + setDefaultStreamValues(context); Log.i("PlaybackEngine", "Hello PlaybackEngine"); - mEngineHandle = native_createEngine(); + mEngineHandle = native_createEngine(context.getFilesDir().toString()); } return (mEngineHandle != 0); } + private static void setDefaultStreamValues(Context context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1){ + AudioManager myAudioMgr = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + String sampleRateStr = myAudioMgr.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE); + int defaultSampleRate = Integer.parseInt(sampleRateStr); + String framesPerBurstStr = myAudioMgr.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER); + int defaultFramesPerBurst = Integer.parseInt(framesPerBurstStr); + + native_setDefaultStreamValues(defaultSampleRate, defaultFramesPerBurst); + } + } + public static void delete() { if (mEngineHandle != 0){ native_deleteEngine(mEngineHandle); @@ -30,8 +57,9 @@ public class PlaybackEngine { mEngineHandle = 0; } - private static native long native_createEngine(); + private static native long native_createEngine(String filesDir); private static native void native_deleteEngine(long engineHandle); + private static native void native_setDefaultStreamValues(int sampleRate, int framesPerBurst); private static native int native_mpg123_init(); } diff --git a/app/src/main/res/raw/track_beat.mp3 b/app/src/main/res/raw/track_beat.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..ed6a555cd8308aefbda8231e51a72a5f29ad9c7d GIT binary patch literal 5736 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 BgE#;H literal 0 HcmV?d00001