BluetoothMidi test: test encoder and decoder
Also test MidiFramer Add end-to-end test through encoder and decoder. Add test for reserved bit in header. Bug: 35669198 Bug: 140638458 Bug: 149927520 Test: atest BluetoothMidiTests Change-Id: I767deab6847d2b45e01bed29f5eed3df0f0e46c0 Merged-In: I767deab6847d2b45e01bed29f5eed3df0f0e46c0
This commit is contained in:
@@ -1,6 +1,35 @@
|
||||
//
|
||||
// Copyright (C) 2019 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.
|
||||
//
|
||||
|
||||
android_library {
|
||||
name: "BluetoothMidiLib",
|
||||
srcs: [
|
||||
"src/**/*.java",
|
||||
],
|
||||
platform_apis: true,
|
||||
plugins: ["java_api_finder"],
|
||||
manifest: "AndroidManifestBase.xml",
|
||||
}
|
||||
|
||||
android_app {
|
||||
name: "BluetoothMidiService",
|
||||
srcs: ["src/**/*.java"],
|
||||
srcs: [
|
||||
"src/**/*.java",
|
||||
],
|
||||
platform_apis: true,
|
||||
certificate: "platform",
|
||||
manifest: "AndroidManifest.xml",
|
||||
}
|
||||
|
||||
@@ -1,12 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
/*
|
||||
* Copyright (C) 2015 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.
|
||||
*/
|
||||
-->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.android.bluetoothmidiservice"
|
||||
android:versionCode="1"
|
||||
android:versionName="R-initial"
|
||||
>
|
||||
<uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29" />
|
||||
|
||||
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
|
||||
<uses-feature android:name="android.software.midi" android:required="true"/>
|
||||
<uses-permission android:name="android.permission.BLUETOOTH"/>
|
||||
|
||||
<application
|
||||
tools:replace="android:label"
|
||||
android:label="@string/app_name">
|
||||
<service android:name=".BluetoothMidiService"
|
||||
android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE">
|
||||
|
||||
30
media/packages/BluetoothMidiService/AndroidManifestBase.xml
Normal file
30
media/packages/BluetoothMidiService/AndroidManifestBase.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
/*
|
||||
* Copyright (C) 2020 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.
|
||||
*/
|
||||
-->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.android.bluetoothmidiservice"
|
||||
android:versionCode="1"
|
||||
android:versionName="R-initial"
|
||||
>
|
||||
<uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29" />
|
||||
<application
|
||||
android:label="BluetoothMidi"
|
||||
android:defaultToDeviceProtectedStorage="true"
|
||||
android:directBootAware="true">
|
||||
</application>
|
||||
</manifest>
|
||||
38
media/packages/BluetoothMidiService/tests/unit/Android.bp
Normal file
38
media/packages/BluetoothMidiService/tests/unit/Android.bp
Normal file
@@ -0,0 +1,38 @@
|
||||
//
|
||||
// Copyright (C) 2019 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.
|
||||
//
|
||||
|
||||
android_test {
|
||||
name: "BluetoothMidiTests",
|
||||
srcs: ["src/**/*.java"],
|
||||
certificate: "platform",
|
||||
static_libs: [
|
||||
//"frameworks-base-testutils",
|
||||
"android-support-test",
|
||||
"androidx.test.core",
|
||||
"androidx.test.ext.truth",
|
||||
"androidx.test.runner",
|
||||
"androidx.test.rules",
|
||||
"platform-test-annotations",
|
||||
"BluetoothMidiLib",
|
||||
],
|
||||
test_suites: ["device-tests"],
|
||||
libs: [
|
||||
"framework-res",
|
||||
"android.test.runner",
|
||||
"android.test.base",
|
||||
"android.test.mock",
|
||||
],
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2019 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.
|
||||
-->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.android.bluetoothmidiservice.tests.unit">
|
||||
<uses-sdk
|
||||
android:minSdkVersion="29"
|
||||
android:targetSdkVersion="29" />
|
||||
|
||||
<application android:testOnly="true">
|
||||
<uses-library android:name="android.test.runner" />
|
||||
</application>
|
||||
|
||||
<instrumentation
|
||||
android:name="androidx.test.runner.AndroidJUnitRunner"
|
||||
android:targetPackage="com.android.bluetoothmidiservice.tests.unit"
|
||||
android:label="Bluetooth MIDI Service tests">
|
||||
</instrumentation>
|
||||
</manifest>
|
||||
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2020 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.
|
||||
-->
|
||||
|
||||
<configuration description="Runs Bluetooth MIDI Service Tests.">
|
||||
<option name="test-suite-tag" value="apct" />
|
||||
<option name="test-suite-tag" value="apct-instrumentation" />
|
||||
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
|
||||
<option name="cleanup-apks" value="true" />
|
||||
<option name="install-arg" value="-t" />
|
||||
<option name="test-file-name" value="BluetoothMidiTests.apk" />
|
||||
</target_preparer>
|
||||
|
||||
<option name="test-tag" value="BLEMidiTests" />
|
||||
<test class="com.android.tradefed.testtype.AndroidJUnitTest">
|
||||
<option name="package" value="com.android.bluetoothmidiservice.tests.unit" />
|
||||
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
|
||||
<option name="hidden-api-checks" value="false" />
|
||||
</test>
|
||||
</configuration>
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.
|
||||
*/
|
||||
|
||||
package com.android.bluetoothmidiservice;
|
||||
|
||||
import android.media.midi.MidiReceiver;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.midi.MidiFramer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
class AccumulatingMidiReceiver extends MidiReceiver {
|
||||
private static final String TAG = "AccumulatingMidiReceiver";
|
||||
ArrayList<byte[]> mBuffers = new ArrayList<byte[]>();
|
||||
ArrayList<Long> mTimestamps = new ArrayList<Long>();
|
||||
|
||||
public void onSend(byte[] buffer, int offset, int count, long timestamp) {
|
||||
Log.d(TAG, "onSend() passed " + MidiFramer.formatMidiData(buffer, offset, count));
|
||||
byte[] actualRow = new byte[count];
|
||||
System.arraycopy(buffer, offset, actualRow, 0, count);
|
||||
mBuffers.add(actualRow);
|
||||
mTimestamps.add(timestamp);
|
||||
}
|
||||
|
||||
byte[][] getBuffers() {
|
||||
return mBuffers.toArray(new byte[mBuffers.size()][]);
|
||||
}
|
||||
|
||||
Long[] getTimestamps() {
|
||||
return mTimestamps.toArray(new Long[mTimestamps.size()]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,257 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.
|
||||
*/
|
||||
|
||||
package com.android.bluetoothmidiservice;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.internal.midi.MidiFramer;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.LinkedBlockingDeque;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* End to end testing of the Bluetooth Encoder and Decoder
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class BluetoothMidiCodecTest {
|
||||
|
||||
private static final String TAG = "BluetoothMidiCodecTest";
|
||||
private static final String[] PROVISIONING_APP_NAME = {"some", "app"};
|
||||
private static final long NANOS_PER_MSEC = 1000000L;
|
||||
|
||||
static class EncoderDecoderChecker implements PacketEncoder.PacketReceiver {
|
||||
BluetoothPacketEncoder mEncoder;
|
||||
BluetoothPacketDecoder mDecoder;
|
||||
AccumulatingMidiReceiver mReceiver;
|
||||
MidiFramer mFramer;
|
||||
AccumulatingMidiReceiver mBypassReceiver;
|
||||
MidiFramer mBypassFramer;
|
||||
int mMaxPacketsPerConnection;
|
||||
int mConnectionIntervalMillis;
|
||||
BlockingQueue<byte[]> mPacketQueue;
|
||||
ScheduledExecutorService mScheduler;
|
||||
|
||||
EncoderDecoderChecker() {
|
||||
this(2, 15, 20);
|
||||
}
|
||||
|
||||
EncoderDecoderChecker(
|
||||
int maxPacketsPerConnection,
|
||||
int connectionIntervalMillis,
|
||||
int maxBytesPerPacket) {
|
||||
mMaxPacketsPerConnection = maxPacketsPerConnection;
|
||||
mConnectionIntervalMillis = connectionIntervalMillis;
|
||||
mEncoder = new BluetoothPacketEncoder(this, maxBytesPerPacket);
|
||||
mDecoder = new BluetoothPacketDecoder(maxBytesPerPacket);
|
||||
mReceiver = new AccumulatingMidiReceiver();
|
||||
mFramer = new MidiFramer(mReceiver);
|
||||
mBypassReceiver = new AccumulatingMidiReceiver();
|
||||
mBypassFramer = new MidiFramer(mBypassReceiver);
|
||||
mScheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
mPacketQueue = new LinkedBlockingDeque<>(maxPacketsPerConnection);
|
||||
}
|
||||
|
||||
void processQueue() throws InterruptedException {
|
||||
for (int i = 0; i < mMaxPacketsPerConnection; i++) {
|
||||
byte[] packet = mPacketQueue.poll(0, TimeUnit.SECONDS);
|
||||
if (packet == null) break;
|
||||
Log.d(TAG, "decode " + MidiFramer.formatMidiData(packet, 0, packet.length));
|
||||
mDecoder.decodePacket(packet, mFramer);
|
||||
}
|
||||
Log.d(TAG, "call writeComplete()");
|
||||
mEncoder.writeComplete();
|
||||
}
|
||||
|
||||
public void start() {
|
||||
mScheduler.scheduleAtFixedRate(
|
||||
() -> {
|
||||
Log.d(TAG, "run scheduled task");
|
||||
try {
|
||||
processQueue();
|
||||
} catch (Exception e) {
|
||||
assertEquals(null, e);
|
||||
}
|
||||
},
|
||||
mConnectionIntervalMillis, // initial delay
|
||||
mConnectionIntervalMillis, // period
|
||||
TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
// TODO wait for queue to empty
|
||||
mScheduler.shutdown();
|
||||
}
|
||||
|
||||
// TODO Should this block?
|
||||
// Store the packets and then write them from a periodic task.
|
||||
@Override
|
||||
public void writePacket(byte[] buffer, int count) {
|
||||
Log.d(TAG, "writePacket() passed " + MidiFramer.formatMidiData(buffer, 0, count));
|
||||
byte[] packet = new byte[count];
|
||||
System.arraycopy(buffer, 0, packet, 0, count);
|
||||
try {
|
||||
mPacketQueue.put(packet);
|
||||
} catch (Exception e) {
|
||||
assertEquals(null, e);
|
||||
}
|
||||
Log.d(TAG, "writePacket() returns");
|
||||
}
|
||||
|
||||
void test(final byte[][] midi)
|
||||
throws IOException, InterruptedException {
|
||||
test(midi, 2);
|
||||
}
|
||||
|
||||
// Send the MIDI messages through the encoder,
|
||||
// then through the decoder,
|
||||
// then gather the resulting MIDI and compare the results.
|
||||
void test(final byte[][] midi, int intervalMillis)
|
||||
throws IOException, InterruptedException {
|
||||
start();
|
||||
long timestamp = 0;
|
||||
// Send all of the MIDI messages and gather the response.
|
||||
for (int i = 0; i < midi.length; i++) {
|
||||
byte[] outMessage = midi[i];
|
||||
Log.d(TAG, "outMessage "
|
||||
+ MidiFramer.formatMidiData(outMessage, 0, outMessage.length));
|
||||
mEncoder.send(outMessage, 0, outMessage.length, timestamp);
|
||||
timestamp += 2 * NANOS_PER_MSEC;
|
||||
// Also send a copy through a MidiFramer to align the messages.
|
||||
mBypassFramer.send(outMessage, 0, outMessage.length, timestamp);
|
||||
}
|
||||
Thread.sleep(200);
|
||||
stop();
|
||||
|
||||
// Compare the gathered rows with the expected rows.
|
||||
byte[][] expectedMessages = mBypassReceiver.getBuffers();
|
||||
byte[][] inMessages = mReceiver.getBuffers();
|
||||
Log.d(TAG, "expectedMessage length = " + expectedMessages.length
|
||||
+ ", inMessages length = " + inMessages.length);
|
||||
assertEquals(expectedMessages.length, inMessages.length);
|
||||
Long[] actualTimestamps = mReceiver.getTimestamps();
|
||||
long previousTime = 0;
|
||||
for (int i = 0; i < expectedMessages.length; i++) {
|
||||
byte[] expectedMessage = expectedMessages[i];
|
||||
Log.d(TAG, "expectedMessage = "
|
||||
+ MidiFramer.formatMidiData(expectedMessage,
|
||||
0, expectedMessage.length));
|
||||
byte[] actualMessage = inMessages[i];
|
||||
Log.d(TAG, "actualMessage = "
|
||||
+ MidiFramer.formatMidiData(actualMessage, 0, actualMessage.length));
|
||||
assertArrayEquals(expectedMessage, actualMessage);
|
||||
// Are the timestamps monotonic?
|
||||
long currentTime = actualTimestamps[i];
|
||||
Log.d(TAG, "previousTime = " + previousTime
|
||||
+ ", currentTime = " + currentTime);
|
||||
assertTrue(currentTime >= previousTime);
|
||||
previousTime = currentTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOneNoteOn() throws IOException, InterruptedException {
|
||||
final byte[][] midi = {
|
||||
{(byte) 0x90, 0x40, 0x64}
|
||||
};
|
||||
EncoderDecoderChecker checker = new EncoderDecoderChecker();
|
||||
checker.test(midi);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTwoNoteOnSameTime() throws IOException, InterruptedException {
|
||||
final byte[][] midi = {
|
||||
{(byte) 0x90, 0x40, 0x64, (byte) 0x90, 0x47, 0x70}
|
||||
};
|
||||
EncoderDecoderChecker checker = new EncoderDecoderChecker();
|
||||
checker.test(midi);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTwoNoteOnStaggered() throws IOException, InterruptedException {
|
||||
final byte[][] midi = {
|
||||
{(byte) 0x90, 0x40, 0x64},
|
||||
{(byte) 0x90, 0x47, 0x70}
|
||||
};
|
||||
EncoderDecoderChecker checker = new EncoderDecoderChecker();
|
||||
checker.test(midi);
|
||||
}
|
||||
|
||||
public void checkNoteBurst(int maxPacketsPerConnection,
|
||||
int period,
|
||||
int maxBytesPerPacket) throws IOException, InterruptedException {
|
||||
final int numNotes = 100;
|
||||
final byte[][] midi = new byte[numNotes][];
|
||||
int channel = 2;
|
||||
for (int i = 0; i < numNotes; i++) {
|
||||
byte[] message = {(byte) (0x90 + channel), (byte) (i + 1), 0x64};
|
||||
midi[i] = message;
|
||||
channel ^= 1;
|
||||
}
|
||||
EncoderDecoderChecker checker = new EncoderDecoderChecker(
|
||||
maxPacketsPerConnection, 15, maxBytesPerPacket);
|
||||
checker.test(midi, period);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoteBurstM1P6() throws IOException, InterruptedException {
|
||||
checkNoteBurst(1, 6, 20);
|
||||
}
|
||||
@Test
|
||||
public void testNoteBurstM1P2() throws IOException, InterruptedException {
|
||||
checkNoteBurst(1, 2, 20);
|
||||
}
|
||||
@Test
|
||||
public void testNoteBurstM2P6() throws IOException, InterruptedException {
|
||||
checkNoteBurst(2, 6, 20);
|
||||
}
|
||||
@Test
|
||||
public void testNoteBurstM2P2() throws IOException, InterruptedException {
|
||||
checkNoteBurst(2, 2, 20);
|
||||
}
|
||||
@Test
|
||||
public void testNoteBurstM2P0() throws IOException, InterruptedException {
|
||||
checkNoteBurst(2, 0, 20);
|
||||
}
|
||||
@Test
|
||||
public void testNoteBurstM2P6B21() throws IOException, InterruptedException {
|
||||
checkNoteBurst(2, 6, 21);
|
||||
}
|
||||
@Test
|
||||
public void testNoteBurstM2P2B21() throws IOException, InterruptedException {
|
||||
checkNoteBurst(2, 2, 21);
|
||||
}
|
||||
@Test
|
||||
public void testNoteBurstM2P0B21() throws IOException, InterruptedException {
|
||||
checkNoteBurst(2, 0, 21);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.
|
||||
*/
|
||||
|
||||
package com.android.bluetoothmidiservice;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.internal.midi.MidiFramer;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class BluetoothMidiDecoderTest {
|
||||
|
||||
private static final String TAG = "BluetoothMidiDecoderTest";
|
||||
private static final String[] PROVISIONING_APP_NAME = {"some", "app"};
|
||||
private static final long NANOS_PER_MSEC = 1000000L;
|
||||
|
||||
static class DecoderChecker {
|
||||
AccumulatingMidiReceiver mReceiver;
|
||||
BluetoothPacketDecoder mDecoder;
|
||||
|
||||
DecoderChecker() {
|
||||
mReceiver = new AccumulatingMidiReceiver();
|
||||
final int maxBytes = 20;
|
||||
mDecoder = new BluetoothPacketDecoder(maxBytes);
|
||||
}
|
||||
|
||||
void compareWithExpected(final byte[][] expectedMessages) {
|
||||
byte[][] actualRows = mReceiver.getBuffers();
|
||||
Long[] actualTimestamps = mReceiver.getTimestamps();
|
||||
long previousTime = 0;
|
||||
// Compare the gathered with the expected.
|
||||
assertEquals(expectedMessages.length, actualRows.length);
|
||||
for (int i = 0; i < expectedMessages.length; i++) {
|
||||
byte[] expectedRow = expectedMessages[i];
|
||||
Log.d(TAG, "expectedRow = "
|
||||
+ MidiFramer.formatMidiData(expectedRow, 0, expectedRow.length));
|
||||
byte[] actualRow = actualRows[i];
|
||||
Log.d(TAG, "actualRow = "
|
||||
+ MidiFramer.formatMidiData(actualRow, 0, actualRow.length));
|
||||
assertArrayEquals(expectedRow, actualRow);
|
||||
// Are the timestamps monotonic?
|
||||
long currentTime = actualTimestamps[i];
|
||||
Log.d(TAG, "previousTime = " + previousTime + ", currentTime = " + currentTime);
|
||||
assertTrue(currentTime >= previousTime);
|
||||
previousTime = currentTime;
|
||||
}
|
||||
}
|
||||
|
||||
void decodePacket(byte[] packet) throws IOException {
|
||||
mDecoder.decodePacket(packet, mReceiver);
|
||||
}
|
||||
|
||||
void decodePackets(byte[][] multiplePackets) throws IOException {
|
||||
try {
|
||||
for (int i = 0; i < multiplePackets.length; i++) {
|
||||
byte[] packet = multiplePackets[i];
|
||||
mDecoder.decodePacket(packet, mReceiver);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
assertEquals(null, e);
|
||||
}
|
||||
}
|
||||
|
||||
void test(byte[] encoded, byte[][] decoded) throws IOException {
|
||||
decodePacket(encoded);
|
||||
compareWithExpected(decoded);
|
||||
}
|
||||
|
||||
void test(byte[][] encoded, byte[][] decoded) throws IOException {
|
||||
decodePackets(encoded);
|
||||
compareWithExpected(decoded);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOneNoteOn() throws IOException {
|
||||
final byte[] encoded = {
|
||||
(byte) 0x80, // high bit of header must be set
|
||||
(byte) 0x80, // high bit of timestamp
|
||||
(byte) 0x90, 0x40, 0x64
|
||||
};
|
||||
final byte[][] decoded = {
|
||||
{(byte) 0x90, 0x40, 0x64}
|
||||
};
|
||||
new DecoderChecker().test(encoded, decoded);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReservedHeaderBit() throws IOException {
|
||||
final byte[] encoded = {
|
||||
// Decoder should ignore the reserved bit.
|
||||
(byte) (0x80 | 0x40), // set RESERVED bit in header!
|
||||
(byte) 0x80, // high bit of timestamp
|
||||
(byte) 0x90, 0x40, 0x64
|
||||
};
|
||||
final byte[][] decoded = {
|
||||
{(byte) 0x90, 0x40, 0x64}
|
||||
};
|
||||
new DecoderChecker().test(encoded, decoded);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTwoNotesOnRunning() throws IOException {
|
||||
final byte[] encoded = {
|
||||
(byte) 0x80, // high bit of header must be set
|
||||
(byte) 0x80, // high bit of timestamp
|
||||
(byte) 0x90, 0x40, 0x64,
|
||||
(byte) 0x85, // timestamp
|
||||
(byte) 0x42, 0x70
|
||||
};
|
||||
final byte[][] decoded = {
|
||||
{(byte) 0x90, 0x40, 0x64},
|
||||
{(byte) 0x42, 0x70}
|
||||
};
|
||||
new DecoderChecker().test(encoded, decoded);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTwoNoteOnsTwoChannels() throws IOException {
|
||||
final byte[] encoded = {
|
||||
(byte) 0x80, // high bit of header must be set
|
||||
(byte) 0x80, // high bit of timestamp
|
||||
(byte) 0x93, 0x40, 0x60,
|
||||
// two channels so no running status
|
||||
(byte) 0x80, // high bit of timestamp
|
||||
(byte) 0x95, 0x47, 0x64
|
||||
};
|
||||
final byte[][] decoded = {
|
||||
{(byte) 0x93, 0x40, 0x60, (byte) 0x95, 0x47, 0x64}
|
||||
};
|
||||
new DecoderChecker().test(encoded, decoded);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTwoNoteOnsOverTime() throws IOException {
|
||||
final byte[][] encoded = {{
|
||||
(byte) 0x80, // high bit of header must be set
|
||||
(byte) 0x80, // high bit of timestamp
|
||||
(byte) 0x98, 0x45, 0x60
|
||||
},
|
||||
{
|
||||
(byte) 0x80, // high bit of header must be set
|
||||
(byte) 0x82, // timestamp advanced by 2 msec
|
||||
(byte) 0x90, 0x40, 0x64,
|
||||
(byte) 0x84, // timestamp needed because of time delay
|
||||
// encoder uses running status
|
||||
0x47, 0x72
|
||||
}};
|
||||
final byte[][] decoded = {
|
||||
{(byte) 0x98, 0x45, 0x60},
|
||||
{(byte) 0x90, 0x40, 0x64},
|
||||
{(byte) 0x47, 0x72}
|
||||
};
|
||||
new DecoderChecker().test(encoded, decoded);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSysExBasic() throws IOException {
|
||||
final byte[][] encoded = {{
|
||||
(byte) 0x80, // high bit of header must be set
|
||||
(byte) 0x80, // timestamp
|
||||
(byte) 0xF0, 0x7D, // Begin prototyping SysEx
|
||||
0x01, 0x02, 0x03, 0x04, 0x05,
|
||||
(byte) 0x80, // timestamp
|
||||
(byte) 0xF7 // End SysEx
|
||||
}};
|
||||
final byte[][] decoded = {
|
||||
{(byte) 0xF0, 0x7D, // experimental SysEx
|
||||
0x01, 0x02, 0x03, 0x04, 0x05, (byte) 0xF7}
|
||||
};
|
||||
new DecoderChecker().test(encoded, decoded);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSysExTwoPackets() throws IOException {
|
||||
final byte[][] encoded = {{
|
||||
(byte) 0x80, // high bit of header must be set
|
||||
(byte) 0x80, // timestamp
|
||||
(byte) 0xF0, 0x7D, // Begin prototyping SysEx
|
||||
0x01, 0x02
|
||||
},
|
||||
{
|
||||
(byte) 0x80, // high bit of header must be set
|
||||
0x03, 0x04, 0x05,
|
||||
(byte) 0x80, // timestamp
|
||||
(byte) 0xF7 // End SysEx
|
||||
}};
|
||||
final byte[][] decoded = {
|
||||
{(byte) 0xF0, 0x7D, 0x01, 0x02}, // experimental SysEx
|
||||
{0x03, 0x04, 0x05, (byte) 0xF7}
|
||||
};
|
||||
new DecoderChecker().test(encoded, decoded);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSysExThreePackets() throws IOException {
|
||||
final byte[][] encoded = {
|
||||
{(byte) 0x80, // high bit of header must be set
|
||||
(byte) 0x80, // timestamp
|
||||
(byte) 0xF0, 0x7D, // Begin prototyping SysEx
|
||||
0x01, 0x02
|
||||
},
|
||||
{
|
||||
(byte) 0x80, // high bit of header must be set
|
||||
0x03, 0x04, 0x05,
|
||||
},
|
||||
{
|
||||
(byte) 0x80, // high bit of header must be set
|
||||
0x06, 0x07, 0x08,
|
||||
(byte) 0x80, // timestamp
|
||||
(byte) 0xF7 // End SysEx
|
||||
}};
|
||||
final byte[][] decoded = {
|
||||
{(byte) 0xF0, 0x7D, 0x01, 0x02}, // experimental SysEx
|
||||
{0x03, 0x04, 0x05},
|
||||
{0x06, 0x07, 0x08, (byte) 0xF7}
|
||||
};
|
||||
new DecoderChecker().test(encoded, decoded);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.
|
||||
*/
|
||||
|
||||
package com.android.bluetoothmidiservice;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.internal.midi.MidiFramer;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class BluetoothMidiEncoderTest {
|
||||
|
||||
private static final String TAG = "BluetoothMidiEncoderTest";
|
||||
private static final String[] PROVISIONING_APP_NAME = {"some", "app"};
|
||||
private static final long NANOS_PER_MSEC = 1000000L;
|
||||
|
||||
static class AccumulatingPacketReceiver implements PacketEncoder.PacketReceiver {
|
||||
ArrayList<byte[]> mBuffers = new ArrayList<byte[]>();
|
||||
|
||||
public void writePacket(byte[] buffer, int count) {
|
||||
byte[] actualRow = new byte[count];
|
||||
Log.d(TAG, "writePacket() passed " + MidiFramer.formatMidiData(buffer, 0, count));
|
||||
System.arraycopy(buffer, 0, actualRow, 0, count);
|
||||
mBuffers.add(actualRow);
|
||||
}
|
||||
|
||||
byte[][] getBuffers() {
|
||||
return mBuffers.toArray(new byte[mBuffers.size()][]);
|
||||
}
|
||||
}
|
||||
|
||||
static class EncoderChecker {
|
||||
AccumulatingPacketReceiver mReceiver;
|
||||
BluetoothPacketEncoder mEncoder;
|
||||
|
||||
EncoderChecker() {
|
||||
mReceiver = new AccumulatingPacketReceiver();
|
||||
final int maxBytes = 20;
|
||||
mEncoder = new BluetoothPacketEncoder(mReceiver, maxBytes);
|
||||
}
|
||||
|
||||
void send(byte[] data) throws IOException {
|
||||
send(data, 0);
|
||||
}
|
||||
|
||||
void send(byte[] data, long timestamp) throws IOException {
|
||||
Log.d(TAG, "send " + MidiFramer.formatMidiData(data, 0, data.length));
|
||||
mEncoder.send(data, 0, data.length, timestamp);
|
||||
}
|
||||
|
||||
void compareWithExpected(final byte[][] expected) {
|
||||
byte[][] actualRows = mReceiver.getBuffers();
|
||||
assertEquals(expected.length, actualRows.length);
|
||||
// Compare the gathered rows with the expected rows.
|
||||
for (int i = 0; i < expected.length; i++) {
|
||||
byte[] expectedRow = expected[i];
|
||||
Log.d(TAG, "expectedRow = "
|
||||
+ MidiFramer.formatMidiData(expectedRow, 0, expectedRow.length));
|
||||
byte[] actualRow = actualRows[i];
|
||||
Log.d(TAG, "actualRow = "
|
||||
+ MidiFramer.formatMidiData(actualRow, 0, actualRow.length));
|
||||
assertEquals(expectedRow.length, actualRow.length);
|
||||
for (int k = 0; k < expectedRow.length; k++) {
|
||||
assertEquals(expectedRow[k], actualRow[k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void writeComplete() {
|
||||
mEncoder.writeComplete();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOneNoteOn() throws IOException {
|
||||
final byte[][] encoded = {{
|
||||
(byte) 0x80, // high bit of header must be set
|
||||
(byte) 0x80, // high bit of timestamp
|
||||
(byte) 0x90, 0x40, 0x64
|
||||
}};
|
||||
EncoderChecker checker = new EncoderChecker();
|
||||
checker.send(new byte[] {(byte) 0x90, 0x40, 0x64});
|
||||
checker.compareWithExpected(encoded);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTwoNoteOnsSameChannel() throws IOException {
|
||||
final byte[][] encoded = {{
|
||||
(byte) 0x80, // high bit of header must be set
|
||||
(byte) 0x80, // high bit of timestamp
|
||||
(byte) 0x90, 0x40, 0x64,
|
||||
// encoder converts to running status
|
||||
0x47, 0x72
|
||||
}};
|
||||
EncoderChecker checker = new EncoderChecker();
|
||||
checker.send(new byte[] {(byte) 0x90, 0x40, 0x64, (byte) 0x90, 0x47, 0x72});
|
||||
checker.compareWithExpected(encoded);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTwoNoteOnsTwoChannels() throws IOException {
|
||||
final byte[][] encoded = {{
|
||||
(byte) 0x80, // high bit of header must be set
|
||||
(byte) 0x80, // high bit of timestamp
|
||||
(byte) 0x93, 0x40, 0x60,
|
||||
// two channels so no running status
|
||||
(byte) 0x80, // high bit of timestamp
|
||||
(byte) 0x95, 0x47, 0x64
|
||||
}};
|
||||
EncoderChecker checker = new EncoderChecker();
|
||||
checker.send(new byte[] {(byte) 0x93, 0x40, 0x60, (byte) 0x95, 0x47, 0x64});
|
||||
checker.compareWithExpected(encoded);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTwoNoteOnsOverTime() throws IOException {
|
||||
final byte[][] encoded = {
|
||||
{
|
||||
(byte) 0x80, // high bit of header must be set
|
||||
(byte) 0x80, // high bit of timestamp
|
||||
(byte) 0x98, 0x45, 0x60
|
||||
},
|
||||
{
|
||||
(byte) 0x80, // high bit of header must be set
|
||||
(byte) 0x82, // timestamp advanced by 2 msec
|
||||
(byte) 0x90, 0x40, 0x64,
|
||||
(byte) 0x84, // timestamp needed because of time delay
|
||||
// encoder converts to running status
|
||||
0x47, 0x72
|
||||
}};
|
||||
EncoderChecker checker = new EncoderChecker();
|
||||
long timestamp = 0;
|
||||
// Send one note. This will cause an immediate packet write
|
||||
// because we don't know when the next one will arrive.
|
||||
checker.send(new byte[] {(byte) 0x98, 0x45, 0x60}, timestamp);
|
||||
|
||||
// Send two notes. These should accumulate into the
|
||||
// same packet because we do not yet have a writeComplete.
|
||||
timestamp += 2 * NANOS_PER_MSEC;
|
||||
checker.send(new byte[] {(byte) 0x90, 0x40, 0x64}, timestamp);
|
||||
timestamp += 2 * NANOS_PER_MSEC;
|
||||
checker.send(new byte[] {(byte) 0x90, 0x47, 0x72}, timestamp);
|
||||
// Tell the encoder that the first packet has been written to the
|
||||
// hardware. So it can flush the two pending notes.
|
||||
checker.writeComplete();
|
||||
checker.compareWithExpected(encoded);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSysExBasic() throws IOException {
|
||||
final byte[][] encoded = {{
|
||||
(byte) 0x80, // high bit of header must be set
|
||||
(byte) 0x80, // timestamp
|
||||
(byte) 0xF0, 0x7D, // Begin prototyping SysEx
|
||||
0x01, 0x02, 0x03, 0x04, 0x05,
|
||||
(byte) 0x80, // timestamp
|
||||
(byte) 0xF7 // End SysEx
|
||||
}};
|
||||
EncoderChecker checker = new EncoderChecker();
|
||||
checker.send(new byte[] {(byte) 0xF0, 0x7D, // experimental SysEx
|
||||
0x01, 0x02, 0x03, 0x04, 0x05, (byte) 0xF7});
|
||||
checker.compareWithExpected(encoded);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSysExTwoPackets() throws IOException {
|
||||
final byte[][] encoded = {{
|
||||
(byte) 0x80, // high bit of header must be set
|
||||
(byte) 0x80, // timestamp
|
||||
(byte) 0xF0, 0x7D, // Begin prototyping SysEx
|
||||
0x01, 0x02
|
||||
},
|
||||
{
|
||||
(byte) 0x80, // high bit of header must be set
|
||||
0x03, 0x04, 0x05,
|
||||
(byte) 0x80, // timestamp
|
||||
(byte) 0xF7 // End SysEx
|
||||
}};
|
||||
EncoderChecker checker = new EncoderChecker();
|
||||
// Send in two messages.
|
||||
checker.send(new byte[] {(byte) 0xF0, 0x7D, // experimental SysEx
|
||||
0x01, 0x02});
|
||||
checker.send(new byte[] {0x03, 0x04, 0x05, (byte) 0xF7});
|
||||
// Tell the encoder that the first packet has been written to the
|
||||
// hardware. So it can flush the remaining data.
|
||||
checker.writeComplete();
|
||||
checker.compareWithExpected(encoded);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSysExThreePackets() throws IOException {
|
||||
final byte[][] encoded = {{
|
||||
(byte) 0x80, // high bit of header must be set
|
||||
(byte) 0x80, // timestamp
|
||||
(byte) 0xF0, 0x7D, // Begin prototyping SysEx
|
||||
0x01, 0x02
|
||||
},
|
||||
{
|
||||
(byte) 0x80, // high bit of header must be set
|
||||
0x03, 0x04, 0x05,
|
||||
},
|
||||
{
|
||||
(byte) 0x80, // high bit of header must be set
|
||||
0x06, 0x07, 0x08,
|
||||
(byte) 0x80, // timestamp
|
||||
(byte) 0xF7 // End SysEx
|
||||
}};
|
||||
EncoderChecker checker = new EncoderChecker();
|
||||
// Send in three messages.
|
||||
checker.send(new byte[] {(byte) 0xF0, 0x7D, // experimental SysEx
|
||||
0x01, 0x02});
|
||||
checker.send(new byte[] {0x03, 0x04, 0x05});
|
||||
checker.writeComplete();
|
||||
checker.send(new byte[] {0x06, 0x07, 0x08, (byte) 0xF7});
|
||||
checker.writeComplete();
|
||||
checker.compareWithExpected(encoded);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.
|
||||
*/
|
||||
|
||||
package com.android.bluetoothmidiservice;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.internal.midi.MidiFramer;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class MidiFramerTest {
|
||||
|
||||
private static final String TAG = "MidiFramerTest";
|
||||
private static final String[] PROVISIONING_APP_NAME = {"some", "app"};
|
||||
|
||||
// For testing MidiFramer
|
||||
// TODO move MidiFramer tests to their own file
|
||||
static class FramerChecker {
|
||||
AccumulatingMidiReceiver mReceiver;
|
||||
MidiFramer mFramer;
|
||||
|
||||
FramerChecker() {
|
||||
mReceiver = new AccumulatingMidiReceiver();
|
||||
mFramer = new MidiFramer(mReceiver);
|
||||
}
|
||||
|
||||
void compareWithExpected(final byte[][] expected) {
|
||||
byte[][] actualRows = mReceiver.getBuffers();
|
||||
assertEquals(expected.length, actualRows.length);
|
||||
// Compare the gathered rows with the expected rows.
|
||||
for (int i = 0; i < expected.length; i++) {
|
||||
byte[] expectedRow = expected[i];
|
||||
Log.d(TAG, "expectedRow = "
|
||||
+ MidiFramer.formatMidiData(expectedRow, 0, expectedRow.length));
|
||||
byte[] actualRow = actualRows[i];
|
||||
Log.d(TAG, "actualRow = "
|
||||
+ MidiFramer.formatMidiData(actualRow, 0, actualRow.length));
|
||||
assertArrayEquals(expectedRow, actualRow);
|
||||
}
|
||||
}
|
||||
|
||||
void send(byte[] data) throws IOException {
|
||||
Log.d(TAG, "send " + MidiFramer.formatMidiData(data, 0, data.length));
|
||||
mFramer.send(data, 0, data.length, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFramerTwoNoteOns() throws IOException {
|
||||
final byte[][] expected = {
|
||||
{(byte) 0x90, 0x40, 0x64},
|
||||
{(byte) 0x90, 0x47, 0x50}
|
||||
};
|
||||
FramerChecker checker = new FramerChecker();
|
||||
checker.send(new byte[] {(byte) 0x90, 0x40, 0x64, (byte) 0x90, 0x47, 0x50});
|
||||
checker.compareWithExpected(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFramerTwoNoteOnsRunning() throws IOException {
|
||||
final byte[][] expected = {
|
||||
{(byte) 0x90, 0x40, 0x64},
|
||||
{(byte) 0x90, 0x47, 0x70}
|
||||
};
|
||||
FramerChecker checker = new FramerChecker();
|
||||
// Two notes with running status
|
||||
checker.send(new byte[] {(byte) 0x90, 0x40, 0x64, 0x47, 0x70});
|
||||
checker.compareWithExpected(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFramerPreGarbage() throws IOException {
|
||||
final byte[][] expected = {
|
||||
{(byte) 0x90, 0x40, 0x64},
|
||||
{(byte) 0x90, 0x47, 0x70}
|
||||
};
|
||||
FramerChecker checker = new FramerChecker();
|
||||
// Garbage can come before the first status byte if you connect
|
||||
// a MIDI cable in the middle of a message.
|
||||
checker.send(new byte[] {0x01, 0x02, // garbage bytes
|
||||
(byte) 0x90, 0x40, 0x64, 0x47, 0x70});
|
||||
checker.compareWithExpected(expected);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user