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
14 extern "C" {
15 JNIEXPORT jstring JNICALL Java_com_example_android_nativemididemo_NativeMidi_initAudio(
16 JNIEnv* env, jobject thiz, jint sampleRate, jint playSamples);
17 JNIEXPORT void JNICALL Java_com_example_android_nativemididemo_NativeMidi_pauseAudio(
18 JNIEnv* env, jobject thiz);
19 JNIEXPORT void JNICALL Java_com_example_android_nativemididemo_NativeMidi_resumeAudio(
20 JNIEnv* env, jobject thiz);
21 JNIEXPORT void JNICALL Java_com_example_android_nativemididemo_NativeMidi_shutdownAudio(
22 JNIEnv* env, jobject thiz);
23 JNIEXPORT jlong JNICALL Java_com_example_android_nativemididemo_NativeMidi_getPlaybackCounter(
24 JNIEnv* env, jobject thiz);
25 JNIEXPORT jobjectArray JNICALL Java_com_example_android_nativemididemo_NativeMidi_getRecentMessages(
26 JNIEnv* env, jobject thiz);
27 JNIEXPORT void JNICALL Java_com_example_android_nativemididemo_NativeMidi_startReadingMidi(
28 JNIEnv* env, jobject thiz, jint deviceId, jint portNumber);
29 JNIEXPORT void JNICALL Java_com_example_android_nativemididemo_NativeMidi_stopReadingMidi(
30 JNIEnv* env, jobject thiz);
31 }
32
33 static 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
getSLErrStr(int code)51 static const char* getSLErrStr(int code) {
52 return errStrings[code];
53 }
54
55 static SLObjectItf engineObject;
56 static SLEngineItf engineEngine;
57 static SLObjectItf outputMixObject;
58 static SLObjectItf playerObject;
59 static SLPlayItf playerPlay;
60 static SLAndroidSimpleBufferQueueItf playerBufferQueue;
61
62 static const int minPlaySamples = 32;
63 static const int maxPlaySamples = 1000;
64 static std::atomic_int playSamples(maxPlaySamples);
65 static short playBuffer[maxPlaySamples];
66
67 static std::atomic_ullong sharedCounter;
68
69 static AMIDI_Device* midiDevice = AMIDI_INVALID_HANDLE;
70 static std::atomic<AMIDI_OutputPort*> midiOutputPort(AMIDI_INVALID_HANDLE);
71
setPlaySamples(int newPlaySamples)72 static 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.
81 static const size_t MAX_INCOMING_MIDI_MESSAGES = 20;
82 // Static allocation to save time in the callback.
83 static AMIDI_Message incomingMidiMessages[MAX_INCOMING_MIDI_MESSAGES];
84
bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq,void *)85 static 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
Java_com_example_android_nativemididemo_NativeMidi_initAudio(JNIEnv * env,jobject,jint sampleRate,jint playSamples)124 jstring 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
187 handle_error:
188 snprintf(printBuffer, sizeof(printBuffer), "Error at %s: %s", stage, getSLErrStr(result));
189 return env->NewStringUTF(printBuffer);
190 }
191
Java_com_example_android_nativemididemo_NativeMidi_pauseAudio(JNIEnv *,jobject)192 void Java_com_example_android_nativemididemo_NativeMidi_pauseAudio(
193 JNIEnv*, jobject) {
194 if (playerPlay != NULL) {
195 (*playerPlay)->SetPlayState(playerPlay, SL_PLAYSTATE_PAUSED);
196 }
197 }
198
Java_com_example_android_nativemididemo_NativeMidi_resumeAudio(JNIEnv *,jobject)199 void 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
Java_com_example_android_nativemididemo_NativeMidi_shutdownAudio(JNIEnv *,jobject)207 void 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
Java_com_example_android_nativemididemo_NativeMidi_getPlaybackCounter(JNIEnv *,jobject)228 jlong Java_com_example_android_nativemididemo_NativeMidi_getPlaybackCounter(JNIEnv*, jobject) {
229 return sharedCounter.load();
230 }
231
Java_com_example_android_nativemididemo_NativeMidi_getRecentMessages(JNIEnv * env,jobject thiz)232 jobjectArray Java_com_example_android_nativemididemo_NativeMidi_getRecentMessages(
233 JNIEnv* env, jobject thiz) {
234 return nativemididemo::getRecentMessagesForJava(env, thiz);
235 }
236
Java_com_example_android_nativemididemo_NativeMidi_startReadingMidi(JNIEnv *,jobject,jlong deviceHandle,jint portNumber)237 void 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
Java_com_example_android_nativemididemo_NativeMidi_stopReadingMidi(JNIEnv *,jobject)274 void 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