Home | History | Annotate | Download | only in midi
      1 /*
      2  * Copyright (C) 2018 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 #define LOG_TAG "NativeMIDI"
     18 
     19 #include <poll.h>
     20 #include <unistd.h>
     21 
     22 #include <binder/Binder.h>
     23 #include <android_util_Binder.h>
     24 #include <utils/Log.h>
     25 
     26 #include <core_jni_helpers.h>
     27 
     28 #include "android/media/midi/BpMidiDeviceServer.h"
     29 #include "media/MidiDeviceInfo.h"
     30 
     31 #include "include/amidi/AMidi.h"
     32 #include "amidi_internal.h"
     33 
     34 using namespace android::media::midi;
     35 
     36 using android::IBinder;
     37 using android::BBinder;
     38 using android::OK;
     39 using android::sp;
     40 using android::status_t;
     41 using android::base::unique_fd;
     42 using android::binder::Status;
     43 
     44 struct AMIDI_Port {
     45     std::atomic_int     state;      // One of the port status constants below.
     46     const AMidiDevice  *device;    // Points to the AMidiDevice associated with the port.
     47     sp<IBinder>         binderToken;// The Binder token associated with the port.
     48     unique_fd           ufd;        // The unique file descriptor associated with the port.
     49 };
     50 
     51 /*
     52  * Port Status Constants
     53  */
     54 enum {
     55     MIDI_PORT_STATE_CLOSED = 0,
     56     MIDI_PORT_STATE_OPEN_IDLE,
     57     MIDI_PORT_STATE_OPEN_ACTIVE
     58 };
     59 
     60 /*
     61  * Port Type Constants
     62  */
     63 enum {
     64     PORTTYPE_OUTPUT = 0,
     65     PORTTYPE_INPUT = 1
     66 };
     67 
     68 /* TRANSFER PACKET FORMAT (as defined in MidiPortImpl.java)
     69  *
     70  * Transfer packet format is as follows (see MidiOutputPort.mThread.run() to see decomposition):
     71  * |oc|md|md| ......... |md|ts|ts|ts|ts|ts|ts|ts|ts|
     72  *  ^ +--------------------+-----------------------+
     73  *  |  ^                    ^
     74  *  |  |                    |
     75  *  |  |                    + timestamp (8 bytes)
     76  *  |  |
     77  *  |  + MIDI data bytes (numBytes bytes)
     78  *  |
     79  *  + OpCode (AMIDI_OPCODE_DATA)
     80  *
     81  *  NOTE: The socket pair is configured to use SOCK_SEQPACKET mode.
     82  *  SOCK_SEQPACKET, for a sequenced-packet socket that is connection-oriented, preserves message
     83  *  boundaries, and delivers messages in the order that they were sent.
     84  *  So 'read()' always returns a whole message.
     85  */
     86 #define AMIDI_PACKET_SIZE       1024
     87 #define AMIDI_PACKET_OVERHEAD   9
     88 #define AMIDI_BUFFER_SIZE       (AMIDI_PACKET_SIZE - AMIDI_PACKET_OVERHEAD)
     89 
     90 // JNI IDs (see android_media_midi.cpp)
     91 namespace android { namespace midi {
     92 //  MidiDevice Fields
     93 extern jfieldID gFidMidiNativeHandle;         // MidiDevice.mNativeHandle
     94 extern jfieldID gFidMidiDeviceServerBinder;   // MidiDevice.mDeviceServerBinder
     95 extern jfieldID gFidMidiDeviceInfo;           // MidiDevice.mDeviceInfo
     96 
     97 //  MidiDeviceInfo Fields
     98 extern jfieldID mFidMidiDeviceId;             // MidiDeviceInfo.mId
     99 }}
    100 using namespace android::midi;
    101 
    102 static std::mutex openMutex; // Ensure that the device can be connected just once to 1 thread
    103 
    104 //// Handy debugging function.
    105 //static void AMIDI_logBuffer(const uint8_t *data, size_t numBytes) {
    106 //    for (size_t index = 0; index < numBytes; index++) {
    107 //      ALOGI("  data @%zu [0x%X]", index, data[index]);
    108 //    }
    109 //}
    110 
    111 /*
    112  * Device Functions
    113  */
    114 /**
    115  * Retrieves information for the native MIDI device.
    116  *
    117  * device           The Native API token for the device. This value is obtained from the
    118  *                  AMidiDevice_fromJava().
    119  * outDeviceInfoPtr Receives the associated device info.
    120  *
    121  * Returns AMEDIA_OK or a negative error code.
    122  *  - AMEDIA_ERROR_INVALID_PARAMETER
    123  *  AMEDIA_ERROR_UNKNOWN
    124  */
    125 static media_status_t AMIDI_API AMIDI_getDeviceInfo(const AMidiDevice *device,
    126         AMidiDeviceInfo *outDeviceInfoPtr) {
    127     if (device == nullptr) {
    128         return AMEDIA_ERROR_INVALID_PARAMETER;
    129     }
    130 
    131     MidiDeviceInfo deviceInfo;
    132     Status txResult = device->server->getDeviceInfo(&deviceInfo);
    133     if (!txResult.isOk()) {
    134         ALOGE("AMIDI_getDeviceInfo transaction error: %d", txResult.transactionError());
    135         return AMEDIA_ERROR_UNKNOWN;
    136     }
    137 
    138     outDeviceInfoPtr->type = deviceInfo.getType();
    139     outDeviceInfoPtr->inputPortCount = deviceInfo.getInputPortNames().size();
    140     outDeviceInfoPtr->outputPortCount = deviceInfo.getOutputPortNames().size();
    141 
    142     return AMEDIA_OK;
    143 }
    144 
    145 media_status_t AMIDI_API AMidiDevice_fromJava(JNIEnv *env, jobject j_midiDeviceObj,
    146         AMidiDevice** devicePtrPtr)
    147 {
    148     if (j_midiDeviceObj == nullptr) {
    149         ALOGE("AMidiDevice_fromJava() invalid MidiDevice object.");
    150         return AMEDIA_ERROR_INVALID_OBJECT;
    151     }
    152 
    153     {
    154         std::lock_guard<std::mutex> guard(openMutex);
    155 
    156         long handle = env->GetLongField(j_midiDeviceObj, gFidMidiNativeHandle);
    157         if (handle != 0) {
    158             // Already opened by someone.
    159             return AMEDIA_ERROR_INVALID_OBJECT;
    160         }
    161 
    162         jobject serverBinderObj = env->GetObjectField(j_midiDeviceObj, gFidMidiDeviceServerBinder);
    163         sp<IBinder> serverBinder = android::ibinderForJavaObject(env, serverBinderObj);
    164         if (serverBinder.get() == nullptr) {
    165             ALOGE("AMidiDevice_fromJava couldn't connect to native MIDI server.");
    166             return AMEDIA_ERROR_UNKNOWN;
    167         }
    168 
    169         // don't check allocation failures, just abort..
    170         AMidiDevice* devicePtr = new AMidiDevice;
    171         devicePtr->server = new BpMidiDeviceServer(serverBinder);
    172         jobject midiDeviceInfoObj = env->GetObjectField(j_midiDeviceObj, gFidMidiDeviceInfo);
    173         devicePtr->deviceId = env->GetIntField(midiDeviceInfoObj, mFidMidiDeviceId);
    174 
    175         // Synchronize with the associated Java MidiDevice.
    176         env->SetLongField(j_midiDeviceObj, gFidMidiNativeHandle, (long)devicePtr);
    177         env->GetJavaVM(&devicePtr->javaVM);
    178         devicePtr->midiDeviceObj = env->NewGlobalRef(j_midiDeviceObj);
    179 
    180         if (AMIDI_getDeviceInfo(devicePtr, &devicePtr->deviceInfo) != AMEDIA_OK) {
    181             // This is weird, but maybe not fatal?
    182             ALOGE("AMidiDevice_fromJava couldn't retrieve attributes of native device.");
    183         }
    184 
    185         *devicePtrPtr = devicePtr;
    186     }
    187 
    188     return AMEDIA_OK;
    189 }
    190 
    191 media_status_t AMIDI_API AMidiDevice_release(const AMidiDevice *device)
    192 {
    193     if (device == nullptr || device->midiDeviceObj == nullptr) {
    194         return AMEDIA_ERROR_INVALID_PARAMETER;
    195     }
    196 
    197     JNIEnv* env;
    198     jint err = device->javaVM->GetEnv((void**)&env, JNI_VERSION_1_6);
    199     LOG_ALWAYS_FATAL_IF(err != JNI_OK, "AMidiDevice_release Error accessing JNIEnv err:%d", err);
    200 
    201     // Synchronize with the associated Java MidiDevice.
    202     {
    203         std::lock_guard<std::mutex> guard(openMutex);
    204         long handle = env->GetLongField(device->midiDeviceObj, gFidMidiNativeHandle);
    205         if (handle == 0) {
    206             // Not opened as native.
    207             ALOGE("AMidiDevice_release() device not opened in native client.");
    208             return AMEDIA_ERROR_INVALID_OBJECT;
    209         }
    210 
    211         env->SetLongField(device->midiDeviceObj, gFidMidiNativeHandle, 0L);
    212     }
    213     env->DeleteGlobalRef(device->midiDeviceObj);
    214 
    215     delete device;
    216 
    217     return AMEDIA_OK;
    218 }
    219 
    220 int32_t AMIDI_API AMidiDevice_getType(const AMidiDevice *device) {
    221     if (device == nullptr) {
    222         return AMEDIA_ERROR_INVALID_PARAMETER;
    223     }
    224     return device->deviceInfo.type;
    225 }
    226 
    227 ssize_t AMIDI_API AMidiDevice_getNumInputPorts(const AMidiDevice *device) {
    228     if (device == nullptr) {
    229         return AMEDIA_ERROR_INVALID_PARAMETER;
    230     }
    231     return device->deviceInfo.inputPortCount;
    232 }
    233 
    234 ssize_t AMIDI_API AMidiDevice_getNumOutputPorts(const AMidiDevice *device) {
    235     if (device == nullptr) {
    236         return AMEDIA_ERROR_INVALID_PARAMETER;
    237     }
    238     return device->deviceInfo.outputPortCount;
    239 }
    240 
    241 /*
    242  * Port Helpers
    243  */
    244 static media_status_t AMIDI_openPort(const AMidiDevice *device, int32_t portNumber, int type,
    245         AMIDI_Port **portPtr) {
    246     if (device == nullptr) {
    247         return AMEDIA_ERROR_INVALID_PARAMETER;
    248     }
    249 
    250     sp<BBinder> portToken(new BBinder());
    251     unique_fd ufd;
    252     Status txResult = type == PORTTYPE_OUTPUT
    253             ? device->server->openOutputPort(portToken, portNumber, &ufd)
    254             : device->server->openInputPort(portToken, portNumber, &ufd);
    255     if (!txResult.isOk()) {
    256         ALOGE("AMIDI_openPort transaction error: %d", txResult.transactionError());
    257         return AMEDIA_ERROR_UNKNOWN;
    258     }
    259 
    260     AMIDI_Port *port = new AMIDI_Port;
    261     port->state = MIDI_PORT_STATE_OPEN_IDLE;
    262     port->device = device;
    263     port->binderToken = portToken;
    264     port->ufd = std::move(ufd);
    265 
    266     *portPtr = port;
    267 
    268     return AMEDIA_OK;
    269 }
    270 
    271 static void AMIDI_closePort(AMIDI_Port *port) {
    272     if (port == nullptr) {
    273         return;
    274     }
    275 
    276     int portState = MIDI_PORT_STATE_OPEN_IDLE;
    277     while (!port->state.compare_exchange_weak(portState, MIDI_PORT_STATE_CLOSED)) {
    278         if (portState == MIDI_PORT_STATE_CLOSED) {
    279             return; // Already closed
    280         }
    281     }
    282 
    283     Status txResult = port->device->server->closePort(port->binderToken);
    284     if (!txResult.isOk()) {
    285         ALOGE("Transaction error closing MIDI port:%d", txResult.transactionError());
    286     }
    287 
    288     delete port;
    289 }
    290 
    291 /*
    292  * Output (receiving) API
    293  */
    294 media_status_t AMIDI_API AMidiOutputPort_open(const AMidiDevice *device, int32_t portNumber,
    295         AMidiOutputPort **outOutputPortPtr) {
    296     return AMIDI_openPort(device, portNumber, PORTTYPE_OUTPUT, (AMIDI_Port**)outOutputPortPtr);
    297 }
    298 
    299 /*
    300  *  A little RAII (https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization)
    301  *  class to ensure that the port state is correct irrespective of errors.
    302  */
    303 class MidiReceiver {
    304 public:
    305     MidiReceiver(AMIDI_Port *port) : mPort(port) {}
    306 
    307     ~MidiReceiver() {
    308         // flag the port state to idle
    309         mPort->state.store(MIDI_PORT_STATE_OPEN_IDLE);
    310     }
    311 
    312     ssize_t receive(int32_t *opcodePtr, uint8_t *buffer, size_t maxBytes,
    313             size_t *numBytesReceivedPtr, int64_t *timestampPtr) {
    314         int portState = MIDI_PORT_STATE_OPEN_IDLE;
    315         // check to see if the port is idle, then set to active
    316         if (!mPort->state.compare_exchange_strong(portState, MIDI_PORT_STATE_OPEN_ACTIVE)) {
    317             // The port not idle or has been closed.
    318             return AMEDIA_ERROR_UNKNOWN;
    319         }
    320 
    321         struct pollfd checkFds[1] = { { mPort->ufd, POLLIN, 0 } };
    322         if (poll(checkFds, 1, 0) < 1) {
    323             // Nothing there
    324             return 0;
    325         }
    326 
    327         uint8_t readBuffer[AMIDI_PACKET_SIZE];
    328         ssize_t readCount = read(mPort->ufd, readBuffer, sizeof(readBuffer));
    329         if (readCount == EINTR || readCount < 1) {
    330             return  AMEDIA_ERROR_UNKNOWN;
    331         }
    332 
    333         // see Packet Format definition at the top of this file.
    334         size_t numMessageBytes = 0;
    335         *opcodePtr = readBuffer[0];
    336         if (*opcodePtr == AMIDI_OPCODE_DATA && readCount >= AMIDI_PACKET_OVERHEAD) {
    337             numMessageBytes = readCount - AMIDI_PACKET_OVERHEAD;
    338             numMessageBytes = std::min(maxBytes, numMessageBytes);
    339             memcpy(buffer, readBuffer + 1, numMessageBytes);
    340             if (timestampPtr != nullptr) {
    341                 memcpy(timestampPtr, readBuffer + readCount - sizeof(uint64_t),
    342                         sizeof(*timestampPtr));
    343             }
    344         }
    345         *numBytesReceivedPtr = numMessageBytes;
    346         return 1;
    347     }
    348 
    349 private:
    350     AMIDI_Port *mPort;
    351 };
    352 
    353 ssize_t AMIDI_API AMidiOutputPort_receive(const AMidiOutputPort *outputPort, int32_t *opcodePtr,
    354          uint8_t *buffer, size_t maxBytes, size_t* numBytesReceivedPtr, int64_t *timestampPtr) {
    355 
    356     if (outputPort == nullptr || buffer == nullptr) {
    357         return -EINVAL;
    358     }
    359 
    360    return MidiReceiver((AMIDI_Port*)outputPort).receive(opcodePtr, buffer, maxBytes,
    361            numBytesReceivedPtr, timestampPtr);
    362 }
    363 
    364 void AMIDI_API AMidiOutputPort_close(const AMidiOutputPort *outputPort) {
    365     AMIDI_closePort((AMIDI_Port*)outputPort);
    366 }
    367 
    368 /*
    369  * Input (sending) API
    370  */
    371 media_status_t AMIDI_API AMidiInputPort_open(const AMidiDevice *device, int32_t portNumber,
    372         AMidiInputPort **outInputPortPtr) {
    373     return AMIDI_openPort(device, portNumber, PORTTYPE_INPUT, (AMIDI_Port**)outInputPortPtr);
    374 }
    375 
    376 void AMIDI_API AMidiInputPort_close(const AMidiInputPort *inputPort) {
    377     AMIDI_closePort((AMIDI_Port*)inputPort);
    378 }
    379 
    380 static ssize_t AMIDI_makeSendBuffer(
    381         uint8_t *buffer, const uint8_t *data, size_t numBytes, uint64_t timestamp) {
    382     // Error checking will happen in the caller since this isn't an API function.
    383     buffer[0] = AMIDI_OPCODE_DATA;
    384     memcpy(buffer + 1, data, numBytes);
    385     memcpy(buffer + 1 + numBytes, &timestamp, sizeof(timestamp));
    386     return numBytes + AMIDI_PACKET_OVERHEAD;
    387 }
    388 
    389 ssize_t AMIDI_API AMidiInputPort_send(const AMidiInputPort *inputPort, const uint8_t *buffer,
    390                             size_t numBytes) {
    391     return AMidiInputPort_sendWithTimestamp(inputPort, buffer, numBytes, 0);
    392 }
    393 
    394 ssize_t AMIDI_API AMidiInputPort_sendWithTimestamp(const AMidiInputPort *inputPort,
    395         const uint8_t *data, size_t numBytes, int64_t timestamp) {
    396     if (inputPort == nullptr || data == nullptr) {
    397         return AMEDIA_ERROR_INVALID_PARAMETER;
    398     }
    399 
    400     // AMIDI_logBuffer(data, numBytes);
    401 
    402     uint8_t writeBuffer[AMIDI_BUFFER_SIZE + AMIDI_PACKET_OVERHEAD];
    403     size_t numSent = 0;
    404     while (numSent < numBytes) {
    405         size_t blockSize = AMIDI_BUFFER_SIZE;
    406         blockSize = std::min(blockSize, numBytes - numSent);
    407 
    408         ssize_t numTransferBytes =
    409                 AMIDI_makeSendBuffer(writeBuffer, data + numSent, blockSize, timestamp);
    410         ssize_t numWritten = write(((AMIDI_Port*)inputPort)->ufd, writeBuffer, numTransferBytes);
    411         if (numWritten < 0) {
    412             break;  // error so bail out.
    413         }
    414         if (numWritten < numTransferBytes) {
    415             ALOGE("AMidiInputPort_sendWithTimestamp Couldn't write MIDI data buffer."
    416                   " requested:%zu, written%zu",numTransferBytes, numWritten);
    417             break;  // bail
    418         }
    419 
    420         numSent += numWritten  - AMIDI_PACKET_OVERHEAD;
    421     }
    422 
    423     return numSent;
    424 }
    425 
    426 media_status_t AMIDI_API AMidiInputPort_sendFlush(const AMidiInputPort *inputPort) {
    427     if (inputPort == nullptr) {
    428         return AMEDIA_ERROR_INVALID_PARAMETER;
    429     }
    430 
    431     uint8_t opCode = AMIDI_OPCODE_FLUSH;
    432     ssize_t numTransferBytes = 1;
    433     ssize_t numWritten = write(((AMIDI_Port*)inputPort)->ufd, &opCode, numTransferBytes);
    434 
    435     if (numWritten < numTransferBytes) {
    436         ALOGE("AMidiInputPort_flush Couldn't write MIDI flush. requested:%zd, written:%zd",
    437                 numTransferBytes, numWritten);
    438         return AMEDIA_ERROR_UNSUPPORTED;
    439     }
    440 
    441     return AMEDIA_OK;
    442 }
    443 
    444