462 lines
13 KiB
Java
462 lines
13 KiB
Java
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|