Files
moonlight-android/app/src/main/jni/moonlight-core/callbacks.c
2023-06-24 15:19:50 -05:00

480 lines
20 KiB
C

#include <jni.h>
#include <pthread.h>
#include <string.h>
#include <Limelight.h>
#include <opus_multistream.h>
#include <android/log.h>
static OpusMSDecoder* Decoder;
static OPUS_MULTISTREAM_CONFIGURATION OpusConfig;
static JavaVM *JVM;
static pthread_key_t JniEnvKey;
static pthread_once_t JniEnvKeyInitOnce = PTHREAD_ONCE_INIT;
static jclass GlobalBridgeClass;
static jmethodID BridgeDrSetupMethod;
static jmethodID BridgeDrStartMethod;
static jmethodID BridgeDrStopMethod;
static jmethodID BridgeDrCleanupMethod;
static jmethodID BridgeDrSubmitDecodeUnitMethod;
static jmethodID BridgeArInitMethod;
static jmethodID BridgeArStartMethod;
static jmethodID BridgeArStopMethod;
static jmethodID BridgeArCleanupMethod;
static jmethodID BridgeArPlaySampleMethod;
static jmethodID BridgeClStageStartingMethod;
static jmethodID BridgeClStageCompleteMethod;
static jmethodID BridgeClStageFailedMethod;
static jmethodID BridgeClConnectionStartedMethod;
static jmethodID BridgeClConnectionTerminatedMethod;
static jmethodID BridgeClRumbleMethod;
static jmethodID BridgeClConnectionStatusUpdateMethod;
static jmethodID BridgeClSetHdrModeMethod;
static jmethodID BridgeClRumbleTriggersMethod;
static jmethodID BridgeClSetMotionEventStateMethod;
static jbyteArray DecodedFrameBuffer;
static jshortArray DecodedAudioBuffer;
void DetachThread(void* context) {
(*JVM)->DetachCurrentThread(JVM);
}
void JniEnvKeyInit(void) {
// Create a TLS slot for the JNIEnv. We aren't in
// a pthread during init, so we must wait until we
// are to initialize this.
pthread_key_create(&JniEnvKey, DetachThread);
}
JNIEnv* GetThreadEnv(void) {
JNIEnv* env;
// First check if this is already attached to the JVM
if ((*JVM)->GetEnv(JVM, (void**)&env, JNI_VERSION_1_4) == JNI_OK) {
return env;
}
// Create the TLS slot now that we're safely in a pthread
pthread_once(&JniEnvKeyInitOnce, JniEnvKeyInit);
// Try the TLS to see if we already have a JNIEnv
env = pthread_getspecific(JniEnvKey);
if (env)
return env;
// This is the thread's first JNI call, so attach now
(*JVM)->AttachCurrentThread(JVM, &env, NULL);
// Write our JNIEnv to TLS, so we detach before dying
pthread_setspecific(JniEnvKey, env);
return env;
}
JNIEXPORT void JNICALL
Java_com_limelight_nvstream_jni_MoonBridge_init(JNIEnv *env, jclass clazz) {
(*env)->GetJavaVM(env, &JVM);
GlobalBridgeClass = (*env)->NewGlobalRef(env, (*env)->FindClass(env, "com/limelight/nvstream/jni/MoonBridge"));
BridgeDrSetupMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeDrSetup", "(IIII)I");
BridgeDrStartMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeDrStart", "()V");
BridgeDrStopMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeDrStop", "()V");
BridgeDrCleanupMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeDrCleanup", "()V");
BridgeDrSubmitDecodeUnitMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeDrSubmitDecodeUnit", "([BIIIICJJ)I");
BridgeArInitMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeArInit", "(III)I");
BridgeArStartMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeArStart", "()V");
BridgeArStopMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeArStop", "()V");
BridgeArCleanupMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeArCleanup", "()V");
BridgeArPlaySampleMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeArPlaySample", "([S)V");
BridgeClStageStartingMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeClStageStarting", "(I)V");
BridgeClStageCompleteMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeClStageComplete", "(I)V");
BridgeClStageFailedMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeClStageFailed", "(II)V");
BridgeClConnectionStartedMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeClConnectionStarted", "()V");
BridgeClConnectionTerminatedMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeClConnectionTerminated", "(I)V");
BridgeClRumbleMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeClRumble", "(SSS)V");
BridgeClConnectionStatusUpdateMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeClConnectionStatusUpdate", "(I)V");
BridgeClSetHdrModeMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeClSetHdrMode", "(Z[B)V");
BridgeClRumbleTriggersMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeClRumbleTriggers", "(SSS)V");
BridgeClSetMotionEventStateMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeClSetMotionEventState", "(SBS)V");
}
int BridgeDrSetup(int videoFormat, int width, int height, int redrawRate, void* context, int drFlags) {
JNIEnv* env = GetThreadEnv();
int err;
err = (*env)->CallStaticIntMethod(env, GlobalBridgeClass, BridgeDrSetupMethod, videoFormat, width, height, redrawRate);
if ((*env)->ExceptionCheck(env)) {
// This is called on a Java thread, so it's safe to return
return -1;
}
else if (err != 0) {
return err;
}
// Use a 32K frame buffer that will increase if needed
DecodedFrameBuffer = (*env)->NewGlobalRef(env, (*env)->NewByteArray(env, 32768));
return 0;
}
void BridgeDrStart(void) {
JNIEnv* env = GetThreadEnv();
(*env)->CallStaticVoidMethod(env, GlobalBridgeClass, BridgeDrStartMethod);
}
void BridgeDrStop(void) {
JNIEnv* env = GetThreadEnv();
(*env)->CallStaticVoidMethod(env, GlobalBridgeClass, BridgeDrStopMethod);
}
void BridgeDrCleanup(void) {
JNIEnv* env = GetThreadEnv();
(*env)->DeleteGlobalRef(env, DecodedFrameBuffer);
(*env)->CallStaticVoidMethod(env, GlobalBridgeClass, BridgeDrCleanupMethod);
}
int BridgeDrSubmitDecodeUnit(PDECODE_UNIT decodeUnit) {
JNIEnv* env = GetThreadEnv();
int ret;
// Increase the size of our frame data buffer if our frame won't fit
if ((*env)->GetArrayLength(env, DecodedFrameBuffer) < decodeUnit->fullLength) {
(*env)->DeleteGlobalRef(env, DecodedFrameBuffer);
DecodedFrameBuffer = (*env)->NewGlobalRef(env, (*env)->NewByteArray(env, decodeUnit->fullLength));
}
PLENTRY currentEntry;
int offset;
currentEntry = decodeUnit->bufferList;
offset = 0;
while (currentEntry != NULL) {
// Submit parameter set NALUs separately from picture data
if (currentEntry->bufferType != BUFFER_TYPE_PICDATA) {
// Use the beginning of the buffer each time since this is a separate
// invocation of the decoder each time.
(*env)->SetByteArrayRegion(env, DecodedFrameBuffer, 0, currentEntry->length, (jbyte*)currentEntry->data);
ret = (*env)->CallStaticIntMethod(env, GlobalBridgeClass, BridgeDrSubmitDecodeUnitMethod,
DecodedFrameBuffer, currentEntry->length, currentEntry->bufferType,
decodeUnit->frameNumber, decodeUnit->frameType, (jchar)decodeUnit->frameHostProcessingLatency,
(jlong)decodeUnit->receiveTimeMs, (jlong)decodeUnit->enqueueTimeMs);
if ((*env)->ExceptionCheck(env)) {
// We will crash here
(*JVM)->DetachCurrentThread(JVM);
return DR_OK;
}
else if (ret != DR_OK) {
return ret;
}
}
else {
(*env)->SetByteArrayRegion(env, DecodedFrameBuffer, offset, currentEntry->length, (jbyte*)currentEntry->data);
offset += currentEntry->length;
}
currentEntry = currentEntry->next;
}
ret = (*env)->CallStaticIntMethod(env, GlobalBridgeClass, BridgeDrSubmitDecodeUnitMethod,
DecodedFrameBuffer, offset, BUFFER_TYPE_PICDATA,
decodeUnit->frameNumber, decodeUnit->frameType, (jchar)decodeUnit->frameHostProcessingLatency,
(jlong)decodeUnit->receiveTimeMs, (jlong)decodeUnit->enqueueTimeMs);
if ((*env)->ExceptionCheck(env)) {
// We will crash here
(*JVM)->DetachCurrentThread(JVM);
return DR_OK;
}
else {
return ret;
}
}
int BridgeArInit(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURATION opusConfig, void* context, int flags) {
JNIEnv* env = GetThreadEnv();
int err;
err = (*env)->CallStaticIntMethod(env, GlobalBridgeClass, BridgeArInitMethod, audioConfiguration, opusConfig->sampleRate, opusConfig->samplesPerFrame);
if ((*env)->ExceptionCheck(env)) {
// This is called on a Java thread, so it's safe to return
err = -1;
}
if (err == 0) {
memcpy(&OpusConfig, opusConfig, sizeof(*opusConfig));
Decoder = opus_multistream_decoder_create(opusConfig->sampleRate,
opusConfig->channelCount,
opusConfig->streams,
opusConfig->coupledStreams,
opusConfig->mapping,
&err);
if (Decoder == NULL) {
(*env)->CallStaticVoidMethod(env, GlobalBridgeClass, BridgeArCleanupMethod);
return -1;
}
// We know ahead of time what the buffer size will be for decoded audio, so pre-allocate it
DecodedAudioBuffer = (*env)->NewGlobalRef(env, (*env)->NewShortArray(env, opusConfig->channelCount * opusConfig->samplesPerFrame));
}
return err;
}
void BridgeArStart(void) {
JNIEnv* env = GetThreadEnv();
(*env)->CallStaticVoidMethod(env, GlobalBridgeClass, BridgeArStartMethod);
}
void BridgeArStop(void) {
JNIEnv* env = GetThreadEnv();
(*env)->CallStaticVoidMethod(env, GlobalBridgeClass, BridgeArStopMethod);
}
void BridgeArCleanup() {
JNIEnv* env = GetThreadEnv();
opus_multistream_decoder_destroy(Decoder);
(*env)->DeleteGlobalRef(env, DecodedAudioBuffer);
(*env)->CallStaticVoidMethod(env, GlobalBridgeClass, BridgeArCleanupMethod);
}
void BridgeArDecodeAndPlaySample(char* sampleData, int sampleLength) {
JNIEnv* env = GetThreadEnv();
jshort* decodedData = (*env)->GetPrimitiveArrayCritical(env, DecodedAudioBuffer, NULL);
int decodeLen = opus_multistream_decode(Decoder,
(const unsigned char*)sampleData,
sampleLength,
decodedData,
OpusConfig.samplesPerFrame,
0);
if (decodeLen > 0) {
// We must release the array elements before making further JNI calls
(*env)->ReleasePrimitiveArrayCritical(env, DecodedAudioBuffer, decodedData, 0);
(*env)->CallStaticVoidMethod(env, GlobalBridgeClass, BridgeArPlaySampleMethod, DecodedAudioBuffer);
if ((*env)->ExceptionCheck(env)) {
// We will crash here
(*JVM)->DetachCurrentThread(JVM);
}
}
else {
// We can abort here to avoid the copy back since no data was modified
(*env)->ReleasePrimitiveArrayCritical(env, DecodedAudioBuffer, decodedData, JNI_ABORT);
}
}
void BridgeClStageStarting(int stage) {
JNIEnv* env = GetThreadEnv();
(*env)->CallStaticVoidMethod(env, GlobalBridgeClass, BridgeClStageStartingMethod, stage);
}
void BridgeClStageComplete(int stage) {
JNIEnv* env = GetThreadEnv();
(*env)->CallStaticVoidMethod(env, GlobalBridgeClass, BridgeClStageCompleteMethod, stage);
}
void BridgeClStageFailed(int stage, int errorCode) {
JNIEnv* env = GetThreadEnv();
(*env)->CallStaticVoidMethod(env, GlobalBridgeClass, BridgeClStageFailedMethod, stage, errorCode);
}
void BridgeClConnectionStarted(void) {
JNIEnv* env = GetThreadEnv();
(*env)->CallStaticVoidMethod(env, GlobalBridgeClass, BridgeClConnectionStartedMethod);
}
void BridgeClConnectionTerminated(int errorCode) {
JNIEnv* env = GetThreadEnv();
(*env)->CallStaticVoidMethod(env, GlobalBridgeClass, BridgeClConnectionTerminatedMethod, errorCode);
if ((*env)->ExceptionCheck(env)) {
// We will crash here
(*JVM)->DetachCurrentThread(JVM);
}
}
void BridgeClRumble(unsigned short controllerNumber, unsigned short lowFreqMotor, unsigned short highFreqMotor) {
JNIEnv* env = GetThreadEnv();
// The seemingly redundant short casts are required in order to convert the unsigned short to a signed short.
// If we leave it as an unsigned short, CheckJNI will fail when the value exceeds 32767. The cast itself is
// fine because the Java code treats the value as unsigned even though it's stored in a signed type.
(*env)->CallStaticVoidMethod(env, GlobalBridgeClass, BridgeClRumbleMethod, controllerNumber, (short)lowFreqMotor, (short)highFreqMotor);
if ((*env)->ExceptionCheck(env)) {
// We will crash here
(*JVM)->DetachCurrentThread(JVM);
}
}
void BridgeClConnectionStatusUpdate(int connectionStatus) {
JNIEnv* env = GetThreadEnv();
(*env)->CallStaticVoidMethod(env, GlobalBridgeClass, BridgeClConnectionStatusUpdateMethod, connectionStatus);
if ((*env)->ExceptionCheck(env)) {
// We will crash here
(*JVM)->DetachCurrentThread(JVM);
return;
}
}
void BridgeClSetHdrMode(bool enabled) {
JNIEnv* env = GetThreadEnv();
jbyteArray hdrMetadataByteArray = NULL;
SS_HDR_METADATA hdrMetadata;
// Check if HDR metadata was provided
if (enabled && LiGetHdrMetadata(&hdrMetadata)) {
hdrMetadataByteArray = (*env)->NewByteArray(env, sizeof(SS_HDR_METADATA));
(*env)->SetByteArrayRegion(env, hdrMetadataByteArray, 0, sizeof(SS_HDR_METADATA), (jbyte*)&hdrMetadata);
}
(*env)->CallStaticVoidMethod(env, GlobalBridgeClass, BridgeClSetHdrModeMethod, enabled, hdrMetadataByteArray);
if ((*env)->ExceptionCheck(env)) {
// We will crash here
(*JVM)->DetachCurrentThread(JVM);
}
}
void BridgeClRumbleTriggers(unsigned short controllerNumber, unsigned short leftTrigger, unsigned short rightTrigger) {
JNIEnv* env = GetThreadEnv();
// The seemingly redundant short casts are required in order to convert the unsigned short to a signed short.
// If we leave it as an unsigned short, CheckJNI will fail when the value exceeds 32767. The cast itself is
// fine because the Java code treats the value as unsigned even though it's stored in a signed type.
(*env)->CallStaticVoidMethod(env, GlobalBridgeClass, BridgeClRumbleTriggersMethod, controllerNumber, (short)leftTrigger, (short)rightTrigger);
if ((*env)->ExceptionCheck(env)) {
// We will crash here
(*JVM)->DetachCurrentThread(JVM);
}
}
void BridgeClSetMotionEventState(uint16_t controllerNumber, uint8_t motionType, uint16_t reportRateHz) {
JNIEnv* env = GetThreadEnv();
(*env)->CallStaticVoidMethod(env, GlobalBridgeClass, BridgeClSetMotionEventStateMethod, controllerNumber, motionType, reportRateHz);
if ((*env)->ExceptionCheck(env)) {
// We will crash here
(*JVM)->DetachCurrentThread(JVM);
}
}
void BridgeClLogMessage(const char* format, ...) {
va_list va;
va_start(va, format);
__android_log_vprint(ANDROID_LOG_INFO, "moonlight-common-c", format, va);
va_end(va);
}
static DECODER_RENDERER_CALLBACKS BridgeVideoRendererCallbacks = {
.setup = BridgeDrSetup,
.start = BridgeDrStart,
.stop = BridgeDrStop,
.cleanup = BridgeDrCleanup,
.submitDecodeUnit = BridgeDrSubmitDecodeUnit,
};
static AUDIO_RENDERER_CALLBACKS BridgeAudioRendererCallbacks = {
.init = BridgeArInit,
.start = BridgeArStart,
.stop = BridgeArStop,
.cleanup = BridgeArCleanup,
.decodeAndPlaySample = BridgeArDecodeAndPlaySample,
.capabilities = CAPABILITY_SUPPORTS_ARBITRARY_AUDIO_DURATION
};
static CONNECTION_LISTENER_CALLBACKS BridgeConnListenerCallbacks = {
.stageStarting = BridgeClStageStarting,
.stageComplete = BridgeClStageComplete,
.stageFailed = BridgeClStageFailed,
.connectionStarted = BridgeClConnectionStarted,
.connectionTerminated = BridgeClConnectionTerminated,
.logMessage = BridgeClLogMessage,
.rumble = BridgeClRumble,
.connectionStatusUpdate = BridgeClConnectionStatusUpdate,
.setHdrMode = BridgeClSetHdrMode,
.rumbleTriggers = BridgeClRumbleTriggers,
.setMotionEventState = BridgeClSetMotionEventState,
};
JNIEXPORT jint JNICALL
Java_com_limelight_nvstream_jni_MoonBridge_startConnection(JNIEnv *env, jclass clazz,
jstring address, jstring appVersion, jstring gfeVersion,
jstring rtspSessionUrl,
jint width, jint height, jint fps,
jint bitrate, jint packetSize, jint streamingRemotely,
jint audioConfiguration, jboolean supportsHevc,
jboolean enableHdr,
jint hevcBitratePercentageMultiplier,
jint clientRefreshRateX100,
jint encryptionFlags,
jbyteArray riAesKey, jbyteArray riAesIv,
jint videoCapabilities,
jint colorSpace, jint colorRange) {
SERVER_INFORMATION serverInfo = {
.address = (*env)->GetStringUTFChars(env, address, 0),
.serverInfoAppVersion = (*env)->GetStringUTFChars(env, appVersion, 0),
.serverInfoGfeVersion = gfeVersion ? (*env)->GetStringUTFChars(env, gfeVersion, 0) : NULL,
.rtspSessionUrl = rtspSessionUrl ? (*env)->GetStringUTFChars(env, rtspSessionUrl, 0) : NULL,
};
STREAM_CONFIGURATION streamConfig = {
.width = width,
.height = height,
.fps = fps,
.bitrate = bitrate,
.packetSize = packetSize,
.streamingRemotely = streamingRemotely,
.audioConfiguration = audioConfiguration,
.supportsHevc = supportsHevc,
.enableHdr = enableHdr,
.hevcBitratePercentageMultiplier = hevcBitratePercentageMultiplier,
.clientRefreshRateX100 = clientRefreshRateX100,
.encryptionFlags = encryptionFlags,
.colorSpace = colorSpace,
.colorRange = colorRange
};
jbyte* riAesKeyBuf = (*env)->GetByteArrayElements(env, riAesKey, NULL);
memcpy(streamConfig.remoteInputAesKey, riAesKeyBuf, sizeof(streamConfig.remoteInputAesKey));
(*env)->ReleaseByteArrayElements(env, riAesKey, riAesKeyBuf, JNI_ABORT);
jbyte* riAesIvBuf = (*env)->GetByteArrayElements(env, riAesIv, NULL);
memcpy(streamConfig.remoteInputAesIv, riAesIvBuf, sizeof(streamConfig.remoteInputAesIv));
(*env)->ReleaseByteArrayElements(env, riAesIv, riAesIvBuf, JNI_ABORT);
BridgeVideoRendererCallbacks.capabilities = videoCapabilities;
int ret = LiStartConnection(&serverInfo,
&streamConfig,
&BridgeConnListenerCallbacks,
&BridgeVideoRendererCallbacks,
&BridgeAudioRendererCallbacks,
NULL, 0,
NULL, 0);
(*env)->ReleaseStringUTFChars(env, address, serverInfo.address);
(*env)->ReleaseStringUTFChars(env, appVersion, serverInfo.serverInfoAppVersion);
if (gfeVersion != NULL) {
(*env)->ReleaseStringUTFChars(env, gfeVersion, serverInfo.serverInfoGfeVersion);
}
if (rtspSessionUrl != NULL) {
(*env)->ReleaseStringUTFChars(env, rtspSessionUrl, serverInfo.rtspSessionUrl);
}
return ret;
}