| /* |
| * Copyright (C) 2018 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| #include <cstring> |
| #include <pthread.h> |
| #include <unistd.h> |
| |
| #include <midi.h> |
| |
| #define TAG "MidiTestManager" |
| #include <android/log.h> |
| #define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__) |
| #define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__) |
| |
| #include "MidiTestManager.h" |
| |
| static pthread_t readThread; |
| |
| static const bool DEBUG = false; |
| static const bool DEBUG_MIDIDATA = false; |
| |
| // |
| // MIDI Messages |
| // |
| // Channel Commands |
| static const uint8_t kMIDIChanCmd_KeyDown = 9; |
| static const uint8_t kMIDIChanCmd_KeyUp = 8; |
| static const uint8_t kMIDIChanCmd_PolyPress = 10; |
| static const uint8_t kMIDIChanCmd_Control = 11; |
| static const uint8_t kMIDIChanCmd_ProgramChange = 12; |
| static const uint8_t kMIDIChanCmd_ChannelPress = 13; |
| static const uint8_t kMIDIChanCmd_PitchWheel = 14; |
| // System Commands |
| static const uint8_t kMIDISysCmd_SysEx = 0xF0; |
| static const uint8_t kMIDISysCmd_EndOfSysEx = 0xF7; |
| static const uint8_t kMIDISysCmd_ActiveSensing = 0xFE; |
| static const uint8_t kMIDISysCmd_Reset = 0xFF; |
| |
| static void* readThreadRoutine(void * context) { |
| MidiTestManager* testManager = (MidiTestManager*)context; |
| return reinterpret_cast<void*>(static_cast<intptr_t>(testManager->ProcessInput())); |
| } |
| |
| /* |
| * TestMessage |
| */ |
| #define makeMIDICmd(cmd, channel) (uint8_t)((cmd << 4) | (channel & 0x0F)) |
| |
| class TestMessage { |
| public: |
| uint8_t* mMsgBytes; |
| int mNumMsgBytes; |
| |
| TestMessage() |
| : mMsgBytes(NULL), mNumMsgBytes(0) |
| {} |
| |
| ~TestMessage() { |
| delete[] mMsgBytes; |
| } |
| |
| bool set(uint8_t* msgBytes, int numMsgBytes) { |
| if (msgBytes == NULL || numMsgBytes <= 0) { |
| return false; |
| } |
| mNumMsgBytes = numMsgBytes; |
| mMsgBytes = new uint8_t[numMsgBytes]; |
| memcpy(mMsgBytes, msgBytes, mNumMsgBytes * sizeof(uint8_t)); |
| return true; |
| } |
| }; /* class TestMessage */ |
| |
| /* |
| * MidiTestManager |
| */ |
| MidiTestManager::MidiTestManager() |
| : mTestModuleObj(NULL), |
| mTestStream(NULL), mNumTestStreamBytes(0), |
| mReceiveStreamPos(0), |
| mMidiSendPort(NULL), mMidiReceivePort(NULL), |
| mTestMsgs(NULL), mNumTestMsgs(0) |
| {} |
| |
| MidiTestManager::~MidiTestManager(){ |
| delete[] mTestStream; |
| } |
| |
| void MidiTestManager::jniSetup(JNIEnv* env) { |
| env->GetJavaVM(&mJvm); |
| |
| jclass clsMidiTestModule = |
| env->FindClass("com/android/cts/verifier/audio/NDKMidiActivity$NDKMidiTestModule"); |
| if (DEBUG) { |
| ALOGI("gClsMidiTestModule:%p", clsMidiTestModule); |
| } |
| |
| // public void endTest(int endCode) |
| mMidEndTest = env->GetMethodID(clsMidiTestModule, "endTest", "(I)V"); |
| if (DEBUG) { |
| ALOGI("mMidEndTestgMidEndTest:%p", mMidEndTest); |
| } |
| } |
| |
| void MidiTestManager::buildTestStream() { |
| // add up the total space |
| mNumTestStreamBytes = 0; |
| for(int msgIndex = 0; msgIndex < mNumTestMsgs; msgIndex++) { |
| mNumTestStreamBytes += mTestMsgs[msgIndex].mNumMsgBytes; |
| } |
| |
| delete[] mTestStream; |
| mTestStream = new uint8_t[mNumTestStreamBytes]; |
| int streamIndex = 0; |
| for(int msgIndex = 0; msgIndex < mNumTestMsgs; msgIndex++) { |
| for(int byteIndex = 0; byteIndex < mTestMsgs[msgIndex].mNumMsgBytes; byteIndex++) { |
| mTestStream[streamIndex++] = mTestMsgs[msgIndex].mMsgBytes[byteIndex]; |
| } |
| } |
| |
| // Reset stream position |
| mReceiveStreamPos = 0; |
| } |
| |
| /** |
| * Compares the supplied bytes against the sent message stream at the current postion |
| * and advances the stream position. |
| */ |
| bool MidiTestManager::matchStream(uint8_t* bytes, int count) { |
| if (DEBUG) { |
| ALOGI("---- matchStream() count:%d", count); |
| } |
| bool matches = true; |
| |
| for (int index = 0; index < count; index++) { |
| if (bytes[index] != mTestStream[mReceiveStreamPos]) { |
| matches = false; |
| if (DEBUG) { |
| ALOGI("---- mismatch @%d [%d : %d]", |
| index, bytes[index], mTestStream[mReceiveStreamPos]); |
| } |
| } |
| mReceiveStreamPos++; |
| } |
| |
| if (DEBUG) { |
| ALOGI(" returns:%d", matches); |
| } |
| return matches; |
| } |
| |
| /** |
| * Writes out the list of MIDI messages to the output port. |
| * Returns total number of bytes sent. |
| */ |
| int MidiTestManager::sendMessages() { |
| if (DEBUG) { |
| ALOGI("---- sendMessages()..."); |
| for(int msgIndex = 0; msgIndex < mNumTestMsgs; msgIndex++) { |
| if (DEBUG_MIDIDATA) { |
| ALOGI("--------"); |
| for(int byteIndex = 0; byteIndex < mTestMsgs[msgIndex].mNumMsgBytes; byteIndex++) { |
| ALOGI(" 0x%X", mTestMsgs[msgIndex].mMsgBytes[byteIndex]); |
| } |
| } |
| } |
| } |
| |
| int totalSent = 0; |
| for(int msgIndex = 0; msgIndex < mNumTestMsgs; msgIndex++) { |
| ssize_t numSent = |
| AMidiInputPort_send(mMidiSendPort, |
| mTestMsgs[msgIndex].mMsgBytes, mTestMsgs[msgIndex].mNumMsgBytes); |
| totalSent += numSent; |
| } |
| |
| if (DEBUG) { |
| ALOGI("---- totalSent:%d", totalSent); |
| } |
| |
| return totalSent; |
| } |
| |
| int MidiTestManager::ProcessInput() { |
| uint8_t readBuffer[128]; |
| size_t totalNumReceived = 0; |
| |
| bool testRunning = true; |
| int testResult = TESTSTATUS_NOTRUN; |
| |
| int32_t opCode; |
| size_t numBytesReceived; |
| int64_t timeStamp; |
| while (testRunning) { |
| // AMidiOutputPort_receive is non-blocking, so let's not burn up the CPU unnecessarily |
| usleep(2000); |
| |
| numBytesReceived = 0; |
| ssize_t numMessagesReceived = |
| AMidiOutputPort_receive(mMidiReceivePort, &opCode, readBuffer, 128, |
| &numBytesReceived, &timeStamp); |
| |
| if (testRunning && |
| numBytesReceived > 0 && |
| opCode == AMIDI_OPCODE_DATA && |
| readBuffer[0] != kMIDISysCmd_ActiveSensing && |
| readBuffer[0] != kMIDISysCmd_Reset) { |
| if (DEBUG) { |
| ALOGI("---- msgs:%zd, bytes:%zu", numMessagesReceived, numBytesReceived); |
| } |
| // Process Here |
| if (!matchStream(readBuffer, numBytesReceived)) { |
| testResult = TESTSTATUS_FAILED_MISMATCH; |
| testRunning = false; // bail |
| } |
| totalNumReceived += numBytesReceived; |
| if (totalNumReceived > mNumTestStreamBytes) { |
| testResult = TESTSTATUS_FAILED_OVERRUN; |
| testRunning = false; // bail |
| } |
| if (totalNumReceived == mNumTestStreamBytes) { |
| testResult = TESTSTATUS_PASSED; |
| testRunning = false; // done |
| } |
| } |
| } |
| |
| return testResult; |
| } |
| |
| bool MidiTestManager::StartReading(AMidiDevice* nativeReadDevice) { |
| ALOGI("StartReading()..."); |
| |
| media_status_t m_status = |
| AMidiOutputPort_open(nativeReadDevice, 0, &mMidiReceivePort); |
| if (m_status != 0) { |
| ALOGE("Can't open MIDI device for reading err:%d", m_status); |
| return false; |
| } |
| |
| // Start read thread |
| int status = pthread_create(&readThread, NULL, readThreadRoutine, this); |
| if (status != 0) { |
| ALOGE("Can't start readThread: %s (%d)", strerror(status), status); |
| } |
| return status == 0; |
| } |
| |
| bool MidiTestManager::StartWriting(AMidiDevice* nativeWriteDevice) { |
| ALOGI("StartWriting()..."); |
| |
| media_status_t status = |
| AMidiInputPort_open(nativeWriteDevice, 0, &mMidiSendPort); |
| if (status != 0) { |
| ALOGE("Can't open MIDI device for writing err:%d", status); |
| return false; |
| } |
| return true; |
| } |
| |
| uint8_t msg0[] = {makeMIDICmd(kMIDIChanCmd_KeyDown, 0), 64, 120}; |
| //uint8_t msg0Alt[] = {makeMIDICmd(kMIDIChanCmd_KeyDown, 0), 65, 120}; |
| uint8_t msg1[] = {makeMIDICmd(kMIDIChanCmd_KeyUp, 0), 64, 35}; |
| |
| bool MidiTestManager::RunTest(jobject testModuleObj, AMidiDevice* sendDevice, |
| AMidiDevice* receiveDevice) { |
| if (DEBUG) { |
| ALOGI("RunTest(%p, %p, %p)", testModuleObj, sendDevice, receiveDevice); |
| } |
| |
| JNIEnv* env; |
| mJvm->AttachCurrentThread(&env, NULL); |
| if (env == NULL) { |
| EndTest(TESTSTATUS_FAILED_JNI); |
| } |
| |
| mTestModuleObj = env->NewGlobalRef(testModuleObj); |
| |
| // Call StartWriting first because StartReading starts a thread. |
| if (!StartWriting(sendDevice) || !StartReading(receiveDevice)) { |
| // Test call to EndTest will close any open devices. |
| EndTest(TESTSTATUS_FAILED_DEVICE); |
| return false; // bail |
| } |
| |
| // setup messages |
| delete[] mTestMsgs; |
| mNumTestMsgs = 3; |
| mTestMsgs = new TestMessage[mNumTestMsgs]; |
| |
| int sysExSize = 8; |
| uint8_t* sysExMsg = new uint8_t[sysExSize]; |
| sysExMsg[0] = kMIDISysCmd_SysEx; |
| for(int index = 1; index < sysExSize-1; index++) { |
| sysExMsg[index] = (uint8_t)index; |
| } |
| sysExMsg[sysExSize-1] = kMIDISysCmd_EndOfSysEx; |
| |
| if (!mTestMsgs[0].set(msg0, sizeof(msg0)) || |
| !mTestMsgs[1].set(msg1, sizeof(msg1)) || |
| !mTestMsgs[2].set(sysExMsg, sysExSize)) { |
| return false; |
| } |
| delete[] sysExMsg; |
| |
| buildTestStream(); |
| |
| // Inject an error |
| // mTestMsgs[0].set(msg0Alt, 3); |
| |
| sendMessages(); |
| void* threadRetval = (void*)TESTSTATUS_NOTRUN; |
| int status = pthread_join(readThread, &threadRetval); |
| if (status != 0) { |
| ALOGE("Failed to join readThread: %s (%d)", strerror(status), status); |
| } |
| EndTest(static_cast<int>(reinterpret_cast<intptr_t>(threadRetval))); |
| return true; |
| } |
| |
| void MidiTestManager::EndTest(int endCode) { |
| |
| JNIEnv* env; |
| mJvm->AttachCurrentThread(&env, NULL); |
| if (env == NULL) { |
| ALOGE("Error retrieving JNI Env"); |
| } |
| |
| env->CallVoidMethod(mTestModuleObj, mMidEndTest, endCode); |
| env->DeleteGlobalRef(mTestModuleObj); |
| |
| // EndTest() will ALWAYS be called, so we can close the ports here. |
| if (mMidiSendPort != NULL) { |
| AMidiInputPort_close(mMidiSendPort); |
| mMidiSendPort = NULL; |
| } |
| if (mMidiReceivePort != NULL) { |
| AMidiOutputPort_close(mMidiReceivePort); |
| mMidiReceivePort = NULL; |
| } |
| } |