Onde o Delphi / Android procura uma biblioteca de idiomas nativos?
Quero adicionar recursos MIDI aos aplicativos Delphi para Android. O MIDI está disponível na biblioteca SoniVox, que pode ser acessada no Android NDK. Um exemplo deste driver pode ser encontradoaqui. O driver é gravado em C, com o NDK é possível criar uma biblioteca de idioma nativo que pode ser acessada por meio de uma chamada System.loadLibrary.
// MidiDriver - An Android Midi Driver.
// Copyright (C) 2013 Bill Farmer
// Bill Farmer william j farmer [at] yahoo [dot] co [dot] uk.
#include <jni.h>
// for EAS midi
#include "eas.h"
#include "eas_reverb.h"
// determines how many EAS buffers to fill a host buffer
#define NUM_BUFFERS 4
// EAS data
static EAS_DATA_HANDLE pEASData;
const S_EAS_LIB_CONFIG *pLibConfig;
static EAS_PCM *buffer;
static EAS_I32 bufferSize;
static EAS_HANDLE midiHandle;
// init EAS midi
jint
Java_org_drivers_midioutput_MidiDriver_init(JNIEnv *env,
jobject clazz)
{
EAS_RESULT result;
// get the library configuration
pLibConfig = EAS_Config();
if (pLibConfig == NULL || pLibConfig->libVersion != LIB_VERSION)
return 0;
// calculate buffer size
bufferSize = pLibConfig->mixBufferSize * pLibConfig->numChannels *
NUM_BUFFERS;
// init library
if ((result = EAS_Init(&pEASData)) != EAS_SUCCESS)
return 0;
// select reverb preset and enable
EAS_SetParameter(pEASData, EAS_MODULE_REVERB, EAS_PARAM_REVERB_PRESET,
EAS_PARAM_REVERB_CHAMBER);
EAS_SetParameter(pEASData, EAS_MODULE_REVERB, EAS_PARAM_REVERB_BYPASS,
EAS_FALSE);
// open midi stream
if (result = EAS_OpenMIDIStream(pEASData, &midiHandle, NULL) !=
EAS_SUCCESS)
{
EAS_Shutdown(pEASData);
return 0;
}
return bufferSize;
}
// midi config
jintArray
Java_org_drivers_midioutput_MidiDriver_config(JNIEnv *env,
jobject clazz)
{
jboolean isCopy;
if (pLibConfig == NULL)
return NULL;
jintArray configArray = (*env)->NewIntArray(env, 4);
jint *config = (*env)->GetIntArrayElements(env, configArray, &isCopy);
config[0] = pLibConfig->maxVoices;
config[1] = pLibConfig->numChannels;
config[2] = pLibConfig->sampleRate;
config[3] = pLibConfig->mixBufferSize;
(*env)->ReleaseIntArrayElements(env, configArray, config, 0);
return configArray;
}
// midi render
jint
Java_org_drivers_midioutput_MidiDriver_render(JNIEnv *env,
jobject clazz,
jshortArray shortArray)
{
jboolean isCopy;
EAS_RESULT result;
EAS_I32 numGenerated;
EAS_I32 count;
jsize size;
// jbyte* GetByteArrayElements(jbyteArray array, jboolean* isCopy)
// void ReleaseByteArrayElements(jbyteArray array, jbyte* elems,
// void* GetPrimitiveArrayCritical(JNIEnv*, jarray, jboolean*);
// void ReleasePrimitiveArrayCritical(JNIEnv*, jarray, void*, jint);
if (pEASData == NULL)
return 0;
buffer =
(EAS_PCM *)(*env)->GetShortArrayElements(env, shortArray, &isCopy);
size = (*env)->GetArrayLength(env, shortArray);
count = 0;
while (count < size)
{
result = EAS_Render(pEASData, buffer + count,
pLibConfig->mixBufferSize, &numGenerated);
if (result != EAS_SUCCESS)
break;
count += numGenerated * pLibConfig->numChannels;
}
(*env)->ReleaseShortArrayElements(env, shortArray, buffer, 0);
return count;
}
// midi write
jboolean
Java_org_drivers_midioutput_MidiDriver_write(JNIEnv *env,
jobject clazz,
jbyteArray byteArray)
{
jboolean isCopy;
EAS_RESULT result;
jint length;
EAS_U8 *buf;
if (pEASData == NULL || midiHandle == NULL)
return JNI_FALSE;
buf = (EAS_U8 *)(*env)->GetByteArrayElements(env, byteArray, &isCopy);
length = (*env)->GetArrayLength(env, byteArray);
result = EAS_WriteMIDIStream(pEASData, midiHandle, buf, length);
(*env)->ReleaseByteArrayElements(env, byteArray, buf, 0);
if (result != EAS_SUCCESS)
return JNI_FALSE;
return JNI_TRUE;
}
// shutdown EAS midi
jboolean
Java_org_drivers_midioutput_MidiDriver_shutdown(JNIEnv *env,
jobject clazz)
{
EAS_RESULT result;
if (pEASData == NULL || midiHandle == NULL)
return JNI_FALSE;
if ((result = EAS_CloseMIDIStream(pEASData, midiHandle)) != EAS_SUCCESS)
{
EAS_Shutdown(pEASData);
return JNI_FALSE;
}
if ((result = EAS_Shutdown(pEASData)) != EAS_SUCCESS)
return JNI_FALSE;
return JNI_TRUE;
}
Criei um aplicativo Android com o Eclipse, adicionei o MidiDriver como uma biblioteca nativa e coloquei tudo em funcionamento. Com este driver, tenho recursos MIDI no meu aplicativo. O resumo do código MidiDriver você encontrará abaixo.
// MidiDriver - An Android Midi Driver.
// Copyright (C) 2013 Bill Farmer
// Bill Farmer william j farmer [at] yahoo [dot] co [dot] uk.
package org.drivers.midioutput;
import java.io.File;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.util.Log;
// MidiDriver
public class MidiDriver implements Runnable
{
private static final int SAMPLE_RATE = 22050;
private static final int BUFFER_SIZE = 4096;
private Thread thread;
private AudioTrack audioTrack;
private OnMidiStartListener listener;
private short buffer[];
// Constructor
public MidiDriver ()
{
Log.d ("midi", ">> MidiDriver started");
}
public void start ()
{
// Start the thread
thread = new Thread (this, "MidiDriver");
thread.start ();
} // start //
@Override
public void run ()
{
processMidi ();
} // run //
public void stop ()
{
Thread t = thread;
thread = null;
// Wait for the thread to exit
while (t != null && t.isAlive ())
Thread.yield ();
} // stop //
// Process MidiDriver
private void processMidi ()
{
int status = 0;
int size = 0;
// Init midi
if ((size = init()) == 0)
return;
buffer = new short [size];
// Create audio track
audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, SAMPLE_RATE,
AudioFormat.CHANNEL_OUT_STEREO,
AudioFormat.ENCODING_PCM_16BIT,
BUFFER_SIZE, AudioTrack.MODE_STREAM);
if (audioTrack == null)
{
shutdown ();
return;
} // if
// Call listener
if (listener != null)
listener.onMidiStart();
// Play track
audioTrack.play();
// Keep running until stopped
while (thread != null)
{
// Render the audio
if (render(buffer) == 0)
break;
// Write audio to audiotrack
status = audioTrack.write(buffer, 0, buffer.length);
if (status < 0)
break;
} // while
// Render and write the last bit of audio
if (status > 0)
if (render(buffer) > 0)
audioTrack.write(buffer, 0, buffer.length);
// Shut down audio
shutdown();
audioTrack.release();
} // processMidi //
public void setOnMidiStartListener (OnMidiStartListener l)
{
listener = l;
} // setOnMidiStartListener //
public static void load_lib (String libName)
{
File file = new File (libName);
if (file.exists ())
{
System.load (libName);
} else
{
System.loadLibrary (libName);
}
} // Listener interface
public interface OnMidiStartListener
{
public abstract void onMidiStart ();
} // OnMidiStartListener //
// Native midi methods
private native int init ();
public native int [] config ();
private native int render (short a []);
public native boolean write (byte a []);
private native boolean shutdown ();
// Load midi library
static
{
System.loadLibrary ("midi");
}
}
Em seguida, testei a interface JNI para ver se conseguia acessarClasses Java do Delphi via JNI. Não tem problema também. Então, teoricamente, eu deveria poder acessar o MidiDriver através de uma interface Java: legal! Eu envolvi o MidiDriver em outra classe Java: MIDI_Output para lidar com a interface internamente (não tenho idéia de como fazer interface com uma interface Java no Delphi. MIDI_Output cria uma instância do MidiDriver e chama funções do MidiDriver quando necessário. Isso tudo funciona muito bem quando algumas partes do MIDI_Output abaixo:
package org.drivers.midioutput;
import java.io.IOException;
import org.drivers.midioutput.MidiDriver.OnMidiStartListener;
import android.media.MediaPlayer;
import android.os.Environment;
import android.util.Log;
public class MIDI_Output implements OnMidiStartListener
{
protected MidiDriver midi_driver;
protected MediaPlayer media_player;
public MIDI_Output ()
{
// Create midi driver
Log.d ("midi", ">> Before initializing MIDI driver version 1");
midi_driver = new MidiDriver();
// Set onmidistart listener to this class
if (midi_driver != null)
midi_driver.setOnMidiStartListener (this);
} // MIDI_Output () //
protected void putShort (int m, int n, int v)
{
if (midi_driver != null)
{
byte msg [] = new byte [3];
msg [0] = (byte) m;
msg [1] = (byte) n;
msg [2] = (byte) v;
midi_driver.write (msg);
} // if
} // putShort //
// and much more code
// ...
No exemplo acima, putShort chama a função write do MidiDriver, que é uma função definida na biblioteca nativa. Tudo isso funciona bem em Java, mas na prática do Dellphi é um pouco mais difícil, como você deve ter adivinhado. Para mostrar com mais detalhes a cadeia de chamadas, preciso usar toda essa engenhoca em Delphi, veja a imagem abaixo.
Nolibsonivox
(encontrado em/system/lib
) a funçãoEAS_WriteMidiStream
pode ser encontrado, chamado de funçãowrite
nolibmidi.so
(encontrado em todos os lugares, mas também em/system/lib
e/vendor/lib
), declarado emMidiDriver.java
noMIDI_Output.apk
que é chamado deMIDI_Output.java
o que cria umnew MidiDriver
e refere-se amidi_driver.write (...)
em funçãoputShort
do mesmo pacote. FinalmenteputShort
deve ser chamado em Delphi, mas nunca chega lá.
É abortado quandoMIDI_Output
crie onew MidiDriver
que tenta carregar omidi
biblioteca. O programa não pode carregar a biblioteca "midi
”. Eu corriadb -d logcat
para ver o que acontece e a saída é mostrada abaixo. A mensagem de erro exibida na tela do Android é destacada.
D/dalvikvm( 5251): DexOpt: --- BEGIN 'MIDI_Output.apk' (bootstrap=0) ---
D/dalvikvm( 5320): DexOpt: load 50ms, verify+opt 174ms, 1050124 bytes
D/dalvikvm( 5251): DexOpt: --- END 'MIDI_Output.apk' (success) ---
D/dalvikvm( 5251): DEX prep '/storage/emulated/legacy/Data/d/MIDI_Output.apk': u
nzip in 14ms, rewrite 401ms
W/dalvikvm( 5251): dvmFindClassByName rejecting 'org.drivers.midioutput/MIDI_Out
put'
D/midi ( 5251): >> Before initializing MIDI driver version 1
W/dalvikvm( 5251): Exception Ljava/lang/UnsatisfiedLinkError; thrown while initi
alizing Lorg/drivers/midioutput/MidiDriver;
W/System.err( 5251): java.lang.UnsatisfiedLinkError: Couldn't load midi from loa
der dalvik.system.DexClassLoader[DexPathList[[zip file "/storage/sdcard0/Data/d/
MIDI_Output.apk"],nativeLibraryDirectories=[/vendor/lib, /system/lib]]]: findLib
rary returned null
W/System.err( 5251): at java.lang.Runtime.loadLibrary(Runtime.java:358)
W/System.err( 5251): at java.lang.System.loadLibrary(System.java:526)
W/System.err( 5251): at org.drivers.midioutput.MidiDriver.<clinit>(MidiDriver
.java:177)
W/System.err( 5251): at org.drivers.midioutput.MIDI_Output.<init>(MIDI_Output
.java:22)
W/System.err( 5251): at java.lang.Class.newInstanceImpl(Native Method)
W/System.err( 5251): at java.lang.Class.newInstance(Class.java:1208)
W/System.err( 5251): at dalvik.system.NativeStart.run(Native Method)
D/dalvikvm( 5251): GC_FOR_ALLOC freed 795K, 9% free 9091K/9920K, paused 13ms,
total 13ms
D/dalvikvm( 5251): GC_CONCURRENT freed 9K, 4% free 9532K/9920K, paused
2ms+2ms, total 22ms
Entendo que a biblioteca não pode ser encontrada. O problema é que não sei onde esta biblioteca é pesquisada. A mensagem de erro menciona/vendor/lib
e/system/lib
. Adicionei libmidi.so a essas bibliotecas. Eu adicionei a biblioteca ao diretório do aplicativocom.embarcadero.MIDI_Output_Project/lib
(é onde o android armazena a biblioteca), para/storage/sdcard0/Data/d/
(o diretório em que MIDI_Output.apk que contém as classes Java está armazenado). Eu literalmente borrifei meu sistema Android com libmidi.so. Eu tentei carregarlibmidi.so
também.
Como teste, adicionei uma classe de exemplo muito simples ao pacote MIDI_Output e chamei a função test_int. Isso é executado sem problemas.
package org.drivers.midioutput;
import android.util.Log;
public class class_test
{
public int test_int (int n)
{
int sq = n * n;
String mess = "*** test_int computes " + String.valueOf (sq);
Log.d ("midi", mess);
return n * n;
}
}
Minha pergunta é: em qual diretório o DalvikVM está procurando uma biblioteca nativa em uma configuração descrita acima (Delphi chamando uma classe Java via JNI e Java chamando uma biblioteca C via NDK)?
Uma segunda pergunta: é um problema no caminho de pesquisa da biblioteca? Talvez o Delphi não consiga ligar para o NDK via JNI.
Eu tentei ser o mais breve possível. Se alguém achar que devo adicionar mais código, entre em contato. Qualquer ajuda é muito apreciada.