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 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 116 int getNumSends() { return mNumSends; } 117 void incNumSends() { mNumSends++; } 118 119 int getNumBytesSent() { return mNumBytesSent; } 120 void incNumBytesSent(int numBytes) { mNumBytesSent += numBytes; } 121 122 int getNumReceives() { return mNumReceives; } 123 void incNumReceives() { mNumReceives++; } 124 125 int getNumBytesReceived() { return mNumBytesReceived; } 126 void incNumBytesReceived(int numBytes) { mNumBytesReceived += numBytes; } 127 128 void addSent(SentMessageRecord&& msg) { mSentMsgs.push_back(std::move(msg)); } 129 size_t getNumSentMsgs() { return mSentMsgs.size(); } 130 131 void addReceived(ReceivedMessageRecord&& msg) { mReceivedMsgs.push_back(std::move(msg)); } 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 // 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 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 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 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 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 279 JNIEXPORT jlong JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_allocTestContext( 280 JNIEnv* env, jclass) { 281 TestContext* context = new TestContext; 282 if (!context->initN(env)) { 283 delete context; 284 context = NULL; 285 } 286 287 return (jlong)context; 288 } 289 290 JNIEXPORT void JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_freeTestContext( 291 JNIEnv*, jclass, jlong context) { 292 delete (TestContext*)context; 293 } 294 295 /* 296 * Receiving API 297 */ 298 //static void DumpDataMessage(ReceivedMessageRecord* msg) { 299 // char midiDumpBuffer[SIZE_DATABUFFER * 4]; // more than enough 300 // memset(midiDumpBuffer, 0, sizeof(midiDumpBuffer)); 301 // int pos = snprintf(midiDumpBuffer, sizeof(midiDumpBuffer), 302 // "%" PRIx64 " ", msg->timestamp); 303 // for (uint8_t *b = msg->buffer, *e = b + msg->numDataBytes; b < e; ++b) { 304 // pos += snprintf(midiDumpBuffer + pos, sizeof(midiDumpBuffer) - pos, 305 // "%02x ", *b); 306 // } 307 // __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "---- DUMP %s", midiDumpBuffer); 308 //} 309 310 void readThreadRoutine(TestContext* context) { 311 int32_t opCode; 312 uint8_t inDataBuffer[SIZE_DATABUFFER]; 313 size_t numDataBytes; 314 int64_t timestamp; 315 316 while (context->mReading) { 317 AMidiOutputPort* outputPort = context->midiOutputPort.load(); 318 if (outputPort != nullptr) { 319 ssize_t numMessages = 320 AMidiOutputPort_receive(outputPort, &opCode, 321 inDataBuffer, sizeof(inDataBuffer), &numDataBytes, ×tamp); 322 323 if (numMessages > 0) { 324 context->incNumReceives(); 325 context->incNumBytesReceived(numDataBytes); 326 ReceivedMessageRecord receiveRec; 327 receiveRec.timeReceived = System_nanoTime(); 328 receiveRec.numDataBytes = numDataBytes; 329 receiveRec.dataBuff.reset(new uint8_t[receiveRec.numDataBytes]); 330 memcpy(receiveRec.dataBuff.get(), inDataBuffer, receiveRec.numDataBytes); 331 receiveRec.opCode = opCode; 332 receiveRec.timestamp = timestamp; 333 context->addReceived(std::move(receiveRec)); 334 } 335 } 336 } 337 } 338 339 static media_status_t commonDeviceOpen(JNIEnv *env, jobject midiDeviceObj, AMidiDevice** device) { 340 media_status_t status = AMidiDevice_fromJava(env, midiDeviceObj, device); 341 if (status == AMEDIA_OK) { 342 // __android_log_print(ANDROID_LOG_INFO, LOG_TAG, 343 // "---- Obtained device token for obj %p: dev %p", midiDeviceObj, device); 344 } else { 345 __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, 346 "---- Could not obtain device token for obj %p: status:%d", midiDeviceObj, status); 347 } 348 349 return status; 350 } 351 352 JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_openNativeMidiDevice( 353 JNIEnv* env, jobject, jlong ctx, jobject deviceObj) { 354 TestContext* context = (TestContext*)ctx; 355 media_status_t status = commonDeviceOpen(env, deviceObj, &context->nativeDevice); 356 return status; 357 } 358 359 JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_closeNativeMidiDevice( 360 JNIEnv*, jobject, jlong ctx) { 361 TestContext* context = (TestContext*)ctx; 362 media_status_t status = AMidiDevice_release(context->nativeDevice); 363 return status; 364 } 365 366 /* 367 * Sending API 368 */ 369 JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_startWritingMidi( 370 JNIEnv*, jobject, jlong ctx, jint portNumber) { 371 372 TestContext* context = (TestContext*)ctx; 373 374 AMidiInputPort* inputPort; 375 media_status_t status = AMidiInputPort_open(context->nativeDevice, portNumber, &inputPort); 376 if (status == AMEDIA_OK) { 377 // __android_log_print(ANDROID_LOG_INFO, LOG_TAG, 378 // "---- Opened INPUT port %d: token %p", portNumber, inputPort); 379 context->midiInputPort.store(inputPort); 380 } else { 381 __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "---- Could not open INPUT port %p:%d %d", 382 context->nativeDevice, portNumber, status); 383 return status; 384 } 385 386 return AMEDIA_OK; 387 } 388 389 JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_stopWritingMidi( 390 JNIEnv*, jobject, jlong ctx) { 391 392 TestContext* context = (TestContext*)ctx; 393 394 AMidiInputPort* inputPort = context->midiInputPort.exchange(nullptr); 395 if (inputPort == nullptr) { 396 return -1; 397 } 398 399 AMidiInputPort_close(inputPort); 400 401 return 0; 402 } 403 404 JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_writeMidiWithTimestamp( 405 JNIEnv* env, jobject, 406 jlong ctx, jbyteArray data, jint offset, jint numBytes, jlong timestamp) { 407 408 TestContext* context = (TestContext*)ctx; 409 context->incNumSends(); 410 context->incNumBytesSent(numBytes); 411 412 jbyte* bufferPtr = env->GetByteArrayElements(data, NULL); 413 if (bufferPtr == NULL) { 414 return -1; 415 } 416 417 int numWritten = AMidiInputPort_sendWithTimestamp( 418 context->midiInputPort, (uint8_t*)bufferPtr + offset, numBytes, timestamp); 419 if (numWritten > 0) { 420 // Don't save a send record if we didn't send! 421 SentMessageRecord sendRec; 422 sendRec.numDataBytes = numBytes; 423 sendRec.dataBuff.reset(new uint8_t[sendRec.numDataBytes]); 424 memcpy(sendRec.dataBuff.get(), (uint8_t*)bufferPtr + offset, numBytes); 425 sendRec.timestamp = timestamp; 426 sendRec.timeSent = System_nanoTime(); 427 context->addSent(std::move(sendRec)); 428 } 429 430 env->ReleaseByteArrayElements(data, bufferPtr, JNI_ABORT); 431 432 return numWritten; 433 } 434 435 JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_writeMidi( 436 JNIEnv* env, jobject j_object, jlong ctx, jbyteArray data, jint offset, jint numBytes) { 437 return Java_android_nativemidi_cts_NativeMidiEchoTest_writeMidiWithTimestamp( 438 env, j_object, ctx, data, offset, numBytes, 0L); 439 } 440 441 JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_flushSentMessages( 442 JNIEnv*, jobject, jlong ctx) { 443 TestContext* context = (TestContext*)ctx; 444 return AMidiInputPort_sendFlush(context->midiInputPort); 445 } 446 447 JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_getNumSends( 448 JNIEnv*, jobject, jlong ctx) { 449 return ((TestContext*)ctx)->getNumSends(); 450 } 451 452 JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_getNumBytesSent( 453 JNIEnv*, jobject, jlong ctx) { 454 return ((TestContext*)ctx)->getNumBytesSent(); 455 } 456 457 JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_getNumReceives( 458 JNIEnv*, jobject, jlong ctx) { 459 return ((TestContext*)ctx)->getNumReceives(); 460 } 461 462 JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_getNumBytesReceived( 463 JNIEnv*, jobject, jlong ctx) { 464 return ((TestContext*)ctx)->getNumBytesReceived(); 465 } 466 467 JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_startReadingMidi( 468 JNIEnv*, jobject, jlong ctx, jint portNumber) { 469 470 // __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "++++ startReadingMidi()"); 471 TestContext* context = (TestContext*)ctx; 472 473 AMidiOutputPort* outputPort; 474 media_status_t status = AMidiOutputPort_open(context->nativeDevice, portNumber, &outputPort); 475 if (status == AMEDIA_OK) { 476 context->midiOutputPort.store(outputPort); 477 } else { 478 __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, 479 "---- Could not open OUTPUT port %p : %d %d", 480 context->nativeDevice, portNumber, status); 481 return status; 482 } 483 484 // Start read thread 485 context->mReading = true; 486 context->mReadThread.reset(new std::thread(readThreadRoutine, context)); 487 std::this_thread::yield(); // let the read thread startup. 488 489 return status; 490 } 491 492 JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_stopReadingMidi( 493 JNIEnv*, jobject, jlong ctx) { 494 495 // __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "++++ stopReadingMidi()"); 496 TestContext* context = (TestContext*)ctx; 497 context->mReading = false; 498 499 context->mReadThread->join(); 500 501 AMidiOutputPort* outputPort = context->midiOutputPort.exchange(nullptr); 502 if (outputPort == nullptr) { 503 return -1; 504 } 505 506 AMidiOutputPort_close(outputPort); 507 508 return 0; 509 } 510 511 /* 512 * Messages 513 */ 514 JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_getNumReceivedMessages( 515 JNIEnv*, jobject, jlong ctx) { 516 return ((TestContext*)ctx)->getNumReceivedMsgs(); 517 } 518 519 JNIEXPORT jobject JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_getReceivedMessageAt( 520 JNIEnv* env, jobject, jlong ctx, jint index) { 521 return ((TestContext*)ctx)->transferReceiveMsgAt(env, index); 522 } 523 524 JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_matchNativeMessages( 525 JNIEnv*, jobject, jlong ctx) { 526 return ((TestContext*)ctx)->compareInsAndOuts(); 527 } 528 529 JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_checkNativeLatency( 530 JNIEnv*, jobject, jlong ctx, jlong maxLatencyNanos) { 531 return ((TestContext*)ctx)->checkInOutLatency(maxLatencyNanos); 532 } 533 534 } // extern "C" 535