From e5f330f8fd4e93e69a2c243b2d4ce339b1ea76e2 Mon Sep 17 00:00:00 2001 From: Robert Wu Date: Mon, 15 Aug 2022 18:11:19 +0000 Subject: [PATCH] MIDI: Power Boost when MIDI hardware sends data When USB MIDI hardware sends bytes to Android, Android should set power boost as if an interaction event just happened. This change reduces the number of audio underruns. Bug: 122678281 Test: Connect Akai MIDI USB Keyboard to MIDI Synth Ex Test: Connect another Android phone as host to MIDI Synth Ex Test: Collect perfetto traces and verify a difference Change-Id: Ic95283813beb16e55d941f85f876124afa2f2794 --- .../android/server/usb/PowerBoostSetter.java | 59 +++++++++++++++++++ .../server/usb/UsbDirectMidiDevice.java | 22 +++++++ .../com/android/server/usb/UsbMidiDevice.java | 9 +++ 3 files changed, 90 insertions(+) create mode 100644 services/usb/java/com/android/server/usb/PowerBoostSetter.java diff --git a/services/usb/java/com/android/server/usb/PowerBoostSetter.java b/services/usb/java/com/android/server/usb/PowerBoostSetter.java new file mode 100644 index 0000000000000..eb7c196aacc1d --- /dev/null +++ b/services/usb/java/com/android/server/usb/PowerBoostSetter.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2022 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.server.usb; + +import android.os.PowerManagerInternal; +import android.util.Log; + +import com.android.server.LocalServices; + +import java.time.Instant; + +/** + * Sends power boost events to the power manager. + */ +public class PowerBoostSetter { + private static final String TAG = "PowerBoostSetter"; + // Set power boost timeout to 15 seconds + private static final int POWER_BOOST_TIMEOUT_MS = 15 * 1000; + + PowerManagerInternal mPowerManagerInternal = null; + Instant mPreviousTimeout = null; + + PowerBoostSetter() { + mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class); + } + + /** + * Boosts the CPU clock frequency as if the screen is touched + */ + public void boostPower() { + if (mPowerManagerInternal == null) { + mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class); + } + + if (mPowerManagerInternal == null) { + Log.w(TAG, "PowerManagerInternal null"); + } else if ((mPreviousTimeout == null) || Instant.now().isAfter( + mPreviousTimeout.plusMillis(POWER_BOOST_TIMEOUT_MS / 2))) { + // Only boost if the previous timeout is at least halfway done + mPreviousTimeout = Instant.now(); + mPowerManagerInternal.setPowerBoost(PowerManagerInternal.BOOST_INTERACTION, + POWER_BOOST_TIMEOUT_MS); + } + } +} diff --git a/services/usb/java/com/android/server/usb/UsbDirectMidiDevice.java b/services/usb/java/com/android/server/usb/UsbDirectMidiDevice.java index eebcccb68b134..2ae328b0d8e94 100644 --- a/services/usb/java/com/android/server/usb/UsbDirectMidiDevice.java +++ b/services/usb/java/com/android/server/usb/UsbDirectMidiDevice.java @@ -98,6 +98,11 @@ public final class UsbDirectMidiDevice implements Closeable { private UsbMidiPacketConverter mUsbMidiPacketConverter; + private PowerBoostSetter mPowerBoostSetter = null; + + private static final byte MESSAGE_TYPE_MIDI_1_CHANNEL_VOICE = 0x02; + private static final byte MESSAGE_TYPE_MIDI_2_CHANNEL_VOICE = 0x04; + private final MidiDeviceServer.Callback mCallback = new MidiDeviceServer.Callback() { @Override public void onDeviceStatusChanged(MidiDeviceServer server, MidiDeviceStatus status) { @@ -251,6 +256,8 @@ public final class UsbDirectMidiDevice implements Closeable { for (int port = 0; port < numOutputs; port++) { mMidiInputPortReceivers[port] = new InputReceiverProxy(); } + + mPowerBoostSetter = new PowerBoostSetter(); } private int calculateDefaultMidiProtocol() { @@ -418,6 +425,15 @@ public final class UsbDirectMidiDevice implements Closeable { } outputReceivers[portFinal].send(convertedArray, 0, convertedArray.length, timestamp); + + // Boost power if there seems to be a voice message. + // For legacy devices, boost when message is more than size 1. + // For UMP devices, boost for channel voice messages. + if ((mPowerBoostSetter != null && convertedArray.length > 1) + && (!mIsUniversalMidiDevice + || isChannelVoiceMessage(convertedArray))) { + mPowerBoostSetter.boostPower(); + } } } } catch (IOException e) { @@ -721,4 +737,10 @@ public final class UsbDirectMidiDevice implements Closeable { dump.end(token); } + + private boolean isChannelVoiceMessage(byte[] umpMessage) { + byte messageType = (byte) ((umpMessage[0] >> 4) & 0x0f); + return messageType == MESSAGE_TYPE_MIDI_1_CHANNEL_VOICE + || messageType == MESSAGE_TYPE_MIDI_2_CHANNEL_VOICE; + } } diff --git a/services/usb/java/com/android/server/usb/UsbMidiDevice.java b/services/usb/java/com/android/server/usb/UsbMidiDevice.java index 67955e155f191..d9ad703b82230 100644 --- a/services/usb/java/com/android/server/usb/UsbMidiDevice.java +++ b/services/usb/java/com/android/server/usb/UsbMidiDevice.java @@ -77,6 +77,8 @@ public final class UsbMidiDevice implements Closeable { // only accessed from JNI code private int mPipeFD = -1; + private PowerBoostSetter mPowerBoostSetter = null; + private final MidiDeviceServer.Callback mCallback = new MidiDeviceServer.Callback() { @Override public void onDeviceStatusChanged(MidiDeviceServer server, MidiDeviceStatus status) { @@ -167,6 +169,8 @@ public final class UsbMidiDevice implements Closeable { for (int port = 0; port < numOutputs; port++) { mMidiInputPortReceivers[port] = new InputReceiverProxy(); } + + mPowerBoostSetter = new PowerBoostSetter(); } private boolean openLocked() { @@ -240,6 +244,11 @@ public final class UsbMidiDevice implements Closeable { int count = mInputStreams[index].read(buffer); outputReceivers[index].send(buffer, 0, count, timestamp); + + // If messages are more than size 1, boost power. + if (mPowerBoostSetter != null && count > 1) { + mPowerBoostSetter.boostPower(); + } } } }