Merge "nativemidi: Prototype demonstrating native access to IMidiDeviceServer"
This commit is contained in:
@@ -35,6 +35,10 @@ import java.io.IOException;
|
||||
* Instances of this class are created by {@link MidiManager#openDevice}.
|
||||
*/
|
||||
public final class MidiDevice implements Closeable {
|
||||
static {
|
||||
System.loadLibrary("media_jni");
|
||||
}
|
||||
|
||||
private static final String TAG = "MidiDevice";
|
||||
|
||||
private final MidiDeviceInfo mDeviceInfo;
|
||||
@@ -43,6 +47,7 @@ public final class MidiDevice implements Closeable {
|
||||
private final IBinder mClientToken;
|
||||
private final IBinder mDeviceToken;
|
||||
private boolean mIsDeviceClosed;
|
||||
private boolean mIsMirroredToNative;
|
||||
|
||||
private final CloseGuard mGuard = CloseGuard.get();
|
||||
|
||||
@@ -209,10 +214,45 @@ public final class MidiDevice implements Closeable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes Midi Device available to the Native API
|
||||
* @hide
|
||||
*/
|
||||
public void mirrorToNative() throws IOException {
|
||||
if (mIsDeviceClosed || mIsMirroredToNative) {
|
||||
return;
|
||||
}
|
||||
|
||||
int result = mirrorToNative(mDeviceServer.asBinder(), mDeviceInfo.getId());
|
||||
if (result != 0) {
|
||||
throw new IOException("Failed mirroring to native: " + result);
|
||||
}
|
||||
|
||||
mIsMirroredToNative = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes Midi Device no longer available to the Native API
|
||||
* @hide
|
||||
*/
|
||||
public void removeFromNative() throws IOException {
|
||||
if (!mIsMirroredToNative) {
|
||||
return;
|
||||
}
|
||||
|
||||
int result = removeFromNative(mDeviceInfo.getId());
|
||||
if (result != 0) {
|
||||
throw new IOException("Failed removing from native: " + result);
|
||||
}
|
||||
|
||||
mIsMirroredToNative = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
synchronized (mGuard) {
|
||||
if (!mIsDeviceClosed) {
|
||||
removeFromNative();
|
||||
mGuard.close();
|
||||
mIsDeviceClosed = true;
|
||||
try {
|
||||
@@ -238,4 +278,7 @@ public final class MidiDevice implements Closeable {
|
||||
public String toString() {
|
||||
return ("MidiDevice: " + mDeviceInfo.toString());
|
||||
}
|
||||
|
||||
private native int mirrorToNative(IBinder deviceServerBinder, int uid);
|
||||
private native int removeFromNative(int uid);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ LOCAL_SRC_FILES:= \
|
||||
android_mtp_MtpDatabase.cpp \
|
||||
android_mtp_MtpDevice.cpp \
|
||||
android_mtp_MtpServer.cpp \
|
||||
midi/android_media_midi_MidiDevice.cpp \
|
||||
|
||||
LOCAL_SHARED_LIBRARIES := \
|
||||
libandroid_runtime \
|
||||
@@ -34,6 +35,7 @@ LOCAL_SHARED_LIBRARIES := \
|
||||
libbinder \
|
||||
libmedia \
|
||||
libmediadrm \
|
||||
libmidi \
|
||||
libskia \
|
||||
libui \
|
||||
liblog \
|
||||
@@ -55,6 +57,7 @@ LOCAL_C_INCLUDES += \
|
||||
external/tremor/Tremor \
|
||||
frameworks/base/core/jni \
|
||||
frameworks/base/libs/hwui \
|
||||
frameworks/base/media/native \
|
||||
frameworks/av/media/libmedia \
|
||||
frameworks/av/media/libstagefright \
|
||||
frameworks/av/media/mtp \
|
||||
|
||||
49
media/jni/midi/android_media_midi_MidiDevice.cpp
Normal file
49
media/jni/midi/android_media_midi_MidiDevice.cpp
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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_NDEBUG 0
|
||||
#define LOG_TAG "Midi-JNI"
|
||||
|
||||
#include <android_util_Binder.h>
|
||||
#include <midi/MidiDeviceRegistry.h>
|
||||
#include <nativehelper/jni.h>
|
||||
#include <utils/Log.h>
|
||||
|
||||
using namespace android;
|
||||
using namespace android::media::midi;
|
||||
|
||||
extern "C" jint Java_android_media_midi_MidiDevice_mirrorToNative(
|
||||
JNIEnv *env, jobject thiz, jobject midiDeviceServer, jint id)
|
||||
{
|
||||
(void)thiz;
|
||||
sp<IBinder> serverBinder = ibinderForJavaObject(env, midiDeviceServer);
|
||||
if (serverBinder.get() == NULL) {
|
||||
ALOGE("Could not obtain IBinder from passed jobject");
|
||||
return -EINVAL;
|
||||
}
|
||||
// return MidiDeviceManager::getInstance().addDevice(serverBinder, uid);
|
||||
return MidiDeviceRegistry::getInstance().addDevice(
|
||||
new BpMidiDeviceServer(serverBinder), id);
|
||||
}
|
||||
|
||||
extern "C" jint Java_android_media_midi_MidiDevice_removeFromNative(
|
||||
JNIEnv *env, jobject thiz, jint uid)
|
||||
{
|
||||
(void)env;
|
||||
(void)thiz;
|
||||
// return MidiDeviceManager::getInstance().removeDevice(uid);
|
||||
return MidiDeviceRegistry::getInstance().removeDevice(uid);
|
||||
}
|
||||
21
media/native/midi/Android.bp
Normal file
21
media/native/midi/Android.bp
Normal file
@@ -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",
|
||||
}
|
||||
22
media/native/midi/Android.mk
Normal file
22
media/native/midi/Android.mk
Normal file
@@ -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)
|
||||
106
media/native/midi/MidiDeviceRegistry.cpp
Normal file
106
media/native/midi/MidiDeviceRegistry.cpp
Normal file
@@ -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
|
||||
104
media/native/midi/MidiDeviceRegistry.h
Normal file
104
media/native/midi/MidiDeviceRegistry.h
Normal file
@@ -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_
|
||||
200
media/native/midi/MidiPortRegistry.cpp
Normal file
200
media/native/midi/MidiPortRegistry.cpp
Normal file
@@ -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
|
||||
192
media/native/midi/MidiPortRegistry.h
Normal file
192
media/native/midi/MidiPortRegistry.h
Normal file
@@ -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_
|
||||
317
media/native/midi/midi.cpp
Normal file
317
media/native/midi/midi.cpp
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
202
media/native/midi/midi.h
Normal file
202
media/native/midi/midi.h
Normal file
@@ -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_ */
|
||||
30
media/tests/NativeMidiDemo/Android.mk
Normal file
30
media/tests/NativeMidiDemo/Android.mk
Normal file
@@ -0,0 +1,30 @@
|
||||
# Copyright (C) 2016 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.
|
||||
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_MODULE_TAGS := tests
|
||||
LOCAL_PACKAGE_NAME := NativeMidiDemo
|
||||
|
||||
#LOCAL_SDK_VERSION := current
|
||||
LOCAL_PROGUARD_ENABLED := disabled
|
||||
LOCAL_SRC_FILES := $(call all-java-files-under, java)
|
||||
|
||||
LOCAL_JNI_SHARED_LIBRARIES := libnativemidi_jni
|
||||
|
||||
include $(BUILD_PACKAGE)
|
||||
|
||||
# Include packages in subdirectories
|
||||
include $(call all-makefiles-under,$(LOCAL_PATH))
|
||||
20
media/tests/NativeMidiDemo/AndroidManifest.xml
Normal file
20
media/tests/NativeMidiDemo/AndroidManifest.xml
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.android.nativemididemo"
|
||||
android:versionCode="1"
|
||||
android:versionName="1.0">
|
||||
<application
|
||||
android:allowBackup="false"
|
||||
android:fullBackupContent="false"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name">
|
||||
<uses-feature android:name="android.software.midi" android:required="true"/>
|
||||
<activity android:name=".NativeMidi"
|
||||
android:label="@string/app_name">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -0,0 +1,354 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.example.android.nativemididemo;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.media.midi.MidiDevice;
|
||||
import android.media.midi.MidiDeviceInfo;
|
||||
import android.media.midi.MidiManager;
|
||||
import android.media.midi.MidiOutputPort;
|
||||
import android.media.midi.MidiReceiver;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.RadioButton;
|
||||
import android.widget.RadioGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class NativeMidi extends Activity
|
||||
{
|
||||
private TextView mCallbackStatusTextView;
|
||||
private TextView mJavaMidiStatusTextView;
|
||||
private TextView mMessagesTextView;
|
||||
private RadioGroup mMidiDevicesRadioGroup;
|
||||
private Handler mTimerHandler = new Handler();
|
||||
private boolean mAudioWorks;
|
||||
private final int mMinFramesPerBuffer = 32; // See {min|max}PlaySamples in nativemidi-jni.cpp
|
||||
private final int mMaxFramesPerBuffer = 1000;
|
||||
private int mFramesPerBuffer;
|
||||
|
||||
private TouchableScrollView mMessagesContainer;
|
||||
private MidiManager mMidiManager;
|
||||
private MidiOutputPortSelector mActivePortSelector;
|
||||
|
||||
private Runnable mTimerRunnable = new Runnable() {
|
||||
private long mLastTime;
|
||||
private long mLastPlaybackCounter;
|
||||
private int mLastCallbackRate;
|
||||
private long mLastUntouchedTime;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
final long checkIntervalMs = 1000;
|
||||
long currentTime = System.currentTimeMillis();
|
||||
long currentPlaybackCounter = getPlaybackCounter();
|
||||
if (currentTime - mLastTime >= checkIntervalMs) {
|
||||
int callbackRate = Math.round(
|
||||
(float)(currentPlaybackCounter - mLastPlaybackCounter) /
|
||||
((float)(currentTime - mLastTime) / (float)1000));
|
||||
if (mLastCallbackRate != callbackRate) {
|
||||
mCallbackStatusTextView.setText(
|
||||
"CB: " + callbackRate + " Hz");
|
||||
mLastCallbackRate = callbackRate;
|
||||
}
|
||||
mLastTime = currentTime;
|
||||
mLastPlaybackCounter = currentPlaybackCounter;
|
||||
}
|
||||
|
||||
String[] newMessages = getRecentMessages();
|
||||
if (newMessages != null) {
|
||||
for (String message : newMessages) {
|
||||
mMessagesTextView.append(message);
|
||||
mMessagesTextView.append("\n");
|
||||
}
|
||||
if (!mMessagesContainer.isTouched) {
|
||||
if (mLastUntouchedTime == 0) mLastUntouchedTime = currentTime;
|
||||
if (currentTime - mLastUntouchedTime > 3000) {
|
||||
mMessagesContainer.fullScroll(View.FOCUS_DOWN);
|
||||
}
|
||||
} else {
|
||||
mLastUntouchedTime = 0;
|
||||
}
|
||||
}
|
||||
|
||||
mTimerHandler.postDelayed(this, checkIntervalMs / 4);
|
||||
}
|
||||
};
|
||||
|
||||
private void addMessage(final String message) {
|
||||
mTimerHandler.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mMessagesTextView.append(message);
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
private class MidiOutputPortSelector implements View.OnClickListener {
|
||||
private final MidiDeviceInfo mDeviceInfo;
|
||||
private final int mPortNumber;
|
||||
private MidiDevice mDevice;
|
||||
private MidiOutputPort mOutputPort;
|
||||
|
||||
MidiOutputPortSelector() {
|
||||
mDeviceInfo = null;
|
||||
mPortNumber = -1;
|
||||
}
|
||||
|
||||
MidiOutputPortSelector(MidiDeviceInfo info, int portNumber) {
|
||||
mDeviceInfo = info;
|
||||
mPortNumber = portNumber;
|
||||
}
|
||||
|
||||
MidiDeviceInfo getDeviceInfo() { return mDeviceInfo; }
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (mActivePortSelector != null) {
|
||||
mActivePortSelector.close();
|
||||
mActivePortSelector = null;
|
||||
}
|
||||
if (mDeviceInfo == null) {
|
||||
mActivePortSelector = this;
|
||||
return;
|
||||
}
|
||||
mMidiManager.openDevice(mDeviceInfo, new MidiManager.OnDeviceOpenedListener() {
|
||||
@Override
|
||||
public void onDeviceOpened(MidiDevice device) {
|
||||
if (device == null) {
|
||||
addMessage("! Failed to open MIDI device !\n");
|
||||
} else {
|
||||
mDevice = device;
|
||||
try {
|
||||
mDevice.mirrorToNative();
|
||||
startReadingMidi(mDevice.getInfo().getId(), mPortNumber);
|
||||
} catch (IOException e) {
|
||||
addMessage("! Failed to mirror to native !\n" + e.getMessage() + "\n");
|
||||
}
|
||||
|
||||
mActivePortSelector = MidiOutputPortSelector.this;
|
||||
|
||||
mOutputPort = device.openOutputPort(mPortNumber);
|
||||
mOutputPort.connect(mMidiReceiver);
|
||||
}
|
||||
}
|
||||
}, null);
|
||||
}
|
||||
|
||||
void closePortOnly() {
|
||||
stopReadingMidi();
|
||||
}
|
||||
|
||||
void close() {
|
||||
closePortOnly();
|
||||
try {
|
||||
if (mOutputPort != null) {
|
||||
mOutputPort.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
mMessagesTextView.append("! Port close error: " + e + "\n");
|
||||
} finally {
|
||||
mOutputPort = null;
|
||||
}
|
||||
try {
|
||||
if (mDevice != null) {
|
||||
mDevice.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
mMessagesTextView.append("! Device close error: " + e + "\n");
|
||||
} finally {
|
||||
mDevice = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private MidiManager.DeviceCallback mMidiDeviceCallback = new MidiManager.DeviceCallback() {
|
||||
@Override
|
||||
public void onDeviceAdded(MidiDeviceInfo info) {
|
||||
Bundle deviceProps = info.getProperties();
|
||||
String deviceName = deviceProps.getString(MidiDeviceInfo.PROPERTY_NAME);
|
||||
if (deviceName == null) {
|
||||
deviceName = deviceProps.getString(MidiDeviceInfo.PROPERTY_MANUFACTURER);
|
||||
}
|
||||
|
||||
for (MidiDeviceInfo.PortInfo port : info.getPorts()) {
|
||||
if (port.getType() != MidiDeviceInfo.PortInfo.TYPE_OUTPUT) continue;
|
||||
String portName = port.getName();
|
||||
int portNumber = port.getPortNumber();
|
||||
if (portName.length() == 0) portName = "[" + portNumber + "]";
|
||||
portName += "@" + deviceName;
|
||||
RadioButton outputDevice = new RadioButton(NativeMidi.this);
|
||||
outputDevice.setText(portName);
|
||||
outputDevice.setTag(info);
|
||||
outputDevice.setOnClickListener(new MidiOutputPortSelector(info, portNumber));
|
||||
mMidiDevicesRadioGroup.addView(outputDevice);
|
||||
}
|
||||
|
||||
NativeMidi.this.updateKeepScreenOn();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeviceRemoved(MidiDeviceInfo info) {
|
||||
if (mActivePortSelector != null && info.equals(mActivePortSelector.getDeviceInfo())) {
|
||||
mActivePortSelector.close();
|
||||
mActivePortSelector = null;
|
||||
}
|
||||
int removeButtonStart = -1, removeButtonCount = 0;
|
||||
final int buttonCount = mMidiDevicesRadioGroup.getChildCount();
|
||||
boolean checked = false;
|
||||
for (int i = 0; i < buttonCount; ++i) {
|
||||
RadioButton button = (RadioButton) mMidiDevicesRadioGroup.getChildAt(i);
|
||||
if (!info.equals(button.getTag())) continue;
|
||||
if (removeButtonStart == -1) removeButtonStart = i;
|
||||
++removeButtonCount;
|
||||
if (button.isChecked()) checked = true;
|
||||
}
|
||||
if (removeButtonStart != -1) {
|
||||
mMidiDevicesRadioGroup.removeViews(removeButtonStart, removeButtonCount);
|
||||
if (checked) {
|
||||
mMidiDevicesRadioGroup.check(R.id.device_none);
|
||||
}
|
||||
}
|
||||
|
||||
NativeMidi.this.updateKeepScreenOn();
|
||||
}
|
||||
};
|
||||
|
||||
private class JavaMidiReceiver extends MidiReceiver implements Runnable {
|
||||
@Override
|
||||
public void onSend(byte[] data, int offset,
|
||||
int count, long timestamp) throws IOException {
|
||||
mTimerHandler.removeCallbacks(this);
|
||||
mTimerHandler.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mJavaMidiStatusTextView.setText("Java: MSG");
|
||||
}
|
||||
}, 0);
|
||||
mTimerHandler.postDelayed(this, 100);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
mJavaMidiStatusTextView.setText("Java: ---");
|
||||
}
|
||||
}
|
||||
|
||||
private JavaMidiReceiver mMidiReceiver = new JavaMidiReceiver();
|
||||
|
||||
private void updateKeepScreenOn() {
|
||||
if (mMidiDevicesRadioGroup.getChildCount() > 1) {
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
} else {
|
||||
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.main);
|
||||
|
||||
mCallbackStatusTextView = (TextView) findViewById(R.id.callback_status);
|
||||
mJavaMidiStatusTextView = (TextView) findViewById(R.id.java_midi_status);
|
||||
mMessagesTextView = (TextView) findViewById(R.id.messages);
|
||||
mMessagesContainer = (TouchableScrollView) findViewById(R.id.messages_scroll);
|
||||
mMidiDevicesRadioGroup = (RadioGroup) findViewById(R.id.devices);
|
||||
RadioButton deviceNone = (RadioButton) findViewById(R.id.device_none);
|
||||
deviceNone.setOnClickListener(new MidiOutputPortSelector());
|
||||
|
||||
AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
|
||||
String sampleRate = am.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
|
||||
if (sampleRate == null) sampleRate = "48000";
|
||||
String framesPerBuffer = am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER);
|
||||
if (framesPerBuffer == null) framesPerBuffer = Integer.toString(mMaxFramesPerBuffer);
|
||||
mFramesPerBuffer = Integer.parseInt(framesPerBuffer);
|
||||
String audioInitResult = initAudio(Integer.parseInt(sampleRate), mFramesPerBuffer);
|
||||
mMessagesTextView.append("Open SL ES init: " + audioInitResult + "\n");
|
||||
|
||||
if (audioInitResult.startsWith("Success")) {
|
||||
mAudioWorks = true;
|
||||
mTimerHandler.postDelayed(mTimerRunnable, 0);
|
||||
mTimerHandler.postDelayed(mMidiReceiver, 0);
|
||||
}
|
||||
|
||||
mMidiManager = (MidiManager) getSystemService(Context.MIDI_SERVICE);
|
||||
mMidiManager.registerDeviceCallback(mMidiDeviceCallback, new Handler());
|
||||
for (MidiDeviceInfo info : mMidiManager.getDevices()) {
|
||||
mMidiDeviceCallback.onDeviceAdded(info);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
if (mAudioWorks) {
|
||||
mTimerHandler.removeCallbacks(mTimerRunnable);
|
||||
pauseAudio();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
if (mAudioWorks) {
|
||||
mTimerHandler.postDelayed(mTimerRunnable, 0);
|
||||
resumeAudio();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
if (mActivePortSelector != null) {
|
||||
mActivePortSelector.close();
|
||||
mActivePortSelector = null;
|
||||
}
|
||||
shutdownAudio();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
public void onClearMessages(View v) {
|
||||
mMessagesTextView.setText("");
|
||||
}
|
||||
|
||||
public void onClosePort(View v) {
|
||||
if (mActivePortSelector != null) {
|
||||
mActivePortSelector.closePortOnly();
|
||||
}
|
||||
}
|
||||
|
||||
private native String initAudio(int sampleRate, int playSamples);
|
||||
private native void pauseAudio();
|
||||
private native void resumeAudio();
|
||||
private native void shutdownAudio();
|
||||
|
||||
private native long getPlaybackCounter();
|
||||
private native String[] getRecentMessages();
|
||||
|
||||
private native void startReadingMidi(int deviceId, int portNumber);
|
||||
private native void stopReadingMidi();
|
||||
|
||||
static {
|
||||
System.loadLibrary("nativemidi_jni");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.example.android.nativemididemo;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.MotionEvent;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.ScrollView;
|
||||
|
||||
public class TouchableScrollView extends ScrollView {
|
||||
public boolean isTouched;
|
||||
|
||||
public TouchableScrollView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public TouchableScrollView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
switch (event.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
isTouched = true;
|
||||
break;
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
case MotionEvent.ACTION_UP:
|
||||
isTouched = false;
|
||||
break;
|
||||
}
|
||||
return super.onTouchEvent(event);
|
||||
}
|
||||
}
|
||||
35
media/tests/NativeMidiDemo/jni/Android.mk
Normal file
35
media/tests/NativeMidiDemo/jni/Android.mk
Normal file
@@ -0,0 +1,35 @@
|
||||
# Copyright (C) 2016 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.
|
||||
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_MODULE_TAGS := tests
|
||||
|
||||
LOCAL_MODULE := libnativemidi_jni
|
||||
|
||||
LOCAL_SRC_FILES := \
|
||||
nativemidi-jni.cpp \
|
||||
messagequeue.cpp
|
||||
|
||||
LOCAL_CFLAGS += -Wall -Wextra -Werror -O0
|
||||
|
||||
LOCAL_C_INCLUDES += \
|
||||
frameworks/base/media/native
|
||||
|
||||
LOCAL_CXX_STL := libc++_static
|
||||
|
||||
LOCAL_SHARED_LIBRARIES := libOpenSLES libmidi
|
||||
|
||||
include $(BUILD_SHARED_LIBRARY)
|
||||
138
media/tests/NativeMidiDemo/jni/messagequeue.cpp
Normal file
138
media/tests/NativeMidiDemo/jni/messagequeue.cpp
Normal file
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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 <atomic>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "messagequeue.h"
|
||||
|
||||
namespace nativemididemo {
|
||||
|
||||
static const int messageBufferSize = 64 * 1024;
|
||||
static char messageBuffer[messageBufferSize];
|
||||
static std::atomic_ullong messagesLastWritePosition;
|
||||
|
||||
void writeMessage(const char* message)
|
||||
{
|
||||
static unsigned long long lastWritePos = 0;
|
||||
size_t messageLen = strlen(message);
|
||||
if (messageLen == 0) return;
|
||||
|
||||
messageLen += 1; // Also count in the null terminator.
|
||||
char buffer[1024];
|
||||
if (messageLen >= messageBufferSize) {
|
||||
snprintf(buffer, sizeof(buffer), "!!! Message too long: %zu bytes !!!", messageLen);
|
||||
message = buffer;
|
||||
messageLen = strlen(message);
|
||||
}
|
||||
|
||||
size_t wrappedWritePos = lastWritePos % messageBufferSize;
|
||||
if (wrappedWritePos + messageLen >= messageBufferSize) {
|
||||
size_t tailLen = messageBufferSize - wrappedWritePos;
|
||||
memset(messageBuffer + wrappedWritePos, 0, tailLen);
|
||||
lastWritePos += tailLen;
|
||||
wrappedWritePos = 0;
|
||||
}
|
||||
|
||||
memcpy(messageBuffer + wrappedWritePos, message, messageLen);
|
||||
lastWritePos += messageLen;
|
||||
messagesLastWritePosition.store(lastWritePos);
|
||||
}
|
||||
|
||||
static char messageBufferCopy[messageBufferSize];
|
||||
|
||||
jobjectArray getRecentMessagesForJava(JNIEnv* env, jobject)
|
||||
{
|
||||
static unsigned long long lastReadPos = 0;
|
||||
const char* overrunMessage = "";
|
||||
size_t messagesCount = 0;
|
||||
jobjectArray result = NULL;
|
||||
|
||||
// First we copy the portion of the message buffer into messageBufferCopy. If after finishing
|
||||
// the copy we notice that the writer has mutated the portion of the buffer that we were
|
||||
// copying, we report an overrun. Afterwards we can safely read messages from the copy.
|
||||
memset(messageBufferCopy, 0, sizeof(messageBufferCopy));
|
||||
unsigned long long lastWritePos = messagesLastWritePosition.load();
|
||||
if (lastWritePos - lastReadPos > messageBufferSize) {
|
||||
overrunMessage = "!!! Message buffer overrun !!!";
|
||||
messagesCount = 1;
|
||||
lastReadPos = lastWritePos;
|
||||
goto create_array;
|
||||
}
|
||||
if (lastWritePos == lastReadPos) return result;
|
||||
if (lastWritePos / messageBufferSize == lastReadPos / messageBufferSize) {
|
||||
size_t wrappedReadPos = lastReadPos % messageBufferSize;
|
||||
memcpy(messageBufferCopy + wrappedReadPos,
|
||||
messageBuffer + wrappedReadPos,
|
||||
lastWritePos % messageBufferSize - wrappedReadPos);
|
||||
} else {
|
||||
size_t wrappedReadPos = lastReadPos % messageBufferSize;
|
||||
memcpy(messageBufferCopy, messageBuffer, lastWritePos % messageBufferSize);
|
||||
memcpy(messageBufferCopy + wrappedReadPos,
|
||||
messageBuffer + wrappedReadPos,
|
||||
messageBufferSize - wrappedReadPos);
|
||||
}
|
||||
{
|
||||
unsigned long long newLastWritePos = messagesLastWritePosition.load();
|
||||
if (newLastWritePos - lastReadPos > messageBufferSize) {
|
||||
overrunMessage = "!!! Message buffer overrun !!!";
|
||||
messagesCount = 1;
|
||||
lastReadPos = lastWritePos = newLastWritePos;
|
||||
goto create_array;
|
||||
}
|
||||
}
|
||||
// Otherwise we ignore newLastWritePos, since we only have a copy of the buffer
|
||||
// up to lastWritePos.
|
||||
|
||||
for (unsigned long long readPos = lastReadPos; readPos < lastWritePos; ) {
|
||||
size_t messageLen = strlen(messageBufferCopy + (readPos % messageBufferSize));
|
||||
if (messageLen != 0) {
|
||||
readPos += messageLen + 1;
|
||||
messagesCount++;
|
||||
} else {
|
||||
// Skip to the beginning of the buffer.
|
||||
readPos = (readPos / messageBufferSize + 1) * messageBufferSize;
|
||||
}
|
||||
}
|
||||
if (messagesCount == 0) {
|
||||
lastReadPos = lastWritePos;
|
||||
return result;
|
||||
}
|
||||
|
||||
create_array:
|
||||
result = env->NewObjectArray(
|
||||
messagesCount, env->FindClass("java/lang/String"), env->NewStringUTF(overrunMessage));
|
||||
if (lastWritePos == lastReadPos) return result;
|
||||
|
||||
jsize arrayIndex = 0;
|
||||
while (lastReadPos < lastWritePos) {
|
||||
size_t wrappedReadPos = lastReadPos % messageBufferSize;
|
||||
if (messageBufferCopy[wrappedReadPos] != '\0') {
|
||||
jstring message = env->NewStringUTF(messageBufferCopy + wrappedReadPos);
|
||||
env->SetObjectArrayElement(result, arrayIndex++, message);
|
||||
lastReadPos += env->GetStringLength(message) + 1;
|
||||
env->DeleteLocalRef(message);
|
||||
} else {
|
||||
// Skip to the beginning of the buffer.
|
||||
lastReadPos = (lastReadPos / messageBufferSize + 1) * messageBufferSize;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace nativemididemo
|
||||
30
media/tests/NativeMidiDemo/jni/messagequeue.h
Normal file
30
media/tests/NativeMidiDemo/jni/messagequeue.h
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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 NATIVEMIDIDEMO_MESSAGEQUEUE_H
|
||||
#define NATIVEMIDIDEMO_MESSAGEQUEUE_H
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
namespace nativemididemo {
|
||||
|
||||
void writeMessage(const char* message);
|
||||
jobjectArray getRecentMessagesForJava(JNIEnv* env, jobject thiz);
|
||||
|
||||
} // namespace nativemididemo
|
||||
|
||||
#endif // NATIVEMIDIDEMO_MESSAGEQUEUE_H
|
||||
285
media/tests/NativeMidiDemo/jni/nativemidi-jni.cpp
Normal file
285
media/tests/NativeMidiDemo/jni/nativemidi-jni.cpp
Normal file
@@ -0,0 +1,285 @@
|
||||
#include <atomic>
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include <midi/midi.h>
|
||||
#include <SLES/OpenSLES.h>
|
||||
#include <SLES/OpenSLES_Android.h>
|
||||
|
||||
#include "messagequeue.h"
|
||||
|
||||
extern "C" {
|
||||
JNIEXPORT jstring JNICALL Java_com_example_android_nativemididemo_NativeMidi_initAudio(
|
||||
JNIEnv* env, jobject thiz, jint sampleRate, jint playSamples);
|
||||
JNIEXPORT void JNICALL Java_com_example_android_nativemididemo_NativeMidi_pauseAudio(
|
||||
JNIEnv* env, jobject thiz);
|
||||
JNIEXPORT void JNICALL Java_com_example_android_nativemididemo_NativeMidi_resumeAudio(
|
||||
JNIEnv* env, jobject thiz);
|
||||
JNIEXPORT void JNICALL Java_com_example_android_nativemididemo_NativeMidi_shutdownAudio(
|
||||
JNIEnv* env, jobject thiz);
|
||||
JNIEXPORT jlong JNICALL Java_com_example_android_nativemididemo_NativeMidi_getPlaybackCounter(
|
||||
JNIEnv* env, jobject thiz);
|
||||
JNIEXPORT jobjectArray JNICALL Java_com_example_android_nativemididemo_NativeMidi_getRecentMessages(
|
||||
JNIEnv* env, jobject thiz);
|
||||
JNIEXPORT void JNICALL Java_com_example_android_nativemididemo_NativeMidi_startReadingMidi(
|
||||
JNIEnv* env, jobject thiz, jint deviceId, jint portNumber);
|
||||
JNIEXPORT void JNICALL Java_com_example_android_nativemididemo_NativeMidi_stopReadingMidi(
|
||||
JNIEnv* env, jobject thiz);
|
||||
}
|
||||
|
||||
static const char* errStrings[] = {
|
||||
"SL_RESULT_SUCCESS", // 0
|
||||
"SL_RESULT_PRECONDITIONS_VIOLATED", // 1
|
||||
"SL_RESULT_PARAMETER_INVALID", // 2
|
||||
"SL_RESULT_MEMORY_FAILURE", // 3
|
||||
"SL_RESULT_RESOURCE_ERROR", // 4
|
||||
"SL_RESULT_RESOURCE_LOST", // 5
|
||||
"SL_RESULT_IO_ERROR", // 6
|
||||
"SL_RESULT_BUFFER_INSUFFICIENT", // 7
|
||||
"SL_RESULT_CONTENT_CORRUPTED", // 8
|
||||
"SL_RESULT_CONTENT_UNSUPPORTED", // 9
|
||||
"SL_RESULT_CONTENT_NOT_FOUND", // 10
|
||||
"SL_RESULT_PERMISSION_DENIED", // 11
|
||||
"SL_RESULT_FEATURE_UNSUPPORTED", // 12
|
||||
"SL_RESULT_INTERNAL_ERROR", // 13
|
||||
"SL_RESULT_UNKNOWN_ERROR", // 14
|
||||
"SL_RESULT_OPERATION_ABORTED", // 15
|
||||
"SL_RESULT_CONTROL_LOST" }; // 16
|
||||
static const char* getSLErrStr(int code) {
|
||||
return errStrings[code];
|
||||
}
|
||||
|
||||
static SLObjectItf engineObject;
|
||||
static SLEngineItf engineEngine;
|
||||
static SLObjectItf outputMixObject;
|
||||
static SLObjectItf playerObject;
|
||||
static SLPlayItf playerPlay;
|
||||
static SLAndroidSimpleBufferQueueItf playerBufferQueue;
|
||||
|
||||
static const int minPlaySamples = 32;
|
||||
static const int maxPlaySamples = 1000;
|
||||
static std::atomic_int playSamples(maxPlaySamples);
|
||||
static short playBuffer[maxPlaySamples];
|
||||
|
||||
static std::atomic_ullong sharedCounter;
|
||||
|
||||
static AMIDI_Device midiDevice = AMIDI_INVALID_HANDLE;
|
||||
static std::atomic<AMIDI_OutputPort> midiOutputPort(AMIDI_INVALID_HANDLE);
|
||||
|
||||
static int setPlaySamples(int newPlaySamples)
|
||||
{
|
||||
if (newPlaySamples < minPlaySamples) newPlaySamples = minPlaySamples;
|
||||
if (newPlaySamples > maxPlaySamples) newPlaySamples = maxPlaySamples;
|
||||
playSamples.store(newPlaySamples);
|
||||
return newPlaySamples;
|
||||
}
|
||||
|
||||
// Amount of messages we are ready to handle during one callback cycle.
|
||||
static const size_t MAX_INCOMING_MIDI_MESSAGES = 20;
|
||||
// Static allocation to save time in the callback.
|
||||
static AMIDI_Message incomingMidiMessages[MAX_INCOMING_MIDI_MESSAGES];
|
||||
|
||||
static void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void */*context*/)
|
||||
{
|
||||
sharedCounter++;
|
||||
|
||||
AMIDI_OutputPort outputPort = midiOutputPort.load();
|
||||
if (outputPort != AMIDI_INVALID_HANDLE) {
|
||||
char midiDumpBuffer[1024];
|
||||
ssize_t midiReceived = AMIDI_receive(
|
||||
outputPort, incomingMidiMessages, MAX_INCOMING_MIDI_MESSAGES);
|
||||
if (midiReceived >= 0) {
|
||||
for (ssize_t i = 0; i < midiReceived; ++i) {
|
||||
AMIDI_Message* msg = &incomingMidiMessages[i];
|
||||
if (msg->opcode == AMIDI_OPCODE_DATA) {
|
||||
memset(midiDumpBuffer, 0, sizeof(midiDumpBuffer));
|
||||
int pos = snprintf(midiDumpBuffer, sizeof(midiDumpBuffer),
|
||||
"%" PRIx64 " ", msg->timestamp);
|
||||
for (uint8_t *b = msg->buffer, *e = b + msg->len; b < e; ++b) {
|
||||
pos += snprintf(midiDumpBuffer + pos, sizeof(midiDumpBuffer) - pos,
|
||||
"%02x ", *b);
|
||||
}
|
||||
nativemididemo::writeMessage(midiDumpBuffer);
|
||||
} else if (msg->opcode == AMIDI_OPCODE_FLUSH) {
|
||||
nativemididemo::writeMessage("MIDI flush");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
snprintf(midiDumpBuffer, sizeof(midiDumpBuffer),
|
||||
"! MIDI Receive error: %s !", strerror(-midiReceived));
|
||||
nativemididemo::writeMessage(midiDumpBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
size_t usedBufferSize = playSamples.load() * sizeof(playBuffer[0]);
|
||||
if (usedBufferSize > sizeof(playBuffer)) {
|
||||
usedBufferSize = sizeof(playBuffer);
|
||||
}
|
||||
(*bq)->Enqueue(bq, playBuffer, usedBufferSize);
|
||||
}
|
||||
|
||||
jstring Java_com_example_android_nativemididemo_NativeMidi_initAudio(
|
||||
JNIEnv* env, jobject, jint sampleRate, jint playSamples) {
|
||||
const char* stage;
|
||||
SLresult result;
|
||||
char printBuffer[1024];
|
||||
|
||||
playSamples = setPlaySamples(playSamples);
|
||||
|
||||
result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
|
||||
if (SL_RESULT_SUCCESS != result) { stage = "slCreateEngine"; goto handle_error; }
|
||||
|
||||
result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
|
||||
if (SL_RESULT_SUCCESS != result) { stage = "realize Engine object"; goto handle_error; }
|
||||
|
||||
result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);
|
||||
if (SL_RESULT_SUCCESS != result) { stage = "get Engine interface"; goto handle_error; }
|
||||
|
||||
result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 0, NULL, NULL);
|
||||
if (SL_RESULT_SUCCESS != result) { stage = "CreateOutputMix"; goto handle_error; }
|
||||
|
||||
result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
|
||||
if (SL_RESULT_SUCCESS != result) { stage = "realize OutputMix object"; goto handle_error; }
|
||||
|
||||
{
|
||||
SLDataFormat_PCM format_pcm = { SL_DATAFORMAT_PCM, 1, (SLuint32)sampleRate * 1000,
|
||||
SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16,
|
||||
SL_SPEAKER_FRONT_LEFT, SL_BYTEORDER_LITTLEENDIAN };
|
||||
SLDataLocator_AndroidSimpleBufferQueue loc_bufq =
|
||||
{ SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 1 };
|
||||
SLDataSource audioSrc = { &loc_bufq, &format_pcm };
|
||||
SLDataLocator_OutputMix loc_outmix = { SL_DATALOCATOR_OUTPUTMIX, outputMixObject };
|
||||
SLDataSink audioSnk = { &loc_outmix, NULL };
|
||||
const SLInterfaceID ids[1] = { SL_IID_BUFFERQUEUE };
|
||||
const SLboolean req[1] = { SL_BOOLEAN_TRUE };
|
||||
result = (*engineEngine)->CreateAudioPlayer(
|
||||
engineEngine, &playerObject, &audioSrc, &audioSnk, 1, ids, req);
|
||||
if (SL_RESULT_SUCCESS != result) { stage = "CreateAudioPlayer"; goto handle_error; }
|
||||
|
||||
result = (*playerObject)->Realize(playerObject, SL_BOOLEAN_FALSE);
|
||||
if (SL_RESULT_SUCCESS != result) { stage = "realize Player object"; goto handle_error; }
|
||||
}
|
||||
|
||||
result = (*playerObject)->GetInterface(playerObject, SL_IID_PLAY, &playerPlay);
|
||||
if (SL_RESULT_SUCCESS != result) { stage = "get Play interface"; goto handle_error; }
|
||||
|
||||
result = (*playerObject)->GetInterface(playerObject, SL_IID_BUFFERQUEUE, &playerBufferQueue);
|
||||
if (SL_RESULT_SUCCESS != result) { stage = "get BufferQueue interface"; goto handle_error; }
|
||||
|
||||
result = (*playerBufferQueue)->RegisterCallback(playerBufferQueue, bqPlayerCallback, NULL);
|
||||
if (SL_RESULT_SUCCESS != result) { stage = "register BufferQueue callback"; goto handle_error; }
|
||||
|
||||
result = (*playerBufferQueue)->Enqueue(playerBufferQueue, playBuffer, sizeof(playBuffer));
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
stage = "enqueue into PlayerBufferQueue"; goto handle_error; }
|
||||
|
||||
result = (*playerPlay)->SetPlayState(playerPlay, SL_PLAYSTATE_PLAYING);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
stage = "SetPlayState(SL_PLAYSTATE_PLAYING)"; goto handle_error; }
|
||||
|
||||
snprintf(printBuffer, sizeof(printBuffer),
|
||||
"Success, sample rate %d, buffer samples %d", sampleRate, playSamples);
|
||||
return env->NewStringUTF(printBuffer);
|
||||
|
||||
handle_error:
|
||||
snprintf(printBuffer, sizeof(printBuffer), "Error at %s: %s", stage, getSLErrStr(result));
|
||||
return env->NewStringUTF(printBuffer);
|
||||
}
|
||||
|
||||
void Java_com_example_android_nativemididemo_NativeMidi_pauseAudio(
|
||||
JNIEnv*, jobject) {
|
||||
if (playerPlay != NULL) {
|
||||
(*playerPlay)->SetPlayState(playerPlay, SL_PLAYSTATE_PAUSED);
|
||||
}
|
||||
}
|
||||
|
||||
void Java_com_example_android_nativemididemo_NativeMidi_resumeAudio(
|
||||
JNIEnv*, jobject) {
|
||||
if (playerBufferQueue != NULL && playerPlay != NULL) {
|
||||
(*playerBufferQueue)->Enqueue(playerBufferQueue, playBuffer, sizeof(playBuffer));
|
||||
(*playerPlay)->SetPlayState(playerPlay, SL_PLAYSTATE_PLAYING);
|
||||
}
|
||||
}
|
||||
|
||||
void Java_com_example_android_nativemididemo_NativeMidi_shutdownAudio(
|
||||
JNIEnv*, jobject) {
|
||||
if (playerObject != NULL) {
|
||||
(*playerObject)->Destroy(playerObject);
|
||||
playerObject = NULL;
|
||||
playerPlay = NULL;
|
||||
playerBufferQueue = NULL;
|
||||
}
|
||||
|
||||
if (outputMixObject != NULL) {
|
||||
(*outputMixObject)->Destroy(outputMixObject);
|
||||
outputMixObject = NULL;
|
||||
}
|
||||
|
||||
if (engineObject != NULL) {
|
||||
(*engineObject)->Destroy(engineObject);
|
||||
engineObject = NULL;
|
||||
engineEngine = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
jlong Java_com_example_android_nativemididemo_NativeMidi_getPlaybackCounter(JNIEnv*, jobject) {
|
||||
return sharedCounter.load();
|
||||
}
|
||||
|
||||
jobjectArray Java_com_example_android_nativemididemo_NativeMidi_getRecentMessages(
|
||||
JNIEnv* env, jobject thiz) {
|
||||
return nativemididemo::getRecentMessagesForJava(env, thiz);
|
||||
}
|
||||
|
||||
void Java_com_example_android_nativemididemo_NativeMidi_startReadingMidi(
|
||||
JNIEnv*, jobject, jint deviceId, jint portNumber) {
|
||||
char buffer[1024];
|
||||
|
||||
int result = AMIDI_getDeviceById(deviceId, &midiDevice);
|
||||
if (result == 0) {
|
||||
snprintf(buffer, sizeof(buffer), "Obtained device token for uid %d: token %d", deviceId, midiDevice);
|
||||
} else {
|
||||
snprintf(buffer, sizeof(buffer), "Could not obtain device token for uid %d: %d", deviceId, result);
|
||||
}
|
||||
nativemididemo::writeMessage(buffer);
|
||||
if (result) return;
|
||||
|
||||
AMIDI_DeviceInfo deviceInfo;
|
||||
result = AMIDI_getDeviceInfo(midiDevice, &deviceInfo);
|
||||
if (result == 0) {
|
||||
snprintf(buffer, sizeof(buffer), "Device info: uid %d, type %d, priv %d, ports %d I / %d O",
|
||||
deviceInfo.uid, deviceInfo.type, deviceInfo.isPrivate,
|
||||
(int)deviceInfo.inputPortCount, (int)deviceInfo.outputPortCount);
|
||||
} else {
|
||||
snprintf(buffer, sizeof(buffer), "Could not obtain device info %d", result);
|
||||
}
|
||||
nativemididemo::writeMessage(buffer);
|
||||
if (result) return;
|
||||
|
||||
AMIDI_OutputPort outputPort;
|
||||
result = AMIDI_openOutputPort(midiDevice, portNumber, &outputPort);
|
||||
if (result == 0) {
|
||||
snprintf(buffer, sizeof(buffer), "Opened port %d: token %d", portNumber, outputPort);
|
||||
midiOutputPort.store(outputPort);
|
||||
} else {
|
||||
snprintf(buffer, sizeof(buffer), "Could not open port %d: %d", deviceId, result);
|
||||
}
|
||||
nativemididemo::writeMessage(buffer);
|
||||
}
|
||||
|
||||
void Java_com_example_android_nativemididemo_NativeMidi_stopReadingMidi(
|
||||
JNIEnv*, jobject) {
|
||||
AMIDI_OutputPort outputPort = midiOutputPort.exchange(AMIDI_INVALID_HANDLE);
|
||||
if (outputPort == AMIDI_INVALID_HANDLE) return;
|
||||
int result = AMIDI_closeOutputPort(outputPort);
|
||||
char buffer[1024];
|
||||
if (result == 0) {
|
||||
snprintf(buffer, sizeof(buffer), "Closed port by token %d", outputPort);
|
||||
} else {
|
||||
snprintf(buffer, sizeof(buffer), "Could not close port by token %d: %d", outputPort, result);
|
||||
}
|
||||
nativemididemo::writeMessage(buffer);
|
||||
}
|
||||
93
media/tests/NativeMidiDemo/res/layout/main.xml
Normal file
93
media/tests/NativeMidiDemo/res/layout/main.xml
Normal file
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
>
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
>
|
||||
<TextView
|
||||
android:id="@+id/callback_status"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="13sp"
|
||||
android:typeface="monospace"
|
||||
/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="13sp"
|
||||
android:typeface="monospace"
|
||||
android:text=" | "
|
||||
/>
|
||||
<TextView
|
||||
android:id="@+id/java_midi_status"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="13sp"
|
||||
android:typeface="monospace"
|
||||
/>
|
||||
<TextView
|
||||
android:layout_width="0px"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:textSize="13sp"
|
||||
android:typeface="monospace"
|
||||
android:text=" "
|
||||
/>
|
||||
</LinearLayout>
|
||||
<HorizontalScrollView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="6sp"
|
||||
android:paddingBottom="6sp"
|
||||
>
|
||||
<RadioGroup
|
||||
android:id="@+id/devices"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:checkedButton="@+id/device_none"
|
||||
>
|
||||
<RadioButton
|
||||
android:id="@+id/device_none"
|
||||
android:text="None"
|
||||
/>
|
||||
</RadioGroup>
|
||||
</HorizontalScrollView>
|
||||
<com.example.android.nativemididemo.TouchableScrollView android:id="@+id/messages_scroll"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0px"
|
||||
android:layout_weight="1"
|
||||
>
|
||||
<TextView android:id="@+id/messages"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="13sp"
|
||||
android:typeface="monospace"
|
||||
/>
|
||||
</com.example.android.nativemididemo.TouchableScrollView>
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
>
|
||||
<Button android:id="@+id/clear_messages"
|
||||
android:layout_width="0px"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/clear_messages"
|
||||
android:onClick="onClearMessages"
|
||||
/>
|
||||
<Button android:id="@+id/close_port"
|
||||
android:layout_width="0px"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/close_port"
|
||||
android:onClick="onClosePort"
|
||||
/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
BIN
media/tests/NativeMidiDemo/res/mipmap-hdpi/ic_launcher.png
Normal file
BIN
media/tests/NativeMidiDemo/res/mipmap-hdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.3 KiB |
BIN
media/tests/NativeMidiDemo/res/mipmap-mdpi/ic_launcher.png
Normal file
BIN
media/tests/NativeMidiDemo/res/mipmap-mdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
BIN
media/tests/NativeMidiDemo/res/mipmap-xhdpi/ic_launcher.png
Normal file
BIN
media/tests/NativeMidiDemo/res/mipmap-xhdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.7 KiB |
BIN
media/tests/NativeMidiDemo/res/mipmap-xxhdpi/ic_launcher.png
Normal file
BIN
media/tests/NativeMidiDemo/res/mipmap-xxhdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.5 KiB |
6
media/tests/NativeMidiDemo/res/values/strings.xml
Normal file
6
media/tests/NativeMidiDemo/res/values/strings.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">NativeMidiDemo</string>
|
||||
<string name="clear_messages">Clear Messages</string>
|
||||
<string name="close_port">Close Port</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user