blob: 0110b75b7ba491f86313d7ec5630869aa581f1a5 [file] [log] [blame]
#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, jlong deviceHandle, jint portNumber) {
char buffer[1024];
midiDevice = (AMIDI_Device*)deviceHandle;
// 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;
int 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 %p", portNumber, outputPort);
midiOutputPort.store(outputPort);
} else {
snprintf(buffer, sizeof(buffer), "Could not open port %p: %d", midiDevice, 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 %p", outputPort);
} else {
snprintf(buffer, sizeof(buffer), "Could not close port by token %p: %d", outputPort, result);
}
nativemididemo::writeMessage(buffer);
}