| /* |
| * 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; |
| } |
| |