diff options
author | Paul McLean <pmclean@google.com> | 2017-03-14 15:20:51 -0700 |
---|---|---|
committer | Paul McLean <pmclean@google.com> | 2018-03-15 16:20:07 -0700 |
commit | 8a3e33b36a6259708ed44a9c41078d49c0fa9da4 (patch) | |
tree | 4b0e47637cd67486224ffbc865b75e2ecf229ab2 /media/native | |
parent | cbeeed1255c0414afea45292a58bd533b67efa07 (diff) |
(re)integrating Native MIDI API into NDK - base API
Implement native MIDI API (amidi)
Bug: 30252756
Bug: 37090545
Test: manual - Connect PreSonus AudioBox 22VSL and run tests in NativeMidiTestbed app.
Verify MIDI messages sent to external MIDI synthesizer.
Verify MIDI messages received from external MIDI synthesizer.
CTS
Change-Id: I7bb02b8926d01090132ce873c785b5323a9fa5f8
Diffstat (limited to 'media/native')
-rw-r--r-- | media/native/midi/Android.bp | 29 | ||||
-rw-r--r-- | media/native/midi/include/NOTICE | 13 | ||||
-rw-r--r-- | media/native/midi/include/midi.h | 255 | ||||
-rw-r--r-- | media/native/midi/libamidi.map.txt | 18 | ||||
-rw-r--r-- | media/native/midi/midi.cpp | 394 | ||||
-rw-r--r-- | media/native/midi/midi.h | 182 | ||||
-rw-r--r-- | media/native/midi/midi_internal.h | 22 |
7 files changed, 621 insertions, 292 deletions
diff --git a/media/native/midi/Android.bp b/media/native/midi/Android.bp index 11f0deabbcd5..d069bd2f6284 100644 --- a/media/native/midi/Android.bp +++ b/media/native/midi/Android.bp @@ -13,7 +13,7 @@ // limitations under the License. cc_library_shared { - name: "libmidi", + name: "libamidi", srcs: [ "midi.cpp", @@ -22,13 +22,13 @@ cc_library_shared { aidl: { include_dirs: ["frameworks/base/media/java"], - export_aidl_headers: true, + export_aidl_headers: false, }, cflags: [ "-Wall", "-Werror", - "-O0", + "-fvisibility=hidden", ], shared_libs: [ @@ -36,7 +36,28 @@ cc_library_shared { "libbinder", "libutils", "libmedia", + "libmediandk", + "libandroid_runtime", ], - export_include_dirs: ["."], + export_include_dirs: ["include"], +} + +ndk_headers { + name: "amidi", + + from: "include", + + to: "amidi", + + srcs: ["include/midi.h"], + license: "include/NOTICE", +} + +ndk_library { + name: "libamidi", + + symbol_file: "libamidi.map.txt", + + first_version: "29", } diff --git a/media/native/midi/include/NOTICE b/media/native/midi/include/NOTICE new file mode 100644 index 000000000000..e72ff94a8401 --- /dev/null +++ b/media/native/midi/include/NOTICE @@ -0,0 +1,13 @@ +Copyright (C) 2018 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. diff --git a/media/native/midi/include/midi.h b/media/native/midi/include/midi.h new file mode 100644 index 000000000000..780d8a7bfd40 --- /dev/null +++ b/media/native/midi/include/midi.h @@ -0,0 +1,255 @@ +/* + * Copyright (C) 2018 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. + */ + +#ifndef ANDROID_MEDIA_MIDI_H_ +#define ANDROID_MEDIA_MIDI_H_ + +#include <stdarg.h> +#include <stdint.h> +#include <sys/types.h> + +#include <jni.h> + +#include <media/NdkMediaError.h> + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct AMidiDevice AMidiDevice; +typedef struct AMidiInputPort AMidiInputPort; +typedef struct AMidiOutputPort AMidiOutputPort; + +#define AMIDI_API __attribute__((visibility("default"))) + +/* + * Message Op Codes. Used to parse MIDI data packets + */ +enum { + AMIDI_OPCODE_DATA = 1, /* The MIDI packet contains normal MIDI data */ + AMIDI_OPCODE_FLUSH = 2, /* The MIDI packet contains just a MIDI FLUSH command. */ + /* Forces the send of any pending MIDI data. */ +}; + +/* + * Type IDs for various MIDI devices. + */ +enum { + AMIDI_DEVICE_TYPE_USB = 1, /* A MIDI device connected to the Android USB port */ + AMIDI_DEVICE_TYPE_VIRTUAL = 2, /* A software object implementing MidiDeviceService */ + AMIDI_DEVICE_TYPE_BLUETOOTH = 3 /* A MIDI device connected via BlueTooth */ +}; + +/* + * Device API + */ +/** + * Connects a native Midi Device object to the associated Java MidiDevice object. Use this + * AMidiDevice to access the rest of the native MIDI API. Use AMidiDevice_release() to + * disconnect from the Java object when not being used any more. + * + * @param env Points to the Java Environment. + * @param midiDeviceObj The Java MidiDevice Object. + * @param outDevicePtrPtr Points to the pointer to receive the AMidiDevice + * + * @return AMEDIA_OK on success, or a negative error value: + * @see AMEDIA_ERROR_INVALID_OBJECT {@link AMEDIA_ERROR_INVALID_OBJECT} - the midiDeviceObj + * is null or already connected to a native AMidiDevice + * @see AMEDIA_ERROR_UNKNOWN {@link AMEDIA_ERROR_UNKNOWN} - an unknown error occurred. + */ +media_status_t AMIDI_API AMidiDevice_fromJava( + JNIEnv *env, jobject midiDeviceObj, AMidiDevice **outDevicePtrPtr); + +/** + * Disconnects the native Midi Device Object from the associated Java MidiDevice object. + * + * @param midiDevice Points to the native AMIDI_MidiDevice. + * + * @return AMEDIA_OK on success, + * or a negative error value: + * @see AMEDIA_ERROR_INVALID_PARAMETER {@link AMEDIA_ERROR_INVALID_PARAMETER} + * - the device parameter is NULL. + * @see AMEDIA_ERROR_INVALID_OBJECT {@link AMEDIA_ERROR_INVALID_OBJECT} + * - the device is not consistent with the associated Java MidiDevice. + * @see AMEDIA_ERROR_INVALID_OBJECT {@link AMEDIA_ERROR_INVALID_OBJECT} + * - the JNI interface initialization to the associated java MidiDevice failed. + * @see AMEDIA_ERROR_UNKNOWN {@link AMEDIA_ERROR_UNKNOWN} - couldn't retrieve the device info. + */ +media_status_t AMIDI_API AMidiDevice_release(const AMidiDevice *midiDevice); + +/** + * Gets the MIDI device type. + * + * @param device Specifies the MIDI device. + * + * @return The identifier of the MIDI device type: + * AMIDI_DEVICE_TYPE_USB + * AMIDI_DEVICE_TYPE_VIRTUAL + * AMIDI_DEVICE_TYPE_BLUETOOTH + * or a negative error value: + * @see AMEDIA_ERROR_INVALID_PARAMETER {@link AMEDIA_ERROR_INVALID_PARAMETER} - the device + * parameter is NULL. + * @see AMEDIA_ERROR_UNKNOWN {@link AMEDIA_ERROR_UNKNOWN} - Unknown error. + */ +int32_t AMIDI_API AMidiDevice_getType(const AMidiDevice *device); + +/** + * Gets the number of input (sending) ports available on the specified MIDI device. + * + * @param device Specifies the MIDI device. + * + * @return If successful, returns the number of MIDI input (sending) ports available on the + * device. If an error occurs, returns a negative value indicating the error: + * @see AMEDIA_ERROR_INVALID_PARAMETER {@link AMEDIA_ERROR_INVALID_PARAMETER} - the device + * parameter is NULL. + * @see AMEDIA_ERROR_UNKNOWN {@link AMEDIA_ERROR_UNKNOWN} - couldn't retrieve the device info. + */ +ssize_t AMIDI_API AMidiDevice_getNumInputPorts(const AMidiDevice *device); + +/** + * Gets the number of output (receiving) ports available on the specified MIDI device. + * + * @param device Specifies the MIDI device. + * + * @return If successful, returns the number of MIDI output (receiving) ports available on the + * device. If an error occurs, returns a negative value indicating the error: + * @see AMEDIA_ERROR_INVALID_PARAMETER {@link AMEDIA_ERROR_INVALID_PARAMETER} - the device + * parameter is NULL. + * @see AMEDIA_ERROR_UNKNOWN {@link AMEDIA_ERROR_UNKNOWN}- couldn't retrieve the device info. + */ +ssize_t AMIDI_API AMidiDevice_getNumOutputPorts(const AMidiDevice *device); + +/* + * API for receiving data from the Output port of a device. + */ +/** + * Opens the output port so that the client can receive data from it. The port remains open and + * valid until AMidiOutputPort_close() is called for the returned AMidiOutputPort. + * + * @param device Specifies the MIDI device. + * @param portNumber Specifies the zero-based port index on the device to open. This value ranges + * between 0 and one less than the number of output ports reported by the + * AMidiDevice_getNumOutputPorts function. + * @param outOutputPortPtr Receives the native API port identifier of the opened port. + * + * @return AMEDIA_OK, or a negative error code: + * @see AMEDIA_ERROR_UNKNOWN {@link AMEDIA_ERROR_UNKNOWN} - Unknown Error. + */ +media_status_t AMIDI_API AMidiOutputPort_open(const AMidiDevice *device, int32_t portNumber, + AMidiOutputPort **outOutputPortPtr); + +/** + * Closes the output port. + * + * @param outputPort The native API port identifier of the port. + */ +void AMIDI_API AMidiOutputPort_close(const AMidiOutputPort *outputPort); + +/** + * Receives the next pending MIDI message. To retrieve all pending messages, the client should + * repeatedly call this method until it returns 0. + * + * Note that this is a non-blocking call. If there are no Midi messages are available, the function + * returns 0 immediately (for 0 messages received). + * + * @param outputPort Identifies the port to receive messages from. + * @param opcodePtr Receives the message Op Code. + * @param buffer Points to the buffer to receive the message data bytes. + * @param maxBytes Specifies the size of the buffer pointed to by the buffer parameter. + * @param numBytesReceivedPtr On exit, receives the actual number of bytes stored in buffer. + * @param outTimestampPtr If non-NULL, receives the timestamp associated with the message. + * (the current value of the running Java Virtual Machine's high-resolution time source, + * in nanoseconds) + * @return the number of messages received (either 0 or 1), or a negative error code: + * @see AMEDIA_ERROR_UNKNOWN {@link AMEDIA_ERROR_UNKNOWN} - Unknown Error. + */ +ssize_t AMIDI_API AMidiOutputPort_receive(const AMidiOutputPort *outputPort, int32_t *opcodePtr, + uint8_t *buffer, size_t maxBytes, size_t* numBytesReceivedPtr, int64_t *outTimestampPtr); + +/* + * API for sending data to the Input port of a device. + */ +/** + * Opens the input port so that the client can send data to it. The port remains open and + * valid until AMidiInputPort_close() is called for the returned AMidiInputPort. + * + * @param device Specifies the MIDI device. + * @param portNumber Specifies the zero-based port index on the device to open. This value ranges + * between 0 and one less than the number of input ports reported by the + * AMidiDevice_getNumInputPorts() function.. + * @param outInputPortPtr Receives the native API port identifier of the opened port. + * + * @return AMEDIA_OK, or a negative error code: + * @see AMEDIA_ERROR_UNKNOWN {@link AMEDIA_ERROR_UNKNOWN} - Unknown Error. + */ +media_status_t AMIDI_API AMidiInputPort_open(const AMidiDevice *device, int32_t portNumber, + AMidiInputPort **outInputPortPtr); + +/** + * Sends data to the specified input port. + * + * @param inputPort The identifier of the port to send data to. + * @param buffer Points to the array of bytes containing the data to send. + * @param numBytes Specifies the number of bytes to write. + * + * @return The number of bytes sent, which could be less than specified or a negative error code: + * @see AMEDIA_ERROR_INVALID_PARAMETER {@link AMEDIA_ERROR_INVALID_PARAMETER} The specified port + * was NULL, the specified buffer was NULL. + */ +ssize_t AMIDI_API AMidiInputPort_send(const AMidiInputPort *inputPort, const uint8_t *buffer, + size_t numBytes); + +/** + * Sends data to the specified input port with a timestamp. + * + * @param inputPort The identifier of the port to send data to. + * @param buffer Points to the array of bytes containing the data to send. + * @param numBytes Specifies the number of bytes to write. + * @param timestamp The CLOCK_MONOTONIC time in nanoseconds to associate with the sent data. + * + * @return The number of bytes sent, which could be less than specified or a negative error code: + * @see AMEDIA_ERROR_INVALID_PARAMETER {@link AMEDIA_ERROR_INVALID_PARAMETER} The specified port + * was NULL, the specified buffer was NULL. + */ +ssize_t AMIDI_API AMidiInputPort_sendWithTimestamp(const AMidiInputPort *inputPort, + const uint8_t *buffer, size_t numBytes, int64_t timestamp); + +/** + * Sends a message with a 'MIDI flush command code' to the specified port. This should cause + * a receiver to discard any pending MIDI data it may have accumulated and not processed. + * + * @param inputPort The identifier of the port to send the flush command to. + * + * @returns @see AMEDIA_OK {@link AMEDIA_OK} if successful, otherwise a negative error code: + * @see AMEDIA_ERROR_INVALID_PARAMETER {@link AMEDIA_ERROR_INVALID_PARAMETER} The specified port + * was NULL + * @see AMEDIA_ERROR_UNSUPPORTED {@link AMEDIA_ERROR_UNSUPPORTED} The FLUSH command couldn't + * be sent. + */ +media_status_t AMIDI_API AMidiInputPort_sendFlush(const AMidiInputPort *inputPort); + +/** + * Closes the input port. + * + * @param inputPort Identifies the input (sending) port to close. + */ +void AMIDI_API AMidiInputPort_close(const AMidiInputPort *inputPort); + +#ifdef __cplusplus +} +#endif + +#endif /* ANDROID_MEDIA_MIDI_H_ */ diff --git a/media/native/midi/libamidi.map.txt b/media/native/midi/libamidi.map.txt new file mode 100644 index 000000000000..62627f8c5ef7 --- /dev/null +++ b/media/native/midi/libamidi.map.txt @@ -0,0 +1,18 @@ +LIBAMIDI { + global: + AMidiDevice_fromJava; + AMidiDevice_release; + AMidiDevice_getType; + AMidiDevice_getNumInputPorts; + AMidiDevice_getNumOutputPorts; + AMidiOutputPort_open; + AMidiOutputPort_close; + AMidiOutputPort_receive; + AMidiInputPort_open; + AMidiInputPort_send; + AMidiInputPort_sendWithTimestamp; + AMidiInputPort_sendFlush; + AMidiInputPort_close; + local: + *; +}; diff --git a/media/native/midi/midi.cpp b/media/native/midi/midi.cpp index 17b92da3325e..a9b498ffe0c8 100644 --- a/media/native/midi/midi.cpp +++ b/media/native/midi/midi.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2018 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. @@ -20,15 +20,19 @@ #include <unistd.h> #include <binder/Binder.h> -#include <utils/Errors.h> +#include <android_util_Binder.h> #include <utils/Log.h> +#include <core_jni_helpers.h> + #include "android/media/midi/BpMidiDeviceServer.h" #include "media/MidiDeviceInfo.h" -#include "midi.h" +#include "include/midi.h" #include "midi_internal.h" +using namespace android::media::midi; + using android::IBinder; using android::BBinder; using android::OK; @@ -36,23 +40,26 @@ 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; + std::atomic_int state; // One of the port status constants below. + const AMidiDevice *device; // Points to the AMidiDevice associated with the port. + sp<IBinder> binderToken;// The Binder token associated with the port. + unique_fd ufd; // The unique file descriptor associated with the port. }; -#define SIZE_MIDIRECEIVEBUFFER AMIDI_BUFFER_SIZE - +/* + * Port Status Constants + */ enum { MIDI_PORT_STATE_CLOSED = 0, MIDI_PORT_STATE_OPEN_IDLE, MIDI_PORT_STATE_OPEN_ACTIVE }; +/* + * Port Type Constants + */ enum { PORTTYPE_OUTPUT = 0, PORTTYPE_INPUT = 1 @@ -76,32 +83,192 @@ enum { * boundaries, and delivers messages in the order that they were sent. * So 'read()' always returns a whole message. */ +#define AMIDI_PACKET_SIZE 1024 +#define AMIDI_PACKET_OVERHEAD 9 +#define AMIDI_BUFFER_SIZE (AMIDI_PACKET_SIZE - AMIDI_PACKET_OVERHEAD) + +// MidiDevice Fields +static jobject deviceClassGlobalRef = nullptr; // A GlobalRef for MidiDevice Class +static jfieldID fidDeviceClosed = nullptr; // MidiDevice.mIsDeviceClosed +static jfieldID fidNativeHandle = nullptr; // MidiDevice.mNativeHandle +static jfieldID fidDeviceServerBinder = nullptr; // MidiDevice.mDeviceServerBinder +static jfieldID fidDeviceInfo = nullptr; // MidiDevice.mDeviceInfo + +// MidiDeviceInfo Fields +static jobject deviceInfoClassGlobalRef = nullptr; // A GlobalRef for MidiDeviceInfoClass +static jfieldID fidDeviceId = nullptr; // MidiDeviceInfo.mId + +static std::mutex openMutex; // Ensure that the device can be connected just once to 1 thread + +static void AMIDI_initJNI(JNIEnv *env) { + jclass deviceClass = android::FindClassOrDie(env, "android/media/midi/MidiDevice"); + deviceClassGlobalRef = env->NewGlobalRef(deviceClass); + + // MidiDevice Field IDs + fidDeviceClosed = android::GetFieldIDOrDie(env, deviceClass, "mIsDeviceClosed", "Z"); + fidNativeHandle = android::GetFieldIDOrDie(env, deviceClass, "mNativeHandle", "J"); + fidDeviceServerBinder = android::GetFieldIDOrDie(env, deviceClass, + "mDeviceServerBinder", "Landroid/os/IBinder;"); + fidDeviceInfo = android::GetFieldIDOrDie(env, deviceClass, + "mDeviceInfo", "Landroid/media/midi/MidiDeviceInfo;"); + + // MidiDeviceInfo Field IDs + jclass deviceInfoClass = android::FindClassOrDie(env, "android/media/midi/MidiDeviceInfo"); + deviceInfoClassGlobalRef = env->NewGlobalRef(deviceInfoClass); + fidDeviceId = android::GetFieldIDOrDie(env, deviceInfoClass, "mId", "I"); +} + +//// Handy debugging function. +//static void AMIDI_logBuffer(const uint8_t *data, size_t numBytes) { +// for (size_t index = 0; index < numBytes; index++) { +// ALOGI(" data @%zu [0x%X]", index, data[index]); +// } +//} /* * Device Functions */ -status_t AMIDI_getDeviceInfo(AMIDI_Device *device, AMIDI_DeviceInfo *deviceInfoPtr) { +/** + * Retrieves information for the native MIDI device. + * + * device The Native API token for the device. This value is obtained from the + * AMidiDevice_fromJava(). + * outDeviceInfoPtr Receives the associated device info. + * + * Returns AMEDIA_OK or a negative error code. + * - AMEDIA_ERROR_INVALID_PARAMETER + * AMEDIA_ERROR_UNKNOWN + */ +static media_status_t AMIDI_API AMIDI_getDeviceInfo(const AMidiDevice *device, + AMidiDeviceInfo *outDeviceInfoPtr) { + if (device == nullptr) { + return AMEDIA_ERROR_INVALID_PARAMETER; + } + MidiDeviceInfo deviceInfo; Status txResult = device->server->getDeviceInfo(&deviceInfo); if (!txResult.isOk()) { ALOGE("AMIDI_getDeviceInfo transaction error: %d", txResult.transactionError()); - return txResult.transactionError(); + return AMEDIA_ERROR_UNKNOWN; + } + + outDeviceInfoPtr->type = deviceInfo.getType(); + outDeviceInfoPtr->inputPortCount = deviceInfo.getInputPortNames().size(); + outDeviceInfoPtr->outputPortCount = deviceInfo.getOutputPortNames().size(); + + return AMEDIA_OK; +} + +media_status_t AMIDI_API AMidiDevice_fromJava(JNIEnv *env, jobject j_midiDeviceObj, + AMidiDevice** devicePtrPtr) +{ + // Ensures JNI initialization is performed just once. + static std::once_flag initCallFlag; + std::call_once(initCallFlag, AMIDI_initJNI, env); + + if (j_midiDeviceObj == nullptr) { + ALOGE("AMidiDevice_fromJava() invalid MidiDevice object."); + return AMEDIA_ERROR_INVALID_OBJECT; + } + + { + std::lock_guard<std::mutex> guard(openMutex); + + long handle = env->GetLongField(j_midiDeviceObj, fidNativeHandle); + if (handle != 0) { + // Already opened by someone. + return AMEDIA_ERROR_INVALID_OBJECT; + } + + jobject serverBinderObj = env->GetObjectField(j_midiDeviceObj, fidDeviceServerBinder); + sp<IBinder> serverBinder = android::ibinderForJavaObject(env, serverBinderObj); + if (serverBinder.get() == nullptr) { + ALOGE("AMidiDevice_fromJava couldn't connect to native MIDI server."); + return AMEDIA_ERROR_UNKNOWN; + } + + // don't check allocation failures, just abort.. + AMidiDevice* devicePtr = new AMidiDevice; + devicePtr->server = new BpMidiDeviceServer(serverBinder); + jobject midiDeviceInfoObj = env->GetObjectField(j_midiDeviceObj, fidDeviceInfo); + devicePtr->deviceId = env->GetIntField(midiDeviceInfoObj, fidDeviceId); + + // Synchronize with the associated Java MidiDevice. + env->SetLongField(j_midiDeviceObj, fidNativeHandle, (long)devicePtr); + env->GetJavaVM(&devicePtr->javaVM); + devicePtr->midiDeviceObj = env->NewGlobalRef(j_midiDeviceObj); + + if (AMIDI_getDeviceInfo(devicePtr, &devicePtr->deviceInfo) != AMEDIA_OK) { + // This is weird, but maybe not fatal? + ALOGE("AMidiDevice_fromJava couldn't retrieve attributes of native device."); + } + + *devicePtrPtr = devicePtr; + } + + return AMEDIA_OK; +} + +media_status_t AMIDI_API AMidiDevice_release(const AMidiDevice *device) +{ + if (device == nullptr || device->midiDeviceObj == nullptr) { + return AMEDIA_ERROR_INVALID_PARAMETER; + } + + JNIEnv* env; + jint err = device->javaVM->GetEnv((void**)&env, JNI_VERSION_1_6); + LOG_ALWAYS_FATAL_IF(err != JNI_OK, "AMidiDevice_release Error accessing JNIEnv err:%d", err); + + // Synchronize with the associated Java MidiDevice. + // env->CallVoidMethod(j_midiDeviceObj, midClearNativeHandle); + { + std::lock_guard<std::mutex> guard(openMutex); + long handle = env->GetLongField(device->midiDeviceObj, fidNativeHandle); + if (handle == 0) { + // Not opened as native. + ALOGE("AMidiDevice_release() device not opened in native client."); + return AMEDIA_ERROR_INVALID_OBJECT; + } + + env->SetLongField(device->midiDeviceObj, fidNativeHandle, 0L); } + env->DeleteGlobalRef(device->midiDeviceObj); + + delete device; - deviceInfoPtr->type = deviceInfo.getType(); - deviceInfoPtr->uid = deviceInfo.getUid(); - deviceInfoPtr->isPrivate = deviceInfo.isPrivate(); - deviceInfoPtr->inputPortCount = deviceInfo.getInputPortNames().size(); - deviceInfoPtr->outputPortCount = deviceInfo.getOutputPortNames().size(); + return AMEDIA_OK; +} - return OK; +int32_t AMIDI_API AMidiDevice_getType(const AMidiDevice *device) { + if (device == nullptr) { + return AMEDIA_ERROR_INVALID_PARAMETER; + } + return device->deviceInfo.type; +} + +ssize_t AMIDI_API AMidiDevice_getNumInputPorts(const AMidiDevice *device) { + if (device == nullptr) { + return AMEDIA_ERROR_INVALID_PARAMETER; + } + return device->deviceInfo.inputPortCount; +} + +ssize_t AMIDI_API AMidiDevice_getNumOutputPorts(const AMidiDevice *device) { + if (device == nullptr) { + return AMEDIA_ERROR_INVALID_PARAMETER; + } + return device->deviceInfo.outputPortCount; } /* * Port Helpers */ -static status_t AMIDI_openPort(AMIDI_Device *device, int portNumber, int type, +static media_status_t AMIDI_openPort(const AMidiDevice *device, int32_t portNumber, int type, AMIDI_Port **portPtr) { + if (device == nullptr) { + return AMEDIA_ERROR_INVALID_PARAMETER; + } + sp<BBinder> portToken(new BBinder()); unique_fd ufd; Status txResult = type == PORTTYPE_OUTPUT @@ -109,10 +276,10 @@ static status_t AMIDI_openPort(AMIDI_Device *device, int portNumber, int type, : device->server->openInputPort(portToken, portNumber, &ufd); if (!txResult.isOk()) { ALOGE("AMIDI_openPort transaction error: %d", txResult.transactionError()); - return txResult.transactionError(); + return AMEDIA_ERROR_UNKNOWN; } - AMIDI_Port* port = new AMIDI_Port; + AMIDI_Port *port = new AMIDI_Port; port->state = MIDI_PORT_STATE_OPEN_IDLE; port->device = device; port->binderToken = portToken; @@ -120,155 +287,178 @@ static status_t AMIDI_openPort(AMIDI_Device *device, int portNumber, int type, *portPtr = port; - return OK; + return AMEDIA_OK; } -static status_t AMIDI_closePort(AMIDI_Port *port) { +static void AMIDI_closePort(AMIDI_Port *port) { + if (port == nullptr) { + return; + } + 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 + return; // Already closed } } Status txResult = port->device->server->closePort(port->binderToken); if (!txResult.isOk()) { - return txResult.transactionError(); + ALOGE("Transaction error closing MIDI port:%d", 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); +media_status_t AMIDI_API AMidiOutputPort_open(const AMidiDevice *device, int32_t portNumber, + AMidiOutputPort **outOutputPortPtr) { + return AMIDI_openPort(device, portNumber, PORTTYPE_OUTPUT, (AMIDI_Port**)outOutputPortPtr); } -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; +/* + * A little RAII (https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization) + * class to ensure that the port state is correct irrespective of errors. + */ +class MidiReceiver { +public: + MidiReceiver(AMIDI_Port *port) : mPort(port) {} + + ~MidiReceiver() { + // flag the port state to idle + mPort->state.store(MIDI_PORT_STATE_OPEN_IDLE); } - 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; + ssize_t receive(int32_t *opcodePtr, uint8_t *buffer, size_t maxBytes, + size_t *numBytesReceivedPtr, int64_t *timestampPtr) { + int portState = MIDI_PORT_STATE_OPEN_IDLE; + // check to see if the port is idle, then set to active + if (!mPort->state.compare_exchange_strong(portState, MIDI_PORT_STATE_OPEN_ACTIVE)) { + // The port not idle or has been closed. + return AMEDIA_ERROR_UNKNOWN; } - 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; + struct pollfd checkFds[1] = { { mPort->ufd, POLLIN, 0 } }; + if (poll(checkFds, 1, 0) < 1) { + // Nothing there + return 0; } - if (readCount < 1) { - result = android::NOT_ENOUGH_DATA; - break; + + uint8_t readBuffer[AMIDI_PACKET_SIZE]; + ssize_t readCount = read(mPort->ufd, readBuffer, sizeof(readBuffer)); + if (readCount == EINTR || readCount < 1) { + return AMEDIA_ERROR_UNKNOWN; } - // 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); + // see Packet Format definition at the top of this file. + size_t numMessageBytes = 0; + *opcodePtr = readBuffer[0]; + if (*opcodePtr == AMIDI_OPCODE_DATA && readCount >= AMIDI_PACKET_OVERHEAD) { + numMessageBytes = readCount - AMIDI_PACKET_OVERHEAD; + numMessageBytes = std::min(maxBytes, numMessageBytes); + memcpy(buffer, readBuffer + 1, numMessageBytes); + if (timestampPtr != nullptr) { + *timestampPtr = *(uint64_t*)(readBuffer + readCount - sizeof(uint64_t)); } - message->timestamp = *(uint64_t*)(readBuffer + readCount - sizeof(uint64_t)); } - message->len = dataSize; - ++messagesRead; + *numBytesReceivedPtr = numMessageBytes; + return 1; } - port->state.store(MIDI_PORT_STATE_OPEN_IDLE); +private: + AMIDI_Port *mPort; +}; + +ssize_t AMIDI_API AMidiOutputPort_receive(const AMidiOutputPort *outputPort, int32_t *opcodePtr, + uint8_t *buffer, size_t maxBytes, size_t* numBytesReceivedPtr, int64_t *timestampPtr) { + + if (outputPort == nullptr || buffer == nullptr) { + return -EINVAL; + } - return result == OK ? messagesRead : result; + return MidiReceiver((AMIDI_Port*)outputPort).receive(opcodePtr, buffer, maxBytes, + numBytesReceivedPtr, timestampPtr); } -status_t AMIDI_closeOutputPort(AMIDI_OutputPort *outputPort) { - return AMIDI_closePort((AMIDI_Port*)outputPort); +void AMIDI_API AMidiOutputPort_close(const AMidiOutputPort *outputPort) { + 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); +media_status_t AMIDI_API AMidiInputPort_open(const AMidiDevice *device, int32_t portNumber, + AMidiInputPort **outInputPortPtr) { + return AMIDI_openPort(device, portNumber, PORTTYPE_INPUT, (AMIDI_Port**)outInputPortPtr); } -ssize_t AMIDI_getMaxMessageSizeInBytes(AMIDI_InputPort */*inputPort*/) { - return SIZE_MIDIRECEIVEBUFFER; +void AMIDI_API AMidiInputPort_close(const AMidiInputPort *inputPort) { + AMIDI_closePort((AMIDI_Port*)inputPort); } static ssize_t AMIDI_makeSendBuffer( - uint8_t *buffer, uint8_t *data, ssize_t numBytes,uint64_t timestamp) { + uint8_t *buffer, const uint8_t *data, size_t numBytes, uint64_t timestamp) { + // Error checking will happen in the caller since this isn't an API function. 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_API AMidiInputPort_send(const AMidiInputPort *inputPort, const uint8_t *buffer, + size_t numBytes) { + return AMidiInputPort_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; +ssize_t AMIDI_API AMidiInputPort_sendWithTimestamp(const AMidiInputPort *inputPort, + const uint8_t *data, size_t numBytes, int64_t timestamp) { + if (inputPort == nullptr || data == nullptr) { + return AMEDIA_ERROR_INVALID_PARAMETER; } // 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); + uint8_t writeBuffer[AMIDI_BUFFER_SIZE + AMIDI_PACKET_OVERHEAD]; + size_t numSent = 0; + while (numSent < numBytes) { + size_t blockSize = AMIDI_BUFFER_SIZE; + blockSize = std::min(blockSize, numBytes - numSent); + + ssize_t numTransferBytes = + AMIDI_makeSendBuffer(writeBuffer, data + numSent, blockSize, timestamp); + ssize_t numWritten = write(((AMIDI_Port*)inputPort)->ufd, writeBuffer, numTransferBytes); + if (numWritten < 0) { + break; // error so bail out. + } + if (numWritten < numTransferBytes) { + ALOGE("AMidiInputPort_sendWithTimestamp Couldn't write MIDI data buffer." + " requested:%zu, written%zu",numTransferBytes, numWritten); + break; // bail + } - if (numWritten < numTransferBytes) { - ALOGE("AMIDI_sendWithTimestamp Couldn't write MIDI data buffer. requested:%zu, written%zu", - numTransferBytes, numWritten); + numSent += numWritten - AMIDI_PACKET_OVERHEAD; } - return numWritten - AMIDI_PACKET_OVERHEAD; + return numSent; } -status_t AMIDI_flush(AMIDI_InputPort *inputPort) { +media_status_t AMIDI_API AMidiInputPort_sendFlush(const AMidiInputPort *inputPort) { + if (inputPort == nullptr) { + return AMEDIA_ERROR_INVALID_PARAMETER; + } + 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", + ALOGE("AMidiInputPort_flush Couldn't write MIDI flush. requested:%zd, written:%zd", numTransferBytes, numWritten); - return android::INVALID_OPERATION; + return AMEDIA_ERROR_UNSUPPORTED; } - return OK; + return AMEDIA_OK; } diff --git a/media/native/midi/midi.h b/media/native/midi/midi.h deleted file mode 100644 index 9332b44dddd2..000000000000 --- a/media/native/midi/midi.h +++ /dev/null @@ -1,182 +0,0 @@ -/* - * 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. - */ - -#ifndef ANDROID_MEDIA_MIDI_H_ -#define ANDROID_MEDIA_MIDI_H_ - -#include <stdarg.h> -#include <stdint.h> -#include <sys/types.h> - -#include <utils/Errors.h> - -using android::status_t; - -#ifdef __cplusplus -extern "C" { -#endif - -struct AMIDI_Device; -struct AMIDI_InputPort; -struct AMIDI_OutputPort; - -#define AMIDI_INVALID_HANDLE NULL - -enum { - AMIDI_OPCODE_DATA = 1, - AMIDI_OPCODE_FLUSH = 2, - AMIDI_PACKET_SIZE = 1024, /* !!! Currently MidiPortImpl.MAX_PACKET_SIZE !!! */ - AMIDI_PACKET_OVERHEAD = 9, - AMIDI_BUFFER_SIZE = AMIDI_PACKET_SIZE - AMIDI_PACKET_OVERHEAD - /* !!! TBD, currently MidiPortImpl.MAX_PACKET_DATA_SIZE !!! */ -}; - -typedef struct { - uint32_t opcode; - uint8_t buffer[AMIDI_BUFFER_SIZE]; - size_t len; - int64_t timestamp; -} AMIDI_Message; - -enum { - AMIDI_DEVICE_TYPE_USB = 1, - AMIDI_DEVICE_TYPE_VIRTUAL = 2, - AMIDI_DEVICE_TYPE_BLUETOOTH = 3 -}; - -typedef struct { - int32_t type; - int32_t uid; - int32_t isPrivate; - int32_t inputPortCount; - int32_t outputPortCount; -} AMIDI_DeviceInfo; - -/* - * Device API - */ -/* - * Retrieves information for the native MIDI device. - * - * device The Native API token for the device. - * deviceInfoPtr Receives the associated device info. - * - * Returns OK or a (negative) error code. - */ -status_t AMIDI_getDeviceInfo(AMIDI_Device *device, AMIDI_DeviceInfo *deviceInfoPtr); - -/* - * API for receiving data from the Output port of a device. - */ -/* - * Opens the output port. - * - * device Identifies the device. - * portNumber Specifies the zero-based port index on the device to open. - * outputPortPtr Receives the native API port identifier of the opened port. - * - * Returns OK, or a (negative) error code. - */ -status_t AMIDI_openOutputPort(AMIDI_Device *device, int portNumber, - AMIDI_OutputPort **outputPortPtr); - -/* - * Receives any pending MIDI messages (up to the specified maximum number of messages). - * - * outputPort Identifies the port to receive messages from. - * messages Points to an array (size maxMessages) to receive the MIDI messages. - * maxMessages The number of messages allocated in the messages array. - * - * Returns the number of messages received, or a (negative) error code. - */ -ssize_t AMIDI_receive(AMIDI_OutputPort *outputPort, AMIDI_Message *messages, ssize_t maxMessages); - -/* - * Closes the output port. - * - * outputPort The native API port identifier of the port. - * - * Returns OK, or a (negative) error code. - */ -status_t AMIDI_closeOutputPort(AMIDI_OutputPort *outputPort); - -/* - * API for sending data to the Input port of a device. - */ -/* - * Opens the input port. - * - * device Identifies the device. - * portNumber Specifies the zero-based port index on the device to open. - * inputPortPtr Receives the native API port identifier of the opened port. - * - * Returns OK, or a (negative) error code. - */ -status_t AMIDI_openInputPort(AMIDI_Device *device, int portNumber, AMIDI_InputPort **inputPortPtr); - -/* - * Returns the maximum number of bytes that can be received in a single MIDI message. - */ -ssize_t AMIDI_getMaxMessageSizeInBytes(AMIDI_InputPort *inputPort); - -/* - * Sends data to the specified input port. - * - * inputPort The native API identifier of the port to send data to. - * buffer Points to the array of bytes containing the data to send. - * numBytes Specifies the number of bytes to write. - * - * Returns The number of bytes sent or a (negative) error code. - */ -ssize_t AMIDI_send(AMIDI_InputPort *inputPort, uint8_t *buffer, ssize_t numBytes); - -/* - * Sends data to the specified input port with a timestamp. - * - * inputPort The native API identifier of the port to send data to. - * buffer Points to the array of bytes containing the data to send. - * numBytes Specifies the number of bytes to write. - * timestamp The time stamp to associate with the sent data. - * - * Returns The number of bytes sent or a (negative) error code. - */ -ssize_t AMIDI_sendWithTimestamp(AMIDI_InputPort *inputPort, uint8_t *buffer, - ssize_t numBytes, int64_t timestamp); - -/* - * Sends a message with a 'MIDI flush command code' to the specified port. - * - * inputPort The native API identifier of the port to send the flush message to. - * - * Returns OK, or a (negative) error code. - */ -status_t AMIDI_flush(AMIDI_InputPort *inputPort); - -/* - * Closes the input port. - * - * inputPort The native API port identifier of the port. - * - * - * Returns OK, or a (negative) error code. - */ -status_t AMIDI_closeInputPort(AMIDI_InputPort *inputPort); - -#ifdef __cplusplus -} -#endif - -#endif /* ANDROID_MEDIA_MIDI_H_ */ diff --git a/media/native/midi/midi_internal.h b/media/native/midi/midi_internal.h index fd4770e05d6c..cb3ecce13533 100644 --- a/media/native/midi/midi_internal.h +++ b/media/native/midi/midi_internal.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2018 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. @@ -17,11 +17,25 @@ #ifndef ANDROID_MEDIA_MIDI_INTERNAL_H_ #define ANDROID_MEDIA_MIDI_INTERNAL_H_ +#include <jni.h> + #include "android/media/midi/BpMidiDeviceServer.h" -struct AMIDI_Device { - android::sp<android::media::midi::BpMidiDeviceServer> server; - int32_t deviceId; +typedef struct { + int32_t type; /* one of AMIDI_DEVICE_TYPE_* constants */ + int32_t inputPortCount; /* number of input (send) ports associated with the device */ + int32_t outputPortCount; /* number of output (received) ports associated with the device */ +} AMidiDeviceInfo; + +struct AMidiDevice { + android::sp<android::media::midi::BpMidiDeviceServer> + server; /* The Binder interface to the MIDI server (from the Java MidiDevice) */ + int32_t deviceId; /* The integer id of the device assigned in the Java API */ + JavaVM* javaVM; /* The Java VM (so we can obtain the JNIEnv in the + AMidiDevice_close function) */ + jobject midiDeviceObj; /* NewGlobalRef() reference to the Java MidiDevice associated with + this native AMidiDevice. */ + AMidiDeviceInfo deviceInfo; /* Attributes of the device. */ }; #endif // ANDROID_MEDIA_MIDI_INTERNAL_H_ |