| #include <atomic> |
| #include <inttypes.h> |
| #include <stdio.h> |
| #include <string.h> |
| |
| #include <jni.h> |
| |
| #include <midi/midi.h> |
| #include <SLES/OpenSLES.h> |
| #include <SLES/OpenSLES_Android.h> |
| |
| #include "messagequeue.h" |
| |
| extern "C" { |
| JNIEXPORT jstring JNICALL Java_com_example_android_nativemididemo_NativeMidi_initAudio( |
| JNIEnv* env, jobject thiz, jint sampleRate, jint playSamples); |
| JNIEXPORT void JNICALL Java_com_example_android_nativemididemo_NativeMidi_pauseAudio( |
| JNIEnv* env, jobject thiz); |
| JNIEXPORT void JNICALL Java_com_example_android_nativemididemo_NativeMidi_resumeAudio( |
| JNIEnv* env, jobject thiz); |
| JNIEXPORT void JNICALL Java_com_example_android_nativemididemo_NativeMidi_shutdownAudio( |
| JNIEnv* env, jobject thiz); |
| JNIEXPORT jlong JNICALL Java_com_example_android_nativemididemo_NativeMidi_getPlaybackCounter( |
| JNIEnv* env, jobject thiz); |
| JNIEXPORT jobjectArray JNICALL Java_com_example_android_nativemididemo_NativeMidi_getRecentMessages( |
| JNIEnv* env, jobject thiz); |
| JNIEXPORT void JNICALL Java_com_example_android_nativemididemo_NativeMidi_startReadingMidi( |
| JNIEnv* env, jobject thiz, jint deviceId, jint portNumber); |
| JNIEXPORT void JNICALL Java_com_example_android_nativemididemo_NativeMidi_stopReadingMidi( |
| JNIEnv* env, jobject thiz); |
| } |
| |
| static const char* errStrings[] = { |
| "SL_RESULT_SUCCESS", // 0 |
| "SL_RESULT_PRECONDITIONS_VIOLATED", // 1 |
| "SL_RESULT_PARAMETER_INVALID", // 2 |
| "SL_RESULT_MEMORY_FAILURE", // 3 |
| "SL_RESULT_RESOURCE_ERROR", // 4 |
| "SL_RESULT_RESOURCE_LOST", // 5 |
| "SL_RESULT_IO_ERROR", // 6 |
| "SL_RESULT_BUFFER_INSUFFICIENT", // 7 |
| "SL_RESULT_CONTENT_CORRUPTED", // 8 |
| "SL_RESULT_CONTENT_UNSUPPORTED", // 9 |
| "SL_RESULT_CONTENT_NOT_FOUND", // 10 |
| "SL_RESULT_PERMISSION_DENIED", // 11 |
| "SL_RESULT_FEATURE_UNSUPPORTED", // 12 |
| "SL_RESULT_INTERNAL_ERROR", // 13 |
| "SL_RESULT_UNKNOWN_ERROR", // 14 |
| "SL_RESULT_OPERATION_ABORTED", // 15 |
| "SL_RESULT_CONTROL_LOST" }; // 16 |
| static const char* getSLErrStr(int code) { |
| return errStrings[code]; |
| } |
| |
| static SLObjectItf engineObject; |
| static SLEngineItf engineEngine; |
| static SLObjectItf outputMixObject; |
| static SLObjectItf playerObject; |
| static SLPlayItf playerPlay; |
| static SLAndroidSimpleBufferQueueItf playerBufferQueue; |
| |
| static const int minPlaySamples = 32; |
| static const int maxPlaySamples = 1000; |
| static std::atomic_int playSamples(maxPlaySamples); |
| static short playBuffer[maxPlaySamples]; |
| |
| static std::atomic_ullong sharedCounter; |
| |
| static AMIDI_Device* midiDevice = AMIDI_INVALID_HANDLE; |
| static std::atomic<AMIDI_OutputPort*> midiOutputPort(AMIDI_INVALID_HANDLE); |
| |
| static int setPlaySamples(int newPlaySamples) |
| { |
| if (newPlaySamples < minPlaySamples) newPlaySamples = minPlaySamples; |
| if (newPlaySamples > maxPlaySamples) newPlaySamples = maxPlaySamples; |
| playSamples.store(newPlaySamples); |
| return newPlaySamples; |
| } |
| |
| // Amount of messages we are ready to handle during one callback cycle. |
| static const size_t MAX_INCOMING_MIDI_MESSAGES = 20; |
| // Static allocation to save time in the callback. |
| static AMIDI_Message incomingMidiMessages[MAX_INCOMING_MIDI_MESSAGES]; |
| |
| static void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void */*context*/) |
| { |
| sharedCounter++; |
| |
| AMIDI_OutputPort* outputPort = midiOutputPort.load(); |
| if (outputPort != AMIDI_INVALID_HANDLE) { |
| char midiDumpBuffer[1024]; |
| ssize_t midiReceived = AMIDI_receive( |
| outputPort, incomingMidiMessages, MAX_INCOMING_MIDI_MESSAGES); |
| if (midiReceived >= 0) { |
| for (ssize_t i = 0; i < midiReceived; ++i) { |
| AMIDI_Message* msg = &incomingMidiMessages[i]; |
| if (msg->opcode == AMIDI_OPCODE_DATA) { |
| memset(midiDumpBuffer, 0, sizeof(midiDumpBuffer)); |
| int pos = snprintf(midiDumpBuffer, sizeof(midiDumpBuffer), |
| "%" PRIx64 " ", msg->timestamp); |
| for (uint8_t *b = msg->buffer, *e = b + msg->len; b < e; ++b) { |
| pos += snprintf(midiDumpBuffer + pos, sizeof(midiDumpBuffer) - pos, |
| "%02x ", *b); |
| } |
| nativemididemo::writeMessage(midiDumpBuffer); |
| } else if (msg->opcode == AMIDI_OPCODE_FLUSH) { |
| nativemididemo::writeMessage("MIDI flush"); |
| } |
| } |
| } else { |
| snprintf(midiDumpBuffer, sizeof(midiDumpBuffer), |
| "! MIDI Receive error: %s !", strerror(-midiReceived)); |
| nativemididemo::writeMessage(midiDumpBuffer); |
| } |
| } |
| |
| size_t usedBufferSize = playSamples.load() * sizeof(playBuffer[0]); |
| if (usedBufferSize > sizeof(playBuffer)) { |
| usedBufferSize = sizeof(playBuffer); |
| } |
| (*bq)->Enqueue(bq, playBuffer, usedBufferSize); |
| } |
| |
| jstring Java_com_example_android_nativemididemo_NativeMidi_initAudio( |
| JNIEnv* env, jobject, jint sampleRate, jint playSamples) { |
| const char* stage; |
| SLresult result; |
| char printBuffer[1024]; |
| |
| playSamples = setPlaySamples(playSamples); |
| |
| result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL); |
| if (SL_RESULT_SUCCESS != result) { stage = "slCreateEngine"; goto handle_error; } |
| |
| result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE); |
| if (SL_RESULT_SUCCESS != result) { stage = "realize Engine object"; goto handle_error; } |
| |
| result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine); |
| if (SL_RESULT_SUCCESS != result) { stage = "get Engine interface"; goto handle_error; } |
| |
| result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 0, NULL, NULL); |
| if (SL_RESULT_SUCCESS != result) { stage = "CreateOutputMix"; goto handle_error; } |
| |
| result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE); |
| if (SL_RESULT_SUCCESS != result) { stage = "realize OutputMix object"; goto handle_error; } |
| |
| { |
| SLDataFormat_PCM format_pcm = { SL_DATAFORMAT_PCM, 1, (SLuint32)sampleRate * 1000, |
| SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16, |
| SL_SPEAKER_FRONT_LEFT, SL_BYTEORDER_LITTLEENDIAN }; |
| SLDataLocator_AndroidSimpleBufferQueue loc_bufq = |
| { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 1 }; |
| SLDataSource audioSrc = { &loc_bufq, &format_pcm }; |
| SLDataLocator_OutputMix loc_outmix = { SL_DATALOCATOR_OUTPUTMIX, outputMixObject }; |
| SLDataSink audioSnk = { &loc_outmix, NULL }; |
| const SLInterfaceID ids[1] = { SL_IID_BUFFERQUEUE }; |
| const SLboolean req[1] = { SL_BOOLEAN_TRUE }; |
| result = (*engineEngine)->CreateAudioPlayer( |
| engineEngine, &playerObject, &audioSrc, &audioSnk, 1, ids, req); |
| if (SL_RESULT_SUCCESS != result) { stage = "CreateAudioPlayer"; goto handle_error; } |
| |
| result = (*playerObject)->Realize(playerObject, SL_BOOLEAN_FALSE); |
| if (SL_RESULT_SUCCESS != result) { stage = "realize Player object"; goto handle_error; } |
| } |
| |
| result = (*playerObject)->GetInterface(playerObject, SL_IID_PLAY, &playerPlay); |
| if (SL_RESULT_SUCCESS != result) { stage = "get Play interface"; goto handle_error; } |
| |
| result = (*playerObject)->GetInterface(playerObject, SL_IID_BUFFERQUEUE, &playerBufferQueue); |
| if (SL_RESULT_SUCCESS != result) { stage = "get BufferQueue interface"; goto handle_error; } |
| |
| result = (*playerBufferQueue)->RegisterCallback(playerBufferQueue, bqPlayerCallback, NULL); |
| if (SL_RESULT_SUCCESS != result) { stage = "register BufferQueue callback"; goto handle_error; } |
| |
| result = (*playerBufferQueue)->Enqueue(playerBufferQueue, playBuffer, sizeof(playBuffer)); |
| if (SL_RESULT_SUCCESS != result) { |
| stage = "enqueue into PlayerBufferQueue"; goto handle_error; } |
| |
| result = (*playerPlay)->SetPlayState(playerPlay, SL_PLAYSTATE_PLAYING); |
| if (SL_RESULT_SUCCESS != result) { |
| stage = "SetPlayState(SL_PLAYSTATE_PLAYING)"; goto handle_error; } |
| |
| snprintf(printBuffer, sizeof(printBuffer), |
| "Success, sample rate %d, buffer samples %d", sampleRate, playSamples); |
| return env->NewStringUTF(printBuffer); |
| |
| handle_error: |
| snprintf(printBuffer, sizeof(printBuffer), "Error at %s: %s", stage, getSLErrStr(result)); |
| return env->NewStringUTF(printBuffer); |
| } |
| |
| void Java_com_example_android_nativemididemo_NativeMidi_pauseAudio( |
| JNIEnv*, jobject) { |
| if (playerPlay != NULL) { |
| (*playerPlay)->SetPlayState(playerPlay, SL_PLAYSTATE_PAUSED); |
| } |
| } |
| |
| void Java_com_example_android_nativemididemo_NativeMidi_resumeAudio( |
| JNIEnv*, jobject) { |
| if (playerBufferQueue != NULL && playerPlay != NULL) { |
| (*playerBufferQueue)->Enqueue(playerBufferQueue, playBuffer, sizeof(playBuffer)); |
| (*playerPlay)->SetPlayState(playerPlay, SL_PLAYSTATE_PLAYING); |
| } |
| } |
| |
| void Java_com_example_android_nativemididemo_NativeMidi_shutdownAudio( |
| JNIEnv*, jobject) { |
| if (playerObject != NULL) { |
| (*playerObject)->Destroy(playerObject); |
| playerObject = NULL; |
| playerPlay = NULL; |
| playerBufferQueue = NULL; |
| } |
| |
| if (outputMixObject != NULL) { |
| (*outputMixObject)->Destroy(outputMixObject); |
| outputMixObject = NULL; |
| } |
| |
| if (engineObject != NULL) { |
| (*engineObject)->Destroy(engineObject); |
| engineObject = NULL; |
| engineEngine = NULL; |
| } |
| } |
| |
| jlong Java_com_example_android_nativemididemo_NativeMidi_getPlaybackCounter(JNIEnv*, jobject) { |
| return sharedCounter.load(); |
| } |
| |
| jobjectArray Java_com_example_android_nativemididemo_NativeMidi_getRecentMessages( |
| JNIEnv* env, jobject thiz) { |
| return nativemididemo::getRecentMessagesForJava(env, thiz); |
| } |
| |
| void Java_com_example_android_nativemididemo_NativeMidi_startReadingMidi( |
| JNIEnv*, jobject, jlong deviceHandle, jint portNumber) { |
| char buffer[1024]; |
| |
| midiDevice = (AMIDI_Device*)deviceHandle; |
| // int result = AMIDI_getDeviceById(deviceId, &midiDevice); |
| // if (result == 0) { |
| // snprintf(buffer, sizeof(buffer), "Obtained device token for uid %d: token %d", deviceId, midiDevice); |
| // } else { |
| // snprintf(buffer, sizeof(buffer), "Could not obtain device token for uid %d: %d", deviceId, result); |
| // } |
| nativemididemo::writeMessage(buffer); |
| // if (result) return; |
| |
| AMIDI_DeviceInfo deviceInfo; |
| int result = AMIDI_getDeviceInfo(midiDevice, &deviceInfo); |
| if (result == 0) { |
| snprintf(buffer, sizeof(buffer), "Device info: uid %d, type %d, priv %d, ports %d I / %d O", |
| deviceInfo.uid, deviceInfo.type, deviceInfo.isPrivate, |
| (int)deviceInfo.inputPortCount, (int)deviceInfo.outputPortCount); |
| } else { |
| snprintf(buffer, sizeof(buffer), "Could not obtain device info %d", result); |
| } |
| nativemididemo::writeMessage(buffer); |
| if (result) return; |
| |
| AMIDI_OutputPort* outputPort; |
| result = AMIDI_openOutputPort(midiDevice, portNumber, &outputPort); |
| if (result == 0) { |
| snprintf(buffer, sizeof(buffer), "Opened port %d: token %p", portNumber, outputPort); |
| midiOutputPort.store(outputPort); |
| } else { |
| snprintf(buffer, sizeof(buffer), "Could not open port %p: %d", midiDevice, result); |
| } |
| nativemididemo::writeMessage(buffer); |
| } |
| |
| void Java_com_example_android_nativemididemo_NativeMidi_stopReadingMidi( |
| JNIEnv*, jobject) { |
| AMIDI_OutputPort* outputPort = midiOutputPort.exchange(AMIDI_INVALID_HANDLE); |
| if (outputPort == AMIDI_INVALID_HANDLE) return; |
| int result = AMIDI_closeOutputPort(outputPort); |
| char buffer[1024]; |
| if (result == 0) { |
| snprintf(buffer, sizeof(buffer), "Closed port by token %p", outputPort); |
| } else { |
| snprintf(buffer, sizeof(buffer), "Could not close port by token %p: %d", outputPort, result); |
| } |
| nativemididemo::writeMessage(buffer); |
| } |