You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
428 lines
13 KiB
428 lines
13 KiB
/*
|
|
* 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 <stdio.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__)
|
|
#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, 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))
|
|
|
|
uint8_t warmupMsg[] = {makeMIDICmd(kMIDIChanCmd_Control, 0), 0, 0};
|
|
uint8_t msg0[] = {makeMIDICmd(kMIDIChanCmd_KeyDown, 0), 64, 120};
|
|
uint8_t msg1[] = {makeMIDICmd(kMIDIChanCmd_KeyUp, 0), 64, 35};
|
|
|
|
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),
|
|
mReceiveStreamPos(0),
|
|
mMidiSendPort(NULL), mMidiReceivePort(NULL),
|
|
mTestMsgs(NULL), mNumTestMsgs(0),
|
|
mThrottleData(false)
|
|
{}
|
|
|
|
MidiTestManager::~MidiTestManager() {
|
|
mMatchStream.clear();
|
|
}
|
|
|
|
void MidiTestManager::jniSetup(JNIEnv* env) {
|
|
env->GetJavaVM(&mJvm);
|
|
|
|
jclass clsMidiTestModule =
|
|
env->FindClass("com/android/cts/verifier/audio/MidiNativeTestActivity$NativeMidiTestModule");
|
|
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::buildMatchStream() {
|
|
mMatchStream.clear();
|
|
for(int byteIndex = 0; byteIndex < sizeof(warmupMsg); byteIndex++) {
|
|
mMatchStream.push_back(warmupMsg[byteIndex]);
|
|
}
|
|
for(int msgIndex = 0; msgIndex < mNumTestMsgs; msgIndex++) {
|
|
for(int byteIndex = 0; byteIndex < mTestMsgs[msgIndex].mNumMsgBytes; byteIndex++) {
|
|
mMatchStream.push_back(mTestMsgs[msgIndex].mMsgBytes[byteIndex]);
|
|
}
|
|
}
|
|
|
|
// Reset stream position
|
|
mReceiveStreamPos = 0;
|
|
}
|
|
|
|
static void logBytes(uint8_t* bytes, int count) {
|
|
int buffSize = (count * 6) + 1; // count of "0x??, " + '\0';
|
|
|
|
char* logBuff = new char[buffSize];
|
|
for (int dataIndex = 0; dataIndex < count; dataIndex++) {
|
|
sprintf(logBuff + (dataIndex * 6), "0x%.2X", bytes[dataIndex]);
|
|
if (dataIndex < count - 1) {
|
|
sprintf(logBuff + (dataIndex * 6) + 4, ", ");
|
|
}
|
|
}
|
|
ALOGD("logbytes(%d): %s", count, logBuff);
|
|
delete[] logBuff;
|
|
}
|
|
|
|
/**
|
|
* Compares the supplied bytes against the sent message stream at the current position
|
|
* and advances the stream position.
|
|
*/
|
|
bool MidiTestManager::matchStream(uint8_t* bytes, int count) {
|
|
if (DEBUG) {
|
|
ALOGI("---- matchStream() count:%d", count);
|
|
}
|
|
|
|
// a little bit of checking here...
|
|
if (count < 0) {
|
|
ALOGE("Negative Byte Count in MidiTestManager::matchStream()");
|
|
return false;
|
|
}
|
|
|
|
if (count > MESSAGE_MAX_BYTES) {
|
|
ALOGE("Too Large Byte Count (%d) in MidiTestManager::matchStream()", count);
|
|
return false;
|
|
}
|
|
|
|
bool matches = true;
|
|
for (int index = 0; index < count; index++) {
|
|
// Check for buffer overflow
|
|
if (mReceiveStreamPos >= mMatchStream.size()) {
|
|
ALOGD("matchStream() out-of-bounds @%d", mReceiveStreamPos);
|
|
matches = false;
|
|
break;
|
|
}
|
|
|
|
if (bytes[index] != mMatchStream[mReceiveStreamPos]) {
|
|
matches = false;
|
|
ALOGD("---- mismatch @%d [rec:0x%X : exp:0x%X]",
|
|
index, bytes[index], mMatchStream[mReceiveStreamPos]);
|
|
}
|
|
mReceiveStreamPos++;
|
|
}
|
|
|
|
if (DEBUG) {
|
|
ALOGI(" returns:%d", matches);
|
|
}
|
|
|
|
if (!matches) {
|
|
ALOGD("Mismatched Received Data:");
|
|
logBytes(bytes, count);
|
|
}
|
|
|
|
return matches;
|
|
}
|
|
|
|
#define THROTTLE_PERIOD_MS 10
|
|
|
|
int portSend(AMidiInputPort* sendPort, uint8_t* msg, int length, bool throttle) {
|
|
|
|
int numSent = 0;
|
|
if (throttle) {
|
|
for(int index = 0; index < length; index++) {
|
|
AMidiInputPort_send(sendPort, msg + index, 1);
|
|
usleep(THROTTLE_PERIOD_MS * 1000);
|
|
}
|
|
numSent = length;
|
|
} else {
|
|
numSent = AMidiInputPort_send(sendPort, msg, length);
|
|
}
|
|
return numSent;
|
|
}
|
|
|
|
/**
|
|
* 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]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Send "Warm-up" message
|
|
portSend(mMidiSendPort, warmupMsg, sizeof(warmupMsg), mThrottleData);
|
|
|
|
int totalSent = 0;
|
|
for(int msgIndex = 0; msgIndex < mNumTestMsgs; msgIndex++) {
|
|
ssize_t numSent =
|
|
portSend(mMidiSendPort, mTestMsgs[msgIndex].mMsgBytes, mTestMsgs[msgIndex].mNumMsgBytes,
|
|
mThrottleData);
|
|
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 (DEBUG && numBytesReceived > 0) {
|
|
logBytes(readBuffer, numBytesReceived);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
// Check first byte for warm-up message
|
|
if (totalNumReceived == 0 && readBuffer[0] != makeMIDICmd(kMIDIChanCmd_Control, 0)) {
|
|
// advance stream past the "warm up" message
|
|
mReceiveStreamPos += sizeof(warmupMsg);
|
|
if (DEBUG) {
|
|
ALOGD("---- No Warm Up Message Detected.");
|
|
}
|
|
}
|
|
|
|
if (!matchStream(readBuffer, numBytesReceived)) {
|
|
testResult = TESTSTATUS_FAILED_MISMATCH;
|
|
if (DEBUG) {
|
|
ALOGE("---- TESTSTATUS_FAILED_MISMATCH");
|
|
}
|
|
testRunning = false; // bail
|
|
}
|
|
totalNumReceived += numBytesReceived;
|
|
|
|
if (totalNumReceived > mMatchStream.size()) {
|
|
testResult = TESTSTATUS_FAILED_OVERRUN;
|
|
if (DEBUG) {
|
|
ALOGE("---- TESTSTATUS_FAILED_OVERRUN");
|
|
}
|
|
testRunning = false; // bail
|
|
}
|
|
if (totalNumReceived == mMatchStream.size()) {
|
|
testResult = TESTSTATUS_PASSED;
|
|
if (DEBUG) {
|
|
ALOGE("---- TESTSTATUS_PASSED");
|
|
}
|
|
testRunning = false; // done
|
|
}
|
|
}
|
|
}
|
|
|
|
return testResult;
|
|
}
|
|
|
|
bool MidiTestManager::StartReading(AMidiDevice* nativeReadDevice) {
|
|
if (DEBUG) {
|
|
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;
|
|
}
|
|
|
|
bool MidiTestManager::RunTest(jobject testModuleObj, AMidiDevice* sendDevice,
|
|
AMidiDevice* receiveDevice, bool throttleData) {
|
|
if (DEBUG) {
|
|
ALOGI("RunTest(%p, %p, %p)", testModuleObj, sendDevice, receiveDevice);
|
|
}
|
|
|
|
mThrottleData = throttleData;
|
|
|
|
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 = 32;
|
|
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;
|
|
|
|
buildMatchStream();
|
|
|
|
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;
|
|
}
|
|
}
|