/* * Copyright (C) 2017 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 <utils/Errors.h> #include <utils/Log.h> #include "android/media/midi/BpMidiDeviceServer.h" #include "media/MidiDeviceInfo.h" #include "midi.h" #include "midi_internal.h" 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; using android::media::midi::MidiDeviceInfo; struct AMIDI_Port { std::atomic_int state; AMIDI_Device *device; sp<IBinder> binderToken; unique_fd ufd; }; #define SIZE_MIDIRECEIVEBUFFER AMIDI_BUFFER_SIZE enum { MIDI_PORT_STATE_CLOSED = 0, MIDI_PORT_STATE_OPEN_IDLE, MIDI_PORT_STATE_OPEN_ACTIVE }; 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. */ /* * Device Functions */ status_t AMIDI_getDeviceInfo(AMIDI_Device *device, AMIDI_DeviceInfo *deviceInfoPtr) { MidiDeviceInfo deviceInfo; Status txResult = device->server->getDeviceInfo(&deviceInfo); if (!txResult.isOk()) { ALOGE("AMIDI_getDeviceInfo transaction error: %d", txResult.transactionError()); return txResult.transactionError(); } deviceInfoPtr->type = deviceInfo.getType(); deviceInfoPtr->uid = deviceInfo.getUid(); deviceInfoPtr->isPrivate = deviceInfo.isPrivate(); deviceInfoPtr->inputPortCount = deviceInfo.getInputPortNames().size(); deviceInfoPtr->outputPortCount = deviceInfo.getOutputPortNames().size(); return OK; } /* * Port Helpers */ static status_t AMIDI_openPort(AMIDI_Device *device, int portNumber, int type, AMIDI_Port **portPtr) { 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 txResult.transactionError(); } 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 OK; } static status_t AMIDI_closePort(AMIDI_Port *port) { 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 } } Status txResult = port->device->server->closePort(port->binderToken); if (!txResult.isOk()) { return 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); } 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; } 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; } 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; } if (readCount < 1) { result = android::NOT_ENOUGH_DATA; break; } // 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); } message->timestamp = *(uint64_t*)(readBuffer + readCount - sizeof(uint64_t)); } message->len = dataSize; ++messagesRead; } port->state.store(MIDI_PORT_STATE_OPEN_IDLE); return result == OK ? messagesRead : result; } status_t AMIDI_closeOutputPort(AMIDI_OutputPort *outputPort) { return 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); } status_t AMIDI_closeInputPort(AMIDI_InputPort *inputPort) { return AMIDI_closePort((AMIDI_Port*)inputPort); } ssize_t AMIDI_getMaxMessageSizeInBytes(AMIDI_InputPort */*inputPort*/) { return SIZE_MIDIRECEIVEBUFFER; } static ssize_t AMIDI_makeSendBuffer( uint8_t *buffer, uint8_t *data, ssize_t numBytes,uint64_t timestamp) { 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_sendWithTimestamp(AMIDI_InputPort *inputPort, uint8_t *data, ssize_t numBytes, int64_t timestamp) { if (numBytes > SIZE_MIDIRECEIVEBUFFER) { return android::BAD_VALUE; } // 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); if (numWritten < numTransferBytes) { ALOGE("AMIDI_sendWithTimestamp Couldn't write MIDI data buffer. requested:%zu, written%zu", numTransferBytes, numWritten); } return numWritten - AMIDI_PACKET_OVERHEAD; } status_t AMIDI_flush(AMIDI_InputPort *inputPort) { 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", numTransferBytes, numWritten); return android::INVALID_OPERATION; } return OK; }