package QZARelated; import anywheresoftware.b4a.BA; import anywheresoftware.b4a.keywords.DateTime; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.ShortBuffer; import com.sun.jna.Memory; import com.sun.jna.Pointer; import com.un4seen.bass.BASS; import com.un4seen.bass.BASS.BASS_DEVICEINFO; import com.un4seen.bass.BASS.RECORDPROC; import com.un4seen.bass.BASS.bassconstant; @BA.ShortName("QZA_UDP_MicrophoneSender") @BA.Events(values= { "recordstart(timetick as long)", "recordstop(timetick as long)", "recordpcm(bb() as byte)", "recordvu(value as int)", "startstreaming(toip as string)", "stopstreaming(toip as string)", "log(msg as string)" }) public class QZA_UDP_MicrophoneSender extends DataSender implements DataSenderEvent { private BASS bass; private String event = ""; private BA ba; private boolean inited = false; private boolean recording = false; private boolean need_recordstart_event = false; private boolean need_recordstop_event = false; private boolean need_recordpcm_event = false; private boolean need_recordvu_event = false; private boolean need_log_event = false; private boolean need_startstreaming_event = false; private boolean need_stopstreaming_event = false; private int recorddev = 0; private int recordhandle = 0; private long byteread = 0; private RECORDPROC recproc = null; private Object myobject; private float volfactor = 1.0f; public void Initialize(BA bax, String eventname) { this.ba = bax; this.event = eventname; myobject = this; if (ba!=null) { if (!event.isEmpty()) { need_log_event = bax.subExists(event+"_log"); need_recordpcm_event = bax.subExists(event+"_recordpcm"); need_recordstart_event = bax.subExists(event+"_recordstart"); need_recordstop_event = bax.subExists(event+"_recordstop"); need_recordvu_event = bax.subExists(event+"_recordvu"); need_startstreaming_event = bax.subExists(event+"_startstreaming"); need_stopstreaming_event = bax.subExists(event+"_stopstreaming"); } } bass = new BASS(); if (bass.BASS_GetVersion()!=0) { this.inited = true; } // auto close kalau java program closed Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { if (inited) { StopRecording(); BASS.BASS_Free(); } } }); } /** * Check if QZA_UDP_MicrophoneSender is initialized or not * @return true if initialized */ public boolean IsInitialized() { return inited; } /** * Check if Recording Device with device-ID is exist * @param devid : 0 = first recorder device, 1 = second device , .. * @return true if device exist */ public boolean Check_Recording_Device_exists(int devid) { if (inited) { if (devid>=0) { BASS_DEVICEINFO dev = new BASS_DEVICEINFO(); if (bass.BASS_RecordGetDeviceInfo(devid, dev)) { dev.read(); if (dev.name!=null) { if (!dev.name.isEmpty()) { return true; } } } } } return false; } /** * Start Recording in mono * @param devid : recording device, start from 0 = first, 1 = second device * @param samplingrate : sampling rate * @param bytescount : how many pcm bytes captured for raising event recordpcm * @return true if recording started */ public boolean StartRecording(int devid, int samplingrate, int bytescount) { recorddev = -1; if (devid < 0) return false; if (inited) { if (!bass.BASS_RecordSetDevice(devid)) { // tidak bisa recordsetdevice switch(bass.BASS_ErrorGetCode()) { case bassconstant.BASS_ERROR_DEVICE : // tidak ada device ini raise_bass_log_event("Start_Recording","RecordSetDevice "+devid); return false; case bassconstant.BASS_ERROR_INIT : // belum init, maka di-init aja if (!bass.BASS_RecordInit(devid)) { // tidak bisa init raise_bass_log_event("Start_Recording","RecordInit "+devid); return false; } break; default : // unknown error return false; } } // sampe sini sudah bisa recordsetdevice atau sudah record init // RECPROC tested OK !! set period = 5 ms, ChannelSetAttribute ATTRIB_GRANULE 500 --> hasil 1000 bytes per RECPROC // recproc = create_recordproc(); int flag = 0; // if (recproc!=null) { // // ada recproc, maka nilai flag nya diganti // flag = BASS.Utils.MAKELONG(0, 5); // RecordStart flag = 0, period = 5ms // } int rechd = bass.BASS_RecordStart(samplingrate, 1, flag, recproc, null); if (rechd!=0) { // bisa start recording // maka mulai record thread if (bytescount<1) bytescount = 1000; // basic value Recording_Thread rt = new Recording_Thread(devid, rechd, bytescount); rt.start(); return true; } } return false; } // private RECORDPROC create_recordproc() { // RECORDPROC result = new RECORDPROC() { // // @Override // public boolean RECORDPROC(int handle, Pointer buffer, int length, Pointer user) { // if (handle!=recordhandle) return false; // handle beda, stop recording // if (buffer==null) return false; // ada masalah sama buffer // if (length<1) return false; // ada masalah sama length // // // // // return recording; // kalau recording masih true, lanjut // } // // }; // // return result; // } private class Recording_Thread extends Thread { private final int maxpackagesize; Recording_Thread(final int devid, final int rechd, final int bytecount){ recorddev = devid; recordhandle = rechd; maxpackagesize = bytecount; if (!bass.BASS_ChannelSetAttribute(recordhandle, bassconstant.BASS_ATTRIB_VOL, 1.0f)) { raise_bass_log_event("Recording_Thread","ChannelSetAttribute::ATTRIB_VOL"); } // RECPROC tested OK // attrib granule 500 --> 1000 bytes per recproc // attrib granule 512 --> 1024 bytes per recproc // if (recproc!=null) { // // kalau pake recproc // if (!bass.BASS_ChannelSetAttribute(recordhandle, bassconstant.BASS_ATTRIB_GRANULE, maxpackagesize/2)) { // raise_bass_log_event("Recording_Thread","ChannelSetAttribute ATTRIB GRANULE"); // } // } } public void run() { // sampe sini dah recorstart berhasil recording = true; bytesent_multicast = 0; bytesent_unicast = 0; byteread = 0; raise_recordstart_event(); int bytes_available = 0; while(true) { try { Thread.sleep(2); } catch (InterruptedException e) { break; } if (!recording) break; if (recordhandle==0) break; // kalau pake recproc , di sini gak ngapa-ngapain. // sleep aja, tested OK // ini kalau tidak pakai recproc // tidak pake recproc bytes_available = bass.BASS_ChannelGetData(recordhandle, null, bassconstant.BASS_DATA_AVAILABLE); if (bytes_available == -1) { // ada masalah ChannelGetData raise_bass_log_event("RecordingThread","ChannelGetData::DATA_AVAILABLE"); break; } if (bytes_available < maxpackagesize) continue; // belum cukup data, loop balik Pointer BX = new Memory(maxpackagesize); // alokasi bytes sebanyak maxpackagesize bytes_available = bass.BASS_ChannelGetData(recordhandle, BX, maxpackagesize); if (bytes_available<1) { // ada masalah ChannelGetData raise_bass_log_event("RecordingThread","ChannelGetData::Get PCM Data"); break; } if (bytes_available>0) { byteread+=bytes_available; ByteBuffer buf = BX.getByteBuffer(0, bytes_available); Calculate_VU_and_Volume(buf); byte[] bufbyte = new byte[bytes_available]; buf.get(bufbyte); raise_recordpcm_event(bufbyte); SendPCMData(bufbyte, bytes_available); } } // sampe sini, selesai recording recording = false; recproc = null; // recordproc di null kan raise_recordstop_event(); if (recordhandle!=0) { if (!bass.BASS_ChannelStop(recordhandle)) { raise_bass_log_event("close_all","ChannelStop"); } // ChannelStop di recordhandle , otomatis StreamFree recordhandle = 0; } if (bass.BASS_RecordSetDevice(recorddev)) { if (!bass.BASS_RecordFree()) { raise_bass_log_event("close_all","RecordFree"); } } else raise_bass_log_event("close_all","RecordSetDevice"); } } /** * To Stop recording. * also will close Microphone */ public void StopRecording() { recording = false; } /** * Ambil recordhandle, kalau mau direkam, atau di-encode lagi * @return 0 = not recording, lainnya = recording */ public int getRecord_Handle() { return recordhandle; } /** * Get / Set Recording Volume, or -1 if not recording * 0 = silent, 1.0f = normal, more than 1.0f = amplification */ public float getRecordingVolume() { return volfactor; } public void setRecordingVolume(float value) { if (value<0) value = 0; volfactor = value; } private void Calculate_VU_and_Volume(ByteBuffer bufx) { if (bufx==null) return; ShortBuffer vubuf = bufx.order(ByteOrder.LITTLE_ENDIAN).asShortBuffer(); // calculate volume and vu int temp = 0; short maxvalue = 0; int pos = 0; vubuf.rewind(); while(vubuf.hasRemaining()) { pos = vubuf.position(); // ambil posisi sekarang temp = (int)(vubuf.get() * volfactor); // ambil nilai, dikali volfactor if (temp > Short.MAX_VALUE) temp = (short)Short.MAX_VALUE; // harga mutlak max if (temp < Short.MIN_VALUE) temp = (short)Short.MIN_VALUE; // harga mutlak min vubuf.position(pos); // balik posisi tadi vubuf.put((short)temp); // taruh balik if (temp > maxvalue) maxvalue = (short) temp; // cari nilai maksimum untuk VU } int vuvalue = (int)((maxvalue/32767.0)*100); raise_recordvu_event(vuvalue); } /** * Check if still in recording process * @return true if recording */ public boolean getIsRecording() { return recording; } /** * Get Total Sent bytes in Multicast Mode * @return total pcm byte sent, in long */ public long getPCMByteSent_Multicast() { return bytesent_multicast; } /** * Get Total sent Bytes in Unicast mode * @return total pcm byte sent, in long */ public long getPCMByteSent_Unicast() { return bytesent_unicast; } /** * Get Total Bytes read from Microphone * @return total pcm byte read, in long */ public long getPCMByteRead() { return byteread; } private void raise_recordstart_event() { if (need_recordstart_event) { ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_recordstart", false, new Object[] {DateTime.getNow()}); } } private void raise_recordstop_event() { if (need_recordstop_event) { ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_recordstop", false, new Object[] {DateTime.getNow()}); } } private void raise_recordvu_event(int value) { if (need_recordvu_event) { ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_recordvu", false, new Object[] {value}); } } private void raise_recordpcm_event(byte[] bb) { if (need_recordpcm_event) { ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_recordpcm", false, new Object[] {bb}); } } private void raise_log_event(String msg) { if (need_log_event) { ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_log", false, new Object[] {msg}); } } private void raise_bass_log_event(String function, String command) { int errcode = bass.BASS_ErrorGetCode(); if (errcode==0) return; raise_log_event("BASS Error at Function="+function+" Command="+command+", Code="+BASS.GetBassErrorString(errcode)); } private void raise_startstreaming_event(String value) { if (need_startstreaming_event) ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_startstreaming", false, new Object[] {value}); } private void raise_stopstreaming_event(String value) { if (need_stopstreaming_event) ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_stopstreaming", false, new Object[] {value}); } @BA.Hide @Override public void log(String msg) { // log dari DataSender raise_log_event(msg); } @BA.Hide @Override public void startstreaming(String toip) { raise_startstreaming_event(toip); } @BA.Hide @Override public void stopstreaming(String toip) { raise_stopstreaming_event(toip); } }