1 /*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <atomic>
18 #include <inttypes.h>
19 #include <stdio.h>
20 #include <string>
21 #include <thread>
22 #include <time.h>
23 #include <vector>
24
25 //#define LOG_NDEBUG 0
26 #define LOG_TAG "NativeMidiManager-JNI"
27 #include <android/log.h>
28
29 #include <jni.h>
30
31 #include <amidi/AMidi.h>
32
33 extern "C" {
34
35 /*
36 * Structures for storing data flowing through the echo server.
37 */
38 #define SIZE_DATABUFFER 256
39 /*
40 * Received Messages
41 */
42 typedef struct {
43 std::unique_ptr<uint8_t[]> dataBuff;
44 size_t numDataBytes;
45 int32_t opCode;
46 int64_t timestamp;
47 int64_t timeReceived;
48 } ReceivedMessageRecord;
49
50 /*
51 * Sent Messages
52 */
53 typedef struct {
54 std::unique_ptr<uint8_t[]> dataBuff;
55 size_t numDataBytes;
56 int64_t timestamp;
57 long timeSent;
58 } SentMessageRecord;
59
60 /*
61 * Context
62 * Holds the state of a given test and native MIDI I/O setup for that test.
63 * NOTE: There is one of these per test (and therefore unique to each test thread).
64 */
65 class TestContext {
66 private:
67 // counters
68 std::atomic<int> mNumSends;
69 std::atomic<int> mNumBytesSent;
70 std::atomic<int> mNumReceives;
71 std::atomic<int> mNumBytesReceived;
72
73 std::vector<ReceivedMessageRecord> mReceivedMsgs;
74 std::vector<SentMessageRecord> mSentMsgs;
75
76 // Java NativeMidiMessage class stuff, for passing messages back out to the Java client.
77 jclass mClsNativeMidiMessage;
78 jmethodID mMidNativeMidiMessage_ctor;
79 jfieldID mFid_opcode;
80 jfieldID mFid_buffer;
81 jfieldID mFid_len;
82 jfieldID mFid_timestamp;
83 jfieldID mFid_timeReceived;
84
85 std::mutex lock;
86
87 public:
88 // read Thread
89 std::unique_ptr<std::thread> mReadThread;
90 std::atomic<bool> mReading;
91
92 AMidiDevice* nativeDevice;
93 std::atomic<AMidiOutputPort*> midiOutputPort;
94 std::atomic<AMidiInputPort*> midiInputPort;
95
TestContext()96 TestContext() :
97 mNumSends(0),
98 mNumBytesSent(0),
99 mNumReceives(0),
100 mNumBytesReceived(0),
101 mClsNativeMidiMessage(0),
102 mMidNativeMidiMessage_ctor(0),
103 mFid_opcode(0),
104 mFid_buffer(0),
105 mFid_len(0),
106 mFid_timestamp(0),
107 mFid_timeReceived(0),
108 mReading(false),
109 nativeDevice(nullptr),
110 midiOutputPort(nullptr),
111 midiInputPort(nullptr)
112 {}
113
114 bool initN(JNIEnv* env);
115
getNumSends()116 int getNumSends() { return mNumSends; }
incNumSends()117 void incNumSends() { mNumSends++; }
118
getNumBytesSent()119 int getNumBytesSent() { return mNumBytesSent; }
incNumBytesSent(int numBytes)120 void incNumBytesSent(int numBytes) { mNumBytesSent += numBytes; }
121
getNumReceives()122 int getNumReceives() { return mNumReceives; }
incNumReceives()123 void incNumReceives() { mNumReceives++; }
124
getNumBytesReceived()125 int getNumBytesReceived() { return mNumBytesReceived; }
incNumBytesReceived(int numBytes)126 void incNumBytesReceived(int numBytes) { mNumBytesReceived += numBytes; }
127
addSent(SentMessageRecord && msg)128 void addSent(SentMessageRecord&& msg) { mSentMsgs.push_back(std::move(msg)); }
getNumSentMsgs()129 size_t getNumSentMsgs() { return mSentMsgs.size(); }
130
addReceived(ReceivedMessageRecord && msg)131 void addReceived(ReceivedMessageRecord&& msg) { mReceivedMsgs.push_back(std::move(msg)); }
getNumReceivedMsgs()132 size_t getNumReceivedMsgs() { return mReceivedMsgs.size(); }
133
134 jobject transferReceiveMsgAt(JNIEnv* env, int index);
135
136 static const int COMPARE_SUCCESS = 0;
137 static const int COMPARE_COUNTMISSMATCH = 1;
138 static const int COMPARE_DATALENMISMATCH = 2;
139 static const int COMPARE_DATAMISMATCH = 3;
140 static const int COMPARE_TIMESTAMPMISMATCH = 4;
141 int compareInsAndOuts();
142
143 static const int CHECKLATENCY_SUCCESS = 0;
144 static const int CHECKLATENCY_COUNTMISSMATCH = 1;
145 static const int CHECKLATENCY_LATENCYEXCEEDED = 2;
146 int checkInOutLatency(long maxLatencyNanos);
147 };
148
149 //
150 // Helpers
151 //
System_nanoTime()152 static long System_nanoTime() {
153 // this code is the implementation of System.nanoTime()
154 // from system/code/ojluni/src/main/native/System.
155 struct timespec now;
156 clock_gettime(CLOCK_MONOTONIC, &now);
157 return now.tv_sec * 1000000000LL + now.tv_nsec;
158 }
159
initN(JNIEnv * env)160 bool TestContext::initN(JNIEnv* env) {
161 static const char* clsSigNativeMidiMessage = "android/nativemidi/cts/NativeMidiMessage";
162
163 jclass cls = env->FindClass(clsSigNativeMidiMessage);
164 if (cls == NULL) {
165 __android_log_print(ANDROID_LOG_ERROR, LOG_TAG,
166 "JNI Error - couldn't find NativeMidiMessage class");
167 return false; // we are doomed, so bail.
168 }
169 mClsNativeMidiMessage = (jclass)env->NewGlobalRef(cls);
170 if (mClsNativeMidiMessage == NULL) {
171 __android_log_print(ANDROID_LOG_ERROR, LOG_TAG,
172 "JNI Error - couldn't allocate NativeMidiMessage");
173 return false; // we are doomed, so bail.
174 }
175
176 mMidNativeMidiMessage_ctor = env->GetMethodID(mClsNativeMidiMessage, "<init>", "()V");
177 mFid_opcode = env->GetFieldID(mClsNativeMidiMessage, "opcode", "I");
178 mFid_buffer = env->GetFieldID(mClsNativeMidiMessage, "buffer", "[B");
179 mFid_len = env->GetFieldID( mClsNativeMidiMessage, "len", "I");
180 mFid_timestamp = env->GetFieldID(mClsNativeMidiMessage, "timestamp", "J");
181 mFid_timeReceived = env->GetFieldID(mClsNativeMidiMessage, "timeReceived", "J");
182 if (mMidNativeMidiMessage_ctor == NULL ||
183 mFid_opcode == NULL ||
184 mFid_buffer == NULL ||
185 mFid_len == NULL ||
186 mFid_timestamp == NULL ||
187 mFid_timeReceived == NULL) {
188 __android_log_print(ANDROID_LOG_ERROR, LOG_TAG,
189 "JNI Error - couldn't load Field IDs");
190 return false; // we are doomed, so bail.
191 }
192
193 return true;
194 }
195
transferReceiveMsgAt(JNIEnv * env,int index)196 jobject TestContext::transferReceiveMsgAt(JNIEnv* env, int index) {
197 jobject msg = NULL;
198
199 if (index < (int)mReceivedMsgs.size()) {
200 ReceivedMessageRecord receiveRec = std::move(mReceivedMsgs.at(index));
201 msg = env->NewObject(mClsNativeMidiMessage, mMidNativeMidiMessage_ctor);
202
203 env->SetIntField(msg, mFid_opcode, receiveRec.opCode);
204 env->SetIntField(msg, mFid_len, receiveRec.numDataBytes);
205 jobject buffer_array = env->GetObjectField(msg, mFid_buffer);
206 env->SetByteArrayRegion(reinterpret_cast<jbyteArray>(buffer_array), 0,
207 receiveRec.numDataBytes, (jbyte*)receiveRec.dataBuff.get());
208 env->SetLongField(msg, mFid_timestamp, receiveRec.timestamp);
209 env->SetLongField(msg, mFid_timeReceived, receiveRec.timeReceived);
210 }
211
212 return msg;
213 }
214
compareInsAndOuts()215 int TestContext::compareInsAndOuts() {
216 // Number of messages sent/received
217 if (mReceivedMsgs.size() != mSentMsgs.size()) {
218 __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "---- COMPARE_COUNTMISSMATCH r:%zu s:%zu",
219 mReceivedMsgs.size(), mSentMsgs.size());
220 return COMPARE_COUNTMISSMATCH;
221 }
222
223 // we know that both vectors have the same number of messages from the test above.
224 size_t numMessages = mSentMsgs.size();
225 for (size_t msgIndex = 0; msgIndex < numMessages; msgIndex++) {
226 // Data Length?
227 if (mReceivedMsgs[msgIndex].numDataBytes != mSentMsgs[msgIndex].numDataBytes) {
228 __android_log_print(ANDROID_LOG_ERROR, LOG_TAG,
229 "---- COMPARE_DATALENMISMATCH r:%zu s:%zu",
230 mReceivedMsgs[msgIndex].numDataBytes, mSentMsgs[msgIndex].numDataBytes);
231 return COMPARE_DATALENMISMATCH;
232 }
233
234 // Timestamps
235 if (mReceivedMsgs[msgIndex].timestamp != mSentMsgs[msgIndex].timestamp) {
236 __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "---- COMPARE_TIMESTAMPMISMATCH");
237 return COMPARE_TIMESTAMPMISMATCH;
238 }
239
240 // we know that the data in both messages have the same number of bytes from the test above.
241 int dataLen = mReceivedMsgs[msgIndex].numDataBytes;
242 for (int dataIndex = 0; dataIndex < dataLen; dataIndex++) {
243 // Data Values?
244 if (mReceivedMsgs[msgIndex].dataBuff[dataIndex] !=
245 mSentMsgs[msgIndex].dataBuff[dataIndex]) {
246 __android_log_print(ANDROID_LOG_ERROR, LOG_TAG,
247 "---- COMPARE_DATAMISMATCH r:%d s:%d",
248 (int)mReceivedMsgs[msgIndex].dataBuff[dataIndex],
249 (int)mSentMsgs[msgIndex].dataBuff[dataIndex]);
250 return COMPARE_DATAMISMATCH;
251 }
252 }
253 }
254
255 return COMPARE_SUCCESS;
256 }
257
checkInOutLatency(long maxLatencyNanos)258 int TestContext::checkInOutLatency(long maxLatencyNanos) {
259 if (mReceivedMsgs.size() != mSentMsgs.size()) {
260 __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, " ---- CHECKLATENCY_COUNTMISSMATCH");
261 return CHECKLATENCY_COUNTMISSMATCH;
262 }
263
264 // we know that both vectors have the same number of messages
265 // from the test above.
266 int numMessages = mSentMsgs.size();
267 for (int msgIndex = 0; msgIndex < numMessages; msgIndex++) {
268 long timeDelta = mSentMsgs[msgIndex].timeSent - mReceivedMsgs[msgIndex].timestamp;
269 if (timeDelta > maxLatencyNanos) {
270 __android_log_print(ANDROID_LOG_ERROR, LOG_TAG,
271 " ---- CHECKLATENCY_LATENCYEXCEEDED %ld", timeDelta);
272 return CHECKLATENCY_LATENCYEXCEEDED;
273 }
274 }
275
276 return CHECKLATENCY_SUCCESS;
277 }
278
Java_android_nativemidi_cts_NativeMidiEchoTest_allocTestContext(JNIEnv * env,jclass)279 JNIEXPORT jlong JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_allocTestContext(
280 JNIEnv* env, jclass) {
281 __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "allocTestContext()");
282
283 TestContext* context = new TestContext;
284 if (!context->initN(env)) {
285 delete context;
286 context = NULL;
287 }
288
289 return (jlong)context;
290 }
291
Java_android_nativemidi_cts_NativeMidiEchoTest_freeTestContext(JNIEnv *,jclass,jlong context)292 JNIEXPORT void JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_freeTestContext(
293 JNIEnv*, jclass, jlong context) {
294 __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "freeTestContext()");
295 delete (TestContext*)context;
296 }
297
298 /*
299 * Receiving API
300 */
301 //static void DumpDataMessage(ReceivedMessageRecord* msg) {
302 // char midiDumpBuffer[SIZE_DATABUFFER * 4]; // more than enough
303 // memset(midiDumpBuffer, 0, sizeof(midiDumpBuffer));
304 // int pos = snprintf(midiDumpBuffer, sizeof(midiDumpBuffer),
305 // "%" PRIx64 " ", msg->timestamp);
306 // for (uint8_t *b = msg->buffer, *e = b + msg->numDataBytes; b < e; ++b) {
307 // pos += snprintf(midiDumpBuffer + pos, sizeof(midiDumpBuffer) - pos,
308 // "%02x ", *b);
309 // }
310 // __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "---- DUMP %s", midiDumpBuffer);
311 //}
312
readThreadRoutine(TestContext * context)313 void readThreadRoutine(TestContext* context) {
314 int32_t opCode;
315 uint8_t inDataBuffer[SIZE_DATABUFFER];
316 size_t numDataBytes;
317 int64_t timestamp;
318
319 while (context->mReading) {
320 AMidiOutputPort* outputPort = context->midiOutputPort.load();
321 if (outputPort != nullptr) {
322 ssize_t numMessages =
323 AMidiOutputPort_receive(outputPort, &opCode,
324 inDataBuffer, sizeof(inDataBuffer), &numDataBytes, ×tamp);
325
326 if (numMessages > 0) {
327 context->incNumReceives();
328 context->incNumBytesReceived(numDataBytes);
329 ReceivedMessageRecord receiveRec;
330 receiveRec.timeReceived = System_nanoTime();
331 receiveRec.numDataBytes = numDataBytes;
332 receiveRec.dataBuff.reset(new uint8_t[receiveRec.numDataBytes]);
333 memcpy(receiveRec.dataBuff.get(), inDataBuffer, receiveRec.numDataBytes);
334 receiveRec.opCode = opCode;
335 receiveRec.timestamp = timestamp;
336 context->addReceived(std::move(receiveRec));
337 }
338 }
339 }
340 }
341
commonDeviceOpen(JNIEnv * env,jobject midiDeviceObj,AMidiDevice ** device)342 static media_status_t commonDeviceOpen(JNIEnv *env, jobject midiDeviceObj, AMidiDevice** device) {
343 media_status_t status = AMidiDevice_fromJava(env, midiDeviceObj, device);
344 if (status == AMEDIA_OK) {
345 // __android_log_print(ANDROID_LOG_INFO, LOG_TAG,
346 // "---- Obtained device token for obj %p: dev %p", midiDeviceObj, device);
347 } else {
348 __android_log_print(ANDROID_LOG_ERROR, LOG_TAG,
349 "---- Could not obtain device token for obj %p: status:%d", midiDeviceObj, status);
350 }
351
352 return status;
353 }
354
Java_android_nativemidi_cts_NativeMidiEchoTest_openNativeMidiDevice(JNIEnv * env,jobject,jlong ctx,jobject deviceObj)355 JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_openNativeMidiDevice(
356 JNIEnv* env, jobject, jlong ctx, jobject deviceObj) {
357 TestContext* context = (TestContext*)ctx;
358 media_status_t status = commonDeviceOpen(env, deviceObj, &context->nativeDevice);
359 return status;
360 }
361
Java_android_nativemidi_cts_NativeMidiEchoTest_closeNativeMidiDevice(JNIEnv *,jobject,jlong ctx)362 JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_closeNativeMidiDevice(
363 JNIEnv*, jobject, jlong ctx) {
364 TestContext* context = (TestContext*)ctx;
365 media_status_t status = AMidiDevice_release(context->nativeDevice);
366 return status;
367 }
368
369 /*
370 * Sending API
371 */
Java_android_nativemidi_cts_NativeMidiEchoTest_startWritingMidi(JNIEnv *,jobject,jlong ctx,jint portNumber)372 JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_startWritingMidi(
373 JNIEnv*, jobject, jlong ctx, jint portNumber) {
374
375 TestContext* context = (TestContext*)ctx;
376
377 AMidiInputPort* inputPort;
378 media_status_t status = AMidiInputPort_open(context->nativeDevice, portNumber, &inputPort);
379 if (status == AMEDIA_OK) {
380 // __android_log_print(ANDROID_LOG_INFO, LOG_TAG,
381 // "---- Opened INPUT port %d: token %p", portNumber, inputPort);
382 context->midiInputPort.store(inputPort);
383 } else {
384 __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "---- Could not open INPUT port %p:%d %d",
385 context->nativeDevice, portNumber, status);
386 return status;
387 }
388
389 return AMEDIA_OK;
390 }
391
Java_android_nativemidi_cts_NativeMidiEchoTest_stopWritingMidi(JNIEnv *,jobject,jlong ctx)392 JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_stopWritingMidi(
393 JNIEnv*, jobject, jlong ctx) {
394
395 TestContext* context = (TestContext*)ctx;
396
397 AMidiInputPort* inputPort = context->midiInputPort.exchange(nullptr);
398 if (inputPort == nullptr) {
399 return -1;
400 }
401
402 AMidiInputPort_close(inputPort);
403
404 return 0;
405 }
406
Java_android_nativemidi_cts_NativeMidiEchoTest_writeMidiWithTimestamp(JNIEnv * env,jobject,jlong ctx,jbyteArray data,jint offset,jint numBytes,jlong timestamp)407 JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_writeMidiWithTimestamp(
408 JNIEnv* env, jobject,
409 jlong ctx, jbyteArray data, jint offset, jint numBytes, jlong timestamp) {
410
411 TestContext* context = (TestContext*)ctx;
412 context->incNumSends();
413 context->incNumBytesSent(numBytes);
414
415 jbyte* bufferPtr = env->GetByteArrayElements(data, NULL);
416 if (bufferPtr == NULL) {
417 return -1;
418 }
419
420 int numWritten = AMidiInputPort_sendWithTimestamp(
421 context->midiInputPort, (uint8_t*)bufferPtr + offset, numBytes, timestamp);
422 if (numWritten > 0) {
423 // Don't save a send record if we didn't send!
424 SentMessageRecord sendRec;
425 sendRec.numDataBytes = numBytes;
426 sendRec.dataBuff.reset(new uint8_t[sendRec.numDataBytes]);
427 memcpy(sendRec.dataBuff.get(), (uint8_t*)bufferPtr + offset, numBytes);
428 sendRec.timestamp = timestamp;
429 sendRec.timeSent = System_nanoTime();
430 context->addSent(std::move(sendRec));
431 }
432
433 env->ReleaseByteArrayElements(data, bufferPtr, JNI_ABORT);
434
435 return numWritten;
436 }
437
Java_android_nativemidi_cts_NativeMidiEchoTest_writeMidi(JNIEnv * env,jobject j_object,jlong ctx,jbyteArray data,jint offset,jint numBytes)438 JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_writeMidi(
439 JNIEnv* env, jobject j_object, jlong ctx, jbyteArray data, jint offset, jint numBytes) {
440 return Java_android_nativemidi_cts_NativeMidiEchoTest_writeMidiWithTimestamp(
441 env, j_object, ctx, data, offset, numBytes, 0L);
442 }
443
Java_android_nativemidi_cts_NativeMidiEchoTest_flushSentMessages(JNIEnv *,jobject,jlong ctx)444 JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_flushSentMessages(
445 JNIEnv*, jobject, jlong ctx) {
446 TestContext* context = (TestContext*)ctx;
447 return AMidiInputPort_sendFlush(context->midiInputPort);
448 }
449
Java_android_nativemidi_cts_NativeMidiEchoTest_getNumSends(JNIEnv *,jobject,jlong ctx)450 JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_getNumSends(
451 JNIEnv*, jobject, jlong ctx) {
452 return ((TestContext*)ctx)->getNumSends();
453 }
454
Java_android_nativemidi_cts_NativeMidiEchoTest_getNumBytesSent(JNIEnv *,jobject,jlong ctx)455 JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_getNumBytesSent(
456 JNIEnv*, jobject, jlong ctx) {
457 return ((TestContext*)ctx)->getNumBytesSent();
458 }
459
Java_android_nativemidi_cts_NativeMidiEchoTest_getNumReceives(JNIEnv *,jobject,jlong ctx)460 JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_getNumReceives(
461 JNIEnv*, jobject, jlong ctx) {
462 return ((TestContext*)ctx)->getNumReceives();
463 }
464
Java_android_nativemidi_cts_NativeMidiEchoTest_getNumBytesReceived(JNIEnv *,jobject,jlong ctx)465 JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_getNumBytesReceived(
466 JNIEnv*, jobject, jlong ctx) {
467 return ((TestContext*)ctx)->getNumBytesReceived();
468 }
469
Java_android_nativemidi_cts_NativeMidiEchoTest_startReadingMidi(JNIEnv *,jobject,jlong ctx,jint portNumber)470 JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_startReadingMidi(
471 JNIEnv*, jobject, jlong ctx, jint portNumber) {
472
473 // __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "++++ startReadingMidi()");
474 TestContext* context = (TestContext*)ctx;
475 if (context == nullptr) {
476 __android_log_print(ANDROID_LOG_INFO, LOG_TAG,
477 "Test Context is null in startReadingMidi()");
478 return AMEDIA_ERROR_INVALID_OBJECT;
479 }
480
481 AMidiOutputPort* outputPort;
482 media_status_t status = AMidiOutputPort_open(context->nativeDevice, portNumber, &outputPort);
483 if (status == AMEDIA_OK) {
484 context->midiOutputPort.store(outputPort);
485 } else {
486 __android_log_print(ANDROID_LOG_ERROR, LOG_TAG,
487 "---- Could not open OUTPUT port %p : %d %d",
488 context->nativeDevice, portNumber, status);
489 return status;
490 }
491
492 // Start read thread
493 context->mReading = true;
494 context->mReadThread.reset(new std::thread(readThreadRoutine, context));
495 std::this_thread::yield(); // let the read thread startup.
496
497 return status;
498 }
499
Java_android_nativemidi_cts_NativeMidiEchoTest_stopReadingMidi(JNIEnv *,jobject,jlong ctx)500 JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_stopReadingMidi(
501 JNIEnv*, jobject, jlong ctx) {
502
503 // __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "++++ stopReadingMidi()");
504 TestContext* context = (TestContext*)ctx;
505 if (context == nullptr) {
506 __android_log_print(ANDROID_LOG_INFO, LOG_TAG,
507 "Test Context is null in stopReadingMidi()");
508 return AMEDIA_ERROR_INVALID_OBJECT;
509 }
510 context->mReading = false;
511
512 context->mReadThread->join();
513
514 AMidiOutputPort* outputPort = context->midiOutputPort.exchange(nullptr);
515 if (outputPort == nullptr) {
516 return -1;
517 }
518
519 AMidiOutputPort_close(outputPort);
520
521 return 0;
522 }
523
524 /*
525 * Messages
526 */
Java_android_nativemidi_cts_NativeMidiEchoTest_getNumReceivedMessages(JNIEnv *,jobject,jlong ctx)527 JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_getNumReceivedMessages(
528 JNIEnv*, jobject, jlong ctx) {
529 return ((TestContext*)ctx)->getNumReceivedMsgs();
530 }
531
Java_android_nativemidi_cts_NativeMidiEchoTest_getReceivedMessageAt(JNIEnv * env,jobject,jlong ctx,jint index)532 JNIEXPORT jobject JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_getReceivedMessageAt(
533 JNIEnv* env, jobject, jlong ctx, jint index) {
534 return ((TestContext*)ctx)->transferReceiveMsgAt(env, index);
535 }
536
Java_android_nativemidi_cts_NativeMidiEchoTest_matchNativeMessages(JNIEnv *,jobject,jlong ctx)537 JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_matchNativeMessages(
538 JNIEnv*, jobject, jlong ctx) {
539 return ((TestContext*)ctx)->compareInsAndOuts();
540 }
541
Java_android_nativemidi_cts_NativeMidiEchoTest_checkNativeLatency(JNIEnv *,jobject,jlong ctx,jlong maxLatencyNanos)542 JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_checkNativeLatency(
543 JNIEnv*, jobject, jlong ctx, jlong maxLatencyNanos) {
544 return ((TestContext*)ctx)->checkInOutLatency(maxLatencyNanos);
545 }
546
547 } // extern "C"
548