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