AMidi एपीआई, Android NDK r20b और उसके बाद के वर्शन में उपलब्ध है. इसकी मदद से, ऐप्लिकेशन डेवलपर C/C++ कोड के ज़रिए MIDI डेटा भेज और पा सकते हैं.
Android MIDI ऐप्लिकेशन, आम तौर पर
midi एपीआई का इस्तेमाल करके
Android MIDI सेवा से कम्यूनिकेट करते हैं. MIDI ऐप्लिकेशन, एक या उससे ज़्यादा
MidiDevice ऑब्जेक्ट को खोजने, खोलने, और बंद करने के लिए मुख्य रूप से
MidiManager पर निर्भर करते हैं. साथ ही, डिवाइस के
MIDI इनपुट
और आउटपुट पोर्ट के ज़रिए, हर डिवाइस से डेटा पास करते हैं:
AMidi का इस्तेमाल करने पर, JNI कॉल की मदद से, MidiDevice का पता, नेटिव कोड लेयर को पास किया जाता है. इसके बाद, AMidi, AMidiDevice
का रेफ़रंस बनाता है. इसमें MidiDevice की ज़्यादातर सुविधाएं होती हैं. आपका नेटिव कोड,
AMidi के उन फ़ंक्शन का इस्तेमाल करता है जो
सीधे AMidiDevice से कम्यूनिकेट करते हैं. AMidiDevice, सीधे
MIDI सेवा से कनेक्ट होता है:
AMidi कॉल का इस्तेमाल करके, अपने ऐप्लिकेशन के C/C++ ऑडियो/कंट्रोल लॉजिक को MIDI ट्रांसमिशन के साथ इंटिग्रेट किया जा सकता है. आपके ऐप्लिकेशन के Java साइड पर, JNI कॉल या कॉलबैक की ज़रूरत कम होती है. उदाहरण के लिए, C कोड में लागू किया गया डिजिटल सिंथेसाइज़र, Java साइड से इवेंट भेजने के लिए JNI कॉल का इंतज़ार करने के बजाय, सीधे AMidiDevice से की इवेंट पा सकता है. इसके अलावा, एल्गोरिदम से कंपोज़ करने की प्रोसेस, की इवेंट ट्रांसमिट करने के लिए Java साइड पर वापस कॉल किए बिना, सीधे AMidiDevice को MIDI परफ़ॉर्मेंस भेज सकती है.
हालांकि, AMidi, MIDI डिवाइसों से सीधे कनेक्शन को बेहतर बनाता है, लेकिन ऐप्लिकेशन को MidiDevice ऑब्जेक्ट खोजने और खोलने के लिए, अब भी MidiManager का इस्तेमाल करना होगा. इसके बाद, AMidi काम कर सकता है.
कभी-कभी, आपको यूज़र इंटरफ़ेस (यूआई) लेयर से नेटिव कोड में जानकारी पास करनी पड़ सकती है. उदाहरण के लिए, स्क्रीन पर मौजूद बटन के जवाब में MIDI इवेंट भेजे जाते हैं. इसके लिए, अपने नेटिव लॉजिक के लिए, कस्टम JNI कॉल बनाएं. अगर आपको यूआई को अपडेट करने के लिए डेटा वापस भेजना है, तो सामान्य तरीके से नेटिव लेयर से वापस कॉल किया जा सकता है.
इस दस्तावेज़ में, AMidi नेटिव कोड ऐप्लिकेशन सेट अप करने का तरीका बताया गया है. इसमें MIDI कमांड भेजने और पाने, दोनों के उदाहरण दिए गए हैं. काम करने का पूरा उदाहरण देखने के लिए, NativeMidi सैंपल ऐप्लिकेशन देखें.
AMidi का इस्तेमाल करना
AMidi का इस्तेमाल करने वाले सभी ऐप्लिकेशन के लिए, सेटअप और बंद करने के चरण एक जैसे होते हैं. भले ही, वे MIDI भेजते हों या पाते हों या दोनों.
AMidi शुरू करना
Java साइड पर, ऐप्लिकेशन को MIDI हार्डवेयर का अटैच किया गया हिस्सा खोजना होगा. साथ ही, उससे जुड़ा MidiDevice बनाना होगा और उसे नेटिव कोड में पास करना होगा.
- Java
MidiManagerक्लास की मदद से, MIDI हार्डवेयर खोजें. - MIDI हार्डवेयर से जुड़ा Java
MidiDeviceऑब्जेक्ट पाएं. - JNI की मदद से, Java
MidiDeviceको नेटिव कोड में पास करें.
हार्डवेयर और पोर्ट खोजना
इनपुट और आउटपुट पोर्ट ऑब्जेक्ट, ऐप्लिकेशन के नहीं होते. ये midi डिवाइस पर मौजूद पोर्ट को दिखाते हैं. किसी डिवाइस को MIDI डेटा भेजने के लिए, कोई ऐप्लिकेशन MIDIInputPort खोलता है और फिर उसमें डेटा लिखता है. इसके उलट, डेटा पाने के लिए, कोई ऐप्लिकेशन MIDIOutputPort खोलता है. सही तरीके से काम करने के लिए, ऐप्लिकेशन को यह पक्का करना होगा कि उसके खोले गए पोर्ट सही टाइप के हों. डिवाइस और पोर्ट की खोज, Java साइड पर की जाती है.
यहां एक ऐसा तरीका बताया गया है जिससे हर MIDI डिवाइस को खोजा जा सकता है और उसके पोर्ट देखे जा सकते हैं. इससे, डेटा पाने के लिए आउटपुट पोर्ट वाले डिवाइसों की सूची या डेटा भेजने के लिए इनपुट पोर्ट वाले डिवाइसों की सूची मिलती है. किसी MIDI डिवाइस में, इनपुट पोर्ट और आउटपुट पोर्ट, दोनों हो सकते हैं.
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 ListgetMidiDevices(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 साइड को, JNI कॉल के ज़रिए, एक या उससे ज़्यादा MidiDevice ऑब्जेक्ट और पोर्ट नंबर, नेटिव लेयर को पास करने चाहिए. इसके बाद, नेटिव लेयर को ये चरण पूरे करने चाहिए:
- हर Java
MidiDeviceके लिएAMidiDeviceपाने के लिएAMidiDevice_fromJava()का इस्तेमाल करें. AMidiInputPort_open()और/याAMidiOutputPort_open()की मदद से,AMidiDeviceसेAMidiInputPortऔर/याAMidiOutputPortपाएं.- MIDI डेटा भेजने और/या पाने के लिए, मिले हुए पोर्ट का इस्तेमाल करें.
AMidi बंद करना
Java ऐप्लिकेशन को, MIDI डिवाइस का इस्तेमाल न करने पर, नेटिव लेयर को संसाधन रिलीज़ करने का सिग्नल देना चाहिए. ऐसा MIDI डिवाइस के डिसकनेक्ट होने या ऐप्लिकेशन के बंद होने की वजह से हो सकता है.
MIDI संसाधनों को रिलीज़ करने के लिए, आपके कोड को ये काम करने चाहिए:
- MIDI पोर्ट पर पढ़ना और/या लिखना बंद करें. अगर आपने इनपुट के लिए पोल करने के लिए, पढ़ने वाले थ्रेड का इस्तेमाल किया है, तो थ्रेड को बंद करें. इसके बारे में ज़्यादा जानने के लिए, नीचे पोलिंग लूप लागू करना सेक्शन देखें.
-
AMidiInputPort_close()और/याAMidiOutputPort_close()फ़ंक्शन की मदद से, खुले हुएAMidiInputPortऔर/याAMidiOutputPortऑब्जेक्ट बंद करें. AMidiDevice_release()की मदद से,AMidiDeviceको रिलीज़ करें.
MIDI डेटा पाना
MIDI पाने वाले MIDI ऐप्लिकेशन का एक आम उदाहरण, "वर्चुअल सिंथेसाइज़र" है. यह ऑडियो सिंथेसिस को कंट्रोल करने के लिए, MIDI परफ़ॉर्मेंस डेटा पाता है.
आने वाला MIDI डेटा, एसिंक्रोनस तरीके से मिलता है. इसलिए, MIDI को किसी अलग थ्रेड में पढ़ना सबसे अच्छा है. यह थ्रेड, एक या उससे ज़्यादा MIDI आउटपुट पोर्ट को लगातार पोल करता है. यह बैकग्राउंड थ्रेड या ऑडियो थ्रेड हो सकता है. किसी पोर्ट से पढ़ते समय, AMidi ब्लॉक नहीं होता. इसलिए, इसका इस्तेमाल ऑडियो कॉलबैक में किया जा सकता है.
MidiDevice और उसके आउटपुट पोर्ट सेट अप करना
कोई ऐप्लिकेशन, डिवाइस के आउटपुट पोर्ट से आने वाला MIDI डेटा पढ़ता है. आपके ऐप्लिकेशन के Java साइड को यह तय करना होगा कि किस डिवाइस और पोर्ट का इस्तेमाल करना है.
इस स्निपेट से, Android की MIDI सेवा से MidiManager बनता है. साथ ही, यह मिलने वाले पहले डिवाइस के लिए MidiDevice खोलता है. MidiDevice के खुलने पर, MidiManager.OnDeviceOpenedListener() के इंस्टेंस को कॉलबैक मिलता है. इस लिसनर का onDeviceOpened तरीका कॉल किया जाता है. इसके बाद, डिवाइस पर आउटपुट पोर्ट 0 खोलने के लिए, startReadingMidi() को कॉल किया जाता है. यह AppMidiManager.cpp में तय किया गया JNI फ़ंक्शन है. इस फ़ंक्शन के बारे में, अगले स्निपेट में बताया गया है.
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); ListmidiDevices = 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-साइड MIDI डिवाइस और उसके पोर्ट को, AMidi फ़ंक्शन में इस्तेमाल किए जाने वाले रेफ़रंस में बदलता है.
यहां JNI फ़ंक्शन दिया गया है. यह 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...
}
पोलिंग लूप लागू करना
MIDI डेटा पाने वाले ऐप्लिकेशन को, आउटपुट पोर्ट को पोल करना होगा. साथ ही, AMidiOutputPort_receive() के शून्य से ज़्यादा नंबर दिखाने पर जवाब देना होगा.
कम बैंडविथ वाले ऐप्लिकेशन के लिए, जैसे कि MIDI स्कोप, कम प्राथमिकता वाले बैकग्राउंड थ्रेड में पोल किया जा सकता है. इसके लिए, स्लीप का इस्तेमाल किया जा सकता है.
ऑडियो जनरेट करने वाले और रीयलटाइम परफ़ॉर्मेंस की सख्त ज़रूरत वाले ऐप्लिकेशन के लिए, मुख्य ऑडियो जनरेशन कॉलबैक में पोल किया जा सकता है. जैसे, 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), ×tamp);
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), ×tamp);
if (numMessages >= 0 && opCode == AMIDI_OPCODE_DATA) {
// Parse and respond to MIDI data
// ...
}
// Generate Audio…
// ...
}
यहां दिया गया डायग्राम, MIDI पढ़ने वाले ऐप्लिकेशन के फ़्लो को दिखाता है:

MIDI डेटा भेजना
MIDI लिखने वाले ऐप्लिकेशन का एक आम उदाहरण, MIDI कंट्रोलर या सीक्वेंसर है.
MidiDevice और उसके इनपुट पोर्ट सेट अप करना
कोई ऐप्लिकेशन, MIDI डिवाइस के इनपुट पोर्ट पर आउटगोइंग MIDI डेटा लिखता है. आपके ऐप्लिकेशन के Java साइड को यह तय करना होगा कि किस MIDI डिवाइस और पोर्ट का इस्तेमाल करना है.
यहां दिया गया सेटअप कोड, ऊपर दिए गए पाने के उदाहरण का एक वैरिएंट है. इससे, Android की MIDI सेवा से 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); ListmidiDevices = 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); } } }
यहां JNI फ़ंक्शन दिया गया है. यह 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 डेटा भेजना
आउटगोइंग MIDI डेटा का समय, ऐप्लिकेशन खुद तय और कंट्रोल करता है. इसलिए, डेटा ट्रांसमिशन, MIDI ऐप्लिकेशन के मुख्य थ्रेड में किया जा सकता है. हालांकि, परफ़ॉर्मेंस की वजहों से (जैसे, सीक्वेंसर में), MIDI जनरेशन और ट्रांसमिशन, किसी अलग थ्रेड में किया जा सकता है.
ऐप्लिकेशन, ज़रूरत पड़ने पर MIDI डेटा भेज सकते हैं. ध्यान दें कि डेटा लिखते समय, AMidi ब्लॉक हो जाता है.
यहां JNI के एक ऐसे तरीके का उदाहरण दिया गया है जो MIDI कमांड का बफ़र पाता है और उसे लिखता है:
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);
}
यहां दिया गया डायग्राम, MIDI लिखने वाले ऐप्लिकेशन के फ़्लो को दिखाता है:

कॉलबैक
हालांकि, यह AMidi की सुविधा नहीं है, लेकिन आपके नेटिव कोड को Java साइड पर डेटा वापस पास करना पड़ सकता है. जैसे, यूआई को अपडेट करने के लिए. इसके लिए, आपको 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);
}
अन्य संसाधन
- AMidi का रेफ़रंस
- github पर, Native MIDI का पूरा सैंपल ऐप्लिकेशन देखें.