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