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
20 #define TAG "MidiTestManager"
21 #include <android/log.h>
22 #define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
23 #define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
24
25 #include "MidiTestManager.h"
26
27 static pthread_t readThread;
28
29 static const bool DEBUG = false;
30 static const bool DEBUG_MIDIDATA = false;
31
32 //
33 // MIDI Messages
34 //
35 // Channel Commands
36 static const uint8_t kMIDIChanCmd_KeyDown = 9;
37 static const uint8_t kMIDIChanCmd_KeyUp = 8;
38 static const uint8_t kMIDIChanCmd_PolyPress = 10;
39 static const uint8_t kMIDIChanCmd_Control = 11;
40 static const uint8_t kMIDIChanCmd_ProgramChange = 12;
41 static const uint8_t kMIDIChanCmd_ChannelPress = 13;
42 static const uint8_t kMIDIChanCmd_PitchWheel = 14;
43 // System Commands
44 static const uint8_t kMIDISysCmd_SysEx = 0xF0;
45 static const uint8_t kMIDISysCmd_EndOfSysEx = 0xF7;
46 static const uint8_t kMIDISysCmd_ActiveSensing = 0xFE;
47 static const uint8_t kMIDISysCmd_Reset = 0xFF;
48
readThreadRoutine(void * context)49 static void* readThreadRoutine(void * context) {
50 MidiTestManager* testManager = (MidiTestManager*)context;
51 return reinterpret_cast<void*>(static_cast<intptr_t>(testManager->ProcessInput()));
52 }
53
54 /*
55 * TestMessage
56 */
57 #define makeMIDICmd(cmd, channel) (uint8_t)((cmd << 4) | (channel & 0x0F))
58
59 class TestMessage {
60 public:
61 uint8_t* mMsgBytes;
62 int mNumMsgBytes;
63
TestMessage()64 TestMessage()
65 : mMsgBytes(NULL), mNumMsgBytes(0)
66 {}
67
~TestMessage()68 ~TestMessage() {
69 delete[] mMsgBytes;
70 }
71
set(uint8_t * msgBytes,int numMsgBytes)72 bool set(uint8_t* msgBytes, int numMsgBytes) {
73 if (msgBytes == NULL || numMsgBytes <= 0) {
74 return false;
75 }
76 mNumMsgBytes = numMsgBytes;
77 mMsgBytes = new uint8_t[numMsgBytes];
78 memcpy(mMsgBytes, msgBytes, mNumMsgBytes * sizeof(uint8_t));
79 return true;
80 }
81 }; /* class TestMessage */
82
83 /*
84 * MidiTestManager
85 */
MidiTestManager()86 MidiTestManager::MidiTestManager()
87 : mTestModuleObj(NULL),
88 mTestStream(NULL), mNumTestStreamBytes(0),
89 mReceiveStreamPos(0),
90 mMidiSendPort(NULL), mMidiReceivePort(NULL),
91 mTestMsgs(NULL), mNumTestMsgs(0)
92 {}
93
~MidiTestManager()94 MidiTestManager::~MidiTestManager(){
95 delete[] mTestStream;
96 }
97
jniSetup(JNIEnv * env)98 void MidiTestManager::jniSetup(JNIEnv* env) {
99 env->GetJavaVM(&mJvm);
100
101 jclass clsMidiTestModule =
102 env->FindClass("com/android/cts/verifier/audio/NDKMidiActivity$NDKMidiTestModule");
103 if (DEBUG) {
104 ALOGI("gClsMidiTestModule:%p", clsMidiTestModule);
105 }
106
107 // public void endTest(int endCode)
108 mMidEndTest = env->GetMethodID(clsMidiTestModule, "endTest", "(I)V");
109 if (DEBUG) {
110 ALOGI("mMidEndTestgMidEndTest:%p", mMidEndTest);
111 }
112 }
113
buildTestStream()114 void MidiTestManager::buildTestStream() {
115 // add up the total space
116 mNumTestStreamBytes = 0;
117 for(int msgIndex = 0; msgIndex < mNumTestMsgs; msgIndex++) {
118 mNumTestStreamBytes += mTestMsgs[msgIndex].mNumMsgBytes;
119 }
120
121 delete[] mTestStream;
122 mTestStream = new uint8_t[mNumTestStreamBytes];
123 int streamIndex = 0;
124 for(int msgIndex = 0; msgIndex < mNumTestMsgs; msgIndex++) {
125 for(int byteIndex = 0; byteIndex < mTestMsgs[msgIndex].mNumMsgBytes; byteIndex++) {
126 mTestStream[streamIndex++] = mTestMsgs[msgIndex].mMsgBytes[byteIndex];
127 }
128 }
129
130 // Reset stream position
131 mReceiveStreamPos = 0;
132 }
133
134 /**
135 * Compares the supplied bytes against the sent message stream at the current postion
136 * and advances the stream position.
137 */
matchStream(uint8_t * bytes,int count)138 bool MidiTestManager::matchStream(uint8_t* bytes, int count) {
139 if (DEBUG) {
140 ALOGI("---- matchStream() count:%d", count);
141 }
142 bool matches = true;
143
144 for (int index = 0; index < count; index++) {
145 if (bytes[index] != mTestStream[mReceiveStreamPos]) {
146 matches = false;
147 if (DEBUG) {
148 ALOGI("---- mismatch @%d [%d : %d]",
149 index, bytes[index], mTestStream[mReceiveStreamPos]);
150 }
151 }
152 mReceiveStreamPos++;
153 }
154
155 if (DEBUG) {
156 ALOGI(" returns:%d", matches);
157 }
158 return matches;
159 }
160
161 /**
162 * Writes out the list of MIDI messages to the output port.
163 * Returns total number of bytes sent.
164 */
sendMessages()165 int MidiTestManager::sendMessages() {
166 if (DEBUG) {
167 ALOGI("---- sendMessages()...");
168 for(int msgIndex = 0; msgIndex < mNumTestMsgs; msgIndex++) {
169 if (DEBUG_MIDIDATA) {
170 ALOGI("--------");
171 for(int byteIndex = 0; byteIndex < mTestMsgs[msgIndex].mNumMsgBytes; byteIndex++) {
172 ALOGI(" 0x%X", mTestMsgs[msgIndex].mMsgBytes[byteIndex]);
173 }
174 }
175 }
176 }
177
178 int totalSent = 0;
179 for(int msgIndex = 0; msgIndex < mNumTestMsgs; msgIndex++) {
180 ssize_t numSent =
181 AMidiInputPort_send(mMidiSendPort,
182 mTestMsgs[msgIndex].mMsgBytes, mTestMsgs[msgIndex].mNumMsgBytes);
183 totalSent += numSent;
184 }
185
186 if (DEBUG) {
187 ALOGI("---- totalSent:%d", totalSent);
188 }
189
190 return totalSent;
191 }
192
ProcessInput()193 int MidiTestManager::ProcessInput() {
194 uint8_t readBuffer[128];
195 size_t totalNumReceived = 0;
196
197 bool testRunning = true;
198 int testResult = TESTSTATUS_NOTRUN;
199
200 int32_t opCode;
201 size_t numBytesReceived;
202 int64_t timeStamp;
203 while (testRunning) {
204 // AMidiOutputPort_receive is non-blocking, so let's not burn up the CPU unnecessarily
205 usleep(2000);
206
207 numBytesReceived = 0;
208 ssize_t numMessagesReceived =
209 AMidiOutputPort_receive(mMidiReceivePort, &opCode, readBuffer, 128,
210 &numBytesReceived, &timeStamp);
211
212 if (testRunning &&
213 numBytesReceived > 0 &&
214 opCode == AMIDI_OPCODE_DATA &&
215 readBuffer[0] != kMIDISysCmd_ActiveSensing &&
216 readBuffer[0] != kMIDISysCmd_Reset) {
217 if (DEBUG) {
218 ALOGI("---- msgs:%zd, bytes:%zu", numMessagesReceived, numBytesReceived);
219 }
220 // Process Here
221 if (!matchStream(readBuffer, numBytesReceived)) {
222 testResult = TESTSTATUS_FAILED_MISMATCH;
223 testRunning = false; // bail
224 }
225 totalNumReceived += numBytesReceived;
226 if (totalNumReceived > mNumTestStreamBytes) {
227 testResult = TESTSTATUS_FAILED_OVERRUN;
228 testRunning = false; // bail
229 }
230 if (totalNumReceived == mNumTestStreamBytes) {
231 testResult = TESTSTATUS_PASSED;
232 testRunning = false; // done
233 }
234 }
235 }
236
237 return testResult;
238 }
239
StartReading(AMidiDevice * nativeReadDevice)240 bool MidiTestManager::StartReading(AMidiDevice* nativeReadDevice) {
241 ALOGI("StartReading()...");
242
243 media_status_t m_status =
244 AMidiOutputPort_open(nativeReadDevice, 0, &mMidiReceivePort);
245 if (m_status != 0) {
246 ALOGE("Can't open MIDI device for reading err:%d", m_status);
247 return false;
248 }
249
250 // Start read thread
251 int status = pthread_create(&readThread, NULL, readThreadRoutine, this);
252 if (status != 0) {
253 ALOGE("Can't start readThread: %s (%d)", strerror(status), status);
254 }
255 return status == 0;
256 }
257
StartWriting(AMidiDevice * nativeWriteDevice)258 bool MidiTestManager::StartWriting(AMidiDevice* nativeWriteDevice) {
259 ALOGI("StartWriting()...");
260
261 media_status_t status =
262 AMidiInputPort_open(nativeWriteDevice, 0, &mMidiSendPort);
263 if (status != 0) {
264 ALOGE("Can't open MIDI device for writing err:%d", status);
265 return false;
266 }
267 return true;
268 }
269
270 uint8_t msg0[] = {makeMIDICmd(kMIDIChanCmd_KeyDown, 0), 64, 120};
271 //uint8_t msg0Alt[] = {makeMIDICmd(kMIDIChanCmd_KeyDown, 0), 65, 120};
272 uint8_t msg1[] = {makeMIDICmd(kMIDIChanCmd_KeyUp, 0), 64, 35};
273
RunTest(jobject testModuleObj,AMidiDevice * sendDevice,AMidiDevice * receiveDevice)274 bool MidiTestManager::RunTest(jobject testModuleObj, AMidiDevice* sendDevice,
275 AMidiDevice* receiveDevice) {
276 if (DEBUG) {
277 ALOGI("RunTest(%p, %p, %p)", testModuleObj, sendDevice, receiveDevice);
278 }
279
280 JNIEnv* env;
281 mJvm->AttachCurrentThread(&env, NULL);
282 if (env == NULL) {
283 EndTest(TESTSTATUS_FAILED_JNI);
284 }
285
286 mTestModuleObj = env->NewGlobalRef(testModuleObj);
287
288 // Call StartWriting first because StartReading starts a thread.
289 if (!StartWriting(sendDevice) || !StartReading(receiveDevice)) {
290 // Test call to EndTest will close any open devices.
291 EndTest(TESTSTATUS_FAILED_DEVICE);
292 return false; // bail
293 }
294
295 // setup messages
296 delete[] mTestMsgs;
297 mNumTestMsgs = 3;
298 mTestMsgs = new TestMessage[mNumTestMsgs];
299
300 int sysExSize = 8;
301 uint8_t* sysExMsg = new uint8_t[sysExSize];
302 sysExMsg[0] = kMIDISysCmd_SysEx;
303 for(int index = 1; index < sysExSize-1; index++) {
304 sysExMsg[index] = (uint8_t)index;
305 }
306 sysExMsg[sysExSize-1] = kMIDISysCmd_EndOfSysEx;
307
308 if (!mTestMsgs[0].set(msg0, sizeof(msg0)) ||
309 !mTestMsgs[1].set(msg1, sizeof(msg1)) ||
310 !mTestMsgs[2].set(sysExMsg, sysExSize)) {
311 return false;
312 }
313 delete[] sysExMsg;
314
315 buildTestStream();
316
317 // Inject an error
318 // mTestMsgs[0].set(msg0Alt, 3);
319
320 sendMessages();
321 void* threadRetval = (void*)TESTSTATUS_NOTRUN;
322 int status = pthread_join(readThread, &threadRetval);
323 if (status != 0) {
324 ALOGE("Failed to join readThread: %s (%d)", strerror(status), status);
325 }
326 EndTest(static_cast<int>(reinterpret_cast<intptr_t>(threadRetval)));
327 return true;
328 }
329
EndTest(int endCode)330 void MidiTestManager::EndTest(int endCode) {
331
332 JNIEnv* env;
333 mJvm->AttachCurrentThread(&env, NULL);
334 if (env == NULL) {
335 ALOGE("Error retrieving JNI Env");
336 }
337
338 env->CallVoidMethod(mTestModuleObj, mMidEndTest, endCode);
339 env->DeleteGlobalRef(mTestModuleObj);
340
341 // EndTest() will ALWAYS be called, so we can close the ports here.
342 if (mMidiSendPort != NULL) {
343 AMidiInputPort_close(mMidiSendPort);
344 mMidiSendPort = NULL;
345 }
346 if (mMidiReceivePort != NULL) {
347 AMidiOutputPort_close(mMidiReceivePort);
348 mMidiReceivePort = NULL;
349 }
350 }
351