नेटिव एमआईडीआई एपीआई

AMidi एपीआई यह है Android NDK r20b और इसके बाद के वर्शन में उपलब्ध है. यह ऐप्लिकेशन को यह ज़रूरी है कि डेवलपर C/C++कोड के साथ एमआईडीआई डेटा को भेज और पा सकते हैं.

Android MIDI ऐप्लिकेशन आम तौर पर midi एपीआई Android एमआईडीआई की सेवा. एमआईडीआई ऐप्लिकेशन मुख्य रूप से MidiManager की मदद से पता करें, और एक या उससे ज़्यादा पैनल बंद करें MidiDevice ऑब्जेक्ट, और डिवाइस की एमआईडीआई इनपुट और आउटपुट पोर्ट:

Amidi का इस्तेमाल करने पर, आप MidiDevice का पता नेटिव कोड में पास करते हैं JNI कॉल वाली लेयर चुनें. इसके बाद, Amidi, AMidiDevice का रेफ़रंस बनाता है इसमें MidiDevice की ज़्यादातर सुविधाएं मौजूद हैं. आपका नेटिव कोड, AMidi फ़ंक्शन, जो बातचीत करते हैं के साथ सीधे AMidiDevice के साथ AMidiDevice सीधे एमआईडीआई सेवा:

AMidi कॉल का इस्तेमाल करके, अपने ऐप्लिकेशन के C/C++ ऑडियो/कंट्रोल लॉजिक को बारीकी से इंटिग्रेट किया जा सकता है एमआईडीआई ट्रांसमिशन के साथ. JNI कॉल, या कॉलबैक की ज़रूरत आपके ऐप्लिकेशन का Java साइड. उदाहरण के लिए, C कोड में लागू डिजिटल सिंथेसाइज़र किसी जेएनआई का इंतज़ार करने के बजाय, सीधे AMidiDevice से मुख्य इवेंट पाएं Java की ओर से इवेंट को नीचे भेजने के लिए कॉल करें. या एल्गोरिदम वाले कॉन्टेंट का इस्तेमाल करके बनाया गया कॉन्टेंट प्रोसेस, बिना कॉल किए AMidiDevice को सीधे एमआईडीआई की परफ़ॉर्मेंस भेज सकती है का बैक अप लेना होगा, ताकि मुख्य इवेंट ट्रांसमिट किए जा सकें.

हालांकि AMidi, एमआईडीआई डिवाइसों के डायरेक्ट कनेक्शन को बेहतर बनाता है, लेकिन ऐप्लिकेशन को अब भी ऐसा करना चाहिए MidiDevice ऑब्जेक्ट को खोजने और खोलने के लिए, MidiManager का इस्तेमाल करें. Amidi यह कर सकते हैं इसे वहां से ले लो.

कभी-कभी आपको यूज़र इंटरफ़ेस (यूआई) लेयर से जानकारी को नेटिव कोड. उदाहरण के लिए, जब चालू बटन के जवाब में एमआईडीआई इवेंट भेजे जाते हैं स्क्रीन. ऐसा करने के लिए अपने नेटिव लॉजिक के हिसाब से कस्टम JNI कॉल बनाएं. अगर आपको को यूज़र इंटरफ़ेस (यूआई) अपडेट करने के लिए डेटा को वापस भेजना होता है, तो नेटिव लेयर से वापस कॉल करें पहले की तरह.

इस दस्तावेज़ में, AMidi नेटिव कोड ऐप्लिकेशन को सेट अप करने का तरीका बताया गया है. इसमें दिए गए उदाहरण भी दिए गए हैं भेजने और पाने, दोनों के लिए. काम करने का तरीका जानने के लिए, यहां जाएं: NativeMidi सैंपल के तौर पर मिला है.

AMidi का इस्तेमाल करें

AMidi का इस्तेमाल करने वाले सभी ऐप्लिकेशन को सेटअप और बंद करने का तरीका एक जैसा होता है. भले ही, वे एमआईडीआई या दोनों भेजने या पाने की सुविधा मिलती है.

AMidi शुरू करें

Java की ओर से, ऐप्लिकेशन को एमआईडीआई हार्डवेयर का हिस्सा, इससे जुड़ा MidiDevice बनाएं, और उसे नेटिव कोड में पास कर देता है.

  1. Java MidiManager क्लास के साथ एमआईडीआई हार्डवेयर के बारे में जानें.
  2. एमआईडीआई हार्डवेयर से जुड़ा Java MidiDevice ऑब्जेक्ट पाएं.
  3. JNI की मदद से, Java MidiDevice को नेटिव कोड में पास करें.

हार्डवेयर और पोर्ट खोजें

इनपुट और आउटपुट पोर्ट ऑब्जेक्ट, ऐप्लिकेशन से जुड़े नहीं हैं. वे पोर्ट का प्रतिनिधित्व करते हैं मिडी डिवाइस पर. किसी डिवाइस को एमआईडीआई डेटा भेजने के लिए, ऐप्लिकेशन MIDIInputPort और फिर इसमें डेटा लिखता है. वहीं दूसरी ओर, डेटा पाने के लिए एक ऐप्लिकेशन MIDIOutputPort खोलता है. ऐप्लिकेशन ठीक से काम करे, इसके लिए ज़रूरी है कि ऐप्लिकेशन के पास पोर्ट हों ओपनिंग का विकल्प सही है. डिवाइस और पोर्ट की खोज, Java की तरफ़ से की जाती है.

यहां एक तरीका दिया गया है, जो हर एमआईडीआई डिवाइस को खोजता है और उसके पोर्ट. यह सूचना पाने के लिए, आउटपुट पोर्ट वाले डिवाइसों की सूची दिखाता है डेटा या डेटा भेजने के लिए इनपुट पोर्ट वाले डिवाइसों की सूची शामिल है. एमआईडीआई डिवाइस यह कर सकता है उसमें इनपुट पोर्ट और आउटपुट पोर्ट, दोनों हों.

Kotlin

private fun getMidiDevices(isOutput: Boolean) : List {
    if (isOutput) {
        return mMidiManager.devices.filter { it.outputPortCount > 0 }
    } else {
        return mMidiManager.devices.filter { it.inputPortCount > 0 }
    }
}

Java

private List getMidiDevices(boolean isOutput){
  ArrayList filteredMidiDevices = new ArrayList<>();

  for (MidiDeviceInfo midiDevice : mMidiManager.getDevices()){
    if (isOutput){
      if (midiDevice.getOutputPortCount() > 0) filteredMidiDevices.add(midiDevice);
    } else {
      if (midiDevice.getInputPortCount() > 0) filteredMidiDevices.add(midiDevice);
    }
  }
  return filteredMidiDevices;
}

अपने C/C++ कोड में AMidi फ़ंक्शन का इस्तेमाल करने के लिए, आपको AMidi/AMidi.h और amidi लाइब्रेरी से लिंक करें. इन दोनों सोर्स में, Android एनडीके (NDK) में.

Java साइड को एक या उससे ज़्यादा MidiDevice ऑब्जेक्ट और पोर्ट नंबर को नेटिव लेयर को जेएनआई कॉल से रिकॉर्ड करता है. इसके बाद, नेटिव लेयर को इसके लिए, नीचे दिया गया तरीका अपनाएं:

  1. हर Java MidiDevice के लिए, AMidiDevice_fromJava() का इस्तेमाल करके AMidiDevice पाएं.
  2. AMidiDevice से AMidiInputPort और/या AMidiOutputPort पाएं AMidiInputPort_open() और/या AMidiOutputPort_open() के साथ.
  3. एमआईडीआई डेटा भेजने और/या पाने के लिए, मिले पोर्ट का इस्तेमाल करें.

Amidi को बंद करें

जब ऐसा नहीं होता है, तब Java ऐप्लिकेशन को संसाधनों को रिलीज़ करने के लिए नेटिव लेयर को सिग्नल देना चाहिए ज़्यादा देर तक चालू रखता है. ऐसा इसलिए हो सकता था, क्योंकि एमआईडीआई डिवाइस डिसकनेक्ट हो गया है या ऐप्लिकेशन बंद हो रहा है.

एमआईडीआई संसाधनों को रिलीज़ करने के लिए, आपके कोड को ये काम करने होंगे:

  1. एमआईडीआई पोर्ट में पढ़ना और/या लिखना बंद करना. अगर रीडिंग का इस्तेमाल किया जा रहा था, तो इनपुट के लिए, पोल करने के लिए थ्रेड (नीचे पोलिंग लूप लागू करना लेख पढ़ें), थ्रेड को बंद करो.
  2. इसके साथ सभी खुले हुए AMidiInputPort और/या AMidiOutputPort ऑब्जेक्ट बंद करें AMidiInputPort_close() और/या AMidiOutputPort_close() फ़ंक्शन.
  3. AMidiDevice_release() का इस्तेमाल करके AMidiDevice रिलीज़ करें.

एमआईडीआई डेटा पाना

एमआईडीआई ऐप्लिकेशन का एक सामान्य उदाहरण, "वर्चुअल सिंथेसाइज़र" है जिसे ऑडियो सिंथेसिस को कंट्रोल करने के लिए, एमआईडीआई की परफ़ॉर्मेंस का डेटा मिलता है.

एमआईडीआई का डेटा एसिंक्रोनस तरीके से मिलता है. इसलिए, एमआईडीआई को पढ़ना सबसे अच्छा है जो एक अलग थ्रेड में होता है, जो एक या एमआईडीआई आउटपुट पोर्ट को लगातार पोल करता है. यह यह बैकग्राउंड थ्रेड या ऑडियो थ्रेड हो सकती है. एमिडी पोर्ट से पढ़ते समय ब्लॉक नहीं होता है और इसलिए इसे अंदर इस्तेमाल करना सुरक्षित है एक ऑडियो कॉलबैक.

MidiDevice और उसके आउटपुट पोर्ट सेट अप करें

कोई ऐप्लिकेशन, डिवाइस के आउटपुट पोर्ट से एमआईडीआई के डेटा को पढ़ता है. Java साइड आपके ऐप्लिकेशन के डेवलपर को यह तय करना होगा कि कौनसा डिवाइस और पोर्ट इस्तेमाल करना है.

यह स्निपेट Android की एमआईडीआई की सेवा से MidiManager और इस सेवा को खोलता है मिलने वाले पहले डिवाइस के लिए MidiDevice. जब MidiDevice जब कॉलबैक किया जाता है, तो MidiManager.OnDeviceOpenedListener(). इसका onDeviceOpened तरीका लिसनर को कॉल किया जाता है, जो आउटपुट पोर्ट 0 खोलने के लिए startReadingMidi() को कॉल करता है डिवाइस पर. यह एक JNI फ़ंक्शन है, जिसकी परिभाषा AppMidiManager.cpp में दी गई है. यह फ़ंक्शन है अगले स्निपेट में बताया गया है.

Kotlin

//AppMidiManager.kt
class AppMidiManager(context : Context) {
  private external fun startReadingMidi(midiDevice: MidiDevice,
  portNumber: Int)
  val mMidiManager : MidiManager = context.getSystemService(Context.MIDI_SERVICE) as MidiManager

  init {
    val midiDevices = getMidiDevices(true) // method defined in snippet above
    if (midiDevices.isNotEmpty()){
      midiManager.openDevice(midiDevices[0], {
        startReadingMidi(it, 0)
      }, null)
    }
  }
}

Java

//AppMidiManager.java
public class AppMidiManager {
  private native void startReadingMidi(MidiDevice device, int portNumber);
  private MidiManager mMidiManager;
  AppMidiManager(Context context){
    mMidiManager = (MidiManager)
      context.getSystemService(Context.MIDI_SERVICE);
    List midiDevices = getMidiDevices(true); // method defined in snippet above
    if (midiDevices.size() > 0){
      mMidiManager.openDevice(midiDevices.get(0),
        new MidiManager.OnDeviceOpenedListener() {
        @Override
        public void onDeviceOpened(MidiDevice device) {
          startReadingMidi(device, 0);
        }
      },null);
    }
  }
}

नेटिव कोड, Java-साइड एमआईडीआई डिवाइस और उसके पोर्ट का अनुवाद AMidi फ़ंक्शन में इस्तेमाल किए गए रेफ़रंस.

यह रहा जेएनआई फ़ंक्शन, जो कॉल करके AMidiDevice बनाता है AMidiDevice_fromJava(). इसके बाद, AMidiOutputPort_open() को कॉल करता है डिवाइस पर एक आउटपुट पोर्ट:

AppMidiManager.cpp

AMidiDevice midiDevice;
static pthread_t readThread;

static const AMidiDevice* midiDevice = AMIDI_INVALID_HANDLE;
static std::atomic<AMidiOutputPort*> midiOutputPort(AMIDI_INVALID_HANDLE);

void Java_com_nativemidiapp_AppMidiManager_startReadingMidi(
        JNIEnv* env, jobject, jobject deviceObj, jint portNumber) {
    AMidiDevice_fromJava(j_env, deviceObj, &midiDevice);

    AMidiOutputPort* outputPort;
    int32_t result =
      AMidiOutputPort_open(midiDevice, portNumber, &outputPort);
    // check for errors...

    // Start read thread
    int pthread_result =
      pthread_create(&readThread, NULL, readThreadRoutine, NULL);
    // check for errors...

}

पोलिंग लूप लागू करना

जिन ऐप्लिकेशन को एमआईडीआई डेटा मिलता है उन्हें आउटपुट पोर्ट को पोल करना होगा और तब जवाब देना होगा, जब AMidiOutputPort_receive(), शून्य से बड़ी संख्या दिखाता है.

एमआईडीआई स्कोप जैसे कम बैंडविड्थ वाले ऐप्लिकेशन के लिए, कम प्राथमिकता वाले ऐप्लिकेशन की मदद से पोल किया जा सकता है बैकग्राउंड में थ्रेड (सही नींद के साथ).

ऐसे ऐप्लिकेशन जो ऑडियो जनरेट करते हैं और रीयलटाइम में बेहतर परफ़ॉर्म करते हैं ज़रूरी है, तो आप ऑडियो जनरेट करने के मुख्य कॉलबैक में पोल कर सकते हैं ( OpenSL ES के लिए BufferQueue कॉलबैक, AAudio में AudioStream डेटा कॉलबैक). AMidiOutputPort_receive(), ब्लॉक नहीं कर रहा है, इसलिए बहुत कम परफ़ॉर्मेंस पर असर.

startReadingMidi() फ़ंक्शन से कॉल किया गया readThreadRoutine() फ़ंक्शन ऊपर कुछ इस तरह दिख सकता है:

void* readThreadRoutine(void * /*context*/) {
    uint8_t inDataBuffer[SIZE_DATABUFFER];
    int32_t numMessages;
    uint32_t opCode;
    uint64_t timestamp;
    reading = true;
    while (reading) {
        AMidiOutputPort* outputPort = midiOutputPort.load();
        numMessages =
              AMidiOutputPort_receive(outputPort, &opCode, inDataBuffer,
                                sizeof(inDataBuffer), &timestamp);
        if (numMessages >= 0) {
            if (opCode == AMIDI_OPCODE_DATA) {
                // Dispatch the MIDI data….
            }
        } else {
            // some error occurred, the negative numMessages is the error code
            int32_t errorCode = numMessages;
        }
  }
}

नेटिव ऑडियो एपीआई (जैसे, OpenSL ES या AAudio), इस तरह से ऑडियो जनरेट करने वाले कॉलबैक में MIDI रिसीव कोड जोड़ सकता है:

void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void */*context*/)
{
    uint8_t inDataBuffer[SIZE_DATABUFFER];
    int32_t numMessages;
    uint32_t opCode;
    uint64_t timestamp;

    // Read MIDI Data
    numMessages = AMidiOutputPort_receive(outputPort, &opCode, inDataBuffer,
        sizeof(inDataBuffer), &timestamp);
    if (numMessages >= 0 && opCode == AMIDI_OPCODE_DATA) {
        // Parse and respond to MIDI data
        // ...
    }

    // Generate Audio…
    // ...
}

नीचे दिया गया डायग्राम, एमआईडीआई रीडिंग ऐप्लिकेशन का फ़्लो दिखाता है:

एमआईडीआई डेटा भेजें

एमआईडीआई राइटिंग ऐप्लिकेशन का एक सामान्य उदाहरण, एमआईडीआई कंट्रोलर या सीक्वेंसर है.

MidiDevice और उसके इनपुट पोर्ट सेट अप करें

कोई ऐप्लिकेशन, एमआईडीआई डिवाइस के इनपुट पोर्ट में आउटगोइंग एमआईडीआई डेटा लिखता है. Java साइड आपके ऐप्लिकेशन को यह तय करना होगा कि किस एमआईडीआई डिवाइस और पोर्ट का इस्तेमाल करना है.

नीचे दिया गया सेटअप कोड, ऊपर दिए गए रिसीविंग उदाहरण का वैरिएशन है. यह MidiManager बनाता है एमआईडीआई की सेवा का इस्तेमाल करता है. इसके बाद, उसे सबसे पहलेMidiDevice खुलता है और डिवाइस पर पहला इनपुट पोर्ट खोलने के लिए, startWritingMidi() पर कॉल करता है. यह है AppMidiManager.cpp में JNI कॉल तय किया गया. फ़ंक्शन के बारे में यहां बताया गया है अगला स्निपेट.

Kotlin

//AppMidiManager.kt
class AppMidiManager(context : Context) {
  private external fun startWritingMidi(midiDevice: MidiDevice,
  portNumber: Int)
  val mMidiManager : MidiManager = context.getSystemService(Context.MIDI_SERVICE) as MidiManager

  init {
    val midiDevices = getMidiDevices(false) // method defined in snippet above
    if (midiDevices.isNotEmpty()){
      midiManager.openDevice(midiDevices[0], {
        startWritingMidi(it, 0)
      }, null)
    }
  }
}

Java

//AppMidiManager.java
public class AppMidiManager {
  private native void startWritingMidi(MidiDevice device, int portNumber);
  private MidiManager mMidiManager;

  AppMidiManager(Context context){
    mMidiManager = (MidiManager)
      context.getSystemService(Context.MIDI_SERVICE);
    List midiDevices = getMidiDevices(false); // method defined in snippet above
    if (midiDevices.size() > 0){
      mMidiManager.openDevice(midiDevices.get(0),
        new MidiManager.OnDeviceOpenedListener() {
        @Override
        public void onDeviceOpened(MidiDevice device) {
          startWritingMidi(device, 0);
        }
      },null);
    }
  }
}

यह रहा जेएनआई फ़ंक्शन, जो कॉल करके AMidiDevice बनाता है AMidiDevice_fromJava(). इसके बाद, AMidiInputPort_open() को कॉल करता है डिवाइस पर एक इनपुट पोर्ट:

AppMidiManager.cpp

void Java_com_nativemidiapp_AppMidiManager_startWritingMidi(
       JNIEnv* env, jobject, jobject midiDeviceObj, jint portNumber) {
   media_status_t status;
   status = AMidiDevice_fromJava(
     env, midiDeviceObj, &sNativeSendDevice);
   AMidiInputPort *inputPort;
   status = AMidiInputPort_open(
     sNativeSendDevice, portNumber, &inputPort);

   // store it in a global
   sMidiInputPort = inputPort;
}

एमआईडीआई डेटा भेजें

आउटगोइंग एमआईडीआई डेटा का समय अच्छी तरह से समझा और कंट्रोल किया जाता है डेटा का ट्रांसमिशन, MIDI ऐप्लिकेशन के मुख्य थ्रेड में किया जा सकता है. हालांकि, परफ़ॉर्मेंस की वजहों (जैसा कि सीक्वेंसर में होता है) से, जनरेशन और एमआईडीआई का ट्रांसमिशन एक अलग थ्रेड में किया जा सकता है.

ऐप्लिकेशन जब भी ज़रूरी हो, तब एमआईडीआई डेटा भेज सकते हैं. ध्यान दें कि AMidi तब ब्लॉक करता है, जब डेटा लिखने में मदद करता है.

यहां एक जेएनआई तरीके का उदाहरण दिया गया है, जिसे एमआईडीआई कमांड का बफ़र मिलता है और इसे लिखता है:

void Java_com_nativemidiapp_TBMidiManager_writeMidi(
JNIEnv* env, jobject, jbyteArray data, jint numBytes) {
   jbyte* bufferPtr = env->GetByteArrayElements(data, NULL);
   AMidiInputPort_send(sMidiInputPort, (uint8_t*)bufferPtr, numBytes);
   env->ReleaseByteArrayElements(data, bufferPtr, JNI_ABORT);
}

नीचे दिया गया डायग्राम, एमआईडीआई राइटिंग ऐप्लिकेशन का फ़्लो दिखाता है:

कॉलबैक

हालांकि, यह पूरी तरह से AMidi सुविधा नहीं है, लेकिन आपके नेटिव कोड को डेटा पास करना पड़ सकता है वापस ले जाएं (उदाहरण के लिए, यूज़र इंटरफ़ेस को अपडेट करने के लिए). ऐसा करने के लिए, आपको Java साइड और नेटिव लेयर में कोड लिखें:

  • Java साइड पर कॉलबैक का तरीका बनाएं.
  • कोई ऐसा JNI फ़ंक्शन लिखें जिसमें कॉलबैक शुरू करने के लिए ज़रूरी जानकारी सेव हो.

कॉलबैक का समय आने पर, आपका नेटिव कोड बनाया जा सकता है

यहां Java-साइड कॉलबैक तरीका दिया गया है, onNativeMessageReceive():

Kotlin

//MainActivity.kt
private fun onNativeMessageReceive(message: ByteArray) {
  // Messages are received on some other thread, so switch to the UI thread
  // before attempting to access the UI
  runOnUiThread { showReceivedMessage(message) }
}

Java

//MainActivity.java
private void onNativeMessageReceive(final byte[] message) {
        // Messages are received on some other thread, so switch to the UI thread
        // before attempting to access the UI
        runOnUiThread(new Runnable() {
            public void run() {
                showReceivedMessage(message);
            }
        });
}

यहां JNI फ़ंक्शन के लिए C कोड दिया गया है, जो MainActivity.onNativeMessageReceive(). Java MainActivity कॉल स्टार्टअप पर initNative():

MainActivity.cpp

/**
 * Initializes JNI interface stuff, specifically the info needed to call back into the Java
 * layer when MIDI data is received.
 */
JNICALL void Java_com_example_nativemidi_MainActivity_initNative(JNIEnv * env, jobject instance) {
    env->GetJavaVM(&theJvm);

    // Setup the receive data callback (into Java)
    jclass clsMainActivity = env->FindClass("com/example/nativemidi/MainActivity");
    dataCallbackObj = env->NewGlobalRef(instance);
    midDataCallback = env->GetMethodID(clsMainActivity, "onNativeMessageReceive", "([B)V");
}

Java पर डेटा वापस भेजने पर, नेटिव कोड कॉलबैक को हासिल करता है पॉइंटर और कॉलबैक बनाता है:

AppMidiManager.cpp

// The Data Callback
extern JavaVM* theJvm;              // Need this for allocating data buffer for...
extern jobject dataCallbackObj;     // This is the (Java) object that implements...
extern jmethodID midDataCallback;   // ...this callback routine

static void SendTheReceivedData(uint8_t* data, int numBytes) {
    JNIEnv* env;
    theJvm->AttachCurrentThread(&env, NULL);
    if (env == NULL) {
        LOGE("Error retrieving JNI Env");
    }

    // Allocate the Java array and fill with received data
    jbyteArray ret = env->NewByteArray(numBytes);
    env->SetByteArrayRegion (ret, 0, numBytes, (jbyte*)data);

    // send it to the (Java) callback
    env->CallVoidMethod(dataCallbackObj, midDataCallback, ret);
}

अन्य संसाधन