• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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