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