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