diff options
author | Mikhail Naganov <mnaganov@google.com> | 2016-08-31 18:08:10 -0700 |
---|---|---|
committer | Paul McLean <pmclean@google.com> | 2017-03-03 12:00:33 -0700 |
commit | c276c59eb5d0f36a1133ad718e5e1244390f4688 (patch) | |
tree | fe7c51b880eefcdc087b4e701b14e8978f060843 /media/native | |
parent | 56fee637e90d9fb02196681cf62ca3bc54f39b9a (diff) |
nativemidi: Prototype demonstrating native access to IMidiDeviceServer
Framework changes and a demo app
Comment and finalized Native MIDI API
Replaced fixed PortRegistry tables with std::map.
more error handling.
Removed not-very-useful MidiDeviceManager class.
Made Java API functions @hide.
Bug: 30252756
Test: Manual
Change-Id: Iae98e589f38ef6d625ff0842401193fe98c5d881
Diffstat (limited to 'media/native')
-rw-r--r-- | media/native/midi/Android.bp | 21 | ||||
-rw-r--r-- | media/native/midi/Android.mk | 22 | ||||
-rw-r--r-- | media/native/midi/MidiDeviceRegistry.cpp | 106 | ||||
-rw-r--r-- | media/native/midi/MidiDeviceRegistry.h | 104 | ||||
-rw-r--r-- | media/native/midi/MidiPortRegistry.cpp | 200 | ||||
-rw-r--r-- | media/native/midi/MidiPortRegistry.h | 192 | ||||
-rw-r--r-- | media/native/midi/midi.cpp | 317 | ||||
-rw-r--r-- | media/native/midi/midi.h | 202 |
8 files changed, 1164 insertions, 0 deletions
diff --git a/media/native/midi/Android.bp b/media/native/midi/Android.bp new file mode 100644 index 000000000000..3500805d2794 --- /dev/null +++ b/media/native/midi/Android.bp @@ -0,0 +1,21 @@ +// 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. + +// The headers module is in frameworks/media/native/midi/Android.bp. +ndk_library { + name: "libmidi.ndk", + symbol_file: "libmidi.map.txt", + first_version: "26", +// unversioned_until: "current", +} diff --git a/media/native/midi/Android.mk b/media/native/midi/Android.mk new file mode 100644 index 000000000000..b91c430e2bca --- /dev/null +++ b/media/native/midi/Android.mk @@ -0,0 +1,22 @@ +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + ../../java/android/media/midi/IMidiDeviceServer.aidl \ + midi.cpp \ + MidiDeviceRegistry.cpp \ + MidiPortRegistry.cpp + +LOCAL_AIDL_INCLUDES := \ + $(FRAMEWORKS_BASE_JAVA_SRC_DIRS) \ + frameworks/native/aidl/binder + +LOCAL_CFLAGS += -Wall -Werror -O0 + +LOCAL_MODULE := libmidi +LOCAL_MODULE_TAGS := optional + +LOCAL_SHARED_LIBRARIES := liblog libbinder libutils libmedia + +include $(BUILD_SHARED_LIBRARY) diff --git a/media/native/midi/MidiDeviceRegistry.cpp b/media/native/midi/MidiDeviceRegistry.cpp new file mode 100644 index 000000000000..8854a08a0a45 --- /dev/null +++ b/media/native/midi/MidiDeviceRegistry.cpp @@ -0,0 +1,106 @@ +/* + * 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. + */ + +#include "MidiDeviceRegistry.h" + +namespace android { + +ANDROID_SINGLETON_STATIC_INSTANCE(media::midi::MidiDeviceRegistry); + +namespace media { +namespace midi { + +MidiDeviceRegistry::MidiDeviceRegistry() : mNextDeviceToken(1) { +} + +status_t MidiDeviceRegistry::addDevice(sp<BpMidiDeviceServer> server, int32_t deviceId) { + if (server.get() == nullptr) { + return -EINVAL; + } + + std::lock_guard<std::mutex> guard(mMapsLock); + mServers[deviceId] = server; + return OK; +} + +status_t MidiDeviceRegistry::removeDevice(int32_t deviceId) { + std::lock_guard<std::mutex> guard(mMapsLock); + mServers.erase(deviceId); + const auto& iter = mUidToToken.find(deviceId); + if (iter != mUidToToken.end()) { + mTokenToUid.erase(iter->second); + mUidToToken.erase(iter); + } + return OK; +} + +//NOTE: This creates an entry if not found, or returns an existing one. +status_t MidiDeviceRegistry::obtainDeviceToken(int32_t deviceId, AMIDI_Device *deviceTokenPtr) { + std::lock_guard<std::mutex> guard(mMapsLock); + const auto& serversIter = mServers.find(deviceId); + if (serversIter == mServers.end()) { + // Not found. + return -EINVAL; + } + + const auto& iter = mUidToToken.find(deviceId); + if (iter != mUidToToken.end()) { + *deviceTokenPtr = iter->second; + } else { + *deviceTokenPtr = mNextDeviceToken++; + mTokenToUid[*deviceTokenPtr] = deviceId; + mUidToToken[deviceId] = *deviceTokenPtr; + } + return OK; +} + +status_t MidiDeviceRegistry::releaseDevice(AMIDI_Device deviceToken) { + std::lock_guard<std::mutex> guard(mMapsLock); + const auto& iter = mTokenToUid.find(deviceToken); + if (iter == mTokenToUid.end()) { + // Not found + return -EINVAL; + } + + mServers.erase(iter->second); + mUidToToken.erase(iter->second); + mTokenToUid.erase(iter); + return OK; +} + +status_t MidiDeviceRegistry::getDeviceByToken( + AMIDI_Device deviceToken, sp<BpMidiDeviceServer> *devicePtr) { + std::lock_guard<std::mutex> guard(mMapsLock); + int32_t id = -1; + { + const auto& iter = mTokenToUid.find(deviceToken); + if (iter == mTokenToUid.end()) { + return -EINVAL; + } + id = iter->second; + } + const auto& iter = mServers.find(id); + if (iter == mServers.end()) { + return -EINVAL; + } + + *devicePtr = iter->second; + return OK; +} + +} // namespace midi +} // namespace media +} // namespace android diff --git a/media/native/midi/MidiDeviceRegistry.h b/media/native/midi/MidiDeviceRegistry.h new file mode 100644 index 000000000000..93be7338385b --- /dev/null +++ b/media/native/midi/MidiDeviceRegistry.h @@ -0,0 +1,104 @@ +/* + * 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_DEVICE_REGISTRY_H_ +#define ANDROID_MEDIA_MIDI_DEVICE_REGISTRY_H_ + +#include <map> +#include <mutex> + +#include <binder/IBinder.h> +#include <utils/Errors.h> +#include <utils/Singleton.h> + +#include "android/media/midi/BpMidiDeviceServer.h" +#include "midi.h" + +namespace android { +namespace media { +namespace midi { + +/* + * Maintains a thread-safe, (singleton) list of MIDI devices with associated Binder interfaces, + * which are exposed to the Native API via (Java) MidiDevice.mirrorToNative() & + * MidiDevice.removeFromNative(). + * (Called via MidiDeviceManager::addDevice() MidiManager::removeDevice()). + */ +class MidiDeviceRegistry : public Singleton<MidiDeviceRegistry> { + public: + /* Add a MIDI Device to the registry. + * + * server The Binder interface to the MIDI device server. + * deviceUId The unique ID of the device obtained from + * the Java API via MidiDeviceInfo.getId(). + */ + status_t addDevice(sp<BpMidiDeviceServer> server, int32_t deviceId); + + /* Remove the device (and associated server) from the Device registry. + * + * deviceUid The ID of the device which was used in the call to addDevice(). + */ + status_t removeDevice(int32_t deviceId); + + /* Gets a device token associated with the device ID. This is used by the + * native API to identify/access the device. + * Multiple calls without releasing the token will return the same value. + * + * deviceUid The ID of the device. + * deviceTokenPtr Receives the device (native) token associated with the device ID. + * returns: OK on success, error code otherwise. + */ + status_t obtainDeviceToken(int32_t deviceId, AMIDI_Device *deviceTokenPtr); + + /* + * Releases the native API device token associated with a MIDI device. + * + * deviceToken The device (native) token associated with the device ID. + */ + status_t releaseDevice(AMIDI_Device deviceToken); + + /* + * Gets the Device server binder interface associated with the device token. + * + * deviceToken The device (native) token associated with the device ID. + */ + status_t getDeviceByToken(AMIDI_Device deviceToken, sp<BpMidiDeviceServer> *devicePtr); + + private: + friend class Singleton<MidiDeviceRegistry>; + MidiDeviceRegistry(); + + // Access Mutex + std::mutex mMapsLock; + + // maps device IDs to servers + std::map<int32_t, sp<BpMidiDeviceServer>> mServers; + + // maps device tokens to device ID + std::map<AMIDI_Device, int32_t> mTokenToUid; + + // maps device IDs to device tokens + std::map<int32_t, AMIDI_Device> mUidToToken; + + // Value of next device token to dole out. + AMIDI_Device mNextDeviceToken; +}; + +} // namespace midi +} // namespace media +} // namespace android + +#endif // ANDROID_MEDIA_MIDI_DEVICE_REGISTRY_H_ diff --git a/media/native/midi/MidiPortRegistry.cpp b/media/native/midi/MidiPortRegistry.cpp new file mode 100644 index 000000000000..fa70af8d0947 --- /dev/null +++ b/media/native/midi/MidiPortRegistry.cpp @@ -0,0 +1,200 @@ +/* + * 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. + */ + +#include "MidiPortRegistry.h" + +namespace android { + +ANDROID_SINGLETON_STATIC_INSTANCE(media::midi::MidiPortRegistry); + +namespace media { +namespace midi { + +//TODO Note that these 2 are identical +struct MidiPortRegistry::OutputPort { + AMIDI_Device device; + sp<IBinder> binderToken; + base::unique_fd ufd; +}; + +struct MidiPortRegistry::InputPort { + AMIDI_Device device; + sp<IBinder> binderToken; + base::unique_fd ufd; +}; + +MidiPortRegistry::MidiPortRegistry() : mNextOutputPortToken(0), mNextInputPortToken(0) { +} + +status_t MidiPortRegistry::addOutputPort( + AMIDI_Device device, + sp<IBinder> portToken, + base::unique_fd &&ufd, + AMIDI_OutputPort *portPtr) { + *portPtr = mNextOutputPortToken++; + + OutputPortEntry* portEntry = new OutputPortEntry; + portEntry->port = new OutputPort; + portEntry->state = MIDI_OUTPUT_PORT_STATE_OPEN_IDLE; + portEntry->port = new OutputPort; + portEntry->port->device = device; + portEntry->port->binderToken = portToken; + portEntry->port->ufd = std::move(ufd); + + mOutputPortMap[*portPtr] = portEntry; + + return OK; +} + +status_t MidiPortRegistry::removeOutputPort( + AMIDI_OutputPort port, + AMIDI_Device *devicePtr, + sp<IBinder> *portTokenPtr) { + OutputPortMap::iterator itr = mOutputPortMap.find(port); + if (itr == mOutputPortMap.end()) { + return -EINVAL; + } + + OutputPortEntry *entry = mOutputPortMap[port]; + int portState = MIDI_OUTPUT_PORT_STATE_OPEN_IDLE; + while (!entry->state.compare_exchange_weak(portState, MIDI_OUTPUT_PORT_STATE_CLOSED)) { + if (portState == MIDI_OUTPUT_PORT_STATE_CLOSED) { + return -EINVAL; // Already closed + } + } + *devicePtr = entry->port->device; + *portTokenPtr = entry->port->binderToken; + delete entry->port; + entry->port = nullptr; + + mOutputPortMap.erase(itr); + + return OK; +} + +status_t MidiPortRegistry::getOutputPortFdAndLock( + AMIDI_OutputPort port, base::unique_fd **ufdPtr) { + if (mOutputPortMap.find(port) == mOutputPortMap.end()) { + return -EINVAL; + } + + OutputPortEntry *entry = mOutputPortMap[port]; + int portState = MIDI_OUTPUT_PORT_STATE_OPEN_IDLE; + if (!entry->state.compare_exchange_strong(portState, MIDI_OUTPUT_PORT_STATE_OPEN_ACTIVE)) { + // The port has been closed. + return -EPIPE; + } + *ufdPtr = &entry->port->ufd; + + return OK; +} + +status_t MidiPortRegistry::unlockOutputPort(AMIDI_OutputPort port) { + if (mOutputPortMap.find(port) == mOutputPortMap.end()) { + return -EINVAL; + } + + OutputPortEntry *entry = mOutputPortMap[port]; + entry->state.store(MIDI_OUTPUT_PORT_STATE_OPEN_IDLE); + return OK; +} + +status_t MidiPortRegistry::addInputPort( + AMIDI_Device device, + sp<IBinder> portToken, + base::unique_fd &&ufd, + AMIDI_InputPort *portPtr) { + *portPtr = mNextInputPortToken++; + + InputPortEntry *entry = new InputPortEntry; + + entry->state = MIDI_INPUT_PORT_STATE_OPEN_IDLE; + entry->port = new InputPort; + entry->port->device = device; + entry->port->binderToken = portToken; + entry->port->ufd = std::move(ufd); + + mInputPortMap[*portPtr] = entry; + + return OK; +} + +status_t MidiPortRegistry::removeInputPort( + AMIDI_InputPort port, + AMIDI_Device *devicePtr, + sp<IBinder> *portTokenPtr) { + InputPortMap::iterator itr = mInputPortMap.find(port); + if (itr == mInputPortMap.end()) { + return -EINVAL; + } + + InputPortEntry *entry = mInputPortMap[port]; + int portState = MIDI_INPUT_PORT_STATE_OPEN_IDLE; + while (!entry->state.compare_exchange_weak(portState, MIDI_INPUT_PORT_STATE_CLOSED)) { + if (portState == MIDI_INPUT_PORT_STATE_CLOSED) return -EINVAL; // Already closed + } + + *devicePtr = entry->port->device; + *portTokenPtr = entry->port->binderToken; + delete entry->port; + entry->port = nullptr; + + mInputPortMap.erase(itr); + + return OK; +} + +status_t MidiPortRegistry::getInputPortFd(AMIDI_InputPort port, base::unique_fd **ufdPtr) { + if (mInputPortMap.find(port) == mInputPortMap.end()) { + return -EINVAL; + } + + InputPortEntry *entry = mInputPortMap[port]; + + *ufdPtr = &entry->port->ufd; + + return OK; +} + +status_t MidiPortRegistry::getInputPortFdAndLock(AMIDI_InputPort port, base::unique_fd **ufdPtr) { + if (mInputPortMap.find(port) == mInputPortMap.end()) { + return -EINVAL; + } + + InputPortEntry *entry = mInputPortMap[port]; + + int portState = MIDI_INPUT_PORT_STATE_OPEN_IDLE; + if (!entry->state.compare_exchange_strong(portState, MIDI_INPUT_PORT_STATE_OPEN_ACTIVE)) { + // The port has been closed. + return -EPIPE; + } + *ufdPtr = &entry->port->ufd; + return OK; +} + +status_t MidiPortRegistry::MidiPortRegistry::unlockInputPort(AMIDI_InputPort port) { + if (mInputPortMap.find(port) == mInputPortMap.end()) { + return -EINVAL; + } + + InputPortEntry *entry = mInputPortMap[port]; + entry->state.store(MIDI_INPUT_PORT_STATE_OPEN_IDLE); + return OK; +} + +} // namespace midi +} // namespace media +} // namespace android diff --git a/media/native/midi/MidiPortRegistry.h b/media/native/midi/MidiPortRegistry.h new file mode 100644 index 000000000000..f1ffb78e98ab --- /dev/null +++ b/media/native/midi/MidiPortRegistry.h @@ -0,0 +1,192 @@ +/* + * 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_PORT_REGISTRY_H_ +#define ANDROID_MEDIA_MIDI_PORT_REGISTRY_H_ + +#include <atomic> +#include <map> + +#include <android-base/unique_fd.h> +#include <binder/IBinder.h> +#include <utils/Errors.h> +#include <utils/Singleton.h> + +#include "midi.h" + +namespace android { +namespace media { +namespace midi { + +/* + * Maintains lists of all active input and output MIDI ports and controls access to them. Provides + * exclusive access to specific MIDI ports. + */ +class MidiPortRegistry : public Singleton<MidiPortRegistry> { + public: + /* + * Creates an output port entry and associates it with the specified MIDI device. + * Called by AMIDI_openOutputPort(); + * + * device The native API device ID. + * portToken The port token (returned from the device server). + * udf File descriptor for the data port associated with the MIDI output port. + * portPtr Receives the native API port ID of the port being opened. + */ + status_t addOutputPort( + AMIDI_Device device, + sp<IBinder> portToken, + base::unique_fd &&ufd, + AMIDI_OutputPort *portPtr); + + /* + * Removes for the output port list a previously added output port. + * Called by AMIDI_closeOutputPort(); + * + * port The native API port ID of the port being closed. + * devicePtr Receives the native API device ID associated with the port. + * portTokenPtr Receives the binder token associated with the port. + */ + status_t removeOutputPort( + AMIDI_OutputPort port, + AMIDI_Device *devicePtr, + sp<IBinder> *portTokenPtr); + + /* + * Creates an input port entry and associates it with the specified MIDI device. + * Called by AMIDI_openInputPort(); + * + * device The native API device ID. + * portToken The port token (returned from the device server). + * udf File descriptor for the data port associated with the MIDI input port. + * portPtr Receives the native API port ID of the port being opened. + */ + status_t addInputPort( + AMIDI_Device device, + sp<IBinder> portToken, + base::unique_fd &&ufd, + AMIDI_InputPort *portPtr); + + /* + * Removes for the input port list a previously added input port. + * Called by AMIDI_closeINputPort(); + * + * port The native API port ID of the port being closed. + * devicePtr Receives the native API device ID associated with the port. + * portTokenPtr Receives the binder token associated with the port. + */ + status_t removeInputPort( + AMIDI_InputPort port, + AMIDI_Device *devicePtr, + sp<IBinder> *portTokenPtr); + + /* + * Retrieves an exclusive-access file descriptor for an output port. + * Called from AMIDI_receive(). + * + * port The native API id of the output port. + * ufdPtr Receives the exclusive-access file descriptor for the output port. + */ + status_t getOutputPortFdAndLock(AMIDI_OutputPort port, base::unique_fd **ufdPtr); + + /* + * Releases exclusive-access to the port and invalidates the previously received file + * descriptor. + * Called from AMIDI_receive(). + * + * port The native API id of the output port. + */ + status_t unlockOutputPort(AMIDI_OutputPort port); + + /* + * Retrieves an exclusive-access file descriptor for an input port. + * (Not being used as (perhaps) AMIDI_sendWithTimestamp() doesn't need exclusive access + * to the port). + * + * port The native API id of the input port. + * ufdPtr Receives the exclusive-access file descriptor for the input port. + */ + status_t getInputPortFdAndLock(AMIDI_InputPort port, base::unique_fd **ufdPtr); + + /* + * Releases exclusive-access to the port and invalidates the previously received file + * descriptor. + * (Not used. See above). + * + * port The native API id of the input port. + */ + status_t unlockInputPort(AMIDI_InputPort port); + + /* + * Retrieves an unlocked (multi-access) file descriptor for an input port. + * Used by AMIDI_sendWith(), AMIDI_sendWithTimestamp & AMIDI_flush. + * + * port The native API id of the input port. + * ufdPtr Receives the multi-access file descriptor for the input port. + */ + status_t getInputPortFd(AMIDI_InputPort port, base::unique_fd **ufdPtr); + + private: + friend class Singleton<MidiPortRegistry>; + MidiPortRegistry(); + + /* + * Output (data receiving) ports. + */ + struct OutputPort; + enum { + MIDI_OUTPUT_PORT_STATE_CLOSED = 0, + MIDI_OUTPUT_PORT_STATE_OPEN_IDLE, + MIDI_OUTPUT_PORT_STATE_OPEN_ACTIVE + }; + + struct OutputPortEntry { + std::atomic_int state; + OutputPort *port; + }; + + typedef std::map<AMIDI_OutputPort, OutputPortEntry*> OutputPortMap; + // Access is synchronized per record via 'state' field. + std::atomic<AMIDI_OutputPort> mNextOutputPortToken; + OutputPortMap mOutputPortMap; + + /* + * Input (data sending) ports. + */ + struct InputPort; + enum { + MIDI_INPUT_PORT_STATE_CLOSED = 0, + MIDI_INPUT_PORT_STATE_OPEN_IDLE, + MIDI_INPUT_PORT_STATE_OPEN_ACTIVE + }; + + struct InputPortEntry { + std::atomic_int state; + InputPort *port; + }; + + typedef std::map<AMIDI_OutputPort, InputPortEntry*> InputPortMap; + // Access is synchronized per record via 'state' field. + std::atomic<AMIDI_InputPort> mNextInputPortToken; + InputPortMap mInputPortMap; + +}; + +} // namespace midi +} // namespace media +} // namespace android + +#endif // ANDROID_MEDIA_MIDI_PORT_REGISTRY_H_ diff --git a/media/native/midi/midi.cpp b/media/native/midi/midi.cpp new file mode 100644 index 000000000000..1bf0bd080e94 --- /dev/null +++ b/media/native/midi/midi.cpp @@ -0,0 +1,317 @@ +/* + * 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 "MidiDeviceRegistry.h" +#include "MidiPortRegistry.h" + +#include "midi.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::BpMidiDeviceServer; +using android::media::midi::MidiDeviceInfo; +using android::media::midi::MidiDeviceRegistry; +using android::media::midi::MidiPortRegistry; + +#define SIZE_MIDIRECEIVEBUFFER AMIDI_BUFFER_SIZE + +/* 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. + */ + +status_t AMIDI_getDeviceById(int32_t id, AMIDI_Device *devicePtr) { + return MidiDeviceRegistry::getInstance().obtainDeviceToken(id, devicePtr); +} + +status_t AMIDI_getDeviceInfo(AMIDI_Device device, AMIDI_DeviceInfo *deviceInfoPtr) { + sp<BpMidiDeviceServer> deviceServer; + status_t result = MidiDeviceRegistry::getInstance().getDeviceByToken(device, &deviceServer); + if (result != OK) { + ALOGE("AMIDI_getDeviceInfo bad device token %d: %d", device, result); + return result; + } + + MidiDeviceInfo deviceInfo; + Status txResult = deviceServer->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; +} + +/* + * Output (receiving) API + */ +status_t AMIDI_openOutputPort(AMIDI_Device device, int portNumber, AMIDI_OutputPort *outputPortPtr) { + sp<BpMidiDeviceServer> deviceServer; + status_t result = MidiDeviceRegistry::getInstance().getDeviceByToken(device, &deviceServer); + if (result != OK) { + ALOGE("AMIDI_openOutputPort bad device token %d: %d", device, result); + return result; + } + + sp<BBinder> portToken(new BBinder()); + unique_fd ufd; + Status txResult = deviceServer->openOutputPort(portToken, portNumber, &ufd); + if (!txResult.isOk()) { + ALOGE("AMIDI_openOutputPort transaction error: %d", txResult.transactionError()); + return txResult.transactionError(); + } + + result = MidiPortRegistry::getInstance().addOutputPort( + device, portToken, std::move(ufd), outputPortPtr); + if (result != OK) { + ALOGE("AMIDI_openOutputPort port registration error: %d", result); + // Close port + return result; + } + return OK; +} + +ssize_t AMIDI_receive(AMIDI_OutputPort outputPort, AMIDI_Message *messages, ssize_t maxMessages) { + unique_fd *ufd; + // TODO: May return a nicer self-unlocking object + status_t result = MidiPortRegistry::getInstance().getOutputPortFdAndLock(outputPort, &ufd); + if (result != OK) { + return result; + } + + ssize_t messagesRead = 0; + while (messagesRead < maxMessages) { + struct pollfd checkFds[1] = { { *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(*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; + } + + MidiPortRegistry::getInstance().unlockOutputPort(outputPort); + return result == OK ? messagesRead : result; +} + +status_t AMIDI_closeOutputPort(AMIDI_OutputPort outputPort) { + AMIDI_Device device; + sp<IBinder> portToken; + status_t result = + MidiPortRegistry::getInstance().removeOutputPort(outputPort, &device, &portToken); + if (result != OK) { + return result; + } + + sp<BpMidiDeviceServer> deviceServer; + result = MidiDeviceRegistry::getInstance().getDeviceByToken(device, &deviceServer); + if (result != OK) { + return result; + } + + Status txResult = deviceServer->closePort(portToken); + if (!txResult.isOk()) { + return txResult.transactionError(); + } + return OK; +} + +/* + * Input (sending) API + */ +status_t AMIDI_openInputPort(AMIDI_Device device, int portNumber, AMIDI_InputPort *inputPortPtr) { + sp<BpMidiDeviceServer> deviceServer; + status_t result = MidiDeviceRegistry::getInstance().getDeviceByToken(device, &deviceServer); + if (result != OK) { + ALOGE("AMIDI_openInputPort bad device token %d: %d", device, result); + return result; + } + + sp<BBinder> portToken(new BBinder()); + unique_fd ufd; // this is the file descriptor of the "receive" port s + Status txResult = deviceServer->openInputPort(portToken, portNumber, &ufd); + if (!txResult.isOk()) { + ALOGE("AMIDI_openInputPort transaction error: %d", txResult.transactionError()); + return txResult.transactionError(); + } + + result = MidiPortRegistry::getInstance().addInputPort( + device, portToken, std::move(ufd), inputPortPtr); + if (result != OK) { + ALOGE("AMIDI_openInputPort port registration error: %d", result); + // Close port + return result; + } + + return OK; +} + +status_t AMIDI_closeInputPort(AMIDI_InputPort inputPort) { + AMIDI_Device device; + sp<IBinder> portToken; + status_t result = MidiPortRegistry::getInstance().removeInputPort( + inputPort, &device, &portToken); + if (result != OK) { + ALOGE("AMIDI_closeInputPort remove port error: %d", result); + return result; + } + + sp<BpMidiDeviceServer> deviceServer; + result = MidiDeviceRegistry::getInstance().getDeviceByToken(device, &deviceServer); + if (result != OK) { + ALOGE("AMIDI_closeInputPort can't find device error: %d", result); + return result; + } + + Status txResult = deviceServer->closePort(portToken); + if (!txResult.isOk()) { + result = txResult.transactionError(); + ALOGE("AMIDI_closeInputPort transaction error: %d", result); + return result; + } + + return OK; +} + +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); + + unique_fd *ufd = NULL; + status_t result = MidiPortRegistry::getInstance().getInputPortFd(inputPort, &ufd); + if (result != OK) { + return result; + } + + uint8_t writeBuffer[SIZE_MIDIRECEIVEBUFFER + AMIDI_PACKET_OVERHEAD]; + ssize_t numTransferBytes = AMIDI_makeSendBuffer(writeBuffer, data, numBytes, timestamp); + ssize_t numWritten = write(*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) { + unique_fd *ufd = NULL; + status_t result = MidiPortRegistry::getInstance().getInputPortFd(inputPort, &ufd); + if (result != OK) { + return result; + } + + uint8_t opCode = AMIDI_OPCODE_FLUSH; + ssize_t numTransferBytes = 1; + ssize_t numWritten = write(*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; +} + diff --git a/media/native/midi/midi.h b/media/native/midi/midi.h new file mode 100644 index 000000000000..717bc666253e --- /dev/null +++ b/media/native/midi/midi.h @@ -0,0 +1,202 @@ +/* + * 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 + +//typedef struct _AMIDI_Device; +//typedef struct _AMIDI_InputPort; +//typedef struct _AMIDI_OutputPort; +//typedef _AMIDI_Device* AMIDI_Device; +//typedef _AMIDI_InputPort* AMIDI_InputPort; +//typedef _AMIDI_OutputPort* AMIDI_OutputPort; + +typedef int32_t AMIDI_Device; +typedef int32_t AMIDI_InputPort; +typedef int32_t AMIDI_OutputPort; + +//TODO - Do we want to wrap this stuff in namespace android { namespace media { namespace midi {? + +enum { + AMIDI_INVALID_HANDLE = -1 +}; + +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 the native API device token for the specified Java API device ID. + * + * uid The Java API id of the device. + * devicePtr Receives the associated native API token for the device. + * + * Returns OK or a (negative) error code. + */ +status_t AMIDI_getDeviceById(int32_t id, AMIDI_Device *devicePtr); + +/* + * 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_ */ |