feat: LibPasada API: stretcher for lockstep-player integration
This commit is contained in:
@@ -37,11 +37,12 @@ add_library(${CMAKE_PROJECT_NAME} SHARED
|
||||
jni_stepdetector.cpp
|
||||
jni_libpasada.cpp
|
||||
LibPasada.cpp
|
||||
#// jni_logging.cpp // same implemented in jni_libpasada.cpp // JNI_OnLoad and JNI_OnUnload
|
||||
)
|
||||
|
||||
find_package (oboe REQUIRED CONFIG)
|
||||
|
||||
add_library(ndk-logger SHARED logging.cpp jni_logging.cpp)
|
||||
add_library(ndk-logger SHARED logging.cpp) # jni_logging.cpp
|
||||
target_link_libraries(ndk-logger log)
|
||||
|
||||
# Add pre-built libmpg123 library
|
||||
|
||||
@@ -13,6 +13,10 @@ void PasadaPlaybackListener::onError(int errorCode, std::string message) {
|
||||
emitError(errorCode, message);
|
||||
}
|
||||
|
||||
void PasadaPlaybackListener::onTrackClosed(int fd) {
|
||||
emitTrackClosed(fd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mirrors the libpasada state machine documented in DESIGN.md.
|
||||
* Keep values in sync with PasadaState.java
|
||||
@@ -57,7 +61,7 @@ void LibPasada::init() {
|
||||
std::lock_guard<std::mutex> lock(mtxDetector);
|
||||
if(state == LOADED || state == STOPPED) {
|
||||
// perform init
|
||||
auto *playbackEngine = new PlaybackEngine("", 0);
|
||||
auto *playbackEngine = new PlaybackEngine("", 0, this);
|
||||
engine = playbackEngine;
|
||||
|
||||
auto *stepListener = reinterpret_cast<StepListener *>(playbackEngine);
|
||||
@@ -100,6 +104,7 @@ void LibPasada::pause() {
|
||||
std::lock_guard<std::mutex> lockState(mtxState);
|
||||
if(state != PLAYING) return;
|
||||
state = PAUSED;
|
||||
// TODO: pause plays a rect noise in PlaybackEngine. debug it!
|
||||
auto *playbackEngine = reinterpret_cast<PlaybackEngine *>(engine);
|
||||
playbackEngine->pause();
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
// JNI helpers for calling into Java code (PasadaPlaybackListener)
|
||||
void emitTrackFinished();
|
||||
void emitError(int errorCode, const std::string& message);
|
||||
void emitTrackClosed(int fd);
|
||||
|
||||
/**
|
||||
* Mirrors the libpasada state machine documented in DESIGN.md.
|
||||
|
||||
@@ -16,6 +16,7 @@ public:
|
||||
virtual ~PasadaPlaybackListener() = default;
|
||||
/** Current track reached end; service should advance queue and call {@link LibPasada#play}. */
|
||||
virtual void onTrackFinished();
|
||||
virtual void onTrackClosed(int fd);
|
||||
|
||||
/** Decode / Oboe / pipeline failure; service should stop safely and surface error. */
|
||||
virtual void onError(int errorCode, std::string message);
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <chrono>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <random>
|
||||
|
||||
/**
|
||||
* Read samples from the next mp3-frame into the struct MP3File's buffer.
|
||||
@@ -168,10 +169,12 @@ void PlaybackEngine::closeMusicFile() {
|
||||
haveMusicFile.store(false);
|
||||
musicFile = nullptr;
|
||||
if(android_fd) {
|
||||
close(android_fd);
|
||||
//close(android_fd);
|
||||
if(listener) listener->onTrackClosed(android_fd);
|
||||
android_fd = 0;
|
||||
}
|
||||
isSetMusic.store(false);
|
||||
if(mPlayer)
|
||||
mPlayer->setMusic(nullptr);
|
||||
}
|
||||
|
||||
@@ -198,6 +201,10 @@ void PlaybackEngine::mapChannels(int *channel_map, int num_ch_in, int num_ch_out
|
||||
}
|
||||
|
||||
void PlaybackEngine::musicFeedThread() {
|
||||
/*
|
||||
std::mt19937 mt(42);
|
||||
std::uniform_real_distribution<double> dist(0.98, 1.0);
|
||||
*/
|
||||
LOGI("starting musicFeedThread()");
|
||||
|
||||
// strecher num channels: same as output num channels
|
||||
@@ -304,7 +311,10 @@ void PlaybackEngine::musicFeedThread() {
|
||||
// 1024, 512, 512
|
||||
// 7 x 512
|
||||
}
|
||||
memset(buf, 0, num_samples*num_ch_out*sizeof(float));
|
||||
//memset(buf, 0, num_samples*num_ch_out*sizeof(float)); // wrong, because buffer is not contiguous for channels
|
||||
for(int i = 0; i < num_ch_out; i++) {
|
||||
memset(buf_ptr[i], 0, num_samples * sizeof(float));
|
||||
}
|
||||
stretcher.process(buf_ptr, num_samples, false);
|
||||
continue;
|
||||
}
|
||||
@@ -315,6 +325,13 @@ void PlaybackEngine::musicFeedThread() {
|
||||
// feed 1024 music samples
|
||||
// => stretcher is asking for 1024 = getSamplesRequired()
|
||||
// ca. 21 ms
|
||||
|
||||
/*
|
||||
int64_t a = mpg123_seek(musicFile->handle, 0, SEEK_CUR);
|
||||
int64_t b = mpg123_tell(musicFile->handle);
|
||||
LOGI(" checking mpg123_seek(): len=%lld seek=%lld tell=%lld",
|
||||
(long long)musicFile->num_samples, (long long)a, (long long)b);
|
||||
*/
|
||||
}
|
||||
|
||||
markerPos.store((musicFile->num_samples - musicFile->remaining_samples) / musicFile->samples_per_frame * musicFile->secs_per_frame);
|
||||
@@ -324,30 +341,48 @@ void PlaybackEngine::musicFeedThread() {
|
||||
double pos = seekPos.load();
|
||||
// compute seek target in samples, clip to [0..num_samples]
|
||||
auto seek_samples = static_cast<long>(pos * musicFile->samples_per_frame / musicFile->secs_per_frame);
|
||||
LOGI("seek to %.3lf sec = %lld samples", pos, (long long) seek_samples);
|
||||
seek_samples = std::max(seek_samples, 0L);
|
||||
seek_samples = std::min(seek_samples, musicFile->num_samples);
|
||||
// compute seek delta, to update byte 'offset'
|
||||
auto seek_samples_delta = seek_samples - (musicFile->num_samples - musicFile->remaining_samples);
|
||||
auto seek_bytes_delta = seek_samples_delta * num_ch_in * sizeof(int16_t);
|
||||
// perform the seek
|
||||
off_t seekResult = mpg123_seek(musicFile->handle, musicFile->num_samples - musicFile->remaining_samples, SEEK_SET);
|
||||
if(seekResult != seek_samples) {
|
||||
LOGE("error seeking in mp3 file: mpg123_seek(handle, %d, SEEK_SET)=%d", seek_samples, seekResult);
|
||||
}
|
||||
off_t seekResult = mp3file_seek(musicFile.get(), seek_samples);
|
||||
idebug = 0;
|
||||
/*if(seekResult > seek_samples) {
|
||||
LOGE("error seeking in mp3 file: mpg123_seek(handle, %ld, SEEK_SET)=%lld", seek_samples, (long long) seekResult);
|
||||
seekPos.store(pos * dist(mt)); // randomize seek target a bit
|
||||
} else {*/
|
||||
// update structure
|
||||
musicFile->remaining_samples = static_cast<int>(musicFile->num_samples - seek_samples);
|
||||
musicFile->offset += seek_bytes_delta;
|
||||
// compute seek delta, to update byte 'offset'
|
||||
//auto seek_samples_delta = seekResult - (musicFile->num_samples - musicFile->remaining_samples);
|
||||
//auto seek_bytes_delta = seek_samples_delta * num_ch_in * sizeof(int16_t);
|
||||
musicFile->remaining_samples = static_cast<int>(musicFile->num_samples - seekResult);
|
||||
//musicFile->offset += seek_bytes_delta;
|
||||
musicFile->offset = lseek(musicFile->android_fd, 0, SEEK_CUR);
|
||||
// it is not possible to obtain bytes offset reliably??
|
||||
//musicFile->num_bytes = -1; // pretend we can read until the end
|
||||
requestSeek.store(false);
|
||||
/*}*/
|
||||
}
|
||||
|
||||
size_t done = 0; // bytes!
|
||||
size_t read_size_bytes_calc = std::min(num_samples * num_ch_in * sizeof(int16_t), cbuf_size_bytes);
|
||||
size_t read_size_bytes = read_size_bytes_calc;
|
||||
if (musicFile->offset != 0 && musicFile->num_bytes != -1) { read_size_bytes = std::min(read_size_bytes_calc, (size_t) musicFile->num_bytes - musicFile->offset); }
|
||||
size_t read_size_bytes = std::min(num_samples * num_ch_in * sizeof(int16_t), cbuf_size_bytes);
|
||||
/*if (musicFile->offset != 0 && musicFile->num_bytes != -1) { read_size_bytes = std::min(read_size_bytes_calc, (size_t) musicFile->num_bytes - musicFile->offset); }*/
|
||||
//if (idebug < 10) LOGI("mft rsbc=%lld rsb=%lld nb=%lld o=%lld", (long long) read_size_bytes_calc, (long long) read_size_bytes, (long long) musicFile->num_bytes, (long long) musicFile->offset);
|
||||
int err = mpg123_read(musicFile->handle, cbuf, read_size_bytes, &done);
|
||||
if (musicFile->offset != 0 && musicFile->num_bytes != -1 && err == MPG123_OK && read_size_bytes <= read_size_bytes_calc) { err = MPG123_DONE; }
|
||||
//if (idebug < 10) LOGI("mft done=%lld err=%lld small=%d", (long long) done, (long long) err, read_size_bytes < read_size_bytes_calc);
|
||||
/*if (musicFile->offset != 0 && musicFile->num_bytes != -1 && err == MPG123_OK && read_size_bytes < read_size_bytes_calc) {
|
||||
LOGW("out of bytes, finished playing");
|
||||
err = MPG123_DONE;
|
||||
}*/
|
||||
musicFile->remaining_samples -= done / sizeof(int16_t);
|
||||
musicFile->offset += done;
|
||||
//musicFile->offset += done;
|
||||
musicFile->offset = lseek(musicFile->android_fd, 0, SEEK_CUR);
|
||||
if(musicFile->offset >= musicFile->num_bytes) {
|
||||
// TODO: this is inaccurate. either we do not decode everything, or we overrun past the end
|
||||
// TODO: maybe decode as a "stream" instead.
|
||||
LOGW("out of bytes, finished playing");
|
||||
err = MPG123_DONE;
|
||||
}
|
||||
if (err != MPG123_OK && err != MPG123_DONE) {
|
||||
// error!
|
||||
LOGE("error reading mp3 file: mpg123_read() err=%d done=%d", err, done);
|
||||
@@ -363,6 +398,7 @@ void PlaybackEngine::musicFeedThread() {
|
||||
// next iteration will play silence
|
||||
// we keep Stretcher and Oboe alive
|
||||
LOGI("finished reading mp3 file (MPG123_DONE)");
|
||||
idebug = 0;
|
||||
closeMusicFile();
|
||||
if(listener) listener->onTrackFinished();
|
||||
continue;
|
||||
@@ -394,6 +430,7 @@ void PlaybackEngine::musicFeedThread() {
|
||||
}
|
||||
|
||||
void PlaybackEngine::pause() {
|
||||
LOGI("PlaybackEngine::pause() set isPaused.");
|
||||
// next iteration will play silence
|
||||
isPaused.store(true);
|
||||
}
|
||||
|
||||
@@ -4,28 +4,55 @@
|
||||
#include <jni.h>
|
||||
#include <string>
|
||||
#include "LibPasada.h"
|
||||
#include "logging.h"
|
||||
|
||||
static JavaVM* g_vm = nullptr;
|
||||
static jobject g_listener = nullptr; // global ref, or nullptr
|
||||
static jmethodID g_onTrackFinished = nullptr;
|
||||
static jmethodID g_onTrackClosed = nullptr;
|
||||
static jmethodID g_onError = nullptr;
|
||||
|
||||
static LibPasada* g_libpasada = new LibPasada();
|
||||
static LibPasada* g_libpasada = nullptr;
|
||||
|
||||
void clearListener(JNIEnv* env);
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) {
|
||||
g_vm = vm;
|
||||
|
||||
JNIEnv * env = NULL;
|
||||
//jclass clazz;
|
||||
//LOGV("JNI_Onload vm:%p reserved:%p", vm, reserved);
|
||||
|
||||
if (JNI_OK != vm->GetEnv((void**) &env, JNI_VERSION_1_6)) {
|
||||
return JNI_VERSION_1_6;
|
||||
}
|
||||
|
||||
logback_init(vm, env);
|
||||
LOGD("liblockstep-native jni_libpasada.cpp JNI_OnLoad()");
|
||||
|
||||
return JNI_VERSION_1_6;
|
||||
}
|
||||
|
||||
extern "C" void
|
||||
JNI_OnUnload(JavaVM * vm, void * reserved)
|
||||
{
|
||||
JNIEnv * env = NULL;
|
||||
//LOGV("JNI_OnUnload vm:%p reserved:%p", vm, reserved);
|
||||
|
||||
if (JNI_OK != vm->GetEnv((void**) &env, JNI_VERSION_1_6)) {
|
||||
return;
|
||||
}
|
||||
logback_uninit(env);
|
||||
}
|
||||
|
||||
/** Called each time a run/playlist session starts (Oboe silent, buffers ready). */
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_at_lockstep_player_pasada_LibPasada_init(
|
||||
JNIEnv *env,
|
||||
jclass /*unused*/) {
|
||||
LOGD("liblockstep-native jni_libpasada.cpp LibPasada_init()");
|
||||
if (g_libpasada) {
|
||||
clearListener(env);
|
||||
delete g_libpasada;
|
||||
@@ -33,6 +60,7 @@ Java_at_lockstep_player_pasada_LibPasada_init(
|
||||
try {
|
||||
g_libpasada = new LibPasada();
|
||||
g_libpasada->init();
|
||||
return;
|
||||
} catch (const std::bad_alloc&) {
|
||||
jclass cls = env->FindClass("java/lang/OutOfMemoryError");
|
||||
if (cls) env->ThrowNew(cls, "native allocation failed");
|
||||
@@ -83,6 +111,7 @@ extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_at_lockstep_player_pasada_LibPasada_play(JNIEnv *env, jclass clazz, jint fd, jlong offset,
|
||||
jlong length) {
|
||||
LOGD("liblockstep-native jni_libpasada.cpp LibPasada_play()");
|
||||
if (g_libpasada == nullptr)
|
||||
return;
|
||||
|
||||
@@ -306,6 +335,7 @@ Java_at_lockstep_player_pasada_LibPasada_setPlaybackListener(JNIEnv *env, jclass
|
||||
// 3. Resolve methods once (valid until class unload)
|
||||
jclass cls = env->GetObjectClass(g_listener);
|
||||
g_onTrackFinished = env->GetMethodID(cls, "onTrackFinished", "()V");
|
||||
g_onTrackClosed = env->GetMethodID(cls, "onTrackClosed", "(I)V");
|
||||
g_onError = env->GetMethodID(
|
||||
cls, "onError", "(ILjava/lang/String;)V");
|
||||
env->DeleteLocalRef(cls);
|
||||
@@ -338,6 +368,14 @@ void emitError(int errorCode, const char* message) {
|
||||
env->ExceptionClear();
|
||||
}
|
||||
}
|
||||
void emitTrackClosed(int fd) {
|
||||
if (!g_vm || !g_listener || !g_onTrackClosed) return;
|
||||
JNIEnv* env = nullptr;
|
||||
if (g_vm->AttachCurrentThread(&env, nullptr) != JNI_OK) return;
|
||||
env->CallVoidMethod(g_listener, g_onTrackClosed, static_cast<jint>(fd));
|
||||
if (env->ExceptionCheck()) env->ExceptionClear();
|
||||
// DetachCurrentThread only if this thread won't call JNI again
|
||||
}
|
||||
void emitError(int errorCode, const std::string& message) {
|
||||
emitError(errorCode, message.c_str());
|
||||
}
|
||||
|
||||
@@ -22,6 +22,27 @@ Java_at_lockstep_pb_PlaybackEngine_native_1createEngine(
|
||||
return reinterpret_cast<jlong>(engine);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_at_lockstep_pb_PlaybackEngine_native_1stop(
|
||||
JNIEnv *env,
|
||||
jclass,
|
||||
jlong engineHandle) {
|
||||
|
||||
auto *engine = reinterpret_cast<PlaybackEngine *>(engineHandle);
|
||||
engine->stop();
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_at_lockstep_pb_PlaybackEngine_native_1pause(
|
||||
JNIEnv *env,
|
||||
jclass,
|
||||
jlong engineHandle) {
|
||||
|
||||
auto *engine = reinterpret_cast<PlaybackEngine *>(engineHandle);
|
||||
// TODO: this is broken. there is a square wave noise played, instead of silence
|
||||
engine->pause(); // do not tear down the Oboe pipeline, such that we can play again later (without having to re-construct PlaybackEngine)
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_at_lockstep_pb_PlaybackEngine_native_1deleteEngine(
|
||||
JNIEnv *env,
|
||||
|
||||
@@ -66,14 +66,14 @@ JNI_OnLoad(JavaVM * vm, void * reserved)
|
||||
jclass clazz;
|
||||
//LOGV("JNI_Onload vm:%p reserved:%p", vm, reserved);
|
||||
|
||||
if (JNI_OK != vm->GetEnv((void**) &env, JNI_VERSION_1_4)) {
|
||||
//LOGE("JNI_OnUnload GetEnv JNI_VERSION_1_4 failed");
|
||||
return JNI_VERSION_1_4;
|
||||
if (JNI_OK != vm->GetEnv((void**) &env, JNI_VERSION_1_6)) {
|
||||
//LOGE("JNI_OnUnload GetEnv JNI_VERSION_1_6 failed");
|
||||
return JNI_VERSION_1_6;
|
||||
}
|
||||
|
||||
logback_init(vm, env);
|
||||
|
||||
return JNI_VERSION_1_4;
|
||||
return JNI_VERSION_1_6;
|
||||
}
|
||||
|
||||
extern "C" void
|
||||
@@ -82,8 +82,8 @@ JNI_OnUnload(JavaVM * vm, void * reserved)
|
||||
JNIEnv * env = NULL;
|
||||
//LOGV("JNI_OnUnload vm:%p reserved:%p", vm, reserved);
|
||||
|
||||
if (JNI_OK != vm->GetEnv((void**) &env, JNI_VERSION_1_4)) {
|
||||
//LOGE("JNI_OnUnload GetEnv JNI_VERSION_1_4 failed");
|
||||
if (JNI_OK != vm->GetEnv((void**) &env, JNI_VERSION_1_6)) {
|
||||
//LOGE("JNI_OnUnload GetEnv JNI_VERSION_1_6 failed");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ __logback_print(const int level, const char * format, ...)
|
||||
JNIEnv * env = NULL;
|
||||
jint err = JNI_OK;
|
||||
if (! __logback_jvm) return;
|
||||
if (JNI_EDETACHED == (err = __logback_jvm->GetEnv((void**) &env, JNI_VERSION_1_4))) {
|
||||
if (JNI_EDETACHED == (err = __logback_jvm->GetEnv((void**) &env, JNI_VERSION_1_6))) {
|
||||
if (__logback_jvm->AttachCurrentThread(&env, NULL) < 0) {
|
||||
__android_log_write(LOG_LEVEL_WARN, LOG_TAG, "__logback_write failed to get env neither attach current thread");
|
||||
}
|
||||
@@ -115,7 +115,7 @@ __logback_vprint(const int level, const char * format, va_list ap)
|
||||
JNIEnv * env = NULL;
|
||||
jint err = JNI_OK;
|
||||
if (! __logback_jvm) return;
|
||||
if (JNI_EDETACHED == (err = __logback_jvm->GetEnv((void**) &env, JNI_VERSION_1_4))) {
|
||||
if (JNI_EDETACHED == (err = __logback_jvm->GetEnv((void**) &env, JNI_VERSION_1_6))) {
|
||||
if (__logback_jvm->AttachCurrentThread(&env, NULL) < 0) {
|
||||
__android_log_write(LOG_LEVEL_WARN, LOG_TAG, "__logback_write failed to get env neither attach current thread");
|
||||
}
|
||||
@@ -136,7 +136,7 @@ __logback_write(const int level, const char * msg)
|
||||
JNIEnv * env = NULL;
|
||||
jint err = JNI_OK;
|
||||
if (! __logback_jvm) return;
|
||||
if (JNI_EDETACHED == (err = __logback_jvm->GetEnv((void**) &env, JNI_VERSION_1_4))) {
|
||||
if (JNI_EDETACHED == (err = __logback_jvm->GetEnv((void**) &env, JNI_VERSION_1_6))) {
|
||||
if (__logback_jvm->AttachCurrentThread(&env, NULL) < 0) {
|
||||
__android_log_write(LOG_LEVEL_WARN, LOG_TAG, "__logback_write failed to get env neither attach current thread");
|
||||
}
|
||||
|
||||
@@ -105,6 +105,8 @@ MP3File* mp3file_open_fd(int fd, long long offset, long long length, int forceEn
|
||||
goto on_error; \
|
||||
} while(0)
|
||||
|
||||
int64_t len, a, b;
|
||||
|
||||
int err = MPG123_OK;
|
||||
mpg123_handle *mh = mpg123_new(NULL, &err);
|
||||
if(err != MPG123_OK || mh == NULL) {
|
||||
@@ -119,6 +121,9 @@ MP3File* mp3file_open_fd(int fd, long long offset, long long length, int forceEn
|
||||
err = mpg123_open_fd(mh, fd);
|
||||
if(err != MPG123_OK) handleError("mpg123_open()");
|
||||
|
||||
// hopefully the magic bullet for seeking in the file (otherwise, mpg123_seek() returns totally random offsets)
|
||||
mpg123_scan(mh);
|
||||
|
||||
int encoding;
|
||||
err = mpg123_getformat(mh, &mp3->rate, &mp3->channels, &encoding);
|
||||
if(err != MPG123_OK) handleError("mpg123_getformat()");
|
||||
@@ -156,6 +161,17 @@ MP3File* mp3file_open_fd(int fd, long long offset, long long length, int forceEn
|
||||
mp3->remaining_samples = (int) mp3->num_samples;
|
||||
LOGI("channels: %d rate: %ld num_samples: %ld", mp3->channels, mp3->rate, mp3->num_samples);
|
||||
mp3->android_fd = fd;
|
||||
|
||||
// begin debug: seek returns wildly random offsets
|
||||
|
||||
len = mpg123_length(mh);
|
||||
a = mpg123_seek(mh, 0, SEEK_SET);
|
||||
b = mpg123_tell(mh);
|
||||
|
||||
LOGI("using mpg123_scan: len=%lld seek=%lld tell=%lld",
|
||||
(long long)len, (long long)a, (long long)b);
|
||||
// end debug
|
||||
|
||||
return mp3;
|
||||
|
||||
on_error:
|
||||
@@ -165,3 +181,22 @@ on_error:
|
||||
|
||||
#undef handleError
|
||||
}
|
||||
|
||||
off_t mp3file_seek(MP3File *mp3file, off_t samples) {
|
||||
LOGD("mp3file_seek(): seek to %ld samples", samples);
|
||||
off_t seekResult = mpg123_seek(mp3file->handle, samples, SEEK_SET);
|
||||
LOGD(" mp3file_seek(): %ld samples remaining", samples - seekResult);
|
||||
if(seekResult > samples + 4800) {
|
||||
LOGE("mp3file_seek(): error: landed beyond the seek position!");
|
||||
return seekResult;
|
||||
}
|
||||
// try to refine the seek position
|
||||
int max_iter = 6;
|
||||
int cur_iter = 0;
|
||||
for(; cur_iter < max_iter && seekResult < samples - 4800; cur_iter++) {
|
||||
seekResult = mpg123_seek(mp3file->handle, samples - seekResult, SEEK_CUR);
|
||||
LOGD(" mp3file_seek(): %ld samples remaining", samples - seekResult);
|
||||
}
|
||||
LOGD("mp3file_seek(): finished");
|
||||
return seekResult;
|
||||
}
|
||||
|
||||
@@ -33,5 +33,6 @@ MP3File* mp3file_init(mpg123_handle *handle);
|
||||
void mp3file_delete(MP3File *mp3file);
|
||||
MP3File* mp3file_open(const char *filename, int forceEncoding = 0);
|
||||
MP3File* mp3file_open_fd(int fd, long long offset = 0, long long length = -1, int forceEncoding = 0);
|
||||
off_t mp3file_seek(MP3File *mp3file, off_t samples);
|
||||
|
||||
#endif //SAMPLES_MP3FILE_H
|
||||
|
||||
@@ -40,6 +40,7 @@ public class LstForegroundService extends Service implements SensorEventListener
|
||||
|
||||
public static final String ACTION_START = "at.lockstep.action.START";
|
||||
public static final String ACTION_STOP = "at.lockstep.action.STOP";
|
||||
public static final String ACTION_PAUSE = "at.lockstep.action.PAUSE";
|
||||
|
||||
private SensorManager sensorManager;
|
||||
private Sensor accelerometer;
|
||||
@@ -60,6 +61,11 @@ public class LstForegroundService extends Service implements SensorEventListener
|
||||
intent.setAction(ACTION_STOP);
|
||||
return intent;
|
||||
}
|
||||
public static Intent pauseIntent(Context context) {
|
||||
Intent intent = new Intent(context, LstForegroundService.class);
|
||||
intent.setAction(ACTION_PAUSE);
|
||||
return intent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
@@ -98,7 +104,7 @@ public class LstForegroundService extends Service implements SensorEventListener
|
||||
String contentUri = intent.getStringExtra("content_uri");
|
||||
try {
|
||||
if(contentUri != null) {
|
||||
PlaybackEngine.playMusic(uriToFd(contentUri));
|
||||
PlaybackEngine.playMusic(uriToFd(contentUri), this, R.raw.track_beat);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// TODO proper error handling
|
||||
@@ -109,6 +115,10 @@ public class LstForegroundService extends Service implements SensorEventListener
|
||||
} else if (ACTION_STOP.equals(action)) {
|
||||
Log.i("LstForegroundService", "ACTION_STOP");
|
||||
stopCollectionAndSelf();
|
||||
PlaybackEngine.stop();
|
||||
} else if (ACTION_PAUSE.equals(action)) {
|
||||
Log.i("LstForegroundService", "ACTION_PAUSE");
|
||||
PlaybackEngine.pause();
|
||||
}
|
||||
}
|
||||
return START_STICKY;
|
||||
|
||||
@@ -32,6 +32,7 @@ import java.io.Writer;
|
||||
public class MainActivity extends AppCompatActivity implements LstForegroundService.OnResultListener {
|
||||
private Button btnStart;
|
||||
private Button btnStop;
|
||||
private Button btnPause;
|
||||
private Button btnMediaStoreBenchmark;
|
||||
private Button btnPickSong;
|
||||
private final ActivityResultLauncher<Intent> launcher;
|
||||
@@ -61,6 +62,7 @@ public class MainActivity extends AppCompatActivity implements LstForegroundServ
|
||||
|
||||
btnStart = findViewById(R.id.btnStart);
|
||||
btnStop = findViewById(R.id.btnStop);
|
||||
btnPause = findViewById(R.id.btnPause);
|
||||
btnMediaStoreBenchmark = findViewById(R.id.btnMediaStoreBenchmark);
|
||||
btnPickSong = findViewById(R.id.btnPickSong);
|
||||
|
||||
@@ -83,6 +85,10 @@ public class MainActivity extends AppCompatActivity implements LstForegroundServ
|
||||
startService(LstForegroundService.stopIntent(MainActivity.this))
|
||||
);
|
||||
|
||||
btnPause.setOnClickListener(v ->
|
||||
startService(LstForegroundService.pauseIntent(MainActivity.this))
|
||||
);
|
||||
|
||||
btnMediaStoreBenchmark.setOnClickListener(v -> {
|
||||
Intent intent = new Intent(MainActivity.this, MediaStoreBenchmarkActivity.class);
|
||||
startActivity(intent);
|
||||
|
||||
@@ -76,16 +76,38 @@ public class PlaybackEngine {
|
||||
return mEngineHandle;
|
||||
}
|
||||
|
||||
public static void playMusic(int fd) {
|
||||
public static void playMusic(int fd, Context context, int beat_resid) {
|
||||
if (mEngineHandle == 0) {
|
||||
create(context, beat_resid);
|
||||
}
|
||||
if (mEngineHandle != 0) {
|
||||
native_playMusic(mEngineHandle, fd);
|
||||
}
|
||||
}
|
||||
|
||||
public static void stop() {
|
||||
if (mEngineHandle != 0) {
|
||||
//native_stop(mEngineHandle);
|
||||
// for now, (pause is bugged), deleting engine is the only clean way
|
||||
native_deleteEngine(mEngineHandle);
|
||||
mEngineHandle = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static void pause() {
|
||||
if (mEngineHandle != 0) {
|
||||
Log.i("PlaybackEngine", "native_pause() ...");
|
||||
native_pause(mEngineHandle);
|
||||
Log.i("PlaybackEngine", "native_pause() done.");
|
||||
}
|
||||
}
|
||||
|
||||
private static native long native_createEngine(String filesDir, int resid);
|
||||
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();
|
||||
private static native void native_playMusic(long engineHandle, int fd);
|
||||
private static native void native_stop(long engineHandle);
|
||||
private static native void native_pause(long engineHandle);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ public interface PasadaPlaybackListener {
|
||||
/** Current track reached end; service should advance queue and call {@link LibPasada#play}. */
|
||||
void onTrackFinished();
|
||||
|
||||
void onTrackClosed(int fd);
|
||||
|
||||
/** Decode / Oboe / pipeline failure; service should stop safely and surface error. */
|
||||
void onError(int errorCode, String message);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,13 @@
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="Stop collection" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnPause"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="Pause" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnMediaStoreBenchmark"
|
||||
android:layout_width="wrap_content"
|
||||
|
||||
Reference in New Issue
Block a user