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, ×tamp, 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