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&nbsp;(encontrado em/system/lib) a funçãoEAS_WriteMidiStream&nbsp;pode ser encontrado, chamado de funçãowrite&nbsp;nolibmidi.so&nbsp;(encontrado em todos os lugares, mas também em/system/lib&nbsp;e/vendor/lib), declarado emMidiDriver.java&nbsp;noMIDI_Output.apk&nbsp;que é chamado deMIDI_Output.java&nbsp;o que cria umnew MidiDriver&nbsp;e refere-se amidi_driver.write (...)&nbsp;em funçãoputShort&nbsp;do mesmo pacote. FinalmenteputShort&nbsp;deve ser chamado em Delphi, mas nunca chega lá.

É abortado quandoMIDI_Output&nbsp;crie onew MidiDriver&nbsp;que tenta carregar omidi&nbsp;biblioteca. O programa não pode carregar a biblioteca "midi”. Eu corriadb -d logcat&nbsp;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&nbsp;e/system/lib. Adicionei libmidi.so a essas bibliotecas. Eu adicionei a biblioteca ao diretório do aplicativocom.embarcadero.MIDI_Output_Project/lib&nbsp;(é onde o android armazena a biblioteca), para/storage/sdcard0/Data/d/&nbsp;(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&nbsp;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.