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.

questionAnswers(1)

yourAnswerToTheQuestion