1#include <atomic> 2#include <inttypes.h> 3#include <stdio.h> 4#include <string.h> 5 6#include <jni.h> 7 8#include <midi/midi.h> 9#include <SLES/OpenSLES.h> 10#include <SLES/OpenSLES_Android.h> 11 12#include "messagequeue.h" 13 14extern "C" { 15JNIEXPORT jstring JNICALL Java_com_example_android_nativemididemo_NativeMidi_initAudio( 16 JNIEnv* env, jobject thiz, jint sampleRate, jint playSamples); 17JNIEXPORT void JNICALL Java_com_example_android_nativemididemo_NativeMidi_pauseAudio( 18 JNIEnv* env, jobject thiz); 19JNIEXPORT void JNICALL Java_com_example_android_nativemididemo_NativeMidi_resumeAudio( 20 JNIEnv* env, jobject thiz); 21JNIEXPORT void JNICALL Java_com_example_android_nativemididemo_NativeMidi_shutdownAudio( 22 JNIEnv* env, jobject thiz); 23JNIEXPORT jlong JNICALL Java_com_example_android_nativemididemo_NativeMidi_getPlaybackCounter( 24 JNIEnv* env, jobject thiz); 25JNIEXPORT jobjectArray JNICALL Java_com_example_android_nativemididemo_NativeMidi_getRecentMessages( 26 JNIEnv* env, jobject thiz); 27JNIEXPORT void JNICALL Java_com_example_android_nativemididemo_NativeMidi_startReadingMidi( 28 JNIEnv* env, jobject thiz, jint deviceId, jint portNumber); 29JNIEXPORT void JNICALL Java_com_example_android_nativemididemo_NativeMidi_stopReadingMidi( 30 JNIEnv* env, jobject thiz); 31} 32 33static const char* errStrings[] = { 34 "SL_RESULT_SUCCESS", // 0 35 "SL_RESULT_PRECONDITIONS_VIOLATED", // 1 36 "SL_RESULT_PARAMETER_INVALID", // 2 37 "SL_RESULT_MEMORY_FAILURE", // 3 38 "SL_RESULT_RESOURCE_ERROR", // 4 39 "SL_RESULT_RESOURCE_LOST", // 5 40 "SL_RESULT_IO_ERROR", // 6 41 "SL_RESULT_BUFFER_INSUFFICIENT", // 7 42 "SL_RESULT_CONTENT_CORRUPTED", // 8 43 "SL_RESULT_CONTENT_UNSUPPORTED", // 9 44 "SL_RESULT_CONTENT_NOT_FOUND", // 10 45 "SL_RESULT_PERMISSION_DENIED", // 11 46 "SL_RESULT_FEATURE_UNSUPPORTED", // 12 47 "SL_RESULT_INTERNAL_ERROR", // 13 48 "SL_RESULT_UNKNOWN_ERROR", // 14 49 "SL_RESULT_OPERATION_ABORTED", // 15 50 "SL_RESULT_CONTROL_LOST" }; // 16 51static const char* getSLErrStr(int code) { 52 return errStrings[code]; 53} 54 55static SLObjectItf engineObject; 56static SLEngineItf engineEngine; 57static SLObjectItf outputMixObject; 58static SLObjectItf playerObject; 59static SLPlayItf playerPlay; 60static SLAndroidSimpleBufferQueueItf playerBufferQueue; 61 62static const int minPlaySamples = 32; 63static const int maxPlaySamples = 1000; 64static std::atomic_int playSamples(maxPlaySamples); 65static short playBuffer[maxPlaySamples]; 66 67static std::atomic_ullong sharedCounter; 68 69static AMIDI_Device* midiDevice = AMIDI_INVALID_HANDLE; 70static std::atomic<AMIDI_OutputPort*> midiOutputPort(AMIDI_INVALID_HANDLE); 71 72static int setPlaySamples(int newPlaySamples) 73{ 74 if (newPlaySamples < minPlaySamples) newPlaySamples = minPlaySamples; 75 if (newPlaySamples > maxPlaySamples) newPlaySamples = maxPlaySamples; 76 playSamples.store(newPlaySamples); 77 return newPlaySamples; 78} 79 80// Amount of messages we are ready to handle during one callback cycle. 81static const size_t MAX_INCOMING_MIDI_MESSAGES = 20; 82// Static allocation to save time in the callback. 83static AMIDI_Message incomingMidiMessages[MAX_INCOMING_MIDI_MESSAGES]; 84 85static void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void */*context*/) 86{ 87 sharedCounter++; 88 89 AMIDI_OutputPort* outputPort = midiOutputPort.load(); 90 if (outputPort != AMIDI_INVALID_HANDLE) { 91 char midiDumpBuffer[1024]; 92 ssize_t midiReceived = AMIDI_receive( 93 outputPort, incomingMidiMessages, MAX_INCOMING_MIDI_MESSAGES); 94 if (midiReceived >= 0) { 95 for (ssize_t i = 0; i < midiReceived; ++i) { 96 AMIDI_Message* msg = &incomingMidiMessages[i]; 97 if (msg->opcode == AMIDI_OPCODE_DATA) { 98 memset(midiDumpBuffer, 0, sizeof(midiDumpBuffer)); 99 int pos = snprintf(midiDumpBuffer, sizeof(midiDumpBuffer), 100 "%" PRIx64 " ", msg->timestamp); 101 for (uint8_t *b = msg->buffer, *e = b + msg->len; b < e; ++b) { 102 pos += snprintf(midiDumpBuffer + pos, sizeof(midiDumpBuffer) - pos, 103 "%02x ", *b); 104 } 105 nativemididemo::writeMessage(midiDumpBuffer); 106 } else if (msg->opcode == AMIDI_OPCODE_FLUSH) { 107 nativemididemo::writeMessage("MIDI flush"); 108 } 109 } 110 } else { 111 snprintf(midiDumpBuffer, sizeof(midiDumpBuffer), 112 "! MIDI Receive error: %s !", strerror(-midiReceived)); 113 nativemididemo::writeMessage(midiDumpBuffer); 114 } 115 } 116 117 size_t usedBufferSize = playSamples.load() * sizeof(playBuffer[0]); 118 if (usedBufferSize > sizeof(playBuffer)) { 119 usedBufferSize = sizeof(playBuffer); 120 } 121 (*bq)->Enqueue(bq, playBuffer, usedBufferSize); 122} 123 124jstring Java_com_example_android_nativemididemo_NativeMidi_initAudio( 125 JNIEnv* env, jobject, jint sampleRate, jint playSamples) { 126 const char* stage; 127 SLresult result; 128 char printBuffer[1024]; 129 130 playSamples = setPlaySamples(playSamples); 131 132 result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL); 133 if (SL_RESULT_SUCCESS != result) { stage = "slCreateEngine"; goto handle_error; } 134 135 result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE); 136 if (SL_RESULT_SUCCESS != result) { stage = "realize Engine object"; goto handle_error; } 137 138 result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine); 139 if (SL_RESULT_SUCCESS != result) { stage = "get Engine interface"; goto handle_error; } 140 141 result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 0, NULL, NULL); 142 if (SL_RESULT_SUCCESS != result) { stage = "CreateOutputMix"; goto handle_error; } 143 144 result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE); 145 if (SL_RESULT_SUCCESS != result) { stage = "realize OutputMix object"; goto handle_error; } 146 147 { 148 SLDataFormat_PCM format_pcm = { SL_DATAFORMAT_PCM, 1, (SLuint32)sampleRate * 1000, 149 SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16, 150 SL_SPEAKER_FRONT_LEFT, SL_BYTEORDER_LITTLEENDIAN }; 151 SLDataLocator_AndroidSimpleBufferQueue loc_bufq = 152 { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 1 }; 153 SLDataSource audioSrc = { &loc_bufq, &format_pcm }; 154 SLDataLocator_OutputMix loc_outmix = { SL_DATALOCATOR_OUTPUTMIX, outputMixObject }; 155 SLDataSink audioSnk = { &loc_outmix, NULL }; 156 const SLInterfaceID ids[1] = { SL_IID_BUFFERQUEUE }; 157 const SLboolean req[1] = { SL_BOOLEAN_TRUE }; 158 result = (*engineEngine)->CreateAudioPlayer( 159 engineEngine, &playerObject, &audioSrc, &audioSnk, 1, ids, req); 160 if (SL_RESULT_SUCCESS != result) { stage = "CreateAudioPlayer"; goto handle_error; } 161 162 result = (*playerObject)->Realize(playerObject, SL_BOOLEAN_FALSE); 163 if (SL_RESULT_SUCCESS != result) { stage = "realize Player object"; goto handle_error; } 164 } 165 166 result = (*playerObject)->GetInterface(playerObject, SL_IID_PLAY, &playerPlay); 167 if (SL_RESULT_SUCCESS != result) { stage = "get Play interface"; goto handle_error; } 168 169 result = (*playerObject)->GetInterface(playerObject, SL_IID_BUFFERQUEUE, &playerBufferQueue); 170 if (SL_RESULT_SUCCESS != result) { stage = "get BufferQueue interface"; goto handle_error; } 171 172 result = (*playerBufferQueue)->RegisterCallback(playerBufferQueue, bqPlayerCallback, NULL); 173 if (SL_RESULT_SUCCESS != result) { stage = "register BufferQueue callback"; goto handle_error; } 174 175 result = (*playerBufferQueue)->Enqueue(playerBufferQueue, playBuffer, sizeof(playBuffer)); 176 if (SL_RESULT_SUCCESS != result) { 177 stage = "enqueue into PlayerBufferQueue"; goto handle_error; } 178 179 result = (*playerPlay)->SetPlayState(playerPlay, SL_PLAYSTATE_PLAYING); 180 if (SL_RESULT_SUCCESS != result) { 181 stage = "SetPlayState(SL_PLAYSTATE_PLAYING)"; goto handle_error; } 182 183 snprintf(printBuffer, sizeof(printBuffer), 184 "Success, sample rate %d, buffer samples %d", sampleRate, playSamples); 185 return env->NewStringUTF(printBuffer); 186 187handle_error: 188 snprintf(printBuffer, sizeof(printBuffer), "Error at %s: %s", stage, getSLErrStr(result)); 189 return env->NewStringUTF(printBuffer); 190} 191 192void Java_com_example_android_nativemididemo_NativeMidi_pauseAudio( 193 JNIEnv*, jobject) { 194 if (playerPlay != NULL) { 195 (*playerPlay)->SetPlayState(playerPlay, SL_PLAYSTATE_PAUSED); 196 } 197} 198 199void Java_com_example_android_nativemididemo_NativeMidi_resumeAudio( 200 JNIEnv*, jobject) { 201 if (playerBufferQueue != NULL && playerPlay != NULL) { 202 (*playerBufferQueue)->Enqueue(playerBufferQueue, playBuffer, sizeof(playBuffer)); 203 (*playerPlay)->SetPlayState(playerPlay, SL_PLAYSTATE_PLAYING); 204 } 205} 206 207void Java_com_example_android_nativemididemo_NativeMidi_shutdownAudio( 208 JNIEnv*, jobject) { 209 if (playerObject != NULL) { 210 (*playerObject)->Destroy(playerObject); 211 playerObject = NULL; 212 playerPlay = NULL; 213 playerBufferQueue = NULL; 214 } 215 216 if (outputMixObject != NULL) { 217 (*outputMixObject)->Destroy(outputMixObject); 218 outputMixObject = NULL; 219 } 220 221 if (engineObject != NULL) { 222 (*engineObject)->Destroy(engineObject); 223 engineObject = NULL; 224 engineEngine = NULL; 225 } 226} 227 228jlong Java_com_example_android_nativemididemo_NativeMidi_getPlaybackCounter(JNIEnv*, jobject) { 229 return sharedCounter.load(); 230} 231 232jobjectArray Java_com_example_android_nativemididemo_NativeMidi_getRecentMessages( 233 JNIEnv* env, jobject thiz) { 234 return nativemididemo::getRecentMessagesForJava(env, thiz); 235} 236 237void Java_com_example_android_nativemididemo_NativeMidi_startReadingMidi( 238 JNIEnv*, jobject, jlong deviceHandle, jint portNumber) { 239 char buffer[1024]; 240 241 midiDevice = (AMIDI_Device*)deviceHandle; 242// int result = AMIDI_getDeviceById(deviceId, &midiDevice); 243// if (result == 0) { 244// snprintf(buffer, sizeof(buffer), "Obtained device token for uid %d: token %d", deviceId, midiDevice); 245// } else { 246// snprintf(buffer, sizeof(buffer), "Could not obtain device token for uid %d: %d", deviceId, result); 247// } 248 nativemididemo::writeMessage(buffer); 249// if (result) return; 250 251 AMIDI_DeviceInfo deviceInfo; 252 int result = AMIDI_getDeviceInfo(midiDevice, &deviceInfo); 253 if (result == 0) { 254 snprintf(buffer, sizeof(buffer), "Device info: uid %d, type %d, priv %d, ports %d I / %d O", 255 deviceInfo.uid, deviceInfo.type, deviceInfo.isPrivate, 256 (int)deviceInfo.inputPortCount, (int)deviceInfo.outputPortCount); 257 } else { 258 snprintf(buffer, sizeof(buffer), "Could not obtain device info %d", result); 259 } 260 nativemididemo::writeMessage(buffer); 261 if (result) return; 262 263 AMIDI_OutputPort* outputPort; 264 result = AMIDI_openOutputPort(midiDevice, portNumber, &outputPort); 265 if (result == 0) { 266 snprintf(buffer, sizeof(buffer), "Opened port %d: token %p", portNumber, outputPort); 267 midiOutputPort.store(outputPort); 268 } else { 269 snprintf(buffer, sizeof(buffer), "Could not open port %p: %d", midiDevice, result); 270 } 271 nativemididemo::writeMessage(buffer); 272} 273 274void Java_com_example_android_nativemididemo_NativeMidi_stopReadingMidi( 275 JNIEnv*, jobject) { 276 AMIDI_OutputPort* outputPort = midiOutputPort.exchange(AMIDI_INVALID_HANDLE); 277 if (outputPort == AMIDI_INVALID_HANDLE) return; 278 int result = AMIDI_closeOutputPort(outputPort); 279 char buffer[1024]; 280 if (result == 0) { 281 snprintf(buffer, sizeof(buffer), "Closed port by token %p", outputPort); 282 } else { 283 snprintf(buffer, sizeof(buffer), "Could not close port by token %p: %d", outputPort, result); 284 } 285 nativemididemo::writeMessage(buffer); 286} 287