Files
moonlight-android/app/src/main/jni/moonlight-core/callbacks.c
T
2019-08-20 18:51:13 -07:00

453 lines
16 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 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", "([BIIIJ)I");
BridgeArInitMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeArInit", "(I)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", "(IJ)V");
BridgeClConnectionStartedMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeClConnectionStarted", "()V");
BridgeClConnectionTerminatedMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeClConnectionTerminated", "(J)V");
BridgeClRumbleMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeClRumble", "(SSS)V");
BridgeClConnectionStatusUpdateMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeClConnectionStatusUpdate", "(I)V");
}
int BridgeDrSetup(int videoFormat, int width, int height, int redrawRate, void* context, int drFlags) {
JNIEnv* env = GetThreadEnv();
int err;
if ((*env)->ExceptionCheck(env)) {
return -1;
}
err = (*env)->CallStaticIntMethod(env, GlobalBridgeClass, BridgeDrSetupMethod, videoFormat, width, height, redrawRate);
if ((*env)->ExceptionCheck(env)) {
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();
if ((*env)->ExceptionCheck(env)) {
return;
}
(*env)->CallStaticVoidMethod(env, GlobalBridgeClass, BridgeDrStartMethod);
}
void BridgeDrStop(void) {
JNIEnv* env = GetThreadEnv();
if ((*env)->ExceptionCheck(env)) {
return;
}
(*env)->CallStaticVoidMethod(env, GlobalBridgeClass, BridgeDrStopMethod);
}
void BridgeDrCleanup(void) {
JNIEnv* env = GetThreadEnv();
(*env)->DeleteGlobalRef(env, DecodedFrameBuffer);
if ((*env)->ExceptionCheck(env)) {
return;
}
(*env)->CallStaticVoidMethod(env, GlobalBridgeClass, BridgeDrCleanupMethod);
}
int BridgeDrSubmitDecodeUnit(PDECODE_UNIT decodeUnit) {
JNIEnv* env = GetThreadEnv();
int ret;
if ((*env)->ExceptionCheck(env)) {
return DR_OK;
}
// 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->receiveTimeMs);
if ((*env)->ExceptionCheck(env)) {
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;
}
return (*env)->CallStaticIntMethod(env, GlobalBridgeClass, BridgeDrSubmitDecodeUnitMethod,
DecodedFrameBuffer, offset, BUFFER_TYPE_PICDATA,
decodeUnit->frameNumber,
decodeUnit->receiveTimeMs);
}
int BridgeArInit(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURATION opusConfig, void* context, int flags) {
JNIEnv* env = GetThreadEnv();
int err;
if ((*env)->ExceptionCheck(env)) {
return -1;
}
err = (*env)->CallStaticIntMethod(env, GlobalBridgeClass, BridgeArInitMethod, audioConfiguration);
if ((*env)->ExceptionCheck(env)) {
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();
if ((*env)->ExceptionCheck(env)) {
return;
}
(*env)->CallStaticVoidMethod(env, GlobalBridgeClass, BridgeArStartMethod);
}
void BridgeArStop(void) {
JNIEnv* env = GetThreadEnv();
if ((*env)->ExceptionCheck(env)) {
return;
}
(*env)->CallStaticVoidMethod(env, GlobalBridgeClass, BridgeArStopMethod);
}
void BridgeArCleanup() {
JNIEnv* env = GetThreadEnv();
opus_multistream_decoder_destroy(Decoder);
(*env)->DeleteGlobalRef(env, DecodedAudioBuffer);
if ((*env)->ExceptionCheck(env)) {
return;
}
(*env)->CallStaticVoidMethod(env, GlobalBridgeClass, BridgeArCleanupMethod);
}
void BridgeArDecodeAndPlaySample(char* sampleData, int sampleLength) {
JNIEnv* env = GetThreadEnv();
if ((*env)->ExceptionCheck(env)) {
return;
}
jshort* decodedData = (*env)->GetShortArrayElements(env, DecodedAudioBuffer, 0);
int decodeLen = opus_multistream_decode(Decoder,
(const unsigned char*)sampleData,
sampleLength,
decodedData,
OpusConfig.samplesPerFrame,
0);
if (decodeLen > 0) {
// We must release the array elements first to ensure the data is copied before the callback
(*env)->ReleaseShortArrayElements(env, DecodedAudioBuffer, decodedData, 0);
(*env)->CallStaticVoidMethod(env, GlobalBridgeClass, BridgeArPlaySampleMethod, DecodedAudioBuffer);
}
else {
// We can abort here to avoid the copy back since no data was modified
(*env)->ReleaseShortArrayElements(env, DecodedAudioBuffer, decodedData, JNI_ABORT);
}
}
void BridgeClStageStarting(int stage) {
JNIEnv* env = GetThreadEnv();
if ((*env)->ExceptionCheck(env)) {
return;
}
(*env)->CallStaticVoidMethod(env, GlobalBridgeClass, BridgeClStageStartingMethod, stage);
}
void BridgeClStageComplete(int stage) {
JNIEnv* env = GetThreadEnv();
if ((*env)->ExceptionCheck(env)) {
return;
}
(*env)->CallStaticVoidMethod(env, GlobalBridgeClass, BridgeClStageCompleteMethod, stage);
}
void BridgeClStageFailed(int stage, long errorCode) {
JNIEnv* env = GetThreadEnv();
if ((*env)->ExceptionCheck(env)) {
return;
}
(*env)->CallStaticVoidMethod(env, GlobalBridgeClass, BridgeClStageFailedMethod, stage, errorCode);
}
void BridgeClConnectionStarted(void) {
JNIEnv* env = GetThreadEnv();
if ((*env)->ExceptionCheck(env)) {
return;
}
(*env)->CallStaticVoidMethod(env, GlobalBridgeClass, BridgeClConnectionStartedMethod, NULL);
}
void BridgeClConnectionTerminated(long errorCode) {
JNIEnv* env = GetThreadEnv();
if ((*env)->ExceptionCheck(env)) {
return;
}
(*env)->CallStaticVoidMethod(env, GlobalBridgeClass, BridgeClConnectionTerminatedMethod, errorCode);
}
void BridgeClRumble(unsigned short controllerNumber, unsigned short lowFreqMotor, unsigned short highFreqMotor) {
JNIEnv* env = GetThreadEnv();
if ((*env)->ExceptionCheck(env)) {
return;
}
(*env)->CallStaticVoidMethod(env, GlobalBridgeClass, BridgeClRumbleMethod, controllerNumber, lowFreqMotor, highFreqMotor);
}
void BridgeClConnectionStatusUpdate(int connectionStatus) {
JNIEnv* env = GetThreadEnv();
if ((*env)->ExceptionCheck(env)) {
return;
}
(*env)->CallStaticVoidMethod(env, GlobalBridgeClass, BridgeClConnectionStatusUpdateMethod, connectionStatus);
}
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,
};
static CONNECTION_LISTENER_CALLBACKS BridgeConnListenerCallbacks = {
.stageStarting = BridgeClStageStarting,
.stageComplete = BridgeClStageComplete,
.stageFailed = BridgeClStageFailed,
.connectionStarted = BridgeClConnectionStarted,
.connectionTerminated = BridgeClConnectionTerminated,
.logMessage = BridgeClLogMessage,
.rumble = BridgeClRumble,
.connectionStatusUpdate = BridgeClConnectionStatusUpdate,
};
JNIEXPORT jint JNICALL
Java_com_limelight_nvstream_jni_MoonBridge_startConnection(JNIEnv *env, jclass clazz,
jstring address, jstring appVersion, jstring gfeVersion,
jint width, jint height, jint fps,
jint bitrate, jint packetSize, jint streamingRemotely,
jint audioConfiguration, jboolean supportsHevc,
jboolean enableHdr,
jint hevcBitratePercentageMultiplier,
jint clientRefreshRateX100,
jbyteArray riAesKey, jbyteArray riAesIv,
jint videoCapabilities) {
SERVER_INFORMATION serverInfo = {
.address = (*env)->GetStringUTFChars(env, address, 0),
.serverInfoAppVersion = (*env)->GetStringUTFChars(env, appVersion, 0),
.serverInfoGfeVersion = gfeVersion ? (*env)->GetStringUTFChars(env, gfeVersion, 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
};
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);
}
return ret;
}