71f672b98a
Test: manual Change-Id: Ic181008427e6e81106d867cc3a70deef8c591841
275 lines
8.5 KiB
C++
275 lines
8.5 KiB
C++
/*
|
|
* 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;
|
|
}
|
|
|