নেটিভ MIDI API

Amidi API Android NDK r20b এবং পরবর্তী সংস্করণে উপলব্ধ। এটি অ্যাপ ডেভেলপারদের C/C++ কোড সহ MIDI ডেটা পাঠানো এবং গ্রহণ করার ক্ষমতা দেয়।

Android MIDI অ্যাপগুলি সাধারণত Android MIDI পরিষেবার সাথে যোগাযোগ করতে midi API ব্যবহার করে। MIDI অ্যাপ্লিকেশানগুলি প্রাথমিকভাবে এক বা একাধিক MidiDevice অবজেক্ট আবিষ্কার, খুলতে এবং বন্ধ করতে এবং ডিভাইসের MIDI ইনপুট এবং আউটপুট পোর্টের মাধ্যমে প্রতিটি ডিভাইসে এবং থেকে ডেটা পাস করতে MidiManager উপর নির্ভর করে:

আপনি যখন Amidi ব্যবহার করেন তখন আপনি একটি MidiDevice এর ঠিকানা একটি JNI কলের মাধ্যমে নেটিভ কোড লেয়ারে পাস করেন। সেখান থেকে, AMidi একটি AMidiDevice এর একটি রেফারেন্স তৈরি করে যেটিতে একটি MidiDevice এর বেশিরভাগ কার্যকারিতা রয়েছে। আপনার নেটিভ কোড Amidi ফাংশন ব্যবহার করে যা একটি AMidiDevice এর সাথে সরাসরি যোগাযোগ করে AMidiDevice সরাসরি MIDI পরিষেবার সাথে সংযোগ করে:

Amidi কল ব্যবহার করে, আপনি MIDI ট্রান্সমিশনের সাথে আপনার অ্যাপের C/C++ অডিও/কন্ট্রোল লজিককে একীভূত করতে পারেন। আপনার অ্যাপের জাভা সাইডে JNI কল বা কলব্যাকের প্রয়োজন কম। উদাহরণস্বরূপ, C কোডে বাস্তবায়িত একটি ডিজিটাল সিনথেসাইজার জাভা দিক থেকে ইভেন্টগুলি পাঠানোর জন্য একটি JNI কলের জন্য অপেক্ষা না করে সরাসরি একটি AMidiDevice থেকে মূল ইভেন্টগুলি গ্রহণ করতে পারে। অথবা একটি অ্যালগরিদমিক কম্পোজিং প্রক্রিয়া মূল ইভেন্টগুলি প্রেরণ করতে জাভা সাইডে ব্যাক আপ না করে সরাসরি একটি MIDI পারফরম্যান্স একটি AMidiDevice এ পাঠাতে পারে।

যদিও Amidi MIDI ডিভাইসগুলির সাথে সরাসরি সংযোগ উন্নত করে, তবুও অ্যাপগুলিকে MidiDevice অবজেক্টগুলি আবিষ্কার ও খুলতে MidiManager ব্যবহার করতে হবে। আমিদি সেখান থেকে নিতে পারে।

কখনও কখনও আপনাকে UI স্তর থেকে নেটিভ কোডে তথ্য প্রেরণ করতে হতে পারে। উদাহরণস্বরূপ, যখন MIDI ইভেন্টগুলি স্ক্রিনের বোতামগুলির প্রতিক্রিয়া হিসাবে পাঠানো হয়। এটি করতে আপনার স্থানীয় যুক্তিতে কাস্টম JNI কল তৈরি করুন। UI আপডেট করার জন্য যদি আপনাকে ডেটা ফেরত পাঠাতে হয়, আপনি যথারীতি নেটিভ লেয়ার থেকে কল ব্যাক করতে পারেন।

এই ডকুমেন্টটি দেখায় কিভাবে একটি AMidi নেটিভ কোড অ্যাপ সেট আপ করতে হয়, MIDI কমান্ড পাঠানো এবং গ্রহণ করার উদাহরণ দেয়। একটি সম্পূর্ণ কাজের উদাহরণের জন্য NativeMidi নমুনা অ্যাপটি দেখুন।

এমিডি ব্যবহার করুন

Amidi ব্যবহার করে এমন সব অ্যাপের সেটআপ এবং বন্ধ করার ধাপ একই থাকে, তারা MIDI পাঠায় বা গ্রহণ করুক বা উভয়ই করুক।

এমিডি শুরু করুন

জাভা দিকে, অ্যাপটিকে অবশ্যই MIDI হার্ডওয়্যারের একটি সংযুক্ত অংশ আবিষ্কার করতে হবে, একটি সংশ্লিষ্ট MidiDevice তৈরি করতে হবে এবং এটিকে নেটিভ কোডে পাঠাতে হবে।

  1. Java MidiManager ক্লাসের সাথে MIDI হার্ডওয়্যার আবিষ্কার করুন।
  2. MIDI হার্ডওয়্যারের সাথে সম্পর্কিত একটি Java MidiDevice অবজেক্ট পান।
  3. জাভা MidiDevice JNI দিয়ে নেটিভ কোডে পাস করুন।

হার্ডওয়্যার এবং পোর্ট আবিষ্কার করুন

ইনপুট এবং আউটপুট পোর্ট অবজেক্ট অ্যাপের অন্তর্গত নয়। তারা মিডি ডিভাইসে পোর্ট উপস্থাপন করে। একটি ডিভাইসে MIDI ডেটা পাঠাতে, একটি অ্যাপ একটি MIDIInputPort খোলে এবং তারপরে এটিতে ডেটা লেখে। বিপরীতভাবে, ডেটা গ্রহণ করতে, একটি অ্যাপ একটি MIDIOutputPort খোলে। সঠিকভাবে কাজ করার জন্য, অ্যাপটিকে অবশ্যই নিশ্চিত হতে হবে যে এটি যে পোর্টগুলি খোলে তা সঠিক প্রকার। ডিভাইস এবং পোর্ট আবিষ্কার জাভা পাশে সম্পন্ন করা হয়.

এখানে একটি পদ্ধতি যা প্রতিটি MIDI ডিভাইস আবিষ্কার করে এবং এর পোর্টগুলি দেখে। এটি ডেটা গ্রহণের জন্য আউটপুট পোর্ট সহ ডিভাইসগুলির একটি তালিকা বা ডেটা প্রেরণের জন্য ইনপুট পোর্ট সহ ডিভাইসগুলির একটি তালিকা প্রদান করে। একটি MIDI ডিভাইসে ইনপুট পোর্ট এবং আউটপুট পোর্ট উভয়ই থাকতে পারে।

কোটলিন

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

জাভা

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 লাইব্রেরির বিপরীতে লিঙ্ক করতে হবে। এ দুটিই পাওয়া যাবে অ্যান্ড্রয়েড এনডিকেতে

জাভা সাইডকে JNI কলের মাধ্যমে এক বা একাধিক MidiDevice অবজেক্ট এবং পোর্ট নম্বর নেটিভ লেয়ারে পাঠাতে হবে। নেটিভ লেয়ারটিকে নিম্নলিখিত পদক্ষেপগুলি সম্পাদন করা উচিত:

  1. প্রতিটি জাভা MidiDevice এর জন্য AMidiDevice_fromJava() ব্যবহার করে একটি AMidiDevice পান।
  2. AMidiInputPort_open() এবং/অথবা AMidiOutputPort_open() দিয়ে AMidiDevice থেকে একটি AMidiInputPort এবং/অথবা AMidiOutputPort পান।
  3. MIDI ডেটা পাঠাতে এবং/অথবা গ্রহণ করতে প্রাপ্ত পোর্টগুলি ব্যবহার করুন।

এমিদি থামো

জাভা অ্যাপটি যখন আর MIDI ডিভাইস ব্যবহার করছে না তখন রিসোর্স রিলিজ করার জন্য নেটিভ লেয়ারকে সংকেত দিতে হবে। এটি হতে পারে কারণ MIDI ডিভাইসটি সংযোগ বিচ্ছিন্ন হয়েছে বা অ্যাপটি প্রস্থান করা হচ্ছে৷

MIDI সম্পদ প্রকাশ করতে, আপনার কোড এই কাজগুলি সম্পাদন করা উচিত:

  1. MIDI পোর্টে পড়া এবং/অথবা লেখা বন্ধ করুন। আপনি যদি ইনপুটের জন্য পোল করার জন্য একটি রিডিং থ্রেড ব্যবহার করেন (নীচে একটি পোলিং লুপ প্রয়োগ করুন ), থ্রেডটি বন্ধ করুন।
  2. AMidiInputPort_close() এবং/অথবা AMidiOutputPort_close() ফাংশন সহ যেকোনো খোলা AMidiInputPort এবং/অথবা AMidiOutputPort অবজেক্ট বন্ধ করুন।
  3. AMidiDevice_release() দিয়ে AMidiDevice রিলিজ করুন।

MIDI ডেটা পান

একটি MIDI অ্যাপের একটি সাধারণ উদাহরণ যা MIDI গ্রহণ করে একটি "ভার্চুয়াল সিনথেসাইজার" যা অডিও সংশ্লেষণ নিয়ন্ত্রণ করতে MIDI কর্মক্ষমতা ডেটা গ্রহণ করে।

ইনকামিং MIDI ডেটা অ্যাসিঙ্ক্রোনাসভাবে গৃহীত হয়। অতএব, একটি পৃথক থ্রেডে MIDI পড়া ভাল যা ক্রমাগত একটি বা MIDI আউটপুট পোর্টগুলি পোল করে৷ এটি একটি পটভূমি থ্রেড, বা একটি অডিও থ্রেড হতে পারে. পোর্ট থেকে পড়ার সময় Amidi ব্লক করে না এবং তাই অডিও কলব্যাকের ভিতরে ব্যবহার করা নিরাপদ।

একটি MidiDevice এবং এর আউটপুট পোর্ট সেট আপ করুন

একটি অ্যাপ একটি ডিভাইসের আউটপুট পোর্ট থেকে ইনকামিং MIDI ডেটা পড়ে৷ কোন ডিভাইস এবং পোর্ট ব্যবহার করতে হবে তা আপনার অ্যাপের জাভা সাইডকে অবশ্যই নির্ধারণ করতে হবে।

এই স্নিপেটটি Android এর MIDI পরিষেবা থেকে MidiManager তৈরি করে এবং এটি যে প্রথম ডিভাইসটি খুঁজে পায় তার জন্য একটি MidiDevice খোলে৷ যখন MidiDevice খোলা হয় তখন MidiManager.OnDeviceOpenedListener() এর একটি উদাহরণে একটি কলব্যাক প্রাপ্ত হয়। এই শ্রোতার onDeviceOpened পদ্ধতিটিকে বলা হয় যা তারপর startReadingMidi() ডিভাইসে আউটপুট পোর্ট 0 খুলতে কল করে। এটি AppMidiManager.cpp এ সংজ্ঞায়িত একটি JNI ফাংশন। এই ফাংশনটি পরবর্তী স্নিপেটে ব্যাখ্যা করা হয়েছে।

কোটলিন

//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)
    }
  }
}

জাভা

//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);
    }
  }
}

নেটিভ কোড জাভা-সাইড MIDI ডিভাইস এবং এর পোর্টগুলিকে Amidi ফাংশন দ্বারা ব্যবহৃত রেফারেন্সে অনুবাদ করে।

এখানে JNI ফাংশন যা AMidiDevice_fromJava() কল করে AMidiDevice তৈরি করে এবং তারপর 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-তে অডিওস্ট্রিম ডেটা কলব্যাক) ভোট দিতে পারেন। যেহেতু 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;
        }
  }
}

একটি নেটিভ অডিও API ব্যবহার করে একটি অ্যাপ (যেমন 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
    // ...
}

নিম্নলিখিত চিত্রটি একটি MIDI রিডিং অ্যাপের প্রবাহকে চিত্রিত করে:

MIDI ডেটা পাঠান

একটি MIDI লেখার অ্যাপের একটি সাধারণ উদাহরণ হল একটি MIDI কন্ট্রোলার বা সিকোয়েন্সার।

একটি MidiDevice এবং এর ইনপুট পোর্ট সেট আপ করুন

একটি অ্যাপ একটি MIDI ডিভাইসের ইনপুট পোর্টে বহির্গামী MIDI ডেটা লেখে। কোন MIDI ডিভাইস এবং পোর্ট ব্যবহার করতে হবে তা আপনার অ্যাপের জাভা সাইডকে অবশ্যই নির্ধারণ করতে হবে।

নীচের এই সেটআপ কোডটি উপরের প্রাপ্ত উদাহরণের একটি বৈচিত্র। এটি Android এর MIDI পরিষেবা থেকে MidiManager তৈরি করে। এটি তারপর প্রথম MidiDevice খোলে এবং ডিভাইসে প্রথম ইনপুট পোর্ট খুলতে startWritingMidi() কল করে। এটি AppMidiManager.cpp এ সংজ্ঞায়িত একটি JNI কল। ফাংশনটি পরবর্তী স্নিপেটে ব্যাখ্যা করা হয়েছে।

কোটলিন

//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)
    }
  }
}

জাভা

//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);
    }
  }
}

এখানে JNI ফাংশন যা AMidiDevice_fromJava() কল করে AMidiDevice তৈরি করে এবং তারপর 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 ডেটা পাঠাতে পারে৷ মনে রাখবেন যে ডেটা লেখার সময় এমিডি ব্লক করে।

এখানে একটি উদাহরণ 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 বৈশিষ্ট্য নয়, আপনার নেটিভ কোড জাভা সাইডে ডেটা পাস করতে হতে পারে (উদাহরণস্বরূপ UI আপডেট করতে)। এটি করার জন্য, আপনাকে অবশ্যই জাভা সাইডে এবং নেটিভ লেয়ারে কোড লিখতে হবে:

  • জাভা পাশে একটি কলব্যাক পদ্ধতি তৈরি করুন।
  • একটি JNI ফাংশন লিখুন যা কলব্যাক আহ্বান করার জন্য প্রয়োজনীয় তথ্য সংরক্ষণ করে।

যখন এটি কলব্যাক করার সময়, আপনার নেটিভ কোড তৈরি করতে পারে

এখানে জাভা-সাইড কলব্যাক পদ্ধতি, onNativeMessageReceive() :

কোটলিন

//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) }
}

জাভা

//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");
}

যখন জাভাতে ডেটা ফেরত পাঠানোর সময় হয়, তখন নেটিভ কোড কলব্যাক পয়েন্টারগুলি পুনরুদ্ধার করে এবং কলব্যাক তৈরি করে:

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);
}

অতিরিক্ত সম্পদ