Skip to content

Commit

Permalink
It turned out that attaching JNIEnv to callback threads isn't a good …
Browse files Browse the repository at this point in the history
…idea at all!
  • Loading branch information
fgnm committed Oct 31, 2024
1 parent d8921bb commit b5d31e6
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 16 deletions.
3 changes: 3 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[0.6]
- Fix memory leaks and wrong logic in Native -> Java callbacks

[0.5]
- Update jniGen to 2.5.2
- Update MiniAudio to 0.11.22 [dev-6ab4567]
Expand Down
69 changes: 69 additions & 0 deletions jni/queue.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#ifndef QUEUE_H
#define QUEUE_H

#include <stdio.h>
#include <stdlib.h>
#include <stdatomic.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>

#define QUEUE_SIZE 100

typedef struct {
int type;
ma_sound* sound;
ma_uint32 level;
char* message;
int notificationType;
} Event;

typedef struct {
Event* buffer[QUEUE_SIZE];
ma_atomic_uint32 head; // Points to the next free slot for producers
ma_atomic_uint32 tail; // Points to the next item for the consumer to consume
} LockFreeQueue;

// Initialize the lock-free queue
void init_queue(LockFreeQueue *queue) {
ma_atomic_uint32_set(&queue->head, 0);
ma_atomic_uint32_set(&queue->tail, 0);
}

// Add an event to the queue (non-blocking, multi-producer safe)
int enqueue(LockFreeQueue *queue, Event* event) {
ma_uint32 head, tail, next_head;

do {
head = ma_atomic_uint32_get(&queue->head);
tail = ma_atomic_uint32_get(&queue->tail);
next_head = (head + 1) % QUEUE_SIZE;

// Check if the queue is full
if (next_head == tail) {
return -1; // Queue is full
}
} while (!ma_atomic_compare_exchange_weak_32((ma_uint32*) &queue->head, &head, next_head));

// Store the event
queue->buffer[head] = event;
return 0;
}

// Remove an event from the queue (non-blocking, single-consumer)
int dequeue(LockFreeQueue *queue, Event **event) {
int tail = ma_atomic_uint32_get(&queue->tail);
int head = ma_atomic_uint32_get(&queue->head);

// Check if the queue is empty
if (tail == head) {
return -1; // Queue is empty
}

// Retrieve the event
*event = queue->buffer[tail];
ma_atomic_uint32_set(&queue->tail, (tail + 1) % QUEUE_SIZE); // Advance tail
return 0;
}

#endif
88 changes: 72 additions & 16 deletions src/main/java/games/rednblack/miniaudio/MiniAudio.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public class MiniAudio implements Disposable {
#include "ma_encoder_node.c"
#include <stdio.h>
#include <queue.h>
#ifdef MA_ANDROID
#include <android/asset_manager_jni.h>
Expand Down Expand Up @@ -79,7 +80,9 @@ void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uin
static jmethodID jon_native_sound_end;
static jmethodID jon_native_log;
static jmethodID jon_native_notification;
static JNIEnv* soundEnv;
static LockFreeQueue* lock_free_queue;
static ma_thread* callback_thread;
static int running_callback_thread = 1;
// Helper macro for platform-specific thread attachment
#ifdef MA_ANDROID
Expand All @@ -104,29 +107,75 @@ void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uin
}
void sound_end_callback(void* pUserData, ma_sound* pSound) {
if (jvm->GetEnv((void**)&soundEnv, JNI_VERSION_1_6) == JNI_EDETACHED) {
#ifdef MA_ANDROID
jvm->AttachCurrentThreadAsDaemon(&soundEnv, NULL);
#else
jvm->AttachCurrentThreadAsDaemon((void**)&soundEnv, NULL);
#endif
Event* event = (Event*) ma_malloc(sizeof(Event), NULL);
event->type = 0;
event->sound = pSound;
while (enqueue(lock_free_queue, event) == -1) {
usleep(100000); // 0.1 seconds
}
soundEnv->CallVoidMethod(jMiniAudio, jon_native_sound_end, (jlong) pSound);
}
void ma_log_callback_jni(void* pUserData, ma_uint32 level, const char* pMessage) {
ATTACH_ENV()
jstring javaMessage = env->NewStringUTF(pMessage);
env->CallVoidMethod(jMiniAudio, jon_native_log, level, javaMessage);
env->DeleteLocalRef(javaMessage);
DETACH_ENV()
Event* event = (Event*) ma_malloc(sizeof(Event), NULL);
event->type = 1;
event->message = strdup(pMessage);
event->level = level;
while (enqueue(lock_free_queue, event) == -1) {
usleep(100000); // 0.1 seconds
}
}
void notification_callback_jni(const ma_device_notification* pNotification) {
Event* event = (Event*) ma_malloc(sizeof(Event), NULL);
event->type = 2;
event->notificationType = pNotification->type;
while (enqueue(lock_free_queue, event) == -1) {
usleep(100000); // 0.1 seconds
}
}
static ma_thread_result MA_THREADCALL post_event_to_jni(void* pUserData) {
ATTACH_ENV()
env->CallVoidMethod(jMiniAudio, jon_native_notification, pNotification->type);
Event* event;
while (running_callback_thread) {
if (dequeue(lock_free_queue, &event) == 0) {
if (event->type == 0) {
env->CallVoidMethod(jMiniAudio, jon_native_sound_end, (jlong) event->sound);
} else if (event->type == 1) {
jstring javaMessage = env->NewStringUTF(event->message);
env->CallVoidMethod(jMiniAudio, jon_native_log, event->level, javaMessage);
env->DeleteLocalRef(javaMessage);
free(event->message);
} else if (event->type == 2) {
env->CallVoidMethod(jMiniAudio, jon_native_notification, event->notificationType);
}
ma_free(event, NULL);
} else {
// Queue is empty, wait a bit
usleep(100000); // 0.1 seconds
}
}
//Drain queue before exit
while (dequeue(lock_free_queue, &event) == 0) {
if (event->type == 0) {
env->CallVoidMethod(jMiniAudio, jon_native_sound_end, (jlong) event->sound);
} else if (event->type == 1) {
jstring javaMessage = env->NewStringUTF(event->message);
env->CallVoidMethod(jMiniAudio, jon_native_log, event->level, javaMessage);
env->DeleteLocalRef(javaMessage);
free(event->message);
} else if (event->type == 2) {
env->CallVoidMethod(jMiniAudio, jon_native_notification, event->notificationType);
}
ma_free(event, NULL);
}
env->DeleteGlobalRef(jMiniAudio);
DETACH_ENV()
ma_free(lock_free_queue, NULL);
ma_free(callback_thread, NULL);
return (ma_thread_result)0;
}
*/

Expand Down Expand Up @@ -202,6 +251,13 @@ public void initEngine(int listenerCount, long playbackId, long captureId, int c
jon_native_log = env->GetMethodID(handlerClass, "on_native_log", "(ILjava/lang/String;)V");
jon_native_notification = env->GetMethodID(handlerClass, "on_native_notification", "(I)V");
lock_free_queue = (LockFreeQueue*) ma_malloc(sizeof(LockFreeQueue), NULL);
init_queue(lock_free_queue);
callback_thread = (ma_thread*) ma_malloc(sizeof(ma_thread), NULL);
running_callback_thread = 1;
ma_result thread_res = ma_thread_create(callback_thread, ma_thread_priority_normal, 0, post_event_to_jni, NULL, NULL);
if (thread_res != MA_SUCCESS) return thread_res;
ma_log_init(NULL, &maLog);
pLogCallback = ma_log_callback_init(ma_log_callback_jni, NULL);
ma_log_register_callback(&maLog, pLogCallback);
Expand Down Expand Up @@ -514,7 +570,7 @@ public void dispose() {
ma_free(androidVFS, NULL);
env->DeleteGlobalRef(assetManagerGlobalRef);
#endif
env->DeleteGlobalRef(jMiniAudio);
running_callback_thread = 0;
*/

/**
Expand Down

0 comments on commit b5d31e6

Please sign in to comment.