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