diff options
Diffstat (limited to 'media/native/midi/midi.cpp')
-rw-r--r-- | media/native/midi/midi.cpp | 373 |
1 files changed, 271 insertions, 102 deletions
diff --git a/media/native/midi/midi.cpp b/media/native/midi/midi.cpp index 17b92da3325e..a5bdba8569b8 100644 --- a/media/native/midi/midi.cpp +++ b/media/native/midi/midi.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * 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. @@ -20,15 +20,19 @@ #include <unistd.h> #include <binder/Binder.h> -#include <utils/Errors.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 "midi.h" +#include "include/midi.h" #include "midi_internal.h" +using namespace android::media::midi; + using android::IBinder; using android::BBinder; using android::OK; @@ -36,23 +40,26 @@ using android::sp; using android::status_t; using android::base::unique_fd; using android::binder::Status; -using android::media::midi::MidiDeviceInfo; struct AMIDI_Port { - std::atomic_int state; - AMIDI_Device *device; - sp<IBinder> binderToken; - unique_fd ufd; + 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. }; -#define SIZE_MIDIRECEIVEBUFFER AMIDI_BUFFER_SIZE - +/* + * 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 @@ -76,32 +83,170 @@ enum { * 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 */ -status_t AMIDI_getDeviceInfo(AMIDI_Device *device, AMIDI_DeviceInfo *deviceInfoPtr) { +/** + * 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 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); - deviceInfoPtr->type = deviceInfo.getType(); - deviceInfoPtr->uid = deviceInfo.getUid(); - deviceInfoPtr->isPrivate = deviceInfo.isPrivate(); - deviceInfoPtr->inputPortCount = deviceInfo.getInputPortNames().size(); - deviceInfoPtr->outputPortCount = deviceInfo.getOutputPortNames().size(); + delete device; - return OK; + 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 status_t AMIDI_openPort(AMIDI_Device *device, int portNumber, int type, +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 @@ -109,10 +254,10 @@ static status_t AMIDI_openPort(AMIDI_Device *device, int portNumber, int type, : device->server->openInputPort(portToken, portNumber, &ufd); if (!txResult.isOk()) { ALOGE("AMIDI_openPort transaction error: %d", txResult.transactionError()); - return txResult.transactionError(); + return AMEDIA_ERROR_UNKNOWN; } - AMIDI_Port* port = new AMIDI_Port; + AMIDI_Port *port = new AMIDI_Port; port->state = MIDI_PORT_STATE_OPEN_IDLE; port->device = device; port->binderToken = portToken; @@ -120,155 +265,179 @@ static status_t AMIDI_openPort(AMIDI_Device *device, int portNumber, int type, *portPtr = port; - return OK; + return AMEDIA_OK; } -static status_t AMIDI_closePort(AMIDI_Port *port) { +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 -EINVAL; // Already closed + return; // Already closed } } Status txResult = port->device->server->closePort(port->binderToken); if (!txResult.isOk()) { - return txResult.transactionError(); + ALOGE("Transaction error closing MIDI port:%d", txResult.transactionError()); } delete port; - - return OK; } /* * Output (receiving) API */ -status_t AMIDI_openOutputPort(AMIDI_Device *device, int portNumber, - AMIDI_OutputPort **outputPortPtr) { - return AMIDI_openPort(device, portNumber, PORTTYPE_OUTPUT, (AMIDI_Port**)outputPortPtr); +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); } -ssize_t AMIDI_receive(AMIDI_OutputPort *outputPort, AMIDI_Message *messages, ssize_t maxMessages) { - AMIDI_Port *port = (AMIDI_Port*)outputPort; - int portState = MIDI_PORT_STATE_OPEN_IDLE; - if (!port->state.compare_exchange_strong(portState, MIDI_PORT_STATE_OPEN_ACTIVE)) { - // The port has been closed. - return -EPIPE; +/* + * 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); } - status_t result = OK; - ssize_t messagesRead = 0; - while (messagesRead < maxMessages) { - struct pollfd checkFds[1] = { { port->ufd, POLLIN, 0 } }; - int pollResult = poll(checkFds, 1, 0); - if (pollResult < 1) { - result = android::INVALID_OPERATION; - break; + 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; } - AMIDI_Message *message = &messages[messagesRead]; - uint8_t readBuffer[AMIDI_PACKET_SIZE]; - memset(readBuffer, 0, sizeof(readBuffer)); - ssize_t readCount = read(port->ufd, readBuffer, sizeof(readBuffer)); - if (readCount == EINTR) { - continue; + struct pollfd checkFds[1] = { { mPort->ufd, POLLIN, 0 } }; + if (poll(checkFds, 1, 0) < 1) { + // Nothing there + return 0; } - if (readCount < 1) { - result = android::NOT_ENOUGH_DATA; - break; + + uint8_t readBuffer[AMIDI_PACKET_SIZE]; + ssize_t readCount = read(mPort->ufd, readBuffer, sizeof(readBuffer)); + if (readCount == EINTR || readCount < 1) { + return AMEDIA_ERROR_UNKNOWN; } - // set Packet Format definition at the top of this file. - size_t dataSize = 0; - message->opcode = readBuffer[0]; - message->timestamp = 0; - if (message->opcode == AMIDI_OPCODE_DATA && readCount >= AMIDI_PACKET_OVERHEAD) { - dataSize = readCount - AMIDI_PACKET_OVERHEAD; - if (dataSize) { - memcpy(message->buffer, readBuffer + 1, dataSize); + // 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)); } - message->timestamp = *(uint64_t*)(readBuffer + readCount - sizeof(uint64_t)); } - message->len = dataSize; - ++messagesRead; + *numBytesReceivedPtr = numMessageBytes; + return 1; } - port->state.store(MIDI_PORT_STATE_OPEN_IDLE); +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 result == OK ? messagesRead : result; + return MidiReceiver((AMIDI_Port*)outputPort).receive(opcodePtr, buffer, maxBytes, + numBytesReceivedPtr, timestampPtr); } -status_t AMIDI_closeOutputPort(AMIDI_OutputPort *outputPort) { - return AMIDI_closePort((AMIDI_Port*)outputPort); +void AMIDI_API AMidiOutputPort_close(const AMidiOutputPort *outputPort) { + AMIDI_closePort((AMIDI_Port*)outputPort); } /* * Input (sending) API */ -status_t AMIDI_openInputPort(AMIDI_Device *device, int portNumber, AMIDI_InputPort **inputPortPtr) { - return AMIDI_openPort(device, portNumber, PORTTYPE_INPUT, (AMIDI_Port**)inputPortPtr); +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); } -status_t AMIDI_closeInputPort(AMIDI_InputPort *inputPort) { - return AMIDI_closePort((AMIDI_Port*)inputPort); -} - -ssize_t AMIDI_getMaxMessageSizeInBytes(AMIDI_InputPort */*inputPort*/) { - return SIZE_MIDIRECEIVEBUFFER; +void AMIDI_API AMidiInputPort_close(const AMidiInputPort *inputPort) { + AMIDI_closePort((AMIDI_Port*)inputPort); } static ssize_t AMIDI_makeSendBuffer( - uint8_t *buffer, uint8_t *data, ssize_t numBytes,uint64_t timestamp) { + 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; } -// Handy debugging function. -//static void AMIDI_logBuffer(uint8_t *data, size_t numBytes) { -// for (size_t index = 0; index < numBytes; index++) { -// ALOGI(" data @%zu [0x%X]", index, data[index]); -// } -//} - -ssize_t AMIDI_send(AMIDI_InputPort *inputPort, uint8_t *buffer, ssize_t numBytes) { - return AMIDI_sendWithTimestamp(inputPort, buffer, numBytes, 0); +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_sendWithTimestamp(AMIDI_InputPort *inputPort, uint8_t *data, - ssize_t numBytes, int64_t timestamp) { - - if (numBytes > SIZE_MIDIRECEIVEBUFFER) { - return android::BAD_VALUE; +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[SIZE_MIDIRECEIVEBUFFER + AMIDI_PACKET_OVERHEAD]; - ssize_t numTransferBytes = AMIDI_makeSendBuffer(writeBuffer, data, numBytes, timestamp); - ssize_t numWritten = write(((AMIDI_Port*)inputPort)->ufd, writeBuffer, numTransferBytes); + 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 + } - if (numWritten < numTransferBytes) { - ALOGE("AMIDI_sendWithTimestamp Couldn't write MIDI data buffer. requested:%zu, written%zu", - numTransferBytes, numWritten); + numSent += numWritten - AMIDI_PACKET_OVERHEAD; } - return numWritten - AMIDI_PACKET_OVERHEAD; + return numSent; } -status_t AMIDI_flush(AMIDI_InputPort *inputPort) { +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("AMIDI_flush Couldn't write MIDI flush. requested:%zu, written%zu", + ALOGE("AMidiInputPort_flush Couldn't write MIDI flush. requested:%zd, written:%zd", numTransferBytes, numWritten); - return android::INVALID_OPERATION; + return AMEDIA_ERROR_UNSUPPORTED; } - return OK; + return AMEDIA_OK; } |