first commit
This commit is contained in:
BIN
lib/arm64-v8a/libbass.so
Normal file
BIN
lib/arm64-v8a/libbass.so
Normal file
Binary file not shown.
BIN
lib/arm64-v8a/libbass_aac.so
Normal file
BIN
lib/arm64-v8a/libbass_aac.so
Normal file
Binary file not shown.
BIN
lib/arm64-v8a/libbass_ape.so
Normal file
BIN
lib/arm64-v8a/libbass_ape.so
Normal file
Binary file not shown.
BIN
lib/arm64-v8a/libbass_fx.so
Normal file
BIN
lib/arm64-v8a/libbass_fx.so
Normal file
Binary file not shown.
BIN
lib/arm64-v8a/libbass_mpc.so
Normal file
BIN
lib/arm64-v8a/libbass_mpc.so
Normal file
Binary file not shown.
BIN
lib/arm64-v8a/libbass_ssl.so
Normal file
BIN
lib/arm64-v8a/libbass_ssl.so
Normal file
Binary file not shown.
BIN
lib/arm64-v8a/libbass_tta.so
Normal file
BIN
lib/arm64-v8a/libbass_tta.so
Normal file
Binary file not shown.
BIN
lib/arm64-v8a/libbassalac.so
Normal file
BIN
lib/arm64-v8a/libbassalac.so
Normal file
Binary file not shown.
BIN
lib/arm64-v8a/libbassape.so
Normal file
BIN
lib/arm64-v8a/libbassape.so
Normal file
Binary file not shown.
BIN
lib/arm64-v8a/libbassdsd.so
Normal file
BIN
lib/arm64-v8a/libbassdsd.so
Normal file
Binary file not shown.
BIN
lib/arm64-v8a/libbassenc.so
Normal file
BIN
lib/arm64-v8a/libbassenc.so
Normal file
Binary file not shown.
BIN
lib/arm64-v8a/libbassenc_flac.so
Normal file
BIN
lib/arm64-v8a/libbassenc_flac.so
Normal file
Binary file not shown.
BIN
lib/arm64-v8a/libbassenc_mp3.so
Normal file
BIN
lib/arm64-v8a/libbassenc_mp3.so
Normal file
Binary file not shown.
BIN
lib/arm64-v8a/libbassenc_ogg.so
Normal file
BIN
lib/arm64-v8a/libbassenc_ogg.so
Normal file
Binary file not shown.
BIN
lib/arm64-v8a/libbassenc_opus.so
Normal file
BIN
lib/arm64-v8a/libbassenc_opus.so
Normal file
Binary file not shown.
BIN
lib/arm64-v8a/libbassflac.so
Normal file
BIN
lib/arm64-v8a/libbassflac.so
Normal file
Binary file not shown.
BIN
lib/arm64-v8a/libbasshls.so
Normal file
BIN
lib/arm64-v8a/libbasshls.so
Normal file
Binary file not shown.
BIN
lib/arm64-v8a/libbassmidi.so
Normal file
BIN
lib/arm64-v8a/libbassmidi.so
Normal file
Binary file not shown.
BIN
lib/arm64-v8a/libbassmix.so
Normal file
BIN
lib/arm64-v8a/libbassmix.so
Normal file
Binary file not shown.
BIN
lib/arm64-v8a/libbassopus.so
Normal file
BIN
lib/arm64-v8a/libbassopus.so
Normal file
Binary file not shown.
BIN
lib/arm64-v8a/libbasswebm.so
Normal file
BIN
lib/arm64-v8a/libbasswebm.so
Normal file
Binary file not shown.
BIN
lib/arm64-v8a/libbasswv.so
Normal file
BIN
lib/arm64-v8a/libbasswv.so
Normal file
Binary file not shown.
BIN
lib/arm64-v8a/libtags.so
Normal file
BIN
lib/arm64-v8a/libtags.so
Normal file
Binary file not shown.
BIN
lib/armeabi-v7a/libbass.so
Normal file
BIN
lib/armeabi-v7a/libbass.so
Normal file
Binary file not shown.
BIN
lib/armeabi-v7a/libbass_aac.so
Normal file
BIN
lib/armeabi-v7a/libbass_aac.so
Normal file
Binary file not shown.
BIN
lib/armeabi-v7a/libbass_ape.so
Normal file
BIN
lib/armeabi-v7a/libbass_ape.so
Normal file
Binary file not shown.
BIN
lib/armeabi-v7a/libbass_fx.so
Normal file
BIN
lib/armeabi-v7a/libbass_fx.so
Normal file
Binary file not shown.
BIN
lib/armeabi-v7a/libbass_mpc.so
Normal file
BIN
lib/armeabi-v7a/libbass_mpc.so
Normal file
Binary file not shown.
BIN
lib/armeabi-v7a/libbass_ssl.so
Normal file
BIN
lib/armeabi-v7a/libbass_ssl.so
Normal file
Binary file not shown.
BIN
lib/armeabi-v7a/libbass_tta.so
Normal file
BIN
lib/armeabi-v7a/libbass_tta.so
Normal file
Binary file not shown.
BIN
lib/armeabi-v7a/libbassalac.so
Normal file
BIN
lib/armeabi-v7a/libbassalac.so
Normal file
Binary file not shown.
BIN
lib/armeabi-v7a/libbassape.so
Normal file
BIN
lib/armeabi-v7a/libbassape.so
Normal file
Binary file not shown.
BIN
lib/armeabi-v7a/libbassdsd.so
Normal file
BIN
lib/armeabi-v7a/libbassdsd.so
Normal file
Binary file not shown.
BIN
lib/armeabi-v7a/libbassenc.so
Normal file
BIN
lib/armeabi-v7a/libbassenc.so
Normal file
Binary file not shown.
BIN
lib/armeabi-v7a/libbassenc_flac.so
Normal file
BIN
lib/armeabi-v7a/libbassenc_flac.so
Normal file
Binary file not shown.
BIN
lib/armeabi-v7a/libbassenc_mp3.so
Normal file
BIN
lib/armeabi-v7a/libbassenc_mp3.so
Normal file
Binary file not shown.
BIN
lib/armeabi-v7a/libbassenc_ogg.so
Normal file
BIN
lib/armeabi-v7a/libbassenc_ogg.so
Normal file
Binary file not shown.
BIN
lib/armeabi-v7a/libbassenc_opus.so
Normal file
BIN
lib/armeabi-v7a/libbassenc_opus.so
Normal file
Binary file not shown.
BIN
lib/armeabi-v7a/libbassflac.so
Normal file
BIN
lib/armeabi-v7a/libbassflac.so
Normal file
Binary file not shown.
BIN
lib/armeabi-v7a/libbasshls.so
Normal file
BIN
lib/armeabi-v7a/libbasshls.so
Normal file
Binary file not shown.
BIN
lib/armeabi-v7a/libbassmidi.so
Normal file
BIN
lib/armeabi-v7a/libbassmidi.so
Normal file
Binary file not shown.
BIN
lib/armeabi-v7a/libbassmix.so
Normal file
BIN
lib/armeabi-v7a/libbassmix.so
Normal file
Binary file not shown.
BIN
lib/armeabi-v7a/libbassopus.so
Normal file
BIN
lib/armeabi-v7a/libbassopus.so
Normal file
Binary file not shown.
BIN
lib/armeabi-v7a/libbasswebm.so
Normal file
BIN
lib/armeabi-v7a/libbasswebm.so
Normal file
Binary file not shown.
BIN
lib/armeabi-v7a/libbasswv.so
Normal file
BIN
lib/armeabi-v7a/libbasswv.so
Normal file
Binary file not shown.
BIN
lib/armeabi-v7a/libtags.so
Normal file
BIN
lib/armeabi-v7a/libtags.so
Normal file
Binary file not shown.
1408
src/AndroidRelated/AndroidMic.java
Normal file
1408
src/AndroidRelated/AndroidMic.java
Normal file
File diff suppressed because it is too large
Load Diff
354
src/AndroidRelated/AndroidNetworkReceiver.java
Normal file
354
src/AndroidRelated/AndroidNetworkReceiver.java
Normal file
@@ -0,0 +1,354 @@
|
||||
package AndroidRelated;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.SocketException;
|
||||
|
||||
import com.un4seen.bass.BASS;
|
||||
import com.un4seen.bass.BASS.BASS_DEVICEINFO;
|
||||
import com.un4seen.bass.BASS.bassconstant;
|
||||
|
||||
import anywheresoftware.b4a.BA;
|
||||
import anywheresoftware.b4a.objects.collections.List;
|
||||
import anywheresoftware.b4a.objects.collections.Map;
|
||||
import jbass.Bass_DeviceInfo;
|
||||
|
||||
@BA.ShortName("AndroidNetworkReceiver")
|
||||
@BA.Events(values= {
|
||||
"log(msg as string)",
|
||||
"udpserver(isopened as boolean)",
|
||||
"playbackstarted(sourceip as string)",
|
||||
"playbackfinished(sourceip as string)",
|
||||
"playbackfailed(sourceip as string)"
|
||||
})
|
||||
public class AndroidNetworkReceiver implements NetworkReceiverUDPClientEvent {
|
||||
|
||||
private BA bax;
|
||||
private Object caller;
|
||||
private String event;
|
||||
private boolean need_log_event = false;
|
||||
private boolean need_udpserver_event = false;
|
||||
private boolean need_playbackstarted_event = false;
|
||||
private boolean need_playbackfinished_event = false;
|
||||
private boolean need_playbackfailed_event = false;
|
||||
|
||||
private boolean inited = false;
|
||||
private BASS bass;
|
||||
private int playback_dev_id = -1;
|
||||
|
||||
private Boolean udpisworking = false;
|
||||
private Map streamingmap ;
|
||||
|
||||
/**
|
||||
* Initialize AndroidNetworkReceiver
|
||||
* @param callerobject : caller object
|
||||
* @param eventname : event name
|
||||
*/
|
||||
public void Initialize(BA ba, Object callerobject, String eventname) {
|
||||
if (ba instanceof BA) {
|
||||
bax = ba;
|
||||
if (callerobject!=null) {
|
||||
caller = callerobject;
|
||||
if ( eventname instanceof String) {
|
||||
if (!eventname.isEmpty()) {
|
||||
event = eventname;
|
||||
need_log_event = bax.subExists(event+"_log");
|
||||
need_udpserver_event = bax.subExists(event+"_udpserver");
|
||||
need_playbackstarted_event = bax.subExists(event+"_playbackstarted");
|
||||
need_playbackfinished_event = bax.subExists(event+"_playbackfinished");
|
||||
need_playbackfailed_event = bax.subExists(event+"_playbackfailed");
|
||||
bass = new BASS();
|
||||
if (bass.BASS_GetVersion()!=0) {
|
||||
inited = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
streamingmap = new Map();
|
||||
streamingmap.Initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if AndroidNetworkReceiver is initialized or not
|
||||
* @return true if inited
|
||||
*/
|
||||
public boolean IsInitialized() {
|
||||
return inited;
|
||||
}
|
||||
|
||||
|
||||
//////////////////// AUDIO SECTION /////////////////////////
|
||||
|
||||
/**
|
||||
* Get a List containing all Bass_DeviceInfo from Playback
|
||||
* @return List
|
||||
*/
|
||||
public List GetAllPlaybackDevice() {
|
||||
List result = new List();
|
||||
result.Initialize();
|
||||
if (inited) {
|
||||
for(int ii=0;ii<10;ii++) {
|
||||
BASS_DEVICEINFO devinfo = new BASS_DEVICEINFO();
|
||||
if (bass.BASS_GetDeviceInfo(ii, devinfo)) {
|
||||
Bass_DeviceInfo dev = new Bass_DeviceInfo(ii, devinfo);
|
||||
if (dev.isvalid) {
|
||||
result.Add(dev);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close Playback Device and free Playback resources
|
||||
*/
|
||||
public void ClosePlaybackDevice() {
|
||||
if (playback_dev_id>-1) {
|
||||
if (inited) {
|
||||
if (bass.BASS_SetDevice(playback_dev_id)) {
|
||||
if (bass.BASS_Stop()) {
|
||||
if (bass.BASS_Free()) {
|
||||
raise_log_event("ClosePlaybackDevice success");
|
||||
} else raise_log_event("ClosePlaybackDevice Free failed, Msg : "+bass.GetBassErrorString());
|
||||
} else raise_log_event("ClosePlaybackDevice Stop failed, Msg : "+bass.GetBassErrorString());
|
||||
} else raise_log_event("ClosePlaybackDevice SetDevice failed, Msg : "+bass.GetBassErrorString());
|
||||
}
|
||||
playback_dev_id = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open Playback Device
|
||||
* @param deviceindex : device index
|
||||
* @param samplingrate : sampling rate
|
||||
* @return true if can be opened
|
||||
*/
|
||||
public boolean OpenPlaybackDevice(int deviceindex, int samplingrate) {
|
||||
if (inited) {
|
||||
ClosePlaybackDevice();
|
||||
int initflag = bassconstant.BASS_DEVICE_16BITS | bassconstant.BASS_DEVICE_MONO | bassconstant.BASS_DEVICE_FREQ;
|
||||
|
||||
if (bass.BASS_Init(deviceindex, samplingrate, initflag)) {
|
||||
// sampe sini dah init, dan udah automatically setdevice
|
||||
if (bass.BASS_Start()) {
|
||||
playback_dev_id = deviceindex;
|
||||
if (playback_dev_id>0) {
|
||||
// NO SOUND (device 0) tidak bisa SetVolume
|
||||
if (!bass.BASS_SetVolume(1.0f)) raise_log_event("OpenPlaybackDevice SetVolume failed, Msg : "+bass.GetBassErrorString());
|
||||
}
|
||||
return true;
|
||||
} else raise_log_event("OpenPlaybackDevice Start failed, Msg : "+bass.GetBassErrorString());
|
||||
} else raise_log_event("OpenPlaybackDevice Init failed, Msg : "+bass.GetBassErrorString());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
////////////////// NETWORKING SECTION ///////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* start UDP Server for data streaming
|
||||
* @param listenport : listening port
|
||||
* @param maxbytesperpacket : maximum bytes per UDP packet. Exceed this value, will be cropped
|
||||
* @return true if can be opened
|
||||
*/
|
||||
public boolean Start_UDP_Server(int listenport, int maxbytesperpacket) {
|
||||
Stop_UDP_Server();
|
||||
if (maxbytesperpacket<1) maxbytesperpacket = 1000;
|
||||
|
||||
try {
|
||||
DatagramSocket newudp = new DatagramSocket(listenport);
|
||||
Thread tx = new Thread(new UDPThread(newudp, listenport, maxbytesperpacket));
|
||||
tx.start();
|
||||
|
||||
return true;
|
||||
} catch (SocketException e) {
|
||||
raise_log_event("Failed Start_UDP_Server at port "+listenport+", Msg : "+e.getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop UDP Server
|
||||
*/
|
||||
public void Stop_UDP_Server() {
|
||||
synchronized(udpisworking) {
|
||||
udpisworking = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an IP Address to be valid to stream with UDP Server
|
||||
* @param ipaddress : Ip address
|
||||
* @return newly added NetworkReceiverUDPClient , or null if failed
|
||||
*/
|
||||
public NetworkReceiverUDPClient Add_UDP_Client(String ipaddress) {
|
||||
if (streamingmap instanceof Map) {
|
||||
if (streamingmap.IsInitialized()) {
|
||||
NetworkReceiverUDPClient newclient = new NetworkReceiverUDPClient(ipaddress, bass, this);
|
||||
streamingmap.Put(ipaddress, newclient);
|
||||
return newclient;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an IP address from map. after removed, data from this IP will be ignored
|
||||
* @param ipaddress : Ip address
|
||||
* @return NetworkReceiverUDPClient that being removed, or null if not exist
|
||||
*/
|
||||
public NetworkReceiverUDPClient Remove_UDP_Client(String ipaddress) {
|
||||
if (streamingmap instanceof Map) {
|
||||
if (streamingmap.IsInitialized()) {
|
||||
if (streamingmap.ContainsKey(ipaddress)) {
|
||||
Object obj = streamingmap.Remove(ipaddress);
|
||||
if (obj instanceof NetworkReceiverUDPClient) {
|
||||
NetworkReceiverUDPClient client = (NetworkReceiverUDPClient) obj;
|
||||
client.StopBuffering();
|
||||
return client;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private class UDPThread implements Runnable{
|
||||
private final int udpport;
|
||||
private final DatagramSocket udpsock;
|
||||
private final int maxpackagesize;
|
||||
|
||||
public UDPThread(DatagramSocket udp, int listenport, int maxsize) {
|
||||
udpsock = udp;
|
||||
udpport = listenport;
|
||||
maxpackagesize = maxsize;
|
||||
synchronized(udpisworking) {
|
||||
if (udpsock instanceof DatagramSocket) {
|
||||
udpisworking = true;
|
||||
} else {
|
||||
udpisworking = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (udpisworking) {
|
||||
raise_udpserver(true, udpport);
|
||||
|
||||
// muter muter di sini
|
||||
while(true) {
|
||||
if (udpisworking) {
|
||||
if (udpsock instanceof DatagramSocket) {
|
||||
DatagramPacket pkg = new DatagramPacket(new byte[maxpackagesize],maxpackagesize);
|
||||
try {
|
||||
udpsock.receive(pkg);
|
||||
} catch (IOException e) {
|
||||
raise_log_event("UDP receive exception, Msg : "+e.getMessage());
|
||||
|
||||
break; // exit while
|
||||
}
|
||||
|
||||
// kalau sampe sini, packet data sudah diterima
|
||||
|
||||
String key = pkg.getAddress().getHostAddress();
|
||||
if (streamingmap instanceof Map) {
|
||||
if (streamingmap.IsInitialized()) {
|
||||
Object obj = streamingmap.Get(key);
|
||||
if (obj instanceof NetworkReceiverUDPClient) {
|
||||
NetworkReceiverUDPClient client = (NetworkReceiverUDPClient) obj;
|
||||
if (client.getUDPPort()!=pkg.getPort()) {
|
||||
raise_log_event("UDP Port for Streaming from "+client.getRemoteIP()+" is changed from "+client.getUDPPort()+" to "+pkg.getPort());
|
||||
client.setUDPPort(pkg.getPort());
|
||||
}
|
||||
client.StoreBytes(pkg.getData(), pkg.getLength());
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
synchronized(udpisworking) {
|
||||
udpisworking = false;
|
||||
}
|
||||
|
||||
raise_udpserver(false, udpport);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void raise_log_event(String msg) {
|
||||
if (need_log_event) bax.raiseEventFromDifferentThread(caller, null, 0, event+"_log", false, new Object[] {msg});
|
||||
}
|
||||
|
||||
private void raise_udpserver(boolean value, int portnumber) {
|
||||
if (need_udpserver_event) bax.raiseEventFromDifferentThread(caller, null, 0, event+"_udpserver", false, new Object[] {value, portnumber});
|
||||
}
|
||||
|
||||
//TODO: lanjutin ini
|
||||
@SuppressWarnings("unused")
|
||||
private void raise_playbackstarted_event(String sourceip) {
|
||||
if (need_playbackstarted_event) bax.raiseEventFromDifferentThread(caller, null, 0, event+"_playbackstarted", false, new Object[] {sourceip});
|
||||
}
|
||||
|
||||
//TODO: lanjutin ini
|
||||
@SuppressWarnings("unused")
|
||||
private void raise_playbackfinished_event(String sourceip) {
|
||||
if (need_playbackfinished_event) bax.raiseEventFromDifferentThread(caller, null, 0, event+"_playbackfinished", false, new Object[] {sourceip});
|
||||
}
|
||||
|
||||
//TODO: lanjutin ini
|
||||
@SuppressWarnings("unused")
|
||||
private void raise_playbackfailed_event(String sourceip) {
|
||||
if (need_playbackfailed_event) bax.raiseEventFromDifferentThread(caller, null, 0, event+"_playbackfailed", false, new Object[] {sourceip});
|
||||
}
|
||||
|
||||
|
||||
|
||||
//////////// Event dari NetworkReceiverUDPClient /////////////////////
|
||||
@Override
|
||||
@BA.Hide
|
||||
public void log(String senderip, String msg) {
|
||||
raise_log_event("Log from "+senderip+" : "+msg);
|
||||
}
|
||||
|
||||
//TODO: lanjutin ini
|
||||
@Override
|
||||
@BA.Hide
|
||||
public void handleisready(String senderip, int handle) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
//TODO: lanjutin ini
|
||||
@Override
|
||||
@BA.Hide
|
||||
public void handleisinvalid(String senderip) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
//TODO: lanjutin ini
|
||||
@Override
|
||||
@BA.Hide
|
||||
public void handleisfreed(String senderip, int handle) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
}
|
||||
170
src/AndroidRelated/AudioFileInformationV2.java
Normal file
170
src/AndroidRelated/AudioFileInformationV2.java
Normal file
@@ -0,0 +1,170 @@
|
||||
package AndroidRelated;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import com.sun.jna.Memory;
|
||||
import com.sun.jna.Pointer;
|
||||
import com.un4seen.bass.BASS;
|
||||
import com.un4seen.bass.BASS.bassconstant;
|
||||
|
||||
import anywheresoftware.b4a.BA;
|
||||
import anywheresoftware.b4a.objects.streams.File;
|
||||
|
||||
@BA.ShortName("AudioFileInformationV2")
|
||||
public class AudioFileInformationV2 {
|
||||
private final String filename;
|
||||
private int handle=0;
|
||||
private int size=-1;
|
||||
private double seconds=-1;
|
||||
private boolean found = false;
|
||||
private boolean valid=false;
|
||||
private final int _samplingrate;
|
||||
private final int _initflag = bassconstant.BASS_DEVICE_16BITS | bassconstant.BASS_DEVICE_MONO;
|
||||
private final int _loadflag = bassconstant.BASS_STREAM_DECODE | bassconstant.BASS_SAMPLE_MONO;
|
||||
private final int KB_threshold = 1024;
|
||||
private final int MB_threshold = 1024 * 1024;
|
||||
private final int GB_threshold = 1024 * 1024 * 1024;
|
||||
private final int minute_threshold = 60;
|
||||
private final int hour_threshold = 60 * 60;
|
||||
private BASS bass;
|
||||
|
||||
|
||||
public AudioFileInformationV2() {
|
||||
filename ="";
|
||||
_samplingrate=44100;
|
||||
}
|
||||
|
||||
public AudioFileInformationV2(BASS _bass,String path, int samplingrate) {
|
||||
bass = _bass;
|
||||
filename = path;
|
||||
this._samplingrate = samplingrate;
|
||||
|
||||
try {
|
||||
found = File.Exists("", path);
|
||||
} catch (IOException e) {
|
||||
BA.Log("Unable to check existanceo of "+path+", Msg : "+e.getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (bass instanceof BASS) {
|
||||
if (bass.BASS_GetVersion()!=0) {
|
||||
bass.BASS_Init(0, _samplingrate, _initflag);
|
||||
handle = bass.BASS_StreamCreateFile(false, filename, 0, 0, _loadflag);
|
||||
if (handle!=0) {
|
||||
size = (int)bass.BASS_ChannelGetLength(handle, bassconstant.BASS_POS_BYTE);
|
||||
if (size>-1) {
|
||||
seconds = bass.BASS_ChannelBytes2Seconds(handle, size);
|
||||
if (seconds>-1) {
|
||||
valid = true;
|
||||
}
|
||||
}
|
||||
bass.BASS_StreamFree(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
* Check if file is found
|
||||
* @return true if found
|
||||
*/
|
||||
public boolean isfound() {
|
||||
return found;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if file is valid
|
||||
* @return true if valid
|
||||
*/
|
||||
public boolean isvalid() {
|
||||
return valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Size in bytes
|
||||
* @return value in bytes
|
||||
*/
|
||||
public int size_in_bytes() {
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return size in String
|
||||
* if invalid, return N/A
|
||||
* if < Kilobytes, return [X] B
|
||||
* if < Megabytes, return [X] KB
|
||||
* if < Gigabytes, return [X] MB
|
||||
* else return [X] GB
|
||||
* @return value in string
|
||||
*/
|
||||
public String size_in_String() {
|
||||
if (size<0) {
|
||||
return "N/A";
|
||||
} else if (size<KB_threshold) {
|
||||
return String.format("%d B", size);
|
||||
} else if (size < MB_threshold) {
|
||||
return String.format("%.1f KB" , (float)size / KB_threshold);
|
||||
} else if (size < GB_threshold) {
|
||||
return String.format("%.1f MB" , (float)size / MB_threshold);
|
||||
} else {
|
||||
return String.format("%.1f GB" , (float)size / GB_threshold);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return length in seconds
|
||||
* if invalid, return less than 0
|
||||
* @return value in seconds
|
||||
*/
|
||||
public double length_in_seconds() {
|
||||
return seconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return length in string
|
||||
* if invalid, return N/A
|
||||
* if < 60 second, return [X]
|
||||
* if < 3600 seconds, return [X] M [Y] S
|
||||
* else return [X] H [Y] M [Z] S
|
||||
* @return String
|
||||
*/
|
||||
public String length_in_String() {
|
||||
if (seconds<0) {
|
||||
return "N/A";
|
||||
} else if (seconds < minute_threshold) {
|
||||
return String.format("%d", seconds);
|
||||
} else if (seconds < hour_threshold) {
|
||||
int _minute = (int)(seconds / minute_threshold);
|
||||
int _second = (int)(seconds % minute_threshold);
|
||||
return String.format("%d M %d S", _minute, _second);
|
||||
} else {
|
||||
int _hour = (int) (seconds / hour_threshold);
|
||||
int remainder = (int)(seconds - (_hour * hour_threshold));
|
||||
int _minute = (int) (remainder / minute_threshold);
|
||||
int _second = (int)(remainder % minute_threshold);
|
||||
return String.format("%d H %d M %d S", _hour, _minute, _second);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return array of bytes for PCM data
|
||||
* @return null if invalid
|
||||
*/
|
||||
public byte[] GetBytes() {
|
||||
if (valid) {
|
||||
if (size>0) {
|
||||
handle = bass.BASS_StreamCreateFile(false, filename, 0, 0, _loadflag);
|
||||
if (handle!=0) {
|
||||
Pointer pp = new Memory(size);
|
||||
int readsize = bass.BASS_ChannelGetData(handle, pp, size);
|
||||
if (readsize>0) {
|
||||
return pp.getByteArray(0, readsize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
254
src/AndroidRelated/NetworkReceiverUDPClient.java
Normal file
254
src/AndroidRelated/NetworkReceiverUDPClient.java
Normal file
@@ -0,0 +1,254 @@
|
||||
package AndroidRelated;
|
||||
|
||||
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import com.sun.jna.Pointer;
|
||||
import com.un4seen.bass.BASS;
|
||||
import com.un4seen.bass.BASS.BASS_DEVICEINFO;
|
||||
import com.un4seen.bass.BASS.BASS_FILEPROCS;
|
||||
import com.un4seen.bass.BASS.FILECLOSEPROC;
|
||||
import com.un4seen.bass.BASS.FILELENPROC;
|
||||
import com.un4seen.bass.BASS.FILEREADPROC;
|
||||
import com.un4seen.bass.BASS.FILESEEKPROC;
|
||||
import com.un4seen.bass.BASS.bassconstant;
|
||||
|
||||
import anywheresoftware.b4a.BA;
|
||||
|
||||
@BA.ShortName("NetworkReceiverUDPClient")
|
||||
public class NetworkReceiverUDPClient {
|
||||
|
||||
private final String targetip;
|
||||
private int targetport;
|
||||
private final int sizeinterval = 1024 * 1024; // penambahan size tiap 1MB
|
||||
private int capacity = 5 * sizeinterval; // capacity awal
|
||||
private ByteBuffer buf;
|
||||
private int totalbytesreceived = 0; // total sudah ketulis
|
||||
private boolean inited = false; // indicator for NetworkReceiverUDPClient inited or not
|
||||
|
||||
private BASS mybass;
|
||||
|
||||
private final int deviceid = 0; // use NO SOUND
|
||||
private final int samplingrate = 48000; // samplingrate = 48K
|
||||
private final int initflag = bassconstant.BASS_DEVICE_16BITS | bassconstant.BASS_DEVICE_FREQ | bassconstant.BASS_DEVICE_MONO;
|
||||
private boolean dev_enabled = false;
|
||||
private boolean dev_inited = false;
|
||||
private int handle = 0;
|
||||
private NetworkReceiverUDPClientEvent myevent;
|
||||
private Thread tx;
|
||||
|
||||
public NetworkReceiverUDPClient(String ipaddress, BASS bass, NetworkReceiverUDPClientEvent event) {
|
||||
targetip = ipaddress;
|
||||
mybass = bass;
|
||||
myevent = event;
|
||||
inited = false;
|
||||
if (!(mybass instanceof BASS)) {
|
||||
myevent.log(targetip,"NetworkReceiverUDPClient failed, mybass is null");
|
||||
return;
|
||||
}
|
||||
|
||||
if (mybass.BASS_GetVersion()==0) {
|
||||
myevent.log(targetip,"NetworkReceiverUDPClient failed, mybass version = 0");
|
||||
return;
|
||||
}
|
||||
|
||||
BASS_DEVICEINFO dev = new BASS_DEVICEINFO();
|
||||
if (!mybass.BASS_GetDeviceInfo(deviceid, dev)) {
|
||||
myevent.log(targetip,"NetworkReceiverUDPClient failed, unable to GetDeviceInfo device="+deviceid);
|
||||
return;
|
||||
}
|
||||
|
||||
// sampai sini, GetDeviceInfo bisa
|
||||
dev.read();
|
||||
|
||||
dev_enabled = (dev.flags & bassconstant.BASS_DEVICE_ENABLED) > 0 ? true : false;
|
||||
if (! dev_enabled) {
|
||||
myevent.log(targetip,"NetworkReceiverUDPClient failed, device="+deviceid+" is disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
dev_inited = (dev.flags & bassconstant.BASS_DEVICE_INIT) > 0 ? true : false;
|
||||
|
||||
if (! dev_inited) {
|
||||
if (!bass.BASS_Init(deviceid, samplingrate, initflag)) {
|
||||
// gagal init
|
||||
myevent.log(targetip,"NetworkReceiverUDPClient failed, device="+deviceid+" can't be inited");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// sampai sini, dev_inited sudah true
|
||||
inited = true;
|
||||
buf = ByteBuffer.allocate(capacity);
|
||||
tx = new Thread(new BufferingThread());
|
||||
tx.start();
|
||||
|
||||
}
|
||||
|
||||
public boolean IsInitialized() {
|
||||
return inited;
|
||||
}
|
||||
|
||||
public void StopBuffering() {
|
||||
if (tx instanceof Thread) {
|
||||
if (tx.isAlive()) {
|
||||
tx.interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@BA.Hide
|
||||
public void setUDPPort(int value) {
|
||||
targetport = value;
|
||||
}
|
||||
|
||||
public int getUDPPort() {
|
||||
return targetport;
|
||||
}
|
||||
|
||||
public String getRemoteIP() {
|
||||
return targetip;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* How many bytes that stored
|
||||
* @return value in integer
|
||||
*/
|
||||
public int TotalSizeReceived() {
|
||||
return totalbytesreceived;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store data bytes
|
||||
* @param buffer : data bytes
|
||||
* @param length : length to store
|
||||
* @return how many bytes actually stored
|
||||
*/
|
||||
public int StoreBytes(byte[] buffer, int length) {
|
||||
if (buf instanceof ByteBuffer) {
|
||||
if (buffer != null) {
|
||||
if (length>0) {
|
||||
if (length > buffer.length) length = buffer.length;
|
||||
synchronized(buf) {
|
||||
if (buf.remaining() < length) {
|
||||
// buffer gak cukup, gedein capacity
|
||||
capacity = capacity + sizeinterval;
|
||||
ByteBuffer newbuf = ByteBuffer.allocate(capacity); // bikin newbuffer dengan kapasitas + ditambah length
|
||||
newbuf.put(buf); // isi newbuf dengan buf
|
||||
buf = newbuf; // replace buf dengan newbuf
|
||||
myevent.log(targetip, "Buffer increased by 1M");
|
||||
}
|
||||
|
||||
buf.put(buffer, 0, length); // masukin data ke buf
|
||||
buf.notifyAll();
|
||||
}
|
||||
|
||||
totalbytesreceived+=length;
|
||||
return length;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private class BufferingThread implements Runnable{
|
||||
private FILEREADPROC fr;
|
||||
private FILELENPROC fl;
|
||||
private FILECLOSEPROC fc;
|
||||
private FILESEEKPROC fs;
|
||||
private BASS_FILEPROCS fileproc;
|
||||
|
||||
public BufferingThread() {
|
||||
/// create callbacks
|
||||
fr = new FILEREADPROC() {
|
||||
|
||||
@Override
|
||||
public int FILEREADPROC(Pointer buffer, int length, Pointer user) {
|
||||
synchronized(buf) {
|
||||
if (buf.position()==0) {
|
||||
try {
|
||||
buf.wait(5000);
|
||||
} catch (InterruptedException e) {
|
||||
myevent.log(targetip, "buf wait timeout, no more bytes");
|
||||
return 0; // kalau return 0, selesai
|
||||
}
|
||||
}
|
||||
|
||||
buf.flip(); // change to read mode
|
||||
int readcount = buf.remaining();
|
||||
if (readcount>length) readcount = length;
|
||||
byte[] bufread = new byte[readcount];
|
||||
buf.get(bufread);
|
||||
buf.compact(); // back to write mode
|
||||
|
||||
buffer.write(0, bufread, 0, readcount);
|
||||
return readcount; // ada bacaan
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
fl = new FILELENPROC() {
|
||||
|
||||
@Override
|
||||
public long FILELENPROC(Pointer user) {
|
||||
return 0; // size unknown
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
fs = new FILESEEKPROC() {
|
||||
|
||||
@Override
|
||||
public boolean FILESEEKPROC(long offset, Pointer user) {
|
||||
|
||||
return false; // can't seek streaming
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
fc = new FILECLOSEPROC() {
|
||||
|
||||
@Override
|
||||
public void FILECLOSEPROC(Pointer user) {
|
||||
// do nothing here
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
fileproc = new BASS_FILEPROCS(fc, fl, fr, fs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
mybass.BASS_SetDevice(deviceid);
|
||||
handle = mybass.BASS_StreamCreateFileUser(bassconstant.STREAMFILE_BUFFER, 0, fileproc, null);
|
||||
// tunggu di sini sampe handle keluar
|
||||
if (handle!=0) {
|
||||
BASS.BASS_ChannelPause(handle);
|
||||
myevent.handleisready(targetip, handle);
|
||||
while(true) {
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mybass.BASS_StreamFree(handle);
|
||||
myevent.handleisfreed(targetip, handle);
|
||||
handle = 0;
|
||||
|
||||
} else myevent.handleisinvalid(targetip);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
8
src/AndroidRelated/NetworkReceiverUDPClientEvent.java
Normal file
8
src/AndroidRelated/NetworkReceiverUDPClientEvent.java
Normal file
@@ -0,0 +1,8 @@
|
||||
package AndroidRelated;
|
||||
|
||||
public interface NetworkReceiverUDPClientEvent {
|
||||
void log(String senderip, String msg);
|
||||
void handleisready(String senderip, int handle);
|
||||
void handleisinvalid(String senderip);
|
||||
void handleisfreed(String senderip, int handle);
|
||||
}
|
||||
624
src/DatareceiverRelated/DataReceiver.java
Normal file
624
src/DatareceiverRelated/DataReceiver.java
Normal file
@@ -0,0 +1,624 @@
|
||||
package DatareceiverRelated;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
import com.sun.jna.Memory;
|
||||
import com.sun.jna.Pointer;
|
||||
|
||||
import anywheresoftware.b4a.BA;
|
||||
import jbass.commoncodes;
|
||||
|
||||
import java.net.MulticastSocket;
|
||||
import java.net.NetworkInterface;
|
||||
|
||||
@BA.Events(values= {
|
||||
"log(msg as string)",
|
||||
"tcpconnected(success as boolean)",
|
||||
"newdata(functionname as string, bb() as byte, length as int)",
|
||||
"connected(ipaddress as string, portnumber as int)",
|
||||
"disconnected(ipaddress as string, portnumber as int)",
|
||||
"receivedbytescounter(value as long, value_in_string as string)"
|
||||
})
|
||||
@BA.ShortName("NetworkDataReceiver")
|
||||
public class DataReceiver {
|
||||
|
||||
private BA ba;
|
||||
private boolean inited = false;
|
||||
private String event;
|
||||
private DataReceiverEvent dre;
|
||||
private boolean need_log_event = false;
|
||||
private boolean need_tcpconnected_event = false;
|
||||
private boolean need_newdata_event = false;
|
||||
private boolean need_connected_event = false;
|
||||
private boolean need_disconnected_event = false;
|
||||
private boolean need_receivedbytescounter_event = false;
|
||||
|
||||
private DatagramSocket udp;
|
||||
private InetSocketAddress udp_sa;
|
||||
|
||||
private MulticastSocket mulsock;
|
||||
private InetSocketAddress multicastaddress;
|
||||
private NetworkInterface mulsockinterface = null;
|
||||
|
||||
|
||||
private Socket tcpsock;
|
||||
private InetSocketAddress tcp_sa;
|
||||
|
||||
private Thread rx;
|
||||
|
||||
private long rxbytes = 0;
|
||||
private Object myobject;
|
||||
|
||||
/**
|
||||
* How many bytes already received by Network Receiver Unit
|
||||
* @return value in long
|
||||
*/
|
||||
public long getNetworkReceivedBytes() {
|
||||
return rxbytes;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* How many bytes already received by Network Receiver Unit, stated in readable String
|
||||
* @return value in string
|
||||
*/
|
||||
public String getNetworkReceivedBytes_in_String() {
|
||||
return commoncodes.ByteCounter_to_String(rxbytes);
|
||||
}
|
||||
|
||||
@BA.Hide
|
||||
public void SetDataReceiverEvent(DataReceiverEvent xx) {
|
||||
dre = xx;
|
||||
}
|
||||
|
||||
public void Initialize(BA bax, Object callerobject, String eventname) {
|
||||
ba = bax;
|
||||
event = eventname;
|
||||
myobject = callerobject;
|
||||
if (ba!=null) {
|
||||
if (myobject!=null) {
|
||||
if (!event.isEmpty()) {
|
||||
need_log_event = ba.subExists(event+"_log");
|
||||
need_tcpconnected_event = ba.subExists(event+"_tcpconnected");
|
||||
need_newdata_event = ba.subExists(event+"_newdata");
|
||||
need_connected_event = ba.subExists(event+"_connected");
|
||||
need_disconnected_event = ba.subExists(event+"_disconnected");
|
||||
need_receivedbytescounter_event = ba.subExists(event+"_receivedbytescounter");
|
||||
inited = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public boolean IsInitialized() {
|
||||
return inited;
|
||||
}
|
||||
|
||||
/**
|
||||
* on UDP Client, return UDP Server IP
|
||||
* on TCP Client, return TCP server IP
|
||||
* on Join Multicast, return UDP sender IP in Multicast, that has been filtered
|
||||
* @return IP address on string
|
||||
*/
|
||||
public String GetCurrentNetworkSource() {
|
||||
if (udp_sa!=null) {
|
||||
return udp_sa.getAddress().getHostAddress();
|
||||
} else if (tcp_sa!=null) {
|
||||
return tcp_sa.getAddress().getHostAddress();
|
||||
}else return "N/A";
|
||||
}
|
||||
|
||||
/**
|
||||
* on UDP Client, return UDP Server Port
|
||||
* on TCP Client , return TCP server port
|
||||
* on JOin Multicast, return UDP Sender Port in Multicast, that has been filtered
|
||||
* @return -1 if not joining
|
||||
*/
|
||||
public int GetCurrentNetworkPort() {
|
||||
if (udp_sa!=null) {
|
||||
return udp_sa.getPort();
|
||||
} else if (tcp_sa!=null) {
|
||||
return tcp_sa.getPort();
|
||||
} else return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return Multicast Address joined, or empty string if not joining
|
||||
* @return Multicast address in string
|
||||
*/
|
||||
public String GetMulticastAddress() {
|
||||
if (multicastaddress!=null) {
|
||||
return multicastaddress.getAddress().getHostAddress();
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return Multicast Port joined, or -1 if not joining
|
||||
* @return -1 if not joining
|
||||
*/
|
||||
public int GetMulticastPort() {
|
||||
if (multicastaddress!=null) {
|
||||
return multicastaddress.getPort();
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* Connect to a UDP Client
|
||||
* @param targetip : UDP Target IP
|
||||
* @param targetport : UDP Target Port
|
||||
* @param maxpackagesize : package read size
|
||||
* @return true if connected
|
||||
*/
|
||||
public boolean Start_UDP_Connecting(String targetip, int targetport, int maxpackagesize) {
|
||||
if (maxpackagesize<1) maxpackagesize = 1000; // default size is 1000 bytes
|
||||
|
||||
try {
|
||||
udp = new DatagramSocket();
|
||||
} catch (SocketException e) {
|
||||
raise_log_exception("Start_UDP_Connecting","DatagramSocket()",e);
|
||||
return false;
|
||||
}
|
||||
|
||||
udp_sa = new InetSocketAddress(targetip, targetport);
|
||||
|
||||
try {
|
||||
udp.connect(udp_sa);
|
||||
raise_connected(udp_sa);
|
||||
raise_log("Start UDP Connecting to "+udp_sa.getAddress().getHostAddress()+":"+udp_sa.getPort());
|
||||
} catch (SocketException e) {
|
||||
raise_disconnect(udp_sa);
|
||||
raise_log_exception("Start_UDP_Connecting","Connect",e);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (udp.isConnected()) {
|
||||
rx = new Thread(new UDP_Receive_Thread(maxpackagesize, targetip));
|
||||
rx.start();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Start UDP Listening
|
||||
* @param listenport : listen port
|
||||
* @param ipsource : IP address of source stream wanted to receive, or fill with empty string for all address
|
||||
* @param maxpackagesize : package read size
|
||||
* @return true if success
|
||||
*/
|
||||
public boolean Start_UDP_Listening(int listenport, String ipsource, int maxpackagesize) {
|
||||
if (maxpackagesize<1) maxpackagesize = 1000; // default size is 1000 bytes
|
||||
|
||||
|
||||
try {
|
||||
udp = new DatagramSocket(listenport);
|
||||
raise_log("Start UDP Listening at "+udp.getLocalAddress().getHostAddress()+":"+udp.getLocalPort());
|
||||
} catch (SocketException e) {
|
||||
raise_log_exception("Start_UDP_Listening","DatagramSocket("+listenport+")", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
rx = new Thread(new UDP_Receive_Thread(maxpackagesize, ipsource)) ;
|
||||
rx.start();
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Start Multicast Joining
|
||||
* @param localip : Ethernet IP used for this Multicast Joining
|
||||
* @param ipsource : IP address of source stream wanted to receive, or fill with empty string for all address
|
||||
* @param multicastip : Multicast IP address
|
||||
* @param multicastport : Multicast Port
|
||||
* @param maxpackagesize : package read size
|
||||
* @return true if success
|
||||
*/
|
||||
public boolean Start_Multicast_Join(String localip, String ipsource, String multicastip, int multicastport, int maxpackagesize) {
|
||||
|
||||
mulsockinterface = null;
|
||||
multicastaddress = null;
|
||||
try {
|
||||
mulsock = new MulticastSocket();
|
||||
} catch (IOException e) {
|
||||
raise_log_exception("Start_Multicast_Join","create MulticastSocket",e);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
NetworkInterface nif = NetworkInterface.getByInetAddress(InetAddress.getByName(localip));
|
||||
mulsockinterface = nif;
|
||||
} catch (SocketException | UnknownHostException e) {
|
||||
raise_log_exception("Start_Multicast_Join","GetNetworkInterface from LocalIP",e);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
try {
|
||||
InetSocketAddress xx = new InetSocketAddress(multicastip, multicastport); // alamat ketemuan di multicast address
|
||||
mulsock.joinGroup(multicastaddress, mulsockinterface);
|
||||
multicastaddress = xx;
|
||||
} catch (IOException e) {
|
||||
raise_log_exception("Start_Multicast_Join","JoinGroup",e);
|
||||
return false;
|
||||
}
|
||||
|
||||
rx = new Thread(new Multicast_Receive_Thread(maxpackagesize, ipsource));
|
||||
rx.start();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to TCP Server
|
||||
* Use event tcpconnected(success as boolean) to wait
|
||||
* @param targetip : Server IP
|
||||
* @param targetport : Server Port
|
||||
* @param timeout : timeout to wait, in milisecond
|
||||
* @param maxpackagesize : package read size
|
||||
*/
|
||||
public void Start_TCP_Connecting(String targetip, int targetport, int timeout, int maxpackagesize) {
|
||||
|
||||
tcpsock = new Socket();
|
||||
tcp_sa = new InetSocketAddress(targetip, targetport);
|
||||
Thread ct = new Thread() {
|
||||
public void run() {
|
||||
try {
|
||||
tcpsock.connect(tcp_sa, timeout);
|
||||
|
||||
|
||||
raise_tcpconnectresult(tcpsock.isConnected());
|
||||
|
||||
if (tcpsock.isConnected()) {
|
||||
raise_connected(tcp_sa);
|
||||
rx = new Thread(new TCP_Receive_Thread(maxpackagesize));
|
||||
rx.start();
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
raise_log("Exception on TCP Connect to "+targetip+":"+targetport+", Msg="+e.getMessage()+", Caused="+e.getCause());
|
||||
raise_tcpconnectresult(false);
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
ct.start();
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
private class Multicast_Receive_Thread implements Runnable{
|
||||
|
||||
private int packetsize = 1000;
|
||||
private String ipfilter = "";
|
||||
|
||||
public Multicast_Receive_Thread(int maxpkgsize, String targetip) {
|
||||
packetsize = maxpkgsize;
|
||||
ipfilter = targetip;
|
||||
}
|
||||
|
||||
private boolean StillValid() {
|
||||
if (mulsock!=null) {
|
||||
if (multicastaddress!=null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
boolean has_receive = false;
|
||||
rxbytes = 0;
|
||||
raise_receivedbytescounter(rxbytes);
|
||||
raise_log("Multicast_Receive_Thread started");
|
||||
while(true) {
|
||||
if (!StillValid()) break;
|
||||
byte[] BX = new byte[packetsize];
|
||||
DatagramPacket newdata = new DatagramPacket(BX, packetsize);
|
||||
try {
|
||||
mulsock.receive(newdata); // block di sini , sampai data baru sampe
|
||||
|
||||
// cek dengan IP filter
|
||||
if (!ipfilter.isEmpty()) {
|
||||
if (newdata.getAddress().getHostAddress().compareTo(ipfilter)!=0) continue; // kalau tidak sama dengan ip filter, buang saja
|
||||
}
|
||||
|
||||
if (newdata.getLength()>0) {
|
||||
// ada datanya
|
||||
if (!has_receive) {
|
||||
has_receive = true;
|
||||
udp_sa = new InetSocketAddress(newdata.getAddress().getHostAddress(), newdata.getPort());
|
||||
raise_connected(udp_sa);
|
||||
}
|
||||
|
||||
rxbytes+= newdata.getLength();
|
||||
raise_receivedbytescounter(rxbytes);
|
||||
raise_newdata("Multicast_Receive_Thread",newdata.getData(), newdata.getLength());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
//if (udp_sa!=null) raise_disconnect(udp_sa);
|
||||
close_communications();
|
||||
raise_log("Multicast_Receive_Thread stopped");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private class TCP_Receive_Thread implements Runnable {
|
||||
private InputStream fis;
|
||||
private OutputStream fos;
|
||||
private boolean validsocket = false;
|
||||
private int packetsize = 0;
|
||||
|
||||
|
||||
public TCP_Receive_Thread(int pkgsize) {
|
||||
|
||||
if (pkgsize<1) pkgsize = 1000;
|
||||
packetsize = pkgsize;
|
||||
|
||||
|
||||
if (tcpsock instanceof Socket) {
|
||||
if (tcpsock.isConnected()) {
|
||||
try {
|
||||
fis = tcpsock.getInputStream();
|
||||
fos = tcpsock.getOutputStream();
|
||||
validsocket = true;
|
||||
|
||||
} catch (IOException e) {
|
||||
validsocket = false;
|
||||
raise_log_exception("TCP_Thread","Input/OutputStream",e);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
raise_log("TCP_Receive_Thread validsocket = "+validsocket);
|
||||
|
||||
}
|
||||
|
||||
private boolean StillValid() {
|
||||
|
||||
if (tcpsock instanceof Socket) {
|
||||
if (tcpsock.isConnected()) {
|
||||
if (fis instanceof InputStream) {
|
||||
if (fos instanceof OutputStream) {
|
||||
validsocket = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
validsocket = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
boolean has_receive = false;
|
||||
rxbytes = 0;
|
||||
raise_receivedbytescounter(rxbytes);
|
||||
int rxcounter = 0;
|
||||
raise_log("TCP_Receive_Thread started");
|
||||
while(true) {
|
||||
if (!StillValid()) {
|
||||
//BA.Log("TCP Receive Thread StillValid() = false");
|
||||
break;
|
||||
}
|
||||
if (!(tcp_sa instanceof InetSocketAddress)) break;
|
||||
|
||||
byte[] BX = new byte[packetsize];
|
||||
try {
|
||||
// revisi 12/10/2020, pancingan di sini
|
||||
fos.write(String.valueOf(packetsize).getBytes());
|
||||
|
||||
rxcounter = fis.read(BX); // nge-block di sini sampe ada data
|
||||
} catch (IOException e) {
|
||||
raise_log_exception("TCP_Thread","Read",e);
|
||||
break;
|
||||
}
|
||||
|
||||
if (rxcounter>0) {
|
||||
if (!has_receive) {
|
||||
// pertama terima data
|
||||
has_receive = true;
|
||||
if (tcp_sa instanceof InetSocketAddress) raise_connected(tcp_sa);
|
||||
}
|
||||
|
||||
rxbytes += rxcounter;
|
||||
raise_receivedbytescounter(rxbytes);
|
||||
raise_newdata("TCP_Receive_Thread",BX,rxcounter);
|
||||
//raise_log("TCP_Receive_Thread got "+rxcounter+" bytes");
|
||||
}
|
||||
}
|
||||
|
||||
//if (tcp_sa!=null) raise_disconnect(tcp_sa);
|
||||
close_communications();
|
||||
//BA.Log("Close Communications dari TCP Receive Thread");
|
||||
raise_log("TCP_Receive_Thread stopped");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class UDP_Receive_Thread implements Runnable {
|
||||
private int udpsize;
|
||||
|
||||
private String ipfilter="";
|
||||
UDP_Receive_Thread(int maxpackagesize, String sourceIP){
|
||||
udpsize = maxpackagesize;
|
||||
ipfilter = sourceIP;
|
||||
}
|
||||
@Override
|
||||
public void run() {
|
||||
rxbytes = 0;
|
||||
raise_receivedbytescounter(rxbytes);
|
||||
boolean has_receive = false;
|
||||
raise_log("UDP_Receive_Thread started");
|
||||
while(true) {
|
||||
if (udp==null) {
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
byte[] bb = new byte[udpsize];
|
||||
DatagramPacket newdata = new DatagramPacket(bb,udpsize);
|
||||
try {
|
||||
udp.receive(newdata); // nge-block di sini sampai dapat data
|
||||
|
||||
|
||||
// ada data baru, cek dengan sourceip
|
||||
// kalau tidak ada ipfilter, process semua data
|
||||
if (!ipfilter.isEmpty()) {
|
||||
// ada filter
|
||||
if (newdata.getAddress().toString().compareTo(ipfilter)!=0) {
|
||||
|
||||
continue; // tidak sama dengan ipfilter, buang aja
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (newdata.getLength()>0) {
|
||||
// ada isinya
|
||||
if (!has_receive) {
|
||||
has_receive = true;
|
||||
if (udp_sa==null) { // pada UDP listening , UDP_SA awalnya null, jadi ini menandakan paket pertama yang diterima
|
||||
// pada UDP connecting, UDP_SA sudah diset ke UDP Master
|
||||
udp_sa = new InetSocketAddress(newdata.getAddress(), newdata.getPort());
|
||||
}
|
||||
raise_connected(udp_sa);
|
||||
}
|
||||
|
||||
raise_newdata("UDP_Receive_Thread",newdata.getData(),newdata.getLength());
|
||||
rxbytes+= newdata.getLength();
|
||||
raise_receivedbytescounter(rxbytes);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
if (e.getMessage().compareToIgnoreCase("socket closed")!=0) {
|
||||
// kalau socket closed , itu sudah biasa..
|
||||
// kalau yang lainnya, raise_log_exception
|
||||
raise_log_exception("RX_Thread","udp.receive(newdata)",e);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//if (udp_sa!=null) raise_disconnect(udp_sa);
|
||||
close_communications();
|
||||
raise_log("UDP_Receive_Thread stopped");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Close UDP / TCP connections.
|
||||
* This function is also called when calling Start_xxxxx command
|
||||
*/
|
||||
public void close_communications() {
|
||||
boolean isclosingsomething = false;
|
||||
|
||||
if (udp!=null) {
|
||||
if (udp.isConnected()) {
|
||||
udp.disconnect();
|
||||
if (udp_sa!=null) raise_disconnect(udp_sa);
|
||||
}
|
||||
udp.close();
|
||||
isclosingsomething = true;
|
||||
udp = null;
|
||||
|
||||
}
|
||||
|
||||
if (tcpsock instanceof Socket) {
|
||||
try {
|
||||
tcpsock.close();
|
||||
isclosingsomething = true;
|
||||
if (tcp_sa!=null) raise_disconnect(tcp_sa);
|
||||
} catch (IOException e) {
|
||||
raise_log_exception("close_communication","tcpsock.close",e);
|
||||
}
|
||||
tcpsock = null;
|
||||
|
||||
}
|
||||
|
||||
if (mulsock!=null) {
|
||||
if (multicastaddress!=null) {
|
||||
try {
|
||||
if (multicastaddress!=null) {
|
||||
mulsock.leaveGroup(multicastaddress, mulsockinterface);
|
||||
raise_disconnect(multicastaddress);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
raise_log_exception("close_communication","mulsock.leavegroup",e);
|
||||
}
|
||||
multicastaddress = null;
|
||||
}
|
||||
isclosingsomething = true;
|
||||
mulsock.disconnect();
|
||||
mulsock.close();
|
||||
mulsock = null;
|
||||
}
|
||||
|
||||
if (rx!=null) {
|
||||
rx.interrupt();
|
||||
}
|
||||
if (isclosingsomething) raise_log("DataReceiver communications closed");
|
||||
}
|
||||
|
||||
private void raise_log_exception(String functionname, String command, Exception e) {
|
||||
raise_log("Exception on Function="+functionname+", Command="+command+", Msg="+e.getMessage()+", Caused="+e.getCause());
|
||||
}
|
||||
|
||||
private void raise_tcpconnectresult(boolean value) {
|
||||
if (dre!=null) dre.tcpconnectresult(value);
|
||||
if (need_tcpconnected_event) ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_tcpconnected", false, new Object[] {value});
|
||||
}
|
||||
|
||||
private void raise_log(String msg) {
|
||||
if (dre!=null) dre.log(msg);
|
||||
if (need_log_event) ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_log", false, new Object[] {msg});
|
||||
}
|
||||
|
||||
private void raise_disconnect(InetSocketAddress sa) {
|
||||
if (dre!=null) dre.disconnected(sa);
|
||||
if (need_disconnected_event) ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_disconnected", false, new Object[] {sa.getAddress().getHostAddress(), sa.getPort()});
|
||||
}
|
||||
|
||||
private void raise_connected(InetSocketAddress sa) {
|
||||
if (dre!=null) dre.connected(sa);
|
||||
if (need_connected_event) ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_connected", false, new Object[] {sa.getAddress().getHostAddress(), sa.getPort()});
|
||||
}
|
||||
|
||||
private void raise_newdata(String fromfunction,byte[] bb, int length) {
|
||||
if (dre!=null) {
|
||||
Pointer pp = new Memory(length);
|
||||
pp.write(0, bb, 0, length);
|
||||
dre.data_received(pp, length);
|
||||
}
|
||||
if (need_newdata_event) ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_newdata", false, new Object[] {fromfunction, bb, length});
|
||||
}
|
||||
|
||||
private void raise_receivedbytescounter(long value) {
|
||||
if (need_receivedbytescounter_event) ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_receivedbytescounter", false, new Object[] {value,commoncodes.ByteCounter_to_String(value)});
|
||||
}
|
||||
}
|
||||
13
src/DatareceiverRelated/DataReceiverEvent.java
Normal file
13
src/DatareceiverRelated/DataReceiverEvent.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package DatareceiverRelated;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import com.sun.jna.Pointer;
|
||||
|
||||
public interface DataReceiverEvent {
|
||||
public void data_received(Pointer datanya, int length);
|
||||
public void log(String msg);
|
||||
public void connected(InetSocketAddress sa);
|
||||
public void disconnected(InetSocketAddress sa);
|
||||
public void tcpconnectresult(boolean success);
|
||||
}
|
||||
1345
src/FWSRelated/FWS_Audio_Master.java
Normal file
1345
src/FWSRelated/FWS_Audio_Master.java
Normal file
File diff suppressed because it is too large
Load Diff
1543
src/FWSRelated/FWS_Audio_Slave.java
Normal file
1543
src/FWSRelated/FWS_Audio_Slave.java
Normal file
File diff suppressed because it is too large
Load Diff
6
src/MastersenderRelated/CustomSocketEvent.java
Normal file
6
src/MastersenderRelated/CustomSocketEvent.java
Normal file
@@ -0,0 +1,6 @@
|
||||
package MastersenderRelated;
|
||||
|
||||
public interface CustomSocketEvent {
|
||||
public void log(Object sender, String msg);
|
||||
public void socketclosed(Object sender);
|
||||
}
|
||||
145
src/MastersenderRelated/DatagramSocketForMasterSender.java
Normal file
145
src/MastersenderRelated/DatagramSocketForMasterSender.java
Normal file
@@ -0,0 +1,145 @@
|
||||
package MastersenderRelated;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import anywheresoftware.b4a.BA;
|
||||
import anywheresoftware.b4a.keywords.DateTime;
|
||||
|
||||
@BA.ShortName("MasterSender_DatagramSocket")
|
||||
public class DatagramSocketForMasterSender {
|
||||
private final String _ipkey;
|
||||
private final int _portkey;
|
||||
private boolean _isvalid=false;
|
||||
|
||||
private InetSocketAddress _sock = null;
|
||||
private long create_time=0;
|
||||
private long _txcount=0;
|
||||
|
||||
private CustomSocketEvent javaobject;
|
||||
|
||||
@BA.Hide
|
||||
public DatagramSocketForMasterSender(final String ipaddress, final int port, final CustomSocketEvent event) {
|
||||
_ipkey = ipaddress;
|
||||
_portkey = port;
|
||||
javaobject = event;
|
||||
try {
|
||||
_sock = new InetSocketAddress(_ipkey, _portkey);
|
||||
_isvalid = true;
|
||||
create_time = DateTime.getNow();
|
||||
} catch(IllegalArgumentException | SecurityException e) {
|
||||
log("Error Initializing DatagramSocketForMasterSender, Msg : "+e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
private void log(String msg) {
|
||||
if (javaobject instanceof CustomSocketEvent) {
|
||||
javaobject.log(this,msg);
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
* Key String is [ipaddress]:[port]
|
||||
* @return Key string for this class
|
||||
*/
|
||||
public String getKey() {
|
||||
return _isvalid ? _ipkey+":"+_portkey : "N/A";
|
||||
}
|
||||
|
||||
public String getIP() {
|
||||
return _isvalid ? _ipkey : "";
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return _isvalid ? _portkey : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if Datagram Socket is valid
|
||||
* @return true if valid
|
||||
*/
|
||||
public boolean getIsValid() {
|
||||
return _isvalid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get TX counter
|
||||
* @return TX counter
|
||||
*/
|
||||
public long getTXCounter() {
|
||||
return _txcount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Duration between creation and now
|
||||
* @return duration in tick
|
||||
*/
|
||||
public long getDuration() {
|
||||
return _isvalid ? DateTime.getNow() - create_time : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send Data
|
||||
* @param udpsock : datagram socket to use for sending data
|
||||
* @param bb : bytes to send
|
||||
* @return 0 if failed, bb.length if success
|
||||
*/
|
||||
public int SendData(DatagramSocket udpsock, byte[] bb) {
|
||||
if (_isvalid) {
|
||||
if (udpsock instanceof DatagramSocket) {
|
||||
DatagramPacket pkg = new DatagramPacket(bb,bb.length, _sock);
|
||||
try {
|
||||
udpsock.send(pkg);
|
||||
_txcount+=bb.length;
|
||||
return bb.length;
|
||||
} catch (IOException e) {
|
||||
log("SendData failed, Msg : "+e.getMessage());
|
||||
if (javaobject instanceof CustomSocketEvent) {
|
||||
javaobject.socketclosed(this);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
public int SendData_withLengthLimit(DatagramSocket udpsock , byte[] bb, final int datalength, final int maxlengthperpacket) {
|
||||
if (_isvalid) {
|
||||
if (udpsock instanceof DatagramSocket) {
|
||||
ByteBuffer bx = ByteBuffer.wrap(bb,0,datalength);
|
||||
bx.rewind();
|
||||
int sentcounter = 0;
|
||||
|
||||
int xx = bx.capacity() / maxlengthperpacket;
|
||||
int yy = bx.capacity() % maxlengthperpacket;
|
||||
if (xx>0) {
|
||||
for(int ii=0;ii<xx;ii++) {
|
||||
byte[] mm = new byte[maxlengthperpacket];
|
||||
bx.get(mm);
|
||||
int sent = SendData(udpsock, mm);
|
||||
if (sent>0) {
|
||||
sentcounter+=sent;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (yy>0) {
|
||||
byte[] nn = new byte[yy];
|
||||
bx.get(nn);
|
||||
int sent = SendData(udpsock, nn);
|
||||
if (sent>0) {
|
||||
sentcounter+=sent;
|
||||
}
|
||||
}
|
||||
|
||||
return sentcounter;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
938
src/MastersenderRelated/MasterSender.java
Normal file
938
src/MastersenderRelated/MasterSender.java
Normal file
@@ -0,0 +1,938 @@
|
||||
package MastersenderRelated;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
import anywheresoftware.b4a.BA;
|
||||
|
||||
import anywheresoftware.b4a.objects.collections.Map;
|
||||
|
||||
@BA.ShortName("MasterNetworkSender")
|
||||
@BA.Events(values= {
|
||||
"mastersenderlog(msg as string)",
|
||||
"udpmaster(isactive as boolean)",
|
||||
"tcpmaster(isactive as boolean)",
|
||||
"tcpsentcount(value as long)",
|
||||
"udpsentcount(value as long)",
|
||||
"tcpsentlog(targetip as string, targetport as int, bytecount as int)",
|
||||
"udpsentlog(targetip as string, targetport as int, bytecount as int)",
|
||||
"socketisclosed(socket_address as string, socket_port as int)"
|
||||
})
|
||||
|
||||
public class MasterSender implements CustomSocketEvent {
|
||||
|
||||
private BA ba;
|
||||
private String event;
|
||||
|
||||
private boolean need_log_event =false;
|
||||
private boolean need_udpmaster_event = false;
|
||||
private boolean need_tcpmaster_event = false;
|
||||
private boolean need_tcpsentcount_event = false;
|
||||
private boolean need_udpsentcount_event = false;
|
||||
private boolean need_tcpsentlog_event = false;
|
||||
private boolean need_udpsentlog_event = false;
|
||||
private boolean need_socketisclosed_event = false;
|
||||
|
||||
private boolean inited = false;
|
||||
|
||||
private DatagramSocket udpmaster;
|
||||
private boolean udpmaster_is_starting = false;
|
||||
private final int udp_packet_size = 1000;
|
||||
private Map udp_clients_map;
|
||||
private long udp_bytesent = 0;
|
||||
|
||||
private ServerSocket tcpmaster;
|
||||
private boolean tcpmaster_is_starting = false;
|
||||
private InetSocketAddress tcp_master_sa;
|
||||
private Map tcp_clients_map;
|
||||
private long tcp_bytesent = 0;
|
||||
|
||||
private MasterSenderEvent javaevent;
|
||||
private Object myobject;
|
||||
|
||||
public void Initialize(BA bax, Object caller, String eventname) {
|
||||
ba = bax;
|
||||
event = eventname;
|
||||
myobject = caller;
|
||||
if (ba instanceof BA) {
|
||||
if (myobject != null) {
|
||||
if (!event.isEmpty()) {
|
||||
need_log_event = ba.subExists(event+"_mastersenderlog");
|
||||
need_udpmaster_event = ba.subExists(event+"_udpmaster");
|
||||
need_tcpmaster_event = ba.subExists(event+"_tcpmaster");
|
||||
need_tcpsentcount_event = ba.subExists(event+"_tcpsentcount");
|
||||
need_udpsentcount_event = ba.subExists(event+"_udpsentcount");
|
||||
need_tcpsentlog_event = ba.subExists(event+"_tcpsentlog");
|
||||
need_udpsentlog_event = ba.subExists(event+"_udpsentlog");
|
||||
need_socketisclosed_event = ba.subExists(event+"_socketisclosed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
udp_clients_map = new Map();
|
||||
udp_clients_map.Initialize();
|
||||
tcp_clients_map = new Map();
|
||||
tcp_clients_map.Initialize();
|
||||
|
||||
|
||||
|
||||
inited = true;
|
||||
|
||||
Runtime.getRuntime().addShutdownHook(new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
Stop_TCP_Master();
|
||||
Stop_UDP_Master();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public boolean IsInitialized() {
|
||||
return inited;
|
||||
}
|
||||
|
||||
public boolean TCP_Clients_exists() {
|
||||
if (tcp_clients_map instanceof Map) {
|
||||
if (tcp_clients_map.IsInitialized()) {
|
||||
if (tcp_clients_map.getSize()>0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean UDP_Clients_exists() {
|
||||
if (udp_clients_map instanceof Map) {
|
||||
if (udp_clients_map.IsInitialized()) {
|
||||
if (udp_clients_map.getSize()>0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@BA.Hide
|
||||
public void setJavaEvent(MasterSenderEvent value) {
|
||||
javaevent = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get how much bytes already sent via TCP Master
|
||||
* @return positive numbers on sending, or 0 if idle
|
||||
*/
|
||||
public long Get_TCP_SentBytes() {
|
||||
return tcp_bytesent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get TCP Server Listening Port
|
||||
* @return positive number : listening port. 0 / -1 = not listening
|
||||
*/
|
||||
public int Get_TCP_Master_ListeningPort() {
|
||||
if (tcpmaster instanceof ServerSocket) {
|
||||
return tcpmaster.getLocalPort();
|
||||
} else return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get TCP Server Ethernet IP
|
||||
* @return empty string if not listening
|
||||
*/
|
||||
public String Get_TCP_Master_EthernetIP() {
|
||||
if (tcpmaster instanceof ServerSocket) {
|
||||
if (tcpmaster.isClosed()) {
|
||||
return "";
|
||||
} else {
|
||||
InetAddress xx = tcpmaster.getInetAddress();
|
||||
if (xx==null) {
|
||||
return "";
|
||||
} else return xx.getHostAddress();
|
||||
}
|
||||
} else return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Send data bytes to targetlist
|
||||
* @param bb : data to send
|
||||
* @param length : length of bytes to send
|
||||
* @return 0 = not sent, length = sent
|
||||
*/
|
||||
public int SendData_to_TCP_Clients(byte[] bb, int length) {
|
||||
if (bb==null) return 0;
|
||||
if (length<1) return 0;
|
||||
if (bb.length<length) return 0; // data yang mau dikirim lebih kecil daripada jumlah kirim, error !
|
||||
|
||||
if (!TCP_Clients_exists()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
boolean sendingsomething = false;
|
||||
|
||||
int tcpclientsize = tcp_clients_map.getSize();
|
||||
|
||||
for(int ii=0;ii<tcpclientsize;ii++) {
|
||||
Object sk = tcp_clients_map.GetValueAt(ii);
|
||||
|
||||
//revisi dari coding di bawah (15/1/2021)
|
||||
if (sk instanceof TCPSocketForMasterSender) {
|
||||
TCPSocketForMasterSender tcp = (TCPSocketForMasterSender) sk;
|
||||
|
||||
if (!tcp.getIsValid()) {
|
||||
raise_log_event("Unable to TCP Send "+length+" bytes to "+tcp.getKey()+" , socket is invalid");
|
||||
continue;
|
||||
}
|
||||
if (!tcp.getIsConnected()) {
|
||||
raise_log_event("Unable to TCP Send "+length+" bytes to "+tcp.getKey()+" , socket is closed");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tcp.SendData(bb)>0) {
|
||||
sendingsomething =true;
|
||||
raise_tcpsentlog_event(tcp.getIP(), tcp.getPort(), length);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ganti ke class TCPSocketForMasterSender
|
||||
// if (sk instanceof Socket) {
|
||||
// @SuppressWarnings("resource")
|
||||
// Socket sock = (Socket) sk;
|
||||
// String sock_address = sock.getInetAddress().getHostAddress();
|
||||
// int sock_port = sock.getPort();
|
||||
// if (sock.isClosed()) {
|
||||
// raise_log_event("Unable to TCP Send "+length+" bytes to "+sock_address+":"+sock_port+" , socket is closed");
|
||||
// raise_socketisclosed_event(sock_address, sock_port);
|
||||
// if (tcp_clients_map.Remove(sock_address)!=null) {
|
||||
// raise_log_event("Removed "+sock_address+" from tcp_clients_map");
|
||||
// }
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
//
|
||||
// try {
|
||||
// OutputStream fos = sock.getOutputStream();
|
||||
// if (fos instanceof OutputStream) {
|
||||
// fos.write(bb,0, length);
|
||||
// raise_tcpsentlog_event(sock_address, sock_port, length);
|
||||
// sendingsomething = true;
|
||||
// } else {
|
||||
// raise_log_event("Unable to TCP Send "+length+" bytes to "+sock_address+":"+sock_port+", OutputStream is null");
|
||||
// raise_socketisclosed_event(sock_address, sock_port);
|
||||
// if (tcp_clients_map.Remove(sock_address)!=null) {
|
||||
// raise_log_event("Removed "+sock_address+" from tcp_clients_map");
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//
|
||||
//
|
||||
// } catch (IOException e) {
|
||||
// raise_log_exception("SendData_to_TCP_Clients","Socket.OutputStream.Write",e);
|
||||
//
|
||||
// }
|
||||
//
|
||||
// }
|
||||
//
|
||||
|
||||
} // selesai for(int ii=0;ii<tcpclientsize;ii++)
|
||||
|
||||
|
||||
if (sendingsomething) {
|
||||
tcp_bytesent+=length;
|
||||
raise_tcpsentcount_event(tcp_bytesent);
|
||||
return length;
|
||||
} else return 0;
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop TCP Master, and close all sockets accepted
|
||||
*/
|
||||
public void Stop_TCP_Master() {
|
||||
tcpmaster_is_starting = false;
|
||||
if (TCP_Clients_exists()) {
|
||||
int mapsize = tcp_clients_map.getSize();
|
||||
for(int ii=0;ii<mapsize;ii++) {
|
||||
Object cl = tcp_clients_map.GetValueAt(ii);
|
||||
// revisi 15/1/2021
|
||||
if (cl instanceof TCPSocketForMasterSender) {
|
||||
TCPSocketForMasterSender tcp = (TCPSocketForMasterSender) cl;
|
||||
if (tcp.getIsValid()) {
|
||||
tcp.Close();
|
||||
raise_socketisclosed_event(tcp.getIP(),tcp.getPort());
|
||||
}
|
||||
}
|
||||
|
||||
// revisi pakai TCPSocketForMasterSender
|
||||
// if (cl instanceof Socket) {
|
||||
// Socket client = (Socket) cl;
|
||||
// try {
|
||||
// client.close();
|
||||
// } catch (IOException e) {
|
||||
// raise_log_exception("Stop_TCP_Master","Close Socket",e);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
tcp_clients_map.Clear();
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (tcpmaster instanceof ServerSocket) {
|
||||
try {
|
||||
tcpmaster.close();
|
||||
} catch (IOException e) {
|
||||
raise_log_exception("Stop_TCP_Master","Close",e);
|
||||
}
|
||||
tcpmaster = null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public boolean getTCPMasterRunning() {
|
||||
return tcpmaster_is_starting;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Local IP Address used by default network interface used by system
|
||||
* @return IP address , or empty string if failed
|
||||
*/
|
||||
public String GetDefaultNetwork_IPAddress() {
|
||||
|
||||
try {
|
||||
DatagramSocket dgs = new DatagramSocket();
|
||||
dgs.connect(InetAddress.getByName("8.8.8.8"),53);
|
||||
String result = dgs.getLocalAddress().getHostAddress();
|
||||
dgs.close();
|
||||
return result;
|
||||
} catch (SocketException | UnknownHostException e) {
|
||||
raise_log_exception("GetDefaultNetwork_IPAddress","",e);
|
||||
return "";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Add new TCP Clients for streaming
|
||||
* @param iptarget : ip address
|
||||
* @param targetport : port number
|
||||
* @return true if success
|
||||
*/
|
||||
public boolean Add_TCP_Target(String iptarget, int targetport) {
|
||||
if (iptarget.isEmpty()) {
|
||||
raise_log_event("Add_TCP_Target failed, iptarget is empty");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (targetport<1) {
|
||||
raise_log_event("Add_TCP_Target failed , targetport less than 1");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (targetport>65535) {
|
||||
raise_log_event("Add_TCP_Target failed, targetport more than 65535");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(tcp_clients_map instanceof Map)) {
|
||||
raise_log_event("Add_TCP_Target failed, tcp_clients_map is null");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(tcp_clients_map.IsInitialized())) {
|
||||
raise_log_event("Add_TCP_Target failed, tcp_clients_map is not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
Socket trysock = new Socket(iptarget, targetport);
|
||||
if (trysock.isConnected()) {
|
||||
TCPSocketForMasterSender tcpx = new TCPSocketForMasterSender(iptarget, targetport, trysock, this);
|
||||
Object old = tcp_clients_map.Put(iptarget, tcpx);
|
||||
if (old instanceof TCPSocketForMasterSender) {
|
||||
TCPSocketForMasterSender tcpold = (TCPSocketForMasterSender) old;
|
||||
tcpold.Close();
|
||||
raise_log_event("Replacing Old Socket "+tcpold.getKey()+" with New Socket "+tcpx.getKey());
|
||||
} else {
|
||||
raise_log_event("Add new TCP Client "+tcpx.getKey());
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
raise_log_event("Add_TCP_Target failed, Can't connect with "+iptarget+":"+targetport);
|
||||
trysock.close();
|
||||
return false;
|
||||
}
|
||||
} catch (UnknownHostException e) {
|
||||
raise_log_event("Add_TCP_target failed, Msg : "+e.getMessage());
|
||||
return false;
|
||||
} catch (IOException e) {
|
||||
raise_log_event("Add_TCP_target failed, Msg : "+e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start TCP Master (Listening server)
|
||||
* @param ethernetip : ethernet IP to bind with ServerSocket. Put empty string for all ip
|
||||
* @param listeningport : listening port. put 0 = random port number
|
||||
* @return true if tcp master started
|
||||
*/
|
||||
public boolean Start_TCP_Master(String ethernetip, int listeningport) {
|
||||
tcp_bytesent = 0;
|
||||
|
||||
|
||||
if (listeningport<0) listeningport = 0;
|
||||
|
||||
if (tcpmaster instanceof ServerSocket) {
|
||||
try {
|
||||
tcpmaster.close();
|
||||
} catch (IOException e) {
|
||||
raise_log_exception("Start_TCP_Master","Initial tcpmaster close",e);
|
||||
}
|
||||
tcpmaster = null;
|
||||
}
|
||||
|
||||
if (ethernetip.isEmpty()) {
|
||||
// cari default outbond
|
||||
ethernetip = GetDefaultNetwork_IPAddress();
|
||||
if (ethernetip.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
tcpmaster = new ServerSocket();
|
||||
tcp_master_sa = new InetSocketAddress(ethernetip,listeningport);
|
||||
tcpmaster.bind(tcp_master_sa);
|
||||
} catch(IOException e) {
|
||||
raise_log_exception("Start_TCP_Master","New ServerSocket",e);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
tcpmasterthread tmt = new tcpmasterthread(this);
|
||||
tmt.start();
|
||||
return true;
|
||||
}
|
||||
|
||||
// tcp client harus socket.conect dulu, baru dimasukkan ke tcp_clients_map
|
||||
private class tcpmasterthread extends Thread{
|
||||
private final CustomSocketEvent event;
|
||||
public tcpmasterthread(CustomSocketEvent mainevent) {
|
||||
event = mainevent;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
raise_tcpmaster_event(true);
|
||||
tcpmaster_is_starting = true;
|
||||
while(true) {
|
||||
if (!tcpmaster_is_starting) break;
|
||||
if (!(tcpmaster instanceof ServerSocket)) break;
|
||||
if (tcpmaster.isClosed()) break;
|
||||
|
||||
Socket xx;
|
||||
try {
|
||||
xx = tcpmaster.accept();
|
||||
} catch (IOException e) {
|
||||
if (e.getMessage().equalsIgnoreCase("socket closed")) {
|
||||
// socket closed, exit thread
|
||||
break;
|
||||
}
|
||||
raise_log_exception("TCP Master Thread","Accept",e);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (xx instanceof Socket) {
|
||||
|
||||
InetAddress cc = xx.getInetAddress();
|
||||
int port = xx.getPort();
|
||||
if (cc!=null) {
|
||||
String senderip = cc.getHostAddress();
|
||||
|
||||
// revisi pakai TCPSocketForMasterSender (15/1/2021)
|
||||
TCPSocketForMasterSender newsock = new TCPSocketForMasterSender(senderip, port, xx, event);
|
||||
|
||||
Object old = tcp_clients_map.Put(senderip, newsock);
|
||||
if (old instanceof TCPSocketForMasterSender) {
|
||||
TCPSocketForMasterSender oldsock = (TCPSocketForMasterSender) old;
|
||||
oldsock.Close();
|
||||
raise_log_event("TCP Client IP="+senderip+":"+port+" replacing IP="+oldsock.getKey());
|
||||
|
||||
} else {
|
||||
raise_log_event("Add New TCP Client IP="+senderip+":"+port);
|
||||
}
|
||||
|
||||
// revisi (15/1/2021)
|
||||
// if (old instanceof Socket) {
|
||||
// Socket oldsock = (Socket) old;
|
||||
// raise_log_event("TCP Client IP="+senderip+":"+port+" replacing IP="+oldsock.getInetAddress().getHostAddress()+":"+oldsock.getPort());
|
||||
// try {
|
||||
// oldsock.close();
|
||||
// } catch (IOException e) {
|
||||
// raise_log_exception("TCP Master Thread","Close old Socket",e);
|
||||
// }
|
||||
// oldsock = null;
|
||||
// } else {
|
||||
// raise_log_event("Add New TCP Client IP="+senderip+":"+port);
|
||||
// }
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
raise_tcpmaster_event(false);
|
||||
tcpmaster_is_starting = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send Data to target list via UDP
|
||||
* @param bb : data to send, size must be same or larger than length
|
||||
* @param length : length of bytes to send
|
||||
* @return 0 if failed, or length if data sent
|
||||
*/
|
||||
public int SendData_to_UDP_Clients(byte[] bb, int length) {
|
||||
if (!(udpmaster instanceof DatagramSocket)) {
|
||||
raise_log_event("SendData_to_UDP_Clients failed, udpmaster is null");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (length<1) {
|
||||
raise_log_event("SendData_to_UDP_Clients failed, length invalid");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (bb==null) {
|
||||
raise_log_event("SendData_to_UDP_Clients failed, data isn null");
|
||||
return 0;
|
||||
}
|
||||
if (bb.length<length) {
|
||||
raise_log_event("SendData_to_UDP_Clients failed, data length not same as length info");
|
||||
return 0; // data yang mau dikirim , lebih kecil dari jumlah yang mau dikirim, fault !
|
||||
}
|
||||
|
||||
if (!UDP_Clients_exists()) {
|
||||
raise_log_event("SendData_to_UDP_Clients failed, UDP_Clients is not exists" );
|
||||
return 0;
|
||||
}
|
||||
|
||||
boolean sendingsomething = false; // indikator pernah ngirim sesuatu lewat UDP
|
||||
int mapsize = udp_clients_map.getSize();
|
||||
for(int ii=0;ii<mapsize;ii++) {
|
||||
Object cl = udp_clients_map.GetValueAt(ii);
|
||||
if (cl instanceof DatagramSocketForMasterSender) {
|
||||
DatagramSocketForMasterSender udp = (DatagramSocketForMasterSender) cl;
|
||||
if (udp.getIsValid()) {
|
||||
int sentcounter = udp.SendData_withLengthLimit(udpmaster, bb, length, 1000);
|
||||
if (sentcounter>0) {
|
||||
sendingsomething = true;
|
||||
raise_udpsentlog_event(udp.getIP(), udp.getPort(), sentcounter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// revisi 15/1/2021, pakai DatagramSocketForMasterSender
|
||||
// if (cl instanceof InetSocketAddress) {
|
||||
// InetSocketAddress client = (InetSocketAddress) cl;
|
||||
// InetAddress ipx = client.getAddress();
|
||||
// int port = client.getPort();
|
||||
//
|
||||
// ByteBuffer bx = ByteBuffer.wrap(bb, 0, length);
|
||||
// bx.rewind();
|
||||
//
|
||||
// boolean sendinghere = false; // indikator ngirim sesuatu di ipx : port
|
||||
// int xx = length / 1000;
|
||||
// int yy = length % 1000;
|
||||
//
|
||||
// if (xx>0) {
|
||||
// for(int jj=0;jj<xx;jj++) {
|
||||
// byte[] mm = new byte[1000];
|
||||
// bx.get(mm);
|
||||
// if (sending_bytes_to_udp(mm, ipx, port)) {
|
||||
// sendingsomething = true;
|
||||
// sendinghere = true;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if (yy>0) {
|
||||
// byte[] nn = new byte[yy];
|
||||
// bx.get(nn);
|
||||
// if (sending_bytes_to_udp(nn, ipx, port)) {
|
||||
// sendingsomething = true;
|
||||
// sendinghere = true;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if (sendinghere) {
|
||||
// raise_udpsentlog_event(ipx.getHostAddress(), port, length);
|
||||
// }
|
||||
//
|
||||
//
|
||||
// } else {
|
||||
// raise_log_event("Object at udp_clients_map index "+ii+" is not InetSocketAddress");
|
||||
//
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if (sendingsomething) {
|
||||
udp_bytesent+=length;
|
||||
raise_udpsentcount_event(udp_bytesent);
|
||||
return length;
|
||||
}else return 0;
|
||||
}
|
||||
|
||||
|
||||
// private boolean sending_bytes_to_udp(byte[] xx, InetAddress target, int port) {
|
||||
// if (udpmaster==null) return false;
|
||||
// DatagramPacket pkg = new DatagramPacket(xx, xx.length, target, port);
|
||||
// try {
|
||||
// udpmaster.send(pkg);
|
||||
// return true;
|
||||
// } catch(IOException e) {
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
|
||||
public long Get_UDP_SentBytes() {
|
||||
return udp_bytesent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get UDP Master Listening Port
|
||||
* @return -1 if invalid , or positive numbers
|
||||
*/
|
||||
public int Get_UDP_Master_ListeningPort() {
|
||||
if (udpmaster instanceof DatagramSocket) {
|
||||
return udpmaster.getLocalPort();
|
||||
} else return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Ethernet IP used by UDP Master
|
||||
* @return empty string if invalid
|
||||
*/
|
||||
public String Get_UDP_Master_EthernetIP() {
|
||||
if (udpmaster instanceof DatagramSocket) {
|
||||
InetAddress loc = udpmaster.getLocalAddress();
|
||||
if (loc!=null) {
|
||||
return loc.getHostAddress();
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
|
||||
} else return "";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Stop UDP Master
|
||||
*/
|
||||
public void Stop_UDP_Master() {
|
||||
if (udpmaster instanceof DatagramSocket) {
|
||||
udpmaster.close();
|
||||
udpmaster = null;
|
||||
}
|
||||
udpmaster_is_starting = false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Add an UDP target to udp_clients_map
|
||||
* @param iptarget : Ip address
|
||||
* @param targetport : port
|
||||
* @return true if can be add to UDP List
|
||||
*/
|
||||
public boolean Add_UDP_Target(String iptarget, int targetport) {
|
||||
|
||||
if (iptarget.isEmpty()) {
|
||||
raise_log_event("Add_UDP_Target failed, targetip is empty");
|
||||
|
||||
return false;
|
||||
}
|
||||
if (targetport<1) {
|
||||
raise_log_event("Add_UDP_Target failed, targetport less than 1");
|
||||
return false;
|
||||
}
|
||||
if (targetport>65535) {
|
||||
raise_log_event("Add_UDP_Target failed, targetport more than 65535");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (!(udpmaster instanceof DatagramSocket)) {
|
||||
raise_log_event("Add_UDP_Target failed, udpmaster is null");
|
||||
return false;
|
||||
}
|
||||
if (!(udp_clients_map instanceof Map)) {
|
||||
raise_log_event("Add_UDP_Target failed, udp_clients_map is null");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!udp_clients_map.IsInitialized()) {
|
||||
raise_log_event("Add_UDP_Target failed, udp_clients_map is not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
InetSocketAddress sa;
|
||||
try {
|
||||
sa = new InetSocketAddress(iptarget,targetport);
|
||||
} catch(IllegalArgumentException e) {
|
||||
raise_log_event("Add_UDP_Target failed, new InetSocketAddress exception, Msg="+e.getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sa.isUnresolved()) {
|
||||
raise_log_event("Add_UDP_Target failed, InetSocketAddress is not resolved");
|
||||
return false;
|
||||
}
|
||||
|
||||
DatagramSocketForMasterSender udp = new DatagramSocketForMasterSender(iptarget, targetport, this);
|
||||
Object old = udp_clients_map.Put(iptarget, udp);
|
||||
if (old instanceof DatagramSocketForMasterSender) {
|
||||
DatagramSocketForMasterSender oldudp = (DatagramSocketForMasterSender) old;
|
||||
raise_log_event(udp.getKey()+" is replacing "+oldudp.getKey()+" in udp_clients_map");
|
||||
} else {
|
||||
raise_log_event(udp.getKey()+" is added to udp_clients_map");
|
||||
}
|
||||
|
||||
// revisi 15/1/2021
|
||||
//udp_clients_map.Put(iptarget, sa);
|
||||
//raise_log_event("InetSocketAddress "+iptarget+":"+targetport+" is added to udp_clients_map");
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Open UDP Master (Listening Server)
|
||||
* UDP Master hanya untuk kirim data ke UDP clients, tidak terima data balik
|
||||
* UDP Clients harus kirim sesuatu dulu ke sini, supaya dimasukkan dalam udp_clients_map
|
||||
* @param ethernetip : ethernet ip untuk set listening di ethernet mana. Kalau semua, kasih string kosong
|
||||
* @param listenport : listening port. kalau 0, udp port random
|
||||
* @return true if success
|
||||
*/
|
||||
public boolean Start_UDP_Master(String ethernetip, int listenport) {
|
||||
udp_bytesent = 0;
|
||||
if (udp_clients_map==null) {
|
||||
udp_clients_map = new Map();
|
||||
udp_clients_map.Initialize();
|
||||
}
|
||||
|
||||
if (udpmaster!=null) {
|
||||
udpmaster.disconnect();
|
||||
udpmaster.close();
|
||||
udpmaster = null;
|
||||
}
|
||||
|
||||
if (listenport<1) {
|
||||
listenport = 0;
|
||||
}
|
||||
|
||||
if (ethernetip.isEmpty()) {
|
||||
ethernetip = GetDefaultNetwork_IPAddress();
|
||||
}
|
||||
|
||||
try {
|
||||
InetAddress local = InetAddress.getByName(ethernetip);
|
||||
udpmaster = new DatagramSocket(listenport, local);
|
||||
} catch(UnknownHostException e) {
|
||||
raise_log_event("Unable to determine InetAddress from "+ethernetip);
|
||||
return false;
|
||||
} catch(SocketException e) {
|
||||
raise_log_event("Unable to create DatagramSocket at port "+listenport);
|
||||
return false;
|
||||
} catch(SecurityException e) {
|
||||
raise_log_event("Unable to create DatagramSocket at port "+listenport+" because of Security reason");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
raise_log_event("Start_UDP_Master success, DatagramSocket created at port "+udpmaster.getLocalPort());
|
||||
|
||||
udpmasterthread umt = new udpmasterthread(this);
|
||||
umt.start();
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// UDP Master hanya untuk kirim data streaming, tidak terima data balik
|
||||
// client harus kirim sesuatu supaya dimasukkan ke udp_clients_map
|
||||
private class udpmasterthread extends Thread {
|
||||
private final CustomSocketEvent event;
|
||||
public udpmasterthread(CustomSocketEvent mainevent) {
|
||||
event = mainevent;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
raise_udpmaster_event(true);
|
||||
|
||||
udpmaster_is_starting = true;
|
||||
while(true) {
|
||||
if (udpmaster==null) break;
|
||||
if (udpmaster.isClosed()) break;
|
||||
if (!udpmaster_is_starting) break;
|
||||
DatagramPacket pkg = new DatagramPacket(new byte[udp_packet_size],udp_packet_size);
|
||||
try {
|
||||
udpmaster.receive(pkg);
|
||||
} catch (IOException e) {
|
||||
if (e.getMessage().compareToIgnoreCase("socket closed")!=0) {
|
||||
// kalau socket closed, itu biasa ..
|
||||
// kalau bukan socket closed, raise_log_exception
|
||||
raise_log_exception("udpmasterthread","receive",e);
|
||||
}
|
||||
|
||||
pkg = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pkg instanceof DatagramPacket) {
|
||||
String senderip = pkg.getAddress().getHostAddress();
|
||||
int senderport = pkg.getPort();
|
||||
|
||||
DatagramSocketForMasterSender newudp = new DatagramSocketForMasterSender(senderip,senderport, event);
|
||||
if (newudp.getIsValid()) {
|
||||
Object old = udp_clients_map.Put(senderip, newudp);
|
||||
if (old instanceof DatagramSocketForMasterSender) {
|
||||
DatagramSocketForMasterSender oldudp = (DatagramSocketForMasterSender) old;
|
||||
raise_log_event("New UDP Client IP="+newudp.getKey()+" is replacing Old UDP Client IP="+oldudp.getKey());
|
||||
} else {
|
||||
raise_log_event("Add new UDP Client IP="+newudp.getKey());
|
||||
}
|
||||
} else raise_log_event("Unable to Add UDP Client IP="+senderip+":"+senderport+", Invalid DatagramSocketForMasterSender");
|
||||
|
||||
// revisi 15/1/2021
|
||||
// String senderip = pkg.getAddress().getHostAddress();
|
||||
// InetSocketAddress sender_sa = (InetSocketAddress) pkg.getSocketAddress();
|
||||
// Object oldobject = udp_clients_map.Put(senderip, sender_sa);
|
||||
// if (oldobject instanceof InetSocketAddress) {
|
||||
// InetSocketAddress old = (InetSocketAddress) oldobject;
|
||||
// raise_log_event("UDP Client IP="+sender_sa.getAddress().getHostAddress()+":"+sender_sa.getPort()+" replacing IP="+old.getAddress().getHostAddress()+":"+old.getPort());
|
||||
// } else {
|
||||
// raise_log_event("Add new UDP client IP="+sender_sa.getAddress().getHostAddress()+":"+sender_sa.getPort());
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
raise_udpmaster_event(false);
|
||||
udpmaster_is_starting = false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean getUDPMasterRunning() {
|
||||
return udpmaster_is_starting;
|
||||
}
|
||||
|
||||
private void raise_log_exception(String function, String command, Exception e) {
|
||||
raise_log_event("Exception on Function="+function+", Command="+command+", Msg="+e.getMessage()+", Caused="+e.getCause());
|
||||
}
|
||||
|
||||
private void raise_log_event(String msg) {
|
||||
if (need_log_event) ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_mastersenderlog", false, new Object[] {msg});
|
||||
if (javaevent!=null) javaevent.mastersenderlog(msg);
|
||||
}
|
||||
|
||||
private void raise_udpmaster_event(boolean isactive) {
|
||||
if (need_udpmaster_event) ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_udpmaster", false, new Object[] {isactive});
|
||||
if (javaevent!=null) javaevent.udpmaster(isactive);
|
||||
}
|
||||
|
||||
private void raise_tcpmaster_event(boolean isactive) {
|
||||
if (need_tcpmaster_event) ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_tcpmaster", false, new Object[] {isactive});
|
||||
if (javaevent!=null) javaevent.tcpmaster(isactive);
|
||||
}
|
||||
|
||||
private void raise_udpsentcount_event(long value) {
|
||||
if (need_udpsentcount_event) ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_udpsentcount", false, new Object[] {value});
|
||||
if (javaevent!=null) javaevent.udpsentcount(value);
|
||||
}
|
||||
|
||||
private void raise_tcpsentcount_event(long value) {
|
||||
if (need_tcpsentcount_event) ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_tcpsentcount", false, new Object[] {value});
|
||||
if (javaevent!=null) javaevent.tcpsentcount(value);
|
||||
}
|
||||
|
||||
private void raise_tcpsentlog_event(String targetip, int targetport, int bytecount) {
|
||||
if (need_tcpsentlog_event) ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_tcpsentlog", false, new Object[] {targetip, targetport, bytecount});
|
||||
if (javaevent!=null) javaevent.tcpsentlog(targetip, targetport, bytecount);
|
||||
}
|
||||
|
||||
private void raise_udpsentlog_event(String targetip, int targetport, int bytecount) {
|
||||
if (need_udpsentlog_event) ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_udpsentlog", false, new Object[] {targetip, targetport, bytecount});
|
||||
if (javaevent!=null) javaevent.udpsentlog(targetip, targetport, bytecount);
|
||||
}
|
||||
|
||||
private void raise_socketisclosed_event(String socket_address, int socket_port) {
|
||||
if (need_socketisclosed_event) ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_socketisclosed", false, new Object[] {socket_address, socket_port});
|
||||
if (javaevent!=null) javaevent.socketisclosed(socket_address, socket_port);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Event dari TCPSocketForMasterSender dan DatagramSocketForMasterSender
|
||||
*/
|
||||
@Override
|
||||
@BA.Hide
|
||||
public void log(Object sender, String msg) {
|
||||
if (sender instanceof TCPSocketForMasterSender) {
|
||||
TCPSocketForMasterSender tcpx = (TCPSocketForMasterSender) sender;
|
||||
raise_log_event("Log from "+tcpx.getKey()+" = "+msg);
|
||||
} else if (sender instanceof DatagramSocketForMasterSender) {
|
||||
DatagramSocketForMasterSender udpx = (DatagramSocketForMasterSender) sender;
|
||||
raise_log_event("Log from "+udpx.getKey()+" = "+msg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@BA.Hide
|
||||
public void socketclosed(Object sender) {
|
||||
if (sender instanceof TCPSocketForMasterSender) {
|
||||
TCPSocketForMasterSender tcpx = (TCPSocketForMasterSender) sender;
|
||||
raise_socketisclosed_event(tcpx.getIP(), tcpx.getPort());
|
||||
if (tcp_clients_map instanceof Map) {
|
||||
if (tcp_clients_map.IsInitialized()) {
|
||||
Object old = tcp_clients_map.Remove(tcpx.getIP());
|
||||
if (old instanceof TCPSocketForMasterSender) {
|
||||
TCPSocketForMasterSender tcpold = (TCPSocketForMasterSender) old;
|
||||
raise_log_event(tcpold.getKey()+" is removed from tcp_clients_map");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (sender instanceof DatagramSocketForMasterSender) {
|
||||
DatagramSocketForMasterSender udpx = (DatagramSocketForMasterSender) sender;
|
||||
raise_socketisclosed_event(udpx.getIP(), udpx.getPort());
|
||||
if (udp_clients_map instanceof Map) {
|
||||
if (udp_clients_map.IsInitialized()) {
|
||||
Object old = udp_clients_map.Remove(udpx.getIP());
|
||||
if (old instanceof DatagramSocketForMasterSender) {
|
||||
DatagramSocketForMasterSender udpold = (DatagramSocketForMasterSender) old;
|
||||
raise_log_event(udpold.getKey()+" is removed from udp_clients_map");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
12
src/MastersenderRelated/MasterSenderEvent.java
Normal file
12
src/MastersenderRelated/MasterSenderEvent.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package MastersenderRelated;
|
||||
|
||||
public interface MasterSenderEvent {
|
||||
void mastersenderlog(String log);
|
||||
void udpmaster(boolean isactive);
|
||||
void tcpmaster(boolean isactive);
|
||||
void tcpsentcount(long value);
|
||||
void udpsentcount(long value);
|
||||
void tcpsentlog(String targetip, int targetport, int bytecount);
|
||||
void udpsentlog(String targetip, int targetport, int bytecount);
|
||||
void socketisclosed(String socket_address, int socket_port);
|
||||
}
|
||||
168
src/MastersenderRelated/TCPSocketForMasterSender.java
Normal file
168
src/MastersenderRelated/TCPSocketForMasterSender.java
Normal file
@@ -0,0 +1,168 @@
|
||||
package MastersenderRelated;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
|
||||
import anywheresoftware.b4a.BA;
|
||||
import anywheresoftware.b4a.keywords.DateTime;
|
||||
|
||||
@BA.ShortName("MasterSender_TCPSocket")
|
||||
public class TCPSocketForMasterSender {
|
||||
private final String _ipkey;
|
||||
private final int _portkey;
|
||||
private boolean _isvalid=false;
|
||||
private OutputStream _outputstream = null;
|
||||
|
||||
private Socket _sock;
|
||||
private long create_time=0;
|
||||
private long _txcount=0;
|
||||
|
||||
private CustomSocketEvent javaobject;
|
||||
|
||||
@BA.Hide
|
||||
public TCPSocketForMasterSender(final String ipaddress, final int port, final Socket ss, final CustomSocketEvent event) {
|
||||
_ipkey = ipaddress;
|
||||
_portkey = port;
|
||||
_sock = ss;
|
||||
javaobject = event;
|
||||
|
||||
if (!_ipkey.isEmpty()) {
|
||||
if (_portkey>0) {
|
||||
if (_portkey<65535) {
|
||||
if (_sock instanceof Socket) {
|
||||
if (_sock.isConnected()) {
|
||||
|
||||
try {
|
||||
|
||||
_outputstream = _sock.getOutputStream();
|
||||
_isvalid = true;
|
||||
create_time = DateTime.getNow();
|
||||
} catch (IOException e) {
|
||||
log("Failed to Initialize TCPSocketForMasterSender, Msg : "+e.getMessage());
|
||||
|
||||
}
|
||||
return;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void log(String msg) {
|
||||
if (javaobject instanceof CustomSocketEvent) {
|
||||
javaobject.log(this, msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* Key String is [ipaddress]:[port]
|
||||
* @return Key string for this class, or return N/A if invalid
|
||||
*/
|
||||
public String getKey() {
|
||||
return _isvalid ? _ipkey+":"+_portkey : "N/A";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get IP Adddres
|
||||
* @return empty string if invalid
|
||||
*/
|
||||
public String getIP() {
|
||||
return _isvalid ? _ipkey : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get TCP Port
|
||||
* @return zero if invalid
|
||||
*/
|
||||
public int getPort() {
|
||||
return _isvalid ? _portkey : 0 ;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if TCP Socket is valid
|
||||
* @return true if valid
|
||||
*/
|
||||
public boolean getIsValid() {
|
||||
return _isvalid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if TCP Socket is connected
|
||||
* @return true if connected
|
||||
*/
|
||||
public boolean getIsConnected() {
|
||||
if (_sock instanceof Socket) {
|
||||
if (_sock.isClosed()==false) {
|
||||
return _sock.isConnected();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close TCP Socket
|
||||
*/
|
||||
public void Close() {
|
||||
if (_sock instanceof Socket) {
|
||||
try {
|
||||
_sock.close();
|
||||
} catch (IOException e) {
|
||||
log("Error closing "+getKey()+", Msg : "+e.getMessage());
|
||||
}
|
||||
}
|
||||
_sock = null;
|
||||
if (javaobject instanceof CustomSocketEvent) {
|
||||
javaobject.socketclosed(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Duration between creation and now
|
||||
* @return duration in tick
|
||||
*/
|
||||
public long getDuration() {
|
||||
return _isvalid ? DateTime.getNow() - create_time : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get TX counter
|
||||
* @return TX counter
|
||||
*/
|
||||
public long getTXCounter() {
|
||||
return _txcount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send Data to TCP Socket
|
||||
* @param bb : bytes to send
|
||||
* @return 0 if failed, else return bb.length
|
||||
*/
|
||||
public int SendData(byte[] bb) {
|
||||
if (getIsValid()) {
|
||||
if (getIsConnected()) {
|
||||
if (_outputstream instanceof OutputStream) {
|
||||
if (bb != null) {
|
||||
if (bb.length>0) {
|
||||
try {
|
||||
_outputstream.write(bb);
|
||||
_txcount+= bb.length;
|
||||
return bb.length;
|
||||
} catch (IOException e) {
|
||||
log("Failed to SendData to "+getKey()+", Msg : "+e.getMessage());
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
401
src/QZARelated/DataSender.java
Normal file
401
src/QZARelated/DataSender.java
Normal file
@@ -0,0 +1,401 @@
|
||||
package QZARelated;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.MulticastSocket;
|
||||
import java.net.SocketException;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
import anywheresoftware.b4a.BA;
|
||||
import anywheresoftware.b4a.objects.collections.List;
|
||||
|
||||
@BA.ShortName("NetworkDataSender")
|
||||
public class DataSender {
|
||||
private DataSenderEvent dse;
|
||||
private MulticastSocket mulsock = null;
|
||||
private InetAddress multicastip = null;
|
||||
private InetSocketAddress multicastsockaddress = null;
|
||||
private InetAddress multicast_ethernet = null;
|
||||
private int multicastport = 0;
|
||||
private boolean multicastready = false;
|
||||
|
||||
private DatagramSocket unisock = null;
|
||||
private List unicasttarget = null;
|
||||
private int unicastport = 0;
|
||||
private InetAddress unicast_ethernet = null;
|
||||
private boolean unicastready = false;
|
||||
protected long bytesent_multicast = 0;
|
||||
protected long bytesent_unicast = 0;
|
||||
protected long bytesent = 0;
|
||||
|
||||
private boolean is_unicasting = false;
|
||||
private boolean is_multicasting = false;
|
||||
|
||||
|
||||
|
||||
protected void SetDataSenderEvent(DataSenderEvent value) {
|
||||
dse = value;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Close Unicast Sending
|
||||
*/
|
||||
public void CloseUnicast() {
|
||||
if (unicasttarget!=null) {
|
||||
if (unicasttarget.getSize()>0) {
|
||||
StringBuilder str = new StringBuilder();
|
||||
for(int ii=0;ii<unicasttarget.getSize();ii++) {
|
||||
InetSocketAddress ipx = (InetSocketAddress) unicasttarget.Get(ii);
|
||||
if (str.length()>0) str.append(";");
|
||||
str.append(ipx.getAddress().getHostAddress());
|
||||
}
|
||||
|
||||
if (str.length()>0) {
|
||||
if (dse != null) {
|
||||
dse.stopstreaming(str.toString());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
unicasttarget.Clear();
|
||||
unicasttarget = null;
|
||||
}
|
||||
if (unisock!=null) {
|
||||
unisock.disconnect();
|
||||
unisock.close();
|
||||
unisock = null;
|
||||
}
|
||||
unicastready = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize Multicast Sending
|
||||
* @param MulticastIp : Multicast Destination IP
|
||||
* @param Port : Multicast Port
|
||||
* @param EthernetIP : Local Ethernet IP, for use with multiple ethernet cards
|
||||
* @return true if success
|
||||
*/
|
||||
public boolean SetMulticastTarget(String MulticastIp, int Port, String EthernetIP) {
|
||||
if (this.mulsock!=null) {
|
||||
this.mulsock.disconnect();
|
||||
this.mulsock.close();
|
||||
this.mulsock = null;
|
||||
}
|
||||
|
||||
if (Port<1) {
|
||||
raise_log_event("Invalid Target Multicast Port");
|
||||
return false;
|
||||
}
|
||||
|
||||
MulticastIp = MulticastIp.trim();
|
||||
if (MulticastIp.isEmpty()) {
|
||||
raise_log_event("Invalid Target Multicast IP");
|
||||
return false;
|
||||
}
|
||||
|
||||
this.multicastport=Port;
|
||||
this.multicastready = false;
|
||||
try {
|
||||
this.multicastip = InetAddress.getByName(MulticastIp);
|
||||
this.multicastsockaddress = new InetSocketAddress(this.multicastip, this.multicastport);
|
||||
} catch (UnknownHostException | SecurityException | IllegalArgumentException e) {
|
||||
raise_log_event("Invalid MulticastIP="+MulticastIp+", Exception="+e.getMessage()+", Caused="+e.getCause());
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
this.mulsock = new MulticastSocket();
|
||||
|
||||
} catch (IOException e) {
|
||||
raise_log_event("Unable to create MulticastSocket, Msg : "+e.getMessage()+", Caused : "+e.getCause());
|
||||
this.multicastip = null;
|
||||
this.multicastsockaddress = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!EthernetIP.isEmpty()) {
|
||||
try {
|
||||
this.multicast_ethernet = InetAddress.getByName(EthernetIP);
|
||||
} catch (UnknownHostException e1) {
|
||||
raise_log_event("Invalid Local EthernetIP="+EthernetIP);
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
mulsock.setInterface(this.multicast_ethernet);
|
||||
} catch (SocketException e) {
|
||||
raise_log_event("Unable to bind MulticastSocket with EthernetIP="+EthernetIP);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
mulsock.joinGroup(this.multicastip);
|
||||
} catch (IOException | SecurityException e) {
|
||||
raise_log_event("MulticastSocket unable to JoinGroup to "+MulticastIp+", Exception="+e.getMessage()+", Caused="+e.getCause());
|
||||
return false;
|
||||
}
|
||||
|
||||
multicastready = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close Multicast Sending
|
||||
*/
|
||||
public void CloseMulticast() {
|
||||
if (mulsock!=null) {
|
||||
if (multicastip!=null) {
|
||||
try {
|
||||
mulsock.leaveGroup(multicastip);
|
||||
if (dse!=null) {
|
||||
dse.stopstreaming(multicastip.getHostAddress());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
raise_log_event("MulticastSocket unable to LeaveGroup from "+multicastip.getHostAddress());
|
||||
}
|
||||
multicastip = null;
|
||||
}
|
||||
this.multicastsockaddress = null;
|
||||
mulsock.disconnect();
|
||||
mulsock.close();
|
||||
mulsock = null;
|
||||
}
|
||||
multicastready = false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Ethernet IP to bind with Multicast Socket
|
||||
* @return IP Address in String
|
||||
*/
|
||||
public String Multicast_Ethernet_IP() {
|
||||
if (multicast_ethernet==null)
|
||||
return "";
|
||||
else
|
||||
return multicast_ethernet.getHostAddress();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Ethernet IP to bind with Unicast Socket
|
||||
* @return IP Address in String
|
||||
*/
|
||||
public String Unicast_Ethernet_IP() {
|
||||
if (unicast_ethernet==null)
|
||||
return "";
|
||||
else
|
||||
return unicast_ethernet.getHostAddress();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize Unicast Sending
|
||||
* @param TargetIP : List of IP Address in string
|
||||
* @param Port : unicast port
|
||||
* @param EthernetIP : Local Ethernet IP, for use with multiple ethernet cards
|
||||
* @return true if success
|
||||
*/
|
||||
public boolean SetUnicastTarget(List TargetIP, int Port, String EthernetIP) {
|
||||
if (this.unisock!=null) {
|
||||
this.unisock.disconnect();
|
||||
this.unisock.close();
|
||||
this.unisock = null;
|
||||
}
|
||||
|
||||
if (Port<1) {
|
||||
raise_log_event("Invalid Target Unicast Port");
|
||||
return false;
|
||||
}
|
||||
|
||||
this.unicastport = Port;
|
||||
this.unicastready = false;
|
||||
if (unicasttarget==null) {
|
||||
unicasttarget = new List();
|
||||
}
|
||||
if (!unicasttarget.IsInitialized()) {
|
||||
unicasttarget.Initialize();
|
||||
} else unicasttarget.Clear();
|
||||
|
||||
if (TargetIP==null) {
|
||||
raise_log_event("Unicast TargetIP is null");
|
||||
return false;
|
||||
}
|
||||
if (TargetIP.getSize()<1) {
|
||||
raise_log_event("Unicast TargetIP is empty");
|
||||
return false;
|
||||
}
|
||||
for(int ii=0;ii<TargetIP.getSize();ii++) {
|
||||
Object xx = TargetIP.Get(ii);
|
||||
if (xx instanceof String) {
|
||||
String ip = (String) xx;
|
||||
try {
|
||||
InetAddress ipx = InetAddress.getByName(ip);
|
||||
InetSocketAddress ipsock = new InetSocketAddress(ipx, Port);
|
||||
unicasttarget.Add(ipsock);
|
||||
|
||||
} catch (IOException | SecurityException | IllegalArgumentException e) {
|
||||
raise_log_event("Unicast Target "+ip+" is invalid, will be excluded from Streaming, Exception="+e.getMessage()+", Caused="+e.getCause());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int unicount = unicasttarget.getSize();
|
||||
if (unicount<1) {
|
||||
raise_log_event("Final Unicast Destination is empty");
|
||||
return false;
|
||||
} else {
|
||||
raise_log_event("Will send Unicast to "+unicount+" targets");
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (!EthernetIP.isEmpty()) {
|
||||
try {
|
||||
this.unicast_ethernet = InetAddress.getByName(EthernetIP);
|
||||
} catch (UnknownHostException e) {
|
||||
this.unicast_ethernet = null;
|
||||
raise_log_event("Invalid Local Ethernet IP="+EthernetIP+", Message="+e.getMessage()+", Caused="+e.getCause());
|
||||
return false;
|
||||
}
|
||||
|
||||
InetSocketAddress localsock = null;
|
||||
try {
|
||||
localsock = new InetSocketAddress(this.unicast_ethernet,0);
|
||||
} catch(IllegalArgumentException e) {
|
||||
localsock = null;
|
||||
raise_log_event("Invalid Local Ethernet Socket Address, Message="+e.getMessage()+", Caused="+e.getCause());
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
unisock = new DatagramSocket(localsock);
|
||||
|
||||
} catch (SocketException e) {
|
||||
raise_log_event("Unable to create DatagramSocket, Msg : "+e.getMessage()+", Caused : "+e.getCause());
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
unisock = new DatagramSocket();
|
||||
} catch (SocketException e) {
|
||||
raise_log_event("Unable to create DatagramSocket, Msg : "+e.getMessage()+", Caused : "+e.getCause());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
raise_log_event("Unicast Socket created at "+unisock.getLocalAddress().getHostAddress()+":"+unisock.getLocalPort());
|
||||
this.unicastready = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if MulticastSocket is created
|
||||
* @return true if opened
|
||||
*/
|
||||
public boolean getMulticastIsOpened() {
|
||||
if (mulsock != null) return true; else return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if UnicastSocket is created
|
||||
* @return true if opened
|
||||
*/
|
||||
public boolean getUnicastIsOpened() {
|
||||
if (unisock != null) return true; else return false;
|
||||
}
|
||||
|
||||
protected void SendPCMData(byte[] xx, int length) {
|
||||
if (xx==null) return;
|
||||
if (xx.length<0) return;
|
||||
if (length<0) return;
|
||||
boolean is_sending = false;
|
||||
|
||||
if (multicastready) {
|
||||
if (multicastsockaddress!=null) {
|
||||
try {
|
||||
DatagramPacket pkg = new DatagramPacket(xx, length, multicastsockaddress);
|
||||
if (mulsock!=null) mulsock.send(pkg);
|
||||
bytesent_multicast += xx.length;
|
||||
is_sending = true;
|
||||
if (!is_multicasting) {
|
||||
is_multicasting = true;
|
||||
if (dse!=null) {
|
||||
dse.startstreaming(multicastsockaddress.getAddress().getHostAddress());
|
||||
}
|
||||
|
||||
}
|
||||
} catch (IOException | SecurityException | IllegalArgumentException e) {
|
||||
raise_log_event("Unable to send Multicast Packet to "+multicastip.getHostAddress()+":"+multicastport+", Exception="+e.getMessage()+", Caused="+e.getCause());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (unicastready) {
|
||||
if (unicasttarget.getSize()>0) {
|
||||
StringBuilder str = new StringBuilder();
|
||||
for (int ii=0;ii<unicasttarget.getSize();ii++) {
|
||||
InetSocketAddress ipx = (InetSocketAddress) unicasttarget.Get(ii);
|
||||
try {
|
||||
DatagramPacket pkg = new DatagramPacket(xx,length, ipx);
|
||||
if (unisock!=null) unisock.send(pkg);
|
||||
if (str.length()>0) str.append(";");
|
||||
str.append(ipx.getAddress().getHostAddress());
|
||||
} catch (IOException | SecurityException | IllegalArgumentException e) {
|
||||
raise_log_event("Unable to send Unicast Packet to "+ipx.getAddress().getHostAddress()+":"+unicastport+", Exception="+e.getMessage()+", Caused="+e.getCause());
|
||||
}
|
||||
}
|
||||
bytesent_unicast+=xx.length;
|
||||
is_sending = true;
|
||||
if (!is_unicasting) {
|
||||
is_unicasting = true;
|
||||
if (str.length()>0) {
|
||||
if (dse != null) {
|
||||
dse.startstreaming(str.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (is_sending) bytesent+= xx.length;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Total Sent bytes to Multicast Target
|
||||
* To get in String format, use filesize_to_string function
|
||||
* @return total bytes sent in long
|
||||
*/
|
||||
public long getPCMByteSent_Multicast() {
|
||||
return bytesent_multicast;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Total Sent bytes to Unicast target
|
||||
* To get in String format, use filesize_to_string function
|
||||
* @return total bytes sent in long
|
||||
*/
|
||||
public long getPCMByteSent_Unicast() {
|
||||
return bytesent_unicast;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Sent bytes, either in Multicast or In Unicast
|
||||
* @return total bytes sent in long
|
||||
*/
|
||||
public long getPCMByteSent() {
|
||||
return bytesent;
|
||||
}
|
||||
|
||||
private void raise_log_event(String msg) {
|
||||
if (dse!=null) dse.log(msg);
|
||||
}
|
||||
|
||||
}
|
||||
7
src/QZARelated/DataSenderEvent.java
Normal file
7
src/QZARelated/DataSenderEvent.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package QZARelated;
|
||||
|
||||
public interface DataSenderEvent {
|
||||
void log(String msg);
|
||||
void startstreaming(String toip);
|
||||
void stopstreaming(String toip);
|
||||
}
|
||||
754
src/QZARelated/QZA_UDP_FileSender.java
Normal file
754
src/QZARelated/QZA_UDP_FileSender.java
Normal file
@@ -0,0 +1,754 @@
|
||||
package QZARelated;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.ShortBuffer;
|
||||
|
||||
import com.sun.jna.Pointer;
|
||||
import com.un4seen.bass.BASS;
|
||||
import com.un4seen.bass.BASS.IDSPPROC;
|
||||
import com.un4seen.bass.BASS.bassconstant;
|
||||
import com.un4seen.bass.BASSmix;
|
||||
|
||||
import anywheresoftware.b4a.BA;
|
||||
|
||||
|
||||
@BA.ShortName("QZA_UDP_FileSender")
|
||||
@BA.Events(values= {
|
||||
"playbackstarted(filename as string,success as boolean)",
|
||||
"playbackstopped(filename as string)",
|
||||
"playbackpcm(filename as string, bb() as byte)",
|
||||
"log(msg as string)",
|
||||
"vulevel(value as int)",
|
||||
"sendprogress(sent as long, totalsize as long)",
|
||||
"startstreaming(toip as string)",
|
||||
"stopstreaming(toip as string)"
|
||||
})
|
||||
|
||||
public class QZA_UDP_FileSender extends DataSender implements DataSenderEvent{
|
||||
private BASS bass;
|
||||
private BASSmix bassmix;
|
||||
|
||||
private BA bax;
|
||||
private String event = "";
|
||||
private String namafile = "";
|
||||
|
||||
private boolean need_playbackstarted_event = false;
|
||||
private boolean need_playbackstopped_event = false;
|
||||
private boolean need_playbackpcm_event = false;
|
||||
private boolean need_log_event = false;
|
||||
private boolean need_vulevel_event = false;
|
||||
private boolean need_sendprogress_event = false;
|
||||
private boolean need_startstreaming_event = false;
|
||||
private boolean need_stopstreaming_event = false;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private long dspnanotime = 0;
|
||||
private int current_play_handle = 0;
|
||||
|
||||
private long audiofilesize = 0;
|
||||
private double audioduration = 0;
|
||||
private long resamplingsize = 0;
|
||||
|
||||
private int samplingrate = 0;
|
||||
private int mixerhandle = 0;
|
||||
|
||||
private int dsphandle = 0;
|
||||
|
||||
private final long KBthreshold = 1024;
|
||||
private final long MBthreshold = 1024 * 1024;
|
||||
private final long GBthreshold = 1024 * 1024 * 1024;
|
||||
private final int MINUTEthreshold = 60;
|
||||
private final int HOURthreshold = 60 * 60;
|
||||
|
||||
private boolean issending = false;
|
||||
private boolean inited = false;
|
||||
|
||||
private int maxpackagesize = 1000;
|
||||
|
||||
private Object myobject;
|
||||
private float volfactor = 1.0f;
|
||||
private int loopingcount = 0;
|
||||
private long byteread = 0;
|
||||
private int devid = -1;
|
||||
private IDSPPROC theproc;
|
||||
|
||||
/**
|
||||
* Initialize QZA UDP File Sender
|
||||
* @param eventname : event prefix
|
||||
* @param device id : 0 = no sound, 1 = first real hardware
|
||||
*/
|
||||
public void Initialize(BA ba, String eventname, int deviceid) {
|
||||
|
||||
inited = false;
|
||||
bax = ba;
|
||||
event = eventname;
|
||||
devid = deviceid;
|
||||
myobject = this;
|
||||
if (bax!=null) {
|
||||
if (!event.isEmpty()) {
|
||||
need_playbackstarted_event = bax.subExists(event+"_playbackstarted");
|
||||
need_playbackstopped_event = bax.subExists(event+"_playbackstopped");
|
||||
need_playbackpcm_event = bax.subExists(event+"_playbackpcm");
|
||||
need_log_event = bax.subExists(event+"_log");
|
||||
need_vulevel_event = bax.subExists(event+"_vulevel");
|
||||
need_sendprogress_event = bax.subExists(event+"_sendprogress");
|
||||
need_startstreaming_event = bax.subExists(event+"_startstreaming");
|
||||
need_stopstreaming_event = bax.subExists(event+"_stopstreaming");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
SetDataSenderEvent(this); // buat set event DataSender
|
||||
|
||||
bass = new BASS();
|
||||
bassmix = new BASSmix();
|
||||
int bassversion = bass.BASS_GetVersion();
|
||||
int bassmixversion = bassmix.BASS_Mixer_GetVersion();
|
||||
if (bassversion!=0) {
|
||||
if (bassmixversion!=0) {
|
||||
inited = true;
|
||||
}
|
||||
}
|
||||
|
||||
// auto close kalau java program closed
|
||||
Runtime.getRuntime().addShutdownHook(new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (inited) {
|
||||
StopPlayback();
|
||||
if (devid != -1) {
|
||||
if (BASS.BASS_SetDevice(devid)) {
|
||||
BASS.BASS_Stop();
|
||||
}
|
||||
}
|
||||
BASS.BASS_Free();
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if QZA UDP FileSender is initialzed
|
||||
* @return true if initialized
|
||||
*/
|
||||
public boolean getIsInitialized() {
|
||||
return inited;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop current sending
|
||||
*/
|
||||
public void StopPlayback() {
|
||||
issending = false;
|
||||
close_all();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get / Set Streaming Volume
|
||||
* 0 = silent, 1.0f = normal, more than 1.0f = amplification
|
||||
*/
|
||||
public float getStreamVolume() {
|
||||
return volfactor;
|
||||
}
|
||||
|
||||
public void setStreamVolume(float value) {
|
||||
if (value<0) value = 0;
|
||||
volfactor = value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get Total bytes read from file (read progress)
|
||||
* To get in String format, use filesize_to_string function
|
||||
* @return total byte read, in long
|
||||
*/
|
||||
public long getPCMByteRead() {
|
||||
return byteread;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get Original Filesize in bytes
|
||||
* @return filesize in bytes
|
||||
*/
|
||||
public long getAudio_FileSize() {
|
||||
return audiofilesize;
|
||||
}
|
||||
|
||||
public String getAudio_FileSize_String() {
|
||||
return filesize_to_string(audiofilesize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Audio duration in seconds
|
||||
* @return duration in seconds
|
||||
*/
|
||||
public double getAudio_Duration() {
|
||||
return audioduration;
|
||||
}
|
||||
|
||||
public String getAudio_Duration_String() {
|
||||
return duration_to_string(audioduration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Resampled Filesize in bytes
|
||||
* @return resampled size in bytes
|
||||
*/
|
||||
public long getResampling_FileSize() {
|
||||
return resamplingsize;
|
||||
}
|
||||
|
||||
public String getResampling_FileSize_String() {
|
||||
return filesize_to_string(resamplingsize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there is opened audio file
|
||||
* @return true if opened
|
||||
*/
|
||||
public boolean getFileIsOpened() {
|
||||
if (current_play_handle==0) return false; else return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ambil Mixer_handle, kalau mau direkam , atau di-encode
|
||||
* @return 0 = mixer mati, lainnya = mixer aktif
|
||||
*/
|
||||
public int getMixer_Handle() {
|
||||
return mixerhandle;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Open Audio file
|
||||
* @param filenya : valid file path for audio file
|
||||
* @param newsamplingrate : new sampling rate if required
|
||||
* @param ismono : true for mono
|
||||
* @return true if success
|
||||
*/
|
||||
public boolean OpenFile(String filenya, int newsamplingrate, boolean ismono) {
|
||||
|
||||
this.samplingrate = newsamplingrate;
|
||||
|
||||
if (!bass.BASS_SetDevice(devid)) { // sudah bisa SetDevice, berarti sudah Init
|
||||
// kalau tidak bis SetDevice, cek error
|
||||
int errcode = bass.BASS_ErrorGetCode();
|
||||
if (errcode==bassconstant.BASS_ERROR_DEVICE) {
|
||||
// device tidak ada
|
||||
raise_bass_log_event("OpenFile","SetDevice "+devid);
|
||||
return false;
|
||||
} else if (errcode==bassconstant.BASS_ERROR_INIT) {
|
||||
// belum di-init, maka sekarang init
|
||||
int initflag = bassconstant.BASS_DEVICE_FREQ | bassconstant.BASS_DEVICE_16BITS ;
|
||||
if (ismono) initflag |= bassconstant.BASS_DEVICE_MONO;
|
||||
if (!bass.BASS_Init(devid, newsamplingrate, initflag)) {
|
||||
// tidak bisa init, maka gagal
|
||||
raise_bass_log_event("OpenFile","Init "+devid);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// sampai sini, sudah set device dan sudah init
|
||||
|
||||
// create mixer, untuk ubah samplingrate file original
|
||||
int mixflag = 0;
|
||||
mixerhandle= bassmix.BASS_Mixer_StreamCreate(samplingrate, 1, mixflag);
|
||||
if (mixerhandle==0) {
|
||||
raise_bass_log_event("OpenFile","Mixer_StreamCreate");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// buka file original
|
||||
// sumber data untuk Mixer, harus selalu decode
|
||||
int openflag = bassconstant.BASS_STREAM_DECODE;
|
||||
current_play_handle = bass.BASS_StreamCreateFile(false,filenya, 0, 0, openflag);
|
||||
|
||||
if (current_play_handle==0) {
|
||||
raise_bass_log_event("OpenFile","StreamCreateFile");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
namafile = filenya;
|
||||
audiofilesize = 0;
|
||||
audioduration = 0;
|
||||
resamplingsize = 0;
|
||||
|
||||
// ambil properties nya
|
||||
audiofilesize = bass.BASS_ChannelGetLength(current_play_handle, bassconstant.BASS_POS_BYTE);
|
||||
if (audiofilesize == -1) {
|
||||
raise_bass_log_event("OpenFile","ChannelGetLength play_handle");
|
||||
return false;
|
||||
}
|
||||
audioduration = bass.BASS_ChannelBytes2Seconds(current_play_handle, audiofilesize);
|
||||
if (audioduration<0) {
|
||||
raise_bass_log_event("OpenFile","ChannelBytes2Seconds play_handle");
|
||||
return false;
|
||||
}
|
||||
resamplingsize = bass.BASS_ChannelSeconds2Bytes(mixerhandle, audioduration);
|
||||
if (resamplingsize == -1) {
|
||||
raise_bass_log_event("OpenFile","ChannelSeconds2Bytes mixerhandle");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public String filesize_to_string(long value) {
|
||||
if (value < KBthreshold) {
|
||||
return value+" B";
|
||||
} else if (value < MBthreshold ) {
|
||||
return String.format("%.2f", (double)value/KBthreshold)+" KB";
|
||||
} else if (value < GBthreshold) {
|
||||
return String.format("%.2f", (double)value/MBthreshold)+" MB";
|
||||
} else {
|
||||
return String.format("%.2f", (double)value/GBthreshold)+" GB";
|
||||
}
|
||||
}
|
||||
|
||||
private String duration_to_string(double value) {
|
||||
if (value < MINUTEthreshold) {
|
||||
return (int)value+" seconds";
|
||||
} else if (value < HOURthreshold ) {
|
||||
int mm = (int)value / MINUTEthreshold;
|
||||
int ss = (int)value - (mm * MINUTEthreshold);
|
||||
return mm+" minutes "+ss+" seconds";
|
||||
} else {
|
||||
int hh = (int)value / HOURthreshold;
|
||||
value = value - (hh * HOURthreshold);
|
||||
int mm = (int)value / MINUTEthreshold;
|
||||
int ss = (int)value - (mm*MINUTEthreshold);
|
||||
return hh+" hours "+mm+" minutes "+ss+" seconds ";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if in streaming mode
|
||||
* @return true if streaming
|
||||
*/
|
||||
public boolean getIsStreaming() {
|
||||
return issending;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start Playing (streaming) audio file
|
||||
* will raise event playbackstarted, playbackstopped
|
||||
* also will raise event playbackpcm , and vulevel, if required
|
||||
* @param loopcount : how many file is going to be played
|
||||
* @param packagesize : UDP packagesize, default is 1000 bytes. value above 1500 wil be capped to 1500.
|
||||
* @return true if playfile success
|
||||
*/
|
||||
public boolean PlayFile(int loopcount, int packagesize) {
|
||||
|
||||
issending = false;
|
||||
if (packagesize<1) packagesize = 1000;
|
||||
if (packagesize>1500) packagesize = 1500;
|
||||
this.maxpackagesize = packagesize;
|
||||
|
||||
// audiofile belum diopen
|
||||
if (current_play_handle==0) {
|
||||
raise_log_event("Audio File not opened");
|
||||
raise_playbackstarted_event(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//mixer belum diopen
|
||||
if (mixerhandle==0) {
|
||||
raise_log_event("Mixer not Opened");
|
||||
raise_playbackstarted_event(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
// "Plug In" file original ke Mixer
|
||||
int addflag = 0;
|
||||
if (!bassmix.BASS_Mixer_StreamAddChannel(mixerhandle, current_play_handle, addflag)) {
|
||||
raise_bass_log_event("PlayFile","Mixer_StreamAddChannel");
|
||||
raise_playbackstarted_event(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set DSPPROC untuk ambil PCM Bytes, lewat decodepcmproc
|
||||
theproc = create_dspproc();
|
||||
|
||||
dsphandle = bass.BASS_ChannelSetDSP(mixerhandle,theproc , null, 1);
|
||||
|
||||
if (dsphandle==0) {
|
||||
raise_bass_log_event("PlayFile","ChannelSetDSP");
|
||||
raise_playbackstarted_event(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set No_Buffer dan Granule, supaya konstan 1000 bytes per packet
|
||||
if (!bass.BASS_ChannelSetAttribute(mixerhandle, bassconstant.BASS_ATTRIB_NOBUFFER, 1)) {
|
||||
raise_bass_log_event("PlayFile","ChannelSetAttribute ATTRIB_NOBUFFER failed");
|
||||
raise_playbackstarted_event(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
int granule = packagesize / 2; // granule = packagesize / 2, karena 16 bits = 2x 8bit
|
||||
if (!bass.BASS_ChannelSetAttribute(mixerhandle,bassconstant.BASS_ATTRIB_GRANULE, granule)) {
|
||||
raise_bass_log_event("PlayFile","ChannelSetAttribute ATTRIB_GRANULE "+granule+" failed");
|
||||
raise_playbackstarted_event(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set Device untuk persiapan BASS_Start()
|
||||
if (!bass.BASS_SetDevice(devid)) {
|
||||
// tidak bisa set to device 0
|
||||
raise_bass_log_event("PlayFile","SetDevice "+devid);
|
||||
raise_playbackstarted_event(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
// start output
|
||||
if (!bass.BASS_Start()) {
|
||||
raise_bass_log_event("PlayFile","Start");
|
||||
raise_playbackstarted_event(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
// play mixer stream
|
||||
if (!bass.BASS_ChannelPlay(mixerhandle, true)) {
|
||||
raise_bass_log_event("PlayFile","ChannelPlay");
|
||||
raise_playbackstarted_event(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
loopingcount = loopcount;
|
||||
|
||||
|
||||
Thread tx = new Thread(new Play_Monitor());
|
||||
tx.start();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private class Play_Monitor implements Runnable{
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
bytesent_multicast = 0;
|
||||
bytesent_unicast = 0;
|
||||
bytesent = 0;
|
||||
byteread = 0;
|
||||
dspnanotime = System.nanoTime();
|
||||
issending = true;
|
||||
raise_playbackstarted_event(true);
|
||||
int playstatus = -1;
|
||||
int lastplaystatus = -1;
|
||||
while(true) {
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
raise_log_event("PlayFile:Thread:InterruptedException");
|
||||
break;
|
||||
}
|
||||
if (current_play_handle==0) {
|
||||
raise_log_event("PlayFile Thread for "+namafile+" finished, current_play_handle=0");
|
||||
break;
|
||||
}
|
||||
if (mixerhandle==0) {
|
||||
raise_log_event("PlayFile Thread for "+namafile+" finished, mixer handle = 0");
|
||||
break;
|
||||
}
|
||||
|
||||
if (!issending) {
|
||||
raise_log_event("PlayFile Thread for "+namafile+" finished, user stopped");
|
||||
break;
|
||||
}
|
||||
|
||||
// revisi 06-11-2019
|
||||
// kadang playback 2 detik , stop sendiri, gak tau kenapa
|
||||
// ini untuk cek playback status
|
||||
playstatus = bass.BASS_ChannelIsActive(mixerhandle);
|
||||
if (playstatus!=lastplaystatus) {
|
||||
lastplaystatus = playstatus;
|
||||
switch(playstatus) {
|
||||
|
||||
case bassconstant.BASS_ACTIVE_PLAYING :
|
||||
raise_log_event("ChannelIsActive mixerhandle "+mixerhandle+" = ACTIVE_PLAYING");
|
||||
continue; // normal
|
||||
case bassconstant.BASS_ACTIVE_PAUSED :
|
||||
raise_log_event("ChannelIsActive mixerhandle "+mixerhandle+" = ACTIVE_PAUSED");
|
||||
break;
|
||||
case bassconstant.BASS_ACTIVE_STALLED :
|
||||
raise_log_event("ChannelIsActive mixerhandle "+mixerhandle+" = ACTIVE_STALLED");
|
||||
break;
|
||||
case bassconstant.BASS_ACTIVE_STOPPED :
|
||||
raise_log_event("ChannelIsActive mixerhandle "+mixerhandle+" = ACTIVE_STOPPED");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (playstatus!=bassconstant.BASS_ACTIVE_PLAYING) {
|
||||
if (loopingcount>0) {
|
||||
loopingcount-=1;
|
||||
if (loopingcount==0) {
|
||||
raise_log_event("Looping finished");
|
||||
break; // looping = 0, selesai
|
||||
} else {
|
||||
raise_log_event("Looping for "+loopingcount+" more");
|
||||
}
|
||||
}
|
||||
/// ulang dari awal
|
||||
|
||||
bass.BASS_ChannelStop(mixerhandle);
|
||||
if (issending) bass.BASS_ChannelPlay(mixerhandle, true); // kalau loopingcount masih positif, dan belum distop user, mainkan lagi
|
||||
}
|
||||
}
|
||||
|
||||
raise_playbackstopped_event();
|
||||
|
||||
close_all();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void close_all() {
|
||||
issending =false;
|
||||
|
||||
|
||||
if (mixerhandle!=0) {
|
||||
|
||||
// kadang bikin error !
|
||||
//http://www.un4seen.com/forum/?topic=18676.msg130824#msg130824
|
||||
//The crash happened when deleting a reference to a Java DSPPROC callback function because the reference was no longer valid.
|
||||
//It looks like that could possibly happen if BASS_ChannelRemoveDSP was called just before BASS_StreamFree was
|
||||
|
||||
if (!bass.BASS_ChannelStop(mixerhandle)) {
|
||||
raise_bass_log_event("close_all","ChannelStop:mixerhandle");
|
||||
}
|
||||
|
||||
if (dsphandle!=0) {
|
||||
if (!bass.BASS_ChannelRemoveDSP(mixerhandle, dsphandle)) {
|
||||
raise_bass_log_event("close_all","ChannelRemoveDSP");
|
||||
}
|
||||
dsphandle = 0;
|
||||
}
|
||||
|
||||
if (current_play_handle!=0) {
|
||||
if (!bassmix.BASS_Mixer_ChannelRemove(current_play_handle)) {
|
||||
raise_bass_log_event("close_all","Mixer_ChannelRemove:current_play_handle");
|
||||
}
|
||||
}
|
||||
|
||||
if (!bass.BASS_StreamFree(mixerhandle)) {
|
||||
raise_bass_log_event("close_all","StreamFree:mixerhandle");
|
||||
}
|
||||
mixerhandle = 0;
|
||||
}
|
||||
|
||||
if (current_play_handle!=0) {
|
||||
|
||||
if (!bass.BASS_StreamFree(current_play_handle)) {
|
||||
raise_bass_log_event("close_all","StreamFree:current_play_handle");
|
||||
}
|
||||
|
||||
current_play_handle=0;
|
||||
}
|
||||
|
||||
// revisi 29/09/2020 , BASS_Free dan BASS_Stop jangan di bagian close_all
|
||||
|
||||
// if (bass.BASS_SetDevice(devid)) {
|
||||
// masih bisa set device
|
||||
|
||||
// if (!bass.BASS_Stop()) {
|
||||
// raise_bass_log_event("close_all","Stop");
|
||||
// }
|
||||
// jangan di-free-in , siapa tau deviceid ini terpakai di tempat lain, malah jadi error
|
||||
// if (!bass.BASS_Free()) {
|
||||
// raise_bass_log_event("close_all","Free");
|
||||
// }
|
||||
// } else raise_bass_log_event("close_all","SetDevice");
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
private IDSPPROC create_dspproc() {
|
||||
IDSPPROC newproc = new IDSPPROC() {
|
||||
// Di sini dapat PCM bytes untuk dikirim
|
||||
@Override
|
||||
public void DSPPROC(int handle, int channel, Pointer bx, int length, Pointer user) {
|
||||
if (handle==0) return;
|
||||
if (channel==0) return;
|
||||
if (bx==null) return;
|
||||
if (length<2) return;
|
||||
if (!issending) return;
|
||||
|
||||
// buat diagnostic, masuk ke DSPPROC setiap berapa ms
|
||||
|
||||
dspnanotime = System.nanoTime();
|
||||
|
||||
|
||||
// convert to ByteBuffer
|
||||
Calculate_VU_and_Volume(bx.getByteBuffer(0, length)); // di sini Vu dihitung, juga volume di adjust
|
||||
|
||||
// get bytes
|
||||
byte[] bufbyte = bx.getByteArray(0, length);
|
||||
|
||||
|
||||
|
||||
if (length<= maxpackagesize)
|
||||
// kalau ukuran dah pas, langsung aja kirim
|
||||
SendPCMData(bufbyte,bufbyte.length);
|
||||
else
|
||||
// kalau kelebihan, pecah-pecah dulu, baru kirim
|
||||
Split_and_Send(bx.getByteBuffer(0, length));
|
||||
|
||||
// raise raw PCM event
|
||||
raise_playbackpcm_event(bufbyte);
|
||||
byteread+= length;
|
||||
raise_sendprogress_event();
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
return newproc;
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void Calculate_VU_and_Volume(ByteBuffer bufx) {
|
||||
if (bufx==null) return;
|
||||
ShortBuffer vubuf = bufx.order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();
|
||||
// calculate volume and vu
|
||||
short temp = 0;
|
||||
short maxvalue = 0;
|
||||
int pos = 0;
|
||||
vubuf.rewind();
|
||||
while(vubuf.hasRemaining()) {
|
||||
pos = vubuf.position(); // ambil posisi sekarang
|
||||
temp = (short)(vubuf.get() * volfactor); // ambil nilai, dikali volfactor
|
||||
vubuf.position(pos); // balik posisi tadi
|
||||
vubuf.put(temp); // taruh balik
|
||||
|
||||
if (temp > maxvalue) maxvalue = temp; // cari nilai maksimum untuk VU
|
||||
}
|
||||
int vuvalue = (int)((maxvalue/32767.0)*100);
|
||||
raise_vulevel_event(vuvalue);
|
||||
}
|
||||
|
||||
// Butuh ini kalau Buffer lebih besar dari MaxPackagesize
|
||||
private void Split_and_Send(ByteBuffer bufx) {
|
||||
if (bufx==null) return;
|
||||
int length = bufx.capacity();
|
||||
|
||||
int X = length / maxpackagesize; // package per maxpackagesize
|
||||
int Y = length % maxpackagesize; // remainder of
|
||||
|
||||
bufx.rewind();
|
||||
while (X>0) {
|
||||
// sending per maxpackagesize limit
|
||||
byte[] xxx = new byte[maxpackagesize];
|
||||
bufx.get(xxx);
|
||||
SendPCMData(xxx,xxx.length); // Pakai DataSender
|
||||
X-=1;
|
||||
}
|
||||
|
||||
if (Y>0) {
|
||||
// if has remainder, or length is less than maxpackagesize
|
||||
byte[] xxx = new byte[Y];
|
||||
bufx.get(xxx);
|
||||
SendPCMData(xxx,xxx.length); // Pakai DataSender
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void log(String msg) { // event dari DataSender
|
||||
raise_log_event("DataSender Log : "+ msg);
|
||||
|
||||
}
|
||||
|
||||
private void raise_playbackstarted_event(boolean success) {
|
||||
if (need_playbackstarted_event) {
|
||||
bax.raiseEventFromDifferentThread(myobject, null, 0, event+"_playbackstarted", false, new Object[] {namafile, success});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void raise_playbackstopped_event() {
|
||||
if (need_playbackstopped_event) {
|
||||
bax.raiseEventFromDifferentThread(myobject, null, 0, event+"_playbackstopped", false, new Object[] {namafile});
|
||||
}
|
||||
}
|
||||
|
||||
private void raise_playbackpcm_event(byte[] bb) {
|
||||
if (need_playbackpcm_event) {
|
||||
bax.raiseEventFromDifferentThread(myobject, null, 0, event+"_playbackpcm", false, new Object[] {namafile,bb});
|
||||
}
|
||||
}
|
||||
|
||||
private void raise_log_event(String msg) {
|
||||
if (need_log_event) {
|
||||
bax.raiseEventFromDifferentThread(myobject, null, 0, event+"_log", false, new Object[] {msg});
|
||||
}
|
||||
}
|
||||
|
||||
private void raise_sendprogress_event() {
|
||||
if (need_sendprogress_event) {
|
||||
bax.raiseEventFromDifferentThread(myobject, null, 0, event+"_sendprogress", false, new Object[] {byteread, resamplingsize});
|
||||
}
|
||||
}
|
||||
|
||||
private void raise_bass_log_event(String function, String basscommand) {
|
||||
int errcode = bass.BASS_ErrorGetCode();
|
||||
if (errcode==0) return;
|
||||
String msg = "Error on Function="+function+" from Bass Command="+basscommand+", Code="+BASS.GetBassErrorString(errcode);
|
||||
raise_log_event(msg);
|
||||
}
|
||||
|
||||
private void raise_vulevel_event(int value) {
|
||||
if (need_vulevel_event) {
|
||||
bax.raiseEventFromDifferentThread(myobject, null, 0, event+"_vulevel", false, new Object[] {value});
|
||||
}
|
||||
}
|
||||
|
||||
private void raise_startstreaming_event(String value) {
|
||||
if (need_startstreaming_event) bax.raiseEventFromDifferentThread(myobject, null, 0, event+"_startstreaming", false, new Object[] {value});
|
||||
}
|
||||
|
||||
private void raise_stopstreaming_event(String value) {
|
||||
if (need_stopstreaming_event) bax.raiseEventFromDifferentThread(myobject, null, 0, event+"_stopstreaming", false, new Object[] {value});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startstreaming(String toip) {
|
||||
raise_startstreaming_event(toip);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopstreaming(String toip) {
|
||||
raise_stopstreaming_event(toip);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
602
src/QZARelated/QZA_UDP_StreamReceiver.java
Normal file
602
src/QZARelated/QZA_UDP_StreamReceiver.java
Normal file
@@ -0,0 +1,602 @@
|
||||
package QZARelated;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.ShortBuffer;
|
||||
|
||||
|
||||
import com.sun.jna.Pointer;
|
||||
import com.un4seen.bass.BASS;
|
||||
import com.un4seen.bass.BASS.BASS_CHANNELINFO;
|
||||
import com.un4seen.bass.BASS.BASS_FILEPROCS;
|
||||
import com.un4seen.bass.BASS.IDSPPROC;
|
||||
import com.un4seen.bass.BASS.FILECLOSEPROC;
|
||||
import com.un4seen.bass.BASS.FILELENPROC;
|
||||
import com.un4seen.bass.BASS.FILEREADPROC;
|
||||
import com.un4seen.bass.BASS.FILESEEKPROC;
|
||||
import com.un4seen.bass.BASS.bassconstant;
|
||||
|
||||
import DatareceiverRelated.DataReceiver;
|
||||
import DatareceiverRelated.DataReceiverEvent;
|
||||
import anywheresoftware.b4a.BA;
|
||||
import anywheresoftware.b4a.keywords.DateTime;
|
||||
|
||||
@BA.ShortName("QZA_Network_StreamReceiver")
|
||||
@BA.Events(values= {
|
||||
"log(msg as string)",
|
||||
"streamstarted(timetick as long,success as boolean)",
|
||||
"streamstopped(timetick as long)",
|
||||
"streamidle(timetick as long)",
|
||||
"streamrunning(timetick as long)",
|
||||
"vulevel(value as int)",
|
||||
"tcpconnected(success as boolean)",
|
||||
"datareceiverstatus(isconnected as boolean, targetip as string, targetport as int)",
|
||||
"pcmbytes(bb() as byte)"
|
||||
})
|
||||
|
||||
public class QZA_UDP_StreamReceiver extends DataReceiver implements DataReceiverEvent {
|
||||
|
||||
|
||||
private BASS bass;
|
||||
private String event = "";
|
||||
private BA ba;
|
||||
private boolean inited = false;
|
||||
private boolean need_log_event = false;
|
||||
private boolean need_streamstarted_event = false;
|
||||
private boolean need_streamstopped_event = false;
|
||||
private boolean need_streamidle_event = false;
|
||||
private boolean need_streamrunning_event = false;
|
||||
|
||||
private boolean need_pcmbytes_event = false;
|
||||
private boolean need_vulevel_event = false;
|
||||
private boolean need_tcpconnected_event = false;
|
||||
private boolean need_datareceiverstatus_event =false;
|
||||
|
||||
private boolean isstreaming = false;
|
||||
private int current_play_handle = 0;
|
||||
private int devid = -1;
|
||||
private BASS_CHANNELINFO channelinfo;
|
||||
private int dsphandle = 0;
|
||||
|
||||
private final int bufmemsize = 1024 * 32;
|
||||
private ByteBuffer bufmem;
|
||||
private float volfactor = 1.0f;
|
||||
private final int bufmemwait = 60 * 1000; // 60 second
|
||||
private long pcmbytescount = 0;
|
||||
|
||||
private boolean isidle = false; // untuk indikator streamidle dan streamrunning
|
||||
private IDSPPROC dspproc;
|
||||
|
||||
private Object myobject;
|
||||
/**
|
||||
* Initialize Network Stream Receiver
|
||||
* @param eventname : event name
|
||||
* @param deviceid : playback device id
|
||||
*/
|
||||
public void Initialize(BA bax, String eventname, int deviceid) {
|
||||
|
||||
ba = bax;
|
||||
event = eventname;
|
||||
myobject = this;
|
||||
if (ba!=null) {
|
||||
if (!event.isEmpty()) {
|
||||
need_log_event = ba.subExists(event+"_log");
|
||||
need_streamstarted_event = ba.subExists(event+"_streamstarted");
|
||||
need_streamstopped_event = ba.subExists(event+"_streamstopped");
|
||||
need_streamidle_event = ba.subExists(event+"_streamidle");
|
||||
need_streamrunning_event = ba.subExists(event+"_streamrunning");
|
||||
|
||||
need_datareceiverstatus_event = ba.subExists(event+"_datareceiverstatus");
|
||||
|
||||
need_pcmbytes_event = ba.subExists(event+"_pcmbytes");
|
||||
need_vulevel_event = ba.subExists(event+"_vulevel");
|
||||
need_tcpconnected_event = ba.subExists(event+"_tcpconnected");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
devid = deviceid;
|
||||
SetDataReceiverEvent(this); // perlu supaya DataReceiverEvent jalan
|
||||
bass = new BASS();
|
||||
int version = bass.BASS_GetVersion();
|
||||
if (version!=0) {
|
||||
bufmem = ByteBuffer.allocateDirect(bufmemsize);
|
||||
inited = true;
|
||||
|
||||
}
|
||||
|
||||
// auto close kalau java program closed
|
||||
Runtime.getRuntime().addShutdownHook(new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (inited) {
|
||||
Stop_Receiving_Streaming();
|
||||
if (devid != -1) {
|
||||
if (BASS.BASS_SetDevice(devid)) {
|
||||
BASS.BASS_Stop();
|
||||
}
|
||||
}
|
||||
BASS.BASS_Free();
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get / Set Streaming Volume
|
||||
* 0 = silemt, 1.0f = normal, more than 1.0f = amplification
|
||||
*/
|
||||
public float getStreamVolume() {
|
||||
return volfactor;
|
||||
}
|
||||
|
||||
public void setStreamVolume(float value) {
|
||||
volfactor = (value<0) ? 0 : value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if Initialized
|
||||
* @return true if initialized
|
||||
*/
|
||||
public boolean IsInitialized() {
|
||||
return inited;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open Playback Device for Streaming
|
||||
* Stream format maybe PCM, MP3, or other supported codec
|
||||
* Better to wait_for streamstarted event to get result
|
||||
* @param samplingrate : sampling rate
|
||||
*/
|
||||
public void OpenStream(int samplingrate) {
|
||||
isstreaming = false;
|
||||
if (!IsInitialized()) {
|
||||
raise_log_event("QZA_UDP_StreamReceiver Not Initialized");
|
||||
raise_streamstarted_event(false);
|
||||
}
|
||||
|
||||
if (!bass.BASS_SetDevice(devid)) { // kalau return true, artinya sudah init
|
||||
// kalau return false, cek errornya
|
||||
int errcode = bass.BASS_ErrorGetCode();
|
||||
if (errcode==bassconstant.BASS_ERROR_DEVICE) {
|
||||
// tidak ada device ini, return false;
|
||||
raise_log_event("QZA_UDP_StreamReceiver No Device with ID="+devid);
|
||||
raise_streamstarted_event(false);
|
||||
|
||||
} else if (errcode==bassconstant.BASS_ERROR_INIT) {
|
||||
// belum initialize, maka sekarang initialize
|
||||
int flags = bassconstant.BASS_DEVICE_16BITS | bassconstant.BASS_DEVICE_MONO | bassconstant.BASS_DEVICE_FREQ;
|
||||
if (!bass.BASS_Init(devid, samplingrate, flags)) {
|
||||
// gagal init, return false
|
||||
raise_bass_error_log("OpenStream","Init");
|
||||
raise_streamstarted_event(false);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isidle = false;
|
||||
Thread creatingstream = new Thread(new streamcreatefileuser_job());
|
||||
creatingstream.start();
|
||||
|
||||
}
|
||||
|
||||
// mesti di-thread, kalau nggak , akan stuck di BASS_StreamCreateFile
|
||||
private class streamcreatefileuser_job implements Runnable{
|
||||
private BASS_FILEPROCS procs;
|
||||
private FILECLOSEPROC fc;
|
||||
private FILELENPROC fl;
|
||||
private FILESEEKPROC fs;
|
||||
private FILEREADPROC fr;
|
||||
streamcreatefileuser_job(){
|
||||
fc = new FILECLOSEPROC() {
|
||||
|
||||
@Override
|
||||
public void FILECLOSEPROC(Pointer user) {
|
||||
// do thing here
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
fs = new FILESEEKPROC() {
|
||||
|
||||
@Override
|
||||
public boolean FILESEEKPROC(long offset, Pointer user) {
|
||||
// return false karena streaming gak bisa seek
|
||||
return false;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
fl = new FILELENPROC() {
|
||||
|
||||
@Override
|
||||
public long FILELENPROC(Pointer user) {
|
||||
// return 0 , karena size awal streaming tidak tahu
|
||||
return 0;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
fr = new FILEREADPROC() {
|
||||
|
||||
@Override
|
||||
public int FILEREADPROC(Pointer buffer, int length, Pointer user) {
|
||||
// isi Pointer buffer dengan data, supaya StreamCreateFileUser bisa keluarin handle
|
||||
// kalau belum ada data masuk, tahan di sini
|
||||
if (bufmem==null) return 0; // kalau sampai null, artinya selesai
|
||||
synchronized(bufmem) {
|
||||
if (bufmem.position()<1) {
|
||||
// dalam write-mode, position<1 artinya tidak ada data, tungguin 60 detik
|
||||
|
||||
|
||||
if (!isidle) {
|
||||
// kalau sebelumnya running, sekarang jadi idle, dan raise event streamidle
|
||||
isidle = true;
|
||||
raise_streamidle_event();
|
||||
}
|
||||
try {
|
||||
bufmem.wait(bufmemwait);
|
||||
} catch (InterruptedException e) {
|
||||
// ada interrupt selama waiting, artinya diminta selesai
|
||||
return 0; // selesai aja
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
byte[] BX = BufMem_Read(length);
|
||||
if (BX==null) return 0; // ternyata tidak ada datanya, selesai aja
|
||||
if (BX.length<1) return 0; // ternyata tidak ada datanya, selesai aja
|
||||
|
||||
buffer.write(0, BX, 0, BX.length);
|
||||
|
||||
|
||||
if (isidle) {
|
||||
// kalau sebelumnya idle, sekarang menjadi running (tidak idle), dan raise event streamrunning
|
||||
isidle = false;
|
||||
raise_streamrunning_event();
|
||||
}
|
||||
|
||||
return BX.length; // kembalikan berapa banyak data yang sudah diterima
|
||||
}
|
||||
|
||||
};
|
||||
procs = new BASS_FILEPROCS(fc, fl, fr, fs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
|
||||
current_play_handle = bass.BASS_StreamCreateFileUser(bassconstant.STREAMFILE_BUFFER, 0, procs, null); // nunggu di sini sampai FILEREADPROC dapat data
|
||||
if (current_play_handle==0) {
|
||||
// tidak bisa open
|
||||
raise_bass_error_log("OpenStream","StreamCreateFileUser");
|
||||
raise_streamstarted_event(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!bass.BASS_ChannelGetInfo(current_play_handle, channelinfo)) {
|
||||
// tidak bisa get channelinfo
|
||||
raise_bass_error_log("OpenStream","ChannelGetInfo");
|
||||
raise_streamstarted_event(false);
|
||||
return;
|
||||
|
||||
} else {
|
||||
channelinfo.read(); // baca datanya
|
||||
}
|
||||
|
||||
if (!bass.BASS_ChannelSetAttribute(current_play_handle, bassconstant.BASS_ATTRIB_VOL, 1.0f)) {
|
||||
// tidak bisa set volume
|
||||
raise_bass_error_log("OpenStream","ChannelSetAttribute::Attrib_Vol");
|
||||
raise_streamstarted_event(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!bass.BASS_ChannelSetAttribute(current_play_handle, bassconstant.BASS_ATTRIB_NOBUFFER, 1)) {
|
||||
// tidak bisa set No Buffer
|
||||
raise_bass_error_log("OpenStream","ChannelSetAttribute::Attrib_NoBuffer");
|
||||
raise_streamstarted_event(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!bass.BASS_ChannelSetAttribute(current_play_handle, bassconstant.BASS_ATTRIB_GRANULE, 500)) { // 500 = 1000 bytes, karena 16bits = 2 byte
|
||||
// tidak bisa set Granule
|
||||
raise_bass_error_log("OpenStream","ChannelSetAttribute::Attrib_Granule");
|
||||
raise_streamstarted_event(false);
|
||||
return;
|
||||
}
|
||||
dspproc = create_dspproc();
|
||||
dsphandle = bass.BASS_ChannelSetDSP(current_play_handle,dspproc , null, 1);
|
||||
if (dsphandle==0) {
|
||||
// tidak bisa set DSP
|
||||
raise_bass_error_log("OpenStream","ChannelSetDSP");
|
||||
raise_streamstarted_event(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!bass.BASS_ChannelPlay(current_play_handle, true)) {
|
||||
raise_bass_error_log("OpenStream","ChannelPlay");
|
||||
raise_streamstarted_event(false);
|
||||
return;
|
||||
}
|
||||
|
||||
pcmbytescount = 0;
|
||||
isstreaming = true;
|
||||
|
||||
Thread tx = new Thread(new playback_monitoring());
|
||||
tx.start();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
private class playback_monitoring implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
raise_streamstarted_event(true);
|
||||
int playstatus = 0;
|
||||
|
||||
while(true) {
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!isstreaming) break;
|
||||
if (current_play_handle==0) break;
|
||||
playstatus = bass.BASS_ChannelIsActive(current_play_handle);
|
||||
if (playstatus==bassconstant.BASS_ACTIVE_PAUSED) break; // paused, berhenti aja
|
||||
if (playstatus==bassconstant.BASS_ACTIVE_STOPPED) break; // kalau stop, berhenti
|
||||
// kalau statusnya active_play atau active_stalled , biarin aja
|
||||
|
||||
}
|
||||
close_all();
|
||||
raise_streamstopped_event();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* How much bytes already decoded in playback buffer (PCM format)
|
||||
* @return value in long
|
||||
*/
|
||||
public long getPCM_BytesCount() {
|
||||
return pcmbytescount;
|
||||
}
|
||||
|
||||
|
||||
private IDSPPROC create_dspproc() {
|
||||
IDSPPROC newproc = new IDSPPROC() {
|
||||
|
||||
@Override
|
||||
public void DSPPROC(int handle, int channel, Pointer bx, int length, Pointer user) {
|
||||
// handle = DSP handle
|
||||
// channel = current_play_handle
|
||||
// buffer = decoded bytes di sini
|
||||
// length = jumlahnya
|
||||
// user = abaikan aja
|
||||
if (handle==0) return;
|
||||
if (channel==0) return;
|
||||
if (length<1) return;
|
||||
if (bx==null) return;
|
||||
|
||||
|
||||
pcmbytescount+= length;
|
||||
|
||||
Calculate_VU_and_Volume(bx.getByteBuffer(0, length));
|
||||
byte[] bufbyte = bx.getByteArray(0, length);
|
||||
|
||||
raise_pcmbytes_event(bufbyte);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
return newproc;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Ambil Streaming Handle, kalau mau direkam, atau di-encode lagi
|
||||
* @return 0 = Not Streaming, lainnya = Streaming
|
||||
*/
|
||||
public int getStream_Handle() {
|
||||
return current_play_handle;
|
||||
}
|
||||
|
||||
private void Calculate_VU_and_Volume(ByteBuffer bufx) {
|
||||
if (bufx==null) return;
|
||||
ShortBuffer vubuf = bufx.order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();
|
||||
// calculate volume and vu
|
||||
short temp = 0;
|
||||
short maxvalue = 0;
|
||||
int pos = 0;
|
||||
vubuf.rewind();
|
||||
while(vubuf.hasRemaining()) {
|
||||
pos = vubuf.position(); // ambil posisi sekarang
|
||||
temp = (short)(vubuf.get() * volfactor); // ambil nilai, dikali volfactor
|
||||
vubuf.position(pos); // balik posisi tadi
|
||||
vubuf.put(temp); // taruh balik
|
||||
|
||||
if (temp > maxvalue) maxvalue = temp; // cari nilai maksimum untuk VU
|
||||
}
|
||||
int vuvalue = (int)((maxvalue/32767.0)*100);
|
||||
raise_vulevel_event(vuvalue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop Playback from receiving streams
|
||||
*/
|
||||
public void Stop_Receiving_Streaming() {
|
||||
isstreaming = false;
|
||||
close_all();
|
||||
}
|
||||
|
||||
private void close_all() {
|
||||
isstreaming = false;
|
||||
|
||||
|
||||
if (current_play_handle!=0) {
|
||||
if (dsphandle!=0) {
|
||||
if (!bass.BASS_ChannelRemoveDSP(current_play_handle, dsphandle)) {
|
||||
raise_bass_error_log("close_all","ChannelRemoveDSP");
|
||||
}
|
||||
dsphandle = 0;
|
||||
}
|
||||
|
||||
if (!bass.BASS_ChannelStop(current_play_handle)) {
|
||||
raise_bass_error_log("close_all","ChannelStop");
|
||||
}
|
||||
if (!bass.BASS_StreamFree(current_play_handle)) {
|
||||
raise_bass_error_log("close_all","StreamFree");
|
||||
}
|
||||
|
||||
current_play_handle = 0;
|
||||
}
|
||||
|
||||
// revisi 29-09-2020
|
||||
// Bass_Stop dan Bass_Free di Auto Close
|
||||
|
||||
// if (bass.BASS_SetDevice(devid)) {
|
||||
// if (!bass.BASS_Stop()) {
|
||||
// raise_bass_error_log("close_all","Stop");
|
||||
// }
|
||||
// jangan di-free-in , siapa tau deviceid ini terpakai di tempat lain, malah jadi error
|
||||
// if (!bass.BASS_Free()) {
|
||||
// raise_bass_error_log("close_all","Free");
|
||||
// }
|
||||
// } else raise_bass_error_log("close_all","SetDevice");
|
||||
}
|
||||
|
||||
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_error_log(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_streamstarted_event(boolean success) {
|
||||
if (need_streamstarted_event) ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_streamstarted", false, new Object[] {DateTime.getNow(), success});
|
||||
}
|
||||
|
||||
private void raise_streamstopped_event() {
|
||||
if (need_streamstopped_event) ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_streamstopped", false, new Object[] {DateTime.getNow()});
|
||||
}
|
||||
|
||||
private void raise_streamidle_event() {
|
||||
if (need_streamidle_event) ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_streamidle", false, new Object[] {DateTime.getNow()});
|
||||
}
|
||||
|
||||
private void raise_streamrunning_event() {
|
||||
if (need_streamrunning_event) ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_streamrunning", false, new Object[] {DateTime.getNow()});
|
||||
}
|
||||
|
||||
|
||||
private void raise_pcmbytes_event(byte[] bb) {
|
||||
if (need_pcmbytes_event) ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_pcmbytes", false, new Object[] {bb});
|
||||
}
|
||||
|
||||
private void raise_vulevel_event(int value) {
|
||||
if (need_vulevel_event) {
|
||||
ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_vulevel", false, new Object[] {value});
|
||||
}
|
||||
}
|
||||
|
||||
private void raise_tcpconnected_event(boolean value) {
|
||||
if (need_tcpconnected_event) {
|
||||
ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_tcpconnected", false, new Object[] {value});
|
||||
}
|
||||
}
|
||||
|
||||
private void raise_datareceiverstatus_event(boolean status, InetSocketAddress sa) {
|
||||
if (need_datareceiverstatus_event) ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_datareceiverstatus", false, new Object[] {status, sa.getHostString(),sa.getPort()});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private byte[] BufMem_Read(int readsize) {
|
||||
if (bufmem==null) return null;
|
||||
if (readsize<1) return null; // kagak niat baca
|
||||
|
||||
synchronized(bufmem){
|
||||
bufmem.flip(); // pindah mode baca
|
||||
int bytescount = bufmem.remaining();
|
||||
if (bytescount>0) {
|
||||
// ada yang bisa dibaca
|
||||
if (readsize > bytescount) readsize = bytescount; // data tersedia, tidak sebanyak jumlah data yang ingin dibaca. Maka crop aja
|
||||
byte[] bytenya = new byte[readsize];
|
||||
bufmem.get(bytenya);
|
||||
bufmem.compact(); // pindah mode tulis
|
||||
return bytenya;
|
||||
} else {
|
||||
// tidak ada yang bisa dibaca
|
||||
bufmem.flip(); // gak jadi baca, pindah mode tulis lagi
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@BA.Hide
|
||||
public void data_received(Pointer datanya, int length) {
|
||||
if (bufmem!=null) {
|
||||
if (bufmem.remaining() < length) {
|
||||
// kurang size nya, buang dulu yang depan
|
||||
int sizebuang = length - bufmem.remaining(); // ini size yang harus dibuang
|
||||
BufMem_Read(sizebuang); // hasil balikan ini, bodo amat !
|
||||
|
||||
}
|
||||
|
||||
synchronized(bufmem) {
|
||||
byte[] bytetulis = datanya.getByteArray(0, length);
|
||||
bufmem.put(bytetulis);
|
||||
bufmem.notifyAll(); // notify bufmem.wait yang ada di FILEREADPROC
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
@BA.Hide
|
||||
public void log(String msg) {
|
||||
raise_log_event("DataReceiver Log : "+msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
@BA.Hide
|
||||
public void tcpconnectresult(boolean success) {
|
||||
raise_tcpconnected_event(success);
|
||||
}
|
||||
|
||||
@Override
|
||||
@BA.Hide
|
||||
public void connected(InetSocketAddress sa) {
|
||||
raise_log_event("DataReceiver connected with "+sa.getHostString()+":"+sa.getPort());
|
||||
raise_datareceiverstatus_event(true,sa);
|
||||
}
|
||||
|
||||
@Override
|
||||
@BA.Hide
|
||||
public void disconnected(InetSocketAddress sa) {
|
||||
if (isstreaming) {
|
||||
raise_log_event("Force Stop Receiving Streaming, because DataReceiver disconnected");
|
||||
Stop_Receiving_Streaming();
|
||||
} else {
|
||||
raise_log_event("DataReceiver disconnected");
|
||||
}
|
||||
raise_datareceiverstatus_event(false,sa);
|
||||
}
|
||||
}
|
||||
789
src/aas/AAS_Receiver_Multichannel.java
Normal file
789
src/aas/AAS_Receiver_Multichannel.java
Normal file
@@ -0,0 +1,789 @@
|
||||
package aas;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.ShortBuffer;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
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.BASS_INFO;
|
||||
import com.un4seen.bass.BASS.IDSPPROC;
|
||||
import com.un4seen.bass.BASS.SYNCPROC;
|
||||
import com.un4seen.bass.BASS.bassconstant;
|
||||
import com.un4seen.bass.BASSmix;
|
||||
|
||||
import anywheresoftware.b4a.BA;
|
||||
import anywheresoftware.b4a.keywords.Bit;
|
||||
import jbass.Bass_DeviceInfo;
|
||||
|
||||
@BA.Events(values = {
|
||||
"log(msg as string)",
|
||||
"openudpstreamchannel(success as boolean, localip as string, localport as int, streamhandle as int, channelnumber as int)",
|
||||
"playbackdevicefailed(deviceid as int)",
|
||||
"streamingstatus(channel as int, vu as int, bufferspace as int)",
|
||||
"playbackstatus(channel as int, value as string)"
|
||||
})
|
||||
|
||||
@BA.ShortName("AAS_Receiver_Multichannel")
|
||||
|
||||
/**
|
||||
* AAS Receiver multichannel with 7.1 soundcard
|
||||
* @author rdkartono
|
||||
*/
|
||||
public class AAS_Receiver_Multichannel {
|
||||
|
||||
private BA ba;
|
||||
private String event;
|
||||
private Object Me = this;
|
||||
private BASS bass;
|
||||
private BASSmix bassmix;
|
||||
private boolean inited = false;
|
||||
private int deviceid = -1;
|
||||
private int mixerhandle = 0 ;
|
||||
private boolean need_log_event = false;
|
||||
private boolean need_openudpstreamchannel_event = false;
|
||||
private boolean need_playbackdevicefailed_event = false;
|
||||
private boolean need_streamingstatus_event = false;
|
||||
private boolean need_playbackstatus_event = false;
|
||||
|
||||
ExecutorService exec = null;
|
||||
|
||||
private class streamstatusclass {
|
||||
public int vu = 0;
|
||||
public int handle = 0;
|
||||
public final Integer chnumber;
|
||||
public DatagramSocket theudp = null;
|
||||
private ByteBuffer buffer;
|
||||
public final IDSPPROC channeldsp;
|
||||
public final SYNCPROC channelstalled;
|
||||
public final SYNCPROC channelend;
|
||||
public final SYNCPROC mixerchannelstalled;
|
||||
public final SYNCPROC mixerchannelend;
|
||||
public int relaystatus = -1;
|
||||
|
||||
public streamstatusclass(int chnumber) {
|
||||
this.chnumber = chnumber;
|
||||
buffer = ByteBuffer.allocate(32*1000);
|
||||
channeldsp = new IDSPPROC() {
|
||||
|
||||
@Override
|
||||
public void DSPPROC(int handle, int channel, Pointer buffer, int length, Pointer user) {
|
||||
if (buffer!=null) {
|
||||
if (length>0) {
|
||||
ByteBuffer bb = buffer.getByteBuffer(0, length);
|
||||
ShortBuffer sb = bb.order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();
|
||||
short curvalue=0, maxvalue=0;
|
||||
while(sb.hasRemaining()) {
|
||||
curvalue = sb.get();
|
||||
if (curvalue>maxvalue) maxvalue = curvalue;
|
||||
}
|
||||
vu = (int)( (maxvalue * 100.0)/Short.MAX_VALUE );
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
channelstalled = new SYNCPROC() {
|
||||
|
||||
@Override
|
||||
public void SYNCPROC(int handle, int channel, int data, Pointer user) {
|
||||
switch(data) {
|
||||
case 0 :
|
||||
raise_log_event("BASS_ChannelSetSync Channel="+chnumber+" is stalled");
|
||||
break;
|
||||
case 1 :
|
||||
raise_log_event("BASS_ChannelSetSync Channel="+chnumber+" is resumed");
|
||||
break;
|
||||
default :
|
||||
raise_log_event("BASS_ChannelSetSync Channel="+chnumber+" is unknown stall, data="+data);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
mixerchannelstalled = new SYNCPROC() {
|
||||
|
||||
@Override
|
||||
public void SYNCPROC(int handle, int channel, int data, Pointer user) {
|
||||
switch(data) {
|
||||
case 0 :
|
||||
raise_log_event("BASS_Mixer_ChannelSetSync Channel="+chnumber+" is stalled");
|
||||
break;
|
||||
case 1 :
|
||||
raise_log_event("BASS_Mixer_ChannelSetSync Channel="+chnumber+" is resumed");
|
||||
break;
|
||||
default :
|
||||
raise_log_event("BASS_Mixer_ChannelSetSync Channel="+chnumber+" is unknown stall, data="+data);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
channelend = new SYNCPROC() {
|
||||
|
||||
@Override
|
||||
public void SYNCPROC(int handle, int channel, int data, Pointer user) {
|
||||
switch(data) {
|
||||
case 0 :
|
||||
raise_log_event("BASS_ChannelSetSync channel="+chnumber+" is normal end position");
|
||||
break;
|
||||
case 1 :
|
||||
raise_log_event("BASS_ChannelSetSync channel="+chnumber+" is backward jump in MOD music");
|
||||
break;
|
||||
case 2 :
|
||||
raise_log_event("BASS_ChannelSetSync channel="+chnumber+" is BASS_POS_END position");
|
||||
break;
|
||||
case 3 :
|
||||
raise_log_event("BASS_ChannelSetSync channel="+chnumber+" is end of tail (BASS_ATTRIB_TAIL)");
|
||||
break;
|
||||
default :
|
||||
raise_log_event("BASS_ChannelSetSync channel="+chnumber+" is unknown end, data="+data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
mixerchannelend = new SYNCPROC() {
|
||||
|
||||
@Override
|
||||
public void SYNCPROC(int handle, int channel, int data, Pointer user) {
|
||||
switch(data) {
|
||||
case 0 :
|
||||
raise_log_event("BASS_Mixer_ChannelSetSync channel="+chnumber+" is normal end position");
|
||||
break;
|
||||
case 1 :
|
||||
raise_log_event("BASS_Mixer_ChannelSetSync channel="+chnumber+" is backward jump in MOD music");
|
||||
break;
|
||||
case 2 :
|
||||
raise_log_event("BASS_Mixer_ChannelSetSync channel="+chnumber+" is BASS_POS_END position");
|
||||
break;
|
||||
case 3 :
|
||||
raise_log_event("BASS_Mixer_ChannelSetSync channel="+chnumber+" is end of tail (BASS_ATTRIB_TAIL)");
|
||||
break;
|
||||
default :
|
||||
raise_log_event("BASS_Mixer_ChannelSetSync channel="+chnumber+" is unknown end, data="+data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public int BufferRemaining() {
|
||||
return buffer.remaining();
|
||||
}
|
||||
|
||||
public void PushData(byte[] bb, int bblen) {
|
||||
buffer.put(bb,0,bblen);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void PushData(byte[] bb) {
|
||||
buffer.put(bb);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public byte[] PullData(int length) {
|
||||
byte[] readdata = new byte[length];
|
||||
buffer.flip();
|
||||
buffer.get(readdata);
|
||||
buffer.compact();
|
||||
return readdata;
|
||||
}
|
||||
|
||||
public byte[] PullAllData() {
|
||||
if (BufferPosition()>0) {
|
||||
buffer.flip();
|
||||
byte[] readdata = new byte[buffer.remaining()];
|
||||
buffer.get(readdata);
|
||||
buffer.compact();
|
||||
return readdata;
|
||||
} else return null;
|
||||
}
|
||||
|
||||
public int BufferPosition() {
|
||||
return buffer.position();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public boolean Buffer_inWriteMode() {
|
||||
return buffer.limit()==buffer.capacity() ? true : false;
|
||||
}
|
||||
}
|
||||
|
||||
private volatile streamstatusclass[] streamingstatus = null;
|
||||
|
||||
public AAS_Receiver_Multichannel() {
|
||||
Runtime.getRuntime().addShutdownHook(new Thread() {
|
||||
public void run() {
|
||||
BA.Log("AAS_Receiver_Multichannel ShutdownHook");
|
||||
if (exec!=null) {
|
||||
exec.shutdown();
|
||||
try {
|
||||
exec.awaitTermination(10, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
BA.Log("ExecutorService awaitTermination failed, exception="+e.getMessage());
|
||||
}
|
||||
}
|
||||
CloseDevice();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize AAS Receiver Multichannel
|
||||
* @param event eventname
|
||||
*/
|
||||
public void Initialize(BA ba, String event) {
|
||||
this.ba = ba;
|
||||
this.event = event;
|
||||
check_events();
|
||||
|
||||
bass = new BASS();
|
||||
bassmix = new BASSmix();
|
||||
int bassversion = bass.BASS_GetVersion();
|
||||
int bassmixversion = bassmix.BASS_Mixer_GetVersion();
|
||||
if (bassversion!=0) {
|
||||
if (bassmixversion!=0) {
|
||||
BA.Log("BASS Version="+Bit.ToHexString(bassversion)+", Mixer Version="+Bit.ToHexString(bassmixversion));
|
||||
inited = true;
|
||||
exec = Executors.newFixedThreadPool(20);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if already initialized and BASS can be loaded
|
||||
* @return
|
||||
*/
|
||||
public boolean IsInitialized() {
|
||||
return inited;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open Playback device
|
||||
* @param devid device id, 0 = no sound, 1 = first real device, 2 = ....
|
||||
* @param samplingrate samplingrate
|
||||
* @return true if can be opened
|
||||
*/
|
||||
public boolean OpenDevice(final int devid, final int samplingrate) {
|
||||
deviceid = -1;
|
||||
if (inited) {
|
||||
BASS_DEVICEINFO dvi = new BASS_DEVICEINFO();
|
||||
if (bass.BASS_GetDeviceInfo(devid, dvi)) {
|
||||
dvi.read();
|
||||
Bass_DeviceInfo dv = new Bass_DeviceInfo(devid, dvi);
|
||||
if (dv.isvalid) {
|
||||
if (dv.IsEnabled()) {
|
||||
if (dv.IsInited()) {
|
||||
// sudah initialized, free dulu
|
||||
bass.BASS_SetDevice(devid);
|
||||
bass.BASS_Free();
|
||||
}
|
||||
|
||||
int flags = bassconstant.BASS_DEVICE_16BITS | bassconstant.BASS_DEVICE_SPEAKERS | bassconstant.BASS_DEVICE_FREQ;
|
||||
if (bass.BASS_Init(devid, samplingrate, flags)) {
|
||||
this.deviceid = devid;
|
||||
|
||||
BASS_INFO bi = new BASS_INFO();
|
||||
if (bass.BASS_GetInfo(bi)) {
|
||||
bi.read();
|
||||
|
||||
|
||||
streamingstatus = new streamstatusclass[bi.speakers];
|
||||
for(int ii=0;ii<streamingstatus.length;ii++) streamingstatus[ii] = new streamstatusclass(ii);
|
||||
|
||||
|
||||
|
||||
} else raise_log_event("BASS_GetInfo failed, error="+bass.GetBassErrorString());
|
||||
|
||||
// bassmix.BASS_MIXER_NONSTOP
|
||||
int mixerflag = 0;
|
||||
int mh = bassmix.BASS_Mixer_StreamCreate(samplingrate, 8, mixerflag);
|
||||
if (mh!=0) {
|
||||
if (bass.BASS_ChannelPlay(mh, true)) {
|
||||
this.mixerhandle = mh;
|
||||
bass.BASS_ChannelSetSync(mh, bassconstant.BASS_SYNC_DEV_FAIL, 0, mixerdevfail, null);
|
||||
bass.BASS_ChannelSetSync(mh, bassconstant.BASS_SYNC_STALL, 0, mixerstalled, null);
|
||||
bass.BASS_ChannelSetSync(mh, bassconstant.BASS_SYNC_END, 0, mixerend, null);
|
||||
|
||||
// pancingan
|
||||
exec.submit(new raisestreamingstatusrunnable());
|
||||
|
||||
return true;
|
||||
} else raise_log_event("BASS_ChannelPlay mixerhandle failed, error="+bass.GetBassErrorString());
|
||||
} else raise_log_event("BASS_Mixer_StreamCreate failed, error="+bass.GetBassErrorString());
|
||||
} else raise_log_event("BASS_Init device="+devid+" failed, error="+bass.GetBassErrorString());
|
||||
} else raise_log_event("OpenDevice device="+devid+" failed, Device is disabled");
|
||||
} else raise_log_event("DeviceInfo for device="+devid+" is invalid");
|
||||
} else raise_log_event("BASS_GetDeviceInfo device="+devid+" failed, error="+bass.GetBassErrorString());
|
||||
} else raise_log_event("Call Initialize first");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private SYNCPROC mixerdevfail = new SYNCPROC() {
|
||||
|
||||
@Override
|
||||
public void SYNCPROC(int handle, int channel, int data, Pointer user) {
|
||||
raise_playbackdevicefailed_event(deviceid);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
private SYNCPROC mixerend = new SYNCPROC() {
|
||||
|
||||
@Override
|
||||
public void SYNCPROC(int handle, int channel, int data, Pointer user) {
|
||||
raise_log_event("Mixer reached END");
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
private SYNCPROC mixerstalled = new SYNCPROC() {
|
||||
|
||||
@Override
|
||||
public void SYNCPROC(int handle, int channel, int data, Pointer user) {
|
||||
switch(data) {
|
||||
case 0 :
|
||||
raise_playbackstatus_event(100,"stalled");
|
||||
break;
|
||||
case 1 :
|
||||
raise_playbackstatus_event(100,"resumed");
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get Opened Playback Device ID
|
||||
* @return -1 if not opened
|
||||
*/
|
||||
public int getDeviceID() {
|
||||
return deviceid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close Playback Device
|
||||
*/
|
||||
public void CloseDevice() {
|
||||
if (deviceid!=-1) {
|
||||
if (inited) {
|
||||
|
||||
if (mixerhandle!=0) {
|
||||
bass.BASS_StreamFree(mixerhandle);
|
||||
}
|
||||
mixerhandle = 0;
|
||||
|
||||
bass.BASS_SetDevice(deviceid);
|
||||
bass.BASS_Free();
|
||||
}
|
||||
}
|
||||
deviceid = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close UDP Streaming for channel
|
||||
* @param channelnumber 0 - 7
|
||||
* @return true if success
|
||||
*/
|
||||
public boolean Close_UDP_StreamChannel(int channelnumber) {
|
||||
if (inited) {
|
||||
if (deviceid!=-1) {
|
||||
streamstatusclass sc = GetStreamingStatusMember(channelnumber);
|
||||
if (sc!=null) {
|
||||
if (sc.theudp!=null) {
|
||||
if (sc.theudp.isClosed()==false) {
|
||||
sc.theudp.close();
|
||||
raise_log_event("Close_UDP_StreamChannel, closing UDP for channel="+sc.chnumber);
|
||||
}
|
||||
sc.theudp = null;
|
||||
}
|
||||
if (sc.handle!=0) {
|
||||
if (!bassmix.BASS_Mixer_ChannelRemove(sc.handle)) {
|
||||
raise_log_event("Close_UDP_StreamChannel BASS_Mixer_ChannelRemove failed for channel="+sc.chnumber+", error="+bass.GetBassErrorString());
|
||||
}
|
||||
if (!bass.BASS_ChannelStop(sc.handle)) {
|
||||
raise_log_event("Close_UDP_StreamChannel BASS_ChannelStop failed for channel="+sc.chnumber+", error="+bass.GetBassErrorString());
|
||||
}
|
||||
if (!bass.BASS_StreamFree(sc.handle)) {
|
||||
raise_log_event("Close_UDP_StreamChannel BASS_StreamFree failed for channel="+sc.chnumber+", error="+bass.GetBassErrorString());
|
||||
}
|
||||
sc.handle = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open UDP Streaming , linked with Soundcard channel
|
||||
* will raise event openudpstreamchannel(success as boolean, localip as string, localport as int, streamhandle as int, channelnumber as int)
|
||||
* @param samplingrate samplingrate
|
||||
* @param localip valid IP address to bind
|
||||
* @param localport valid port
|
||||
* @param channelnumber 0 - (detectedspeakers-1)
|
||||
* @return true if DatagramSocket can be created
|
||||
*/
|
||||
public boolean Open_UDP_StreamChannel(int samplingrate, String localip, int localport, int channelnumber) {
|
||||
if (inited) {
|
||||
if (deviceid!=-1) {
|
||||
streamstatusclass sc = GetStreamingStatusMember(channelnumber);
|
||||
if (sc!=null) {
|
||||
|
||||
DatagramSocket udp = null;
|
||||
// coba open UDP listen
|
||||
try {
|
||||
udp = new DatagramSocket(new InetSocketAddress(localip, localport));
|
||||
|
||||
} catch (SocketException e) {
|
||||
raise_log_event("Unable to create DatagramSocket, exception="+e.getMessage());
|
||||
}
|
||||
if (udp != null) {
|
||||
// berhasil UDP listen
|
||||
// mesti bikin final supaya bisa masuk ba.submitrunnable
|
||||
|
||||
sc.theudp = udp;
|
||||
|
||||
// runnable untuk ambil data dari udp;
|
||||
exec.submit(new udprunnable(channelnumber));
|
||||
|
||||
int flag = bassconstant.BASS_STREAM_DECODE;
|
||||
int handle = bass.BASS_StreamCreate(samplingrate, 1, flag, bassconstant.STREAMPROC_PUSH, null);
|
||||
if (handle!=0) {
|
||||
sc.handle = handle;
|
||||
bass.BASS_ChannelSetAttribute(handle,bassconstant.BASS_ATTRIB_PUSH_LIMIT , 0);
|
||||
bass.BASS_ChannelSetDSP(handle, sc.channeldsp, null, 0);
|
||||
bass.BASS_ChannelSetSync(handle, bassconstant.BASS_SYNC_END, 0, sc.channelend, null);
|
||||
bass.BASS_ChannelSetSync(handle, bassconstant.BASS_SYNC_STALL, 0, sc.channelstalled, null);
|
||||
raise_openudpstreamchannel_event(true, sc.theudp.getLocalAddress().getHostAddress(), sc.theudp.getLocalPort(), sc.handle, sc.chnumber);
|
||||
|
||||
exec.submit(new streamputdatarunnable(channelnumber));
|
||||
|
||||
}else {
|
||||
// gagal buka bass stream
|
||||
raise_log_event("BASS_StreamCreate failed, error="+bass.GetBassErrorString());
|
||||
if (sc.theudp != null) {
|
||||
raise_openudpstreamchannel_event(false, sc.theudp.getLocalAddress().getHostAddress(), sc.theudp.getLocalPort(),0, sc.chnumber);
|
||||
if (sc.theudp.isClosed()==false) {
|
||||
sc.theudp.close();
|
||||
}
|
||||
sc.theudp = null;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
} else raise_log_event("Call OpenDevice first");
|
||||
} else raise_log_event("Call Initialize first");
|
||||
return false;
|
||||
}
|
||||
|
||||
private streamstatusclass GetStreamingStatusMember(int index) {
|
||||
if (streamingstatus != null) {
|
||||
if (index>=0) {
|
||||
if (index<streamingstatus.length) {
|
||||
return streamingstatus[index];
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
// Runnable untuk raise_streamingstatus_event periodik
|
||||
private class raisestreamingstatusrunnable implements Runnable{
|
||||
boolean keeprunning = true;
|
||||
public raisestreamingstatusrunnable() {
|
||||
|
||||
}
|
||||
@Override
|
||||
public void run() {
|
||||
keeprunning = true;
|
||||
raise_log_event("raisestreamingstatusrunnable started");
|
||||
while(keeprunning) {
|
||||
try {
|
||||
Thread.sleep(150);
|
||||
} catch (InterruptedException e) {
|
||||
keeprunning = false;
|
||||
}
|
||||
|
||||
for(streamstatusclass ssc : streamingstatus) {
|
||||
raise_streamingstatus_event(ssc.chnumber, ssc.vu, ssc.BufferRemaining());
|
||||
}
|
||||
}
|
||||
raise_log_event("raisestreamingstatusrunnable finished");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private class streamputdatarunnable implements Runnable{
|
||||
private final int channelnumber;
|
||||
private final streamstatusclass ssc;
|
||||
private int mixchanflag = 0;
|
||||
private boolean keeprunning = false;
|
||||
public streamputdatarunnable(int chnum) {
|
||||
channelnumber = chnum;
|
||||
ssc = GetStreamingStatusMember(channelnumber);
|
||||
}
|
||||
@Override
|
||||
public void run() {
|
||||
if (ssc!=null) {
|
||||
final long delay = bass.BASS_ChannelSeconds2Bytes(ssc.handle, 2);
|
||||
raise_log_event("Runnable StreamPutData started for channel="+ssc.chnumber+", will delay "+delay+" bytes");
|
||||
keeprunning = true;
|
||||
while(keeprunning) {
|
||||
|
||||
synchronized(ssc) {
|
||||
if (ssc.handle==0) {
|
||||
raise_log_event("ssc.handle=0 for channel="+ssc.chnumber+", breaking now");
|
||||
keeprunning = false;
|
||||
}
|
||||
if (ssc.BufferPosition()==0) {
|
||||
try {
|
||||
ssc.wait(3000);
|
||||
} catch (InterruptedException e) {
|
||||
raise_log_event("InterruptException on buffer waiting for channel="+ssc.chnumber);
|
||||
keeprunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
// sampe sini , harusnya ada data, kalau gak ada, selesai aja
|
||||
byte[] dataread = ssc.PullAllData();
|
||||
if (dataread!=null) {
|
||||
int rem = dataread.length;
|
||||
Pointer bb = new Memory(rem);
|
||||
bb.write(0, dataread, 0, rem);
|
||||
|
||||
|
||||
int qq = bass.BASS_StreamPutData(ssc.handle, bb, rem);
|
||||
if (ssc.relaystatus!=1) {
|
||||
ssc.relaystatus = 1;
|
||||
raise_playbackstatus_event(ssc.chnumber, "resumed");
|
||||
}
|
||||
mixchanflag = bassmix.BASS_Mixer_ChannelFlags(ssc.handle, 0, 0);
|
||||
if ((mixchanflag & bassmix.BASS_MIXER_CHAN_PAUSE)>0) {
|
||||
if (qq>delay) {
|
||||
raise_log_event("Resuming mixer for channel="+ssc.chnumber);
|
||||
bassmix.BASS_Mixer_ChannelFlags(ssc.handle, 0, bassmix.BASS_MIXER_CHAN_PAUSE);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
|
||||
ssc.vu = 0;
|
||||
if (ssc.relaystatus!=0) {
|
||||
ssc.relaystatus = 0;
|
||||
raise_playbackstatus_event(ssc.chnumber,"stalled");
|
||||
//bass.BASS_StreamPutData(ssc.handle, null, bassconstant.BASS_STREAMPROC_END); // bikin gak bisa streaming
|
||||
raise_log_event("Pausing mixer for channel="+ssc.chnumber);
|
||||
bassmix.BASS_Mixer_ChannelFlags(ssc.handle, bassmix.BASS_MIXER_CHAN_PAUSE, bassmix.BASS_MIXER_CHAN_PAUSE);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (ssc!=null) {
|
||||
|
||||
raise_log_event("Runnable StreamPutData finished for channel="+ssc.chnumber);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class udprunnable implements Runnable{
|
||||
private final int channelnumber;
|
||||
private final streamstatusclass ssc;
|
||||
private boolean keeprunning = false;
|
||||
public udprunnable(int chnum) {
|
||||
channelnumber = chnum;
|
||||
ssc = GetStreamingStatusMember(channelnumber);
|
||||
}
|
||||
@Override
|
||||
public void run() {
|
||||
if (ssc!=null) {
|
||||
raise_log_event("Runnable UDP Receive started for channel="+ssc.chnumber);
|
||||
keeprunning = true;
|
||||
while(keeprunning) {
|
||||
if (ssc.theudp!=null && ssc.theudp.isClosed()==false) {
|
||||
try {
|
||||
// ngeblok di sini sampe dapat paket
|
||||
DatagramPacket pkg = new DatagramPacket(new byte[1500],1500);
|
||||
ssc.theudp.receive(pkg);
|
||||
|
||||
if (pkg!=null) {
|
||||
|
||||
int length = pkg.getLength();
|
||||
byte[] data = pkg.getData();
|
||||
|
||||
if (ssc!=null) {
|
||||
synchronized(ssc) {
|
||||
ssc.PushData(data, length);
|
||||
ssc.notify();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
raise_log_event("IOException dari theudp, exception="+e.getMessage());
|
||||
ssc.theudp.close();
|
||||
ssc.theudp = null;
|
||||
keeprunning = false;
|
||||
}
|
||||
} else {
|
||||
raise_log_event("theudp Runnable must break, theudp isclosed");
|
||||
keeprunning = false;
|
||||
|
||||
}
|
||||
}
|
||||
raise_log_event("Runnable UDP Receive finished for channel="+ssc.chnumber);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Play handle
|
||||
* Obtain handle from event openudpstreamchannel
|
||||
* @param handle
|
||||
* @param channelnumber 0 - 7
|
||||
* @return true if can be played
|
||||
*/
|
||||
public boolean PlayHandle(int handle, int channelnumber) {
|
||||
if (inited) {
|
||||
if (mixerhandle!=0) {
|
||||
if (handle != 0) {
|
||||
|
||||
int flag = bassmix.BASS_MIXER_CHAN_PAUSE;
|
||||
switch(channelnumber) {
|
||||
case 1 :
|
||||
flag |= bassconstant.BASS_SPEAKER_FRONTRIGHT;
|
||||
break;
|
||||
case 2:
|
||||
flag |= bassconstant.BASS_SPEAKER_REARLEFT;
|
||||
break;
|
||||
case 3:
|
||||
flag |= bassconstant.BASS_SPEAKER_REARRIGHT;
|
||||
break;
|
||||
case 4:
|
||||
flag |= bassconstant.BASS_SPEAKER_REAR2LEFT;
|
||||
break;
|
||||
case 5:
|
||||
flag |= bassconstant.BASS_SPEAKER_REAR2RIGHT;
|
||||
break;
|
||||
case 6:
|
||||
flag |= bassconstant.BASS_SPEAKER_CENTER;
|
||||
break;
|
||||
case 7:
|
||||
flag |= bassconstant.BASS_SPEAKER_LFE;
|
||||
break;
|
||||
default:
|
||||
flag |= bassconstant.BASS_SPEAKER_FRONTLEFT;
|
||||
break;
|
||||
}
|
||||
|
||||
if (bassmix.BASS_Mixer_StreamAddChannel(mixerhandle, handle, flag)) {
|
||||
streamstatusclass ssc = GetStreamingStatusMember(channelnumber);
|
||||
if (ssc!=null) {
|
||||
bassmix.BASS_Mixer_ChannelSetSync(handle, bassconstant.BASS_SYNC_STALL, 0, ssc.mixerchannelstalled, null);
|
||||
bassmix.BASS_Mixer_ChannelSetSync(handle, bassconstant.BASS_SYNC_END, 0, ssc.mixerchannelend, null);
|
||||
}
|
||||
|
||||
return true;
|
||||
} else raise_log_event("BASS_Mixer_StreamAddChannel failed , error="+bass.GetBassErrorString());
|
||||
} else raise_log_event("Invalid handle, obtain handle from event openudpstreamchannel");
|
||||
} else raise_log_event("Call OpenDevice first");
|
||||
} else raise_log_event("Call Initialize first");
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop playing handle, and remove it
|
||||
* @param handle
|
||||
* @return true if can be done
|
||||
*/
|
||||
public boolean StopHandle(int handle) {
|
||||
if (inited) {
|
||||
if (mixerhandle!=0) {
|
||||
if (handle!=0) {
|
||||
if (bassmix.BASS_Mixer_ChannelRemove(handle)) {
|
||||
if (bass.BASS_StreamFree(handle)) {
|
||||
raise_log_event("StopHandle handle="+handle+" succcess");
|
||||
return true;
|
||||
} else raise_log_event("BASS_StreamFree failed, error="+bass.GetBassErrorString());
|
||||
} else raise_log_event("BASS_Mixer_ChannelRemove failed, error="+bass.GetBassErrorString());
|
||||
} else raise_log_event("Invalid handle, obtain handle from event openudpstreamchannel");
|
||||
} else raise_log_event("Call OpenDevice first");
|
||||
} else raise_log_event("Call Initialize first");
|
||||
return false;
|
||||
}
|
||||
|
||||
private void check_events() {
|
||||
need_log_event = ba.subExists(event+"_log");
|
||||
need_openudpstreamchannel_event = ba.subExists(event+"_openudpstreamchannel");
|
||||
need_playbackdevicefailed_event = ba.subExists(event+"_playbackdevicefailed");
|
||||
need_streamingstatus_event = ba.subExists(event+"_streamingstatus");
|
||||
need_playbackstatus_event = ba.subExists(event+"_playbackstatus");
|
||||
}
|
||||
|
||||
private void raise_log_event(String msg) {
|
||||
if (need_log_event) ba.raiseEventFromDifferentThread(Me, null, 0, event+"_log", false, new Object[] {msg});
|
||||
}
|
||||
|
||||
private void raise_openudpstreamchannel_event(boolean success, String localip, int localport, int streamhandle, int channelnumber) {
|
||||
if (need_openudpstreamchannel_event) ba.raiseEventFromDifferentThread(Me, null, 0, event+"_openudpstreamchannel", false, new Object[] {success, localip, localport, streamhandle, channelnumber});
|
||||
}
|
||||
|
||||
private void raise_playbackdevicefailed_event(int deviceid) {
|
||||
if (need_playbackdevicefailed_event) ba.raiseEventFromDifferentThread(Me, null, 0, event+"_playbackdevicefailed", false, new Object[] {deviceid});
|
||||
}
|
||||
|
||||
private void raise_streamingstatus_event(int channelnumber, int vu, int buffferspace) {
|
||||
if (need_streamingstatus_event) ba.raiseEventFromDifferentThread(Me, null, 0, event+"_streamingstatus", false, new Object[] {channelnumber, vu, buffferspace});
|
||||
}
|
||||
|
||||
private void raise_playbackstatus_event(int channelnumber, String value) {
|
||||
if (need_playbackstatus_event) ba.raiseEventFromDifferentThread(Me, null, 0, event+"_playbackstatus", false, new Object[] {channelnumber, value});
|
||||
}
|
||||
|
||||
}
|
||||
1174
src/com/un4seen/bass/BASS.java
Normal file
1174
src/com/un4seen/bass/BASS.java
Normal file
File diff suppressed because it is too large
Load Diff
31
src/com/un4seen/bass/BASSALAC.java
Normal file
31
src/com/un4seen/bass/BASSALAC.java
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
BASSALAC 2.4 C/C++ header file
|
||||
Copyright (c) 2016 Un4seen Developments Ltd.
|
||||
|
||||
See the BASSALAC.CHM file for more detailed documentation
|
||||
*/
|
||||
|
||||
package com.un4seen.bass;
|
||||
|
||||
|
||||
import com.sun.jna.Native;
|
||||
import com.sun.jna.Pointer;
|
||||
|
||||
|
||||
public class BASSALAC
|
||||
{
|
||||
// additional error codes returned by BASS_ErrorGetCode
|
||||
public static final int BASS_ERROR_MP4_NOSTREAM = 6000; // non-streamable due to MP4 atom order ("mdat" before "moov")
|
||||
|
||||
// BASS_CHANNELINFO type
|
||||
public static final int BASS_CTYPE_STREAM_ALAC = 0x10e00;
|
||||
|
||||
public static native int BASS_ALAC_StreamCreateFile(String file, long offset, long length, int flags);
|
||||
public static native int BASS_ALAC_StreamCreateURL(String url, int offset, int flags, BASS.DOWNLOADPROC proc, Pointer user);
|
||||
public static native int BASS_ALAC_StreamCreateFileUser(int system, int flags, BASS.BASS_FILEPROCS procs, Pointer user);
|
||||
|
||||
static {
|
||||
String path = BassLibrary.ExtractLibraryFromJar(BassLibrary.lib_bassalac);
|
||||
if (path!=null && path.length()>0) Native.register(BASSALAC.class, path);
|
||||
}
|
||||
}
|
||||
73
src/com/un4seen/bass/BASSDSD.java
Normal file
73
src/com/un4seen/bass/BASSDSD.java
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
BASSDSD 2.4 Java class
|
||||
Copyright (c) 2014-2017 Un4seen Developments Ltd.
|
||||
|
||||
See the BASSDSD.CHM file for more detailed documentation
|
||||
*/
|
||||
|
||||
package com.un4seen.bass;
|
||||
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import com.sun.jna.Native;
|
||||
import com.sun.jna.Pointer;
|
||||
import com.sun.jna.Structure;
|
||||
|
||||
import anywheresoftware.b4a.BA;
|
||||
|
||||
public class BASSDSD
|
||||
{
|
||||
// Additional BASS_SetConfig options
|
||||
public static final int BASS_CONFIG_DSD_FREQ = 0x10800;
|
||||
public static final int BASS_CONFIG_DSD_GAIN = 0x10801;
|
||||
|
||||
// Additional BASS_DSD_StreamCreateFile/etc flags
|
||||
public static final int BASS_DSD_RAW = 0x200;
|
||||
public static final int BASS_DSD_DOP = 0x400;
|
||||
public static final int BASS_DSD_DOP_AA = 0x800;
|
||||
|
||||
// BASS_CHANNELINFO type
|
||||
public static final int BASS_CTYPE_STREAM_DSD = 0x11700;
|
||||
|
||||
// Additional tag types
|
||||
public static final int BASS_TAG_DSD_ARTIST = 0x13000; // DSDIFF artist : String
|
||||
public static final int BASS_TAG_DSD_TITLE = 0x13001; // DSDIFF title : String
|
||||
public static final int BASS_TAG_DSD_COMMENT = 0x13100; // + index, DSDIFF comment : TAG_DSD_COMMENT
|
||||
|
||||
@BA.ShortName("TAG_DSD_COMMENT")
|
||||
// @Structure.FieldOrder({"timeStampYear","TimeStampMonth","timeStampDay","timeStampHour","timeStampMinutes","cmtType","cmtRef","commentText"})
|
||||
public static class TAG_DSD_COMMENT extends Structure {
|
||||
short timeStampYear; // creation year
|
||||
byte TimeStampMonth; // creation month
|
||||
byte timeStampDay; // creation day
|
||||
byte timeStampHour; // creation hour
|
||||
byte timeStampMinutes; // creation minutes
|
||||
short cmtType; // comment type
|
||||
short cmtRef; // comment reference
|
||||
String commentText; // text
|
||||
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return Arrays.asList(
|
||||
"timeStampYear","TimeStampMonth","timeStampDay","timeStampHour","timeStampMinutes",
|
||||
"cmtType","cmtRef","commentText");
|
||||
}
|
||||
|
||||
@BA.Hide public void autoWrite(){}
|
||||
@BA.Hide public void autoRead(){}
|
||||
}
|
||||
|
||||
// Additional attributes
|
||||
public static final int BASS_ATTRIB_DSD_GAIN = 0x14000;
|
||||
public static final int BASS_ATTRIB_DSD_RATE = 0x14001;
|
||||
|
||||
public static native int BASS_DSD_StreamCreateFile(String file, long offset, long length, int flags, int freq);
|
||||
public static native int BASS_DSD_StreamCreateURL(String url, int offset, int flags, BASS.DOWNLOADPROC proc, Pointer user, int freq);
|
||||
public static native int BASS_DSD_StreamCreateFileUser(int system, int flags, BASS.BASS_FILEPROCS procs, Pointer user, int freq);
|
||||
|
||||
static {
|
||||
String path = BassLibrary.ExtractLibraryFromJar(BassLibrary.lib_bassdsd);
|
||||
if (path!=null && path.length()>0) Native.register(BASSDSD.class, path); }
|
||||
}
|
||||
135
src/com/un4seen/bass/BASSFLAC.java
Normal file
135
src/com/un4seen/bass/BASSFLAC.java
Normal file
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
BASSFLAC 2.4 Java class
|
||||
Copyright (c) 2004-2017 Un4seen Developments Ltd.
|
||||
|
||||
See the BASSFLAC.CHM file for more detailed documentation
|
||||
*/
|
||||
|
||||
package com.un4seen.bass;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import com.sun.jna.Native;
|
||||
import com.sun.jna.Pointer;
|
||||
import com.sun.jna.Structure;
|
||||
|
||||
import anywheresoftware.b4a.BA;
|
||||
|
||||
public class BASSFLAC
|
||||
{
|
||||
// BASS_CHANNELINFO type
|
||||
public static final int BASS_CTYPE_STREAM_FLAC = 0x10900;
|
||||
public static final int BASS_CTYPE_STREAM_FLAC_OGG = 0x10901;
|
||||
|
||||
// Additional tag types
|
||||
public static final int BASS_TAG_FLAC_CUE = 12; // cuesheet : TAG_FLAC_CUE
|
||||
public static final int BASS_TAG_FLAC_PICTURE = 0x12000; // + index #, picture : TAG_FLAC_PICTURE
|
||||
public static final int BASS_TAG_FLAC_METADATA = 0x12400; // + index #, application metadata : TAG_FLAC_METADATA
|
||||
|
||||
@BA.ShortName("TAG_FLAC_PICTURE")
|
||||
// @Structure.FieldOrder({"apic","mime","desc","width","height","depth","colors","length","data"})
|
||||
public static class TAG_FLAC_PICTURE extends Structure{
|
||||
public int apic; // ID3v2 "APIC" picture type
|
||||
public String mime; // mime type
|
||||
public String desc; // description
|
||||
public int width;
|
||||
public int height;
|
||||
public int depth;
|
||||
public int colors;
|
||||
public int length; // data length
|
||||
public Pointer data;
|
||||
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return Arrays.asList(
|
||||
"apic","mime","desc","width","height","depth","colors","length","data");
|
||||
}
|
||||
|
||||
@BA.Hide public void autoWrite(){}
|
||||
@BA.Hide public void autoRead(){}
|
||||
}
|
||||
|
||||
@BA.ShortName("TAG_FLAC_CUE_TRACK_INDEX")
|
||||
// @Structure.FieldOrder({"offset","number"})
|
||||
public static class TAG_FLAC_CUE_TRACK_INDEX extends Structure{
|
||||
public long offset; // index offset relative to track offset (samples)
|
||||
public int number; // index number
|
||||
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return Arrays.asList(
|
||||
"offset","number");
|
||||
}
|
||||
|
||||
@BA.Hide public void autoWrite(){}
|
||||
@BA.Hide public void autoRead(){}
|
||||
}
|
||||
|
||||
@BA.ShortName("TAG_FLAC_CUE_TRACK")
|
||||
// @Structure.FieldOrder({"offset","number","isrc","flags","nindexes","indexes"})
|
||||
public static class TAG_FLAC_CUE_TRACK extends Structure{
|
||||
public long offset; // track offset (samples)
|
||||
public int number; // track number
|
||||
public String isrc; // ISRC
|
||||
public int flags;
|
||||
public int nindexes; // number of indexes
|
||||
public TAG_FLAC_CUE_TRACK_INDEX[] indexes; // the indexes
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return Arrays.asList(
|
||||
"offset","number","isrc","flags","nindexes","indexes");
|
||||
}
|
||||
|
||||
@BA.Hide public void autoWrite(){}
|
||||
@BA.Hide public void autoRead(){}
|
||||
}
|
||||
|
||||
@BA.ShortName("TAG_FLAC_CUE")
|
||||
// @Structure.FieldOrder({"catalog","leadin","iscd","ntracks","tracks"})
|
||||
public static class TAG_FLAC_CUE extends Structure{
|
||||
public String catalog; // media catalog number
|
||||
public int leadin; // lead-in (samples)
|
||||
public boolean iscd; // a CD?
|
||||
public int ntracks; // number of tracks
|
||||
public TAG_FLAC_CUE_TRACK[] tracks; // the tracks
|
||||
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return Arrays.asList(
|
||||
"catalog","leadin","iscd","ntracks","tracks");
|
||||
}
|
||||
|
||||
@BA.Hide public void autoWrite(){}
|
||||
@BA.Hide public void autoRead(){}
|
||||
}
|
||||
|
||||
// TAG_FLAC_CUE_TRACK flags
|
||||
public static final int TAG_FLAC_CUE_TRACK_DATA = 1; // data track
|
||||
public static final int TAG_FLAC_CUE_TRACK_PRE = 2; // pre-emphasis
|
||||
|
||||
@BA.ShortName("TAG_FLAC_METADATA")
|
||||
// @Structure.FieldOrder({"id","length","data"})
|
||||
public static class TAG_FLAC_METADATA extends Structure{
|
||||
public String id;
|
||||
public int length; // data length
|
||||
public Pointer data;
|
||||
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return Arrays.asList(
|
||||
"id","length","data");
|
||||
}
|
||||
|
||||
@BA.Hide public void autoWrite(){}
|
||||
@BA.Hide public void autoRead(){}
|
||||
}
|
||||
|
||||
public static native int BASS_FLAC_StreamCreateFile(String file, long offset, long length, int flags);
|
||||
public static native int BASS_FLAC_StreamCreateURL(String url, int offset, int flags, BASS.DOWNLOADPROC proc, Pointer user);
|
||||
public static native int BASS_FLAC_StreamCreateFileUser(int system, int flags, BASS.BASS_FILEPROCS procs, Pointer user);
|
||||
|
||||
static {
|
||||
String path = BassLibrary.ExtractLibraryFromJar(BassLibrary.lib_bassflac);
|
||||
if (path!=null && path.length()>0) Native.register(BASSFLAC.class, path); }
|
||||
}
|
||||
40
src/com/un4seen/bass/BASSHLS.java
Normal file
40
src/com/un4seen/bass/BASSHLS.java
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
BASSHLS 2.4 Java class
|
||||
Copyright (c) 2015-2019 Un4seen Developments Ltd.
|
||||
|
||||
See the BASSHLS.CHM file for more detailed documentation
|
||||
*/
|
||||
|
||||
package com.un4seen.bass;
|
||||
|
||||
import com.sun.jna.Native;
|
||||
import com.sun.jna.Pointer;
|
||||
|
||||
|
||||
public class BASSHLS
|
||||
{
|
||||
// additional BASS_SetConfig options
|
||||
public static final int BASS_CONFIG_HLS_DOWNLOAD_TAGS = 0x10900;
|
||||
public static final int BASS_CONFIG_HLS_BANDWIDTH = 0x10901;
|
||||
public static final int BASS_CONFIG_HLS_DELAY = 0x10902;
|
||||
|
||||
// additional sync type
|
||||
public static final int BASS_SYNC_HLS_SEGMENT = 0x10300;
|
||||
|
||||
// additional tag types
|
||||
public static final int BASS_TAG_HLS_EXTINF = 0x14000; // segment's EXTINF tag : String
|
||||
public static final int BASS_TAG_HLS_STREAMINF = 0x14001; // EXT-X-STREAM-INF tag : UTF-8 string
|
||||
public static final int BASS_TAG_HLS_DATE = 0x14002; // EXT-X-PROGRAM-DATE-TIME tag : UTF-8 string
|
||||
|
||||
// additional BASS_StreamGetFilePosition mode
|
||||
public static final int BASS_FILEPOS_HLS_SEGMENT = 0x10000; // segment sequence number
|
||||
|
||||
public static native int BASS_HLS_StreamCreateFile(String file, long offset, long length, int flags);
|
||||
public static native int BASS_HLS_StreamCreateURL(String url, int flags, BASS.DOWNLOADPROC proc, Pointer user);
|
||||
|
||||
static {
|
||||
String path = BassLibrary.ExtractLibraryFromJar(BassLibrary.lib_basshls);
|
||||
if (path!=null && path.length()>0) Native.register(BASSHLS.class, path);
|
||||
}
|
||||
|
||||
}
|
||||
389
src/com/un4seen/bass/BASSMIDI.java
Normal file
389
src/com/un4seen/bass/BASSMIDI.java
Normal file
@@ -0,0 +1,389 @@
|
||||
/*
|
||||
BASSMIDI 2.4 Java class
|
||||
Copyright (c) 2006-2018 Un4seen Developments Ltd.
|
||||
|
||||
See the BASSMIDI.CHM file for more detailed documentation
|
||||
*/
|
||||
|
||||
package com.un4seen.bass;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import com.sun.jna.Callback;
|
||||
import com.sun.jna.Native;
|
||||
import com.sun.jna.Pointer;
|
||||
import com.sun.jna.Structure;
|
||||
|
||||
import anywheresoftware.b4a.BA;
|
||||
|
||||
//import com.un4seen.bass.BASS.Asset;
|
||||
|
||||
public class BASSMIDI
|
||||
{
|
||||
// Additional error codes returned by BASS_ErrorGetCode
|
||||
public static final int BASS_ERROR_MIDI_INCLUDE=7000;
|
||||
|
||||
// Additional BASS_SetConfig options
|
||||
public static final int BASS_CONFIG_MIDI_COMPACT=0x10400;
|
||||
public static final int BASS_CONFIG_MIDI_VOICES=0x10401;
|
||||
public static final int BASS_CONFIG_MIDI_AUTOFONT=0x10402;
|
||||
|
||||
// Additional BASS_SetConfigPtr options
|
||||
public static final int BASS_CONFIG_MIDI_DEFFONT=0x10403;
|
||||
|
||||
// Additional sync types
|
||||
public static final int BASS_SYNC_MIDI_MARK=0x10000;
|
||||
public static final int BASS_SYNC_MIDI_EVENT=0x10004;
|
||||
public static final int BASS_SYNC_MIDI_TICK=0x10005;
|
||||
|
||||
// Additional BASS_MIDI_StreamCreateFile/etc flags
|
||||
public static final int BASS_MIDI_NOSYSRESET=0x800;
|
||||
public static final int BASS_MIDI_DECAYEND=0x1000;
|
||||
public static final int BASS_MIDI_NOFX=0x2000;
|
||||
public static final int BASS_MIDI_DECAYSEEK=0x4000;
|
||||
public static final int BASS_MIDI_NOCROP=0x8000;
|
||||
public static final int BASS_MIDI_NOTEOFF1=0x10000;
|
||||
public static final int BASS_MIDI_SINCINTER=0x800000;
|
||||
|
||||
// BASS_MIDI_FontInit flags
|
||||
public static final int BASS_MIDI_FONT_MMAP=0x20000;
|
||||
public static final int BASS_MIDI_FONT_XGDRUMS=0x40000;
|
||||
public static final int BASS_MIDI_FONT_NOFX=0x80000;
|
||||
public static final int BASS_MIDI_FONT_LINATTMOD=0x100000;
|
||||
|
||||
@BA.ShortName("BASS_MIDI_FONT")
|
||||
// @Structure.FieldOrder({"font","preset","bank"})
|
||||
public static class BASS_MIDI_FONT extends Structure {
|
||||
public int font; // soundfont
|
||||
public int preset; // preset number (-1=all)
|
||||
public int bank;
|
||||
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return Arrays.asList(
|
||||
"font","preset","bank"
|
||||
);
|
||||
}
|
||||
|
||||
@BA.Hide public void autoWrite(){}
|
||||
@BA.Hide public void autoRead(){}
|
||||
}
|
||||
|
||||
@BA.ShortName("BASS_MIDI_FONTEX")
|
||||
// @Structure.FieldOrder({"font","spreset","sbank","dpreset","dbank","dbanklsb"})
|
||||
public static class BASS_MIDI_FONTEX extends Structure{
|
||||
public int font; // soundfont
|
||||
public int spreset; // source preset number
|
||||
public int sbank; // source bank number
|
||||
public int dpreset; // destination preset/program number
|
||||
public int dbank; // destination bank number
|
||||
public int dbanklsb; // destination bank number LSB
|
||||
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return Arrays.asList(
|
||||
"font","spreset","sbank","dpreset","dbank","dbanklsb"
|
||||
);
|
||||
}
|
||||
|
||||
@BA.Hide public void autoWrite(){}
|
||||
@BA.Hide public void autoRead(){}
|
||||
}
|
||||
|
||||
// BASS_MIDI_StreamSet/GetFonts flag
|
||||
public static final int BASS_MIDI_FONT_EX=0x1000000; // BASS_MIDI_FONTEX (auto-detected)
|
||||
|
||||
@BA.ShortName("BASS_MIDI_FONTINFO")
|
||||
// @Structure.FieldOrder({"name","copyright","comment","presets","samsize","samload","samtype"})
|
||||
public static class BASS_MIDI_FONTINFO extends Structure{
|
||||
public String name;
|
||||
public String copyright;
|
||||
public String comment;
|
||||
public int presets; // number of presets/instruments
|
||||
public int samsize; // total size (in bytes) of the sample data
|
||||
public int samload; // amount of sample data currently loaded
|
||||
public int samtype; // sample format (CTYPE) if packed
|
||||
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return Arrays.asList(
|
||||
"name","copyright","comment","presets","samsize","samload","samtype"
|
||||
);
|
||||
}
|
||||
|
||||
@BA.Hide public void autoWrite(){}
|
||||
@BA.Hide public void autoRead(){}
|
||||
}
|
||||
|
||||
@BA.ShortName("BASS_MIDI_MARK")
|
||||
// @Structure.FieldOrder({"track","pos","text"})
|
||||
public static class BASS_MIDI_MARK extends Structure{
|
||||
public int track; // track containing marker
|
||||
public int pos; // marker position
|
||||
public String text; // marker text
|
||||
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return Arrays.asList(
|
||||
"track","pos","text"
|
||||
);
|
||||
}
|
||||
|
||||
@BA.Hide public void autoWrite(){}
|
||||
@BA.Hide public void autoRead(){}
|
||||
}
|
||||
|
||||
@BA.ShortName("BASS_MIDI_MARKB")
|
||||
// @Structure.FieldOrder({"track","pos","text"})
|
||||
public static class BASS_MIDI_MARKB extends Structure{
|
||||
public int track; // track containing marker
|
||||
public int pos; // marker position
|
||||
public byte[] text; // marker text
|
||||
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return Arrays.asList(
|
||||
"track","pos","text"
|
||||
);
|
||||
}
|
||||
|
||||
@BA.Hide public void autoWrite(){}
|
||||
@BA.Hide public void autoRead(){}
|
||||
}
|
||||
|
||||
|
||||
// Marker types
|
||||
public static final int BASS_MIDI_MARK_MARKER=0; // marker
|
||||
public static final int BASS_MIDI_MARK_CUE=1; // cue point
|
||||
public static final int BASS_MIDI_MARK_LYRIC=2; // lyric
|
||||
public static final int BASS_MIDI_MARK_TEXT=3; // text
|
||||
public static final int BASS_MIDI_MARK_TIMESIG=4; // time signature
|
||||
public static final int BASS_MIDI_MARK_KEYSIG=5; // key signature
|
||||
public static final int BASS_MIDI_MARK_COPY=6; // copyright notice
|
||||
public static final int BASS_MIDI_MARK_TRACK=7; // track name
|
||||
public static final int BASS_MIDI_MARK_INST=8; // instrument name
|
||||
public static final int BASS_MIDI_MARK_TRACKSTART=9; // track start (SMF2)
|
||||
public static final int BASS_MIDI_MARK_TICK=0x10000; // flag: get position in ticks (otherwise bytes)
|
||||
|
||||
// MIDI events
|
||||
public static final int MIDI_EVENT_NOTE=1;
|
||||
public static final int MIDI_EVENT_PROGRAM=2;
|
||||
public static final int MIDI_EVENT_CHANPRES=3;
|
||||
public static final int MIDI_EVENT_PITCH=4;
|
||||
public static final int MIDI_EVENT_PITCHRANGE=5;
|
||||
public static final int MIDI_EVENT_DRUMS=6;
|
||||
public static final int MIDI_EVENT_FINETUNE=7;
|
||||
public static final int MIDI_EVENT_COARSETUNE=8;
|
||||
public static final int MIDI_EVENT_MASTERVOL=9;
|
||||
public static final int MIDI_EVENT_BANK=10;
|
||||
public static final int MIDI_EVENT_MODULATION=11;
|
||||
public static final int MIDI_EVENT_VOLUME=12;
|
||||
public static final int MIDI_EVENT_PAN=13;
|
||||
public static final int MIDI_EVENT_EXPRESSION=14;
|
||||
public static final int MIDI_EVENT_SUSTAIN=15;
|
||||
public static final int MIDI_EVENT_SOUNDOFF=16;
|
||||
public static final int MIDI_EVENT_RESET=17;
|
||||
public static final int MIDI_EVENT_NOTESOFF=18;
|
||||
public static final int MIDI_EVENT_PORTAMENTO=19;
|
||||
public static final int MIDI_EVENT_PORTATIME=20;
|
||||
public static final int MIDI_EVENT_PORTANOTE=21;
|
||||
public static final int MIDI_EVENT_MODE=22;
|
||||
public static final int MIDI_EVENT_REVERB=23;
|
||||
public static final int MIDI_EVENT_CHORUS=24;
|
||||
public static final int MIDI_EVENT_CUTOFF=25;
|
||||
public static final int MIDI_EVENT_RESONANCE=26;
|
||||
public static final int MIDI_EVENT_RELEASE=27;
|
||||
public static final int MIDI_EVENT_ATTACK=28;
|
||||
public static final int MIDI_EVENT_DECAY=29;
|
||||
public static final int MIDI_EVENT_REVERB_MACRO=30;
|
||||
public static final int MIDI_EVENT_CHORUS_MACRO=31;
|
||||
public static final int MIDI_EVENT_REVERB_TIME=32;
|
||||
public static final int MIDI_EVENT_REVERB_DELAY=33;
|
||||
public static final int MIDI_EVENT_REVERB_LOCUTOFF=34;
|
||||
public static final int MIDI_EVENT_REVERB_HICUTOFF=35;
|
||||
public static final int MIDI_EVENT_REVERB_LEVEL=36;
|
||||
public static final int MIDI_EVENT_CHORUS_DELAY=37;
|
||||
public static final int MIDI_EVENT_CHORUS_DEPTH=38;
|
||||
public static final int MIDI_EVENT_CHORUS_RATE=39;
|
||||
public static final int MIDI_EVENT_CHORUS_FEEDBACK=40;
|
||||
public static final int MIDI_EVENT_CHORUS_LEVEL=41;
|
||||
public static final int MIDI_EVENT_CHORUS_REVERB=42;
|
||||
public static final int MIDI_EVENT_USERFX=43;
|
||||
public static final int MIDI_EVENT_USERFX_LEVEL=44;
|
||||
public static final int MIDI_EVENT_USERFX_REVERB=45;
|
||||
public static final int MIDI_EVENT_USERFX_CHORUS=46;
|
||||
public static final int MIDI_EVENT_DRUM_FINETUNE=50;
|
||||
public static final int MIDI_EVENT_DRUM_COARSETUNE=51;
|
||||
public static final int MIDI_EVENT_DRUM_PAN=52;
|
||||
public static final int MIDI_EVENT_DRUM_REVERB=53;
|
||||
public static final int MIDI_EVENT_DRUM_CHORUS=54;
|
||||
public static final int MIDI_EVENT_DRUM_CUTOFF=55;
|
||||
public static final int MIDI_EVENT_DRUM_RESONANCE=56;
|
||||
public static final int MIDI_EVENT_DRUM_LEVEL=57;
|
||||
public static final int MIDI_EVENT_DRUM_USERFX=58;
|
||||
public static final int MIDI_EVENT_SOFT=60;
|
||||
public static final int MIDI_EVENT_SYSTEM=61;
|
||||
public static final int MIDI_EVENT_TEMPO=62;
|
||||
public static final int MIDI_EVENT_SCALETUNING=63;
|
||||
public static final int MIDI_EVENT_CONTROL=64;
|
||||
public static final int MIDI_EVENT_CHANPRES_VIBRATO=65;
|
||||
public static final int MIDI_EVENT_CHANPRES_PITCH=66;
|
||||
public static final int MIDI_EVENT_CHANPRES_FILTER=67;
|
||||
public static final int MIDI_EVENT_CHANPRES_VOLUME=68;
|
||||
public static final int MIDI_EVENT_MOD_VIBRATO=69;
|
||||
public static final int MIDI_EVENT_MODRANGE=69;
|
||||
public static final int MIDI_EVENT_BANK_LSB=70;
|
||||
public static final int MIDI_EVENT_KEYPRES=71;
|
||||
public static final int MIDI_EVENT_KEYPRES_VIBRATO=72;
|
||||
public static final int MIDI_EVENT_KEYPRES_PITCH=73;
|
||||
public static final int MIDI_EVENT_KEYPRES_FILTER=74;
|
||||
public static final int MIDI_EVENT_KEYPRES_VOLUME=75;
|
||||
public static final int MIDI_EVENT_SOSTENUTO=76;
|
||||
public static final int MIDI_EVENT_MOD_PITCH=77;
|
||||
public static final int MIDI_EVENT_MOD_FILTER=78;
|
||||
public static final int MIDI_EVENT_MOD_VOLUME=79;
|
||||
public static final int MIDI_EVENT_MIXLEVEL=0x10000;
|
||||
public static final int MIDI_EVENT_TRANSPOSE=0x10001;
|
||||
public static final int MIDI_EVENT_SYSTEMEX=0x10002;
|
||||
public static final int MIDI_EVENT_SPEED=0x10004;
|
||||
|
||||
public static final int MIDI_EVENT_END=0;
|
||||
public static final int MIDI_EVENT_END_TRACK=0x10003;
|
||||
|
||||
public static final int MIDI_EVENT_NOTES=0x20000;
|
||||
public static final int MIDI_EVENT_VOICES=0x20001;
|
||||
|
||||
public static final int MIDI_SYSTEM_DEFAULT=0;
|
||||
public static final int MIDI_SYSTEM_GM1=1;
|
||||
public static final int MIDI_SYSTEM_GM2=2;
|
||||
public static final int MIDI_SYSTEM_XG=3;
|
||||
public static final int MIDI_SYSTEM_GS=4;
|
||||
|
||||
@BA.ShortName("BASS_MIDI_EVENT")
|
||||
// @Structure.FieldOrder({"event","param","chan","tick","pos"})
|
||||
public static class BASS_MIDI_EVENT extends Structure{
|
||||
public int event; // MIDI_EVENT_xxx
|
||||
public int param;
|
||||
public int chan;
|
||||
public int tick; // event position (ticks)
|
||||
public int pos; // event position (bytes)
|
||||
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return Arrays.asList(
|
||||
"event","param","chan","tick","pos"
|
||||
);
|
||||
}
|
||||
|
||||
@BA.Hide public void autoWrite(){}
|
||||
@BA.Hide public void autoRead(){}
|
||||
}
|
||||
|
||||
// BASS_MIDI_StreamEvents modes
|
||||
public static final int BASS_MIDI_EVENTS_STRUCT=0; // BASS_MIDI_EVENT structures
|
||||
public static final int BASS_MIDI_EVENTS_RAW=0x10000; // raw MIDI event data
|
||||
public static final int BASS_MIDI_EVENTS_SYNC=0x1000000; // flag: trigger event syncs
|
||||
public static final int BASS_MIDI_EVENTS_NORSTATUS=0x2000000; // flag: no running status
|
||||
public static final int BASS_MIDI_EVENTS_CANCEL=0x4000000; // flag: cancel pending events
|
||||
public static final int BASS_MIDI_EVENTS_TIME=0x8000000; // flag: delta-time info is present
|
||||
public static final int BASS_MIDI_EVENTS_ABSTIME=0x10000000; // flag: absolute time info is present
|
||||
|
||||
// BASS_MIDI_StreamGetChannel special channels
|
||||
public static final int BASS_MIDI_CHAN_CHORUS=-1;
|
||||
public static final int BASS_MIDI_CHAN_REVERB=-2;
|
||||
public static final int BASS_MIDI_CHAN_USERFX=-3;
|
||||
|
||||
// BASS_CHANNELINFO type
|
||||
public static final int BASS_CTYPE_STREAM_MIDI=0x10d00;
|
||||
|
||||
// Additional attributes
|
||||
public static final int BASS_ATTRIB_MIDI_PPQN=0x12000;
|
||||
public static final int BASS_ATTRIB_MIDI_CPU=0x12001;
|
||||
public static final int BASS_ATTRIB_MIDI_CHANS=0x12002;
|
||||
public static final int BASS_ATTRIB_MIDI_VOICES=0x12003;
|
||||
public static final int BASS_ATTRIB_MIDI_VOICES_ACTIVE=0x12004;
|
||||
public static final int BASS_ATTRIB_MIDI_STATE=0x12005;
|
||||
public static final int BASS_ATTRIB_MIDI_SRC=0x12006;
|
||||
public static final int BASS_ATTRIB_MIDI_KILL=0x12007;
|
||||
public static final int BASS_ATTRIB_MIDI_TRACK_VOL=0x12100; // + track #
|
||||
|
||||
// Additional tag type
|
||||
public static final int BASS_TAG_MIDI_TRACK=0x11000; // + track #, track text : array of null-terminated ANSI strings
|
||||
|
||||
// BASS_ChannelGetLength/GetPosition/SetPosition mode
|
||||
public static final int BASS_POS_MIDI_TICK=2; // tick position
|
||||
|
||||
public interface MIDIFILTERPROC extends Callback
|
||||
{
|
||||
boolean MIDIFILTERPROC(int handle, int track, BASS_MIDI_EVENT event, boolean seeking, Pointer user);
|
||||
/* Event filtering callback function.
|
||||
handle : MIDI stream handle
|
||||
track : Track containing the event
|
||||
event : The event
|
||||
seeking: true = the event is being processed while seeking, false = it is being played
|
||||
user : The 'user' parameter value given when calling BASS_MIDI_StreamSetFilter
|
||||
RETURN : true = process the event, false = drop the event */
|
||||
}
|
||||
|
||||
@BA.ShortName("BASS_MIDI_DEVICEINFO")
|
||||
// @Structure.FieldOrder({"name","id","flags"})
|
||||
public static class BASS_MIDI_DEVICEINFO extends Structure{
|
||||
public String name; // description
|
||||
public int id;
|
||||
public int flags;
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return Arrays.asList(
|
||||
"name","id","flags"
|
||||
);
|
||||
}
|
||||
|
||||
@BA.Hide public void autoWrite(){}
|
||||
@BA.Hide public void autoRead(){}
|
||||
}
|
||||
|
||||
public static native int BASS_MIDI_StreamCreate(int channels, int flags, int freq);
|
||||
public static native int BASS_MIDI_StreamCreateFile(String file, long offset, long length, int flags, int freq);
|
||||
public static native int BASS_MIDI_StreamCreateURL(String url, int offset, int flags, BASS.DOWNLOADPROC proc, Pointer user, int freq);
|
||||
public static native int BASS_MIDI_StreamCreateFileUser(int system, int flags, BASS.BASS_FILEPROCS procs, Pointer user, int freq);
|
||||
public static native int BASS_MIDI_StreamCreateEvents(BASS_MIDI_EVENT[] events, int ppqn, int flags, int freq);
|
||||
public static native boolean BASS_MIDI_StreamGetMark(int handle, int type, int index, BASS_MIDI_MARK mark);
|
||||
public static native boolean BASS_MIDI_StreamGetMark(int handle, int type, int index, BASS_MIDI_MARKB mark);
|
||||
public static native int BASS_MIDI_StreamGetMarks(int handle, int track, int type, BASS_MIDI_MARK[] marks);
|
||||
public static native int BASS_MIDI_StreamGetMarks(int handle, int track, int type, BASS_MIDI_MARKB[] marks);
|
||||
public static native boolean BASS_MIDI_StreamSetFonts(int handle, BASS_MIDI_FONT[] fonts, int count);
|
||||
public static native boolean BASS_MIDI_StreamSetFonts(int handle, BASS_MIDI_FONTEX[] fonts, int count);
|
||||
public static native int BASS_MIDI_StreamGetFonts(int handle, BASS_MIDI_FONT[] fonts, int count);
|
||||
public static native int BASS_MIDI_StreamGetFonts(int handle, BASS_MIDI_FONTEX[] fonts, int count);
|
||||
public static native boolean BASS_MIDI_StreamLoadSamples(int handle);
|
||||
public static native boolean BASS_MIDI_StreamEvent(int handle, int chan, int event, int param);
|
||||
public static native int BASS_MIDI_StreamEvents(int handle, int mode, BASS_MIDI_EVENT[] events, int length);
|
||||
public static native int BASS_MIDI_StreamEvents(int handle, int mode, Pointer events, int length);
|
||||
public static native int BASS_MIDI_StreamGetEvent(int handle, int chan, int event);
|
||||
public static native int BASS_MIDI_StreamGetEvents(int handle, int track, int filter, BASS_MIDI_EVENT[] events);
|
||||
public static native int BASS_MIDI_StreamGetEventsEx(int handle, int track, int filter, BASS_MIDI_EVENT[] events, int start, int count);
|
||||
public static native boolean BASS_MIDI_StreamGetPreset(int handle, int chan, BASS_MIDI_FONT font);
|
||||
public static native int BASS_MIDI_StreamGetChannel(int handle, int chan);
|
||||
public static native boolean BASS_MIDI_StreamSetFilter(int handle, boolean seeking, MIDIFILTERPROC proc, Pointer user);
|
||||
|
||||
public static native int BASS_MIDI_FontInit(String file, int flags);
|
||||
public static native int BASS_MIDI_FontInitUser(BASS.BASS_FILEPROCS procs, Pointer user, int flags);
|
||||
public static native boolean BASS_MIDI_FontFree(int handle);
|
||||
public static native boolean BASS_MIDI_FontGetInfo(int handle, BASS_MIDI_FONTINFO info);
|
||||
public static native boolean BASS_MIDI_FontGetPresets(int handle, int[] presets);
|
||||
public static native String BASS_MIDI_FontGetPreset(int handle, int preset, int bank);
|
||||
public static native boolean BASS_MIDI_FontLoad(int handle, int preset, int bank);
|
||||
public static native boolean BASS_MIDI_FontUnload(int handle, int preset, int bank);
|
||||
public static native boolean BASS_MIDI_FontCompact(int handle);
|
||||
public static native boolean BASS_MIDI_FontUnpack(int handle, String outfile, int flags);
|
||||
public static native boolean BASS_MIDI_FontSetVolume(int handle, float volume);
|
||||
public static native float BASS_MIDI_FontGetVolume(int handle);
|
||||
|
||||
public static native int BASS_MIDI_ConvertEvents(Pointer data, int length, BASS_MIDI_EVENT[] events, int count, int flags);
|
||||
|
||||
static {
|
||||
String path = BassLibrary.ExtractLibraryFromJar(BassLibrary.lib_bassmidi);
|
||||
if (path!=null && path.length()>0) Native.register(BASSMIDI.class, path); }
|
||||
|
||||
}
|
||||
29
src/com/un4seen/bass/BASSOPUS.java
Normal file
29
src/com/un4seen/bass/BASSOPUS.java
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
BASSOPUS 2.4 Java class
|
||||
Copyright (c) 2012 Un4seen Developments Ltd.
|
||||
|
||||
See the BASSOPUS.CHM file for more detailed documentation
|
||||
*/
|
||||
|
||||
package com.un4seen.bass;
|
||||
|
||||
import com.sun.jna.Native;
|
||||
import com.sun.jna.Pointer;
|
||||
|
||||
public class BASSOPUS
|
||||
{
|
||||
// BASS_CHANNELINFO type
|
||||
public static final int BASS_CTYPE_STREAM_OPUS = 0x11200;
|
||||
|
||||
// Additional attributes
|
||||
public static final int BASS_ATTRIB_OPUS_ORIGFREQ = 0x13000;
|
||||
|
||||
public static native int BASS_OPUS_StreamCreateFile(String file, long offset, long length, int flags);
|
||||
public static native int BASS_OPUS_StreamCreateURL(String url, int offset, int flags, BASS.DOWNLOADPROC proc, Pointer user);
|
||||
public static native int BASS_OPUS_StreamCreateFileUser(int system, int flags, BASS.BASS_FILEPROCS procs, Pointer user);
|
||||
|
||||
static {
|
||||
String path = BassLibrary.ExtractLibraryFromJar(BassLibrary.lib_bassopus);
|
||||
if (path!=null && path.length()>0) Native.register(BASSOPUS.class, path); }
|
||||
|
||||
}
|
||||
34
src/com/un4seen/bass/BASSWEBM.java
Normal file
34
src/com/un4seen/bass/BASSWEBM.java
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
BASSWEBM 2.4 Java class
|
||||
Copyright (c) 2018-2019 Un4seen Developments Ltd.
|
||||
|
||||
See the BASSWEBM.CHM file for more detailed documentation
|
||||
*/
|
||||
|
||||
package com.un4seen.bass;
|
||||
|
||||
import com.sun.jna.Native;
|
||||
import com.sun.jna.Pointer;
|
||||
|
||||
public class BASSWEBM
|
||||
{
|
||||
// Additional error codes returned by BASS_ErrorGetCode
|
||||
public static final int BASS_ERROR_WEBM_TRACK = 8000;
|
||||
|
||||
// Additional tag types
|
||||
public static final int BASS_TAG_WEBM = 0x15000; // file tags : String array
|
||||
public static final int BASS_TAG_WEBM_TRACK = 0x15001; // track tags : String array
|
||||
|
||||
// Additional attributes
|
||||
public static final int BASS_ATTRIB_WEBM_TRACK = 0x16000;
|
||||
public static final int BASS_ATTRIB_WEBM_TRACKS = 0x16001;
|
||||
|
||||
public static native int BASS_WEBM_StreamCreateFile(String file, long offset, long length, int flags, int track);
|
||||
public static native int BASS_WEBM_StreamCreateURL(String url, int offset, int flags, BASS.DOWNLOADPROC proc, Pointer user, int track);
|
||||
public static native int BASS_WEBM_StreamCreateFileUser(int system, int flags, BASS.BASS_FILEPROCS procs, Pointer user, int track);
|
||||
|
||||
static {
|
||||
String path = BassLibrary.ExtractLibraryFromJar(BassLibrary.lib_basswebm);
|
||||
if (path!=null && path.length()>0) Native.register(BASSWEBM.class, path); }
|
||||
|
||||
}
|
||||
26
src/com/un4seen/bass/BASSWV.java
Normal file
26
src/com/un4seen/bass/BASSWV.java
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
BASSWV 2.4 Java class
|
||||
Copyright (c) 2007-2012 Un4seen Developments Ltd.
|
||||
|
||||
See the BASSWV.CHM file for more detailed documentation
|
||||
*/
|
||||
|
||||
package com.un4seen.bass;
|
||||
|
||||
import com.sun.jna.Native;
|
||||
import com.sun.jna.Pointer;
|
||||
|
||||
public class BASSWV
|
||||
{
|
||||
// BASS_CHANNELINFO type
|
||||
public static final int BASS_CTYPE_STREAM_WV = 0x10500;
|
||||
|
||||
public static native int BASS_WV_StreamCreateFile(String file, long offset, long length, int flags);
|
||||
public static native int BASS_WV_StreamCreateURL(String url, int offset, int flags, BASS.DOWNLOADPROC proc, Pointer user);
|
||||
public static native int BASS_WV_StreamCreateFileUser(int system, int flags, BASS.BASS_FILEPROCS procs, Pointer user);
|
||||
|
||||
static {
|
||||
String path = BassLibrary.ExtractLibraryFromJar(BassLibrary.lib_basswv);
|
||||
if (path!=null && path.length()>0) Native.register(BASSWV.class, path); }
|
||||
|
||||
}
|
||||
37
src/com/un4seen/bass/BASS_AAC.java
Normal file
37
src/com/un4seen/bass/BASS_AAC.java
Normal file
@@ -0,0 +1,37 @@
|
||||
package com.un4seen.bass;
|
||||
|
||||
import com.sun.jna.Native;
|
||||
import com.sun.jna.Pointer;
|
||||
|
||||
|
||||
public class BASS_AAC
|
||||
{
|
||||
// additional error codes returned by BASS_ErrorGetCode
|
||||
public static final int BASS_ERROR_MP4_NOSTREAM = 6000; // non-streamable due to MP4 atom order ("mdat" before "moov")
|
||||
|
||||
// Additional BASS_SetConfig options
|
||||
public static final int BASS_CONFIG_MP4_VIDEO = 0x10700; // play the audio from MP4 videos
|
||||
public static final int BASS_CONFIG_AAC_MP4 = 0x10701; // support MP4 in BASS_AAC_StreamCreateXXX functions (no need for BASS_MP4_StreamCreateXXX)
|
||||
public static final int BASS_CONFIG_AAC_PRESCAN = 0x10702; // pre-scan ADTS AAC files for seek points and accurate length
|
||||
|
||||
// Additional BASS_AAC_StreamCreateFile/etc flags
|
||||
public static final int BASS_AAC_FRAME960 = 0x1000; // 960 samples per frame
|
||||
public static final int BASS_AAC_STEREO = 0x400000; // downmatrix to stereo
|
||||
|
||||
// BASS_CHANNELINFO type
|
||||
public static final int BASS_CTYPE_STREAM_AAC = 0x10b00; // AAC
|
||||
public static final int BASS_CTYPE_STREAM_MP4 = 0x10b01; // AAC in MP4
|
||||
|
||||
public static native int BASS_AAC_StreamCreateFile(String file, long offset, long length, int flags);
|
||||
public static native int BASS_AAC_StreamCreateURL(String url, int offset, int flags, BASS.DOWNLOADPROC proc, Pointer user);
|
||||
public static native int BASS_AAC_StreamCreateFileUser(int system, int flags, BASS.BASS_FILEPROCS procs, Pointer user);
|
||||
public static native int BASS_MP4_StreamCreateFile(String file, long offset, long length, int flags);
|
||||
public static native int BASS_MP4_StreamCreateFileUser(int system, int flags, BASS.BASS_FILEPROCS procs, Pointer user);
|
||||
|
||||
public static boolean lib_is_loaded = false;
|
||||
static {
|
||||
|
||||
String path = BassLibrary.ExtractLibraryFromJar(BassLibrary.lib_bass_aac);
|
||||
if (path!=null && path.length()>0) Native.register(BASS_AAC.class, path);
|
||||
}
|
||||
}
|
||||
21
src/com/un4seen/bass/BASS_APE.java
Normal file
21
src/com/un4seen/bass/BASS_APE.java
Normal file
@@ -0,0 +1,21 @@
|
||||
package com.un4seen.bass;
|
||||
|
||||
import com.sun.jna.Native;
|
||||
import com.sun.jna.Pointer;
|
||||
|
||||
|
||||
|
||||
public class BASS_APE
|
||||
{
|
||||
// BASS_CHANNELINFO type
|
||||
public static final int BASS_CTYPE_STREAM_APE = 0x10700;
|
||||
|
||||
public static native int BASS_APE_StreamCreateFile(String file, long offset, long length, int flags);
|
||||
public static native int BASS_APE_StreamCreateFileUser(int system, int flags, BASS.BASS_FILEPROCS procs, Pointer user);
|
||||
|
||||
static {
|
||||
|
||||
String path = BassLibrary.ExtractLibraryFromJar(BassLibrary.lib_bass_ape);
|
||||
if (path!=null && path.length()>0) Native.register(BASS_APE.class, path);
|
||||
}
|
||||
}
|
||||
713
src/com/un4seen/bass/BASS_FX.java
Normal file
713
src/com/un4seen/bass/BASS_FX.java
Normal file
@@ -0,0 +1,713 @@
|
||||
/*===========================================================================
|
||||
BASS_FX 2.4 - Copyright (c) 2002-2018 (: JOBnik! :) [Arthur Aminov, ISRAEL]
|
||||
[http://www.jobnik.org]
|
||||
|
||||
bugs/suggestions/questions:
|
||||
forum : http://www.un4seen.com/forum/?board=1
|
||||
http://www.jobnik.org/forums
|
||||
e-mail : bass_fx@jobnik.org
|
||||
--------------------------------------------------
|
||||
|
||||
NOTE: This header will work only with BASS_FX version 2.4.12
|
||||
Check www.un4seen.com or www.jobnik.org for any later versions.
|
||||
|
||||
* Requires BASS 2.4 (available at http://www.un4seen.com)
|
||||
===========================================================================*/
|
||||
|
||||
package com.un4seen.bass;
|
||||
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import com.sun.jna.Callback;
|
||||
import com.sun.jna.Native;
|
||||
import com.sun.jna.Pointer;
|
||||
import com.sun.jna.Structure;
|
||||
import com.sun.jna.ptr.FloatByReference;
|
||||
|
||||
import anywheresoftware.b4a.BA;
|
||||
|
||||
public class BASS_FX
|
||||
{
|
||||
// BASS_CHANNELINFO types
|
||||
public static final int BASS_CTYPE_STREAM_TEMPO = 0x1f200;
|
||||
public static final int BASS_CTYPE_STREAM_REVERSE = 0x1f201;
|
||||
|
||||
// Tempo / Reverse / BPM / Beat flag
|
||||
public static final int BASS_FX_FREESOURCE = 0x10000; // Free the source handle as well?
|
||||
|
||||
// BASS_FX Version
|
||||
public static native int BASS_FX_GetVersion();
|
||||
|
||||
/*===========================================================================
|
||||
DSP (Digital Signal Processing)
|
||||
===========================================================================*/
|
||||
|
||||
/*
|
||||
Multi-channel order of each channel is as follows:
|
||||
3 channels left-front, right-front, center.
|
||||
4 channels left-front, right-front, left-rear/side, right-rear/side.
|
||||
5 channels left-front, right-front, center, left-rear/side, right-rear/side.
|
||||
6 channels (5.1) left-front, right-front, center, LFE, left-rear/side, right-rear/side.
|
||||
8 channels (7.1) left-front, right-front, center, LFE, left-rear/side, right-rear/side, left-rear center, right-rear center.
|
||||
*/
|
||||
|
||||
// DSP channels flags
|
||||
public static final int BASS_BFX_CHANALL = -1; // all channels at once (as by default)
|
||||
public static final int BASS_BFX_CHANNONE = 0; // disable an effect for all channels
|
||||
public static final int BASS_BFX_CHAN1 = 1; // left-front channel
|
||||
public static final int BASS_BFX_CHAN2 = 2; // right-front channel
|
||||
public static final int BASS_BFX_CHAN3 = 4; // see above info
|
||||
public static final int BASS_BFX_CHAN4 = 8; // see above info
|
||||
public static final int BASS_BFX_CHAN5 = 16; // see above info
|
||||
public static final int BASS_BFX_CHAN6 = 32; // see above info
|
||||
public static final int BASS_BFX_CHAN7 = 64; // see above info
|
||||
public static final int BASS_BFX_CHAN8 = 128; // see above info
|
||||
|
||||
// if you have more than 8 channels (7.1), use this function
|
||||
public static int BASS_BFX_CHANNEL_N(int n) { return (1<<((n)-1)); }
|
||||
|
||||
// DSP effects
|
||||
public static final int BASS_FX_BFX_ROTATE = 0x10000; // A channels volume ping-pong / multi channel
|
||||
public static final int BASS_FX_BFX_ECHO = 0x10001; // Echo / 2 channels max (deprecated)
|
||||
public static final int BASS_FX_BFX_FLANGER = 0x10002; // Flanger / multi channel (deprecated)
|
||||
public static final int BASS_FX_BFX_VOLUME = 0x10003; // Volume / multi channel
|
||||
public static final int BASS_FX_BFX_PEAKEQ = 0x10004; // Peaking Equalizer / multi channel
|
||||
public static final int BASS_FX_BFX_REVERB = 0x10005; // Reverb / 2 channels max (deprecated)
|
||||
public static final int BASS_FX_BFX_LPF = 0x10006; // Low Pass Filter 24dB / multi channel (deprecated)
|
||||
public static final int BASS_FX_BFX_MIX = 0x10007; // Swap, remap and mix channels / multi channel
|
||||
public static final int BASS_FX_BFX_DAMP = 0x10008; // Dynamic Amplification / multi channel
|
||||
public static final int BASS_FX_BFX_AUTOWAH = 0x10009; // Auto Wah / multi channel
|
||||
public static final int BASS_FX_BFX_ECHO2 = 0x1000a; // Echo 2 / multi channel (deprecated)
|
||||
public static final int BASS_FX_BFX_PHASER = 0x1000b; // Phaser / multi channel
|
||||
public static final int BASS_FX_BFX_ECHO3 = 0x1000c; // Echo 3 / multi channel (deprecated)
|
||||
public static final int BASS_FX_BFX_CHORUS = 0x1000d; // Chorus/Flanger / multi channel
|
||||
public static final int BASS_FX_BFX_APF = 0x1000e; // All Pass Filter / multi channel (deprecated)
|
||||
public static final int BASS_FX_BFX_COMPRESSOR = 0x1000f; // Compressor / multi channel (deprecated)
|
||||
public static final int BASS_FX_BFX_DISTORTION = 0x10010; // Distortion / multi channel
|
||||
public static final int BASS_FX_BFX_COMPRESSOR2 = 0x10011; // Compressor 2 / multi channel
|
||||
public static final int BASS_FX_BFX_VOLUME_ENV = 0x10012; // Volume envelope / multi channel
|
||||
public static final int BASS_FX_BFX_BQF = 0x10013; // BiQuad filters / multi channel
|
||||
public static final int BASS_FX_BFX_ECHO4 = 0x10014; // Echo 4 / multi channel
|
||||
public static final int BASS_FX_BFX_PITCHSHIFT = 0x10015; // Pitch shift using FFT / multi channel (not available on mobile)
|
||||
public static final int BASS_FX_BFX_FREEVERB = 0x10016; // Reverb using "Freeverb" algo / multi channel
|
||||
|
||||
/*
|
||||
Deprecated effects in 2.4.10 version:
|
||||
------------------------------------
|
||||
BASS_FX_BFX_ECHO -> use BASS_FX_BFX_ECHO4
|
||||
BASS_FX_BFX_ECHO2 -> use BASS_FX_BFX_ECHO4
|
||||
BASS_FX_BFX_ECHO3 -> use BASS_FX_BFX_ECHO4
|
||||
BASS_FX_BFX_REVERB -> use BASS_FX_BFX_FREEVERB
|
||||
BASS_FX_BFX_FLANGER -> use BASS_FX_BFX_CHORUS
|
||||
BASS_FX_BFX_COMPRESSOR -> use BASS_FX_BFX_COMPRESSOR2
|
||||
BASS_FX_BFX_APF -> use BASS_FX_BFX_BQF with BASS_BFX_BQF_ALLPASS filter
|
||||
BASS_FX_BFX_LPF -> use 2x BASS_FX_BFX_BQF with BASS_BFX_BQF_LOWPASS filter and appropriate fQ values
|
||||
*/
|
||||
|
||||
// Rotate
|
||||
@BA.ShortName("BASS_BFX_ROTATE")
|
||||
// @Structure.FieldOrder({"fRate","lChannel"})
|
||||
public static class BASS_BFX_ROTATE extends Structure{
|
||||
public float fRate; // rotation rate/speed in Hz (A negative rate can be used for reverse direction)
|
||||
public int lChannel; // BASS_BFX_CHANxxx flag/s (supported only even number of channels)
|
||||
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return Arrays.asList(
|
||||
"fRate","lChannel"
|
||||
);
|
||||
}
|
||||
@BA.Hide public void autoWrite(){}
|
||||
@BA.Hide public void autoRead(){}
|
||||
}
|
||||
|
||||
// Echo (deprecated)
|
||||
@BA.ShortName("BASS_BFX_ECHO")
|
||||
// @Structure.FieldOrder({"fLevel","lDelay"})
|
||||
public static class BASS_BFX_ECHO extends Structure{
|
||||
public float fLevel; // [0....1....n] linear
|
||||
public int lDelay; // [1200..30000]
|
||||
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return Arrays.asList(
|
||||
"fLevel","lDelay"
|
||||
);
|
||||
}
|
||||
@BA.Hide public void autoWrite(){}
|
||||
@BA.Hide public void autoRead(){}
|
||||
}
|
||||
|
||||
// Flanger (deprecated)
|
||||
@BA.ShortName("BASS_BFX_FLANGER")
|
||||
// @Structure.FieldOrder({"fWetDry","fSpeed","lChannel"})
|
||||
public static class BASS_BFX_FLANGER extends Structure{
|
||||
public float fWetDry; // [0....1....n] linear
|
||||
public float fSpeed; // [0......0.09]
|
||||
public int lChannel; // BASS_BFX_CHANxxx flag/s
|
||||
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return Arrays.asList(
|
||||
"fWetDry","fSpeed","lChannel"
|
||||
);
|
||||
}
|
||||
@BA.Hide public void autoWrite(){}
|
||||
@BA.Hide public void autoRead(){}
|
||||
}
|
||||
|
||||
// Volume
|
||||
@BA.ShortName("BASS_BFX_VOLUME")
|
||||
// @Structure.FieldOrder({"lChannel","fVolume"})
|
||||
public static class BASS_BFX_VOLUME extends Structure{
|
||||
public int lChannel; // BASS_BFX_CHANxxx flag/s or 0 for global volume control
|
||||
public float fVolume; // [0....1....n] linear
|
||||
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return Arrays.asList(
|
||||
"lChannel","fVolume"
|
||||
);
|
||||
}
|
||||
@BA.Hide public void autoWrite(){}
|
||||
@BA.Hide public void autoRead(){}
|
||||
}
|
||||
|
||||
// Peaking Equalizer
|
||||
@BA.ShortName("BASS_BFX_PEAKEQ")
|
||||
// @Structure.FieldOrder({"lBand","fBandwidth","fQ","fCenter","fGain","lChannel"})
|
||||
public static class BASS_BFX_PEAKEQ extends Structure{
|
||||
public int lBand; // [0...............n] more bands means more memory & cpu usage
|
||||
public float fBandwidth; // [0.1...........<10] in octaves - fQ is not in use (Bandwidth has a priority over fQ)
|
||||
public float fQ; // [0...............1] the EE kinda definition (linear) (if Bandwidth is not in use)
|
||||
public float fCenter; // [1Hz..<info.freq/2] in Hz
|
||||
public float fGain; // [-15dB...0...+15dB] in dB (can be above/below these limits)
|
||||
public int lChannel; // BASS_BFX_CHANxxx flag/s
|
||||
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return Arrays.asList(
|
||||
"lBand","fBandwidth","fQ","fCenter","fGain","lChannel"
|
||||
);
|
||||
}
|
||||
@BA.Hide public void autoWrite(){}
|
||||
@BA.Hide public void autoRead(){}
|
||||
}
|
||||
|
||||
// Reverb (deprecated)
|
||||
@BA.ShortName("BASS_BFX_REVERB")
|
||||
// @Structure.FieldOrder({"fLevel","lDelay"})
|
||||
public static class BASS_BFX_REVERB extends Structure{
|
||||
public float fLevel; // [0....1....n] linear
|
||||
public int lDelay; // [1200..10000]
|
||||
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return Arrays.asList(
|
||||
"fLevel","lDelay"
|
||||
);
|
||||
}
|
||||
@BA.Hide public void autoWrite(){}
|
||||
@BA.Hide public void autoRead(){}
|
||||
}
|
||||
|
||||
// Low Pass Filter (deprecated)
|
||||
@BA.ShortName("BASS_BFX_LPF")
|
||||
// @Structure.FieldOrder({"fResonance","fCutOffFreq","lChannel"})
|
||||
public static class BASS_BFX_LPF extends Structure{
|
||||
public float fResonance; // [0.01...........10]
|
||||
public float fCutOffFreq; // [1Hz...info.freq/2] cutoff frequency
|
||||
public int lChannel; // BASS_BFX_CHANxxx flag/s
|
||||
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return Arrays.asList(
|
||||
"fResonance","fCutOffFreq","lChannel"
|
||||
);
|
||||
}
|
||||
@BA.Hide public void autoWrite(){}
|
||||
@BA.Hide public void autoRead(){}
|
||||
}
|
||||
|
||||
// Swap, remap and mix
|
||||
@BA.ShortName("BASS_BFX_MIX")
|
||||
// @Structure.FieldOrder({"lChannel"})
|
||||
public static class BASS_BFX_MIX extends Structure{
|
||||
public int[] lChannel; // an array of channels to mix using BASS_BFX_CHANxxx flag/s (lChannel[0] is left channel...)
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return Arrays.asList(
|
||||
"lChannel"
|
||||
);
|
||||
}
|
||||
@BA.Hide public void autoWrite(){}
|
||||
@BA.Hide public void autoRead(){}
|
||||
}
|
||||
|
||||
// Dynamic Amplification
|
||||
@BA.ShortName("BASS_BFX_DAMP")
|
||||
// @Structure.FieldOrder({"fTarget","fQuiet","fRate","fGain","fDelay","lChannel"})
|
||||
public static class BASS_BFX_DAMP extends Structure{
|
||||
public float fTarget; // target volume level [0<......1] linear
|
||||
public float fQuiet; // quiet volume level [0.......1] linear
|
||||
public float fRate; // amp adjustment rate [0.......1] linear
|
||||
public float fGain; // amplification level [0...1...n] linear
|
||||
public float fDelay; // delay in seconds before increasing level [0.......n] linear
|
||||
public int lChannel; // BASS_BFX_CHANxxx flag/s
|
||||
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return Arrays.asList(
|
||||
"fTarget","fQuiet","fRate","fGain","fDelay","lChannel"
|
||||
);
|
||||
}
|
||||
@BA.Hide public void autoWrite(){}
|
||||
@BA.Hide public void autoRead(){}
|
||||
}
|
||||
|
||||
// Auto Wah
|
||||
@BA.ShortName("BASS_BFX_AUTOWAH")
|
||||
// @Structure.FieldOrder({"fDryMix","fWetMix","fFeedback","fRate","fRange","fFreq","lChannel"})
|
||||
public static class BASS_BFX_AUTOWAH extends Structure{
|
||||
public float fDryMix; // dry (unaffected) signal mix [-2......2]
|
||||
public float fWetMix; // wet (affected) signal mix [-2......2]
|
||||
public float fFeedback; // output signal to feed back into input [-1......1]
|
||||
public float fRate; // rate of sweep in cycles per second [0<....<10]
|
||||
public float fRange; // sweep range in octaves [0<....<10]
|
||||
public float fFreq; // base frequency of sweep Hz [0<...1000]
|
||||
public int lChannel; // BASS_BFX_CHANxxx flag/s
|
||||
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return Arrays.asList(
|
||||
"fDryMix","fWetMix","fFeedback","fRate","fRange","fFreq","lChannel"
|
||||
);
|
||||
}
|
||||
@BA.Hide public void autoWrite(){}
|
||||
@BA.Hide public void autoRead(){}
|
||||
}
|
||||
|
||||
// Echo 2 (deprecated)
|
||||
@BA.ShortName("BASS_BFX_ECHO2")
|
||||
// @Structure.FieldOrder({"fDryMix","fWetMix","fFeedback","fDelay","lChannel"})
|
||||
public static class BASS_BFX_ECHO2 extends Structure{
|
||||
public float fDryMix; // dry (unaffected) signal mix [-2......2]
|
||||
public float fWetMix; // wet (affected) signal mix [-2......2]
|
||||
public float fFeedback; // output signal to feed back into input [-1......1]
|
||||
public float fDelay; // delay sec [0<......n]
|
||||
public int lChannel; // BASS_BFX_CHANxxx flag/s
|
||||
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return Arrays.asList(
|
||||
"fDryMix","fWetMix","fFeedback","fDelay","lChannel"
|
||||
);
|
||||
}
|
||||
@BA.Hide public void autoWrite(){}
|
||||
@BA.Hide public void autoRead(){}
|
||||
}
|
||||
|
||||
// Phaser
|
||||
@BA.ShortName("BASS_BFX_PHASER")
|
||||
// @Structure.FieldOrder({"fDryMix","fWetMix","fFeedback","fRate","fRange","fFreq","lChannel"})
|
||||
public static class BASS_BFX_PHASER extends Structure{
|
||||
public float fDryMix; // dry (unaffected) signal mix [-2......2]
|
||||
public float fWetMix; // wet (affected) signal mix [-2......2]
|
||||
public float fFeedback; // output signal to feed back into input [-1......1]
|
||||
public float fRate; // rate of sweep in cycles per second [0<....<10]
|
||||
public float fRange; // sweep range in octaves [0<....<10]
|
||||
public float fFreq; // base frequency of sweep [0<...1000]
|
||||
public int lChannel; // BASS_BFX_CHANxxx flag/s
|
||||
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return Arrays.asList(
|
||||
"fDryMix","fWetMix","fFeedback","fRate","fRange","fFreq","lChannel"
|
||||
);
|
||||
}
|
||||
@BA.Hide public void autoWrite(){}
|
||||
@BA.Hide public void autoRead(){}
|
||||
}
|
||||
|
||||
// Echo 3 (deprecated)
|
||||
@BA.ShortName("BASS_BFX_ECHO3")
|
||||
// @Structure.FieldOrder({"fDryMix","fWetMix","fDelay","lChannel"})
|
||||
public static class BASS_BFX_ECHO3 extends Structure{
|
||||
public float fDryMix; // dry (unaffected) signal mix [-2......2]
|
||||
public float fWetMix; // wet (affected) signal mix [-2......2]
|
||||
public float fDelay; // delay sec [0<......n]
|
||||
public int lChannel; // BASS_BFX_CHANxxx flag/s
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return Arrays.asList(
|
||||
"fDryMix","fWetMix","fDelay","lChannel"
|
||||
);
|
||||
}
|
||||
@BA.Hide public void autoWrite(){}
|
||||
@BA.Hide public void autoRead(){}
|
||||
}
|
||||
|
||||
// Chorus/Flanger
|
||||
@BA.ShortName("BASS_BFX_CHORUS")
|
||||
// @Structure.FieldOrder({"fDryMix","fWetMix","fFeedback","fMinSweep","fMaxSweep","fRate","lChannel"})
|
||||
public static class BASS_BFX_CHORUS extends Structure{
|
||||
public float fDryMix; // dry (unaffected) signal mix [-2......2]
|
||||
public float fWetMix; // wet (affected) signal mix [-2......2]
|
||||
public float fFeedback; // output signal to feed back into input [-1......1]
|
||||
public float fMinSweep; // minimal delay ms [0<...6000]
|
||||
public float fMaxSweep; // maximum delay ms [0<...6000]
|
||||
public float fRate; // rate ms/s [0<...1000]
|
||||
public int lChannel; // BASS_BFX_CHANxxx flag/s
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return Arrays.asList(
|
||||
"fDryMix","fWetMix","fFeedback","fMinSweep","fMaxSweep","fRate","lChannel"
|
||||
);
|
||||
}
|
||||
@BA.Hide public void autoWrite(){}
|
||||
@BA.Hide public void autoRead(){}
|
||||
}
|
||||
|
||||
// All Pass Filter (deprecated)
|
||||
@BA.ShortName("BASS_BFX_APF")
|
||||
// @Structure.FieldOrder({"fGain","fDelay","lChannel"})
|
||||
public static class BASS_BFX_APF extends Structure{
|
||||
public float fGain; // reverberation time [-1=<..<=1]
|
||||
public float fDelay; // delay sec [0<....<=n]
|
||||
public int lChannel; // BASS_BFX_CHANxxx flag/s
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return Arrays.asList(
|
||||
"fGain","fDelay","lChannel"
|
||||
);
|
||||
}
|
||||
@BA.Hide public void autoWrite(){}
|
||||
@BA.Hide public void autoRead(){}
|
||||
}
|
||||
|
||||
// Compressor (deprecated)
|
||||
@BA.ShortName("BASS_BFX_COMPRESSOR")
|
||||
// @Structure.FieldOrder({"fThreshold","fAttacktime","fReleasetime","lChannel"})
|
||||
public static class BASS_BFX_COMPRESSOR extends Structure{
|
||||
public float fThreshold; // compressor threshold [0<=...<=1]
|
||||
public float fAttacktime; // attack time ms [0<.<=1000]
|
||||
public float fReleasetime; // release time ms [0<.<=5000]
|
||||
public int lChannel; // BASS_BFX_CHANxxx flag/s
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return Arrays.asList(
|
||||
"fThreshold","fAttacktime","fReleasetime","lChannel"
|
||||
);
|
||||
}
|
||||
@BA.Hide public void autoWrite(){}
|
||||
@BA.Hide public void autoRead(){}
|
||||
}
|
||||
|
||||
// Distortion
|
||||
@BA.ShortName("BASS_BFX_DISTORTION")
|
||||
// @Structure.FieldOrder({"fDrive","fDryMix","fWetMix","fFeedback","fVolume","lChannel"})
|
||||
public static class BASS_BFX_DISTORTION extends Structure{
|
||||
public float fDrive; // distortion drive [0<=...<=5]
|
||||
public float fDryMix; // dry (unaffected) signal mix [-5<=..<=5]
|
||||
public float fWetMix; // wet (affected) signal mix [-5<=..<=5]
|
||||
public float fFeedback; // output signal to feed back into input [-1<=..<=1]
|
||||
public float fVolume; // distortion volume [0=<...<=2]
|
||||
public int lChannel; // BASS_BFX_CHANxxx flag/s
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return Arrays.asList(
|
||||
"fDrive","fDryMix","fWetMix","fFeedback","fVolume","lChannel"
|
||||
);
|
||||
}
|
||||
@BA.Hide public void autoWrite(){}
|
||||
@BA.Hide public void autoRead(){}
|
||||
}
|
||||
|
||||
// Compressor 2
|
||||
@BA.ShortName("BASS_BFX_COMPRESSOR2")
|
||||
// @Structure.FieldOrder({"fGain","fThreshold","fRatio","fAttack","fRelease","lChannel"})
|
||||
public static class BASS_BFX_COMPRESSOR2 extends Structure{
|
||||
public float fGain; // output gain of signal after compression [-60....60] in dB
|
||||
public float fThreshold; // point at which compression begins [-60.....0] in dB
|
||||
public float fRatio; // compression ratio [1.......n]
|
||||
public float fAttack; // attack time in ms [0.01.1000]
|
||||
public float fRelease; // release time in ms [0.01.5000]
|
||||
public int lChannel; // BASS_BFX_CHANxxx flag/s
|
||||
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return Arrays.asList(
|
||||
"fGain","fThreshold","fRatio","fAttack","fRelease","lChannel"
|
||||
);
|
||||
}
|
||||
@BA.Hide public void autoWrite(){}
|
||||
@BA.Hide public void autoRead(){}
|
||||
}
|
||||
|
||||
// Volume envelope
|
||||
@BA.ShortName("BASS_BFX_VOLUME_ENV")
|
||||
// @Structure.FieldOrder({"lChannel","lNodeCount","pNodes","bFollow"})
|
||||
public static class BASS_BFX_VOLUME_ENV extends Structure{
|
||||
public int lChannel; // BASS_BFX_CHANxxx flag/s
|
||||
public int lNodeCount; // number of nodes
|
||||
public BASS_BFX_ENV_NODE[] pNodes; // the nodes
|
||||
public boolean bFollow; // follow source position
|
||||
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return Arrays.asList(
|
||||
"lChannel","lNodeCount","pNodes","bFollow"
|
||||
);
|
||||
}
|
||||
@BA.Hide public void autoWrite(){}
|
||||
@BA.Hide public void autoRead(){}
|
||||
}
|
||||
|
||||
@BA.ShortName("BASS_BFX_ENV_NODE")
|
||||
// @Structure.FieldOrder({"pos","val"})
|
||||
public static class BASS_BFX_ENV_NODE extends Structure{
|
||||
public double pos; // node position in seconds (1st envelope node must be at position 0)
|
||||
public float val; // node value
|
||||
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return Arrays.asList(
|
||||
"pos","val"
|
||||
);
|
||||
}
|
||||
@BA.Hide public void autoWrite(){}
|
||||
@BA.Hide public void autoRead(){}
|
||||
}
|
||||
|
||||
// BiQuad Filters
|
||||
public static final int BASS_BFX_BQF_LOWPASS = 0;
|
||||
public static final int BASS_BFX_BQF_HIGHPASS = 1;
|
||||
public static final int BASS_BFX_BQF_BANDPASS = 2; // constant 0 dB peak gain
|
||||
public static final int BASS_BFX_BQF_BANDPASS_Q = 3; // constant skirt gain, peak gain = Q
|
||||
public static final int BASS_BFX_BQF_NOTCH = 4;
|
||||
public static final int BASS_BFX_BQF_ALLPASS = 5;
|
||||
public static final int BASS_BFX_BQF_PEAKINGEQ = 6;
|
||||
public static final int BASS_BFX_BQF_LOWSHELF = 7;
|
||||
public static final int BASS_BFX_BQF_HIGHSHELF = 8;
|
||||
|
||||
@BA.ShortName("BASS_BFX_BQF")
|
||||
// @Structure.FieldOrder({"lFilter","fCenter","fGain","fBandwidth","fQ","fS"})
|
||||
public static class BASS_BFX_BQF extends Structure {
|
||||
public int lFilter; // BASS_BFX_BQF_xxx filter types
|
||||
public float fCenter; // [1Hz..<info.freq/2] Cutoff (central) frequency in Hz
|
||||
public float fGain; // [-15dB...0...+15dB] Used only for PEAKINGEQ and Shelving filters in dB (can be above/below these limits)
|
||||
public float fBandwidth; // [0.1...........<10] Bandwidth in octaves (fQ is not in use (fBandwidth has a priority over fQ))
|
||||
// (between -3 dB frequencies for BANDPASS and NOTCH or between midpoint
|
||||
// (fGgain/2) gain frequencies for PEAKINGEQ)
|
||||
public float fQ; // [0.1.............1] The EE kinda definition (linear) (if fBandwidth is not in use)
|
||||
public float fS; // [0.1.............1] A "shelf slope" parameter (linear) (used only with Shelving filters)
|
||||
// when fS = 1, the shelf slope is as steep as you can get it and remain monotonically
|
||||
// increasing or decreasing gain with frequency.
|
||||
public int lChannel; // BASS_BFX_CHANxxx flag/s
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return Arrays.asList(
|
||||
"lFilter","fCenter","fGain","fBandwidth","fQ","fS"
|
||||
);
|
||||
}
|
||||
@BA.Hide public void autoWrite(){}
|
||||
@BA.Hide public void autoRead(){}
|
||||
}
|
||||
|
||||
// Echo 4
|
||||
@BA.ShortName("BASS_BFX_ECHO4")
|
||||
// @Structure.FieldOrder({"fDryMix","fWetMix","fFeedback","fDelay","bStereo","lChannel"})
|
||||
public static class BASS_BFX_ECHO4 extends Structure{
|
||||
public float fDryMix; // dry (unaffected) signal mix [-2.......2]
|
||||
public float fWetMix; // wet (affected) signal mix [-2.......2]
|
||||
public float fFeedback; // output signal to feed back into input [-1.......1]
|
||||
public float fDelay; // delay sec [0<.......n]
|
||||
public boolean bStereo; // echo adjoining channels to each other [TRUE/FALSE]
|
||||
public int lChannel; // BASS_BFX_CHANxxx flag/s
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return Arrays.asList(
|
||||
"fDryMix","fWetMix","fFeedback","fDelay","bStereo","lChannel"
|
||||
);
|
||||
}
|
||||
@BA.Hide public void autoWrite(){}
|
||||
@BA.Hide public void autoRead(){}
|
||||
}
|
||||
|
||||
// Pitch shift (not available on mobile)
|
||||
@BA.ShortName("BASS_BFX_PITCHSHIFT")
|
||||
// @Structure.FieldOrder({"fPitchShift","fSemitones","lFFTsize","lOsamp","lChannel"})
|
||||
public static class BASS_BFX_PITCHSHIFT extends Structure{
|
||||
public float fPitchShift; // A factor value which is between 0.5 (one octave down) and 2 (one octave up) (1 won't change the pitch) [1 default]
|
||||
// (fSemitones is not in use, fPitchShift has a priority over fSemitones)
|
||||
public float fSemitones; // Semitones (0 won't change the pitch) [0 default]
|
||||
public int lFFTsize; // Defines the FFT frame size used for the processing. Typical values are 1024, 2048 and 4096 [2048 default]
|
||||
// It may be any value <= 8192 but it MUST be a power of 2
|
||||
public int lOsamp; // Is the STFT oversampling factor which also determines the overlap between adjacent STFT frames [8 default]
|
||||
// It should at least be 4 for moderate scaling ratios. A value of 32 is recommended for best quality (better quality = higher CPU usage)
|
||||
public int lChannel; // BASS_BFX_CHANxxx flag/s
|
||||
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return Arrays.asList(
|
||||
"fPitchShift","fSemitones","lFFTsize","lOsamp","lChannel"
|
||||
);
|
||||
}
|
||||
@BA.Hide public void autoWrite(){}
|
||||
@BA.Hide public void autoRead(){}
|
||||
}
|
||||
|
||||
// Freeverb
|
||||
public static final int BASS_BFX_FREEVERB_MODE_FREEZE = 1;
|
||||
|
||||
@BA.ShortName("BASS_BFX_FREEVERB")
|
||||
// @Structure.FieldOrder({"fDryMix","fWetMix","fRoomSize","fDamp","fWidth","lMode","lChannel"})
|
||||
public static class BASS_BFX_FREEVERB extends Structure {
|
||||
public float fDryMix; // dry (unaffected) signal mix [0........1], def. 0
|
||||
public float fWetMix; // wet (affected) signal mix [0........3], def. 1.0f
|
||||
public float fRoomSize; // room size [0........1], def. 0.5f
|
||||
public float fDamp; // damping [0........1], def. 0.5f
|
||||
public float fWidth; // stereo width [0........1], def. 1
|
||||
public int lMode; // 0 or BASS_BFX_FREEVERB_MODE_FREEZE, def. 0 (no freeze)
|
||||
public int lChannel; // BASS_BFX_CHANxxx flag/s
|
||||
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return Arrays.asList(
|
||||
"fDryMix","fWetMix","fRoomSize","fDamp","fWidth","lMode","lChannel"
|
||||
);
|
||||
}
|
||||
@BA.Hide public void autoWrite(){}
|
||||
@BA.Hide public void autoRead(){}
|
||||
}
|
||||
|
||||
/*===========================================================================
|
||||
set dsp fx - BASS_ChannelSetFX
|
||||
remove dsp fx - BASS_ChannelRemoveFX
|
||||
set parameters - BASS_FXSetParameters
|
||||
retrieve parameters - BASS_FXGetParameters
|
||||
reset the state - BASS_FXReset
|
||||
===========================================================================*/
|
||||
|
||||
/*===========================================================================
|
||||
Tempo, Pitch scaling and Sample rate changers
|
||||
===========================================================================*/
|
||||
|
||||
// NOTE: Enable Tempo supported flags in BASS_FX_TempoCreate and the others to source handle.
|
||||
|
||||
// tempo attributes (BASS_ChannelSet/GetAttribute)
|
||||
public static final int BASS_ATTRIB_TEMPO = 0x10000;
|
||||
public static final int BASS_ATTRIB_TEMPO_PITCH = 0x10001;
|
||||
public static final int BASS_ATTRIB_TEMPO_FREQ = 0x10002;
|
||||
|
||||
// tempo attributes options
|
||||
public static final int BASS_ATTRIB_TEMPO_OPTION_USE_AA_FILTER = 0x10010; // TRUE (default) / FALSE (default for multi-channel on mobile devices for lower CPU usage)
|
||||
public static final int BASS_ATTRIB_TEMPO_OPTION_AA_FILTER_LENGTH = 0x10011; // 32 default (8 .. 128 taps)
|
||||
public static final int BASS_ATTRIB_TEMPO_OPTION_USE_QUICKALGO = 0x10012; // TRUE (default on mobile devices for lower CPU usage) / FALSE (default)
|
||||
public static final int BASS_ATTRIB_TEMPO_OPTION_SEQUENCE_MS = 0x10013; // 82 default, 0 = automatic
|
||||
public static final int BASS_ATTRIB_TEMPO_OPTION_SEEKWINDOW_MS = 0x10014; // 28 default, 0 = automatic
|
||||
public static final int BASS_ATTRIB_TEMPO_OPTION_OVERLAP_MS = 0x10015; // 8 default
|
||||
public static final int BASS_ATTRIB_TEMPO_OPTION_PREVENT_CLICK = 0x10016; // TRUE / FALSE (default)
|
||||
// tempo algorithm flags
|
||||
public static final int BASS_FX_TEMPO_ALGO_LINEAR = 0x200;
|
||||
public static final int BASS_FX_TEMPO_ALGO_CUBIC = 0x400; // default
|
||||
public static final int BASS_FX_TEMPO_ALGO_SHANNON = 0x800;
|
||||
|
||||
public static native int BASS_FX_TempoCreate(int chan, int flags);
|
||||
public static native int BASS_FX_TempoGetSource(int chan);
|
||||
public static native float BASS_FX_TempoGetRateRatio(int chan);
|
||||
|
||||
/*===========================================================================
|
||||
Reverse playback
|
||||
===========================================================================*/
|
||||
|
||||
// NOTES: 1. MODs won't load without BASS_MUSIC_PRESCAN flag.
|
||||
// 2. Enable Reverse supported flags in BASS_FX_ReverseCreate and the others to source handle.
|
||||
|
||||
// reverse attribute (BASS_ChannelSet/GetAttribute)
|
||||
public static final int BASS_ATTRIB_REVERSE_DIR = 0x11000;
|
||||
|
||||
// playback directions
|
||||
public static final int BASS_FX_RVS_REVERSE = -1;
|
||||
public static final int BASS_FX_RVS_FORWARD = 1;
|
||||
|
||||
public static native int BASS_FX_ReverseCreate(int chan, float dec_block, int flags);
|
||||
public static native int BASS_FX_ReverseGetSource(int chan);
|
||||
|
||||
/*===========================================================================
|
||||
BPM (Beats Per Minute)
|
||||
===========================================================================*/
|
||||
|
||||
// bpm flags
|
||||
public static final int BASS_FX_BPM_BKGRND = 1; // if in use, then you can do other processing while detection's in progress. Available only in Windows platforms (BPM/Beat)
|
||||
public static final int BASS_FX_BPM_MULT2 = 2; // if in use, then will auto multiply bpm by 2 (if BPM < minBPM*2)
|
||||
|
||||
// translation options (deprecated)
|
||||
public static final int BASS_FX_BPM_TRAN_X2 = 0; // multiply the original BPM value by 2 (may be called only once & will change the original BPM as well!)
|
||||
public static final int BASS_FX_BPM_TRAN_2FREQ = 1; // BPM value to Frequency
|
||||
public static final int BASS_FX_BPM_TRAN_FREQ2 = 2; // Frequency to BPM value
|
||||
public static final int BASS_FX_BPM_TRAN_2PERCENT = 3; // BPM value to Percents
|
||||
public static final int BASS_FX_BPM_TRAN_PERCENT2 = 4; // Percents to BPM value
|
||||
|
||||
public interface BPMPROC extends Callback
|
||||
{
|
||||
void BPMPROC(int chan, float bpm, Pointer user);
|
||||
}
|
||||
|
||||
public interface BPMPROGRESSPROC extends Callback
|
||||
{
|
||||
void BPMPROGRESSPROC(int chan, float percent, Pointer user);
|
||||
}
|
||||
|
||||
// back-compatibility
|
||||
public interface BPMPROCESSPROC extends Callback
|
||||
{
|
||||
void BPMPROCESSPROC(int chan, float percent, Pointer user);
|
||||
}
|
||||
|
||||
public static native float BASS_FX_BPM_DecodeGet(int chan, double startSec, double endSec, int minMaxBPM, int flags, BPMPROGRESSPROC proc, Pointer user);
|
||||
public static native boolean BASS_FX_BPM_CallbackSet(int handle, BPMPROC proc, double period, int minMaxBPM, int flags, Pointer user);
|
||||
public static native boolean BASS_FX_BPM_CallbackReset(int handle);
|
||||
public static native float BASS_FX_BPM_Translate(int handle, float val2tran, int trans); // deprecated
|
||||
public static native boolean BASS_FX_BPM_Free(int handle);
|
||||
|
||||
/*===========================================================================
|
||||
Beat position trigger
|
||||
===========================================================================*/
|
||||
|
||||
public interface BPMBEATPROC extends Callback
|
||||
{
|
||||
void BPMBEATPROC(int chan, double beatpos, Pointer user);
|
||||
}
|
||||
|
||||
public static native boolean BASS_FX_BPM_BeatCallbackSet(int handle, BPMBEATPROC proc, Pointer user);
|
||||
public static native boolean BASS_FX_BPM_BeatCallbackReset(int handle);
|
||||
public static native boolean BASS_FX_BPM_BeatDecodeGet(int chan, double startSec, double endSec, int flags, BPMBEATPROC proc, Pointer user);
|
||||
public static native boolean BASS_FX_BPM_BeatSetParameters(int handle, float bandwidth, float centerfreq, float beat_rtime);
|
||||
public static native boolean BASS_FX_BPM_BeatGetParameters(int handle, FloatByReference bandwidth, FloatByReference centerfreq, FloatByReference beat_rtime);
|
||||
public static native boolean BASS_FX_BPM_BeatFree(int handle);
|
||||
|
||||
/*===========================================================================
|
||||
Macros
|
||||
===========================================================================*/
|
||||
|
||||
// translate linear level to logarithmic dB
|
||||
public static double BASS_BFX_Linear2dB(double level)
|
||||
{
|
||||
return (20*Math.log10(level));
|
||||
}
|
||||
|
||||
// translate logarithmic dB level to linear
|
||||
public static double BASS_BFX_dB2Linear(double dB)
|
||||
{
|
||||
return (Math.pow(10,(dB)/20));
|
||||
}
|
||||
|
||||
static {
|
||||
|
||||
String path = BassLibrary.ExtractLibraryFromJar(BassLibrary.lib_bass_fx);
|
||||
if (path!=null && path.length()>0) Native.register(BASS_FX.class, path);
|
||||
}
|
||||
}
|
||||
21
src/com/un4seen/bass/BASS_MPC.java
Normal file
21
src/com/un4seen/bass/BASS_MPC.java
Normal file
@@ -0,0 +1,21 @@
|
||||
package com.un4seen.bass;
|
||||
|
||||
import com.sun.jna.Native;
|
||||
import com.sun.jna.Pointer;
|
||||
|
||||
public class BASS_MPC
|
||||
{
|
||||
// BASS_CHANNELINFO type
|
||||
public static final int BASS_CTYPE_STREAM_MPC = 0x10a00;
|
||||
|
||||
public static native int BASS_MPC_StreamCreateFile(String file, long offset, long length, int flags);
|
||||
public static native int BASS_MPC_StreamCreateURL(String url, int offset, int flags, BASS.DOWNLOADPROC proc, Pointer user);
|
||||
public static native int BASS_MPC_StreamCreateFileUser(int system, int flags, BASS.BASS_FILEPROCS procs, Pointer user);
|
||||
|
||||
static {
|
||||
|
||||
String path = BassLibrary.ExtractLibraryFromJar(BassLibrary.lib_bass_mpc);
|
||||
if (path!=null && path.length()>0) Native.register(BASS_MPC.class, path);
|
||||
}
|
||||
|
||||
}
|
||||
20
src/com/un4seen/bass/BASS_TTA.java
Normal file
20
src/com/un4seen/bass/BASS_TTA.java
Normal file
@@ -0,0 +1,20 @@
|
||||
package com.un4seen.bass;
|
||||
|
||||
|
||||
import com.sun.jna.Native;
|
||||
import com.sun.jna.Pointer;
|
||||
|
||||
public class BASS_TTA
|
||||
{
|
||||
// BASS_CHANNELINFO type
|
||||
public static final int BASS_CTYPE_STREAM_TTA = 0x10f00;
|
||||
|
||||
public static native int BASS_TTA_StreamCreateFile(String file, long offset, long length, int flags);
|
||||
public static native int BASS_TTA_StreamCreateFileUser(int system, int flags, BASS.BASS_FILEPROCS procs, Pointer user);
|
||||
|
||||
static {
|
||||
String path = BassLibrary.ExtractLibraryFromJar(BassLibrary.lib_bass_tta);
|
||||
if (path!=null && path.length()>0) Native.register(BASS_TTA.class, path);
|
||||
}
|
||||
|
||||
}
|
||||
164
src/com/un4seen/bass/BASSenc.java
Normal file
164
src/com/un4seen/bass/BASSenc.java
Normal file
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
BASSenc 2.4 Java class
|
||||
Copyright (c) 2003-2018 Un4seen Developments Ltd.
|
||||
|
||||
See the BASSENC.CHM file for more detailed documentation
|
||||
*/
|
||||
|
||||
package com.un4seen.bass;
|
||||
|
||||
|
||||
|
||||
import com.sun.jna.Callback;
|
||||
import com.sun.jna.Native;
|
||||
import com.sun.jna.Pointer;
|
||||
|
||||
|
||||
public class BASSenc
|
||||
{
|
||||
// Additional error codes returned by BASS_ErrorGetCode
|
||||
public static final int BASS_ERROR_CAST_DENIED = 2100; // access denied (invalid password)
|
||||
|
||||
// Additional BASS_SetConfig options
|
||||
public static final int BASS_CONFIG_ENCODE_PRIORITY = 0x10300;
|
||||
public static final int BASS_CONFIG_ENCODE_QUEUE = 0x10301;
|
||||
public static final int BASS_CONFIG_ENCODE_CAST_TIMEOUT = 0x10310;
|
||||
|
||||
// Additional BASS_SetConfigPtr options
|
||||
public static final int BASS_CONFIG_ENCODE_CAST_PROXY = 0x10311;
|
||||
|
||||
// BASS_Encode_Start flags
|
||||
public static final int BASS_ENCODE_NOHEAD = 1; // don't send a WAV header to the encoder
|
||||
public static final int BASS_ENCODE_FP_8BIT = 2; // convert floating-point sample data to 8-bit integer
|
||||
public static final int BASS_ENCODE_FP_16BIT = 4; // convert floating-point sample data to 16-bit integer
|
||||
public static final int BASS_ENCODE_FP_24BIT = 6; // convert floating-point sample data to 24-bit integer
|
||||
public static final int BASS_ENCODE_FP_32BIT = 8; // convert floating-point sample data to 32-bit integer
|
||||
public static final int BASS_ENCODE_FP_AUTO = 14; // convert floating-point sample data back to channel's format
|
||||
public static final int BASS_ENCODE_BIGEND = 16; // big-endian sample data
|
||||
public static final int BASS_ENCODE_PAUSE = 32; // start encording paused
|
||||
public static final int BASS_ENCODE_PCM = 64; // write PCM sample data (no encoder)
|
||||
public static final int BASS_ENCODE_RF64 = 128; // send an RF64 header
|
||||
public static final int BASS_ENCODE_QUEUE = 0x200; // queue data to feed encoder asynchronously
|
||||
public static final int BASS_ENCODE_WFEXT = 0x400; // WAVEFORMATEXTENSIBLE "fmt" chunk
|
||||
public static final int BASS_ENCODE_CAST_NOLIMIT = 0x1000; // don't limit casting data rate
|
||||
public static final int BASS_ENCODE_LIMIT = 0x2000; // limit data rate to real-time
|
||||
public static final int BASS_ENCODE_AIFF = 0x4000; // send an AIFF header rather than WAV
|
||||
public static final int BASS_ENCODE_DITHER = 0x8000; // apply dither when converting floating-point sample data to integer
|
||||
public static final int BASS_ENCODE_AUTOFREE = 0x40000; // free the encoder when the channel is freed
|
||||
|
||||
// BASS_Encode_GetCount counts
|
||||
public static final int BASS_ENCODE_COUNT_IN = 0; // sent to encoder
|
||||
public static final int BASS_ENCODE_COUNT_OUT = 1; // received from encoder
|
||||
public static final int BASS_ENCODE_COUNT_CAST = 2; // sent to cast server
|
||||
public static final int BASS_ENCODE_COUNT_QUEUE = 3; // queued
|
||||
public static final int BASS_ENCODE_COUNT_QUEUE_LIMIT = 4; // queue limit
|
||||
public static final int BASS_ENCODE_COUNT_QUEUE_FAIL = 5; // failed to queue
|
||||
|
||||
// BASS_Encode_CastInit content MIME types
|
||||
public static final String BASS_ENCODE_TYPE_MP3 = "audio/mpeg";
|
||||
public static final String BASS_ENCODE_TYPE_OGG = "audio/ogg";
|
||||
public static final String BASS_ENCODE_TYPE_AAC = "audio/aacp";
|
||||
|
||||
// BASS_Encode_CastGetStats types
|
||||
public static final int BASS_ENCODE_STATS_SHOUT = 0; // Shoutcast stats
|
||||
public static final int BASS_ENCODE_STATS_ICE = 1; // Icecast mount-point stats
|
||||
public static final int BASS_ENCODE_STATS_ICESERV = 2; // Icecast server stats
|
||||
|
||||
public interface ENCODEPROC extends Callback
|
||||
{
|
||||
void ENCODEPROC(int handle, int channel, Pointer buffer, int length, Pointer user);
|
||||
/* Encoding callback function.
|
||||
handle : The encoder
|
||||
channel: The channel handle
|
||||
buffer : Buffer containing the encoded data
|
||||
length : Number of bytes
|
||||
user : The 'user' parameter value given when starting the encoder */
|
||||
}
|
||||
|
||||
public interface ENCODEPROCEX extends Callback
|
||||
{
|
||||
void ENCODEPROCEX(int handle, int channel, Pointer buffer, int length, long offset, Pointer user);
|
||||
/* Encoding callback function.
|
||||
handle : The encoder
|
||||
channel: The channel handle
|
||||
buffer : Buffer containing the encoded data
|
||||
length : Number of bytes
|
||||
offset : File offset of the data
|
||||
user : The 'user' parameter value given when starting the encoder */
|
||||
}
|
||||
|
||||
public interface ENCODERPROC extends Callback
|
||||
{
|
||||
int ENCODERPROC(int handle, int channel, Pointer buffer, int length, int maxout, Pointer user);
|
||||
/* Encoder callback function.
|
||||
handle : The encoder
|
||||
channel: The channel handle
|
||||
buffer : Buffer containing the PCM data (input) and receiving the encoded data (output)
|
||||
length : Number of bytes in (-1=closing)
|
||||
maxout : Maximum number of bytes out
|
||||
user : The 'user' parameter value given when calling BASS_Encode_StartUser
|
||||
RETURN : The amount of encoded data (-1=stop) */
|
||||
}
|
||||
|
||||
public interface ENCODECLIENTPROC extends Callback
|
||||
{
|
||||
boolean ENCODECLIENTPROC(int handle, boolean connect, String client, StringBuffer headers, Pointer user);
|
||||
/* Client connection notification callback function.
|
||||
handle : The encoder
|
||||
connect: true/false=client is connecting/disconnecting
|
||||
client : The client's address (xxx.xxx.xxx.xxx:port)
|
||||
headers: Request headers (optionally response headers on return)
|
||||
user : The 'user' parameter value given when calling BASS_Encode_ServerInit
|
||||
RETURN : true/false=accept/reject connection (ignored if connect=false) */
|
||||
}
|
||||
|
||||
public interface ENCODENOTIFYPROC extends Callback
|
||||
{
|
||||
void ENCODENOTIFYPROC(int handle, int status, Pointer user);
|
||||
/* Encoder death notification callback function.
|
||||
handle : The encoder
|
||||
status : Notification (BASS_ENCODE_NOTIFY_xxx)
|
||||
user : The 'user' parameter value given when calling BASS_Encode_SetNotify */
|
||||
}
|
||||
|
||||
// Encoder notifications
|
||||
public static final int BASS_ENCODE_NOTIFY_ENCODER = 1; // encoder died
|
||||
public static final int BASS_ENCODE_NOTIFY_CAST = 2; // cast server connection died
|
||||
public static final int BASS_ENCODE_NOTIFY_CAST_TIMEOUT = 0x10000; // cast timeout
|
||||
public static final int BASS_ENCODE_NOTIFY_QUEUE_FULL = 0x10001; // queue is out of space
|
||||
public static final int BASS_ENCODE_NOTIFY_FREE = 0x10002; // encoder has been freed
|
||||
|
||||
// BASS_Encode_ServerInit flags
|
||||
public static final int BASS_ENCODE_SERVER_NOHTTP = 1; // no HTTP headers
|
||||
public static final int BASS_ENCODE_SERVER_META = 2; // Shoutcast metadata
|
||||
|
||||
public static native int BASS_Encode_GetVersion();
|
||||
|
||||
public static native int BASS_Encode_Start(int handle, String cmdline, int flags, ENCODEPROC proc, Pointer user);
|
||||
public static native int BASS_Encode_StartLimit(int handle, String cmdline, int flags, ENCODEPROC proc, Pointer user, int limit);
|
||||
public static native int BASS_Encode_StartUser(int handle, String file, int flags, ENCODERPROC proc, Pointer user);
|
||||
public static native boolean BASS_Encode_AddChunk(int handle, String id, Pointer buffer, int length);
|
||||
public static native int BASS_Encode_IsActive(int handle);
|
||||
public static native boolean BASS_Encode_Stop(int handle);
|
||||
public static native boolean BASS_Encode_StopEx(int handle, boolean queue);
|
||||
public static native boolean BASS_Encode_SetPaused(int handle, boolean paused);
|
||||
public static native boolean BASS_Encode_Write(int handle, Pointer buffer, int length);
|
||||
public static native boolean BASS_Encode_SetNotify(int handle, ENCODENOTIFYPROC proc, Pointer user);
|
||||
public static native long BASS_Encode_GetCount(int handle, int count);
|
||||
public static native boolean BASS_Encode_SetChannel(int handle, int channel);
|
||||
public static native int BASS_Encode_GetChannel(int handle);
|
||||
//public static native boolean BASS_Encode_UserOutput(int handle, long offset, Pointer buffer, int length);
|
||||
|
||||
public static native boolean BASS_Encode_CastInit(int handle, String server, String pass, String content, String name, String url, String genre, String desc, String headers, int bitrate, boolean pub);
|
||||
public static native boolean BASS_Encode_CastSetTitle(int handle, String title, String url);
|
||||
public static native boolean BASS_Encode_CastSendMeta(int handle, int type, Pointer data, int length);
|
||||
public static native String BASS_Encode_CastGetStats(int handle, int type, String pass);
|
||||
|
||||
public static native int BASS_Encode_ServerInit(int handle, String port, int buffer, int burst, int flags, ENCODECLIENTPROC proc, Pointer user);
|
||||
public static native boolean BASS_Encode_ServerKick(int handle, String client);
|
||||
|
||||
static {
|
||||
String path = BassLibrary.ExtractLibraryFromJar(BassLibrary.lib_bassenc);
|
||||
Native.register(BASSenc.class, path);
|
||||
}
|
||||
}
|
||||
26
src/com/un4seen/bass/BASSenc_FLAC.java
Normal file
26
src/com/un4seen/bass/BASSenc_FLAC.java
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
BASSenc_FLAC 2.4 Java class
|
||||
Copyright (c) 2017-2018 Un4seen Developments Ltd.
|
||||
|
||||
See the BASSENC_FLAC.CHM file for more detailed documentation
|
||||
*/
|
||||
|
||||
package com.un4seen.bass;
|
||||
|
||||
import com.sun.jna.Native;
|
||||
import com.sun.jna.Pointer;
|
||||
|
||||
|
||||
|
||||
public class BASSenc_FLAC
|
||||
{
|
||||
public static native int BASS_Encode_FLAC_GetVersion();
|
||||
|
||||
public static native int BASS_Encode_FLAC_Start(int handle, String options, int flags, BASSenc.ENCODEPROCEX proc, Pointer user);
|
||||
public static native int BASS_Encode_FLAC_StartFile(int handle, String options, int flags, String filename);
|
||||
|
||||
static {
|
||||
String path = BassLibrary.ExtractLibraryFromJar(BassLibrary.lib_bassenc_flac);
|
||||
if (path!=null && path.length()>0) Native.register(BASSenc_FLAC.class, path); }
|
||||
|
||||
}
|
||||
29
src/com/un4seen/bass/BASSenc_MP3.java
Normal file
29
src/com/un4seen/bass/BASSenc_MP3.java
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
BASSenc_MP3 2.4 Java class
|
||||
Copyright (c) 2018 Un4seen Developments Ltd.
|
||||
|
||||
See the BASSENC_MP3.CHM file for more detailed documentation
|
||||
*/
|
||||
|
||||
package com.un4seen.bass;
|
||||
|
||||
|
||||
import com.sun.jna.Native;
|
||||
import com.sun.jna.Pointer;
|
||||
|
||||
|
||||
|
||||
public class BASSenc_MP3
|
||||
{
|
||||
static {
|
||||
String path = BassLibrary.ExtractLibraryFromJar(BassLibrary.lib_bassenc_mp3);
|
||||
Native.register(BASSenc_MP3.class, path);
|
||||
}
|
||||
|
||||
|
||||
public static native int BASS_Encode_MP3_GetVersion();
|
||||
|
||||
public static native int BASS_Encode_MP3_Start(int handle, String options, int flags, BASSenc.ENCODEPROCEX proc, Pointer user);
|
||||
public static native int BASS_Encode_MP3_StartFile(int handle, String options, int flags, String filename);
|
||||
|
||||
}
|
||||
28
src/com/un4seen/bass/BASSenc_OGG.java
Normal file
28
src/com/un4seen/bass/BASSenc_OGG.java
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
BASSenc_OGG 2.4 Java class
|
||||
Copyright (c) 2016 Un4seen Developments Ltd.
|
||||
|
||||
See the BASSENC_OGG.CHM file for more detailed documentation
|
||||
*/
|
||||
|
||||
package com.un4seen.bass;
|
||||
|
||||
import com.sun.jna.Native;
|
||||
import com.sun.jna.Pointer;
|
||||
|
||||
|
||||
|
||||
public class BASSenc_OGG
|
||||
{
|
||||
static {
|
||||
String path = BassLibrary.ExtractLibraryFromJar(BassLibrary.lib_bassenc_ogg);
|
||||
if (path!=null && path.length()>0) Native.register(BASSenc_OGG.class, path);
|
||||
}
|
||||
|
||||
public native static int BASS_Encode_OGG_GetVersion();
|
||||
|
||||
public static native int BASS_Encode_OGG_Start(int handle, String options, int flags, BASSenc.ENCODEPROC proc, Pointer user);
|
||||
public static native int BASS_Encode_OGG_StartFile(int handle, String options, int flags, String filename);
|
||||
|
||||
|
||||
}
|
||||
26
src/com/un4seen/bass/BASSenc_OPUS.java
Normal file
26
src/com/un4seen/bass/BASSenc_OPUS.java
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
BASSenc_OPUS 2.4 Java class
|
||||
Copyright (c) 2016 Un4seen Developments Ltd.
|
||||
|
||||
See the BASSENC_OPUS.CHM file for more detailed documentation
|
||||
*/
|
||||
|
||||
package com.un4seen.bass;
|
||||
|
||||
import com.sun.jna.Native;
|
||||
import com.sun.jna.Pointer;
|
||||
|
||||
|
||||
|
||||
public class BASSenc_OPUS
|
||||
{
|
||||
public static native int BASS_Encode_OPUS_GetVersion();
|
||||
|
||||
public static native int BASS_Encode_OPUS_Start(int handle, String options, int flags, BASSenc.ENCODEPROC proc, Pointer user);
|
||||
public static native int BASS_Encode_OPUS_StartFile(int handle, String options, int flags, String filename);
|
||||
|
||||
static {
|
||||
String path = BassLibrary.ExtractLibraryFromJar(BassLibrary.lib_bassenc_opus);
|
||||
if (path!=null && path.length()>0) Native.register(BASSenc_OPUS.class, path); }
|
||||
|
||||
}
|
||||
152
src/com/un4seen/bass/BASSmix.java
Normal file
152
src/com/un4seen/bass/BASSmix.java
Normal file
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
BASSmix 2.4 Java class
|
||||
Copyright (c) 2005-2017 Un4seen Developments Ltd.
|
||||
|
||||
See the BASSMIX.CHM file for more detailed documentation
|
||||
*/
|
||||
|
||||
package com.un4seen.bass;
|
||||
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import com.sun.jna.Native;
|
||||
import com.sun.jna.Pointer;
|
||||
import com.sun.jna.Structure;
|
||||
import com.sun.jna.ptr.FloatByReference;
|
||||
|
||||
import anywheresoftware.b4a.BA;
|
||||
|
||||
public class BASSmix
|
||||
{
|
||||
static {
|
||||
String path = BassLibrary.ExtractLibraryFromJar(BassLibrary.lib_bassmix);
|
||||
Native.register(BASSmix.class, path);
|
||||
}
|
||||
|
||||
// additional BASS_SetConfig option
|
||||
public static final int BASS_CONFIG_MIXER_BUFFER = 0x10601;
|
||||
public static final int BASS_CONFIG_MIXER_POSEX = 0x10602;
|
||||
public static final int BASS_CONFIG_SPLIT_BUFFER = 0x10610;
|
||||
|
||||
// BASS_Mixer_StreamCreate flags
|
||||
public static final int BASS_MIXER_RESUME = 0x1000; // resume stalled immediately upon new/unpaused source
|
||||
public static final int BASS_MIXER_POSEX = 0x2000; // enable BASS_Mixer_ChannelGetPositionEx support
|
||||
public static final int BASS_MIXER_NOSPEAKER = 0x4000; // ignore speaker arrangement
|
||||
public static final int BASS_MIXER_QUEUE = 0x8000; // queue sources
|
||||
public static final int BASS_MIXER_END = 0x10000; // end the stream when there are no sources
|
||||
public static final int BASS_MIXER_NONSTOP = 0x20000; // don't stall when there are no sources
|
||||
|
||||
// BASS_Mixer_StreamAddChannel/Ex flags
|
||||
public static final int BASS_MIXER_CHAN_ABSOLUTE = 0x1000; // start is an absolute position
|
||||
public static final int BASS_MIXER_CHAN_BUFFER = 0x2000; // buffer data for BASS_Mixer_ChannelGetData/Level
|
||||
public static final int BASS_MIXER_CHAN_LIMIT = 0x4000; // limit mixer processing to the amount available from this source
|
||||
public static final int BASS_MIXER_CHAN_MATRIX = 0x10000; // matrix mixing
|
||||
public static final int BASS_MIXER_CHAN_PAUSE = 0x20000; // don't process the source
|
||||
public static final int BASS_MIXER_CHAN_DOWNMIX = 0x400000; // downmix to stereo/mono
|
||||
public static final int BASS_MIXER_CHAN_NORAMPIN = 0x800000; // don't ramp-in the start
|
||||
public static final int BASS_MIXER_BUFFER = BASS_MIXER_CHAN_BUFFER;
|
||||
public static final int BASS_MIXER_LIMIT = BASS_MIXER_CHAN_LIMIT;
|
||||
public static final int BASS_MIXER_MATRIX = BASS_MIXER_CHAN_MATRIX;
|
||||
public static final int BASS_MIXER_PAUSE = BASS_MIXER_CHAN_PAUSE;
|
||||
public static final int BASS_MIXER_DOWNMIX = BASS_MIXER_CHAN_DOWNMIX;
|
||||
public static final int BASS_MIXER_NORAMPIN = BASS_MIXER_CHAN_NORAMPIN;
|
||||
|
||||
|
||||
// Mixer attributes
|
||||
public static final int BASS_ATTRIB_MIXER_LATENCY = 0x15000;
|
||||
public static final int BASS_ATTRIB_MIXER_THREADS = 0x15001;
|
||||
public static final int BASS_ATTRIB_MIXER_VOL = 0x15002;
|
||||
|
||||
// Additional BASS_Mixer_ChannelIsActive return values
|
||||
public static final int BASS_ACTIVE_WAITING = 5;
|
||||
public static final int BASS_ACTIVE_QUEUED = 6;
|
||||
|
||||
// splitter flags
|
||||
public static final int BASS_SPLIT_SLAVE = 0x1000; // only read buffered data
|
||||
public static final int BASS_SPLIT_POS = 0x2000;
|
||||
|
||||
// splitter attributes
|
||||
public static final int BASS_ATTRIB_SPLIT_ASYNCBUFFER = 0x15010;
|
||||
public static final int BASS_ATTRIB_SPLIT_ASYNCPERIOD = 0x15011;
|
||||
|
||||
// envelope node
|
||||
@BA.ShortName("BASS_MIXER_NODE")
|
||||
// @Structure.FieldOrder({"pos","value"})
|
||||
public static class BASS_MIXER_NODE extends Structure{
|
||||
public BASS_MIXER_NODE() {}
|
||||
public BASS_MIXER_NODE(long _pos, float _value) { pos=_pos; value=_value; }
|
||||
public long pos;
|
||||
public float value;
|
||||
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return Arrays.asList("pos","value");
|
||||
}
|
||||
|
||||
@BA.Hide public void autoRead(){};
|
||||
@BA.Hide public void autoWrite(){};
|
||||
}
|
||||
|
||||
// envelope types
|
||||
public static final int BASS_MIXER_ENV_FREQ = 1;
|
||||
public static final int BASS_MIXER_ENV_VOL = 2;
|
||||
public static final int BASS_MIXER_ENV_PAN = 3;
|
||||
public static final int BASS_MIXER_ENV_LOOP = 0x10000; // flag: loop
|
||||
public static final int BASS_MIXER_ENV_REMOVE = 0x20000; // flag: remove at end
|
||||
|
||||
// additional sync type
|
||||
public static final int BASS_SYNC_MIXER_ENVELOPE = 0x10200;
|
||||
public static final int BASS_SYNC_MIXER_ENVELOPE_NODE = 0x10201;
|
||||
public static final int BASS_SYNC_MIXER_QUEUE = 0x10202;
|
||||
|
||||
// additional BASS_Mixer_ChannelSetPosition flag
|
||||
public static final int BASS_POS_MIXER_RESET = 0x10000; // flag: clear mixer's playback buffer
|
||||
|
||||
// Additional BASS_Mixer_ChannelGetPosition mode
|
||||
public static final int BASS_POS_MIXER_DELAY = 5;
|
||||
|
||||
// BASS_CHANNELINFO type
|
||||
public static final int BASS_CTYPE_STREAM_MIXER = 0x10800;
|
||||
public static final int BASS_CTYPE_STREAM_SPLIT = 0x10801;
|
||||
|
||||
public static native int BASS_Mixer_GetVersion();
|
||||
|
||||
public static native int BASS_Mixer_StreamCreate(int freq, int chans, int flags);
|
||||
public static native boolean BASS_Mixer_StreamAddChannel(int handle, int channel, int flags);
|
||||
public static native boolean BASS_Mixer_StreamAddChannelEx(int handle, int channel, int flags, long start, long length);
|
||||
public static native int BASS_Mixer_StreamGetChannels(int handle, int[] channels, int count);
|
||||
|
||||
public static native int BASS_Mixer_ChannelGetMixer(int handle);
|
||||
public static native int BASS_Mixer_ChannelIsActive(int handle);
|
||||
public static native int BASS_Mixer_ChannelFlags(int handle, int flags, int mask);
|
||||
public static native boolean BASS_Mixer_ChannelRemove(int handle);
|
||||
public static native boolean BASS_Mixer_ChannelSetPosition(int handle, long pos, int mode);
|
||||
public static native long BASS_Mixer_ChannelGetPosition(int handle, int mode);
|
||||
public static native long BASS_Mixer_ChannelGetPositionEx(int channel, int mode, int delay);
|
||||
public static native int BASS_Mixer_ChannelGetLevel(int handle);
|
||||
public static native boolean BASS_Mixer_ChannelGetLevelEx(int handle, float[] levels, float length, int flags);
|
||||
public static native int BASS_Mixer_ChannelGetData(int handle, Pointer buffer, int length);
|
||||
public static native int BASS_Mixer_ChannelSetSync(int handle, int type, long param, BASS.SYNCPROC proc, Pointer user);
|
||||
public static native boolean BASS_Mixer_ChannelRemoveSync(int channel, int sync);
|
||||
//public static native boolean BASS_Mixer_ChannelSetMatrix(int handle, float[][] matrix);
|
||||
public static native boolean BASS_Mixer_ChannelSetMatrix(int handle, float[] matrix);
|
||||
//public static native boolean BASS_Mixer_ChannelSetMatrixEx(int handle, float[][] matrix, float time);
|
||||
public static native boolean BASS_Mixer_ChannelSetMatrixEx(int handle, float[] matrix, float time);
|
||||
//public static native boolean BASS_Mixer_ChannelGetMatrix(int handle, float[][] matrix);
|
||||
public static native boolean BASS_Mixer_ChannelGetMatrix(int handle, float[] matrix);
|
||||
//public static native boolean BASS_Mixer_ChannelSetEnvelope(int handle, int type, BASS_MIXER_NODE[] nodes, int count);
|
||||
public static native boolean BASS_Mixer_ChannelSetEnvelope(int handle, int type, Structure nodes, int count);
|
||||
public static native boolean BASS_Mixer_ChannelSetEnvelopePos(int handle, int type, long pos);
|
||||
public static native long BASS_Mixer_ChannelGetEnvelopePos(int handle, int type, FloatByReference value);
|
||||
|
||||
public static native int BASS_Split_StreamCreate(int channel, int flags, int[] chanmap);
|
||||
public static native int BASS_Split_StreamGetSource(int handle);
|
||||
public static native int BASS_Split_StreamGetSplits(int handle, int[] splits, int count);
|
||||
public static native boolean BASS_Split_StreamReset(int handle);
|
||||
public static native boolean BASS_Split_StreamResetEx(int handle, int offset);
|
||||
public static native int BASS_Split_StreamGetAvailable(int handle);
|
||||
|
||||
|
||||
}
|
||||
320
src/com/un4seen/bass/BassLibrary.java
Normal file
320
src/com/un4seen/bass/BassLibrary.java
Normal file
@@ -0,0 +1,320 @@
|
||||
package com.un4seen.bass;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import com.sun.jna.Native;
|
||||
import com.sun.jna.Platform;
|
||||
|
||||
import android.os.Build;
|
||||
import anywheresoftware.b4a.BA;
|
||||
|
||||
|
||||
/**
|
||||
* BASS Library copier
|
||||
* @author rdkartono
|
||||
*
|
||||
*/
|
||||
|
||||
public class BassLibrary {
|
||||
|
||||
|
||||
public static final String lib_bass = "bass";
|
||||
public static final String lib_bass_ac3 = "bass_ac3";
|
||||
public static final String lib_bass_ape = "bass_ape";
|
||||
public static final String lib_bass_fx = "bass_fx";
|
||||
public static final String lib_bass_mpc = "bass_mpc";
|
||||
public static final String lib_bass_spx = "bass_spx";
|
||||
public static final String lib_bass_tta = "bass_tta";
|
||||
public static final String lib_basscd = "basscd";
|
||||
public static final String lib_bassdsd = "bassdsd";
|
||||
public static final String lib_bassenc_flac = "bassenc_flac";
|
||||
public static final String lib_bassenc_mp3 = "bassenc_mp3";
|
||||
public static final String lib_bassenc_ogg = "bassenc_ogg";
|
||||
public static final String lib_bassenc_opus = "bassenc_opus";
|
||||
public static final String lib_bassenc = "bassenc";
|
||||
public static final String lib_bassflac = "bassflac";
|
||||
public static final String lib_basshls = "basshls";
|
||||
public static final String lib_bassmidi = "bassmidi";
|
||||
public static final String lib_bassmix = "bassmix";
|
||||
public static final String lib_bassopus = "bassopus";
|
||||
public static final String lib_basswebm = "basswebm";
|
||||
public static final String lib_basswv = "basswv";
|
||||
public static final String lib_tags = "tags";
|
||||
public static final String lib_bass_aac = "bass_aac";
|
||||
public static final String lib_bassalac = "bassalac";
|
||||
public static String userdir = ""; // user directory
|
||||
public static String homedir = ""; // home directory
|
||||
public static String rundir = ""; // run directory
|
||||
private static String osname = ""; // OS name
|
||||
private static String osarch = ""; // OS architecture
|
||||
private static String osversion = ""; // OS version
|
||||
private static String javaLibPath = ""; // Java library path
|
||||
private static String jvm = ""; // java virtual machine
|
||||
private static String nativepath = ""; // library native path
|
||||
private static String pathseparator = ""; // path separator untuk library
|
||||
private static String jnalibpath = "";
|
||||
|
||||
private static void setJNALibPath(String value) {
|
||||
if (value != null) {
|
||||
if (!value.isEmpty()) {
|
||||
if (!value.equals(jnalibpath)) {
|
||||
String prev = System.setProperty("jna.library.path", value);
|
||||
jnalibpath = value;
|
||||
if (prev instanceof String) {
|
||||
BA.Log("jna.library.path changed from "+prev+" to "+value);
|
||||
} else {
|
||||
BA.Log("New value for jna.library.path to "+value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private static String getJNALibPath() {
|
||||
if (jnalibpath.isEmpty()) {
|
||||
jnalibpath = System.getProperty("jna.library.path");
|
||||
}
|
||||
return jnalibpath;
|
||||
}
|
||||
|
||||
private static String getJavaLibPath() {
|
||||
if (javaLibPath.isEmpty()) {
|
||||
javaLibPath = System.getProperty("java.library.path");
|
||||
}
|
||||
return javaLibPath;
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static void DetectOS() {
|
||||
System.setProperty("jna.debug_load", "true");
|
||||
|
||||
osname = System.getProperty("os.name");
|
||||
osarch = System.getProperty("os.arch");
|
||||
osversion = System.getProperty("os.version");
|
||||
jvm = System.getProperty("java.vm.name");
|
||||
homedir = System.getProperty("user.home");
|
||||
userdir = System.getProperty("user.dir");
|
||||
rundir = new File("").getAbsolutePath();
|
||||
pathseparator = System.getProperty("path.separator");
|
||||
javaLibPath = getJavaLibPath();
|
||||
jnalibpath = getJNALibPath();
|
||||
if (jnalibpath==null)
|
||||
setJNALibPath(rundir);
|
||||
else if (jnalibpath.isEmpty())
|
||||
setJNALibPath(rundir);
|
||||
|
||||
BA.Log("OS Name : "+osname);
|
||||
BA.Log("OS Architecture : "+osarch);
|
||||
BA.Log("OS Version : "+osversion);
|
||||
BA.Log("JVM Name : "+jvm);
|
||||
BA.Log("Java Lib path : "+javaLibPath);
|
||||
BA.Log("Path Separator : ["+pathseparator+"]");
|
||||
BA.Log("Run Directory : "+rundir);
|
||||
BA.Log("User Home : "+homedir);
|
||||
BA.Log("User Directory : "+userdir);
|
||||
BA.Log("JNA Library Path : "+jnalibpath);
|
||||
|
||||
|
||||
if (osname.toLowerCase().contains("window")) {
|
||||
if (osarch.toLowerCase().contains("86")) {
|
||||
BA.Log("Windows 32bit");
|
||||
nativepath="/lib/win32/";
|
||||
} else if (osarch.toLowerCase().contains("64")) {
|
||||
BA.Log("Windows 64bit");
|
||||
nativepath = "/lib/win64/";
|
||||
}
|
||||
} else if (osname.toLowerCase().contains("linux")) {
|
||||
|
||||
if (jvm.toLowerCase().contains("dalvik")) {
|
||||
String[] abi_list = Build.SUPPORTED_ABIS;
|
||||
if (abi_list == null) {
|
||||
BA.Log("Android ABI is null");
|
||||
} else if (abi_list.length==0) {
|
||||
BA.Log("ABI list is zero");
|
||||
} else {
|
||||
|
||||
String abi = abi_list[0].toLowerCase();
|
||||
BA.Log("Android ABI="+abi);
|
||||
if (abi.contains("arm64")) {
|
||||
nativepath="/lib/arm64-v8a/";
|
||||
BA.Log("Android ARM64");
|
||||
} else if (abi.contains("armeabi-v7a")) {
|
||||
nativepath="/lib/armeabi-v7a/";
|
||||
BA.Log("Android ARMEABI-V7A");
|
||||
} else if (abi.contains("x86")){
|
||||
nativepath="/lib/x86/";
|
||||
BA.Log("Android X86");
|
||||
} else if (abi.contains("x86_64")) {
|
||||
nativepath="/lib/x86_64/";
|
||||
BA.Log("Android X86_64");
|
||||
}
|
||||
else {
|
||||
BA.Log("Unsupported ABI");
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
|
||||
if (osarch.toLowerCase().contains("arm")) {
|
||||
BA.Log("Raspberry");
|
||||
nativepath = "/lib/raspberry/";
|
||||
} else if (osarch.toLowerCase().contains("aarch64")) {
|
||||
BA.Log("ARM 64bit");
|
||||
nativepath = "/lib/aarch64/";
|
||||
} else if (osarch.toLowerCase().contains("86")) {
|
||||
BA.Log("Linux 32bit");
|
||||
nativepath = "/lib/linux32/";
|
||||
} else if (osarch.toLowerCase().contains("64")) {
|
||||
BA.Log("Linux 64bit");
|
||||
nativepath = "/lib/linux64/";
|
||||
} else {
|
||||
BA.Log("Unsuppported Linux");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
BA.Log("Unsupported OS");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if BassLibrary is Initialized or not
|
||||
* @return true if initalized
|
||||
*/
|
||||
private static boolean IsInitialized() {
|
||||
if (nativepath.length()>0) {
|
||||
if (rundir.length()>0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract selected library to App Directory
|
||||
* @param libname : select from predefined values
|
||||
* @return true if success
|
||||
*/
|
||||
private static String Extract_Library(String libname) {
|
||||
if (IsInitialized()) {
|
||||
return copylib(nativepath, System.mapLibraryName(libname), rundir);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
public static boolean Valid_String(String str) {
|
||||
if (str!=null) {
|
||||
if (str.length()>0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private static String copylib(String sourcepath, String libname, String targetpath){
|
||||
if (Valid_String(sourcepath)) {
|
||||
if (Valid_String(libname)) {
|
||||
if (Valid_String(targetpath)) {
|
||||
File targetcheck = new File(targetpath);
|
||||
if (targetcheck.exists()) {
|
||||
if (targetcheck.isDirectory()) {
|
||||
if (targetcheck.canWrite()) {
|
||||
File fsource = null;
|
||||
String filetoextract = sourcepath+libname;
|
||||
try {
|
||||
fsource = Native.extractFromResourcePath(filetoextract);
|
||||
File ftarget = new File(targetpath, libname);
|
||||
if (ftarget.exists()) {
|
||||
|
||||
if (ftarget.length()==fsource.length()) {
|
||||
if (!fsource.delete()) {BA.Log("Unable to delete "+fsource.getAbsolutePath());}
|
||||
BA.Log(libname+" already exists and same in "+targetpath);
|
||||
return ftarget.getAbsolutePath();
|
||||
} else {
|
||||
if (fsource.renameTo(ftarget)) {
|
||||
BA.Log("Copied "+libname+" to "+targetpath);
|
||||
return ftarget.getAbsolutePath();
|
||||
} else {
|
||||
BA.Log("Failed to copy "+libname+" to "+targetpath);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (fsource.renameTo(ftarget)) {
|
||||
BA.Log("Copied "+libname+" to "+targetpath);
|
||||
return ftarget.getAbsolutePath();
|
||||
} else {
|
||||
BA.Log("Failed to copy "+libname+" to "+targetpath);
|
||||
}
|
||||
}
|
||||
} catch (IOException e1) {
|
||||
BA.Log("Unable to extractFromResourcePath "+filetoextract);
|
||||
}
|
||||
} else BA.Log("copylib failed, targetpath="+targetpath+" is not writable");
|
||||
} else BA.Log("copylib failed, targetpath="+targetpath+" is not folder");
|
||||
} else BA.Log("copylib failed, targetpath="+targetpath+" is not exists");
|
||||
} else BA.Log("copylib failed, targetpath is invalid");
|
||||
} else BA.Log("copylib failed, libname is invalid");
|
||||
} else BA.Log("copylib failed, sourcepath is invalid");
|
||||
return "";
|
||||
};
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static boolean LoadLibraryFromJar(Class c, String libname) {
|
||||
if (Platform.isAndroid()) {
|
||||
try {
|
||||
System.loadLibrary(libname);
|
||||
return true;
|
||||
} catch(Exception e) {
|
||||
System.out.println("LoadLibraryFromJar isAndroid failed on library="+libname);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// if (!IsInitialized()) DetectOS();
|
||||
// Native.register(c, libname);
|
||||
// return true;
|
||||
String libpath = Extract_Library(libname);
|
||||
if (Valid_String(libpath)) {
|
||||
Native.register(c, libname);
|
||||
return true;
|
||||
} else {
|
||||
System.out.println("Extract_Library failed on library="+libname);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String ExtractLibraryFromJar(String libname) {
|
||||
String currentdir = new File("").getAbsolutePath();
|
||||
String fulllibname = new File(currentdir, System.mapLibraryName(libname)).getAbsolutePath();
|
||||
//System.out.println("Checking if "+fulllibname+" exists");
|
||||
File libfile = new File(fulllibname);
|
||||
if (libfile.exists() && libfile.length()>0) {
|
||||
System.out.println(fulllibname+" already existed");
|
||||
return fulllibname;
|
||||
} else {
|
||||
try {
|
||||
File ff = Native.extractFromResourcePath(libname);
|
||||
if (ff.exists() && ff.length()>0) {
|
||||
if (ff.renameTo(libfile)) {
|
||||
System.out.println("created "+fulllibname);
|
||||
return fulllibname;
|
||||
} else System.out.println("Unable to create "+fulllibname);
|
||||
} else System.out.println("extractFromResourcePath produce invalid File");
|
||||
} catch (IOException e) {
|
||||
System.out.println("ExtractLibraryFromJar exception on libname="+libname+", message = "+e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
18
src/internetradio/RadioURLs.java
Normal file
18
src/internetradio/RadioURLs.java
Normal file
@@ -0,0 +1,18 @@
|
||||
package internetradio;
|
||||
|
||||
import anywheresoftware.b4a.BA;
|
||||
|
||||
@BA.ShortName("radioURL")
|
||||
public class RadioURLs {
|
||||
public static final String radioparadise_uk = "http://stream-uk3.radioparadise.com:80/mp3-128";
|
||||
public static final String JakFM = "http://streaming.sportku.com:8000/jak";
|
||||
public static final String GenFM = "http://streaming.sportku.com:8000/genfm";
|
||||
public static final String HardRockFM = "http://streaming.mramedia.com:8000/hardrock";
|
||||
public static final String ElshintaFM = "http://202.137.4.147:8000/";
|
||||
public static final String CosmopolitanFM = "http://streaming.mramedia.com:8000/cosmo-aac";
|
||||
public static final String SonoraFM = "http://103.226.246.245/kompas-sonorajakarta";
|
||||
public static final String SmartFM = "http://103.226.246.245/kompas-smartjakarta";
|
||||
public static final String HeartLineFM = "http://202.129.186.43:7010/";
|
||||
|
||||
|
||||
}
|
||||
358
src/jbass/AudioCombiner.java
Normal file
358
src/jbass/AudioCombiner.java
Normal file
@@ -0,0 +1,358 @@
|
||||
package jbass;
|
||||
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import com.sun.jna.Memory;
|
||||
import com.sun.jna.Pointer;
|
||||
import com.un4seen.bass.BASS;
|
||||
import com.un4seen.bass.BASS.bassconstant;
|
||||
import com.un4seen.bass.BASSmix;
|
||||
|
||||
import anywheresoftware.b4a.BA;
|
||||
import anywheresoftware.b4a.keywords.Bit;
|
||||
import anywheresoftware.b4a.objects.collections.List;
|
||||
|
||||
@BA.ShortName("AudioCombiner")
|
||||
@BA.Events(values={
|
||||
"log(msg as string)",
|
||||
"combinewav(success as boolean, destination as string)",
|
||||
"combinemp3(success as boolean, destination as string)"
|
||||
})
|
||||
public class AudioCombiner {
|
||||
|
||||
private Object caller;
|
||||
private String event;
|
||||
private BA ba;
|
||||
private boolean inited = false;
|
||||
private boolean need_log_event = false;
|
||||
private boolean need_combinewav_event = false;
|
||||
private boolean need_combinemp3_event = false;
|
||||
private BASS bass;
|
||||
private BASSmix bassmix;
|
||||
|
||||
private ExecutorService executor = Executors.newFixedThreadPool(10);
|
||||
|
||||
public void Initialize(BA bax, Object callerobject, String eventname) {
|
||||
ba = bax;
|
||||
caller = callerobject;
|
||||
event = eventname;
|
||||
|
||||
if (ba!=null) {
|
||||
if (caller!=null) {
|
||||
if (!event.isEmpty()) {
|
||||
need_log_event = ba.subExists(event+"_log");
|
||||
need_combinewav_event = ba.subExists(event+"_combinewav");
|
||||
need_combinemp3_event = ba.subExists(event+"_combinemp3");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bass = new BASS();
|
||||
bassmix = new BASSmix();
|
||||
if (bass.BASS_GetVersion()!=0) {
|
||||
if (bassmix.BASS_Mixer_GetVersion()!=0) {
|
||||
inited = true;
|
||||
} else raise_log("Failed to Load BassMix");
|
||||
|
||||
} else raise_log("Failed to Load Bass");
|
||||
}
|
||||
|
||||
public boolean IsInitialized() {
|
||||
return inited;
|
||||
}
|
||||
|
||||
private boolean run_basic_checks(final List source, String destination) {
|
||||
if (inited) {
|
||||
if (source!=null) {
|
||||
if (source.IsInitialized()) {
|
||||
if (source.getSize()>0) {
|
||||
if (!destination.isEmpty()) {
|
||||
File fdest = new File(destination);
|
||||
if (!fdest.isDirectory()) {
|
||||
// cek satu per satu
|
||||
|
||||
for(int ii=0;ii<source.getSize();ii++) {
|
||||
Object xx = source.Get(ii);
|
||||
|
||||
if ((xx instanceof String)==false) {
|
||||
raise_log("Member of Source List is not String Path");
|
||||
return false;
|
||||
}
|
||||
|
||||
File xxx = new File((String) xx);
|
||||
if (!xxx.exists()) {
|
||||
raise_log("Member of Source List is not valid Path / not exist");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
} else {
|
||||
raise_log("Destination is directory path");
|
||||
}
|
||||
} else {
|
||||
raise_log("Destination Path is empty");
|
||||
}
|
||||
} else {
|
||||
raise_log("Source List is empty");
|
||||
}
|
||||
} else {
|
||||
raise_log("Source List not initialized");
|
||||
}
|
||||
} else {
|
||||
raise_log("Source List is null");
|
||||
}
|
||||
} else {
|
||||
raise_log("BASS not inited");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine paths inside source to destination , in WAV format
|
||||
* will raise event combinewav(success as boolean, destination as string)
|
||||
* @param source : list of valid file path
|
||||
* @param destination : valid full path of target WAV file
|
||||
*/
|
||||
public void Combine_to_Wav(final List source, String destination, int target_samplingrate, int target_channel) {
|
||||
if (run_basic_checks(source, destination)) {
|
||||
// sampe sini dah cek semua, tinggal execute
|
||||
executor.execute(new WavCombineWork(source, destination, target_samplingrate, target_channel));
|
||||
} else {
|
||||
raise_combinewav(false, destination);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine paths inside source to destination , in MP3 format
|
||||
* will raise event combinemp3(success as boolean, destination as string)
|
||||
* @param source : list of valid file path
|
||||
* @param destination : valid full path of target MP3 file
|
||||
*/
|
||||
public void Combine_to_MP3(final List source, String destination, int target_samplingrate, int target_channel) {
|
||||
if (run_basic_checks(source, destination)) {
|
||||
// sampe sini dah cek semua, tinggal execute
|
||||
executor.execute(new Mp3CombineWork(source, destination, target_samplingrate, target_channel));
|
||||
} else {
|
||||
raise_combinemp3(false, destination);
|
||||
}
|
||||
}
|
||||
|
||||
private abstract class CombineBasic implements Runnable{
|
||||
protected List source;
|
||||
protected String destination;
|
||||
|
||||
protected int destinationhandle = 0;
|
||||
protected final int readsize = 16000;
|
||||
protected final int samplingrate;
|
||||
protected final int channel;
|
||||
protected final int mode;
|
||||
private int mixerhandle = 0;
|
||||
|
||||
/**
|
||||
* Create CombineBasic
|
||||
* @param source : List of valid paths
|
||||
* @param destination : target path
|
||||
* @param samplingrate : sampling rate
|
||||
* @param channel : 1 = mono, 2 = stereo
|
||||
* @param mode : 0 = wav, 1 = mp3
|
||||
*/
|
||||
CombineBasic(final List source, final String destination, int samplingrate, int channel, int mode){
|
||||
this.source = source;
|
||||
this.destination = destination;
|
||||
this.samplingrate = samplingrate;
|
||||
this.channel = channel;
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (bass_init_dev0()) {
|
||||
if (Create_Mixer()) {
|
||||
if (Create_HandleWriter(mixerhandle)) {
|
||||
|
||||
List sourcehandles = new List();
|
||||
if (Open_Sources(sourcehandles)) {
|
||||
// sampe sini semua bisa diopen dengan normal
|
||||
final int mixerflag = Bit.Or(bassconstant.BASS_STREAM_AUTOFREE, bassmix.BASS_MIXER_CHAN_NORAMPIN);
|
||||
for(int ii=0;ii<sourcehandles.getSize();ii++) {
|
||||
int hh = (int)sourcehandles.Get(ii);
|
||||
String namafile = (String) source.Get(ii);
|
||||
if (bassmix.BASS_Mixer_StreamAddChannel(mixerhandle, hh, mixerflag)) {
|
||||
|
||||
|
||||
int readcount = 0;
|
||||
do {
|
||||
Pointer buffer = new Memory(readsize);
|
||||
readcount = bass.BASS_ChannelGetData(mixerhandle, buffer, readsize);
|
||||
|
||||
} while (readcount>0);
|
||||
|
||||
|
||||
} else raise_log("Failed Mixer_StreamAddChannel file="+namafile+", Msg:"+bass.GetBassErrorString());
|
||||
}
|
||||
}
|
||||
|
||||
// tutup mixer, dah kelar
|
||||
bass.BASS_StreamFree(mixerhandle);
|
||||
raise_combineresult(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
raise_combineresult(false);
|
||||
}
|
||||
|
||||
private boolean Open_Sources(List sourcehandles) {
|
||||
sourcehandles.Initialize();
|
||||
for (int ii = 0; ii<source.getSize();ii++) {
|
||||
String filename = (String)source.Get(ii);
|
||||
int handle = bass.BASS_StreamCreateFile(false, filename, 0, 0, bassconstant.BASS_STREAM_DECODE);
|
||||
if (handle==0) {
|
||||
// gagal buka
|
||||
raise_log("Unable to Open "+filename+", Msg:"+bass.GetBassErrorString());
|
||||
return false;
|
||||
}
|
||||
// sampe sini bisa open
|
||||
sourcehandles.Add(handle);
|
||||
}
|
||||
// sampe sini berarti semua file bisa open
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private boolean Create_Mixer() {
|
||||
final int mixerflag = bassconstant.BASS_STREAM_DECODE;
|
||||
|
||||
mixerhandle = bassmix.BASS_Mixer_StreamCreate(samplingrate, channel, mixerflag);
|
||||
if (mixerhandle!=0) {
|
||||
return true;
|
||||
} else {
|
||||
raise_log("CreateMixer failed, Msg:"+bass.GetBassErrorString());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// cuma beda di sini aja
|
||||
protected abstract boolean Create_HandleWriter();
|
||||
protected abstract boolean Create_HandleWriter(int fromhandle);
|
||||
|
||||
|
||||
|
||||
private boolean bass_init_dev0() {
|
||||
if (bass.BASS_SetDevice(0)) {
|
||||
// bisa setdevice, berarti sudah init
|
||||
return true;
|
||||
} else {
|
||||
int err = bass.BASS_ErrorGetCode();
|
||||
if (err == bassconstant.BASS_ERROR_DEVICE) {
|
||||
raise_log("Unable to SetDevice 0");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bass.BASS_Init(0, samplingrate, 0)) {
|
||||
return true;
|
||||
} else {
|
||||
raise_log("Unable to Init Device 0");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void raise_combineresult(boolean value) {
|
||||
switch(mode) {
|
||||
case 1 :
|
||||
raise_combinemp3(value, destination);
|
||||
break;
|
||||
default :
|
||||
raise_combinewav(value, destination);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class WavCombineWork extends CombineBasic{
|
||||
private HandleWavWriter hww;
|
||||
WavCombineWork(List source, String destination, int samplingrate, int channel) {
|
||||
super(source, destination, samplingrate, channel,0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean Create_HandleWriter() {
|
||||
hww = new HandleWavWriter();
|
||||
hww.Initialize(ba, event);
|
||||
if (hww.CreateFile_from_Handle(destination,destinationhandle)) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
raise_log("Unable to Create_HandleWavWriter"); return false; }
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean Create_HandleWriter(int fromhandle) {
|
||||
hww = new HandleWavWriter();
|
||||
hww.Initialize(ba, event);
|
||||
if (hww.CreateFile_from_Handle(destination,fromhandle)) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
raise_log("Unable to Create_HandleWavWriter"); return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class Mp3CombineWork extends CombineBasic{
|
||||
private HandleMP3Writer hww;
|
||||
Mp3CombineWork(List source, String destination, int samplingrate, int channel) {
|
||||
super(source, destination, samplingrate, channel,1);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean Create_HandleWriter() {
|
||||
hww = new HandleMP3Writer();
|
||||
hww.Initialize(ba, event);
|
||||
if (hww.CreateFile_from_Handle(destination, destinationhandle)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean Create_HandleWriter(int fromhandle) {
|
||||
hww = new HandleMP3Writer();
|
||||
hww.Initialize(ba, event);
|
||||
if (hww.CreateFile_from_Handle(destination, fromhandle)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void raise_log(String value) {
|
||||
if (need_log_event) ba.raiseEventFromDifferentThread(caller, null, 0, event+"_log", false, new Object[] {value});
|
||||
}
|
||||
|
||||
private void raise_combinewav(boolean success, String destination) {
|
||||
if (need_combinewav_event) ba.raiseEventFromDifferentThread(caller, null, 0, event+"_combinewav", false, new Object[] {success, destination});
|
||||
}
|
||||
|
||||
private void raise_combinemp3(boolean success, String destination){
|
||||
if (need_combinemp3_event) ba.raiseEventFromDifferentThread(caller, null, 0, event+"_combinemp3", false, new Object[] {success, destination});
|
||||
}
|
||||
}
|
||||
234
src/jbass/AudioFileInformation.java
Normal file
234
src/jbass/AudioFileInformation.java
Normal file
@@ -0,0 +1,234 @@
|
||||
package jbass;
|
||||
|
||||
import anywheresoftware.b4a.BA;
|
||||
import anywheresoftware.b4a.keywords.DateTime;
|
||||
|
||||
@BA.ShortName("MessageInformation")
|
||||
public class AudioFileInformation {
|
||||
private String filename;
|
||||
private long size;
|
||||
private double lengthseconds;
|
||||
private boolean valid;
|
||||
private boolean found;
|
||||
private boolean playing = false;
|
||||
private long startplayingtick = 0;
|
||||
private final long KB_threshold = 1024;
|
||||
private final long MB_threshold = 1024 * 1024;
|
||||
private final long GB_threshold = 1024 * 1024 * 1024;
|
||||
|
||||
|
||||
/**
|
||||
* Check if currently being played
|
||||
* @return true if playing
|
||||
*/
|
||||
public boolean getIsPlaying() {
|
||||
return playing;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set playing status
|
||||
* @param value : true if playing
|
||||
*/
|
||||
public void setIsPlaying(boolean value) {
|
||||
playing = value;
|
||||
if (playing) {
|
||||
startplayingtick = DateTime.getNow();
|
||||
} else {
|
||||
startplayingtick = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Playback duration since IsPlaying is set true
|
||||
* @return seconds of playback
|
||||
*/
|
||||
public int getPlaybackDuration() {
|
||||
if (startplayingtick==-1) {
|
||||
return -1;
|
||||
} else {
|
||||
return (int)((DateTime.getNow() - startplayingtick)/DateTime.TicksPerSecond);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Playback duration since IsPlaying is set true
|
||||
* @return duration of playback in String
|
||||
*/
|
||||
public String getPlaybackDurationInString() {
|
||||
int xx = getPlaybackDuration();
|
||||
if (xx<0) {
|
||||
return "N/A";
|
||||
} else if (xx<60) {
|
||||
return xx+" sec";
|
||||
} else if (xx<3600) {
|
||||
int mm = xx / 60;
|
||||
int ss = xx - (mm*60);
|
||||
return mm+"min "+ss+"sec";
|
||||
} else {
|
||||
int hh = xx / 3600;
|
||||
xx = xx - (hh*3600);
|
||||
int mm = xx / 60;
|
||||
int ss = xx - (mm*60);
|
||||
return hh+"h "+mm+"m "+ss+"s";
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get File size in bytes
|
||||
* @return file size in bytes
|
||||
*/
|
||||
public long getSizeInBytes() {
|
||||
return size;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set File Size in bytes
|
||||
* @param value : file size in bytes
|
||||
*/
|
||||
public void setSizeInBytes(long value) {
|
||||
size = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get File Size in readable string
|
||||
* @return file size in readable string
|
||||
*/
|
||||
public String getSizeinString() {
|
||||
if (valid) {
|
||||
if (size<KB_threshold) {
|
||||
return size+" B";
|
||||
} else if (size<MB_threshold) {
|
||||
double vv = (double)size / KB_threshold;
|
||||
vv = Math.round(vv * 10.0) / 10.0; // 1 desimal
|
||||
return vv+" KB";
|
||||
} else if (size<GB_threshold) {
|
||||
double vv = (double)size / MB_threshold;
|
||||
vv = Math.round(vv*10.0)/ 10.0; // 1 desimal
|
||||
return vv+" MB";
|
||||
} else {
|
||||
double vv = (double)size/ GB_threshold;
|
||||
vv = Math.round(vv*10.0) / 10.0; // 1 desimal
|
||||
return vv+" GB";
|
||||
}
|
||||
} else return "N/A";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get File length in seconds
|
||||
* @return audio file duration in seconds
|
||||
*/
|
||||
public double getLengthInSeconds() {
|
||||
return lengthseconds;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set File length in seconds
|
||||
* @param value : file duration in seconds
|
||||
*/
|
||||
public void setLengthInSeconds(double value) {
|
||||
lengthseconds = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get File length in seconds in readable string
|
||||
* @return audio file duration in readable string
|
||||
*/
|
||||
public String getLengthinString() {
|
||||
if (valid) {
|
||||
int sec = (int)lengthseconds;
|
||||
if (sec < 60) {
|
||||
return sec+" sec";
|
||||
} else if (sec < 3600) {
|
||||
int mm = (int)sec / 60;
|
||||
int ss = (int)sec - (mm*60);
|
||||
return mm+" min "+ss+" sec";
|
||||
} else {
|
||||
int hh = sec / 3600;
|
||||
sec = sec - (hh*3600);
|
||||
int mm = sec / 60;
|
||||
int ss = sec - (mm*60);
|
||||
return hh+"h "+mm+"m "+ss+"s";
|
||||
}
|
||||
} else return "N/A";
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get Filepath assigned
|
||||
* @return filename or N/A if not assigned
|
||||
*/
|
||||
public String getFilePath() {
|
||||
if (valid) {
|
||||
|
||||
return filename;
|
||||
} else return "N/A";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set Filepath
|
||||
* @param value : filepath assigned
|
||||
*/
|
||||
public void setFilePath(String value) {
|
||||
filename = value;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this file is a valid audio file and can be played
|
||||
* @return true if valid audio file
|
||||
*/
|
||||
public boolean getIsValid() {
|
||||
return valid;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set audio file validity
|
||||
* @param value : true if valid
|
||||
*/
|
||||
public void setIsValid(boolean value) {
|
||||
valid = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this file path is found
|
||||
* @return true if found
|
||||
*/
|
||||
public boolean getIsFound() {
|
||||
return found;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set audio file found
|
||||
* @param value
|
||||
*/
|
||||
public void setIsFound(boolean value) {
|
||||
found = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return summary in string
|
||||
* @return PATH:[filepath or N/A];VALID:[YES or NO];SIZE:[size in bytes];LENGTH:[length in seconds]
|
||||
*/
|
||||
public String toString() {
|
||||
StringBuilder str = new StringBuilder();
|
||||
str.append("PATH:").append((filename.isEmpty())?"N/A":filename).append(";");
|
||||
str.append("VALID:").append((valid) ? "YES":"NO").append(";");
|
||||
str.append("SIZE:").append(getSizeinString()).append(";");
|
||||
str.append("LENGTH:").append(getLengthinString());
|
||||
|
||||
return str.toString();
|
||||
}
|
||||
|
||||
}
|
||||
156
src/jbass/Bass_ChannelInfo.java
Normal file
156
src/jbass/Bass_ChannelInfo.java
Normal file
@@ -0,0 +1,156 @@
|
||||
package jbass;
|
||||
|
||||
import com.un4seen.bass.BASS.BASS_CHANNELINFO;
|
||||
import com.un4seen.bass.BASS.bassconstant;
|
||||
|
||||
import anywheresoftware.b4a.BA;
|
||||
|
||||
@BA.ShortName("BASS_CHANNELINFO")
|
||||
/**
|
||||
* Pengganti BASS_CHANNELINFO
|
||||
* @author rdkartono
|
||||
*
|
||||
*/
|
||||
public class Bass_ChannelInfo {
|
||||
|
||||
/**
|
||||
* Samplingrate for playback or recording
|
||||
*/
|
||||
public final int frequency;
|
||||
|
||||
/**
|
||||
* Number of channel
|
||||
* 1 = mono, 2 = stereo, etc
|
||||
*/
|
||||
public final int channels;
|
||||
|
||||
/**
|
||||
* Flags
|
||||
* BASS_SAMPLE_8BITS The channel's resolution is 8-bit. If neither this or the BASS_SAMPLE_FLOAT flags are present, then the channel's resolution is 16-bit.
|
||||
* BASS_SAMPLE_FLOAT The channel's resolution is 32-bit floating-point.
|
||||
* BASS_SAMPLE_LOOP The channel is looped.
|
||||
* BASS_SAMPLE_3D The channel has 3D functionality enabled.
|
||||
* BASS_SAMPLE_SOFTWARE The channel is not using hardware DirectSound mixing.
|
||||
* BASS_SAMPLE_VAM The channel is using the DX7 voice allocation and management features. (HCHANNEL only)
|
||||
* BASS_SAMPLE_MUTEMAX The channel is muted when at (or beyond) its max distance.
|
||||
* BASS_SAMPLE_FX The channel has the "with FX flag" DX8 effect implementation enabled. (HSTREAM/HMUSIC)
|
||||
* BASS_STREAM_RESTRATE The internet file download rate is restricted. (HSTREAM)
|
||||
* BASS_STREAM_BLOCK The internet file (or "buffered" user file) is streamed in small blocks. (HSTREAM)
|
||||
* BASS_STREAM_AUTOFREE The channel will automatically be freed when it ends. (HSTREAM/HMUSIC)
|
||||
* BASS_STREAM_DECODE The channel is a "decoding channel". (HSTREAM/HMUSIC/HRECORD)
|
||||
* BASS_MUSIC_RAMP The MOD music is using "normal" ramping. (HMUSIC)
|
||||
* BASS_MUSIC_RAMPS The MOD music is using "sensitive" ramping. (HMUSIC)
|
||||
* BASS_MUSIC_SURROUND The MOD music is using surround sound. (HMUSIC)
|
||||
* BASS_MUSIC_SURROUND2 The MOD music is using surround sound mode 2. (HMUSIC)
|
||||
* BASS_MUSIC_NONINTER The MOD music is using non-interpolated mixing. (HMUSIC)
|
||||
* BASS_MUSIC_FT2MOD The MOD music is using FastTracker 2 .MOD playback. (HMUSIC)
|
||||
* BASS_MUSIC_PT1MOD The MOD music is using ProTracker 1 .MOD playback. (HMUSIC)
|
||||
* BASS_MUSIC_STOPBACK The MOD music will be stopped when a backward jump effect is played. (HMUSIC)
|
||||
* BASS_SPEAKER_xxx Speaker assignment flags. (HSTREAM/HMUSIC)
|
||||
* BASS_ASYNCFILE The file is read asynchronously. (HSTREAM)
|
||||
* BASS_UNICODE filename is in UTF-16 form.
|
||||
*/
|
||||
public final int flags;
|
||||
|
||||
/**
|
||||
* Channel Type
|
||||
* BASS_CTYPE_SAMPLE Sample channel. (HCHANNEL)
|
||||
* BASS_CTYPE_STREAM User sample stream. This can also be used as a flag to test if the channel is any kind of HSTREAM.
|
||||
* BASS_CTYPE_STREAM_DUMMY "dummy" stream (using STREAMPROC_DUMMY).
|
||||
* BASS_CTYPE_STREAM_DEVICE "dummy" stream for device final output mix (using STREAMPROC_DEVICE).
|
||||
* BASS_CTYPE_STREAM_VORBIS Ogg Vorbis format stream.
|
||||
* BASS_CTYPE_STREAM_MP1 MPEG layer 1 format stream.
|
||||
* BASS_CTYPE_STREAM_MP2 MPEG layer 2 format stream.
|
||||
* BASS_CTYPE_STREAM_MP3 MPEG layer 3 format stream.
|
||||
* BASS_CTYPE_STREAM_AIFF Audio IFF format stream.
|
||||
* BASS_CTYPE_STREAM_AM Android media codec stream. Additional format information is avaliable from BASS_ChannelGetTags (BASS_TAG_AM_MIME).
|
||||
* BASS_CTYPE_STREAM_CA CoreAudio codec stream. Additional format information is avaliable from BASS_ChannelGetTags (BASS_TAG_CA_CODEC).
|
||||
* BASS_CTYPE_STREAM_MF Media Foundation codec stream. Additional format information is avaliable from BASS_ChannelGetTags (BASS_TAG_WAVEFORMAT).
|
||||
* BASS_CTYPE_STREAM_WAV_PCM Integer PCM WAVE format stream.
|
||||
* BASS_CTYPE_STREAM_WAV_FLOAT Floating-point PCM WAVE format stream.
|
||||
* BASS_CTYPE_STREAM_WAV WAVE format flag. This can be used to test if the channel is any kind of WAVE format. The codec (the file's "wFormatTag") is specified in the LOWORD. Additional information is also avaliable from BASS_ChannelGetTags (BASS_TAG_WAVEFORMAT).
|
||||
* BASS_CTYPE_MUSIC_MOD Generic MOD format music. This can also be used as a flag to test if the channel is any kind of HMUSIC.
|
||||
* BASS_CTYPE_MUSIC_MTM MultiTracker format music.
|
||||
* BASS_CTYPE_MUSIC_S3M ScreamTracker 3 format music.
|
||||
* BASS_CTYPE_MUSIC_XM FastTracker 2 format music.
|
||||
* BASS_CTYPE_MUSIC_IT Impulse Tracker format music.
|
||||
* BASS_CTYPE_MUSIC_MO3 MO3 format flag, used in combination with one of the BASS_CTYPE_MUSIC types.
|
||||
* BASS_CTYPE_RECORD Recording channel. (HRECORD)
|
||||
*/
|
||||
public final int channeltype;
|
||||
|
||||
/**
|
||||
* The original resolution (bits per sample)... 0 = undefined. If the original sample format is floating-point, then the BASS_ORIGRES_FLOAT flag will be set and the number of bits will be in the LOWORD.
|
||||
*/
|
||||
public final int originalresolution;
|
||||
|
||||
/**
|
||||
* The plugin that is handling the channel... 0 = not using a plugin. Note this is only available with streams created using the plugin system via the standard BASS stream creation functions, not those created by add-on functions. Information on the plugin can be retrieved via BASS_PluginGetInfo.
|
||||
*/
|
||||
public final int pluginhandle;
|
||||
|
||||
/**
|
||||
* The sample that is playing on the channel. (HCHANNEL only)
|
||||
*/
|
||||
public final int handlesample;
|
||||
|
||||
/**
|
||||
* The filename associated with the channel. (HSTREAM only)
|
||||
*/
|
||||
public final String streamfilename;
|
||||
|
||||
public final boolean isvalid;
|
||||
|
||||
public Bass_ChannelInfo(BASS_CHANNELINFO chi) {
|
||||
if (chi instanceof BASS_CHANNELINFO) {
|
||||
chi.read();
|
||||
this.frequency = chi.freq;
|
||||
this.channels = chi.chans;
|
||||
this.channeltype = chi.ctype;
|
||||
this.flags = chi.flags;
|
||||
this.handlesample = chi.sample;
|
||||
this.originalresolution = chi.origres;
|
||||
this.pluginhandle = chi.plugin;
|
||||
this.streamfilename = chi.filename;
|
||||
this.isvalid = true;
|
||||
} else {
|
||||
this.frequency = 0;
|
||||
this.channels = 0;
|
||||
this.channeltype = 0;
|
||||
this.flags = 0;
|
||||
this.handlesample = 0;
|
||||
this.originalresolution = 0;
|
||||
this.pluginhandle = 0;
|
||||
this.streamfilename=null;
|
||||
this.isvalid = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this channel is recording channel
|
||||
* @return true if recording channel
|
||||
*/
|
||||
public boolean IsRecordingChannel() {
|
||||
if (isvalid) {
|
||||
return (channeltype & bassconstant.BASS_CTYPE_RECORD)>0 ? true:false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this channel is stream channel
|
||||
* @return true if stream channel
|
||||
*/
|
||||
public boolean IsFileStreamChannel() {
|
||||
if (isvalid) {
|
||||
if ((channeltype & bassconstant.BASS_CTYPE_STREAM)>0) {
|
||||
if (streamfilename instanceof String) {
|
||||
if (streamfilename.length()>0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
135
src/jbass/Bass_DeviceInfo.java
Normal file
135
src/jbass/Bass_DeviceInfo.java
Normal file
@@ -0,0 +1,135 @@
|
||||
package jbass;
|
||||
|
||||
import com.un4seen.bass.BASS.BASS_DEVICEINFO;
|
||||
import com.un4seen.bass.BASS.bassconstant;
|
||||
|
||||
import anywheresoftware.b4a.BA;
|
||||
|
||||
@BA.ShortName("BASS_DEVICEINFO")
|
||||
/**
|
||||
* Pengganti BASS_DEVICEINFO dari Bass
|
||||
* @author rdkartono
|
||||
*
|
||||
*/
|
||||
public class Bass_DeviceInfo {
|
||||
public final int index;
|
||||
public final String Name;
|
||||
public final String Driver;
|
||||
public final int Flags;
|
||||
public final boolean isvalid;
|
||||
|
||||
public Bass_DeviceInfo() {
|
||||
index = -1;
|
||||
Name = "";
|
||||
Driver = "";
|
||||
Flags = 0;
|
||||
isvalid = false;
|
||||
}
|
||||
|
||||
public Bass_DeviceInfo(int index, BASS_DEVICEINFO source) {
|
||||
this.index = index;
|
||||
if (source instanceof BASS_DEVICEINFO) {
|
||||
source.read();
|
||||
if (source.name instanceof String) {
|
||||
Name = source.name;
|
||||
Driver = (source.driver instanceof String) ? source.driver : "";
|
||||
Flags = source.flags;
|
||||
isvalid = true;
|
||||
return;
|
||||
}
|
||||
|
||||
Name="";
|
||||
Driver="";
|
||||
Flags = 0;
|
||||
isvalid = false;
|
||||
} else {
|
||||
Name = "";
|
||||
Driver = "";
|
||||
Flags = 0;
|
||||
isvalid = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get String Status
|
||||
*/
|
||||
public String toString() {
|
||||
return "index="+index+", Name="+Name+", Driver="+Driver+", Inited="+IsInited()+", Enabled="+IsEnabled()+", DefaultDevice="+IsDefaultDevice();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this device is already inited by BASS
|
||||
* @return true if inited
|
||||
*/
|
||||
public boolean IsInited() {
|
||||
return (Flags & bassconstant.BASS_DEVICE_INIT)>0 ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this device is enabled by OS
|
||||
* @return true if enabled
|
||||
*/
|
||||
public boolean IsEnabled() {
|
||||
return (Flags & bassconstant.BASS_DEVICE_ENABLED) > 0 ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this device is the default device by OS
|
||||
* @return true if default device
|
||||
*/
|
||||
public boolean IsDefaultDevice() {
|
||||
return (Flags & bassconstant.BASS_DEVICE_DEFAULT) > 0 ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this device is the system default communication device.
|
||||
* @return true if default device
|
||||
*/
|
||||
public boolean IsDefaultCommunicationDevice() {
|
||||
return (Flags & bassconstant.BASS_DEVICE_DEFAULTCOM) > 0 ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this device capable to do loopback recording
|
||||
* it captures the sound from an output device.
|
||||
* The corresponding output device can be identified by having the same driver value.
|
||||
* @return true if capable
|
||||
*/
|
||||
public boolean IsLoopbackRecordingDevice() {
|
||||
return (Flags & bassconstant.BASS_DEVICE_LOOPBACK) > 0 ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Device Connection Type
|
||||
*
|
||||
* DIGITAL : An audio endpoint device that connects to an audio adapter through a connector for a digital interface of unknown type.
|
||||
* DISPLAYPORT : An audio endpoint device that connects to an audio adapter through a DisplayPort connector.
|
||||
* HANDSET : part of telephone handheld contains a speaker and a microphone for two-way communication.
|
||||
* HDMI : An audio endpoint device that connects to an audio adapter through a High-Definition Multimedia Interface (HDMI) connector.
|
||||
* HEADPHONES : A set of headphones.
|
||||
* HEADSET : An earphone or a pair of earphones with an attached mouthpiece for two-way communication.
|
||||
* LINE : Line input / Line Output
|
||||
* MICROPHONE : microphone.
|
||||
* NETWORK : An audio endpoint device that the user accesses remotely through a network.
|
||||
* SPDIF : An audio endpoint device that connects to an audio adapter through a Sony/Philips Digital Interface (S/PDIF) connector.
|
||||
* SPEAKERS : A set of speakers.
|
||||
* @return String value or empty string if unknown
|
||||
*/
|
||||
public String Device_Connection_Type() {
|
||||
int val = Flags & bassconstant.BASS_DEVICE_TYPE_MASK;
|
||||
switch(val) {
|
||||
case bassconstant.BASS_DEVICE_TYPE_DIGITAL : return "DIGITAL";
|
||||
case bassconstant.BASS_DEVICE_TYPE_DISPLAYPORT : return "DISPLAYPORT";
|
||||
case bassconstant.BASS_DEVICE_TYPE_HANDSET: return "HANDSET";
|
||||
case bassconstant.BASS_DEVICE_TYPE_HDMI: return "HDMI";
|
||||
case bassconstant.BASS_DEVICE_TYPE_HEADPHONES: return "HEADPHONES";
|
||||
case bassconstant.BASS_DEVICE_TYPE_HEADSET: return "HEADSET";
|
||||
case bassconstant.BASS_DEVICE_TYPE_LINE: return "LINE";
|
||||
case bassconstant.BASS_DEVICE_TYPE_MICROPHONE: return "MICROPHONE";
|
||||
case bassconstant.BASS_DEVICE_TYPE_NETWORK: return "NETWORK";
|
||||
case bassconstant.BASS_DEVICE_TYPE_SPDIF: return "SPDIF";
|
||||
case bassconstant.BASS_DEVICE_TYPE_SPEAKERS: return "SPEAKERS";
|
||||
default : return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
181
src/jbass/Bass_Info.java
Normal file
181
src/jbass/Bass_Info.java
Normal file
@@ -0,0 +1,181 @@
|
||||
package jbass;
|
||||
|
||||
import com.un4seen.bass.BASS.BASS_INFO;
|
||||
import com.un4seen.bass.BASS.bassconstant;
|
||||
|
||||
import anywheresoftware.b4a.BA;
|
||||
|
||||
@BA.ShortName("BASS_DEVICEINFO")
|
||||
/**
|
||||
* Pengganti BASS_INFO dari BASS
|
||||
* http://www.un4seen.com/doc/#bass/BASS_INFO.html
|
||||
* @author rdkartono
|
||||
*
|
||||
*/
|
||||
public class Bass_Info {
|
||||
public final int flags;
|
||||
|
||||
/**
|
||||
* Total Hardware Memory Size
|
||||
*/
|
||||
public final int hwsize;
|
||||
|
||||
/**
|
||||
* Free Hardware Memory Size
|
||||
*/
|
||||
public final int hwfree;
|
||||
|
||||
/**
|
||||
* Free sample slots in hardware
|
||||
*/
|
||||
public final int freesam;
|
||||
|
||||
/**
|
||||
* Free 3D sample slots in hardware
|
||||
*/
|
||||
public final int free3d;
|
||||
|
||||
/**
|
||||
* minimum sample rate supported
|
||||
*/
|
||||
public final int minrate;
|
||||
|
||||
/**
|
||||
* maximum sample rate supported
|
||||
*/
|
||||
public final int maxrate;
|
||||
|
||||
/**
|
||||
* Support EAX ?
|
||||
*/
|
||||
public final boolean eax;
|
||||
|
||||
/**
|
||||
* Minimum Buffer length, in miliseconds
|
||||
*/
|
||||
public final int minbuf;
|
||||
|
||||
/**
|
||||
* DirectSound version
|
||||
*/
|
||||
public final int dsver;
|
||||
|
||||
/**
|
||||
* Average delay for channel playback to start and be heard
|
||||
*/
|
||||
public final int latency;
|
||||
|
||||
/**
|
||||
* Flags parameter of the BASS_Init call
|
||||
*/
|
||||
public final int initflags;
|
||||
|
||||
/**
|
||||
* Number of available speakers
|
||||
*/
|
||||
public final int speakers;
|
||||
|
||||
/**
|
||||
* Device's current output sample rate
|
||||
*/
|
||||
public final int freq;
|
||||
|
||||
/**
|
||||
* If Bass_Info contains valid data
|
||||
*/
|
||||
public final boolean isvalid;
|
||||
|
||||
public Bass_Info(BASS_INFO inf) {
|
||||
if (inf instanceof BASS_INFO) {
|
||||
inf.read();
|
||||
flags = inf.flags;
|
||||
hwsize = inf.hwsize;
|
||||
hwfree = inf.hwfree;
|
||||
freesam = inf.freesam;
|
||||
free3d = inf.free3d;
|
||||
minrate = inf.minrate;
|
||||
maxrate= inf.maxrate;
|
||||
eax = inf.eax == 0 ? false : true;
|
||||
minbuf = inf.minbuf;
|
||||
dsver = inf.dsver;
|
||||
latency = inf.latency;
|
||||
initflags = inf.initflags;
|
||||
speakers = inf.speakers;
|
||||
freq = inf.freq;
|
||||
isvalid = true;
|
||||
} else {
|
||||
flags = 0;
|
||||
hwsize = 0;
|
||||
hwfree = 0;
|
||||
freesam = 0;
|
||||
free3d = 0;
|
||||
minrate = 0;
|
||||
maxrate = 0;
|
||||
eax = false;
|
||||
minbuf = 0;
|
||||
dsver = 0;
|
||||
latency = 0;
|
||||
initflags = 0;
|
||||
speakers = 0;
|
||||
freq = 0;
|
||||
isvalid = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* check if device supports all sample rates between minrate and maxrate.
|
||||
* @return true or false
|
||||
*/
|
||||
public boolean Support_Continuous_Rate() {
|
||||
return (flags & bassconstant.DSCAPS_CONTINUOUSRATE) > 0 ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if device have DirectSound support
|
||||
* @return true or false
|
||||
*/
|
||||
public boolean Support_DirectSound() {
|
||||
return (flags & bassconstant.DSCAPS_EMULDRIVER) > 0 ? false : true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if device is certified by microsoft
|
||||
* @return true or false
|
||||
*/
|
||||
public boolean Certified_by_Microsoft() {
|
||||
return (flags & bassconstant.DSCAPS_CERTIFIED) > 0 ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if device support hardware mixing in mono
|
||||
* @return true or false
|
||||
*/
|
||||
public boolean Support_Mono_Mixing() {
|
||||
return (flags & bassconstant.DSCAPS_SECONDARYMONO) > 0 ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if device support hardware mixing in stereo
|
||||
* @return true or false
|
||||
*/
|
||||
public boolean Support_Stereo_Mixing() {
|
||||
return (flags & bassconstant.DSCAPS_SECONDARYSTEREO) > 0 ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if device support hardware mixing in 8bits
|
||||
* @return true or false
|
||||
*/
|
||||
public boolean Support_8bit_Mixing() {
|
||||
return (flags & bassconstant.DSCAPS_SECONDARY8BIT) > 0 ? true : false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if device support hardware mixing in 16bits
|
||||
* @return true or false
|
||||
*/
|
||||
public boolean Support_16bit_Mixing() {
|
||||
return (flags & bassconstant.DSCAPS_SECONDARY16BIT) > 0 ? true : false;
|
||||
}
|
||||
}
|
||||
174
src/jbass/Bass_RecordInfo.java
Normal file
174
src/jbass/Bass_RecordInfo.java
Normal file
@@ -0,0 +1,174 @@
|
||||
package jbass;
|
||||
import com.un4seen.bass.BASS.BASS_RECORDINFO;
|
||||
import com.un4seen.bass.BASS.bassconstant;
|
||||
|
||||
import anywheresoftware.b4a.BA;
|
||||
|
||||
@BA.ShortName("BASS_RECORDINFO")
|
||||
/**
|
||||
* Pengganti BASS_RECORDINFO
|
||||
* @author rdkartono
|
||||
*
|
||||
*/
|
||||
public class Bass_RecordInfo {
|
||||
|
||||
/**
|
||||
* Device Flags
|
||||
*/
|
||||
public final int flags;
|
||||
|
||||
/**
|
||||
* Formats supported by device
|
||||
*/
|
||||
public final int formats;
|
||||
|
||||
/**
|
||||
* Available input sources
|
||||
*/
|
||||
public final int inputs;
|
||||
|
||||
/**
|
||||
* True = only one input may be active at a time
|
||||
*/
|
||||
public final boolean singlein;
|
||||
|
||||
/***
|
||||
* Device native sample rate
|
||||
*/
|
||||
public final int freq;
|
||||
|
||||
/**
|
||||
* True = have valid data
|
||||
*/
|
||||
public final boolean isvalid;
|
||||
|
||||
public Bass_RecordInfo(BASS_RECORDINFO inf) {
|
||||
if (inf instanceof BASS_RECORDINFO) {
|
||||
inf.read();
|
||||
flags = inf.flags;
|
||||
formats = inf.formats;
|
||||
inputs = inf.inputs;
|
||||
singlein = inf.singlein;
|
||||
freq = inf.freq;
|
||||
isvalid = true;
|
||||
} else {
|
||||
isvalid = false;
|
||||
flags = 0;
|
||||
formats = 0;
|
||||
inputs = 0;
|
||||
singlein = false;
|
||||
freq = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Check if device have DirectSound support
|
||||
* @return true or false
|
||||
*/
|
||||
public boolean Support_DirectSound() {
|
||||
return (flags & bassconstant.DSCAPS_EMULDRIVER) > 0 ? false : true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if device is certified by microsoft
|
||||
* @return true or false
|
||||
*/
|
||||
public boolean Certified_by_Microsoft() {
|
||||
return (flags & bassconstant.DSCAPS_CERTIFIED) > 0 ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if Recorder support this format
|
||||
* @param samplingrate : samplingrate, 11025 , 22050, 44100, 48000, 96000
|
||||
* @param bits : 8 or 16
|
||||
* @param channel : 1 or 2
|
||||
* @return true if support
|
||||
*/
|
||||
public boolean Recorder_SupportFormat(int samplingrate, int bits, int channel) {
|
||||
switch(samplingrate) {
|
||||
case 11025 :
|
||||
if (bits==8) {
|
||||
if (channel==1) {
|
||||
return (flags & bassconstant.WAVE_FORMAT_1M08)>0 ? true : false;
|
||||
} else if (channel==2) {
|
||||
return (flags & bassconstant.WAVE_FORMAT_1S08)>0 ? true : false;
|
||||
}
|
||||
} else if (bits==16) {
|
||||
if (channel==1) {
|
||||
return (flags & bassconstant.WAVE_FORMAT_1M16)>0 ? true : false;
|
||||
} else if (channel==2) {
|
||||
return (flags & bassconstant.WAVE_FORMAT_1S16)>0 ? true : false;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case 22050 :
|
||||
if (bits==8) {
|
||||
if (channel==1) {
|
||||
return (flags & bassconstant.WAVE_FORMAT_2M08)>0 ? true : false;
|
||||
} else if (channel==2) {
|
||||
return (flags & bassconstant.WAVE_FORMAT_2S08)>0 ? true : false;
|
||||
}
|
||||
} else if (bits==16) {
|
||||
if (channel==1) {
|
||||
return (flags & bassconstant.WAVE_FORMAT_2M16)>0 ? true : false;
|
||||
} else if (channel==2) {
|
||||
return (flags & bassconstant.WAVE_FORMAT_2S16)>0 ? true : false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 44100 :
|
||||
|
||||
if (bits==8) {
|
||||
if (channel==1) {
|
||||
return (flags & bassconstant.WAVE_FORMAT_4M08)>0 ? true : false;
|
||||
} else if (channel==2) {
|
||||
return (flags & bassconstant.WAVE_FORMAT_4S08)>0 ? true : false;
|
||||
}
|
||||
} else if (bits==16) {
|
||||
if (channel==1) {
|
||||
return (flags & bassconstant.WAVE_FORMAT_4M16)>0 ? true : false;
|
||||
} else if (channel==2) {
|
||||
return (flags & bassconstant.WAVE_FORMAT_4S16)>0 ? true : false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 48000 :
|
||||
if (bits==8) {
|
||||
if (channel==1) {
|
||||
return (flags & bassconstant.WAVE_FORMAT_48M08)>0 ? true : false;
|
||||
} else if (channel==2) {
|
||||
return (flags & bassconstant.WAVE_FORMAT_48S08)>0 ? true : false;
|
||||
}
|
||||
} else if (bits==16) {
|
||||
if (channel==1) {
|
||||
return (flags & bassconstant.WAVE_FORMAT_48M16)>0 ? true : false;
|
||||
} else if (channel==2) {
|
||||
return (flags & bassconstant.WAVE_FORMAT_48S16)>0 ? true : false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 96000 :
|
||||
if (bits==8) {
|
||||
if (channel==1) {
|
||||
return (flags & bassconstant.WAVE_FORMAT_96M08)>0 ? true : false;
|
||||
} else if (channel==2) {
|
||||
return (flags & bassconstant.WAVE_FORMAT_96S08)>0 ? true : false;
|
||||
}
|
||||
} else if (bits==16) {
|
||||
if (channel==1) {
|
||||
return (flags & bassconstant.WAVE_FORMAT_96M16)>0 ? true : false;
|
||||
} else if (channel==2) {
|
||||
return (flags & bassconstant.WAVE_FORMAT_96S16)>0 ? true : false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
252
src/jbass/HandleMP3Writer.java
Normal file
252
src/jbass/HandleMP3Writer.java
Normal file
@@ -0,0 +1,252 @@
|
||||
package jbass;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
|
||||
import com.sun.jna.Pointer;
|
||||
import com.un4seen.bass.BASS;
|
||||
import com.un4seen.bass.BASSenc;
|
||||
import com.un4seen.bass.BASSenc.ENCODEPROCEX;
|
||||
import com.un4seen.bass.BASSenc_MP3;
|
||||
|
||||
import anywheresoftware.b4a.BA;
|
||||
|
||||
@BA.ShortName("HandleMP3Writer")
|
||||
@BA.Events(values= {
|
||||
"log(msg as string)",
|
||||
"mp3bytes(sourcehandle as int, bb() as byte)",
|
||||
"localrecordstart(namafile as string, success as boolean)",
|
||||
"localrecordstop(namafile as string)"
|
||||
})
|
||||
public class HandleMP3Writer implements HandleWriter {
|
||||
private String event = "";
|
||||
private BA ba;
|
||||
private int MP3Size=0; // size of written MP3 bytes
|
||||
private boolean inited = false;
|
||||
private String NamaFile = "";
|
||||
private boolean need_log_event = false;
|
||||
private boolean need_mp3bytes_event = false;
|
||||
private boolean need_localrecordstart_event = false;
|
||||
private boolean need_localrecordstop_event = false;
|
||||
|
||||
private BASS bass;
|
||||
private BASSenc bassenc;
|
||||
private BASSenc_MP3 bassencmp3;
|
||||
private ENCODEPROCEX theproc;
|
||||
private int encoder_handle = 0;
|
||||
private RandomAccessFile raf ;
|
||||
private HandleMp3WriterEvent ev;
|
||||
private Object myobject;
|
||||
|
||||
/**
|
||||
* Initialize MP3 Writer for B4A and B4J
|
||||
* @param eventname : event name
|
||||
*/
|
||||
public void Initialize(BA bax, String eventname) {
|
||||
ba = bax;
|
||||
event = eventname;
|
||||
inited = false;
|
||||
myobject = this;
|
||||
if (ba!=null) {
|
||||
if (!event.isEmpty()) {
|
||||
need_log_event = ba.subExists(event+"_log");
|
||||
need_mp3bytes_event = ba.subExists(event+"_mp3bytes");
|
||||
need_localrecordstart_event = ba.subExists(event+"_localrecordstart");
|
||||
need_localrecordstop_event = ba.subExists(event+"_localrecordstop");
|
||||
}
|
||||
}
|
||||
|
||||
bass = new BASS();
|
||||
bassenc = new BASSenc();
|
||||
bassencmp3 = new BASSenc_MP3();
|
||||
if (bass.BASS_GetVersion()!=0) {
|
||||
if (bassenc.BASS_Encode_GetVersion()!=0) {
|
||||
if (bassencmp3.BASS_Encode_MP3_GetVersion()!=0) {
|
||||
inited = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@BA.Hide
|
||||
public void SetHandleMp3WriterEvent(HandleMp3WriterEvent xx) {
|
||||
ev = xx;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if MP3 Writer is initialized
|
||||
* @return true if initialized
|
||||
*/
|
||||
public boolean IsInitialized() {
|
||||
return inited;
|
||||
}
|
||||
|
||||
private ENCODEPROCEX create_encodeproc() {
|
||||
ENCODEPROCEX newproc = new ENCODEPROCEX() {
|
||||
|
||||
@Override
|
||||
public void ENCODEPROCEX(int handle, int channel, Pointer buffer, int length, long offset, Pointer user) {
|
||||
|
||||
if (handle==0) return;
|
||||
if (channel==0) return;
|
||||
if (buffer==null) return;
|
||||
if (length<1) return;
|
||||
|
||||
byte[] bb = buffer.getByteArray(0, length);
|
||||
raise_mp3bytes_event(channel, bb);
|
||||
if (raf!=null) {
|
||||
// ketika pertama dipanggil, offset = 0 (MP3Size masih 0)
|
||||
// ketika terakhir dipanggil, offset juga = 0 , tapi MP3Size mestinya sudah lebih dari 1
|
||||
// pakai ini untuk close randomaccessfile
|
||||
|
||||
try {
|
||||
raf.seek(offset);
|
||||
raf.write(bb);
|
||||
if (offset==0) {
|
||||
if (MP3Size>0) {
|
||||
Close();
|
||||
raise_localrecordstop_event(channel, NamaFile);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
raise_log_event("Failed to Write , Msg="+e.getMessage()+", Caused="+e.getCause());
|
||||
// ada error writing , close ajah
|
||||
Close();
|
||||
raise_localrecordstop_event(channel, NamaFile);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
// update nilai MP3Size di sini. Jangan dipindah pindah !!
|
||||
MP3Size+=length;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
return newproc;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Close RandomAccessFile
|
||||
*/
|
||||
private void Close() {
|
||||
if (raf!=null) {
|
||||
try {
|
||||
raf.close();
|
||||
raise_log_event("File "+NamaFile+" is closed with Size="+getSize());
|
||||
} catch (IOException e) {
|
||||
raise_log_event("Close RandomAccessFile failed, code="+e.getMessage());
|
||||
}
|
||||
raf = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Record from Other Bass Handle to file
|
||||
* File will be auto written by source handle
|
||||
* If source handle is closed, then MP3 Writer will also auto closed
|
||||
* @param recordfile : target MP3 filename. If empty string, no file will be created
|
||||
* @param handle : other handle
|
||||
* @return true if success
|
||||
*/
|
||||
public boolean CreateFile_from_Handle(String recordfile, int handle) {
|
||||
if (inited) {
|
||||
int flag = bassenc.BASS_ENCODE_AUTOFREE;
|
||||
MP3Size = 0;
|
||||
NamaFile = "";
|
||||
// -m m = mode mono
|
||||
// --preset cd = sama dengan -b 192 = bitrate 192
|
||||
// --preset studio = sama dengan -b 256 = bitrate 256
|
||||
// --preset standard = sama dengan -V 2 = VBR encoding, average bitrate 190
|
||||
// --preset extreme = sama dengan -V 0 = VBR encoding, best quality, average bitrate 245
|
||||
// --preset insane = sama dengan -b 320 = bitrate 320
|
||||
// -B [number] = ABR / VBR , max bitrate is [number]
|
||||
// --abr [number] = average bitrate, bitrate is [number]
|
||||
// -q [number, 0 - 9] = 0 is best quality, 9 = worst quality
|
||||
|
||||
String options = "-m m -q 0 -V 0";
|
||||
theproc = create_encodeproc();
|
||||
encoder_handle = bassencmp3.BASS_Encode_MP3_Start(handle, options, flag, theproc, null);
|
||||
if (encoder_handle==0) {
|
||||
// failed to open encoder
|
||||
raise_localrecordstart_event(handle, recordfile, false);
|
||||
raise_log_event("CreateFile_From_Handle::Encode_MP3_Start failed, code="+bass.GetBassErrorString());
|
||||
return false;
|
||||
} else {
|
||||
// success to open encoder
|
||||
if (recordfile!="") {
|
||||
// ada request untuk recording ke file
|
||||
try {
|
||||
raf = new RandomAccessFile(recordfile,"rw");
|
||||
NamaFile = recordfile;
|
||||
raise_log_event("CreateFile_From_Handle success, target="+NamaFile);
|
||||
raise_localrecordstart_event(handle, recordfile, true);
|
||||
return true;
|
||||
} catch (FileNotFoundException e) {
|
||||
raise_localrecordstart_event(handle, recordfile,false);
|
||||
raise_log_event("CreateFile_From_Handle::RandomAccessFile failed, code="+e.getMessage());
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// tidak ada request untuk recording ke file
|
||||
// jadi Class ini cuma dipakai untuk encoding, dapetin mp3 bytes nya aja
|
||||
Close(); // cuma mau nge-null-in raf , kalau sampai kebuka
|
||||
return true; // tetep return true.
|
||||
}
|
||||
|
||||
}
|
||||
} else return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Current Opened Filename
|
||||
* @return empty string if no file is opened
|
||||
*/
|
||||
public String getCurrentFileName() {
|
||||
return NamaFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get MP3 Size, obtained from EncodeProcEx
|
||||
* @return value in int
|
||||
*/
|
||||
public int getSize() {
|
||||
return MP3Size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if MP3 file is opened and ready to write
|
||||
* @return true if opened
|
||||
*/
|
||||
public boolean FileIsOpened() {
|
||||
return (encoder_handle != 0) ? true : false;
|
||||
}
|
||||
|
||||
private void raise_log_event(String value) {
|
||||
if (need_log_event) ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_log", false, new Object[] {value});
|
||||
if (ev!=null) ev.mp3writer_log(value);
|
||||
}
|
||||
|
||||
private void raise_mp3bytes_event(int sourcehandle, byte[] value) {
|
||||
if (need_mp3bytes_event) ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_mp3bytes", false, new Object[] {sourcehandle, value});
|
||||
if (ev!=null) ev.mp3writer_mp3bytes(sourcehandle, value);
|
||||
}
|
||||
|
||||
private void raise_localrecordstart_event(int sourcehandle, String namafile, boolean success) {
|
||||
if (need_localrecordstart_event) ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_localrecordstart", false, new Object[] {namafile, success});
|
||||
if (ev!=null) ev.localrecordstart(sourcehandle, namafile, success);
|
||||
}
|
||||
|
||||
private void raise_localrecordstop_event(int sourcehandle, String namafile) {
|
||||
if (need_localrecordstop_event) ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_localrecordstop", false, new Object[] {namafile});
|
||||
if (ev!=null) ev.localrecordstop(sourcehandle, namafile);
|
||||
}
|
||||
}
|
||||
8
src/jbass/HandleMp3WriterEvent.java
Normal file
8
src/jbass/HandleMp3WriterEvent.java
Normal file
@@ -0,0 +1,8 @@
|
||||
package jbass;
|
||||
|
||||
public interface HandleMp3WriterEvent {
|
||||
void mp3writer_log(String msg);
|
||||
void mp3writer_mp3bytes(int sourcehandle, byte[] bb);
|
||||
void localrecordstart(int sourcehandle, String namafile, boolean success);
|
||||
void localrecordstop(int sourcehandle, String namafile);
|
||||
}
|
||||
156
src/jbass/HandleWavWriter.java
Normal file
156
src/jbass/HandleWavWriter.java
Normal file
@@ -0,0 +1,156 @@
|
||||
package jbass;
|
||||
import com.sun.jna.Pointer;
|
||||
import com.un4seen.bass.BASS;
|
||||
import com.un4seen.bass.BASSenc;
|
||||
import com.un4seen.bass.BASSenc.ENCODEPROC;
|
||||
|
||||
import anywheresoftware.b4a.BA;
|
||||
|
||||
@BA.ShortName("HandleWavWriter")
|
||||
@BA.Events(values= {
|
||||
"log(msg as string)",
|
||||
"pcmbytes(bb() as byte)"
|
||||
})
|
||||
public class HandleWavWriter implements HandleWriter {
|
||||
private String event = "";
|
||||
private BA ba;
|
||||
private int PCMSize=0; // size of written PCM bytes
|
||||
private boolean inited = false;
|
||||
private String NamaFile = "";
|
||||
private boolean need_log_event = false;
|
||||
private boolean need_pcmbytes_event = false;
|
||||
|
||||
private BASS bass;
|
||||
private BASSenc bassenc;
|
||||
private ENCODEPROC theproc;
|
||||
private int encoder_handle = 0;
|
||||
private Object myobject;
|
||||
|
||||
/**
|
||||
* Initialize WavWriter for B4A and B4J
|
||||
* @param eventname : event name
|
||||
*/
|
||||
public void Initialize(BA bax, String eventname) {
|
||||
NamaFile = "";
|
||||
inited = false;
|
||||
ba = bax;
|
||||
event = eventname;
|
||||
myobject = this;
|
||||
if (ba!=null) {
|
||||
if (!event.isEmpty()) {
|
||||
need_log_event = ba.subExists(event+"_log");
|
||||
need_pcmbytes_event = ba.subExists(event+"_pcmbytes");
|
||||
}
|
||||
}
|
||||
|
||||
bass = new BASS();
|
||||
bassenc = new BASSenc();
|
||||
if (bass.BASS_GetVersion()!=0) {
|
||||
if (bassenc.BASS_Encode_GetVersion()!=0) {
|
||||
inited = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if WavWriter is initialized
|
||||
* @return true if initialized
|
||||
*/
|
||||
public boolean IsInitialized() {
|
||||
return inited;
|
||||
}
|
||||
|
||||
private ENCODEPROC create_encodeproc() {
|
||||
ENCODEPROC newproc = new ENCODEPROC() {
|
||||
|
||||
@Override
|
||||
public void ENCODEPROC(int handle, int channel, Pointer buffer, int length, Pointer user) {
|
||||
if (handle==0) return;
|
||||
if (channel==0) return;
|
||||
if (buffer==null) return;
|
||||
if (length<1) return;
|
||||
PCMSize+=length;
|
||||
raise_pcmbytes_event(buffer.getByteArray(0, length));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
return newproc;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Record from Other Bass Handle to file
|
||||
* File will be auto written by source handle
|
||||
* If source handle is closed, then WavWriter will also auto closed
|
||||
* @param recordfile : target WAV filename, if empty string, no file will be created
|
||||
* @param handle : other handle
|
||||
* @return true if success
|
||||
*/
|
||||
public boolean CreateFile_from_Handle(String recordfile, int handle) {
|
||||
if (inited) {
|
||||
int flag = bassenc.BASS_ENCODE_PCM | bassenc.BASS_ENCODE_AUTOFREE;
|
||||
PCMSize = 0;
|
||||
theproc = create_encodeproc();
|
||||
encoder_handle = bassenc.BASS_Encode_Start(handle, recordfile, flag, theproc, null);
|
||||
if (encoder_handle==0) {
|
||||
raise_log_event("CreateFile_From_Handle::Encode_Start failed, code="+bass.GetBassErrorString());
|
||||
return false;
|
||||
} else {
|
||||
NamaFile = recordfile;
|
||||
raise_log_event("CreateFile_From_Handle success, target="+NamaFile);
|
||||
return true;
|
||||
}
|
||||
} else return false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Current Opened Filename
|
||||
* @return empty string if no file is opened
|
||||
*/
|
||||
public String getCurrentFileName() {
|
||||
return NamaFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get PCM Size, obtained from ENCPROC
|
||||
* @return value in int
|
||||
*/
|
||||
public int getSize() {
|
||||
return PCMSize;
|
||||
}
|
||||
|
||||
// auto close by handler
|
||||
// /**
|
||||
// * Close Wav Writer
|
||||
// */
|
||||
// public void Close_Wav() {
|
||||
//
|
||||
// if (encoder_handle!=0) {
|
||||
// if (!bassenc.BASS_Encode_Stop(encoder_handle)) {
|
||||
// raise_log_event("Close_Wav::Encode_Stop failed, code="+bass.GetBassErrorString());
|
||||
// }
|
||||
// encoder_handle = 0;
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
/**
|
||||
* Check if WAV file is opened and ready to write
|
||||
* @return true if opened
|
||||
*/
|
||||
public boolean FileIsOpened() {
|
||||
return (encoder_handle != 0) ? true : false;
|
||||
}
|
||||
|
||||
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_pcmbytes_event(byte[] bb) {
|
||||
if (need_pcmbytes_event) ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_pcmbytes", false, new Object[] {bb});
|
||||
}
|
||||
}
|
||||
17
src/jbass/HandleWriter.java
Normal file
17
src/jbass/HandleWriter.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package jbass;
|
||||
|
||||
import anywheresoftware.b4a.BA;
|
||||
|
||||
/**
|
||||
* Basic functions that all HandleWriter should have
|
||||
* @author rdkartono
|
||||
*
|
||||
*/
|
||||
public interface HandleWriter {
|
||||
public void Initialize(BA bax, String eventname);
|
||||
public boolean IsInitialized();
|
||||
public boolean CreateFile_from_Handle(String recordfile, int handle);
|
||||
public String getCurrentFileName() ;
|
||||
public int getSize();
|
||||
public boolean FileIsOpened() ;
|
||||
}
|
||||
2731
src/jbass/JBass.java
Normal file
2731
src/jbass/JBass.java
Normal file
File diff suppressed because it is too large
Load Diff
10
src/jbass/RecordGetInputResult.java
Normal file
10
src/jbass/RecordGetInputResult.java
Normal file
@@ -0,0 +1,10 @@
|
||||
package jbass;
|
||||
|
||||
import anywheresoftware.b4a.BA;
|
||||
|
||||
@BA.ShortName("RecordGetInputResult")
|
||||
public class RecordGetInputResult {
|
||||
public String InputName = "";
|
||||
public float Volume = -1.0f;
|
||||
public boolean IsOn = false;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user