first commit
This commit is contained in:
461
src/QZARelated/QZA_UDP_MicrophoneSender.java
Normal file
461
src/QZARelated/QZA_UDP_MicrophoneSender.java
Normal file
@@ -0,0 +1,461 @@
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user