diff options
author | Paul McLean <pmclean@google.com> | 2019-02-06 13:27:43 -0700 |
---|---|---|
committer | Paul McLean <pmclean@google.com> | 2019-02-12 14:08:00 -0800 |
commit | f79b8d1e8e00147c66023bfbf7c753dbb277a9eb (patch) | |
tree | 166de8156c0cf97922801acd1ea05f036f2d4b53 /media/native/midi/amidi.cpp | |
parent | 9d67bb2c2d88d69073b28069ae47f9ebcb78bee9 (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.cpp | 443 |
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, ×tamp, 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; +} + |