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 #include <cstring>
17 #include <pthread.h>
18 #include <unistd.h>
19 #include <stdio.h>
20 #include <chrono>
21
22 #define TAG "MidiTestManager"
23 #include <android/log.h>
24 #define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
25 #define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
26 #define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
27
28 #include "MidiTestManager.h"
29
30 static pthread_t readThread;
31
32 static const bool DEBUG = true;
33 static const bool DEBUG_MIDIDATA = true;
34
35 static const int MAX_PACKET_SIZE = 1024;
36 static const int TIMEOUT_SECONDS = 5;
37
38 //
39 // MIDI Messages
40 //
41 // Channel Commands
42 static const uint8_t kMIDIChanCmd_KeyDown = 9;
43 static const uint8_t kMIDIChanCmd_KeyUp = 8;
44 static const uint8_t kMIDIChanCmd_PolyPress = 10;
45 static const uint8_t kMIDIChanCmd_Control = 11;
46 static const uint8_t kMIDIChanCmd_ProgramChange = 12;
47 static const uint8_t kMIDIChanCmd_ChannelPress = 13;
48 static const uint8_t kMIDIChanCmd_PitchWheel = 14;
49 // System Commands
50 static const uint8_t kMIDISysCmd_SysEx = 0xF0;
51 static const uint8_t kMIDISysCmd_EndOfSysEx = 0xF7;
52 static const uint8_t kMIDISysCmd_ActiveSensing = 0xFE;
53 static const uint8_t kMIDISysCmd_Reset = 0xFF;
54
readThreadRoutine(void * context)55 static void* readThreadRoutine(void * context) {
56 MidiTestManager* testManager = (MidiTestManager*)context;
57 return reinterpret_cast<void*>(static_cast<intptr_t>(testManager->ProcessInput()));
58 }
59
60 /*
61 * TestMessage
62 */
63 #define makeMIDICmd(cmd, channel) (uint8_t)((cmd << 4) | (channel & 0x0F))
64
65 uint8_t warmupMsg[] = {makeMIDICmd(kMIDIChanCmd_Control, 0), 0, 0};
66 uint8_t msg0[] = {makeMIDICmd(kMIDIChanCmd_KeyDown, 0), 64, 120};
67 uint8_t msg1[] = {makeMIDICmd(kMIDIChanCmd_KeyUp, 0), 64, 35};
68
69 class TestMessage {
70 public:
71 uint8_t* mMsgBytes;
72 int mNumMsgBytes;
73
TestMessage()74 TestMessage()
75 : mMsgBytes(NULL), mNumMsgBytes(0)
76 {}
77
~TestMessage()78 ~TestMessage() {
79 delete[] mMsgBytes;
80 }
81
set(uint8_t * msgBytes,int numMsgBytes)82 bool set(uint8_t* msgBytes, int numMsgBytes) {
83 if (msgBytes == NULL || numMsgBytes <= 0) {
84 return false;
85 }
86 mNumMsgBytes = numMsgBytes;
87 mMsgBytes = new uint8_t[numMsgBytes];
88 memcpy(mMsgBytes, msgBytes, mNumMsgBytes * sizeof(uint8_t));
89 return true;
90 }
91
setSysExMessage(int numMsgBytes)92 bool setSysExMessage(int numMsgBytes) {
93 if (numMsgBytes <= 0) {
94 return false;
95 }
96 mNumMsgBytes = numMsgBytes;
97 mMsgBytes = new uint8_t[mNumMsgBytes];
98 memset(mMsgBytes, 0, mNumMsgBytes * sizeof(uint8_t));
99 mMsgBytes[0] = kMIDISysCmd_SysEx;
100 for(int index = 1; index < numMsgBytes - 1; index++) {
101 mMsgBytes[index] = (uint8_t) (index % 100);
102 }
103 mMsgBytes[numMsgBytes - 1] = kMIDISysCmd_EndOfSysEx;
104 return true;
105 }
106
setTwoSysExMessage(int firstMsgBytes,int secondMsgBytes)107 bool setTwoSysExMessage(int firstMsgBytes, int secondMsgBytes) {
108 if (firstMsgBytes <= 0 || secondMsgBytes <= 0) {
109 return false;
110 }
111 mNumMsgBytes = firstMsgBytes + secondMsgBytes;
112 mMsgBytes = new uint8_t[mNumMsgBytes];
113 memset(mMsgBytes, 0, mNumMsgBytes * sizeof(uint8_t));
114 mMsgBytes[0] = kMIDISysCmd_SysEx;
115 for(int index = 1; index < firstMsgBytes - 1; index++) {
116 mMsgBytes[index] = (uint8_t) (index % 100);
117 }
118 mMsgBytes[firstMsgBytes - 1] = kMIDISysCmd_EndOfSysEx;
119 mMsgBytes[firstMsgBytes] = kMIDISysCmd_SysEx;
120 for(int index = firstMsgBytes + 1; index < firstMsgBytes + secondMsgBytes - 1; index++) {
121 mMsgBytes[index] = (uint8_t) (index % 100);
122 }
123 mMsgBytes[firstMsgBytes + secondMsgBytes - 1] = kMIDISysCmd_EndOfSysEx;
124 return true;
125 }
126 }; /* class TestMessage */
127
128 /*
129 * MidiTestManager
130 */
MidiTestManager()131 MidiTestManager::MidiTestManager()
132 : mTestModuleObj(NULL),
133 mReceiveStreamPos(0),
134 mMidiSendPort(NULL), mMidiReceivePort(NULL),
135 mTestMsgs(0), mNumTestMsgs(0),
136 mThrottleData(false)
137 {}
138
~MidiTestManager()139 MidiTestManager::~MidiTestManager() {
140 mMatchStream.clear();
141 }
142
jniSetup(JNIEnv * env)143 void MidiTestManager::jniSetup(JNIEnv* env) {
144 env->GetJavaVM(&mJvm);
145
146 jclass clsMidiTestModule =
147 env->FindClass("com/android/cts/verifier/audio/MidiNativeTestActivity$NativeMidiTestModule");
148 if (DEBUG) {
149 ALOGI("gClsMidiTestModule:%p", clsMidiTestModule);
150 }
151
152 // public void endTest(int endCode)
153 mMidEndTest = env->GetMethodID(clsMidiTestModule, "endTest", "(I)V");
154 if (DEBUG) {
155 ALOGI("mMidEndTestgMidEndTest:%p", mMidEndTest);
156 }
157 }
158
setupMessages()159 bool MidiTestManager::setupMessages() {
160 mNumTestMsgs = 7;
161 mTestMsgs.resize(mNumTestMsgs);
162
163 if (!mTestMsgs[0].set(msg0, sizeof(msg0)) ||
164 !mTestMsgs[1].set(msg1, sizeof(msg1)) ||
165 !mTestMsgs[2].setSysExMessage(30) ||
166 !mTestMsgs[3].setSysExMessage(6) ||
167 !mTestMsgs[4].setSysExMessage(120) ||
168 !mTestMsgs[5].setTwoSysExMessage(5, 13) ||
169 !mTestMsgs[6].setSysExMessage(340)) {
170 return false;
171 }
172 return true;
173 }
174
buildMatchStream()175 void MidiTestManager::buildMatchStream() {
176 mMatchStream.clear();
177 for(int byteIndex = 0; byteIndex < sizeof(warmupMsg); byteIndex++) {
178 mMatchStream.push_back(warmupMsg[byteIndex]);
179 }
180 for(int msgIndex = 0; msgIndex < mNumTestMsgs; msgIndex++) {
181 for(int byteIndex = 0; byteIndex < mTestMsgs[msgIndex].mNumMsgBytes; byteIndex++) {
182 mMatchStream.push_back(mTestMsgs[msgIndex].mMsgBytes[byteIndex]);
183 }
184 }
185
186 // Reset stream position
187 mReceiveStreamPos = 0;
188 }
189
logBytes(uint8_t * bytes,int count)190 static void logBytes(uint8_t* bytes, int count) {
191 int buffSize = (count * 6) + 1; // count of "0x??, " + '\0';
192
193 char* logBuff = new char[buffSize];
194 for (int dataIndex = 0; dataIndex < count; dataIndex++) {
195 sprintf(logBuff + (dataIndex * 6), "0x%.2X", bytes[dataIndex]);
196 if (dataIndex < count - 1) {
197 sprintf(logBuff + (dataIndex * 6) + 4, ", ");
198 }
199 }
200 ALOGD("logbytes(%d): %s", count, logBuff);
201 delete[] logBuff;
202 }
203
204 /**
205 * Compares the supplied bytes against the sent message stream at the current position
206 * and advances the stream position.
207 *
208 * Returns the number of matched bytes on success and -1 on failure.
209 *
210 */
matchStream(uint8_t * bytes,int count)211 int MidiTestManager::matchStream(uint8_t* bytes, int count) {
212 if (DEBUG) {
213 ALOGI("---- matchStream() count:%d", count);
214 }
215
216 int matchedByteCount = 0;
217
218 // a little bit of checking here...
219 if (count < 0) {
220 ALOGE("Negative Byte Count in MidiTestManager::matchStream()");
221 return -1;
222 }
223
224 if (count > MESSAGE_MAX_BYTES) {
225 ALOGE("Too Large Byte Count (%d) in MidiTestManager::matchStream()", count);
226 return -1;
227 }
228
229 bool matches = true;
230 for (int index = 0; index < count; index++) {
231 // Check for buffer overflow
232 if (mReceiveStreamPos >= mMatchStream.size()) {
233 ALOGD("matchStream() out-of-bounds @%d", mReceiveStreamPos);
234 matches = false;
235 break;
236 }
237
238 if (bytes[index] == kMIDISysCmd_ActiveSensing) {
239 if (bytes[index] == mMatchStream[mReceiveStreamPos]) {
240 ALOGD("matched active sensing message");
241 matchedByteCount++;
242 mReceiveStreamPos++;
243 } else {
244 ALOGD("skipping active sensing message");
245 }
246 } else {
247 // Check first byte for warm-up message
248 if ((mReceiveStreamPos == 0) && bytes[index] != makeMIDICmd(kMIDIChanCmd_Control, 0)) {
249 ALOGD("skipping warm-up message");
250 matchedByteCount += sizeof(warmupMsg);
251 mReceiveStreamPos += sizeof(warmupMsg);
252 }
253
254 if (bytes[index] != mMatchStream[mReceiveStreamPos]) {
255 matches = false;
256 ALOGD("---- mismatch @%d [rec:0x%X : exp:0x%X]",
257 index, bytes[index], mMatchStream[mReceiveStreamPos]);
258 } else {
259 matchedByteCount++;
260 mReceiveStreamPos++;
261 }
262 }
263 }
264
265 if (DEBUG) {
266 ALOGI(" success:%d", matches);
267 }
268
269 if (!matches) {
270 ALOGD("Mismatched Received Data:");
271 logBytes(bytes, count);
272 return -1;
273 }
274
275 return matchedByteCount;
276 }
277
278 #define THROTTLE_PERIOD_MS 20
279 #define THROTTLE_MAX_PACKET_SIZE 15
280
281 /**
282 * Send a number of bytes.
283 *
284 * Returns the number of sent bytes on success or negative error code on failure.
285 *
286 */
portSend(AMidiInputPort * sendPort,uint8_t * msg,int length,bool throttle)287 int portSend(AMidiInputPort* sendPort, uint8_t* msg, int length, bool throttle) {
288
289 int numSent = 0;
290 if (throttle) {
291 for(int index = 0; index < length; index += THROTTLE_MAX_PACKET_SIZE) {
292 int packetSize = std::min(length - index, THROTTLE_MAX_PACKET_SIZE);
293 int curSent = AMidiInputPort_send(sendPort, msg + index, packetSize);
294 if (curSent < 0) {
295 return curSent;
296 }
297 numSent += curSent;
298 usleep(THROTTLE_PERIOD_MS * 1000);
299 }
300 } else {
301 numSent = AMidiInputPort_send(sendPort, msg, length);
302 }
303 return numSent;
304 }
305
306 /**
307 * Writes out the list of MIDI messages to the output port.
308 * Returns total number of bytes sent or negative error code.
309 */
sendMessages()310 int MidiTestManager::sendMessages() {
311 if (DEBUG) {
312 ALOGI("---- sendMessages()...");
313 for(int msgIndex = 0; msgIndex < mNumTestMsgs; msgIndex++) {
314 if (DEBUG_MIDIDATA) {
315 ALOGI("--------");
316 for(int byteIndex = 0; byteIndex < mTestMsgs[msgIndex].mNumMsgBytes; byteIndex++) {
317 ALOGI(" 0x%X", mTestMsgs[msgIndex].mMsgBytes[byteIndex]);
318 }
319 }
320 }
321 }
322
323 // Send "Warm-up" message
324 portSend(mMidiSendPort, warmupMsg, sizeof(warmupMsg), mThrottleData);
325
326 int totalSent = 0;
327 for(int msgIndex = 0; msgIndex < mNumTestMsgs; msgIndex++) {
328 ssize_t numSent =
329 portSend(mMidiSendPort, mTestMsgs[msgIndex].mMsgBytes, mTestMsgs[msgIndex].mNumMsgBytes,
330 mThrottleData);
331 if (numSent < 0) {
332 ALOGE("sendMessages(): Send failed. index: %d, error: %zd", msgIndex, numSent);
333 return numSent;
334 }
335 totalSent += numSent;
336 }
337
338 if (DEBUG) {
339 ALOGI("---- totalSent:%d", totalSent);
340 }
341
342 return totalSent;
343 }
344
ProcessInput()345 int MidiTestManager::ProcessInput() {
346 uint8_t readBuffer[MAX_PACKET_SIZE];
347 size_t totalNumReceived = 0;
348
349 int testResult = TESTSTATUS_NOTRUN;
350
351 int32_t opCode;
352 size_t numBytesReceived;
353 int64_t timeStamp;
354
355 auto startTime = std::chrono::system_clock::now();
356
357 while (true) {
358 // AMidiOutputPort_receive is non-blocking, so let's not burn up the CPU unnecessarily
359 usleep(2000);
360
361 numBytesReceived = 0;
362 ssize_t numMessagesReceived =
363 AMidiOutputPort_receive(mMidiReceivePort, &opCode, readBuffer, MAX_PACKET_SIZE,
364 &numBytesReceived, &timeStamp);
365
366 if (DEBUG && numBytesReceived > 0) {
367 logBytes(readBuffer, numBytesReceived);
368 }
369
370 if (numBytesReceived > 0 &&
371 opCode == AMIDI_OPCODE_DATA &&
372 readBuffer[0] != kMIDISysCmd_Reset) {
373 if (DEBUG) {
374 ALOGI("---- msgs:%zd, bytes:%zu", numMessagesReceived, numBytesReceived);
375 }
376
377 int matchResult = matchStream(readBuffer, numBytesReceived);
378 if (matchResult < 0) {
379 testResult = TESTSTATUS_FAILED_MISMATCH;
380 if (DEBUG) {
381 ALOGE("---- TESTSTATUS_FAILED_MISMATCH");
382 }
383 return testResult;
384 }
385 totalNumReceived += matchResult;
386
387 if (totalNumReceived > mMatchStream.size()) {
388 testResult = TESTSTATUS_FAILED_OVERRUN;
389 if (DEBUG) {
390 ALOGE("---- TESTSTATUS_FAILED_OVERRUN");
391 }
392 return testResult;
393 }
394 if (totalNumReceived == mMatchStream.size()) {
395 testResult = TESTSTATUS_PASSED;
396 if (DEBUG) {
397 ALOGE("---- TESTSTATUS_PASSED");
398 }
399 return testResult;
400 }
401 }
402
403 auto currentTime = std::chrono::system_clock::now();
404 std::chrono::duration<double> elapsedSeconds = currentTime - startTime;
405 if (elapsedSeconds.count() > TIMEOUT_SECONDS) {
406 testResult = TESTSTATUS_FAILED_TIMEOUT;
407 if (DEBUG) {
408 ALOGE("---- TESTSTATUS_FAILED_TIMEOUT");
409 }
410 return testResult;
411 }
412 }
413
414 return testResult;
415 }
416
StartReading(AMidiDevice * nativeReadDevice)417 bool MidiTestManager::StartReading(AMidiDevice* nativeReadDevice) {
418 if (DEBUG) {
419 ALOGI("StartReading()...");
420 }
421
422 media_status_t m_status =
423 AMidiOutputPort_open(nativeReadDevice, 0, &mMidiReceivePort);
424 if (m_status != 0) {
425 ALOGE("Can't open MIDI device for reading err:%d", m_status);
426 return false;
427 }
428
429 // Start read thread
430 int status = pthread_create(&readThread, NULL, readThreadRoutine, this);
431 if (status != 0) {
432 ALOGE("Can't start readThread: %s (%d)", strerror(status), status);
433 }
434 return status == 0;
435 }
436
StartWriting(AMidiDevice * nativeWriteDevice)437 bool MidiTestManager::StartWriting(AMidiDevice* nativeWriteDevice) {
438 ALOGI("StartWriting()...");
439
440 media_status_t status =
441 AMidiInputPort_open(nativeWriteDevice, 0, &mMidiSendPort);
442 if (status != 0) {
443 ALOGE("Can't open MIDI device for writing err:%d", status);
444 return false;
445 }
446 return true;
447 }
448
RunTest(jobject testModuleObj,AMidiDevice * sendDevice,AMidiDevice * receiveDevice,bool throttleData)449 bool MidiTestManager::RunTest(jobject testModuleObj, AMidiDevice* sendDevice,
450 AMidiDevice* receiveDevice, bool throttleData) {
451 if (DEBUG) {
452 ALOGI("RunTest(%p, %p, %p)", testModuleObj, sendDevice, receiveDevice);
453 }
454
455 mThrottleData = throttleData;
456
457 JNIEnv* env;
458 mJvm->AttachCurrentThread(&env, NULL);
459 if (env == NULL) {
460 EndTest(TESTSTATUS_FAILED_JNI);
461 return false; // bail
462 }
463
464 if (!setupMessages()) {
465 EndTest(TESTSTATUS_FAILED_SETUP);
466 return false; // bail
467 }
468 buildMatchStream();
469
470 mTestModuleObj = env->NewGlobalRef(testModuleObj);
471
472 // Call StartWriting first because StartReading starts a thread.
473 if (!StartWriting(sendDevice) || !StartReading(receiveDevice)) {
474 // Test call to EndTest will close any open devices.
475 EndTest(TESTSTATUS_FAILED_DEVICE);
476 return false; // bail
477 }
478
479 int bytesSent = sendMessages();
480 void* threadRetval = (void*)TESTSTATUS_NOTRUN;
481 int status = pthread_join(readThread, &threadRetval);
482
483 // If send fails, the test should still wait for the receiver port to timeout to flush buffers.
484 if (bytesSent < 0) {
485 EndTest(TESTSTATUS_FAILED_SEND);
486 return false;
487 }
488
489 if (status != 0) {
490 ALOGE("Failed to join readThread: %s (%d)", strerror(status), status);
491 }
492 EndTest(static_cast<int>(reinterpret_cast<intptr_t>(threadRetval)));
493 return true;
494 }
495
EndTest(int endCode)496 void MidiTestManager::EndTest(int endCode) {
497
498 JNIEnv* env;
499 mJvm->AttachCurrentThread(&env, NULL);
500 if (env == NULL) {
501 ALOGE("Error retrieving JNI Env");
502 }
503
504 env->CallVoidMethod(mTestModuleObj, mMidEndTest, endCode);
505 env->DeleteGlobalRef(mTestModuleObj);
506
507 // EndTest() will ALWAYS be called, so we can close the ports here.
508 if (mMidiSendPort != NULL) {
509 AMidiInputPort_close(mMidiSendPort);
510 mMidiSendPort = NULL;
511 }
512 if (mMidiReceivePort != NULL) {
513 AMidiOutputPort_close(mMidiReceivePort);
514 mMidiReceivePort = NULL;
515 }
516 }
517