summaryrefslogtreecommitdiff
path: root/media/native/midi/amidi.cpp
diff options
context:
space:
mode:
authorPaul McLean <pmclean@google.com>2019-02-06 13:27:43 -0700
committerPaul McLean <pmclean@google.com>2019-02-12 14:08:00 -0800
commitf79b8d1e8e00147c66023bfbf7c753dbb277a9eb (patch)
tree166de8156c0cf97922801acd1ea05f036f2d4b53 /media/native/midi/amidi.cpp
parent9d67bb2c2d88d69073b28069ae47f9ebcb78bee9 (diff)
Rename instances of "midi" to "AMidi".
Bug: 124002441 Test: Builds OK Change-Id: I7cdeae9b72f9f7e3f2f815bc83959dba72059f75
Diffstat (limited to 'media/native/midi/amidi.cpp')
-rw-r--r--media/native/midi/amidi.cpp443
1 files changed, 443 insertions, 0 deletions
diff --git a/media/native/midi/amidi.cpp b/media/native/midi/amidi.cpp
new file mode 100644
index 000000000000..1e9a194d76c8
--- /dev/null
+++ b/media/native/midi/amidi.cpp
@@ -0,0 +1,443 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "NativeMIDI"
+
+#include <poll.h>
+#include <unistd.h>
+
+#include <binder/Binder.h>
+#include <android_util_Binder.h>
+#include <utils/Log.h>
+
+#include <core_jni_helpers.h>
+
+#include "android/media/midi/BpMidiDeviceServer.h"
+#include "media/MidiDeviceInfo.h"
+
+#include "include/amidi/AMidi.h"
+#include "amidi_internal.h"
+
+using namespace android::media::midi;
+
+using android::IBinder;
+using android::BBinder;
+using android::OK;
+using android::sp;
+using android::status_t;
+using android::base::unique_fd;
+using android::binder::Status;
+
+struct AMIDI_Port {
+ std::atomic_int state; // One of the port status constants below.
+ const AMidiDevice *device; // Points to the AMidiDevice associated with the port.
+ sp<IBinder> binderToken;// The Binder token associated with the port.
+ unique_fd ufd; // The unique file descriptor associated with the port.
+};
+
+/*
+ * Port Status Constants
+ */
+enum {
+ MIDI_PORT_STATE_CLOSED = 0,
+ MIDI_PORT_STATE_OPEN_IDLE,
+ MIDI_PORT_STATE_OPEN_ACTIVE
+};
+
+/*
+ * Port Type Constants
+ */
+enum {
+ PORTTYPE_OUTPUT = 0,
+ PORTTYPE_INPUT = 1
+};
+
+/* TRANSFER PACKET FORMAT (as defined in MidiPortImpl.java)
+ *
+ * Transfer packet format is as follows (see MidiOutputPort.mThread.run() to see decomposition):
+ * |oc|md|md| ......... |md|ts|ts|ts|ts|ts|ts|ts|ts|
+ * ^ +--------------------+-----------------------+
+ * | ^ ^
+ * | | |
+ * | | + timestamp (8 bytes)
+ * | |
+ * | + MIDI data bytes (numBytes bytes)
+ * |
+ * + OpCode (AMIDI_OPCODE_DATA)
+ *
+ * NOTE: The socket pair is configured to use SOCK_SEQPACKET mode.
+ * SOCK_SEQPACKET, for a sequenced-packet socket that is connection-oriented, preserves message
+ * boundaries, and delivers messages in the order that they were sent.
+ * So 'read()' always returns a whole message.
+ */
+#define AMIDI_PACKET_SIZE 1024
+#define AMIDI_PACKET_OVERHEAD 9
+#define AMIDI_BUFFER_SIZE (AMIDI_PACKET_SIZE - AMIDI_PACKET_OVERHEAD)
+
+// JNI IDs (see android_media_midi.cpp)
+namespace android { namespace midi {
+// MidiDevice Fields
+extern jfieldID gFidMidiNativeHandle; // MidiDevice.mNativeHandle
+extern jfieldID gFidMidiDeviceServerBinder; // MidiDevice.mDeviceServerBinder
+extern jfieldID gFidMidiDeviceInfo; // MidiDevice.mDeviceInfo
+
+// MidiDeviceInfo Fields
+extern jfieldID mFidMidiDeviceId; // MidiDeviceInfo.mId
+}}
+using namespace android::midi;
+
+static std::mutex openMutex; // Ensure that the device can be connected just once to 1 thread
+
+//// Handy debugging function.
+//static void AMIDI_logBuffer(const uint8_t *data, size_t numBytes) {
+// for (size_t index = 0; index < numBytes; index++) {
+// ALOGI(" data @%zu [0x%X]", index, data[index]);
+// }
+//}
+
+/*
+ * Device Functions
+ */
+/**
+ * Retrieves information for the native MIDI device.
+ *
+ * device The Native API token for the device. This value is obtained from the
+ * AMidiDevice_fromJava().
+ * outDeviceInfoPtr Receives the associated device info.
+ *
+ * Returns AMEDIA_OK or a negative error code.
+ * - AMEDIA_ERROR_INVALID_PARAMETER
+ * AMEDIA_ERROR_UNKNOWN
+ */
+static media_status_t AMIDI_API AMIDI_getDeviceInfo(const AMidiDevice *device,
+ AMidiDeviceInfo *outDeviceInfoPtr) {
+ if (device == nullptr) {
+ return AMEDIA_ERROR_INVALID_PARAMETER;
+ }
+
+ MidiDeviceInfo deviceInfo;
+ Status txResult = device->server->getDeviceInfo(&deviceInfo);
+ if (!txResult.isOk()) {
+ ALOGE("AMIDI_getDeviceInfo transaction error: %d", txResult.transactionError());
+ return AMEDIA_ERROR_UNKNOWN;
+ }
+
+ outDeviceInfoPtr->type = deviceInfo.getType();
+ outDeviceInfoPtr->inputPortCount = deviceInfo.getInputPortNames().size();
+ outDeviceInfoPtr->outputPortCount = deviceInfo.getOutputPortNames().size();
+
+ return AMEDIA_OK;
+}
+
+media_status_t AMIDI_API AMidiDevice_fromJava(JNIEnv *env, jobject j_midiDeviceObj,
+ AMidiDevice** devicePtrPtr)
+{
+ if (j_midiDeviceObj == nullptr) {
+ ALOGE("AMidiDevice_fromJava() invalid MidiDevice object.");
+ return AMEDIA_ERROR_INVALID_OBJECT;
+ }
+
+ {
+ std::lock_guard<std::mutex> guard(openMutex);
+
+ long handle = env->GetLongField(j_midiDeviceObj, gFidMidiNativeHandle);
+ if (handle != 0) {
+ // Already opened by someone.
+ return AMEDIA_ERROR_INVALID_OBJECT;
+ }
+
+ jobject serverBinderObj = env->GetObjectField(j_midiDeviceObj, gFidMidiDeviceServerBinder);
+ sp<IBinder> serverBinder = android::ibinderForJavaObject(env, serverBinderObj);
+ if (serverBinder.get() == nullptr) {
+ ALOGE("AMidiDevice_fromJava couldn't connect to native MIDI server.");
+ return AMEDIA_ERROR_UNKNOWN;
+ }
+
+ // don't check allocation failures, just abort..
+ AMidiDevice* devicePtr = new AMidiDevice;
+ devicePtr->server = new BpMidiDeviceServer(serverBinder);
+ jobject midiDeviceInfoObj = env->GetObjectField(j_midiDeviceObj, gFidMidiDeviceInfo);
+ devicePtr->deviceId = env->GetIntField(midiDeviceInfoObj, mFidMidiDeviceId);
+
+ // Synchronize with the associated Java MidiDevice.
+ env->SetLongField(j_midiDeviceObj, gFidMidiNativeHandle, (long)devicePtr);
+ env->GetJavaVM(&devicePtr->javaVM);
+ devicePtr->midiDeviceObj = env->NewGlobalRef(j_midiDeviceObj);
+
+ if (AMIDI_getDeviceInfo(devicePtr, &devicePtr->deviceInfo) != AMEDIA_OK) {
+ // This is weird, but maybe not fatal?
+ ALOGE("AMidiDevice_fromJava couldn't retrieve attributes of native device.");
+ }
+
+ *devicePtrPtr = devicePtr;
+ }
+
+ return AMEDIA_OK;
+}
+
+media_status_t AMIDI_API AMidiDevice_release(const AMidiDevice *device)
+{
+ if (device == nullptr || device->midiDeviceObj == nullptr) {
+ return AMEDIA_ERROR_INVALID_PARAMETER;
+ }
+
+ JNIEnv* env;
+ jint err = device->javaVM->GetEnv((void**)&env, JNI_VERSION_1_6);
+ LOG_ALWAYS_FATAL_IF(err != JNI_OK, "AMidiDevice_release Error accessing JNIEnv err:%d", err);
+
+ // Synchronize with the associated Java MidiDevice.
+ {
+ std::lock_guard<std::mutex> guard(openMutex);
+ long handle = env->GetLongField(device->midiDeviceObj, gFidMidiNativeHandle);
+ if (handle == 0) {
+ // Not opened as native.
+ ALOGE("AMidiDevice_release() device not opened in native client.");
+ return AMEDIA_ERROR_INVALID_OBJECT;
+ }
+
+ env->SetLongField(device->midiDeviceObj, gFidMidiNativeHandle, 0L);
+ }
+ env->DeleteGlobalRef(device->midiDeviceObj);
+
+ delete device;
+
+ return AMEDIA_OK;
+}
+
+int32_t AMIDI_API AMidiDevice_getType(const AMidiDevice *device) {
+ if (device == nullptr) {
+ return AMEDIA_ERROR_INVALID_PARAMETER;
+ }
+ return device->deviceInfo.type;
+}
+
+ssize_t AMIDI_API AMidiDevice_getNumInputPorts(const AMidiDevice *device) {
+ if (device == nullptr) {
+ return AMEDIA_ERROR_INVALID_PARAMETER;
+ }
+ return device->deviceInfo.inputPortCount;
+}
+
+ssize_t AMIDI_API AMidiDevice_getNumOutputPorts(const AMidiDevice *device) {
+ if (device == nullptr) {
+ return AMEDIA_ERROR_INVALID_PARAMETER;
+ }
+ return device->deviceInfo.outputPortCount;
+}
+
+/*
+ * Port Helpers
+ */
+static media_status_t AMIDI_openPort(const AMidiDevice *device, int32_t portNumber, int type,
+ AMIDI_Port **portPtr) {
+ if (device == nullptr) {
+ return AMEDIA_ERROR_INVALID_PARAMETER;
+ }
+
+ sp<BBinder> portToken(new BBinder());
+ unique_fd ufd;
+ Status txResult = type == PORTTYPE_OUTPUT
+ ? device->server->openOutputPort(portToken, portNumber, &ufd)
+ : device->server->openInputPort(portToken, portNumber, &ufd);
+ if (!txResult.isOk()) {
+ ALOGE("AMIDI_openPort transaction error: %d", txResult.transactionError());
+ return AMEDIA_ERROR_UNKNOWN;
+ }
+
+ AMIDI_Port *port = new AMIDI_Port;
+ port->state = MIDI_PORT_STATE_OPEN_IDLE;
+ port->device = device;
+ port->binderToken = portToken;
+ port->ufd = std::move(ufd);
+
+ *portPtr = port;
+
+ return AMEDIA_OK;
+}
+
+static void AMIDI_closePort(AMIDI_Port *port) {
+ if (port == nullptr) {
+ return;
+ }
+
+ int portState = MIDI_PORT_STATE_OPEN_IDLE;
+ while (!port->state.compare_exchange_weak(portState, MIDI_PORT_STATE_CLOSED)) {
+ if (portState == MIDI_PORT_STATE_CLOSED) {
+ return; // Already closed
+ }
+ }
+
+ Status txResult = port->device->server->closePort(port->binderToken);
+ if (!txResult.isOk()) {
+ ALOGE("Transaction error closing MIDI port:%d", txResult.transactionError());
+ }
+
+ delete port;
+}
+
+/*
+ * Output (receiving) API
+ */
+media_status_t AMIDI_API AMidiOutputPort_open(const AMidiDevice *device, int32_t portNumber,
+ AMidiOutputPort **outOutputPortPtr) {
+ return AMIDI_openPort(device, portNumber, PORTTYPE_OUTPUT, (AMIDI_Port**)outOutputPortPtr);
+}
+
+/*
+ * A little RAII (https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization)
+ * class to ensure that the port state is correct irrespective of errors.
+ */
+class MidiReceiver {
+public:
+ MidiReceiver(AMIDI_Port *port) : mPort(port) {}
+
+ ~MidiReceiver() {
+ // flag the port state to idle
+ mPort->state.store(MIDI_PORT_STATE_OPEN_IDLE);
+ }
+
+ ssize_t receive(int32_t *opcodePtr, uint8_t *buffer, size_t maxBytes,
+ size_t *numBytesReceivedPtr, int64_t *timestampPtr) {
+ int portState = MIDI_PORT_STATE_OPEN_IDLE;
+ // check to see if the port is idle, then set to active
+ if (!mPort->state.compare_exchange_strong(portState, MIDI_PORT_STATE_OPEN_ACTIVE)) {
+ // The port not idle or has been closed.
+ return AMEDIA_ERROR_UNKNOWN;
+ }
+
+ struct pollfd checkFds[1] = { { mPort->ufd, POLLIN, 0 } };
+ if (poll(checkFds, 1, 0) < 1) {
+ // Nothing there
+ return 0;
+ }
+
+ uint8_t readBuffer[AMIDI_PACKET_SIZE];
+ ssize_t readCount = read(mPort->ufd, readBuffer, sizeof(readBuffer));
+ if (readCount == EINTR || readCount < 1) {
+ return AMEDIA_ERROR_UNKNOWN;
+ }
+
+ // see Packet Format definition at the top of this file.
+ size_t numMessageBytes = 0;
+ *opcodePtr = readBuffer[0];
+ if (*opcodePtr == AMIDI_OPCODE_DATA && readCount >= AMIDI_PACKET_OVERHEAD) {
+ numMessageBytes = readCount - AMIDI_PACKET_OVERHEAD;
+ numMessageBytes = std::min(maxBytes, numMessageBytes);
+ memcpy(buffer, readBuffer + 1, numMessageBytes);
+ if (timestampPtr != nullptr) {
+ memcpy(timestampPtr, readBuffer + readCount - sizeof(uint64_t),
+ sizeof(*timestampPtr));
+ }
+ }
+ *numBytesReceivedPtr = numMessageBytes;
+ return 1;
+ }
+
+private:
+ AMIDI_Port *mPort;
+};
+
+ssize_t AMIDI_API AMidiOutputPort_receive(const AMidiOutputPort *outputPort, int32_t *opcodePtr,
+ uint8_t *buffer, size_t maxBytes, size_t* numBytesReceivedPtr, int64_t *timestampPtr) {
+
+ if (outputPort == nullptr || buffer == nullptr) {
+ return -EINVAL;
+ }
+
+ return MidiReceiver((AMIDI_Port*)outputPort).receive(opcodePtr, buffer, maxBytes,
+ numBytesReceivedPtr, timestampPtr);
+}
+
+void AMIDI_API AMidiOutputPort_close(const AMidiOutputPort *outputPort) {
+ AMIDI_closePort((AMIDI_Port*)outputPort);
+}
+
+/*
+ * Input (sending) API
+ */
+media_status_t AMIDI_API AMidiInputPort_open(const AMidiDevice *device, int32_t portNumber,
+ AMidiInputPort **outInputPortPtr) {
+ return AMIDI_openPort(device, portNumber, PORTTYPE_INPUT, (AMIDI_Port**)outInputPortPtr);
+}
+
+void AMIDI_API AMidiInputPort_close(const AMidiInputPort *inputPort) {
+ AMIDI_closePort((AMIDI_Port*)inputPort);
+}
+
+static ssize_t AMIDI_makeSendBuffer(
+ uint8_t *buffer, const uint8_t *data, size_t numBytes, uint64_t timestamp) {
+ // Error checking will happen in the caller since this isn't an API function.
+ buffer[0] = AMIDI_OPCODE_DATA;
+ memcpy(buffer + 1, data, numBytes);
+ memcpy(buffer + 1 + numBytes, &timestamp, sizeof(timestamp));
+ return numBytes + AMIDI_PACKET_OVERHEAD;
+}
+
+ssize_t AMIDI_API AMidiInputPort_send(const AMidiInputPort *inputPort, const uint8_t *buffer,
+ size_t numBytes) {
+ return AMidiInputPort_sendWithTimestamp(inputPort, buffer, numBytes, 0);
+}
+
+ssize_t AMIDI_API AMidiInputPort_sendWithTimestamp(const AMidiInputPort *inputPort,
+ const uint8_t *data, size_t numBytes, int64_t timestamp) {
+ if (inputPort == nullptr || data == nullptr) {
+ return AMEDIA_ERROR_INVALID_PARAMETER;
+ }
+
+ // AMIDI_logBuffer(data, numBytes);
+
+ uint8_t writeBuffer[AMIDI_BUFFER_SIZE + AMIDI_PACKET_OVERHEAD];
+ size_t numSent = 0;
+ while (numSent < numBytes) {
+ size_t blockSize = AMIDI_BUFFER_SIZE;
+ blockSize = std::min(blockSize, numBytes - numSent);
+
+ ssize_t numTransferBytes =
+ AMIDI_makeSendBuffer(writeBuffer, data + numSent, blockSize, timestamp);
+ ssize_t numWritten = write(((AMIDI_Port*)inputPort)->ufd, writeBuffer, numTransferBytes);
+ if (numWritten < 0) {
+ break; // error so bail out.
+ }
+ if (numWritten < numTransferBytes) {
+ ALOGE("AMidiInputPort_sendWithTimestamp Couldn't write MIDI data buffer."
+ " requested:%zu, written%zu",numTransferBytes, numWritten);
+ break; // bail
+ }
+
+ numSent += numWritten - AMIDI_PACKET_OVERHEAD;
+ }
+
+ return numSent;
+}
+
+media_status_t AMIDI_API AMidiInputPort_sendFlush(const AMidiInputPort *inputPort) {
+ if (inputPort == nullptr) {
+ return AMEDIA_ERROR_INVALID_PARAMETER;
+ }
+
+ uint8_t opCode = AMIDI_OPCODE_FLUSH;
+ ssize_t numTransferBytes = 1;
+ ssize_t numWritten = write(((AMIDI_Port*)inputPort)->ufd, &opCode, numTransferBytes);
+
+ if (numWritten < numTransferBytes) {
+ ALOGE("AMidiInputPort_flush Couldn't write MIDI flush. requested:%zd, written:%zd",
+ numTransferBytes, numWritten);
+ return AMEDIA_ERROR_UNSUPPORTED;
+ }
+
+ return AMEDIA_OK;
+}
+