commit 2b1fe05f494c9708c3a4a87faa42c418c46de58e Author: rdkartono Date: Thu Nov 28 13:06:18 2024 +0700 first commit diff --git a/lib/arm64-v8a/libbass.so b/lib/arm64-v8a/libbass.so new file mode 100644 index 0000000..e33e7b7 Binary files /dev/null and b/lib/arm64-v8a/libbass.so differ diff --git a/lib/arm64-v8a/libbass_aac.so b/lib/arm64-v8a/libbass_aac.so new file mode 100644 index 0000000..dd8d7ba Binary files /dev/null and b/lib/arm64-v8a/libbass_aac.so differ diff --git a/lib/arm64-v8a/libbass_ape.so b/lib/arm64-v8a/libbass_ape.so new file mode 100644 index 0000000..367c26b Binary files /dev/null and b/lib/arm64-v8a/libbass_ape.so differ diff --git a/lib/arm64-v8a/libbass_fx.so b/lib/arm64-v8a/libbass_fx.so new file mode 100644 index 0000000..181706e Binary files /dev/null and b/lib/arm64-v8a/libbass_fx.so differ diff --git a/lib/arm64-v8a/libbass_mpc.so b/lib/arm64-v8a/libbass_mpc.so new file mode 100644 index 0000000..a4c2783 Binary files /dev/null and b/lib/arm64-v8a/libbass_mpc.so differ diff --git a/lib/arm64-v8a/libbass_ssl.so b/lib/arm64-v8a/libbass_ssl.so new file mode 100644 index 0000000..a422cba Binary files /dev/null and b/lib/arm64-v8a/libbass_ssl.so differ diff --git a/lib/arm64-v8a/libbass_tta.so b/lib/arm64-v8a/libbass_tta.so new file mode 100644 index 0000000..67f42a0 Binary files /dev/null and b/lib/arm64-v8a/libbass_tta.so differ diff --git a/lib/arm64-v8a/libbassalac.so b/lib/arm64-v8a/libbassalac.so new file mode 100644 index 0000000..e44accc Binary files /dev/null and b/lib/arm64-v8a/libbassalac.so differ diff --git a/lib/arm64-v8a/libbassape.so b/lib/arm64-v8a/libbassape.so new file mode 100644 index 0000000..4a98008 Binary files /dev/null and b/lib/arm64-v8a/libbassape.so differ diff --git a/lib/arm64-v8a/libbassdsd.so b/lib/arm64-v8a/libbassdsd.so new file mode 100644 index 0000000..c5f8317 Binary files /dev/null and b/lib/arm64-v8a/libbassdsd.so differ diff --git a/lib/arm64-v8a/libbassenc.so b/lib/arm64-v8a/libbassenc.so new file mode 100644 index 0000000..35a5633 Binary files /dev/null and b/lib/arm64-v8a/libbassenc.so differ diff --git a/lib/arm64-v8a/libbassenc_flac.so b/lib/arm64-v8a/libbassenc_flac.so new file mode 100644 index 0000000..b374cf1 Binary files /dev/null and b/lib/arm64-v8a/libbassenc_flac.so differ diff --git a/lib/arm64-v8a/libbassenc_mp3.so b/lib/arm64-v8a/libbassenc_mp3.so new file mode 100644 index 0000000..45f4cf6 Binary files /dev/null and b/lib/arm64-v8a/libbassenc_mp3.so differ diff --git a/lib/arm64-v8a/libbassenc_ogg.so b/lib/arm64-v8a/libbassenc_ogg.so new file mode 100644 index 0000000..6e69f2f Binary files /dev/null and b/lib/arm64-v8a/libbassenc_ogg.so differ diff --git a/lib/arm64-v8a/libbassenc_opus.so b/lib/arm64-v8a/libbassenc_opus.so new file mode 100644 index 0000000..ccc7a30 Binary files /dev/null and b/lib/arm64-v8a/libbassenc_opus.so differ diff --git a/lib/arm64-v8a/libbassflac.so b/lib/arm64-v8a/libbassflac.so new file mode 100644 index 0000000..360da28 Binary files /dev/null and b/lib/arm64-v8a/libbassflac.so differ diff --git a/lib/arm64-v8a/libbasshls.so b/lib/arm64-v8a/libbasshls.so new file mode 100644 index 0000000..3fb6df1 Binary files /dev/null and b/lib/arm64-v8a/libbasshls.so differ diff --git a/lib/arm64-v8a/libbassmidi.so b/lib/arm64-v8a/libbassmidi.so new file mode 100644 index 0000000..fe8b57b Binary files /dev/null and b/lib/arm64-v8a/libbassmidi.so differ diff --git a/lib/arm64-v8a/libbassmix.so b/lib/arm64-v8a/libbassmix.so new file mode 100644 index 0000000..cae720c Binary files /dev/null and b/lib/arm64-v8a/libbassmix.so differ diff --git a/lib/arm64-v8a/libbassopus.so b/lib/arm64-v8a/libbassopus.so new file mode 100644 index 0000000..68e7d55 Binary files /dev/null and b/lib/arm64-v8a/libbassopus.so differ diff --git a/lib/arm64-v8a/libbasswebm.so b/lib/arm64-v8a/libbasswebm.so new file mode 100644 index 0000000..328afbe Binary files /dev/null and b/lib/arm64-v8a/libbasswebm.so differ diff --git a/lib/arm64-v8a/libbasswv.so b/lib/arm64-v8a/libbasswv.so new file mode 100644 index 0000000..9c6fdfd Binary files /dev/null and b/lib/arm64-v8a/libbasswv.so differ diff --git a/lib/arm64-v8a/libtags.so b/lib/arm64-v8a/libtags.so new file mode 100644 index 0000000..0f99eb2 Binary files /dev/null and b/lib/arm64-v8a/libtags.so differ diff --git a/lib/armeabi-v7a/libbass.so b/lib/armeabi-v7a/libbass.so new file mode 100644 index 0000000..cd1734f Binary files /dev/null and b/lib/armeabi-v7a/libbass.so differ diff --git a/lib/armeabi-v7a/libbass_aac.so b/lib/armeabi-v7a/libbass_aac.so new file mode 100644 index 0000000..7ba8ed5 Binary files /dev/null and b/lib/armeabi-v7a/libbass_aac.so differ diff --git a/lib/armeabi-v7a/libbass_ape.so b/lib/armeabi-v7a/libbass_ape.so new file mode 100644 index 0000000..11ee184 Binary files /dev/null and b/lib/armeabi-v7a/libbass_ape.so differ diff --git a/lib/armeabi-v7a/libbass_fx.so b/lib/armeabi-v7a/libbass_fx.so new file mode 100644 index 0000000..aad0e9e Binary files /dev/null and b/lib/armeabi-v7a/libbass_fx.so differ diff --git a/lib/armeabi-v7a/libbass_mpc.so b/lib/armeabi-v7a/libbass_mpc.so new file mode 100644 index 0000000..bbb508b Binary files /dev/null and b/lib/armeabi-v7a/libbass_mpc.so differ diff --git a/lib/armeabi-v7a/libbass_ssl.so b/lib/armeabi-v7a/libbass_ssl.so new file mode 100644 index 0000000..be385e0 Binary files /dev/null and b/lib/armeabi-v7a/libbass_ssl.so differ diff --git a/lib/armeabi-v7a/libbass_tta.so b/lib/armeabi-v7a/libbass_tta.so new file mode 100644 index 0000000..fcb59e0 Binary files /dev/null and b/lib/armeabi-v7a/libbass_tta.so differ diff --git a/lib/armeabi-v7a/libbassalac.so b/lib/armeabi-v7a/libbassalac.so new file mode 100644 index 0000000..50f1ea8 Binary files /dev/null and b/lib/armeabi-v7a/libbassalac.so differ diff --git a/lib/armeabi-v7a/libbassape.so b/lib/armeabi-v7a/libbassape.so new file mode 100644 index 0000000..e115466 Binary files /dev/null and b/lib/armeabi-v7a/libbassape.so differ diff --git a/lib/armeabi-v7a/libbassdsd.so b/lib/armeabi-v7a/libbassdsd.so new file mode 100644 index 0000000..99961a4 Binary files /dev/null and b/lib/armeabi-v7a/libbassdsd.so differ diff --git a/lib/armeabi-v7a/libbassenc.so b/lib/armeabi-v7a/libbassenc.so new file mode 100644 index 0000000..928afe2 Binary files /dev/null and b/lib/armeabi-v7a/libbassenc.so differ diff --git a/lib/armeabi-v7a/libbassenc_flac.so b/lib/armeabi-v7a/libbassenc_flac.so new file mode 100644 index 0000000..52cd9af Binary files /dev/null and b/lib/armeabi-v7a/libbassenc_flac.so differ diff --git a/lib/armeabi-v7a/libbassenc_mp3.so b/lib/armeabi-v7a/libbassenc_mp3.so new file mode 100644 index 0000000..8d30ce8 Binary files /dev/null and b/lib/armeabi-v7a/libbassenc_mp3.so differ diff --git a/lib/armeabi-v7a/libbassenc_ogg.so b/lib/armeabi-v7a/libbassenc_ogg.so new file mode 100644 index 0000000..7b9ece5 Binary files /dev/null and b/lib/armeabi-v7a/libbassenc_ogg.so differ diff --git a/lib/armeabi-v7a/libbassenc_opus.so b/lib/armeabi-v7a/libbassenc_opus.so new file mode 100644 index 0000000..2ed863d Binary files /dev/null and b/lib/armeabi-v7a/libbassenc_opus.so differ diff --git a/lib/armeabi-v7a/libbassflac.so b/lib/armeabi-v7a/libbassflac.so new file mode 100644 index 0000000..e95459b Binary files /dev/null and b/lib/armeabi-v7a/libbassflac.so differ diff --git a/lib/armeabi-v7a/libbasshls.so b/lib/armeabi-v7a/libbasshls.so new file mode 100644 index 0000000..fef5371 Binary files /dev/null and b/lib/armeabi-v7a/libbasshls.so differ diff --git a/lib/armeabi-v7a/libbassmidi.so b/lib/armeabi-v7a/libbassmidi.so new file mode 100644 index 0000000..f8ccfb5 Binary files /dev/null and b/lib/armeabi-v7a/libbassmidi.so differ diff --git a/lib/armeabi-v7a/libbassmix.so b/lib/armeabi-v7a/libbassmix.so new file mode 100644 index 0000000..28733a0 Binary files /dev/null and b/lib/armeabi-v7a/libbassmix.so differ diff --git a/lib/armeabi-v7a/libbassopus.so b/lib/armeabi-v7a/libbassopus.so new file mode 100644 index 0000000..b0c2965 Binary files /dev/null and b/lib/armeabi-v7a/libbassopus.so differ diff --git a/lib/armeabi-v7a/libbasswebm.so b/lib/armeabi-v7a/libbasswebm.so new file mode 100644 index 0000000..512b67b Binary files /dev/null and b/lib/armeabi-v7a/libbasswebm.so differ diff --git a/lib/armeabi-v7a/libbasswv.so b/lib/armeabi-v7a/libbasswv.so new file mode 100644 index 0000000..5a4068b Binary files /dev/null and b/lib/armeabi-v7a/libbasswv.so differ diff --git a/lib/armeabi-v7a/libtags.so b/lib/armeabi-v7a/libtags.so new file mode 100644 index 0000000..689918b Binary files /dev/null and b/lib/armeabi-v7a/libtags.so differ diff --git a/src/AndroidRelated/AndroidMic.java b/src/AndroidRelated/AndroidMic.java new file mode 100644 index 0000000..5686bd4 --- /dev/null +++ b/src/AndroidRelated/AndroidMic.java @@ -0,0 +1,1408 @@ +package AndroidRelated; + +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.BASS_INFO; +import com.un4seen.bass.BASS.BASS_RECORDINFO; +import com.un4seen.bass.BASS.IDSPPROC; +import com.un4seen.bass.BASS.SYNCPROC; +import com.un4seen.bass.BASS.bassconstant; + +import MastersenderRelated.MasterSender; +import anywheresoftware.b4a.BA; +import anywheresoftware.b4a.keywords.DateTime; +import anywheresoftware.b4a.objects.LabelWrapper; +import anywheresoftware.b4a.objects.ProgressBarWrapper; +import anywheresoftware.b4a.objects.collections.List; +import anywheresoftware.b4a.objects.collections.Map; +import jbass.Bass_DeviceInfo; +import jbass.Bass_Info; +import jbass.Bass_RecordInfo; + + +@BA.ShortName("AndroidMic") +@BA.Events(values = { + "log(msg as string)", + "recordingstatus(rechandle as int, started as boolean)", + "recordingbytes(rechandle as int, bb() as byte, length as int)", + "recordingvu(rechandle as int, value as int)", + "recordingpcmcounter(rechandle as int, value as long)", + "openedfileinformation(playhandle as int, filepath as string, filesize as long, duration as double)", + "playbackstatus(playhandle as int, filepath as string, started as boolean)", + "playbackbytes(playhandle as int, bb() as byte, length as int)", + "playbackvu(playhandle as int, value as int)", + "playbackpcmcounter(playhandle as int, value as long)" + }) + +@BA.Permissions(values = { "android.permission.WRITE_EXTERNAL_STORAGE", "android.permission.RECORD_AUDIO" }) + +/** + * Android Microphone Class + * + * @author rdkartono + * + */ +public class AndroidMic { + private BA bax; + private Object caller; + private String event; + private boolean need_log_event = false; + private boolean need_recordingstatus_event = false; + private boolean need_recordingbytes_event = false; + private boolean need_recordingvu_event = false; + private boolean need_recordingpcmcounter_event = false; + private boolean need_playbackstatus_event = false; + private boolean need_playbackbytes_event = false; + private boolean need_playbackvu_event = false; + private boolean need_playbackpcmcounter_event = false; + private boolean need_openedfileinformation_event = false; + + private boolean bass_inited = false; + private boolean record_inited = false; + private boolean playback_inited = false; + private int playback_deviceid = 1; + private BASS bass = new BASS(); + + private boolean record_started = false; // master record indicator, one to rule all + private boolean playback_started = false; // master playback indicator, one to rule all + + private Map recordhandlemap = new Map(); + private Map playbackhandlemap = new Map(); + private int playbacksamplingrate = 44100; + private AudioFileInformationV2 chimeup = null; + private AudioFileInformationV2 chimedown = null; + + /** + * Initialize Android Microphone Class + * + * @param callerobject : caller object + * @param eventname : event name + */ + public void Initialize(BA ba, Object callerobject, String eventname) { + bax = ba; + caller = callerobject; + event = eventname; + if (bax instanceof BA) { + if (caller != null) { + if (event instanceof String) { + if (!event.isEmpty()) { + need_log_event = bax.subExists(event + "_log"); + need_recordingstatus_event = bax.subExists(event + "_recordingstatus"); + need_recordingbytes_event = bax.subExists(event + "_recordingbytes"); + need_recordingvu_event = bax.subExists(event + "_recordingvu"); + need_recordingpcmcounter_event = bax.subExists(event + "_recordingpcmcounter"); + need_playbackstatus_event = bax.subExists(event+"_playbackstatus"); + need_playbackbytes_event = bax.subExists(event+"_playbackbytes"); + need_playbackvu_event = bax.subExists(event+"_playbackvu"); + need_playbackpcmcounter_event = bax.subExists(event+"_playbackpcmcounter"); + need_openedfileinformation_event = bax.subExists(event+"_openedfileinformation"); + } + } + } + } + + recordhandlemap.Initialize(); + playbackhandlemap.Initialize(); + + if (bass.BASS_GetVersion() != 0) { + bass_inited = true; + record_inited = recordinit_0(); + int playback_init_flag = bassconstant.BASS_DEVICE_16BITS; + playback_inited = playbackinit(1,playbacksamplingrate, playback_init_flag); + + if (record_inited) { + raise_log("Recorder dev=0 ready"); + + // try to get names + int rec_input = -1; + while(true) { + String inputname = bass.BASS_RecordGetInputName(rec_input); + if (inputname instanceof String) { + raise_log("Recorder Input: "+rec_input+", Name: "+inputname); + rec_input++; + } else { + raise_log("Unable to RecordGetInputName Input: "+rec_input+", Msg: "+bass.GetBassErrorString()); + break; + } + } + } else { + raise_log("Recorder dev=0 not ready"); + } + + if (playback_inited) { + raise_log("Playback dev=1 ready"); + } else { + raise_log("Playback dev=1 not ready"); + } + } + + // auto close kalau java program closed + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + if (record_started) { + CloseAllMicrophone(); + } + + if (record_inited) + bass.BASS_RecordFree(); + + if (playback_started) { + CloseAllPlayback(); + } + if (playback_inited) + bass.BASS_Free(); + + } + }); + } + + /** + * Check if Android Microphone class is inited + * + * @return true if inited + */ + public boolean IsInitialized() { + return bass_inited; + } + + /** + * Load Chime Up + * isvalid() : check if audio is able to load + * @param path : valid path of chimeup file + * @return AudioFileInformationV2 object + */ + public AudioFileInformationV2 LoadChimeUp(String path) { + chimeup = LoadAudioFile(path); + return chimeup; + } + + /** + * Load Chime Down + * isvalid : check if audio is able to load + * @param path : valid path of chimedown file + * @return AudioFileInformationV2 object + */ + public AudioFileInformationV2 LoadChimeDown(String path) { + chimedown = LoadAudioFile(path); + return chimedown; + } + + /** + * Load audiofile + * isvalid : check if audio file is able to load + * LoadChimeUP and LoadChimeDown use this function internally + * @param path : valid path of audio file + * @return AudioFileInformationV2 object + */ + public AudioFileInformationV2 LoadAudioFile(String path) { + return bass_inited ? new AudioFileInformationV2(bass, path, playbacksamplingrate) : null; + } + + + //////////////// RECORDER SECTION //////////////////////////////////// + + /** + * Initialize recording at device 0 + * + * @return true if succes + */ + private boolean recordinit_0() { + if (bass_inited) { + if (bass.BASS_RecordInit(0)) { + return true; // berhasil init + } else { + int errcode = bass.BASS_ErrorGetCode(); + if (errcode == bassconstant.BASS_ERROR_ALREADY) { + return true; // sudah init + } else { + raise_log("RecordInit dev=0 fail, Msg : " + bass.GetBassErrorString()); + } + } + } + return false; + } + + /** + * Get Recorder Device Info for Android, always at device ID 0 + * + * @return Bass_DeviceInfo + */ + public Bass_DeviceInfo GetRecorderDeviceInfo() { + BASS_DEVICEINFO dev = new BASS_DEVICEINFO(); + if (bass.BASS_RecordGetDeviceInfo(0, dev)) { + dev.read(); + return new Bass_DeviceInfo(0, dev); + } else { + return new Bass_DeviceInfo(0, null); + } + } + + /** + * Get Bass_RecordInfo from Recorder Device + * + * @return Bass_RecordInfo + */ + public Bass_RecordInfo GetRecorderInfo() { + BASS_RECORDINFO inf = new BASS_RECORDINFO(); + if (bass.BASS_RecordGetInfo(inf)) { + inf.read(); + return new Bass_RecordInfo(inf); + } else + return new Bass_RecordInfo(null); + } + + /** + * Set ProgressBar for VU meter display + * + * @param handle : record handle to display + * @param pb : progressbar to display + */ + public boolean setRecordVUMeter(int handle, ProgressBarWrapper pb) { + if (recordhandlemap instanceof Map) { + if (recordhandlemap.IsInitialized()) { + synchronized (recordhandlemap) { + if (recordhandlemap.ContainsKey(handle)) { + Object rr = recordhandlemap.Get(handle); + if (rr instanceof rec_job) { + rec_job rj = (rec_job) rr; + rj.setProgressBar(pb); + return true; + } + } + } + } + } + return false; + } + + /** + * Get ProgressBar for VU meter display + * + * @param handle : record handle to display + * @return null if not exist + */ + public ProgressBarWrapper getRecordVUMeter(int handle) { + if (recordhandlemap instanceof Map) { + if (recordhandlemap.IsInitialized()) { + synchronized (recordhandlemap) { + if (recordhandlemap.ContainsKey(handle)) { + Object rr = recordhandlemap.Get(handle); + if (rr instanceof rec_job) { + rec_job rj = (rec_job) rr; + return rj.getProgressBar(); + } + } + } + + } + } + return null; + } + + /** + * Set Label for Record VU level will display VU= [0 - 100] + * + * @param handle record handle to display + * @param lbl label to display + * @return true if success + */ + public boolean setRecordVULabel(int handle, LabelWrapper lbl) { + if (recordhandlemap instanceof Map) { + if (recordhandlemap.IsInitialized()) { + synchronized (recordhandlemap) { + if (recordhandlemap.ContainsKey(handle)) { + Object rr = recordhandlemap.Get(handle); + if (rr instanceof rec_job) { + rec_job rj = (rec_job) rr; + rj.setVULabel(lbl); + return true; + } + } + } + } + } + return false; + } + + /** + * Get Label assigned for Record VU Level + * + * @param handle record handle to display + * @return null if not exist + */ + public LabelWrapper getRecordVULabel(int handle) { + if (recordhandlemap instanceof Map) { + if (recordhandlemap.IsInitialized()) { + synchronized (recordhandlemap) { + if (recordhandlemap.ContainsKey(handle)) { + Object rr = recordhandlemap.Get(handle); + if (rr instanceof rec_job) { + rec_job rj = (rec_job) rr; + return rj.getVULabel(); + } + } + } + } + } + return null; + } + + /** + * Get MasterSender assigned for specific record handle + * @param handle : record handle + * @return null if not found + */ + public MasterSender getRecordMasterNetworkSender(int handle) { + if (recordhandlemap instanceof Map) { + if (recordhandlemap.IsInitialized()) { + synchronized (recordhandlemap) { + if (recordhandlemap.ContainsKey(handle)) { + Object rr = recordhandlemap.Get(handle); + if (rr instanceof rec_job) { + rec_job rj = (rec_job) rr; + return rj.getMasterSender(); + } + } + } + } + } + return null; + } + + /** + * Assign MasterSender to specific record handle + * @param handle : record handle + * @param mastersender : Master Sender + */ + public void setRecordMasterNetworkSender(int handle, MasterSender mastersender) { + if (recordhandlemap instanceof Map) { + if (recordhandlemap.IsInitialized()) { + synchronized (recordhandlemap) { + if (recordhandlemap.ContainsKey(handle)) { + Object rr = recordhandlemap.Get(handle); + if (rr instanceof rec_job) { + rec_job rj = (rec_job) rr; + rj.setMasterSender(mastersender); + } + } + } + } + } + } + + + /** + * Get all recording handles that still opened + * + * @return List of handle (integer) + */ + public List OpenedRecordingMics() { + List result = new List(); + result.Initialize(); + if (recordhandlemap instanceof Map) { + if (recordhandlemap.IsInitialized()) { + synchronized (recordhandlemap) { + if (recordhandlemap.getSize() > 0) { + for (int ii = 0; ii < recordhandlemap.getSize(); ii++) { + Object XX = recordhandlemap.GetKeyAt(ii); + if (XX instanceof Integer) { + int xx = (Integer) XX; + result.Add(xx); + } + + } + } + } + } + } + return result; + } + + /** + * Stop Microphone Recording by specific record handle + * + * @param handle : record handle to stop + * @return true if success + */ + public boolean StopMicrophone_byHandle(int handle) { + if (recordhandlemap instanceof Map) { + if (recordhandlemap.IsInitialized()) { + synchronized (recordhandlemap) { + Object rr = recordhandlemap.Get(handle); + if (rr instanceof rec_job) { + rec_job rj = (rec_job) rr; + rj.StopRecording(); + return true; + } + } + } + } + return false; + } + + + + + /** + * Open Microphone for Recording + * + * @param samplingrate : 11025, 22050, 44100, 48000, or 96000 Hz + * @param bits : 8 or 16 bits + * @param channel : 1 (mono) or 2 (stereo) + * @param maxbytesize : maximum bytes per package to read + * @return true if success + */ + public boolean OpenMicrophone(int samplingrate, int bits, int channel, int maxbytesize) { + int recflag = bits == 8 ? bassconstant.BASS_SAMPLE_8BITS : 0; + recflag |= bassconstant.BASS_RECORD_PAUSE; // start dari pause, ntar harus di channel play + int record_handle = bass.BASS_RecordStart(samplingrate, channel, recflag, null, null); + if (record_handle != 0) { + // try to maximize recording volume + // maximize master record volume + if (!bass.BASS_RecordSetInput(-1, bassconstant.BASS_INPUT_ON, 1.0f)) { + raise_log("Unable to set RecordSetInput Master to 1.0f, Msg : "+bass.GetBassErrorString()); + } else raise_log("Recorder Master Volume set to MAX"); + + SYNCPROC recdevfail = new SYNCPROC() { + + @Override + public void SYNCPROC(int handle, int channel, int data, Pointer user) { + raise_log("Recorder Device Failed on handle="+channel+", Closing all Microphones"); + if (recordhandlemap instanceof Map) { + if (recordhandlemap.IsInitialized()) { + synchronized(recordhandlemap) { + Object xx = recordhandlemap.Get(channel); + if (xx instanceof rec_job) { + rec_job rj = (rec_job) xx; + rj.StopRecording(); + } + } + } + } + } + + }; + + if (bass.BASS_ChannelSetSync(record_handle, (bassconstant.BASS_SYNC_DEV_FAIL | bassconstant.BASS_SYNC_ONETIME), 0, recdevfail, null)==0) { + raise_log("Unable to set ChannelSetSync BASS_SYNC_DEV_FAIL on record_handle="+record_handle+", Msg : "+bass.GetBassErrorString()); + } else raise_log("OpenMicrophone monitoring for BASS_SYNC_DEV_FAIL on record_handl="+record_handle); + + rec_job rr = new rec_job(record_handle, maxbytesize); + + Thread tx = new Thread(rr); + tx.start(); + + if (recordhandlemap instanceof Map) { + if (recordhandlemap.IsInitialized()) { + synchronized(recordhandlemap) { + Object prev = recordhandlemap.Put(record_handle, rr); + raise_log("Add " + record_handle + " to RecordHandleMap"); + if (prev instanceof rec_job) { + rec_job pr = (rec_job) prev; + pr.StopRecording(); + raise_log("Stopping previous RecordJob with same handle"); + } + } + + } + } + + return true; + } else { + raise_log("RecordStart failed, Msg : " + bass.GetBassErrorString()); + return false; + } + + } + + /** + * Close All Microphones from Recording + */ + public void CloseAllMicrophone() { + record_started = false; + } + + private class rec_job implements Runnable { + private final int _maxbytes; + private final int _rechandle; + + private int bytesavailable; + private int bytesread; + private long record_pcm_counter = 0; + + // sebagai penahan supaya label dan progressbar hanya berubah ketika memang ada + // value baru + private int last_vu_value = -1; + private ProgressBarWrapper vu_pb; + private LabelWrapper vu_label; + + + private boolean needtostop = false; + private long maxbytetick; // tick for measure time required until data available as much as _maxbytes + + private MasterSender _msender; // MasterNetworkSender untuk kirim otomatis + + + public void setMasterSender(MasterSender mastersender) { + _msender = mastersender; + } + + public MasterSender getMasterSender() { + return _msender; + } + + public rec_job(int rec_handle, int maxbytes) { + _rechandle = rec_handle; + _maxbytes = maxbytes; + + } + + + + public void setVULabel(LabelWrapper value) { + vu_label = value; + } + + public LabelWrapper getVULabel() { + return vu_label; + } + + + + public ProgressBarWrapper getProgressBar() { + return vu_pb; + } + + public void setProgressBar(ProgressBarWrapper value) { + vu_pb = value; + } + + private void SendToMasterSender(byte[] bb, int length) { + if (_msender instanceof MasterSender) { + if (_msender.IsInitialized()) { + if (_msender.UDP_Clients_exists()) _msender.SendData_to_UDP_Clients(bb, length); + if (_msender.TCP_Clients_exists()) _msender.SendData_to_TCP_Clients(bb, length); + } + } + } + + + public void StopRecording() { + needtostop = true; + } + + private void GetVU(Pointer buf, int length) { + if (buf instanceof Pointer) { + ShortBuffer sb = buf.getByteBuffer(0, length).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer(); + sb.rewind(); + short maxvalue = 0; + short tmpvalue = 0; + while (sb.hasRemaining()) { + tmpvalue = sb.get(); + if (tmpvalue > maxvalue) + maxvalue = tmpvalue; + } + int vuvalue = (int) ((maxvalue / 32767.0) * 100); + raise_recordingvu(_rechandle, vuvalue); + + // untuk mengurangi keperluan refresh display + // cuma ganti display kalau berubah value aja + if (vuvalue != last_vu_value) { + last_vu_value = vuvalue; + if (vu_pb instanceof ProgressBarWrapper) { + if (vu_pb.IsInitialized()) { + if (vu_pb.getVisible()) { + vu_pb.setProgress(vuvalue); + } + } + } + + if (vu_label instanceof LabelWrapper) { + if (vu_label.IsInitialized()) { + if (vu_label.getVisible()) { + vu_label.setText("VU= " + vuvalue); + } + } + } + } + + } else { + raise_recordingvu(_rechandle, 0); + + // untuk mengurangi keperluan refresh display + // cuma ganti display kalau berubah value aja + if (last_vu_value != 0) { + last_vu_value = 0; + if (vu_pb instanceof ProgressBarWrapper) { + if (vu_pb.IsInitialized()) { + if (vu_pb.getVisible()) { + vu_pb.setProgress(0); + } + } + } + if (vu_label instanceof LabelWrapper) { + if (vu_label.IsInitialized()) { + if (vu_label.getVisible()) { + vu_label.setText("VU= 0"); + } + } + } + } + + } + } + + private void GetPCMBytes() { + bytesavailable = bass.BASS_ChannelGetData(_rechandle, null, bassconstant.BASS_DATA_AVAILABLE); + if (bytesavailable >= _maxbytes) { + Pointer buf = new Memory(_maxbytes); + bytesread = bass.BASS_ChannelGetData(_rechandle, buf, _maxbytes); + if (bytesread == -1) { + int errcode = bass.BASS_ErrorGetCode(); + if (errcode != 0) { + raise_log("ChannelGetData PCM return -1, Msg : " + bass.GetBassErrorString(errcode)); + needtostop = true; // berhenti record + } + return; + } + + + if (bytesread > 0) { + // update tick + long newtick = DateTime.getNow(); + int interval = (int) (newtick - maxbytetick); + maxbytetick = newtick; + if (BA.debugMode) + BA.Log("Get " + bytesread + " bytes in " + interval + " ms"); + + record_pcm_counter += bytesread; + raise_recordingpcmcounter(_rechandle, record_pcm_counter); + final byte[] bb = buf.getByteArray(0, bytesread); + raise_recordingbytes(_rechandle, bb, bytesread); + // send to MasterSender + SendToMasterSender(bb, bytesread); + GetVU(buf, bytesread); + } + + } + } + + @Override + public void run() { + record_pcm_counter = 0; + // awalnya record di pause. sekarang mulai di sini + if (bass.BASS_ChannelPlay(_rechandle, false)) { + + maxbytetick = DateTime.getNow(); + + record_started = true; + raise_recordingstatus(_rechandle, record_started); + while (record_started) { + try { + Thread.sleep(2); + if (needtostop) + break; + if (bass.BASS_ChannelIsActive(_rechandle) != bassconstant.BASS_ACTIVE_PLAYING) + break; + GetPCMBytes(); + + } catch (InterruptedException e) { + break; + } + } + + } + + record_started = false; + raise_recordingstatus(_rechandle, false); + GetVU(null, 0); // buat nge-nol-in vu meter + + // channelstop di record_handle untuk selesai recording dan free resource + if (!bass.BASS_ChannelStop(_rechandle)) { + raise_log("Record ChannelStop failed, Msg : " + bass.GetBassErrorString()); + } + + if (recordhandlemap instanceof Map) { + if (recordhandlemap.IsInitialized()) { + synchronized (recordhandlemap) { + recordhandlemap.Remove(_rechandle); + raise_log("Removing " + _rechandle + " from RecordHandleMap"); + } + } + } + + } + + } + + /////////////////////////////////////////////////////////////////////// + + //////////////// PLAYBACK SECTION //////////////////////////////////// + + /** + * Initialize playback at device 1 + * + * @param freq : samplingrate for playback + * @param flags : init flags + * @return true if success + */ + private boolean playbackinit(int deviceid, int freq, int flags) { + playback_deviceid = deviceid; + if (bass_inited) { + if (bass.BASS_Init(playback_deviceid, freq, flags)) { + return true; + } else { + int errcode = bass.BASS_ErrorGetCode(); + if (errcode == bassconstant.BASS_ERROR_ALREADY) { + return true; + } else { + raise_log("PlaybackInit dev="+playback_deviceid+" fail, Msg : " + bass.GetBassErrorString(errcode)); + } + } + } + return false; + } + + /** + * Get Playback Device Info for Android, always ad device ID 1 + * + * @return Bass_DeviceInfo + */ + public Bass_DeviceInfo GetPlaybackDeviceInfo() { + BASS_DEVICEINFO dev = new BASS_DEVICEINFO(); + if (bass.BASS_GetDeviceInfo(playback_deviceid, dev)) { + dev.read(); + return new Bass_DeviceInfo(playback_deviceid, dev); + } else { + return new Bass_DeviceInfo(playback_deviceid, null); + } + } + + /** + * Get Bass_Info from Playback Device + * + * @return Bass_Info + */ + public Bass_Info GetPlaybackInfo() { + BASS_INFO inf = new BASS_INFO(); + if (bass.BASS_GetInfo(inf)) { + inf.read(); + return new Bass_Info(inf); + } else + return new Bass_Info(null); + } + + /** + * Stop specific playback + * @param handle : handle to stop + * @return true if this handle can be stopped + */ + public boolean StopPlayback_byHandle(int handle) { + if (playbackhandlemap instanceof Map) { + if (playbackhandlemap.IsInitialized()) { + Object xx = playbackhandlemap.Get(handle); + if (xx instanceof playback_job) { + playback_job pj = (playback_job) xx; + pj.Stop_Playback(); + return true; + } + } + } + return false; + } + + /** + * Get ProgressBar assigned to specific playback handle + * @param handle : playback handle + * @return null if not exist + */ + public ProgressBarWrapper getPlaybackVUMeter(int handle) { + if (playbackhandlemap instanceof Map) { + if (playbackhandlemap.IsInitialized()) { + Object xx = playbackhandlemap.Get(handle); + if (xx instanceof playback_job) { + playback_job pj = (playback_job) xx; + return pj.getPlaybackVUBar(); + } + } + } + return null; + } + + /** + * Assign Progressbar to specific playback handle + * @param handle : playback handle + * @param pb : progressbar + * @return true if success + */ + public boolean setPlaybackVUMeter(int handle, ProgressBarWrapper pb) { + if (playbackhandlemap instanceof Map) { + if (playbackhandlemap.IsInitialized()) { + Object xx = playbackhandlemap.Get(handle); + if (xx instanceof playback_job) { + playback_job pj = (playback_job) xx; + if (pb.IsInitialized()) { + pj.setPlaybackVUBar(pb); + return true; + } + + } + } + } + return false; + } + + /** + * Get VU Label assigned to specific playback handle + * @param handle : playback handle + * @return null if not available + */ + public LabelWrapper getPlaybackVULabel(int handle) { + if (playbackhandlemap instanceof Map) { + if (playbackhandlemap.IsInitialized()) { + Object xx = playbackhandlemap.Get(handle); + if (xx instanceof playback_job) { + playback_job pj = (playback_job) xx; + return pj.getPlaybackVULabel(); + } + } + } + return null; + } + + /** + * Assign VU Label to specific playback handle + * @param handle : playback handle + * @param lb : label + * @return true if success + */ + public boolean setPlaybackVULabel(int handle, LabelWrapper lb) { + if (playbackhandlemap instanceof Map) { + if (playbackhandlemap.IsInitialized()) { + Object xx = playbackhandlemap.Get(handle); + if (xx instanceof playback_job) { + playback_job pj = (playback_job) xx; + if (lb.IsInitialized()) { + pj.setPlaybackVULabel(lb); + return true; + } + + } + } + } + return false; + } + + /** + * Open audio file to stream it + * on success opening, will raise event openedfileinformation + * on playback and finish, will raise event playbackstatus + * @param path : valid audio file + * @param mute : if true, no audio will out from speaker + * @param maxbytes : maximum bytes per event playbackbytes + * @return true if audio file can be opened + */ + public boolean StreamAudioFile(String path, boolean mute, int maxbytes) { + if (playback_inited) { + int handle = bass.BASS_StreamCreateFile(false, path, 0, 0, 0); + if (handle!=0) { + SYNCPROC playdevfail = new SYNCPROC() { + + @Override + public void SYNCPROC(int handle, int channel, int data, Pointer user) { + raise_log("Playback Device failed on handle="+channel+", Closing Playback"); + if (playbackhandlemap instanceof Map) { + if (playbackhandlemap.IsInitialized()) { + synchronized(playbackhandlemap) { + Object xx = playbackhandlemap.Get(channel); + if (xx instanceof playback_job) { + playback_job pj = (playback_job) xx; + pj.Stop_Playback(); + } + } + } + } + } + + }; + + if (bass.BASS_ChannelSetSync(handle, bassconstant.BASS_SYNC_DEV_FAIL, 0, playdevfail, null)!=0) { + raise_log("StreamAudioFile monitoring for BASS_SYNC_DEV_FAIL on playback_handle="+handle); + } else { + raise_log("Unable to set ChannelSetSync BASS_SYNC_DEV_FAIL on playback_handle="+handle+", Msg : "+bass.GetBassErrorString()); + } + + if (bass.BASS_ChannelSetAttribute(handle, bassconstant.BASS_ATTRIB_NOBUFFER, 1.0f)) { + raise_log("StreamAudioFile set to NOBUFFER"); + } else { + raise_log("StreamAudioFile unable to set NOBUFFER, Msg : "+bass.GetBassErrorString()); + } + + playback_job pj = new playback_job(handle, path, mute, true, maxbytes); + raise_openedfileinformation(handle, path, pj.getFilesize(), pj.getDuration()); + + + Thread tx = new Thread(pj); + tx.start(); + + if (playbackhandlemap instanceof Map) { + if (playbackhandlemap.IsInitialized()) { + synchronized(playbackhandlemap) { + Object prev = playbackhandlemap.Put(handle, pj); + raise_log("Added playback_handle="+handle+" to PlaybackHandleMap"); + if (prev instanceof playback_job) { + playback_job prevpj = (playback_job) prev; + prevpj.Stop_Playback(); + raise_log("Stopping Previous Playback Job with the same playback_handle"); + } + } + } + } + + return true; + + } else raise_log("StreamAudioFile failed, can not open path "+path+", Msg : "+bass.GetBassErrorString()); + } else raise_log("StreamAudioFile failed, playback device not inited"); + return false; + } + + /** + * Simple Playback for audio file + * on success opening, will raise event openedfileinformation + * on playback and finish, will raise event playbackstatus + * @param path : valid audio file + * @return true if audio file can be opened + */ + public boolean PlayAudioFile(String path) { + + if (playback_inited) { + int handle = bass.BASS_StreamCreateFile(false, path, 0, 0, 0); + if (handle!=0) { + + SYNCPROC playdevfail = new SYNCPROC() { + + @Override + public void SYNCPROC(int handle, int channel, int data, Pointer user) { + raise_log("Playback Device failed on handle="+channel+", Closing Playback"); + if (playbackhandlemap instanceof Map) { + if (playbackhandlemap.IsInitialized()) { + synchronized(playbackhandlemap) { + Object xx = playbackhandlemap.Get(channel); + if (xx instanceof playback_job) { + playback_job pj = (playback_job) xx; + pj.Stop_Playback(); + } + } + } + } + } + + }; + + if (bass.BASS_ChannelSetSync(handle, bassconstant.BASS_SYNC_DEV_FAIL, 0, playdevfail, null)!=0) { + raise_log("PlayAudioFile monitoring for BASS_SYNC_DEV_FAIL on playback_handle="+handle); + } else { + raise_log("Unable to set ChannelSetSync BASS_SYNC_DEV_FAIL on playback_handle="+handle+", Msg : "+bass.GetBassErrorString()); + } + + playback_job pj = new playback_job(handle, path); + raise_openedfileinformation(handle, path, pj.getFilesize(), pj.getDuration()); + + + Thread tx = new Thread(pj); + tx.start(); + + if (playbackhandlemap instanceof Map) { + if (playbackhandlemap.IsInitialized()) { + synchronized(playbackhandlemap) { + Object prev = playbackhandlemap.Put(handle, pj); + raise_log("Added playback_handle="+handle+" to PlaybackHandleMap"); + if (prev instanceof playback_job) { + playback_job prevpj = (playback_job) prev; + prevpj.Stop_Playback(); + raise_log("Stopping Previous Playback Job with the same playback_handle"); + } + } + } + } + + return true; + } else raise_log("PlayAudioFile failed, StreamCreateFile return 0, Msg : "+bass.GetBassErrorString()); + } else raise_log("PlayAudioFile failed, playback device not inited"); + + return false; + } + + /** + * Stop All Playback + */ + public void CloseAllPlayback() { + playback_started = false; + } + + /** + * Get all Playback handles that still opened + * @return List of Handle (integer) + */ + public List OpenedPlaybacks() { + List result = new List(); + result.Initialize(); + if (playbackhandlemap instanceof Map) { + if (playbackhandlemap.IsInitialized()) { + synchronized(playbackhandlemap) { + if (playbackhandlemap.getSize()>0) { + for(int ii=0;ii=0 ? _duration : 0; + } + + /** + * Get File Size in bytes + * @return filesize in bytes + */ + public long getFilesize() { + return _filesize >=0 ? _filesize : 0; + } + + private void SendToMasterSender(byte[] bb, int length) { + if (_msender instanceof MasterSender) { + if (_msender.IsInitialized()) { + if (_msender.UDP_Clients_exists()) _msender.SendData_to_UDP_Clients(bb, length); + if (_msender.TCP_Clients_exists()) _msender.SendData_to_TCP_Clients(bb, length); + } + } + } + + @Override + public void run() { + + if (bass.BASS_ChannelPlay(_handle, true)) { + is_playing = true; // playback status only for this thread + if (_muted) { + if (bass.BASS_ChannelSetAttribute(_handle, bassconstant.BASS_ATTRIB_VOL, 0.0f)) { + raise_log("Set mute playback for "+_path); + } else { + raise_log("Unable to set mute playback for "+_path+", Msg :"+bass.GetBassErrorString()); + } + } + + if (_getpcm) { + IDSPPROC dsp = new IDSPPROC() { + + @Override + public void DSPPROC(int handle, int channel, Pointer buffer, int length, Pointer user) { + long interval = DateTime.getNow() - dsptick; + dsptick = DateTime.getNow(); + if (BA.debugMode) raise_log("DSPPROC playback_handle="+channel+" interval="+interval+" ms"); + + if (length>0) { + _pcmcounter+=length; + raise_playbackpcmcounter(_handle, _pcmcounter); + final byte[] buf = buffer.getByteArray(0, length); + raise_playbackbytes(_handle, buf, length); + // send to master sender + SendToMasterSender(buf,length); + } + + } + + }; + + dsphandle = bass.BASS_ChannelSetDSP(_handle, dsp, null, 1); + if (dsphandle!=0) { + raise_log("Set DSP for "+_path); + + if (_maxpcmsize>0) { + float sz = _maxpcmsize / 2.0f; + if (bass.BASS_ChannelSetAttribute(_handle, bassconstant.BASS_ATTRIB_GRANULE, sz)) { + raise_log("Set BASS_ATTRIB_GRANULE to "+sz); + } else { + raise_log("Unable to set BASS_ATTRIB_GRANULE for "+_path+", Msg : "+bass.GetBassErrorString()); + } + + } + + dsptick = DateTime.getNow(); // untuk ukur interval antar dspproc + } else { + raise_log("Unable to set DSP for "+_path+", Msg : "+bass.GetBassErrorString()); + } + + + } + + playback_started = true; // total playback status + raise_playbackstatus(_handle, _path, true); + int channelstatus = 0; + while(true) { + if (! is_playing) break; + if (! playback_started) break; + try { + Thread.sleep((long)(threadsleep * 1000)); + } catch (InterruptedException e) { + is_playing = false; + + raise_log("InterruptedException on SimplePlayback_Job, Msg : "+e.getMessage()); + break; + } + + channelstatus = bass.BASS_ChannelIsActive(_handle); + if (channelstatus == bassconstant.BASS_ACTIVE_STOPPED) { + is_playing = false; + break; + } + + + if (!bass.BASS_ChannelGetLevelEx(_handle, vu, threadsleep, bassconstant.BASS_LEVEL_MONO)) { + if (vu[0]<0) vu[0] = 0; + if (vu[0]>1.0f) vu[0] = 1.0f; + int vuint = (int)(vu[0]*100); + raise_playbackvu(_handle,vuint); + update_vu(vuint); + } + } + + is_playing = false; + + + if (playbackhandlemap instanceof Map) { + if (playbackhandlemap.IsInitialized()) { + synchronized(playbackhandlemap) { + playbackhandlemap.Remove(_handle); + raise_log("Removing playback_handle="+_handle+" from PlaybackhandleMap because already finished"); + if (playbackhandlemap.getSize()<1) { + playback_started = false; + raise_log("All Playback job already finished"); + } + } + } + } + } + + if (!bass.BASS_ChannelStop(_handle)) { + raise_log("ChannelStop on playback_handle="+_handle+" is failed, Msg : "+bass.GetBassErrorString()); + } + if (!bass.BASS_StreamFree(_handle)) { + raise_log("StreamFree on playback_handle="+_handle+" is failed, Msg : "+bass.GetBassErrorString()); + } + raise_playbackstatus(_handle, _path, false); + } + + /** + * Stop playback for this thread + */ + public void Stop_Playback() { + is_playing = false; + } + + private void update_vu(int value) { + + if (_vulabel instanceof LabelWrapper) { + if (_vulabel.IsInitialized()) { + if (_vulabel.getVisible()) { + _vulabel.setText("VU= "+value); + } + } + } + + if (_vubar instanceof ProgressBarWrapper) { + if (_vubar.IsInitialized()) { + if (_vubar.getVisible()) { + _vubar.setProgress(value); + } + } + } + } + } + + /////////////////////////////////////////////////////////////////////// + + //////////////// SUPPORTING FUNCTIONS /////////////////////////////// + + private void raise_log(String value) { + if (need_log_event) + bax.raiseEventFromDifferentThread(caller, null, 0, event + "_log", false, new Object[] { value }); + } + + private void raise_recordingstatus(int handle, boolean started) { + if (need_recordingstatus_event) + bax.raiseEventFromDifferentThread(caller, null, 0, event + "_recordingstatus", false, + new Object[] { handle, started }); + } + + private void raise_recordingbytes(int handle, byte[] buf, int length) { + if (need_recordingbytes_event) + bax.raiseEventFromDifferentThread(caller, null, 0, event + "_recordingbytes", false, + new Object[] { handle, buf, length }); + } + + private void raise_recordingvu(int handle, int value) { + if (need_recordingvu_event) + bax.raiseEventFromDifferentThread(caller, null, 0, event + "_recordingvu", false, + new Object[] { handle, value }); + + } + + private void raise_recordingpcmcounter(int handle, long value) { + if (need_recordingpcmcounter_event) + bax.raiseEventFromDifferentThread(caller, null, 0, event + "_recordingpcmcounter", false, + new Object[] { handle, value }); + } + + private void raise_playbackstatus(int handle, String filepath, boolean started) { + if (need_playbackstatus_event) { + bax.raiseEventFromDifferentThread(caller, null, 0, event+"_playbackstatus", false, new Object[] {handle, filepath, started}); + } + } + + private void raise_playbackbytes(int handle, byte[] bb, int length) { + if (need_playbackbytes_event) bax.raiseEventFromDifferentThread(caller, null, 0, event+"_playbackbytes", false, new Object[] {handle, bb, length}); + } + + private void raise_playbackvu(int handle, int value) { + if (need_playbackvu_event) bax.raiseEventFromDifferentThread(caller, null, 0, event+"_playbackvu", false, new Object[] {handle, value}); + } + + private void raise_playbackpcmcounter(int handle, long value) { + if (need_playbackpcmcounter_event) bax.raiseEventFromDifferentThread(caller, null, 0, event+"_playbackpcmcounter", false, new Object[] {handle, value}); + } + + private void raise_openedfileinformation(int handle, String path, long filesize, double duration) { + if (need_openedfileinformation_event) bax.raiseEventFromDifferentThread(caller, null, 0, event+"_openedfileinformation", false, new Object[] {handle, path, filesize, duration}); + } + + + +} diff --git a/src/AndroidRelated/AndroidNetworkReceiver.java b/src/AndroidRelated/AndroidNetworkReceiver.java new file mode 100644 index 0000000..c018921 --- /dev/null +++ b/src/AndroidRelated/AndroidNetworkReceiver.java @@ -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 + + } +} diff --git a/src/AndroidRelated/AudioFileInformationV2.java b/src/AndroidRelated/AudioFileInformationV2.java new file mode 100644 index 0000000..690feb4 --- /dev/null +++ b/src/AndroidRelated/AudioFileInformationV2.java @@ -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 (size0) { + 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; + } +} diff --git a/src/AndroidRelated/NetworkReceiverUDPClient.java b/src/AndroidRelated/NetworkReceiverUDPClient.java new file mode 100644 index 0000000..8aa941a --- /dev/null +++ b/src/AndroidRelated/NetworkReceiverUDPClient.java @@ -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); + + + } + + + }; + +} diff --git a/src/AndroidRelated/NetworkReceiverUDPClientEvent.java b/src/AndroidRelated/NetworkReceiverUDPClientEvent.java new file mode 100644 index 0000000..d42f717 --- /dev/null +++ b/src/AndroidRelated/NetworkReceiverUDPClientEvent.java @@ -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); +} diff --git a/src/DatareceiverRelated/DataReceiver.java b/src/DatareceiverRelated/DataReceiver.java new file mode 100644 index 0000000..42cc83d --- /dev/null +++ b/src/DatareceiverRelated/DataReceiver.java @@ -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)}); + } +} diff --git a/src/DatareceiverRelated/DataReceiverEvent.java b/src/DatareceiverRelated/DataReceiverEvent.java new file mode 100644 index 0000000..0410d8d --- /dev/null +++ b/src/DatareceiverRelated/DataReceiverEvent.java @@ -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); +} diff --git a/src/FWSRelated/FWS_Audio_Master.java b/src/FWSRelated/FWS_Audio_Master.java new file mode 100644 index 0000000..6efd824 --- /dev/null +++ b/src/FWSRelated/FWS_Audio_Master.java @@ -0,0 +1,1345 @@ +package FWSRelated; + +import java.io.File; +import java.nio.BufferOverflowException; +import java.nio.ByteBuffer; +import java.nio.ReadOnlyBufferException; +import java.nio.ShortBuffer; + +import com.sun.jna.Memory; +import com.sun.jna.Pointer; +import com.un4seen.bass.BASS; +import com.un4seen.bass.BASSenc; +import com.un4seen.bass.BASSenc_MP3; +import com.un4seen.bass.BASS.BASS_CHANNELINFO; +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.IDSPPROC; +//import com.un4seen.bass.BASS.RECORDPROC; // test recording gak pake recordproc +import com.un4seen.bass.BASS.bassconstant; + +import MastersenderRelated.MasterSender; +import anywheresoftware.b4a.BA; +import anywheresoftware.b4a.keywords.DateTime; +import anywheresoftware.b4a.objects.collections.List; +import jbass.AudioFileInformation; +import jbass.Bass_DeviceInfo; +import jbass.HandleMP3Writer; +import jbass.HandleMp3WriterEvent; + +@BA.ShortName("FWS_Audio_Master") +@BA.Events(values={ + "log(msg as string)", + "sendfilestarted(namafile as string, success as boolean)", + "sendfilestopped(namafile as string)", + "sendfilemp3bytes(bb() as byte)", + "sendfilepcmbytes(bb() as byte)", + "sendfilestatistic(pcmcount as int, mp3count as int, ratio as double, vu as int, elapsed as double, filesize as long)", + "micpagingstarted(success as boolean)", + "micpagingstopped", + "micmp3bytes(bb() as byte)", + "micpcmbytes(bb() as byte)", + "micstatistic(pcmcount as int, mp3count as int, ratio as double, vu as int, elapsed as double)", + "mp3listenerstatus(isopened as boolean)" + +}) + +public class FWS_Audio_Master implements HandleMp3WriterEvent { + + private BA ba; + private String event; + + private boolean need_log_event = false; + private boolean need_sendfilestarted_event = false; + private boolean need_sendfilestopped_event = false; + private boolean need_micpagingstarted_event = false; + private boolean need_micpagingstopped_event = false; + private boolean need_micmp3bytes_event = false; + private boolean need_sendfilemp3bytes_event = false; + private boolean need_micpcmbytes_event = false; + private boolean need_sendfilepcmbytes_event = false; + private boolean need_sendfilestatistic_event = false; + private boolean need_micstatistic_event = false; + private boolean need_mp3listenerstatus_event = false; + + private boolean inited = false; + private BASS bass; + private BASSenc bassenc; + private BASSenc_MP3 bassencmp3; + + private final int send_samplingrate = 44100; + private final int send_initflag = bassconstant.BASS_DEVICE_FREQ | bassconstant.BASS_DEVICE_MONO | bassconstant.BASS_DEVICE_16BITS; + private int send_devid = 0; + private int send_play_handle = 0; + private boolean send_isplaying = false; + private BASS_CHANNELINFO send_channelinfo; + private MasterSender send_master; // untuk sending mp3 hasilnya + private HandleMP3Writer mp3writer; // encoder mp3 untuk sendfile + private IDSPPROC sendfileproc; // untuk event sendfilepcmbytes dan update sendfilebytecount + private int sendfilebytecount = 0; // in PCM + private int sendfilemp3count = 0; // in mp3 + private double sendfile_ratio = 0; // ratio between MP3 to PCM + private long sendfile_size = 0; // size of file being sent + private double sendfile_duration = 0; // duration of file being sent, in seconds + private double sendfile_elapsed = 0; // elapsed of file being sent, in seconds + private int sendfile_vu = 0; //0 - 100 + + private final int mic_samplingrate = 44100; + private final int mic_openflag = 0; + private int mic_devid = -1; + private int mic_record_handle = 0; + private boolean mic_isrecording = false; + //private RECORDPROC micproc; // untuk event micpcmbytes dan update micbytecount + private HandleMP3Writer micwriter ; // encoder mp3 untuk mic + private int micbytecount = 0; // in PCM + private int micmp3count = 0; // in MP3 + private double mic_ratio = 0; // ratio between MP3 to PCM + private double mic_elapsed =0 ; // elapsed of microphone being working, in seconds + private int mic_vu = 0; // 0 - 100 + + private int mp3listener_handle = -1; + private final int mp3listener_samplingrate = 44100; + private final int mp3listener_initflag = bassconstant.BASS_DEVICE_16BITS | bassconstant.BASS_DEVICE_MONO; + + // revisi 12/10/2020, tambah flag BASS_STREAM_RESTRATE supaya download rate nya konstan + private final int mp3listener_createstreamflag = bassconstant.BASS_SAMPLE_MONO | bassconstant.BASS_STREAM_RESTRATE; + private boolean mp3listener_running = false; + private ByteBuffer mp3listener_streamingbuffer; + private final int mp3listener_buffersize = 8000; + private int mp3listener_remaining = 0; + + private Object myobject; + /** + * Initialize FWS Audio Master + * @param eventname : Eventname + */ + public void Initialize(BA bax, String eventname) { + ba = bax; + event = eventname; + myobject = this; + if (ba!=null) { + if (!event.isEmpty()) { + need_log_event = ba.subExists(event+"_log"); + need_sendfilestarted_event = ba.subExists(event+"_sendfilestarted"); + need_sendfilestopped_event = ba.subExists(event+"_sendfilestopped"); + need_micpagingstarted_event = ba.subExists(event+"_micpagingstarted"); + need_micpagingstopped_event = ba.subExists(event+"_micpagingstopped"); + need_micmp3bytes_event = ba.subExists(event+"_micmp3bytes"); + need_sendfilemp3bytes_event = ba.subExists(event+"_sendfilemp3bytes"); + need_micpcmbytes_event = ba.subExists(event+"_micpcmbytes"); + need_sendfilepcmbytes_event = ba.subExists(event+"_sendfilepcmbytes"); + need_sendfilestatistic_event = ba.subExists(event+"_sendfilestatistic"); + need_micstatistic_event = ba.subExists(event+"_micstatistic"); + need_mp3listenerstatus_event = ba.subExists(event+"_mp3listenerstatus"); + } + } + + 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; + } + } + } + + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + // closing semua recording dan playback + } + }); + + + } + + + + /** + * Get All Playback Devices + * @return list of BASS_DEVICEINFO + */ + public List Bass_GetDeviceInfos() { + List result = new List(); + result.Initialize(); + + if (inited) { + for(int ii=0;ii<10;ii++) { + BASS_DEVICEINFO tt = new BASS_DEVICEINFO(); + if (bass.BASS_GetDeviceInfo(ii, tt)) { + tt.read(); + if (tt.name==null) continue; + if (tt.name.isEmpty()) continue; + if (tt.driver==null) continue; + if (tt.driver.isEmpty()) continue; + result.Add(new Bass_DeviceInfo(ii,tt)); + } + } + } + return result; + } + + /** + * Get all Recording devices + * @return list of BASS_DEVICEINFO + */ + public List Bass_RecordGetDeviceInfos(){ + List result = new List(); + result.Initialize(); + if (inited) { + for(int ii=0;ii<10;ii++) { + BASS_DEVICEINFO tt = new BASS_DEVICEINFO(); + if (bass.BASS_RecordGetDeviceInfo(ii, tt)) { + tt.read(); + if (tt.name==null) continue; + if (tt.name.isEmpty()) continue; + if (tt.driver==null) continue; + if (tt.driver.isEmpty()) continue; + result.Add(new Bass_DeviceInfo(ii,tt)); + } + } + } + return result; + } + + + /** + * Get / Set Network Sender assigned for Send File operation + */ + public void setMasterNetworkSender(MasterSender value) { + send_master = value; + } + + public MasterSender getMasterNetworkSender() { + return send_master; + } + + + + public void StopPaging() { + mic_isrecording = false; + } + + private boolean SetRecorderDevice(int micdevid) { + if (!bass.BASS_RecordSetDevice(micdevid)) { + int errcode = bass.BASS_ErrorGetCode(); + if (errcode==bassconstant.BASS_ERROR_DEVICE) { + // device id tidak ada + raise_log_event("OpenPaging failed, Record Device ID="+micdevid+" is not found"); + return false; + } + if (errcode==bassconstant.BASS_ERROR_INIT) { + // belum init, sekarang di-init + if (!bass.BASS_RecordInit(micdevid)) { + raise_bass_error("OpenPaging","RecordInit"); + return false; + } + } + } + // sampai sini RecodSetDevice sudah bisa + return true; + } + + /** + * Get Max VU from byte bytebuffer + * @param bb : bytebuffer as source + * @return 0 ~ 100 value in int + */ + private int GetVU(ByteBuffer bb) { + if (bb==null) return 0; + ShortBuffer sb = bb.asShortBuffer(); + sb.rewind(); + int maxvalue = 0; + short currentvalue = 0; + while (sb.hasRemaining()) { + currentvalue = sb.get(); + if (maxvalue16000) readsize = 16000; + + micrecordthread mrt = new micrecordthread(readsize); + mrt.start(); + + return true; + } + + private class micrecordthread extends Thread{ + private final int recbytemax; // ganti dari 1000 + + public micrecordthread(int bufsize) { + recbytemax = bufsize; + } + + public void run() { + int temp_rounding = 0; + long tick = DateTime.getNow(); + mic_isrecording = true; + raise_micpagingstarted_event(true); + + int reclength = 0; + while(true) { + try { + Thread.sleep(5); + } catch (InterruptedException e) { + raise_log_exception("MicRecordThread","sleep",e); + break; + } + + if (!mic_isrecording) break; + if (bass==null) break; + if (mic_record_handle==0) break; + if (bass.BASS_ChannelIsActive(mic_record_handle)!=bassconstant.BASS_ACTIVE_PLAYING) break; + + reclength = bass.BASS_ChannelGetData(mic_record_handle, null, bassconstant.BASS_DATA_AVAILABLE); + if (reclength<0) { + raise_bass_error("micrecordthread", "ChannelGetData::DATA_AVAILABLE"); + break; // hasilnya -1 , ada error + } + + if (reclength0) { + if (micmp3count>0) { + mic_ratio = (double) micmp3count / micbytecount; + temp_rounding = (int)(mic_ratio * 100 * 100); // mau dibulatin 2 digit, dan jadiin persen + mic_ratio = temp_rounding / 100.0; // balikin jadi persen dan pembulantan 2 digit + + } + } + + raise_micstatistic_event(); + } + if (micwriter instanceof HandleMP3Writer) { + micwriter.SetHandleMp3WriterEvent(null); + + } + + close_record(); + raise_micpagingstopped_event(); + } + } + + public boolean getMic_is_recording() { + return mic_isrecording; + } + + private void close_record() { + mic_isrecording = false; + if (mic_record_handle!=0) { + if (!bass.BASS_ChannelStop(mic_record_handle)) { // sudah otomatis free + raise_bass_error("close_record","ChannelStop"); + } + mic_record_handle = 0; + } + + if (bass.BASS_RecordSetDevice(mic_devid)) { + // bisa set, artinya belum free + if (!bass.BASS_RecordFree()) { + raise_bass_error("close_record","RecordFree"); + } + mic_devid = -1; + } + } + + /** + * Get how many bytes , in PCM, file has been read + * @return value in integer + */ + public int getSendFile_PCM_Counter() { + return sendfilebytecount; + } + + + /** + * Get how many bytes, in MP3, file has been encoded + * @return value in integer + */ + public int getSendFile_MP3_Counter() { + return sendfilemp3count; + } + + /** + * Get filesize of sendfile target + * @return - 1 = invalid, other = valid size in bytes + */ + public long getSendFile_FileSize() { + return sendfile_size; + } + + /** + * Get Duration , in seconds, of sendfile target + * @return positive number, in seconds + */ + public double getSendFile_FileDuration() { + return sendfile_duration; + } + + /** + * Get current SendFile VU + * @return 0 - 100 in integer + */ + public int getSendFile_VU() { + return sendfile_vu; + } + + /** + * Get how many seconds sendfile has been working + * @return duration in seconds + */ + public double getSendFile_Elapsed() { + return sendfile_elapsed; + } + + /** + * Get how many percent MP3 byte compared to PCM byte + * @return value in percent, 2 decimals + */ + public double getSendFile_MP3_Ratio() { + return sendfile_ratio; + } + + private IDSPPROC create_dspproc() { + IDSPPROC newproc = new IDSPPROC() { + + @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<1) return; + + + byte[] xx = bx.getByteArray(0, length); + + + + raise_sendfilepcmbytes_event(xx); + sendfilebytecount+=length; + + sendfile_vu = GetVU(bx.getByteBuffer(0, length)); + } + + }; + return newproc; + } + + public boolean getSendFile_is_Active() { + return send_isplaying; + } + + /** + * Force stop SendFile progress + */ + public void StopFile() { + send_isplaying = false; + } + + public int getMp3Listener_RemainingSize() { + return mp3listener_remaining; + } + + public int getMp3Listener_RemainingPercent() { + double ff = (mp3listener_remaining / mp3listener_buffersize) * 100; + return (int) ff; + } + + /** + * Push MP3 bytes to MP3 Listener + * @param bb : mp3 bytes + * @return true if bytes pushed, or false if failed + */ + public boolean PushBytes_to_Mp3Listener(byte[] bb) { + boolean success = false; + if (mp3listener_running) { + if (mp3listener_streamingbuffer instanceof ByteBuffer) { + synchronized(mp3listener_streamingbuffer) { + mp3listener_remaining = mp3listener_streamingbuffer.remaining(); + + try { + if (mp3listener_remaining > 0) { + // ada sisa space tulis + if (mp3listener_remaining >= bb.length) { + // sisa cukup, langsung tulis + mp3listener_streamingbuffer.put(bb); + success = true; + } else { + mp3listener_streamingbuffer.put(bb,0,mp3listener_remaining); + success = true; + raise_log_event("MP3Listener_StreamingBuffer left = "+mp3listener_remaining+", data length="+bb.length+", discarding "+(bb.length-mp3listener_remaining)+" bytes"); + } + } else raise_log_event("MP3Listener_StreamingBuffer not enough space for data="+bb.length+" bytes, discarding all bytes"); + + + } catch(BufferOverflowException e) { + raise_log_exception("PushBytes_to_Mp3Listener", "Put", e); + } catch(ReadOnlyBufferException e) { + raise_log_exception("PushBytes_to_Mp3Listener", "Put", e); + } + + mp3listener_remaining = mp3listener_streamingbuffer.remaining(); + + mp3listener_streamingbuffer.notifyAll(); + } + + + } + } + + return success; + } + + /** + * Open MP3 Listener + * Will raise mp3listenerstatus event + * @param playbackdev : playback device, start from 1 + * @return true if success + */ + public boolean Open_MP3Listener(int playbackdev) { + mp3listener_running = false; + //BA.Log("Playbackdev = ["+playbackdev+"]"); +// if (!bass.BASS_SetDevice(playbackdev)) { +// int errcode = bass.BASS_ErrorGetCode(); +// if (errcode == bassconstant.BASS_ERROR_DEVICE) { +// // tidak ada device ini +// raise_bass_error("Open_MP3Listener", "SetDevice"); +// raise_mp3listenerstatus(false); +// return false; +// } +// if (errcode == bassconstant.BASS_ERROR_INIT) { +// // belum di init +// if (!bass.BASS_Init(playbackdev, mp3listener_samplingrate, mp3listener_initflag)) { +// // gagal init +// raise_bass_error("Open_MP3Listener","Init"); +// raise_mp3listenerstatus(false); +// return false; +// } +// } +// } + + if (!bass.BASS_Init(playbackdev, mp3listener_samplingrate, mp3listener_initflag)) { + int errcode = bass.BASS_ErrorGetCode(); + if (errcode != bassconstant.BASS_ERROR_ALREADY) { + raise_bass_error("Open_MP3Listener","Init"); + raise_mp3listenerstatus(false); + return false; + } else { + // ternyata sudah init, jadi ini bukan error + BA.Log("Playbackdev="+playbackdev+" already inited"); + } + } + + //BA.Log("Init playbackdev="+playbackdev+" success"); + // sampai sini sudah bisa setdevice dan init + mp3listener_streamingbuffer = ByteBuffer.allocateDirect(mp3listener_buffersize); + + Thread tx = new Thread(new mp3listener_runnable(playbackdev)); + tx.start(); + + return true; + } + + /*** + * Check if MP3 Listener is playing + * @return true if MP3 Listener is playing + */ + public boolean Mp3Listener_isPlaying() { + if (mp3listener_handle!=0) { + return (bass.BASS_ChannelIsActive(mp3listener_handle) == bassconstant.BASS_ACTIVE_PLAYING) ? true : false; + }else return false; + } + + /** + * Stop Mp3 Listener + */ + public void Close_MP3Listener() { + mp3listener_running = false; + } + + private class mp3listener_runnable implements Runnable { + private FILECLOSEPROC fc; + private FILELENPROC fl; + private FILESEEKPROC fs; + private FILEREADPROC fr; + + private BASS_FILEPROCS fileproc; + private final int playbackdev; + public mp3listener_runnable(int playbackdev) { + this.playbackdev = playbackdev; + fc = new FILECLOSEPROC() { + + @Override + public void FILECLOSEPROC(Pointer user) { + return; // do nothing here + + } + + }; + + fl = new FILELENPROC() { + + @Override + public long FILELENPROC(Pointer user) { + // return 0, artinya size awal tidak tau + return 0; + } + + }; + + fs = new FILESEEKPROC() { + + @Override + public boolean FILESEEKPROC(long offset, Pointer user) { + // return false, artinya tidak bisa seek + return false; + } + + }; + + fr = new FILEREADPROC() { + + @Override + public int FILEREADPROC(Pointer buffer, int length, Pointer user) { + // isi buffer dengan data dari streambuffer + if (!(mp3listener_streamingbuffer instanceof ByteBuffer)) { + BA.Log("Mp3Listener_StreamingBuffer is invalid, stopping FILEREADPROC"); + return 0; // kalau streambuffer gak ada, tutup aja + } + if (buffer==null) { + BA.Log("FILEREADPROC Buffer is null, stopping FILREADPROC"); + return 0; // buffer dari FILEREADPROC sudah null + } + if (length<1) { + BA.Log("FILEREADPROC length less than 1, stopping FILEREADPROC"); + return 0; // space available kurang dari 1 + } + + + synchronized(mp3listener_streamingbuffer) { + if (mp3listener_streamingbuffer.position()<1) { + // gak ada datanya, wait di sini + try { + //BA.Log("Mp3listener_streamingbuffer is empty, waiting here"); + mp3listener_streamingbuffer.wait(); + } catch (InterruptedException e) { + raise_log_event("FILEREADPROC::mp3listener_streambuffer.wait"); + return 0; // tutup aja + } + } + + // sampai sini, ada datanya + byte[] xx = Read_StreamingBuffer(length); + if (xx!=null) { + //BA.Log("Writing "+xx.length+" to FILEREADPROC buffer"); + buffer.write(0, xx, 0, xx.length); // tulis ke buffer + return xx.length; + } else { + BA.Log("Read_StreamingBUffer return null, stopping FILEREADPROC"); + raise_log_event("FILEREADPROC Read_StreamingBuffer return null"); + return 0; + } + + } + } + + }; + + fileproc = new BASS_FILEPROCS(fc, fl, fr, fs); + + } + @Override + public void run() { + if (bass.BASS_SetDevice(playbackdev)) { + mp3listener_running = true; + //BA.Log("StreamCreateFileUser started"); + mp3listener_handle = bass.BASS_StreamCreateFileUser(bassconstant.STREAMFILE_BUFFER, mp3listener_createstreamflag, fileproc, null); + //BA.Log("mp3listener_handle = "+mp3listener_handle); + if (mp3listener_handle!=0) { + if (bass.BASS_Start()) { + //BA.Log("Bass.Start success"); + if (bass.BASS_ChannelPlay(mp3listener_handle, true)) { + //BA.Log("Bass.ChannelPlay sucess"); + raise_mp3listenerstatus(true); + while (mp3listener_running) { + try { + Thread.sleep(500); + } catch (InterruptedException e) { + break; + } + + } + //BA.Log("Mp3Listener_Running is false"); + mp3listener_streamingbuffer = null; + + if (!bass.BASS_ChannelStop(mp3listener_handle)) { + raise_bass_error("Close_MP3Listener", "ChannelStop"); + } + if (!bass.BASS_StreamFree(mp3listener_handle)) { + raise_bass_error("Close_MP3Listener"," StreamFree"); + } + + } else raise_bass_error("Mp3Listener_Runnable", "ChannelPlay"); + } else raise_bass_error("Mp3Listener_Runnable", "Start"); + } else raise_bass_error("Mp3Listener_Runnable","StreamCreateFileUser"); + + + mp3listener_handle = 0; + raise_mp3listenerstatus(false); + + } else raise_bass_error("Mp3Listener_Runnable","SetDevice "+playbackdev); + } + + } + + private byte[] Read_StreamingBuffer(int length) { + byte[] bb = null; + if (mp3listener_streamingbuffer instanceof ByteBuffer) { + if (length>0) { + mp3listener_streamingbuffer.flip(); // pindah mode baca + int byteavailable = mp3listener_streamingbuffer.remaining(); + if (byteavailable>0) { + // ada yang bisa dibaca + if (byteavailable < length) length = byteavailable; // adanya segini doang + bb = new byte[length]; + mp3listener_streamingbuffer.get(bb); + } + mp3listener_streamingbuffer.compact(); // pindah mode tulis + mp3listener_remaining = mp3listener_streamingbuffer.remaining(); + } + } + return bb; + + } + + + private void raise_mp3listenerstatus(boolean isopened) { + if (need_mp3listenerstatus_event) ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_mp3listenerstatus", false, new Object[] {isopened}); + } + + + /** + * Send an audio file + * use event sendfilestarted and sendfilestarted to check real streaming event + * @param namafile : valid filename + * @param loopcount : how many loop. 0 = means continuous looping + * @param granulesize : process size x 2. + * example : granulesize = 500 then will process each 1000 bytes ( 500 samples x 16bit) + * granulesize = 1000 then will process each 2000 bytes ( 1000 samples x 16bit) + * Recommended granulesize is 1000 + * @return true if sendfile can be started + */ + public boolean SendFile(String namafile, int loopcount, final int granulesize) { + sendfilebytecount = 0; + sendfilemp3count = 0; + File ff = new File(namafile); + if (!ff.exists()) { + raise_log_event("SendFile failed, "+namafile+" is not exist"); + return false; + } + if (!inited) { + raise_log_event("SendFile failed, Audio not initialized"); + return false; + } + + if (!SetPlaybackDevice(send_devid, "SendFile")) { + return false; + } + + // revisi 21/01/2021 + // function ini sudah dipindahin ke SetPlaybackDevice +// if (!bass.BASS_SetDevice(send_devid)) { +// int errcode = bass.BASS_ErrorGetCode(); +// if (errcode==bassconstant.BASS_ERROR_DEVICE) { +// raise_log_event("SendFile failed, unable to use DeviceID="+send_devid); +// return false; +// } +// if (errcode==bassconstant.BASS_ERROR_INIT) { +// // belum di-init, maka init sekarang +// if (!bass.BASS_Init(send_devid, send_samplingrate, send_initflag)) { +// raise_bass_error("SendFile","Init"); +// return false; +// } +// } +// } + + // sampai sini sudah bisa SetDevice + send_play_handle = bass.BASS_StreamCreateFile(false, namafile, 0, 0, 0); + if (send_play_handle==0) { + raise_bass_error("SendFile","StreamCreateFile"); + return false; + } + + // sampai sini, send_play_handle sudah create + if (send_channelinfo==null) send_channelinfo = new BASS_CHANNELINFO(); + if (!bass.BASS_ChannelGetInfo(send_play_handle, send_channelinfo)) { + raise_bass_error("SendFile","ChannelGetInfo"); + return false; + } + + // ambil channel info, cocokin dengan samplingrate + send_channelinfo.read(); + if (send_channelinfo.freq != send_samplingrate) { + raise_log_event("File="+namafile+" Samplingrate="+send_channelinfo.freq+", Compatible Samplingrate is "+send_samplingrate); + + close_sending(); + return false; + } + + if (!bass.BASS_ChannelSetAttribute(send_play_handle, bassconstant.BASS_ATTRIB_VOL, 1.0f)) { + raise_bass_error("SendFile","ChannelSetAttribute::Attrib_Vol"); + close_sending(); + return false; + } + + if (!bass.BASS_ChannelSetAttribute(send_play_handle, bassconstant.BASS_ATTRIB_NOBUFFER, 1)) { + raise_bass_error("SendFile","ChannelSetAttribute::Attrib_NoBuffer"); + close_sending(); + return false; + } + + + if (!bass.BASS_ChannelSetAttribute(send_play_handle, bassconstant.BASS_ATTRIB_GRANULE, granulesize)) { + raise_bass_error("SendFile","ChannelSetAttribute::Attrib_Granule"); + close_sending(); + return false; + } + + sendfile_duration = 0; + sendfile_size = bass.BASS_ChannelGetLength(send_play_handle, bassconstant.BASS_POS_BYTE); + if (sendfile_size != -1) { + // bisa baca size dalam bytes + sendfile_duration = bass.BASS_ChannelBytes2Seconds(send_play_handle, sendfile_size); + if (sendfile_duration<0) sendfile_duration = 0; + } + + sendfileproc = create_dspproc(); + + if (bass.BASS_ChannelSetDSP(send_play_handle, sendfileproc, null, 10)==0) { + raise_bass_error("SendFile","ChannelSetDSP"); + // gak usah return false, sikat aja + } + + mp3writer = new HandleMP3Writer(); + mp3writer.Initialize(null, ""); // supaya tidak muncul event di B4A / B4J + + mp3writer.SetHandleMp3WriterEvent(this); /// supaya MP3 Encoder event muncul + if (!mp3writer.CreateFile_from_Handle("",send_play_handle)) {// tidak create file + raise_log_event("Faield to create MP3 Encoder"); + close_sending(); + return false; + }; + + // sampai sini, encoder MP3 sudah siap, tinggal dijalankan di thread + sendfile_vu =0; + sendfile_elapsed = 0; + sendfile_ratio = 0; + sendfilethread sft = new sendfilethread(loopcount,namafile); + sft.start(); + return true; + } + + private class sendfilethread extends Thread{ + private int loopcount; + private String namafile; + sendfilethread(int loop, String filename){ + loopcount = loop; + namafile = filename; + } + public void run() { + if (bass.BASS_Start()){ + if (bass.BASS_ChannelPlay(send_play_handle, true)) { + // sampai sini sudah playing yang pertama + long tick = DateTime.getNow(); + int temp_rounding = 0; + raise_sendfilestarted_event(namafile,true); + send_isplaying = true; + int playstatus = 0; + while(true) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + raise_log_exception("SendFileThread","sleep",e); + break; + } + if (!send_isplaying) break; + if (send_play_handle==0) break; + if (bass==null) break; + playstatus = bass.BASS_ChannelIsActive(send_play_handle); + if (playstatus!=bassconstant.BASS_ACTIVE_PLAYING) { + // berhenti + if (loopcount>0) { + // kalau ada looping + loopcount-=1; + if (loopcount==0) { + // kalau looping dah habis + break; + } + } + + // sampai sini, berarti masih looping + if (!bass.BASS_ChannelPlay(send_play_handle, true)) { + raise_bass_error("SendFileThread","ChannelPlay::loop"); + break; + } + } + + sendfile_elapsed = (double)(DateTime.getNow()-tick) / DateTime.TicksPerSecond; + if (sendfilemp3count>0) { + if (sendfilebytecount>0) { + sendfile_ratio = (double) sendfilemp3count / sendfilebytecount; + temp_rounding = (int)(sendfile_ratio * 100 * 100); // mau dibulatin 2 desimal, dan dijadikan persen + sendfile_ratio = temp_rounding / 100.0; // balikin lagi 2 desimal dan persen + } + } + + raise_sendfilestatistic_event(); + } + + // sampai sini sudah selesai + if (mp3writer!=null) { + mp3writer.SetHandleMp3WriterEvent(null); + + } + close_sending(); + raise_sendfilestopped_event(namafile); + + } else { + raise_bass_error("SendFileThread","ChannelPlay"); + raise_sendfilestarted_event(namafile,false); + } + } else { + raise_bass_error("SendFileThread","Start"); + raise_sendfilestarted_event(namafile,false); + } + } + } + + private void close_sending() { + send_isplaying = false; + if (send_play_handle!=0) { + if (bass.BASS_ChannelStop(send_play_handle)) { + // bisa di-stop, berarti send_play_handle valid + if (!bass.BASS_StreamFree(send_play_handle)) { + raise_bass_error("close_sending","StreamFree"); + } + } else raise_bass_error("close_sending","ChannelStop"); + send_play_handle = 0; + } + + // revisi 07-10-2020 + // tidak usah stop dan free, karena kepake function lain + //if (bass.BASS_SetDevice(send_devid)) { + // bisa set device, berarti device sudah init + // if (!bass.BASS_Stop()){ + // raise_bass_error("close_sending","Stop"); + // } + + // if (send_devid!=0) { + // if (!bass.BASS_Free()) { + // raise_bass_error("close_sending","Free"); + // } + // } + //} + } + + /** + * Check if FWS Audio Master is initialized + */ + public boolean IsInitialized() { + return inited; + } + + /** + * Test Playback Device is usable or not + * @param DeviceID : device ID, 0= no sound, 1.... = real hardware + * @return true if usable, or false if not + */ + public boolean Test_PlaybackDevice(int DeviceID) { + BASS_DEVICEINFO dev = new BASS_DEVICEINFO(); + if (bass.BASS_GetDeviceInfo(DeviceID, dev)) { + Bass_DeviceInfo devinfo = new Bass_DeviceInfo(DeviceID, dev); + if (devinfo.isvalid) { + if (devinfo.IsEnabled()) { + return true; + } + } + } + return false; + } + + /** + * Test Recorder Device is usable or not + * @param DeviceID : device ID, 0 = first recorder + * @return true if usable, or false if not + */ + public boolean Test_RecorderDevice(int DeviceID) { + BASS_DEVICEINFO dev = new BASS_DEVICEINFO(); + if (bass.BASS_RecordGetDeviceInfo(DeviceID, dev)) { + Bass_DeviceInfo devinfo = new Bass_DeviceInfo(DeviceID, dev); + if (devinfo.isvalid) { + if (devinfo.IsEnabled()) { + return true; + } + } + } + return false; + } + + private boolean SetPlaybackDevice(int DeviceID, String function) { + if (!bass.BASS_SetDevice(DeviceID)) { + // tidak bisa set device, berarti belum inited atau DeviceID tidak ada + int errcode = bass.BASS_ErrorGetCode(); + if (errcode==bassconstant.BASS_ERROR_DEVICE) { + // device ID tidak ada + raise_log_event("Playback failed, DeviceID="+DeviceID+" is not available"); + return false; + } else if (errcode==bassconstant.BASS_ERROR_INIT) { + // belum di-init, maka sekarang init + if (!bass.BASS_Init(DeviceID, send_samplingrate, send_initflag)) { + raise_bass_error(function,"Init"); + return false; + } + } + } + return true; + } + + public AudioFileInformation GetAudioFileInformation(String path) { + AudioFileInformation result = new AudioFileInformation(); + result.setFilePath(path); + File ff = new File(path); + if (!ff.exists()) return result; + + result.setIsFound(ff.exists()); + + if (!SetPlaybackDevice(0,"GetAudioFileInformation")) return result; + + // sampai sini sudah bisa setdevice + int hh = 0; + hh = bass.BASS_StreamCreateFile(false,path, 0, 0, 0); + + if (hh==0) { + raise_bass_error("GetAudioFileInformation","StreamCreateFile "+path); + return result; + } + + long sz = bass.BASS_ChannelGetLength(hh, bassconstant.BASS_POS_BYTE); + if (sz==-1) { + raise_bass_error("GetAudioFileInformation","ChannelGetLength"); + bass.BASS_StreamFree(hh); + return result; + } + + result.setSizeInBytes(sz); + double dur = bass.BASS_ChannelBytes2Seconds(hh, sz); + if (dur<=0) { + raise_bass_error("GetAudioFileInformation","ChannelBytes2Seconds"); + bass.BASS_StreamFree(hh); + return result; + } + + result.setLengthInSeconds(dur); + result.setIsValid(true); + bass.BASS_StreamFree(hh); + + return result; + } + + 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_bass_error(String function, String command) { + raise_log_event("BASS Error on Function="+function+", Command="+command+", Code="+bass.GetBassErrorString()); + } + + 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_sendfilestarted_event(String namafile, boolean success) { + if (need_sendfilestarted_event) ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_sendfilestarted", false, new Object[] {namafile, success}); + } + + private void raise_sendfilestopped_event(String namafile) { + if (need_sendfilestopped_event) ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_sendfilestopped", false, new Object[] {namafile}); + } + + private void raise_micpagingstarted_event(boolean success) { + if (need_micpagingstarted_event) ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_micpagingstarted", false, new Object[] {success}); + } + + private void raise_micpagingstopped_event() { + if (need_micpagingstopped_event) ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_micpagingstopped", false, null); + } + + private void raise_sendfilemp3bytes_event(byte[] value) { + if (need_sendfilemp3bytes_event) ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_sendfilemp3bytes", false, new Object[] {value}); + } + + private void raise_micmp3bytes_event(byte[] value) { + if (need_micmp3bytes_event) ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_micmp3bytes", false, new Object[] {value}); + } + + private void raise_micpcmbytes_event(byte[] value) { + if (need_micpcmbytes_event) ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_micpcmbytes", false, new Object[] {value}); + } + private void raise_sendfilepcmbytes_event(byte[] value) { + if (need_sendfilepcmbytes_event) ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_sendfilepcmbytes", false, new Object[] {value}); + } + + private void raise_sendfilestatistic_event() { + if (need_sendfilestatistic_event) ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_sendfilestatistic", false, new Object[] {sendfilebytecount, sendfilemp3count, sendfile_ratio, sendfile_vu, sendfile_elapsed, sendfile_size}); + } + + private void raise_micstatistic_event() { + if (need_micstatistic_event) ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_micstatistic", false, new Object[] {micbytecount, micmp3count, mic_ratio, mic_vu, mic_elapsed}); + } + ///////// Events from HandleMP3Writer ////////////////////////// + + @Override + @BA.Hide + public void mp3writer_log(String msg) { + raise_log_event("MP3 Encoder Log : "+msg); + + } + + @Override + @BA.Hide + public void mp3writer_mp3bytes(int sourcehandle, byte[] bb) { + int length = bb.length; + if (sourcehandle==send_play_handle) { + sendfilemp3count+= length; + + if (send_master instanceof MasterSender) { + if (send_master.TCP_Clients_exists()) { + int sent = send_master.SendData_to_TCP_Clients(bb, length); + if (sent<1) { + raise_log_event("Data for send_play_handle to TCP_clients failed "+length+" bytes"); + } + + } + if (send_master.UDP_Clients_exists()) { + int sent = send_master.SendData_to_UDP_Clients(bb, length); + if (sent<1) { + raise_log_event("Data for send_play_handle to UDP_clients failed "+length+" bytes"); + } + + + } + } + raise_sendfilemp3bytes_event(bb); + } + + if (sourcehandle==mic_record_handle) { + micmp3count+= length; + + if (send_master instanceof MasterSender) { + if (send_master.TCP_Clients_exists()) { + int sent = send_master.SendData_to_TCP_Clients(bb, length); + if (sent<1) { + raise_log_event("Data for mic_record_handle to TCP_clients failed "+length+" bytes"); + } + } + if (send_master.UDP_Clients_exists()) { + int sent = send_master.SendData_to_UDP_Clients(bb, length); + if (sent<1) { + raise_log_event("Data for mic_record_handle to UDP_clients failed "+length+" bytes"); + } + + } + } + raise_micmp3bytes_event(bb); + + } + + + } + + @Override + @BA.Hide + public void localrecordstart(int sourcehandle, String namafile, boolean success) { + if (success) { + if (sourcehandle==send_play_handle) { + raise_log_event("SendFile is recorded as "+namafile); + } else if (sourcehandle==mic_record_handle) { + raise_log_event("OpenPaging is recorded as "+namafile); + } else { + raise_log_event("Handle="+sourcehandle+" is recorded as "+namafile); + } + } else { + if (sourcehandle==send_play_handle) { + raise_log_event("Recording of SendFile is failed"); + } else if (sourcehandle==mic_record_handle) { + raise_log_event("Recording of OpenPaging is failed"); + } else { + raise_log_event("Recording of Handle="+sourcehandle+" is failed"); + } + } + + } + + @Override + @BA.Hide + public void localrecordstop(int sourcehandle, String namafile) { + if (sourcehandle==send_play_handle) { + raise_log_event("Recording for SendFile is finished, result="+namafile); + } else if (sourcehandle==mic_record_handle) { + raise_log_event("Recording for OpenPaging is finished, result="+namafile); + } else { + raise_log_event("Recording for Handle="+sourcehandle+" is finished, result="+namafile); + } + + } + + + } diff --git a/src/FWSRelated/FWS_Audio_Slave.java b/src/FWSRelated/FWS_Audio_Slave.java new file mode 100644 index 0000000..6f6e5e8 --- /dev/null +++ b/src/FWSRelated/FWS_Audio_Slave.java @@ -0,0 +1,1543 @@ +package FWSRelated; + +import java.io.File; +import java.net.InetSocketAddress; +import java.nio.BufferOverflowException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.ShortBuffer; + +import com.sun.jna.Pointer; +import com.sun.jna.ptr.IntByReference; +import com.un4seen.bass.BASS; +import com.un4seen.bass.BASS.BASS_DEVICEINFO; +import com.un4seen.bass.BASS.BASS_FILEPROCS; +import com.un4seen.bass.BASS.IDSPPROC; +import com.un4seen.bass.BASS.RECORDPROC; +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; +import anywheresoftware.b4a.objects.collections.List; +import jbass.AudioFileInformation; +import jbass.Bass_DeviceInfo; +import jbass.HandleMP3Writer; +import jbass.HandleMp3WriterEvent; + +@BA.ShortName("FWS_Audio_Slave") +@BA.Events(values= { + "log(msg as string)", + "playbackstarted(filename as string, success as boolean)", + "playbackstopped(filename as string)", + "receivestreamingstarted(fromip as string, success as boolean)", + "receivestreamingstopped(fromip as string)", + "receivestreamingbuffer(remaining as int, freepercentage as float)", + "localrecordingstarted(sourcehandle as int, filename as string, success as boolean)", + "localrecordingstopped(sourcehandle as int, filename as string)", + "localrecordingmp3bytes(bb() as byte)", + "vuoutput(value as int)", + "vuinput(value as int)", + "playbackbufferingposition(value as int)", + "datareceiverconnected(fromip as string)", + "datareceiverdisconnected(fromip as string)", + "receivestreamingstatistic(pcmcount as int, mp3count as int, ratio as double, vu as int, elapsed as double)", + "playbackstatistic(pcmcount as int, vu as int, elapsed as double)", + "recordingstatistic(pcmcount as int, mp3count as int, ratio as double, vu as int, elapsed as double)" +}) +public class FWS_Audio_Slave implements DataReceiverEvent, HandleMp3WriterEvent { + + private BA bax; + private String event; + + + /// common events + private boolean need_log_event = false; + private boolean need_vuoutput_event = false; + private boolean need_vuinput_event = false; + + + private BASS bass; + private boolean inited = false; + + /// playback variables + private boolean need_playbackstarted_event = false; + private boolean need_playbackstopped_event = false; + private int devid = 0; + private int current_play_handle = 0; + private final int init_freq = 44100; + private final int init_flag = bassconstant.BASS_DEVICE_MONO | bassconstant.BASS_DEVICE_FREQ | bassconstant.BASS_DEVICE_16BITS; + + // revisi 20/11/2020, untuk streamcreatefile local file playback, karena sebelumnya mbrebet + private final int streamcreatefileflag = bassconstant.BASS_ASYNCFILE | bassconstant.BASS_SAMPLE_MONO; + + // revisi 12/10/2020, tambah flag BASS_STREAM_RESTRATE supaya download rate nya konstan + private final int streamcreateflag = bassconstant.BASS_SAMPLE_MONO | bassconstant.BASS_STREAM_RESTRATE; + private boolean isplayback = false; // status kalau audio output keluar, baik dari PlayFile atau dari NetworkStream + + private float playbackvol = 1.0f; + private DataReceiver opennetworkstreaming_dr; + private int playback_dsp = 0; + private int playback_pcmcount = 0; // PCM count untuk play file dari local disk + private int playback_VU = 0; + private double playback_elapsed = 0; + private IDSPPROC playback_proc; + + private int streaming_dsp = 0; + private int streaming_pcmcount = 0; // PCM count untuk network streaming + private int streaming_mp3count = 0; // MP3 count untuk network streaming + private int streaming_VU = 0; + private double streaming_ratio = 0; + private double streaming_elapsed = 0; + private IDSPPROC streaming_proc; + + /// recording variables + private int current_record_handle = 0; + private boolean isrecording = false; + private float recordvol = 1.0f; + private int recorddevid = -1; + + // revisi 12/10/2020 + // pake recordproc, gak usah angka ini + //private final int rec_maxbytes = 8000; + + private HandleMP3Writer micwriter; + private int record_pcmcount = 0; + private int record_mp3count = 0; + private double record_ratio = 0; + private double record_elapsed =0; + private int record_VU = 0; + private long record_start_tick = 0; + + /// network streaming variables + private String network_source_ip = ""; + private boolean isstreaming = false; // status kalau data streaming masih jalan + private final int streamingbuffer_size = 32 * 1024; + private ByteBuffer streamingbuffer; + private boolean need_receivestreamingstarted_event = false; + private boolean need_receivestreamingstopped_event = false; + private boolean need_receivestreamingbuffer_event = false; + + private boolean need_datareceiverconnected_event = false; + private boolean need_datareceiverdisconnected_event = false; + private HandleMP3Writer netstreamwriter; + + private boolean need_receivestreamingstatistic_event = false; + private boolean need_playbackstatistic_event = false; + private boolean need_recordingstatistic_event = false; + + /// event untuk micropone recording dan network streaming recording + private boolean need_localrecordingstarted_event = false; + private boolean need_localrecordingstopped_event = false; + private boolean need_localrecordingmp3bytes_event = false; + private boolean need_playbackbufferingposition_event = false; + + private String _recordfolder = System.getProperty("user.dir"); + private final String default_dateformat = DateTime.getDateFormat(); + private final String default_timeformat = DateTime.getTimeFormat(); + private final String recorder_dateformat = "ddMMyyyy"; + private final String recorder_timeformat = "HHmmss"; + private Object myobject; + private int bassversion = 0; + + /** + * Initialize FWS Audio Slave + * FWS audio slave is used in remote sites controller units + * @param caller : caller object + * @param eventname : eventname + */ + public void Initialize(BA ba, Object caller, String eventname) { + + bax = ba; + event = eventname; + myobject = caller; + if (bax instanceof BA) { + if (myobject!=null) { + if (!event.isEmpty()) { + need_log_event = ba.subExists(event+"_log"); + need_playbackstarted_event = ba.subExists(event+"_playbackstarted"); + need_playbackstopped_event = ba.subExists(event+"_playbackstopped"); + need_vuoutput_event = ba.subExists(event+"_vuoutput"); + need_vuinput_event = ba.subExists(event+"_vuinput"); + need_receivestreamingstarted_event = ba.subExists(event+"_receivestreamingstarted"); + need_receivestreamingstopped_event = ba.subExists(event+"_receivestreamingstopped"); + need_datareceiverconnected_event = ba.subExists(event+"_datareceiverconnected"); + need_datareceiverdisconnected_event = ba.subExists(event+"_datareceiverdisconnected"); + need_localrecordingstarted_event = ba.subExists(event+"_localrecordingstarted"); + need_localrecordingstopped_event = ba.subExists(event+"_localrecordingstopped"); + need_receivestreamingstatistic_event = ba.subExists(event+"_receivestreamingstatistic"); + need_playbackstatistic_event = ba.subExists(event+"_playbackstatistic"); + need_recordingstatistic_event = ba.subExists(event+"_recordingstatistic"); + need_receivestreamingbuffer_event = ba.subExists(event+"_receivestreamingbuffer"); + need_localrecordingmp3bytes_event = ba.subExists(event+"_localrecordingmp3bytes"); + need_playbackbufferingposition_event = ba.subExists(event+"_playbackbufferingposition"); + } + } + + } + + bass = new BASS(); + bassversion = bass.BASS_GetVersion(); + if (bassversion!=0) { + // revisi 20/11/2020, buat gedein playback buffer + bass.BASS_SetConfig(bassconstant.BASS_CONFIG_DEV_BUFFER, 90); + inited = true; + } + + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + // closing semua recording dan playback + } + }); + } + + private void raise_playbackbufferingposition(int value) { + if (need_playbackbufferingposition_event) bax.raiseEventFromDifferentThread(myobject, null, 0, event+"_playbackbufferingposition", false, new Object[] {value}); + } + + private void raise_localrecordingmp3bytes(byte[] bb) { + if (need_localrecordingmp3bytes_event) bax.raiseEventFromDifferentThread(myobject, null, 0, event+"_localrecordingmp3bytes", false, new Object[] {bb}); + } + + private void raise_receivestreamingbuffer(int remaining, float freepercent) { + if (need_receivestreamingbuffer_event) bax.raiseEventFromDifferentThread(myobject, null, 0, event+"_receivestreamingbuffer", false, new Object[] {remaining, freepercent}); + } + + private void raise_receivestreamingstatistic_event() { + if (need_receivestreamingstatistic_event) bax.raiseEventFromDifferentThread(myobject, null, 0, event+"_receivestreamingstatistic", false, new Object[] {streaming_pcmcount, streaming_mp3count, streaming_ratio, streaming_VU, streaming_elapsed}); + } + + private void raise_playbackstatistic_event() { + if (need_playbackstatistic_event) bax.raiseEventFromDifferentThread(myobject, null, 0, event+"_playbackstatistic", false, new Object[] {playback_pcmcount, playback_VU, playback_elapsed}); + } + + private void raise_recordingstatistic_event() { + + if (need_recordingstatistic_event) bax.raiseEventFromDifferentThread(myobject, null, 0, event+"_recordingstatistic", false, new Object[] {record_pcmcount, record_mp3count, record_ratio, record_VU, record_elapsed}); + } + + /** + * Check if FWS audio Slave is initialized or not + * @return true if inited + */ + public boolean IsInitialized() { + return inited; + } + + /** + * Get All Playback Devices + * @return list of BASS_DEVICEINFO + */ + public List Bass_GetDeviceInfos() { + List result = new List(); + result.Initialize(); + + if (inited) { + for(int ii=0;ii<10;ii++) { + BASS_DEVICEINFO tt = new BASS_DEVICEINFO(); + if (bass.BASS_GetDeviceInfo(ii, tt)) { + tt.read(); + if (tt.name==null) continue; + if (tt.name.isEmpty()) continue; + if (tt.driver==null) continue; + if (tt.driver.isEmpty()) continue; + result.Add(new Bass_DeviceInfo(ii,tt)); + //BA.Log("Playback index="+ii+", Name="+tt.name); + } + } + } + return result; + } + + /** + * Get all Recording devices + * @return list of BASS_DEVICEINFO + */ + public List Bass_RecordGetDeviceInfos(){ + List result = new List(); + result.Initialize(); + if (inited) { + for(int ii=0;ii<10;ii++) { + BASS_DEVICEINFO tt = new BASS_DEVICEINFO(); + if (bass.BASS_RecordGetDeviceInfo(ii, tt)) { + tt.read(); + if (tt.name==null) continue; + if (tt.name.isEmpty()) continue; + if (tt.driver==null) continue; + if (tt.driver.isEmpty()) continue; + result.Add(new Bass_DeviceInfo(ii,tt)); + //BA.Log("Recording index="+ii+", Name="+tt.name); + } + } + } + return result; + } + + ///////////////////////////////////// Microphone Monitoring Section /////////////////////////////////////////// + + /** + * Close Microphones + */ + public void Stop_Microphone() { + close_record(); + } + + private boolean SetRecorderDevice(int DeviceID) { + if (!bass.BASS_RecordSetDevice(DeviceID)) { + int errcode = bass.BASS_ErrorGetCode(); + if (errcode==bassconstant.BASS_ERROR_DEVICE) { + // Record Device ini tidak ada + raise_log_event("Record Device "+DeviceID+" is not found"); + return false; + } + if (errcode==bassconstant.BASS_ERROR_INIT) { + // Record Device belum init, sekarang coba di-init + if (!bass.BASS_RecordInit(DeviceID)) { + // gagal init + raise_bass_error("Start_Microphone","RecordInit"); + return false; + } + } + } + // sampai sini sudah init dan recordsetdevice + return true; + } + + /** + * Start Microphone recording + * Opening Microphone will automatically start MP3 recording , at folder Record_Folder + * Name will automatically assigned with format Microphone_date_time.mp3 + * @param DeviceID : recording device ID, 0 = first + * @param samplingrate : recording sampling rate, default Linux at 48000 + * @return true if Microphone is opened + */ + public boolean Start_Microphone(int DeviceID, int samplingrate) { + if (inited) { + close_record(); + + if (!SetRecorderDevice(DeviceID)) return false; + + recorddevid = DeviceID; + + // mentokin volume input + if (!bass.BASS_RecordSetInput(0, bassconstant.BASS_INPUT_ON, 1.0f)) { + raise_bass_error("Start_Microphone","RecordSetInput::0"); + } + + // revisi 12/10/2020, pake RECORDPROC + // karena pake MP3 enteng + current_record_handle = bass.BASS_RecordStart(samplingrate, 1, 0, myrecproc, null); + if (current_record_handle==0) { + raise_bass_error("Start_Microphone","RecordStart"); + return false; + } + + + // sampai sini, current_record_handle dah valid + if (!bass.BASS_ChannelSetAttribute(current_record_handle, bassconstant.BASS_ATTRIB_VOL, 1.0f)) { + raise_bass_error("Start_Microphone","ChannelSetAttribute::Attrib_Vol"); + } + + Setup_Mp3Writer(current_record_handle, "Microphone"); + + + // revisi 12/10/2020, pake RECORDPROC + //recordthread rx = new recordthread(); + //rx.start(); + + record_mp3count = 0; + record_pcmcount = 0; + record_VU = 0; + record_start_tick = DateTime.getNow(); + isrecording = true; + + return true; + } else { + raise_log_event("Start_Microphone failed, Audio not initialized"); + return false; + } + } + + RECORDPROC myrecproc = new RECORDPROC(){ + + @Override + public boolean RECORDPROC(int handle, Pointer buffer, int length, Pointer user) { + if (handle == current_record_handle) { + if (buffer instanceof Pointer) { + if (length > 0) { + // recordproc cuma dipake buat hitung VU dan Recording Statistic + // data asli, sudah diambil oleh HandleMP3Writer di internal BASS + //BA.Log("PCM bytes = "+length); + record_pcmcount+=length; + ByteBuffer bx = buffer.getByteBuffer(0, length); + IntByReference vu = new IntByReference(0); + Calculate_VU_and_Volume(bx, recordvol, vu); + + raise_vuinput_event(vu.getValue()); + record_VU = vu.getValue(); + record_elapsed = (double)(DateTime.getNow() - record_start_tick) / DateTime.TicksPerSecond; + if (record_mp3count>0) { + if (record_pcmcount>0) { + record_ratio = (int)((record_mp3count / record_pcmcount) * 10000)/100.0; + } + } + + raise_recordingstatistic_event(); + + return isrecording; // true = terus record, false = stop record + } + } + } + return false; + + + } + + }; + + private void close_record() { + isrecording = false; + if (current_record_handle!=0) { + if (!bass.BASS_ChannelStop(current_record_handle)) { // channelstop di recordhandle, sudah di-free-in + // tidak usah panggil streamfree + raise_bass_error("close_record","ChannelStop"); + } + current_record_handle =0; + + } + if (bass.BASS_RecordSetDevice(recorddevid)) { + // masih bisa recordsetdevice , berarti belum di-free-in + if (!bass.BASS_RecordFree()) { + raise_bass_error("close_record","RecordFree"); + } + recorddevid = -1; + } + + + } + + /** + * Check if Microphone is recording + * @return true if working + */ + public boolean Microphone_Is_Working() { + return isrecording; + } + + // revisi 12/10/2020, pake recordproc aja + // karena MP3 datanya dikit, gampang + /* + * private class recordthread extends Thread{ + * + * public void run() { + * + * int bytecount = 0; int temp_ratio = 0; long tick = DateTime.getNow(); + * while(true) { try { // revisi 12/10/2020 // karena MP3, rec_maxbytes berubah + * dari 2000 --> 8000 // sleep juga berubah dari 2ms ke 50 Thread.sleep(50); } + * catch (InterruptedException e) { break; } + * + * if (!isrecording) break; if (bass==null) break; if (current_record_handle==0) + * break; + * + * bytecount = bass.BASS_ChannelGetData(current_record_handle, null, + * bassconstant.BASS_DATA_AVAILABLE); if (bytecount<0) { // command gagal, break + * aja raise_bass_error("RecordThread","ChannelGetData::DATA_AVAILABLE"); break; + * } else if (bytecount0) { if (record_pcmcount>0) { record_ratio = record_mp3count + * / record_pcmcount; temp_ratio = (int)(record_ratio * 100 * 100); // dibuat + * persen dan pembulatan 2 desimal record_ratio = temp_ratio/100.0; } } + * + * raise_recordingstatistic_event(); } + * + * close_record(); } }; + */ + ////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + + ////////////////////////////////////// Network Streaming Section ////////////////////////////////////////////// + /** + * Stop current Streaming Playback + */ + public void Stop_Streaming() { + // revisi 12/10/2020, ganti jadi isstreaming = false + isstreaming = false; + //BA.Log("Stop_Streaming dipanggil"); + + //isplayback = false; + } + + /** + * Check if NetworkStreaming is receiving data + * @return true if working + */ + public boolean NetworkStreaming_is_Working() { + return isstreaming; + } + + /** + * Check if NetworkStreaming is Playing + * @return true if working + */ + public boolean NetworkStreaming_is_Playback() { + return isplayback; + } + + /** + * Open Network Streaming using NetworkDataReceiver class + * Create a NetworkDataReceiver first, initialize TCP / UDP / Multicast Connections, then use OpenNetworkStreaming + * check on event receivestreamingstarted and receivestreamingstopped for status + * Note : use_buffering --> will delay playback, but on slow network, will produce better sound + * @param dr : valid NetworkDataReceiver + * @param DeviceID : audio playback device ID, 0 = no sound, 1.... = real device + * @param use_buffering : if false, will play as soon as bytes arrive. If true, will wait until buffer full, then play + */ + public void OpenNetworkStreaming(DataReceiver dr, int DeviceID, boolean use_buffering) { + if (dr!=null) { + opennetworkstreaming_dr = dr; + opennetworkstreaming_dr.SetDataReceiverEvent(this); // supaya event DataReceiverEvent bisa masuk sini + + if (inited) { + close_playback(); // close previous playback , if available + + if (SetPlaybackDevice(DeviceID,"OpenNetworkStreaming")) { + // sampai sini sudah bisa SetDevice + devid = DeviceID; + network_source_ip = dr.GetCurrentNetworkSource(); + streaming_mp3count = 0; + streaming_pcmcount = 0; + streaming_VU = 0; + streaming_elapsed = 0; + streaming_ratio = 0; + + networkstreamthread ns = new networkstreamthread(use_buffering); + ns.start(); + + } else { + raise_receivestreamingstarted_event(dr.GetCurrentNetworkSource(),false); + raise_log_event("OpenNetworkStreaming SetPlaybackDevice DeviceID="+DeviceID+" return false"); + } + } else { + raise_receivestreamingstarted_event(dr.GetCurrentNetworkSource(),false); + raise_log_event("OpenNetworkStreaming failed, Audio not Initialized"); + } + } else { + raise_receivestreamingstarted_event("N/A",false); // IP N/A because DataReceiver is null + raise_log_event("OpenNetworkStreaming failed, DataReceiver is null"); + } + } + + private class networkstreamthread extends Thread{ + private FILECLOSEPROC fc; + private FILELENPROC fl; + private FILESEEKPROC fs; + private FILEREADPROC fr; + + private BASS_FILEPROCS fileproc; + private final float no_buffer; + + networkstreamthread(boolean buffering){ + no_buffer = buffering ? 0 : 1; + + fc = new FILECLOSEPROC() { + + @Override + public void FILECLOSEPROC(Pointer user) { + return; // do nothing here + + } + + }; + + fl = new FILELENPROC() { + + @Override + public long FILELENPROC(Pointer user) { + // return 0, artinya size awal tidak tau + return 0; + } + + }; + + fs = new FILESEEKPROC() { + + @Override + public boolean FILESEEKPROC(long offset, Pointer user) { + // return false, artinya tidak bisa seek + return false; + } + + }; + + fr = new FILEREADPROC() { + + @Override + public int FILEREADPROC(Pointer buffer, int length, Pointer user) { + // isi buffer dengan data dari streambuffer + if (!(streamingbuffer instanceof ByteBuffer)) { + + return 0; // kalau streambuffer gak ada, tutup aja + } + if (buffer==null) { + + return 0; // buffer dari FILEREADPROC sudah null + } + if (length<1) { + + return 0; // space available kurang dari 1 + } + + synchronized(streamingbuffer) { + if (streamingbuffer.position()<1) { + // gak ada datanya, wait di sini + try { + streamingbuffer.wait(); + } catch (InterruptedException e) { + raise_log_exception("FILEREADPROC::streambuffer.wait",e); + return 0; // tutup aja + } + } + + // sampai sini, ada datanya + byte[] xx = Read_StreamingBuffer(length); + if (xx!=null) { + + buffer.write(0, xx, 0, xx.length); // tulis ke buffer + if (!isstreaming) { + isstreaming = true; // sekrang baru streaming + } + return xx.length; + } else { + raise_log_event("FILEREADPROC Read_StreamingBuffer return null"); + return 0; + } + + } + } + + }; + + fileproc = new BASS_FILEPROCS(fc, fl, fr, fs); + + + } + + public void run() { + isstreaming = false; // reset dulu + if (streamingbuffer==null) streamingbuffer = ByteBuffer.allocateDirect(streamingbuffer_size); // streamingbuffer di-create di sini, sebelum streamcreatefileuser + if (!SetPlaybackDevice(devid,"networkstreamthread")) { + raise_receivestreamingstarted_event(network_source_ip,false); + raise_log_event("NetworkStreamThread SetPlaybackDevice DeviceID="+devid+" is false"); + return; + } + + current_play_handle = bass.BASS_StreamCreateFileUser(bassconstant.STREAMFILE_BUFFER, streamcreateflag, fileproc, null); /// nge-blok di sini + if (current_play_handle==0) { + // gagal open, streamingbuffer harus null + streamingbuffer = null; + if (opennetworkstreaming_dr!=null) { + opennetworkstreaming_dr.SetDataReceiverEvent(null); + } + raise_bass_error("NetworkStreamThread","StreamCreateFileUser"); + raise_receivestreamingstarted_event(network_source_ip,false); + return; + } + + // sampai sini, isstreaming sudah true, karena FILEREADPROC sudah ada data masuk + + if (!bass.BASS_ChannelSetAttribute(current_play_handle, bassconstant.BASS_ATTRIB_VOL, 1.0f)) { + // gagal open, streamingbuffer harus null + streamingbuffer = null; + if (opennetworkstreaming_dr!=null) { + opennetworkstreaming_dr.SetDataReceiverEvent(null); + } + raise_bass_error("NetworkStreamThread","ChannelSetAttribute:::Attrib_Vol"); + raise_receivestreamingstarted_event(network_source_ip,false); + bass.BASS_StreamFree(current_play_handle); + return; + } + + + if (!bass.BASS_ChannelSetAttribute(current_play_handle, bassconstant.BASS_ATTRIB_NOBUFFER, no_buffer)) { + // gagal open, streamingbuffer harus null + streamingbuffer = null; + if (opennetworkstreaming_dr!=null) { + opennetworkstreaming_dr.SetDataReceiverEvent(null); + } + raise_bass_error("PlayFile","ChannelSetAttribute:Attrib_NoBuffer"); + raise_receivestreamingstarted_event(network_source_ip,false); + bass.BASS_StreamFree(current_play_handle); + return; + } + +// // dicoba network streaming tidak usah pake beginian +// if (!bass.BASS_ChannelSetAttribute(current_play_handle, bassconstant.BASS_ATTRIB_GRANULE, 500)) { +// // gagal open, streamingbuffer harus null +// streamingbuffer = null; +// if (opennetworkstreaming_dr!=null) { +// opennetworkstreaming_dr.SetDataReceiverEvent(null); +// } +// raise_bass_error("PlayFile","ChannelSetAttribute::Attrib_Granule"); +// raise_receivestreamingstarted_event(network_source_ip,false); +// bass.BASS_StreamFree(current_play_handle); +// return; +// } + + streaming_proc = create_dspproc(); + streaming_dsp = bass.BASS_ChannelSetDSP(current_play_handle, streaming_proc, null, 1); + if (streaming_dsp==0) { + // tidak bisa set DSP + // gagal open, streamingbuffer harus null + streamingbuffer = null; + if (opennetworkstreaming_dr!=null) { + opennetworkstreaming_dr.SetDataReceiverEvent(null); + } + raise_bass_error("NetworkStreamThread","ChannelSetDSP"); + raise_receivestreamingstarted_event(network_source_ip,false); + bass.BASS_StreamFree(current_play_handle); + return; + } + + if (!bass.BASS_Start()) { + // gagal open, streamingbuffer harus null + streamingbuffer = null; + if (opennetworkstreaming_dr!=null) { + opennetworkstreaming_dr.SetDataReceiverEvent(null); + } + raise_bass_error("NetworkStreamThread","Start"); + raise_receivestreamingstarted_event(network_source_ip,false); + bass.BASS_StreamFree(current_play_handle); + return; + } + + Setup_Mp3Writer(current_play_handle,"NetworkStreaming"); + + if (bass.BASS_ChannelPlay(current_play_handle, false)) { + raise_receivestreamingstarted_event(network_source_ip,true); + int playstatus = 0; + isplayback = true; + long tick = DateTime.getNow(); + int temp_ratio = 0; + int bufposition = 0; + while(true) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + raise_log_exception("NetworkStreamThread",e); + break; + } + + if (!isstreaming) { + //BA.Log("Isstreaming = false"); + break; // udah gak streaming + } + if (!isplayback) { + //BA.Log("isplayback = false"); + break; // audio output udah gak keluar + } + + if (current_play_handle==0) { + //BA.Log("current_play_handle = 0"); + break; + } + playstatus = bass.BASS_ChannelIsActive(current_play_handle); + if (playstatus==bassconstant.BASS_ACTIVE_STOPPED) { + //BA.Log("Playstatus = STOPPED"); + break; // kalau stop, break + } + if (playstatus==bassconstant.BASS_ACTIVE_PAUSED) { + //BA.Log("Playstatus = PAUSED"); + break; // kalau paused, break + } + // kalau active dan stalled, biarin aja + + streaming_elapsed = (double)(DateTime.getNow() - tick) / DateTime.TicksPerSecond; + if (streaming_mp3count>0) { + if (streaming_pcmcount>0) { + streaming_ratio = streaming_mp3count / streaming_pcmcount; + temp_ratio = (int)(streaming_ratio * 100 * 100); // mau dibuat persen dan pembulatan 2 desimal + streaming_ratio = temp_ratio / 100.0; + } + } + + raise_receivestreamingstatistic_event(); + bufposition = bass.BASS_ChannelGetData(current_play_handle, null, bassconstant.BASS_DATA_AVAILABLE); + if (bufposition<0) bufposition = 0; + raise_playbackbufferingposition(bufposition); + } + //BA.Log("Keluar dari networkstreamthread"); + //double wait_until_zero = 0; + + // revisi 12/10/2020 + // ngabisin buffer di sini + while(bufposition>0) { + bufposition = bass.BASS_ChannelGetData(current_play_handle, null, bassconstant.BASS_DATA_AVAILABLE); + if (bufposition<=0) bufposition = 0; + raise_playbackbufferingposition(bufposition); + + if (bufposition>0) { + //wait_until_zero = bass.BASS_ChannelBytes2Seconds(current_play_handle, bufposition); + //BA.Log("Wait time until buffer zero = "+String.format("%.2f", wait_until_zero)); + try { + Thread.sleep(500); + } catch (InterruptedException e) { + break; + } + } + } + + + + raise_receivestreamingstopped_event(network_source_ip); + if (streamingbuffer instanceof ByteBuffer) { + streamingbuffer.clear(); + } + streamingbuffer = null; // dilenyapkan + + + } else { + // gagal open, streamingbuffer harus null + streamingbuffer = null; + if (opennetworkstreaming_dr!=null) { + opennetworkstreaming_dr.SetDataReceiverEvent(null); + } + raise_bass_error("NetworkStreamThread","ChannelPlay"); + raise_receivestreamingstarted_event(network_source_ip,false); + } + + close_playback(); + + } + } + + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + + ////////////////////////////////////// Local File Playback Section ////////////////////////////////////////////// + /** + * Stop current File Playback + */ + public void Stop_PlayFile() { + isplayback = false; + //BA.Log("Stop_PlayFile dipanggil"); + } + + public AudioFileInformation GetAudioFileInformation(String path) { + AudioFileInformation result = new AudioFileInformation(); + result.setFilePath(path); + File ff = new File(path); + if (!ff.exists()) return result; + + result.setIsFound(ff.exists()); + + if (!SetPlaybackDevice(0,"GetAudioFileInformation")) return result; + + // sampai sini sudah bisa setdevice + int hh = 0; + hh =bass.BASS_StreamCreateFile(false,path, 0, 0, 0); + if (hh==0) { + raise_bass_error("GetAudioFileInformation","StreamCreateFile "+path); + return result; + } + + long sz = bass.BASS_ChannelGetLength(hh, bassconstant.BASS_POS_BYTE); + if (sz==-1) { + raise_bass_error("GetAudioFileInformation","ChannelGetLength"); + bass.BASS_StreamFree(hh); + return result; + } + + result.setSizeInBytes(sz); + double dur = bass.BASS_ChannelBytes2Seconds(hh, sz); + if (dur<=0) { + raise_bass_error("GetAudioFileInformation","ChannelBytes2Seconds"); + bass.BASS_StreamFree(hh); + return result; + } + + result.setLengthInSeconds(dur); + result.setIsValid(true); + bass.BASS_StreamFree(hh); + + return result; + } + + /** + * Play a local File using MessageInformation object + * on return true, means audio file can be found and opened + * check on event playbackstarted and playbackstopped for real playback + * Playfile only play at audio output, not streaming back to server + * @param DeviceID : audio playback device ID, 0 = no sound, 1.... = real device + * @param filenya : valid MessageInformation object + * @param loopcount : positive numbers = how many times audio want to play, 0 = looping forever + * @return true if MessageInformation is valid and can be played + */ + public boolean PlayMessageInformation(int DeviceID, AudioFileInformation filenya, int loopcount) { + if (filenya instanceof AudioFileInformation) { + if (filenya.getIsValid()) { + return PlayFile(DeviceID, filenya.getFilePath(), loopcount); + } + } + return false; + } + + /** + * Playback a local File + * on return true, means audio file can be found and opened + * check on event playbackstarted and playbackstopped for real playback + * Playfile only play at audio output, not streaming back to server + * @param DeviceID : audio playback device ID, 0 = no sound, 1.... = real device + * @param filename : audio file + * @param loopcount : positive numbers = how many times audio want to play, 0 = looping forever + * @return true if audio file can be opened + */ + public boolean PlayFile(int DeviceID, String filename, int loopcount) { + File ff = new File(filename); + if (ff.exists()) { + if (inited) { + //BA.Log("Close previous Playback"); + + close_playback(); // close previous playback, if available + + if (SetPlaybackDevice(DeviceID,"PlayFile")) { + //BA.Log("SetPlaybackDevice to "+DeviceID+" is success"); + } else { + //BA.Log("Unable to SetPlaybackDevice to "+DeviceID); + return false; + } + + // sampai sini sudah bisa SetDevice + devid = DeviceID; + current_play_handle = bass.BASS_StreamCreateFile(false, filename, 0, 0, streamcreatefileflag); + //BA.Log("Current_Play_Handle for "+filename+" is "+current_play_handle); + + if (current_play_handle!=0) { + // bisa diopen + + if (!bass.BASS_ChannelSetAttribute(current_play_handle, bassconstant.BASS_ATTRIB_VOL, 1.0f)) { + // tidak bisa set volume + raise_bass_error("PlayFile","ChannelSetAttribute::Attrib_Vol"); + return false; + } +// + // playback lokal, tidak usah set macam macam attrib dan granule +// if (!bass.BASS_ChannelSetAttribute(current_play_handle, bassconstant.BASS_ATTRIB_NOBUFFER, 1)) { +// raise_bass_error("PlayFile","ChannelSetAttribute:Attrib_NoBuffer"); +// return false; +// } +// +// if (!bass.BASS_ChannelSetAttribute(current_play_handle, bassconstant.BASS_ATTRIB_GRANULE, 500)) { +// raise_bass_error("PlayFile","ChannelSetAttribute::Attrib_Granule"); +// return false; +// } + + // DSP buat statistic dan kalkulasi volume by software + playback_proc = create_dspproc(); + playback_dsp = bass.BASS_ChannelSetDSP(current_play_handle,playback_proc, null, 1); + if (playback_dsp==0) { + // tidak bisa set DSP + raise_bass_error("PlayFile","ChannelSetDSP"); + return false; + } + + playback_pcmcount = 0; + playback_VU = 0; + + playbackthread tx = new playbackthread(loopcount,filename); + tx.start(); + return true; + } else raise_bass_error("PlayFile","StreamCreateFile"); + + } else raise_log_event("PlayFile failed, FWS_Audio_Slave not initialized"); + } else raise_log_event("PlayFile failed, File="+filename+" is not exist"); + + + return false; + } + + private class playbackthread extends Thread { + private int loopcount = 0; + private String namafile; + playbackthread(int loop, String filename){ + loopcount = loop; + namafile = filename; + } + public void run() { + if (bass.BASS_Start()) { + if (bass.BASS_ChannelPlay(current_play_handle, true)) { + isplayback = true; + raise_playbackstarted_event(namafile,true); + long tick = DateTime.getNow(); + while(true) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + raise_log_exception("playbackthread",e); + } + if (!isplayback) break; + if (bass==null) break; + if (current_play_handle==0) break; + if (bass.BASS_ChannelIsActive(current_play_handle)!=bassconstant.BASS_ACTIVE_PLAYING) { + // berhenti + if (loopcount>0) { + // masih ada looping + loopcount-=1; + if (loopcount==0) break; // loopcount habis + } + + // selama masih ada loopcount, ulang lagi + if (!bass.BASS_ChannelPlay(current_play_handle, true)) { + raise_bass_error("playbackthread","ChannelPlay"); + break; + } + } + + playback_elapsed = (double)(DateTime.getNow() - tick) / DateTime.TicksPerSecond; + raise_playbackstatistic_event(); + int bufposition = bass.BASS_ChannelGetData(current_play_handle, null, bassconstant.BASS_DATA_AVAILABLE); + if (bufposition<0) bufposition = 0; + raise_playbackbufferingposition(bufposition); + } + raise_playbackstopped_event(namafile); + + } else { + raise_playbackstarted_event(namafile, false); // gagal playback + raise_bass_error("playbackthread","ChannelPlay"); + } + } else { + raise_playbackstarted_event(namafile,false); // gagal playback + raise_bass_error("playbackthread","Start"); + } + + + close_playback(); + } + } + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////Common functions ////////////////////////////////////////////// + private void Setup_Mp3Writer(int handle, String prefix) { + HandleMP3Writer mp3writer = new HandleMP3Writer(); + mp3writer.Initialize(bax, "mp3writer"); + mp3writer.SetHandleMp3WriterEvent(this); + if (!mp3writer.CreateFile_from_Handle(create_recording_filename(prefix), handle)) { + if (handle==current_play_handle) { + netstreamwriter = null; + raise_log_event("Failed to Create MP3 Writer for Playback"); + } else if (handle==current_record_handle) { + micwriter = null; + raise_log_event("Failed to Create MP3 Writer for Microphone"); + } + + } else { + if (handle==current_play_handle) { + netstreamwriter = mp3writer; + raise_log_event("MP3 Writer for Playback is started, Filename="+mp3writer.getCurrentFileName()); + } else if (handle==current_record_handle) { + micwriter = mp3writer; + raise_log_event("MP3 Writer for Microphone is started, Filename="+mp3writer.getCurrentFileName()); + } + + + + } + + } + + /** + * Test Playback Device is usable or not + * @param DeviceID : device ID, 0= no sound, 1.... = real hardware + * @return true if usable, or false if not + */ + public boolean Test_PlaybackDevice(int DeviceID) { + BASS_DEVICEINFO dev = new BASS_DEVICEINFO(); + if (bass.BASS_GetDeviceInfo(DeviceID, dev)) { + Bass_DeviceInfo devinfo = new Bass_DeviceInfo(DeviceID, dev); + if (devinfo.isvalid) { + if (devinfo.IsEnabled()) { + return true; + } + } + } + return false; + + } + + /** + * Test Recorder Device is usable or not + * @param DeviceID : device ID, 0 = first recorder + * @return true if usable, or false if not + */ + public boolean Test_RecorderDevice(int DeviceID) { + BASS_DEVICEINFO dev = new BASS_DEVICEINFO(); + if (bass.BASS_RecordGetDeviceInfo(DeviceID, dev)) { + Bass_DeviceInfo devinfo = new Bass_DeviceInfo(DeviceID, dev); + if (devinfo.isvalid) { + if (devinfo.IsEnabled()) { + return true; + } + } + } + return false; + } + + private boolean SetPlaybackDevice(int DeviceID, String function) { + if (!bass.BASS_SetDevice(DeviceID)) { + // tidak bisa set device, berarti belum inited atau DeviceID tidak ada + //BA.Log("SetPlaybackDevice belum bisa SetDevice"); + + int errcode = bass.BASS_ErrorGetCode(); + if (errcode==bassconstant.BASS_ERROR_DEVICE) { + // device ID tidak ada + //BA.Log("SetPlaybackDevice SetDevice gagal karena gak ada DeviceID = "+DeviceID); + + raise_log_event("Playback failed, DeviceID="+DeviceID+" is not available"); + return false; + } else if (errcode==bassconstant.BASS_ERROR_INIT) { + // belum di-init, maka sekarang init + //BA.Log("SetPlaybackDevice SetDevice gagal karena belum init DeviceID = "+DeviceID); + int flag = init_flag | bassconstant.BASS_DEVICE_DMIX; + //BA.Log("use BASS_DEVICE_DMIX flag in BASS_Init"); + + if (!bass.BASS_Init(DeviceID, init_freq, flag)) { + //BA.Log("SetPlaybackDevice BASS_init gagal di DeviceID = "+DeviceID); + raise_bass_error(function,"Init"); + return false; + } //else BA.Log("SetPlaybackDevice BASS_Init success on DeviceID = "+DeviceID); + } + } //else BA.Log("SetPlaybackDevice langsung bisa SetDevice, gak init dulu"); + return true; + } + + private IDSPPROC create_dspproc() { + IDSPPROC newproc = new IDSPPROC() { + + @Override + public void DSPPROC(int handle, int channel, Pointer bx, int length, Pointer user) { + if (length<1) return; + if (bx==null) return; + if (handle==0) return; + if (channel==0) return; + + + + IntByReference vu = new IntByReference(0); + Calculate_VU_and_Volume(bx.getByteBuffer(0, length), playbackvol, vu); + raise_vuoutput_event(vu.getValue()); + + if (handle==playback_dsp) { + playback_pcmcount+=length; + playback_VU = vu.getValue(); + } else if (handle==streaming_dsp) { + streaming_pcmcount+=length; + streaming_VU = vu.getValue(); + } + } + + }; + + return newproc; + } + + + private void Calculate_VU_and_Volume(ByteBuffer source, float vol, IntByReference vu) { + if (source==null) return; + ShortBuffer vubuf = source.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() * vol); // 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 + } + vu.setValue((int)((maxvalue/32767.0)*100)); + + } + + private void close_playback() { + isplayback = false; + raise_vuoutput_event(0); + + if (current_play_handle!=0) { + //BA.Log("Masih ada Current_play_handle, stop and free dulu"); + if (playback_dsp!=0) { + if (!bass.BASS_ChannelRemoveDSP(current_play_handle, playback_dsp)) { + raise_bass_error("close_playback","ChannelRemoveDSP::playback_dsp"); + } + playback_dsp = 0; + } + if (streaming_dsp!=0) { + if (!bass.BASS_ChannelRemoveDSP(current_play_handle, streaming_dsp)) { + raise_bass_error("close_playback","ChannelRemoveDSP::streaming_dsp"); + } + streaming_dsp = 0; + } + + bass.BASS_ChannelStop(current_play_handle); + bass.BASS_StreamFree(current_play_handle); + + current_play_handle = 0; + } + + + /* + * if (bass.BASS_SetDevice(devid)) { + * BA.Log("Masih bisa SetDevice "+devid+", Stop dulu "); + * ini if (!bass.BASS_Stop()) { raise_bass_error("close_playback","Stop"); } } + */ + + } + + /** + * Check if PlayFile is working + * @return true if playback + */ + public boolean getPlayFile_is_Working() { + if (current_play_handle!=0) { + return isplayback; + } + return false; + } + + /** + * Get / Set Record Volume + */ + public int getRecordVolume() { + return (int)(recordvol * 100); + } + + public void setRecordVolume(int value) { + if (value<0) value = 0; + recordvol = value/100.0f; + } + + /** + * Get / Set Playback Volume 0 - 100 + */ + public int getPlaybackVolume() { + return (int)(playbackvol * 100); + } + + public void setPlaybackVolume(int value) { + if (value<0) value = 0; + playbackvol = value / 100.0f; + } + + /** + * Get / Set Record Folder + */ + public String getRecordfolder() { + return _recordfolder; + } + + public void setRecordfolder(String value) { + File ff = new File(value.trim()); + if (ff.isDirectory()) { + // is directory and exist + _recordfolder = ff.getAbsolutePath(); + raise_log_event("setRecordFolder is set to "+value); + } else { + if (ff.mkdirs()) { + _recordfolder= ff.getAbsolutePath(); + raise_log_event("setRecordFolder is creating "+value); + } else { + raise_log_event("Failed to setRecordFolder to "+value); + } + } + + } + + /** + * Get MP3 writer assigned for Local Microphone + * @return null if not assigned + */ + public HandleMP3Writer getMicrophone_MP3Writer() { + return micwriter; + } + + /** + * Get how many bytes Microphone MP3 writer has write to disk + * @return -1 if invalid + */ + public int getMicrophone_MP3Writer_Size() { + if (micwriter!=null) { + if (micwriter.IsInitialized()) { + return micwriter.getSize(); + } + } + return -1; + } + + /** + * Get how many bytes Network Streaming Receiver has write to disk + * @return -1 if invalid + */ + public int getNetworkStream_MP3Writer_Size() { + if (netstreamwriter!=null) { + if (netstreamwriter.IsInitialized()) { + return netstreamwriter.getSize(); + } + } + return -1; + } + + /** + * Get MP3 writer assigned for Network Streaming Receiver + * @return null if not assinged + */ + public HandleMP3Writer getNetworkStream_MP3Writer() { + return netstreamwriter; + } + + + + private String create_recording_filename(String prefix) { + + StringBuilder str = new StringBuilder(); + if (!prefix.isEmpty()) { + str.append(prefix).append("_"); + } + + long tt = DateTime.getNow(); + DateTime.setDateFormat(recorder_dateformat); + DateTime.setTimeFormat(recorder_timeformat); + str.append(DateTime.Date(tt)).append("_").append(DateTime.Time(tt)); + DateTime.setDateFormat(default_dateformat); + DateTime.setTimeFormat(default_timeformat); + + str.append(".mp3"); + + File target = new File(_recordfolder, str.toString()); + return target.getAbsolutePath(); + } + + private byte[] Read_StreamingBuffer(int length) { + if (!(streamingbuffer instanceof ByteBuffer) ) { + + return null; + } + if (length<1) { + + return null; + } + + synchronized(streamingbuffer) { + streamingbuffer.flip(); // pindah mode baca + int remaining = streamingbuffer.remaining(); + + if (remaining > 0) { + if (remaining0) { + for(int ii=0;ii0) { + 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; + } +} diff --git a/src/MastersenderRelated/MasterSender.java b/src/MastersenderRelated/MasterSender.java new file mode 100644 index 0000000..35d879e --- /dev/null +++ b/src/MastersenderRelated/MasterSender.java @@ -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.length0) { + 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;ii65535) { + 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.length0) { + 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;jj0) { +// 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"); + } + } + } + } + + + } +} diff --git a/src/MastersenderRelated/MasterSenderEvent.java b/src/MastersenderRelated/MasterSenderEvent.java new file mode 100644 index 0000000..c6ea46e --- /dev/null +++ b/src/MastersenderRelated/MasterSenderEvent.java @@ -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); +} diff --git a/src/MastersenderRelated/TCPSocketForMasterSender.java b/src/MastersenderRelated/TCPSocketForMasterSender.java new file mode 100644 index 0000000..75a02cb --- /dev/null +++ b/src/MastersenderRelated/TCPSocketForMasterSender.java @@ -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; + } + +} diff --git a/src/QZARelated/DataSender.java b/src/QZARelated/DataSender.java new file mode 100644 index 0000000..b1531fc --- /dev/null +++ b/src/QZARelated/DataSender.java @@ -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;ii0) 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;ii1500) 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); + + } + } + + + + + + diff --git a/src/QZARelated/QZA_UDP_MicrophoneSender.java b/src/QZARelated/QZA_UDP_MicrophoneSender.java new file mode 100644 index 0000000..df06889 --- /dev/null +++ b/src/QZARelated/QZA_UDP_MicrophoneSender.java @@ -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); + + } + + + +} diff --git a/src/QZARelated/QZA_UDP_StreamReceiver.java b/src/QZARelated/QZA_UDP_StreamReceiver.java new file mode 100644 index 0000000..39d0e8a --- /dev/null +++ b/src/QZARelated/QZA_UDP_StreamReceiver.java @@ -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); + } +} diff --git a/src/aas/AAS_Receiver_Multichannel.java b/src/aas/AAS_Receiver_Multichannel.java new file mode 100644 index 0000000..c6ad79d --- /dev/null +++ b/src/aas/AAS_Receiver_Multichannel.java @@ -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=0) { + if (index0) { + 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}); + } + +} diff --git a/src/com/un4seen/bass/BASS.java b/src/com/un4seen/bass/BASS.java new file mode 100644 index 0000000..8142811 --- /dev/null +++ b/src/com/un4seen/bass/BASS.java @@ -0,0 +1,1174 @@ +/* + BASS 2.4 Java class + Copyright (c) 1999-2019 Un4seen Developments Ltd. + + See the BASS.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 com.sun.jna.ptr.FloatByReference; +import com.sun.jna.ptr.IntByReference; + +import anywheresoftware.b4a.BA; + + +public class BASS +{ + public static final int BASSVERSION = 0x204; // API version + public static final String BASSVERSIONTEXT = "2.4"; + + public static String GetBassErrorString() { + int errcode = BASS_ErrorGetCode(); + return GetBassErrorString(errcode); + } + + public static String GetBassErrorString(int err) { + switch(err) { + case 0 : return "NO ERROR"; + case 1 : return "ERROR MEMORY"; + case 2 : return "ERROR FILEOPEN"; + case 3 : return "ERROR DRIVER"; + case 4 : return "ERROR BUFLOST"; + case 5 : return "ERROR HANDLE"; + case 6 : return "ERROR FORMAT"; + case 7 : return "ERROR POSITION"; + case 8 : return "ERROR INIT"; + case 9 : return "ERROR START"; + case 10: return "ERROR SSL"; + + case 14: return "ERROR ALREADY"; + case 17: return "ERROR_NOTAUDIO"; + case 18: return "ERROR NOCHAN"; + case 19: return "ERROR ILLTYPE"; + case 20: return "ERROR ILLPARAM"; + + case 21: return "ERROR NO3D"; + case 22: return "ERROR NOEAX"; + case 23: return "ERROR DEVICE"; + case 24: return "ERROR_NOPLAY"; + case 25: return "ERROR FREQ"; + case 27: return "ERROR NOTFILE"; + case 29: return "ERROR NOHW"; + + case 31: return "ERROR EMPTY"; + case 32: return "ERROR NONET"; + case 33: return "ERROR CREATE"; + case 34: return "ERROR NOFX"; + case 37: return "ERROR NOTAVAIL"; + case 38: return "ERROR DECODE"; + case 39: return "ERROR DX"; + + case 40: return "ERROR TIMEOUT"; + case 41: return "ERROR FILEFORM"; + case 42: return "ERROR SPEAKER"; + case 43: return "ERROR VERSION"; + case 44: return "ERROR CODEC"; + case 45: return "ERROR ENDED"; + case 46: return "ERROR_BUSY"; + case 47: return "ERROR_UNSTREAMABLE"; + + case 500: return "ERROR_JAVA_CLASS"; + default : + return "Unknown error, code="+err; + } + } + + @BA.ShortName("BASS_CONSTANT") + public static class bassconstant{ + // Error codes returned by BASS_ErrorGetCode + public static final int BASS_OK = 0; // all is OK + public static final int BASS_ERROR_MEM = 1; // memory error + public static final int BASS_ERROR_FILEOPEN = 2; // can't open the file + public static final int BASS_ERROR_DRIVER = 3; // can't find a free/valid driver + public static final int BASS_ERROR_BUFLOST = 4; // the sample buffer was lost + public static final int BASS_ERROR_HANDLE = 5; // invalid handle + public static final int BASS_ERROR_FORMAT = 6; // unsupported sample format + public static final int BASS_ERROR_POSITION = 7; // invalid position + public static final int BASS_ERROR_INIT = 8; // BASS_Init has not been successfully called + public static final int BASS_ERROR_START = 9; // BASS_Start has not been successfully called + public static final int BASS_ERROR_SSL = 10; // SSL/HTTPS support isn't available + public static final int BASS_ERROR_ALREADY = 14; // already initialized/paused/whatever + public static final int BASS_ERROR_NOTAUDIO = 17; // file does not contain audio + public static final int BASS_ERROR_NOCHAN = 18; // can't get a free channel + public static final int BASS_ERROR_ILLTYPE = 19; // an illegal type was specified + public static final int BASS_ERROR_ILLPARAM = 20; // an illegal parameter was specified + public static final int BASS_ERROR_NO3D = 21; // no 3D support + public static final int BASS_ERROR_NOEAX = 22; // no EAX support + public static final int BASS_ERROR_DEVICE = 23; // illegal device number + public static final int BASS_ERROR_NOPLAY = 24; // not playing + public static final int BASS_ERROR_FREQ = 25; // illegal sample rate + public static final int BASS_ERROR_NOTFILE = 27; // the stream is not a file stream + public static final int BASS_ERROR_NOHW = 29; // no hardware voices available + public static final int BASS_ERROR_EMPTY = 31; // the MOD music has no sequence data + public static final int BASS_ERROR_NONET = 32; // no internet connection could be opened + public static final int BASS_ERROR_CREATE = 33; // couldn't create the file + public static final int BASS_ERROR_NOFX = 34; // effects are not available + public static final int BASS_ERROR_NOTAVAIL = 37; // requested data/action is not available + public static final int BASS_ERROR_DECODE = 38; // the channel is a "decoding channel" + public static final int BASS_ERROR_DX = 39; // a sufficient DirectX version is not installed + public static final int BASS_ERROR_TIMEOUT = 40; // connection timedout + public static final int BASS_ERROR_FILEFORM = 41; // unsupported file format + public static final int BASS_ERROR_SPEAKER = 42; // unavailable speaker + public static final int BASS_ERROR_VERSION = 43; // invalid BASS version (used by add-ons) + public static final int BASS_ERROR_CODEC = 44; // codec is not available/supported + public static final int BASS_ERROR_ENDED = 45; // the channel/file has ended + public static final int BASS_ERROR_BUSY = 46; // the device is busy + public static final int BASS_ERROR_UNSTREAMABLE = 47; // unstreamable file + public static final int BASS_ERROR_UNKNOWN = -1; // some other mystery problem + + public static final int BASS_ERROR_JAVA_CLASS = 500; // object class problem + + // DSCAPS for BASS_INFO flags + public static final int DSCAPS_CERTIFIED= 64; + public static final int DSCAPS_CONTINUOUSRATE= 16; + public static final int DSCAPS_EMULDRIVER=32; + public static final int DSCAPS_SECONDARY16BIT=2048; + public static final int DSCAPS_SECONDARY8BIT=1024; + public static final int DSCAPS_SECONDARYMONO=256; + public static final int DSCAPS_SECONDARYSTEREO=512; + + + + // BASS_SetConfig options + public static final int BASS_CONFIG_BUFFER = 0; + public static final int BASS_CONFIG_UPDATEPERIOD = 1; + public static final int BASS_CONFIG_GVOL_SAMPLE = 4; + public static final int BASS_CONFIG_GVOL_STREAM = 5; + public static final int BASS_CONFIG_GVOL_MUSIC = 6; + public static final int BASS_CONFIG_CURVE_VOL = 7; + public static final int BASS_CONFIG_CURVE_PAN = 8; + public static final int BASS_CONFIG_FLOATDSP = 9; + public static final int BASS_CONFIG_3DALGORITHM = 10; + public static final int BASS_CONFIG_NET_TIMEOUT = 11; + public static final int BASS_CONFIG_NET_BUFFER = 12; + public static final int BASS_CONFIG_PAUSE_NOPLAY = 13; + public static final int BASS_CONFIG_NET_PREBUF = 15; + public static final int BASS_CONFIG_NET_PASSIVE = 18; + public static final int BASS_CONFIG_REC_BUFFER = 19; + public static final int BASS_CONFIG_NET_PLAYLIST = 21; + public static final int BASS_CONFIG_MUSIC_VIRTUAL = 22; + public static final int BASS_CONFIG_VERIFY = 23; + public static final int BASS_CONFIG_UPDATETHREADS = 24; + public static final int BASS_CONFIG_DEV_BUFFER = 27; + public static final int BASS_CONFIG_DEV_DEFAULT = 36; + public static final int BASS_CONFIG_NET_READTIMEOUT = 37; + public static final int BASS_CONFIG_HANDLES = 41; + public static final int BASS_CONFIG_SRC = 43; + public static final int BASS_CONFIG_SRC_SAMPLE = 44; + public static final int BASS_CONFIG_ASYNCFILE_BUFFER = 45; + public static final int BASS_CONFIG_OGG_PRESCAN = 47; + public static final int BASS_CONFIG_DEV_NONSTOP = 50; + public static final int BASS_CONFIG_VERIFY_NET = 52; + public static final int BASS_CONFIG_DEV_PERIOD = 53; + public static final int BASS_CONFIG_FLOAT = 54; + public static final int BASS_CONFIG_NET_SEEK = 56; + public static final int BASS_CONFIG_AM_DISABLE = 58; + public static final int BASS_CONFIG_NET_PLAYLIST_DEPTH = 59; + public static final int BASS_CONFIG_NET_PREBUF_WAIT = 60; + public static final int BASS_CONFIG_ANDROID_SESSIONID = 62; + public static final int BASS_CONFIG_ANDROID_AAUDIO = 67; + + // BASS_SetConfigPtr options + public static final int BASS_CONFIG_NET_AGENT = 16; + public static final int BASS_CONFIG_NET_PROXY = 17; + public static final int BASS_CONFIG_LIBSSL = 64; + + // BASS_Init flags + public static final int BASS_DEVICE_8BITS = 1; // 8 bit + public static final int BASS_DEVICE_MONO = 2; // mono + public static final int BASS_DEVICE_3D = 4; // enable 3D functionality + public static final int BASS_DEVICE_16BITS = 8; // limit output to 16 bit + public static final int BASS_DEVICE_REINIT=128; // reinitialize + public static final int BASS_DEVICE_LATENCY = 0x100; // calculate device latency (BASS_INFO struct) + public static final int BASS_DEVICE_CPSPEAKERS=0x400; // detect speakers via Windows control panel + public static final int BASS_DEVICE_SPEAKERS = 0x800; // force enabling of speaker assignment + public static final int BASS_DEVICE_NOSPEAKER = 0x1000; // ignore speaker arrangement + public static final int BASS_DEVICE_DMIX = 0x2000; // use ALSA "dmix" plugin + public static final int BASS_DEVICE_FREQ = 0x4000; // set device sample rate + public static final int BASS_DEVICE_STEREO=0x8000; // limit output to stereo + public static final int BASS_DEVICE_HOG = 0x10000; // hog/exclusive mode + public static final int BASS_DEVICE_AUDIOTRACK = 0x20000; // use AudioTrack output + public static final int BASS_DEVICE_DSOUND=0x40000; // use DirectSound output + public static final int BASS_DEVICE_SOFTWARE=0x80000; // disable hardware/fastpath output + + // BASS_DEVICEINFO flags + public static final int BASS_DEVICE_ENABLED = 1; + public static final int BASS_DEVICE_DEFAULT = 2; + public static final int BASS_DEVICE_INIT = 4; + public static final int BASS_DEVICE_LOOPBACK=8; + public static final int BASS_DEVICE_DEFAULTCOM=128; + + public static final int BASS_DEVICE_TYPE_MASK=0xff000000; + public static final int BASS_DEVICE_TYPE_NETWORK=0x01000000; + public static final int BASS_DEVICE_TYPE_SPEAKERS=0x02000000; + public static final int BASS_DEVICE_TYPE_LINE=0x03000000; + public static final int BASS_DEVICE_TYPE_HEADPHONES=0x04000000; + public static final int BASS_DEVICE_TYPE_MICROPHONE=0x05000000; + public static final int BASS_DEVICE_TYPE_HEADSET=0x06000000; + public static final int BASS_DEVICE_TYPE_HANDSET=0x07000000; + public static final int BASS_DEVICE_TYPE_DIGITAL=0x08000000; + public static final int BASS_DEVICE_TYPE_SPDIF=0x09000000; + public static final int BASS_DEVICE_TYPE_HDMI=0x0a000000; + public static final int BASS_DEVICE_TYPE_DISPLAYPORT=0x40000000; + + public static final int BASS_SAMPLE_8BITS = 1; // 8 bit + public static final int BASS_SAMPLE_FLOAT = 256; // 32-bit floating-point + public static final int BASS_SAMPLE_MONO = 2; // mono + public static final int BASS_SAMPLE_LOOP = 4; // looped + public static final int BASS_SAMPLE_3D = 8; // 3D functionality + public static final int BASS_SAMPLE_SOFTWARE = 16; // not using hardware mixing + public static final int BASS_SAMPLE_MUTEMAX = 32; // mute at max distance (3D only) + public static final int BASS_SAMPLE_VAM = 64; // DX7 voice allocation & management + public static final int BASS_SAMPLE_FX = 128; // old implementation of DX8 effects + public static final int BASS_SAMPLE_OVER_VOL = 0x10000; // override lowest volume + public static final int BASS_SAMPLE_OVER_POS = 0x20000; // override longest playing + public static final int BASS_SAMPLE_OVER_DIST = 0x30000; // override furthest from listener (3D only) + + public static final int BASS_STREAM_PRESCAN = 0x20000; // enable pin-point seeking/length (MP3/MP2/MP1) + public static final int BASS_MP3_SETPOS = BASS_STREAM_PRESCAN; + public static final int BASS_STREAM_AUTOFREE = 0x40000; // automatically free the stream when it stop/ends + public static final int BASS_STREAM_RESTRATE = 0x80000; // restrict the download rate of internet file streams + public static final int BASS_STREAM_BLOCK = 0x100000; // download/play internet file stream in small blocks + public static final int BASS_STREAM_DECODE = 0x200000; // don't play the stream, only decode (BASS_ChannelGetData) + public static final int BASS_STREAM_STATUS = 0x800000; // give server status info (HTTP/ICY tags) in DOWNLOADPROC + + public static final int BASS_MUSIC_FLOAT = BASS_SAMPLE_FLOAT; + public static final int BASS_MUSIC_MONO = BASS_SAMPLE_MONO; + public static final int BASS_MUSIC_LOOP = BASS_SAMPLE_LOOP; + public static final int BASS_MUSIC_3D = BASS_SAMPLE_3D; + public static final int BASS_MUSIC_FX = BASS_SAMPLE_FX; + public static final int BASS_MUSIC_AUTOFREE = BASS_STREAM_AUTOFREE; + public static final int BASS_MUSIC_DECODE = BASS_STREAM_DECODE; + public static final int BASS_MUSIC_PRESCAN = BASS_STREAM_PRESCAN; // calculate playback length + public static final int BASS_MUSIC_CALCLEN = BASS_MUSIC_PRESCAN; + public static final int BASS_MUSIC_RAMP = 0x200; // normal ramping + public static final int BASS_MUSIC_RAMPS = 0x400; // sensitive ramping + public static final int BASS_MUSIC_SURROUND = 0x800; // surround sound + public static final int BASS_MUSIC_SURROUND2 = 0x1000; // surround sound (mode 2) + public static final int BASS_MUSIC_FT2PAN = 0x2000; // apply FastTracker 2 panning to XM files + public static final int BASS_MUSIC_FT2MOD = 0x2000; // play .MOD as FastTracker 2 does + public static final int BASS_MUSIC_PT1MOD = 0x4000; // play .MOD as ProTracker 1 does + public static final int BASS_MUSIC_NONINTER = 0x10000; // non-interpolated sample mixing + public static final int BASS_MUSIC_SINCINTER = 0x800000; // sinc interpolated sample mixing + public static final int BASS_MUSIC_POSRESET = 0x8000; // stop all notes when moving position + public static final int BASS_MUSIC_POSRESETEX = 0x400000; // stop all notes and reset bmp/etc when moving position + public static final int BASS_MUSIC_STOPBACK = 0x80000; // stop the music on a backwards jump effect + public static final int BASS_MUSIC_NOSAMPLE = 0x100000; // don't load the samples + + // Speaker assignment flags + public static final int BASS_SPEAKER_FRONT = 0x1000000; // front speakers + public static final int BASS_SPEAKER_REAR = 0x2000000; // rear/side speakers + public static final int BASS_SPEAKER_CENLFE = 0x3000000; // center & LFE speakers (5.1) + public static final int BASS_SPEAKER_REAR2 = 0x4000000; // rear center speakers (7.1) + public static int BASS_SPEAKER_N(int n) { return n<<24; } // n'th pair of speakers (max 15) + public static final int BASS_SPEAKER_LEFT = 0x10000000; // modifier: left + public static final int BASS_SPEAKER_RIGHT = 0x20000000; // modifier: right + public static final int BASS_SPEAKER_FRONTLEFT = BASS_SPEAKER_FRONT|BASS_SPEAKER_LEFT; + public static final int BASS_SPEAKER_FRONTRIGHT = BASS_SPEAKER_FRONT|BASS_SPEAKER_RIGHT; + public static final int BASS_SPEAKER_REARLEFT = BASS_SPEAKER_REAR|BASS_SPEAKER_LEFT; + public static final int BASS_SPEAKER_REARRIGHT = BASS_SPEAKER_REAR|BASS_SPEAKER_RIGHT; + public static final int BASS_SPEAKER_CENTER = BASS_SPEAKER_CENLFE|BASS_SPEAKER_LEFT; + public static final int BASS_SPEAKER_LFE = BASS_SPEAKER_CENLFE|BASS_SPEAKER_RIGHT; + public static final int BASS_SPEAKER_REAR2LEFT = BASS_SPEAKER_REAR2|BASS_SPEAKER_LEFT; + public static final int BASS_SPEAKER_REAR2RIGHT = BASS_SPEAKER_REAR2|BASS_SPEAKER_RIGHT; + + public static final int BASS_ASYNCFILE = 0x40000000; + + public static final int BASS_RECORD_PAUSE = 0x8000; // start recording paused + + public static final int BASS_ORIGRES_FLOAT = 0x10000; + + // BASS_CHANNELINFO types + public static final int BASS_CTYPE_SAMPLE = 1; + public static final int BASS_CTYPE_RECORD = 2; + public static final int BASS_CTYPE_STREAM = 0x10000; + public static final int BASS_CTYPE_STREAM_VORBIS = 0x10002; + public static final int BASS_CTYPE_STREAM_OGG = 0x10002; + public static final int BASS_CTYPE_STREAM_MP1 = 0x10003; + public static final int BASS_CTYPE_STREAM_MP2 = 0x10004; + public static final int BASS_CTYPE_STREAM_MP3 = 0x10005; + public static final int BASS_CTYPE_STREAM_AIFF = 0x10006; + public static final int BASS_CTYPE_STREAM_CA = 0x10007; + public static final int BASS_CTYPE_STREAM_MF = 0x10008; + public static final int BASS_CTYPE_STREAM_AM = 0x10009; + public static final int BASS_CTYPE_STREAM_DUMMY = 0x18000; + public static final int BASS_CTYPE_STREAM_DEVICE = 0x18001; + public static final int BASS_CTYPE_STREAM_WAV = 0x40000; // WAVE flag, LOWORD=codec + public static final int BASS_CTYPE_STREAM_WAV_PCM = 0x50001; + public static final int BASS_CTYPE_STREAM_WAV_FLOAT = 0x50003; + public static final int BASS_CTYPE_MUSIC_MOD = 0x20000; + public static final int BASS_CTYPE_MUSIC_MTM = 0x20001; + public static final int BASS_CTYPE_MUSIC_S3M = 0x20002; + public static final int BASS_CTYPE_MUSIC_XM = 0x20003; + public static final int BASS_CTYPE_MUSIC_IT = 0x20004; + public static final int BASS_CTYPE_MUSIC_MO3 = 0x00100; // MO3 flag + + // 3D channel modes + public static final int BASS_3DMODE_NORMAL = 0; // normal 3D processing + public static final int BASS_3DMODE_RELATIVE = 1; // position is relative to the listener + public static final int BASS_3DMODE_OFF = 2; // no 3D processing + + // software 3D mixing algorithms (used with BASS_CONFIG_3DALGORITHM) + public static final int BASS_3DALG_DEFAULT = 0; + public static final int BASS_3DALG_OFF = 1; + public static final int BASS_3DALG_FULL = 2; + public static final int BASS_3DALG_LIGHT = 3; + + public static final int BASS_STREAMPROC_END = 0x80000000; // end of user stream flag + + // special STREAMPROCs + public static final int STREAMPROC_DUMMY = 0; // "dummy" stream + public static final int STREAMPROC_PUSH = -1; // push stream + public static final int STREAMPROC_DEVICE = -2; // device mix stream + public static final int STREAMPROC_DEVICE_3D = -3; // device 3D mix stream + + // BASS_StreamCreateFileUser file systems + public static final int STREAMFILE_NOBUFFER = 0; + public static final int STREAMFILE_BUFFER = 1; + public static final int STREAMFILE_BUFFERPUSH = 2; + + // BASS_StreamPutFileData options + public static final int BASS_FILEDATA_END = 0; // end & close the file + + // BASS_StreamGetFilePosition modes + public static final int BASS_FILEPOS_CURRENT = 0; + public static final int BASS_FILEPOS_DECODE = BASS_FILEPOS_CURRENT; + public static final int BASS_FILEPOS_DOWNLOAD = 1; + public static final int BASS_FILEPOS_END = 2; + public static final int BASS_FILEPOS_START = 3; + public static final int BASS_FILEPOS_CONNECTED = 4; + public static final int BASS_FILEPOS_BUFFER = 5; + public static final int BASS_FILEPOS_SOCKET = 6; + public static final int BASS_FILEPOS_ASYNCBUF = 7; + public static final int BASS_FILEPOS_SIZE = 8; + public static final int BASS_FILEPOS_BUFFERING = 9; + + // BASS_ChannelSetSync types + public static final int BASS_SYNC_POS = 0; + public static final int BASS_SYNC_END = 2; + public static final int BASS_SYNC_META = 4; + public static final int BASS_SYNC_SLIDE = 5; + public static final int BASS_SYNC_STALL = 6; + public static final int BASS_SYNC_DOWNLOAD = 7; + public static final int BASS_SYNC_FREE = 8; + public static final int BASS_SYNC_SETPOS = 11; + public static final int BASS_SYNC_MUSICPOS = 10; + public static final int BASS_SYNC_MUSICINST = 1; + public static final int BASS_SYNC_MUSICFX = 3; + public static final int BASS_SYNC_OGG_CHANGE = 12; + public static final int BASS_SYNC_DEV_FAIL = 14; + public static final int BASS_SYNC_DEV_FORMAT = 15; + public static final int BASS_SYNC_THREAD = 0x20000000; // flag: call sync in other thread + public static final int BASS_SYNC_MIXTIME = 0x40000000; // flag: sync at mixtime, else at playtime + public static final int BASS_SYNC_ONETIME = 0x80000000; // flag: sync only once, else continuously + + // BASS_ChannelIsActive return values + public static final int BASS_ACTIVE_STOPPED = 0; + public static final int BASS_ACTIVE_PLAYING =1; + public static final int BASS_ACTIVE_STALLED = 2; + public static final int BASS_ACTIVE_PAUSED = 3; + public static final int BASS_ACTIVE_PAUSED_DEVICE = 4; + + // Channel attributes + public static final int BASS_ATTRIB_FREQ = 1; + public static final int BASS_ATTRIB_VOL = 2; + public static final int BASS_ATTRIB_PAN = 3; + public static final int BASS_ATTRIB_EAXMIX = 4; + public static final int BASS_ATTRIB_NOBUFFER = 5; + public static final int BASS_ATTRIB_VBR = 6; + public static final int BASS_ATTRIB_CPU = 7; + public static final int BASS_ATTRIB_SRC = 8; + public static final int BASS_ATTRIB_NET_RESUME = 9; + public static final int BASS_ATTRIB_SCANINFO = 10; + public static final int BASS_ATTRIB_NORAMP = 11; + public static final int BASS_ATTRIB_BITRATE = 12; + public static final int BASS_ATTRIB_BUFFER = 13; + public static final int BASS_ATTRIB_GRANULE = 14; + public static final int BASS_ATTRIB_USER = 15; + public static final int BASS_ATTRIB_TAIL = 16; + public static final int BASS_ATTRIB_PUSH_LIMIT = 17; + public static final int BASS_ATTRIB_DOWNLOADPROC = 18; + public static final int BASS_ATTRIB_VOLDSP = 19; + public static final int BASS_ATTRIB_VOLDSP_PRIORITY = 20; + public static final int BASS_ATTRIB_MUSIC_AMPLIFY = 0x100; + public static final int BASS_ATTRIB_MUSIC_PANSEP = 0x101; + public static final int BASS_ATTRIB_MUSIC_PSCALER = 0x102; + public static final int BASS_ATTRIB_MUSIC_BPM = 0x103; + public static final int BASS_ATTRIB_MUSIC_SPEED = 0x104; + public static final int BASS_ATTRIB_MUSIC_VOL_GLOBAL = 0x105; + public static final int BASS_ATTRIB_MUSIC_VOL_CHAN = 0x200; // + channel # + public static final int BASS_ATTRIB_MUSIC_VOL_INST = 0x300; // + instrument # + + // BASS_ChannelSlideAttribute flags + public static final int BASS_SLIDE_LOG = 0x1000000; + + // BASS_ChannelGetData flags + public static final int BASS_DATA_AVAILABLE = 0; // query how much data is buffered + public static final int BASS_DATA_FIXED = 0x20000000; // flag: return 8.24 fixed-point data + public static final int BASS_DATA_FLOAT = 0x40000000; // flag: return floating-point sample data + public static final int BASS_DATA_FFT256 = 0x80000000; // 256 sample FFT + public static final int BASS_DATA_FFT512 = 0x80000001; // 512 FFT + public static final int BASS_DATA_FFT1024 = 0x80000002; // 1024 FFT + public static final int BASS_DATA_FFT2048 = 0x80000003; // 2048 FFT + public static final int BASS_DATA_FFT4096 = 0x80000004; // 4096 FFT + public static final int BASS_DATA_FFT8192 = 0x80000005; // 8192 FFT + public static final int BASS_DATA_FFT16384 = 0x80000006; // 16384 FFT + public static final int BASS_DATA_FFT32768 = 0x80000007; // 32768 FFT + public static final int BASS_DATA_FFT_INDIVIDUAL = 0x10; // FFT flag: FFT for each channel, else all combined + public static final int BASS_DATA_FFT_NOWINDOW = 0x20; // FFT flag: no Hanning window + public static final int BASS_DATA_FFT_REMOVEDC = 0x40; // FFT flag: pre-remove DC bias + public static final int BASS_DATA_FFT_COMPLEX = 0x80; // FFT flag: return complex data + public static final int BASS_DATA_FFT_NYQUIST = 0x100; // FFT flag: return extra Nyquist value + + // BASS_ChannelGetLevelEx flags + public static final int BASS_LEVEL_MONO = 1; + public static final int BASS_LEVEL_STEREO = 2; + public static final int BASS_LEVEL_RMS = 4; + public static final int BASS_LEVEL_VOLPAN = 8; + + // BASS_ChannelGetTags types : what's returned + public static final int BASS_TAG_ID3 = 0; // ID3v1 tags : TAG_ID3 + public static final int BASS_TAG_ID3V2 = 1; // ID3v2 tags : ByteBuffer + public static final int BASS_TAG_OGG = 2; // OGG comments : String array + public static final int BASS_TAG_HTTP = 3; // HTTP headers : String array + public static final int BASS_TAG_ICY = 4; // ICY headers : String array + public static final int BASS_TAG_META = 5; // ICY metadata : String + public static final int BASS_TAG_APE = 6; // APE tags : String array + public static final int BASS_TAG_MP4 = 7; // MP4/iTunes metadata : String array + public static final int BASS_TAG_VENDOR = 9; // OGG encoder : String + public static final int BASS_TAG_LYRICS3 = 10; // Lyric3v2 tag : String + public static final int BASS_TAG_WAVEFORMAT = 14; // WAVE format : ByteBuffer containing WAVEFORMATEEX structure + public static final int BASS_TAG_AM_MIME = 15; // Android Media MIME type : String + public static final int BASS_TAG_AM_NAME = 16; // Android Media codec name : String + public static final int BASS_TAG_RIFF_INFO = 0x100; // RIFF "INFO" tags : String array + public static final int BASS_TAG_RIFF_BEXT = 0x101; // RIFF/BWF "bext" tags : TAG_BEXT + public static final int BASS_TAG_RIFF_CART = 0x102; // RIFF/BWF "cart" tags : TAG_CART + public static final int BASS_TAG_RIFF_DISP = 0x103; // RIFF "DISP" text tag : String + public static final int BASS_TAG_RIFF_CUE = 0x104; // RIFF "cue " chunk : TAG_CUE structure + public static final int BASS_TAG_RIFF_SMPL = 0x105; // RIFF "smpl" chunk : TAG_SMPL structure + public static final int BASS_TAG_APE_BINARY = 0x1000; // + index #, binary APE tag : TAG_APE_BINARY + public static final int BASS_TAG_MUSIC_NAME = 0x10000; // MOD music name : String + public static final int BASS_TAG_MUSIC_MESSAGE = 0x10001; // MOD message : String + public static final int BASS_TAG_MUSIC_ORDERS = 0x10002; // MOD order list : ByteBuffer + public static final int BASS_TAG_MUSIC_AUTH = 0x10003; // MOD author : UTF-8 string + public static final int BASS_TAG_MUSIC_INST = 0x10100; // + instrument #, MOD instrument name : String + public static final int BASS_TAG_MUSIC_SAMPLE = 0x10300; // + sample #, MOD sample name : String + public static final int BASS_TAG_BYTEBUFFER = 0x10000000; // flag: return a ByteBuffer instead of a String or TAG_ID3 + + // BASS_ChannelGetLength/GetPosition/SetPosition modes + public static final int BASS_POS_BYTE = 0; // byte position + public static final int BASS_POS_MUSIC_ORDER = 1; // order.row position, MAKELONG(order,row) + public static final int BASS_POS_OGG = 3; // OGG bitstream number + public static final int BASS_POS_RESET = 0x2000000; // flag: reset user file buffers + public static final int BASS_POS_RELATIVE = 0x4000000; // flag: seek relative to the current position + public static final int BASS_POS_INEXACT = 0x8000000; // flag: allow seeking to inexact position + public static final int BASS_POS_DECODE = 0x10000000; // flag: get the decoding (not playing) position + public static final int BASS_POS_DECODETO = 0x20000000; // flag: decode to the position instead of seeking + public static final int BASS_POS_SCAN = 0x40000000; // flag: scan to the position + + // BASS_ChannelSetDevice/GetDevice option + public static final int BASS_NODEVICE = 0x20000; + + // DX8 effect types, use with BASS_ChannelSetFX + public static final int BASS_FX_DX8_CHORUS = 0; + public static final int BASS_FX_DX8_COMPRESSOR = 1; + public static final int BASS_FX_DX8_DISTORTION = 2; + public static final int BASS_FX_DX8_ECHO = 3; + public static final int BASS_FX_DX8_FLANGER = 4; + public static final int BASS_FX_DX8_GARGLE = 5; + public static final int BASS_FX_DX8_I3DL2REVERB = 6; + public static final int BASS_FX_DX8_PARAMEQ = 7; + public static final int BASS_FX_DX8_REVERB = 8; + public static final int BASS_FX_VOLUME = 9; + + public static final int BASS_DX8_PHASE_NEG_180 = 0; + public static final int BASS_DX8_PHASE_NEG_90 = 1; + public static final int BASS_DX8_PHASE_ZERO = 2; + public static final int BASS_DX8_PHASE_90 = 3; + public static final int BASS_DX8_PHASE_180 = 4; + + public static final int BASS_INPUT_OFF=0x10000; + public static final int BASS_INPUT_ON=0x20000; + + public static final int BASS_INPUT_TYPE_MASK=0xff000000; + public static final int BASS_INPUT_TYPE_UNDEF=0x00000000; + public static final int BASS_INPUT_TYPE_DIGITAL=0x01000000; + public static final int BASS_INPUT_TYPE_LINE=0x02000000; + public static final int BASS_INPUT_TYPE_MIC =0x03000000; + public static final int BASS_INPUT_TYPE_SYNTH=0x04000000; + public static final int BASS_INPUT_TYPE_CD =0x05000000; + public static final int BASS_INPUT_TYPE_PHONE=0x06000000; + public static final int BASS_INPUT_TYPE_SPEAKER=0x07000000; + public static final int BASS_INPUT_TYPE_WAVE=0x08000000; + public static final int BASS_INPUT_TYPE_AUX=0x09000000; + public static final int BASS_INPUT_TYPE_ANALOG=0x0a000000; + + public static final int WAVE_FORMAT_UNKNOWN =0 ;//unknown format + public static final int WAVE_FORMAT_1M08 =1 ;//11.025 kHz, Mono, 8-bit + public static final int WAVE_FORMAT_1S08 =2 ;//11.025 kHz, Stereo, 8-bit + public static final int WAVE_FORMAT_1M16 =4 ;//11.025 kHz, Mono, 16-bit + public static final int WAVE_FORMAT_1S16 =8 ;//11.025 kHz, Stereo, 16-bit + public static final int WAVE_FORMAT_2M08 =16 ;//22.05 kHz, Mono, 8-bit + public static final int WAVE_FORMAT_2S08 =32 ;//22.05 kHz, Stereo, 8-bit + public static final int WAVE_FORMAT_2M16 =64 ;//22.05 kHz, Mono, 16-bit + public static final int WAVE_FORMAT_2S16 =128 ;//22.05 kHz, Stereo, 16-bit + public static final int WAVE_FORMAT_4M08 =256 ;//44.1 kHz, Mono, 8-bit + public static final int WAVE_FORMAT_4S08 =512 ;//44.1 kHz, Stereo, 8-bit + public static final int WAVE_FORMAT_4M16 =1024 ;//44.1 kHz, Mono, 16-bit + public static final int WAVE_FORMAT_4S16 =2048 ;//44.1 kHz, Stereo, 16-bit + public static final int WAVE_FORMAT_48M08 =4096 ;//48 kHz, Mono, 8-bit + public static final int WAVE_FORMAT_48S08 =8192 ;//48 kHz, Stereo, 8-bit + public static final int WAVE_FORMAT_48M16 =16384 ;//48 kHz, Mono, 16-bit + public static final int WAVE_FORMAT_48S16 =32768 ;//48 kHz, Stereo, 16-bit + public static final int WAVE_FORMAT_96M08 =65536 ;//96 kHz, Mono, 8-bit + public static final int WAVE_FORMAT_96S08 =131072 ;//96 kHz, Stereo, 8-bit + public static final int WAVE_FORMAT_96M16 =262144 ;//96 kHz, Mono, 16-bit + public static final int WAVE_FORMAT_96S16 =524288 ;//96 kHz, Stereo, 16-bit + } + + + // Device info structure + //@BA.ShortName("BASS_DEVICEINFO") + //@Structure.FieldOrder({"name","driver","flags"}) // entah kenapa gak bisa pakai ini + public static class BASS_DEVICEINFO extends Structure { + public String name; // description + public String driver; // driver + public int flags; // flags + + @Override + protected List getFieldOrder() { // entah kenapa , bisanya pakai ini + return Arrays.asList( + "name","driver","flags" + ); + } + + + @BA.Hide public void autoWrite(){} + @BA.Hide public void autoRead(){} + + + } + + + //@BA.ShortName("BASS_INFO") + //@Structure.FieldOrder({"flags","hwsize","hwfree","freesam","free3d","minrate","maxrate","eax","minbuf","dsver","latency","initflags","speakers","freq"}) + public static class BASS_INFO extends Structure { + public int flags; // device capabilities (DSCAPS_xxx flags) + public int hwsize; // size of total device hardware memory + public int hwfree; // size of free device hardware memory + public int freesam; // number of free sample slots in the hardware + public int free3d; // number of free 3D sample slots in the hardware + public int minrate; // min sample rate supported by the hardware + public int maxrate; // max sample rate supported by the hardware + public int eax; // device supports EAX? (always FALSE if BASS_DEVICE_3D was not used) + public int minbuf; // recommended minimum buffer length in ms (requires BASS_DEVICE_LATENCY) + public int dsver; // DirectSound version + public int latency; // delay (in ms) before start of playback (requires BASS_DEVICE_LATENCY) + public int initflags; // BASS_Init "flags" parameter + public int speakers; // number of speakers available + public int freq; // current output rate + + @Override + protected List getFieldOrder() { + return Arrays.asList( + "flags","hwsize","hwfree","freesam","free3d", + "minrate","maxrate","eax","minbuf","dsver", + "latency","initflags","speakers","freq"); + } + + @BA.Hide public void autoWrite(){} + @BA.Hide public void autoRead(){} + } + + // Recording device info structure + //@BA.ShortName("BASS_RECORDINFO") +// @Structure.FieldOrder({"flags","formats","inputs","singlein","freq"}) + public static class BASS_RECORDINFO extends Structure { + public int flags; // device capabilities (DSCCAPS_xxx flags) + public int formats; // supported standard formats (WAVE_FORMAT_xxx flags) + public int inputs; // number of inputs + public boolean singlein; // TRUE = only 1 input can be set at a time + public int freq; // current input rate + + @Override + protected List getFieldOrder() { + return Arrays.asList( + "flags","formats","inputs","singlein","freq" + ); + } + + @BA.Hide public void autoWrite(){} + @BA.Hide public void autoRead(){} + } + + // Sample info structure + @BA.ShortName("BASS_SAMPLE") +// @Structure.FieldOrder({"freq","volume","pan","flags","length","max","origres","chans","mingap","mode3d","mindist","maxdist","iangle","oangle","outvol","vam","priority"}) + public static class BASS_SAMPLE extends Structure { + public int freq; // default playback rate + public float volume; // default volume (0-1) + public float pan; // default pan (-1=left, 0=middle, 1=right) + public int flags; // BASS_SAMPLE_xxx flags + public int length; // length (in bytes) + public int max; // maximum simultaneous playbacks + public int origres; // original resolution bits + public int chans; // number of channels + public int mingap; // minimum gap (ms) between creating channels + public int mode3d; // BASS_3DMODE_xxx mode + public float mindist; // minimum distance + public float maxdist; // maximum distance + public int iangle; // angle of inside projection cone + public int oangle; // angle of outside projection cone + public float outvol; // delta-volume outside the projection cone + public int vam; // voice allocation/management flags (BASS_VAM_xxx) + public int priority; // priority (0=lowest, 0xffffffff=highest) + + @Override + protected List getFieldOrder() { + return Arrays.asList( + "freq","volume","pan","flags","length","max","origres","chans", + "mingap","mode3d","mindist","maxdist","iangle","oangle","outvol", + "vam","priority"); + } + + @BA.Hide public void autoWrite(){} + @BA.Hide public void autoRead(){} + } + + + + // Channel info structure + //@BA.ShortName("BASS_CHANNELINFO") +// @Structure.FieldOrder({"freq","chans","flags","ctype","origres","plugin","sample","filename"}) + public static class BASS_CHANNELINFO extends Structure { + public int freq; // default playback rate + public int chans; // channels + public int flags; // BASS_SAMPLE/STREAM/MUSIC/SPEAKER flags + public int ctype; // type of channel + public int origres; // original resolution + public int plugin; // plugin + public int sample; // sample + public String filename; // filename + + @Override + protected List getFieldOrder() { + return Arrays.asList( + "freq","chans","flags","ctype","origres", + "plugin","sample","filename" + ); + } + + @BA.Hide public void autoWrite(){} + @BA.Hide public void autoRead(){} + } + + + @BA.ShortName("BASS_PLUGINFORM") + //@Structure.FieldOrder({"ctype","name","exts"}) + public static class BASS_PLUGINFORM extends Structure implements Structure.ByReference { + public int ctype; // channel type + public String name; // format description + public String exts; // file extension filter (*.ext1;*.ext2;etc...) + + + @Override + protected List getFieldOrder() { + return Arrays.asList( + "ctype","name","exts" + ); + } + + @BA.Hide public void autoWrite(){} + @BA.Hide public void autoRead(){} + } + + @BA.ShortName("BASS_PLUGININFO") +// @Structure.FieldOrder({"version","formatc","formats"}) + public static class BASS_PLUGININFO extends Structure { + public int version; // version (same form as BASS_GetVersion) + public int formatc; // number of formats + public BASS_PLUGINFORM[] formats; // the array of formats + + public BASS_PLUGININFO() { + formats = new BASS_PLUGINFORM[10]; + write(); + } + + + @Override + protected List getFieldOrder() { + return Arrays.asList( + "version","formatc","formats" + ); + } + + @BA.Hide public void autoWrite(){} + @BA.Hide public void autoRead(){} + + } + + // 3D vector (for 3D positions/velocities/orientations) + @BA.ShortName("BASS_3DVECTOR") +// @Structure.FieldOrder({"x","y","z"}) + public static class BASS_3DVECTOR extends Structure { + public BASS_3DVECTOR() {} + public BASS_3DVECTOR(float _x, float _y, float _z) { x=_x; y=_y; z=_z; } + public float x; // +=right, -=left + public float y; // +=up, -=down + public float z; // +=front, -=behind + + @Override + protected List getFieldOrder() { + return Arrays.asList( + "x","y","z" + ); + } + + @BA.Hide public void autoWrite(){} + @BA.Hide public void autoRead(){} + } + + + + public interface STREAMPROC extends Callback + { + int STREAMPROC(int handle, Pointer buffer, int length, Pointer user); + /* User stream callback function. + handle : The stream that needs writing + buffer : Buffer to write the samples in + length : Number of bytes to write + user : The 'user' parameter value given when calling BASS_StreamCreate + RETURN : Number of bytes written. Set the BASS_STREAMPROC_END flag to end + the stream. */ + } + + + + public interface FILECLOSEPROC extends Callback{ + void FILECLOSEPROC(Pointer user); + } + public interface FILELENPROC extends Callback{ + long FILELENPROC(Pointer user); + } + public interface FILEREADPROC extends Callback{ + int FILEREADPROC(Pointer buffer, int length, Pointer user); + } + public interface FILESEEKPROC extends Callback{ + boolean FILESEEKPROC(long offset, Pointer user); + } + + + + public static class BASS_FILEPROCS extends Structure implements Callback{ + public FILECLOSEPROC fc; + public FILELENPROC fl; + public FILEREADPROC fr; + public FILESEEKPROC fs; + public BASS_FILEPROCS(FILECLOSEPROC f1, FILELENPROC f2, FILEREADPROC f3, FILESEEKPROC f4 ) { + fc = f1; + fl = f2; + fr = f3; + fs = f4; + write(); + } + + @Override + protected List getFieldOrder() { + return Arrays.asList( + "fc","fl","fr","fs" + ); + } + + @BA.Hide public void autoWrite(){} + @BA.Hide public void autoRead(){} + } + + public interface FILEPROCS extends Callback + { + void FILEPROC(FILECLOSEPROC fcp, FILELENPROC flp, FILEREADPROC frp, FILESEEKPROC fsp); + } + + + public interface DOWNLOADPROC extends Callback + { + void DOWNLOADPROC(Pointer buffer, int length, Pointer user); + /* Internet stream download callback function. + buffer : Buffer containing the downloaded data... NULL=end of download + length : Number of bytes in the buffer + user : The 'user' parameter value given when calling BASS_StreamCreateURL */ + } + + + public interface SYNCPROC extends Callback + { + void SYNCPROC(int handle, int channel, int data, Pointer user); + /* Sync callback function. + handle : The sync that has occured + channel: Channel that the sync occured in + data : Additional data associated with the sync's occurance + user : The 'user' parameter given when calling BASS_ChannelSetSync */ + } + + public interface IDSPPROC extends Callback + { + void DSPPROC(int dsp_handle, int channel_handle, Pointer buffer, int length, Pointer user); + /* DSP callback function. + dsp_handle : The DSP handle + channel_handle: Channel that the DSP is being applied to + buffer : Buffer to apply the DSP to + length : Number of bytes in the buffer + user : The 'user' parameter given when calling BASS_ChannelSetDSP */ + } + + public interface RECORDPROC extends Callback + { + boolean RECORDPROC(int handle, Pointer buffer, int length, Pointer user); + /* Recording callback function. + handle : The recording handle + buffer : Buffer containing the recorded sample data + length : Number of bytes + user : The 'user' parameter value given when calling BASS_RecordStart + RETURN : true = continue recording, false = stop */ + } + + + + // ID3v1 tag structure + @BA.ShortName("TAG_ID3") +// @Structure.FieldOrder({"id","title","artist","album","year","comment","genre","track"}) + public static class TAG_ID3 extends Structure { + public String id; + public String title; + public String artist; + public String album; + public String year; + public String comment; + public byte genre; + public byte track; + + @Override + protected List getFieldOrder() { + return Arrays.asList( + "id","title","artist","album","year", + "comment","genre","track" + ); + } + + @BA.Hide public void autoWrite(){} + @BA.Hide public void autoRead(){} + } + + // Binary APE tag structure + @BA.ShortName("TAG_APE_BINARY") +// @Structure.FieldOrder({"key","data","length"}) + public static class TAG_APE_BINARY extends Structure{ + public String key; + public Pointer data; + public int length; + + @Override + protected List getFieldOrder() { + return Arrays.asList( + "key","data","length" + ); + } + + @BA.Hide public void autoWrite(){} + @BA.Hide public void autoRead(){} + } + + + + @BA.ShortName("BASS_DX8_CHORUS") +// @Structure.FieldOrder({"fWetDryMix","fDepth","fFeedback","fFrequency","lWaveform","fDelay","lPhase"}) + public static class BASS_DX8_CHORUS extends Structure { + public float fWetDryMix; + public float fDepth; + public float fFeedback; + public float fFrequency; + public int lWaveform; // 0=triangle, 1=sine + public float fDelay; + public int lPhase; // BASS_DX8_PHASE_xxx + + @Override + protected List getFieldOrder() { + return Arrays.asList( + "fWetDryMix","fDepth","fFeedback","fFrequency", + "lWaveform","fDelay","lPhase" + ); + } + + @BA.Hide public void autoWrite(){} + @BA.Hide public void autoRead(){} + } + + @BA.ShortName("BASS_DX8_DISTORTION") +// @Structure.FieldOrder({"fGain","fEdge","fPostEQCenterFrequency","fPostEQBandwidth","fPreLowpassCutoff"}) + public static class BASS_DX8_DISTORTION extends Structure{ + public float fGain; + public float fEdge; + public float fPostEQCenterFrequency; + public float fPostEQBandwidth; + public float fPreLowpassCutoff; + + @Override + protected List getFieldOrder() { + return Arrays.asList( + "fGain","fEdge","fPostEQCenterFrequency","fPostEQBandwidth","fPreLowpassCutoff" + + ); + } + + @BA.Hide public void autoWrite(){} + @BA.Hide public void autoRead(){} + } + + @BA.ShortName("BASS_DX8_ECHO") +// @Structure.FieldOrder({"fWetDryMix","fFeedback","fLeftDelay","fRightDelay","lPanDelay"}) + public static class BASS_DX8_ECHO extends Structure{ + public float fWetDryMix; + public float fFeedback; + public float fLeftDelay; + public float fRightDelay; + public boolean lPanDelay; + + @Override + protected List getFieldOrder() { + return Arrays.asList( + "fWetDryMix","fFeedback","fLeftDelay","fRightDelay","lPanDelay" + ); + } + + @BA.Hide public void autoWrite(){} + @BA.Hide public void autoRead(){} + } + + @BA.ShortName("BASS_DX8_FLANGER") +// @Structure.FieldOrder({"fWetDryMix","fDepth","fFeedback","fFrequency","lWaveform","fDelay","lPhase"}) + public static class BASS_DX8_FLANGER extends Structure{ + public float fWetDryMix; + public float fDepth; + public float fFeedback; + public float fFrequency; + public int lWaveform; // 0=triangle, 1=sine + public float fDelay; + public int lPhase; // BASS_DX8_PHASE_xxx + + @Override + protected List getFieldOrder() { + return Arrays.asList( + "fWetDryMix","fDepth","fFeedback","fFrequency","lWaveform", + "fDelay","lPhase" + ); + } + + @BA.Hide public void autoWrite(){} + @BA.Hide public void autoRead(){} + } + + @BA.ShortName("BASS_DX8_PARAMEQ") +// @Structure.FieldOrder({"fCenter","fBandwidth","fGain"}) + public static class BASS_DX8_PARAMEQ extends Structure{ + public float fCenter; + public float fBandwidth; + public float fGain; + + @Override + protected List getFieldOrder() { + return Arrays.asList("fCenter","fBandwidth","fGain"); + } + + @BA.Hide public void autoWrite(){} + @BA.Hide public void autoRead(){} + } + + @BA.ShortName("BASS_DX8_REVERB") +// @Structure.FieldOrder({"fInGain","fReverbMix","fReverbTime","fHighFreqRTRatio"}) + public static class BASS_DX8_REVERB extends Structure{ + public float fInGain; + public float fReverbMix; + public float fReverbTime; + public float fHighFreqRTRatio; + + @Override + protected List getFieldOrder() { + return Arrays.asList( + "fInGain","fReverbMix","fReverbTime","fHighFreqRTRatio" + ); + } + + @BA.Hide public void autoWrite(){} + @BA.Hide public void autoRead(){} + } + + @BA.ShortName("BASS_FX_VOLUME_PARAM") +// @Structure.FieldOrder({"fTarget","fCurrent","fTime","lCurve"}) + public static class BASS_FX_VOLUME_PARAM extends Structure{ + public float fTarget; + public float fCurrent; + public float fTime; + public int lCurve; + + @Override + protected List getFieldOrder() { + return Arrays.asList( + "fTarget","fCurrent","fTime","lCurve" + ); + } + + @BA.Hide public void autoWrite(){} + @BA.Hide public void autoRead(){} + } + + + + public static native boolean BASS_SetConfig(int option, int value); + public static native int BASS_GetConfig(int option); + public static native boolean BASS_SetConfigPtr(int option, String value); + public static native String BASS_GetConfigPtr(int option); + public static native int BASS_GetVersion(); + public static native int BASS_ErrorGetCode(); + public static native boolean BASS_GetDeviceInfo(int device, BASS_DEVICEINFO info); + public static native boolean BASS_Init(int device, int freq, int flags); + public static native boolean BASS_SetDevice(int device); + public static native int BASS_GetDevice(); + public static native boolean BASS_Free(); + public static native boolean BASS_GetInfo(BASS_INFO info); + public static native boolean BASS_Update(int length); + public static native float BASS_GetCPU(); + public static native boolean BASS_Start(); + public static native boolean BASS_Stop(); + public static native boolean BASS_Pause(); + public static native int BASS_IsStarted(); + public static native boolean BASS_SetVolume(float volume); + public static native float BASS_GetVolume(); + + public static native int BASS_PluginLoad(String file, int flags); + public static native boolean BASS_PluginFree(int handle); + public static native BASS_PLUGININFO BASS_PluginGetInfo(int handle); + + + public static native boolean BASS_Set3DFactors(float distf, float rollf, float doppf); + public static native boolean BASS_Get3DFactors(FloatByReference distf, FloatByReference rollf, FloatByReference doppf); + public static native boolean BASS_Set3DPosition(BASS_3DVECTOR pos, BASS_3DVECTOR vel, BASS_3DVECTOR front, BASS_3DVECTOR top); + public static native boolean BASS_Get3DPosition(BASS_3DVECTOR pos, BASS_3DVECTOR vel, BASS_3DVECTOR front, BASS_3DVECTOR top); + public static native void BASS_Apply3D(); + + public static native int BASS_SampleLoad(String file, long offset, int length, int max, int flags); + public static native int BASS_SampleLoad(Pointer file, long offset, int length, int max, int flags); + + public static native int BASS_SampleCreate(int length, int freq, int chans, int max, int flags); + public static native boolean BASS_SampleFree(int handle); + public static native boolean BASS_SampleSetData(int handle, Pointer buffer); + public static native boolean BASS_SampleGetData(int handle, Pointer buffer); + public static native boolean BASS_SampleGetInfo(int handle, BASS_SAMPLE info); + public static native boolean BASS_SampleSetInfo(int handle, BASS_SAMPLE info); + public static native int BASS_SampleGetChannel(int handle, boolean onlynew); + public static native int BASS_SampleGetChannels(int handle, int[] channels); + public static native boolean BASS_SampleStop(int handle); + + public static native int BASS_MusicLoad(String file, long offset, int length, int flags, int freq); + public static native int BASS_MusicLoad(Pointer file, long offset, int length, int flags, int freq); + + public static native boolean BASS_MusicFree(int handle); + + public static native int BASS_StreamCreate(int freq, int chans, int flags, STREAMPROC proc, Pointer user); + + + public static native int BASS_StreamCreate(int freq, int chans, int flags, int STREAMPROC_TYPE, Pointer user); + public static native int BASS_StreamCreateFile(boolean onMemory, String file, long offset, long length, int flags); + //public static native int BASS_StreamCreateFile(String file, long offset, long length, int flags); + + //public static native int BASS_StreamCreateFile(Pointer file, long offset, long length, int flags); + + public static native int BASS_StreamCreateURL(String url, int offset, int flags, DOWNLOADPROC proc, Pointer user); + public static native int BASS_StreamCreateFileUser(int system, int flags, BASS_FILEPROCS procs, Pointer user); + + public static native boolean BASS_StreamFree(int handle); + public static native long BASS_StreamGetFilePosition(int handle, int mode); + public static native int BASS_StreamPutData(int handle, Pointer buffer, int length); + public static native int BASS_StreamPutFileData(int handle, Pointer buffer, int length); + + public static native boolean BASS_RecordGetDeviceInfo(int device, BASS_DEVICEINFO info); + public static native boolean BASS_RecordInit(int device); + public static native boolean BASS_RecordSetDevice(int device); + public static native int BASS_RecordGetDevice(); + public static native boolean BASS_RecordFree(); + public static native boolean BASS_RecordGetInfo(BASS_RECORDINFO info); + public static native String BASS_RecordGetInputName(int input); + public static native boolean BASS_RecordSetInput(int input, int flags, float volume); + public static native int BASS_RecordGetInput(int input, FloatByReference volume); + public static native int BASS_RecordStart(int freq, int chans, int flags, RECORDPROC proc, Pointer user); + + public static native double BASS_ChannelBytes2Seconds(int handle, long pos); + public static native long BASS_ChannelSeconds2Bytes(int handle, double pos); + public static native int BASS_ChannelGetDevice(int handle); + public static native boolean BASS_ChannelSetDevice(int handle, int device); + public static native int BASS_ChannelIsActive(int handle); + public static native boolean BASS_ChannelGetInfo(int handle, BASS_CHANNELINFO info); + public static native String BASS_ChannelGetTags(int handle, int tags); + public static native long BASS_ChannelFlags(int handle, int flags, int mask); + public static native boolean BASS_ChannelUpdate(int handle, int length); + public static native boolean BASS_ChannelLock(int handle, boolean lock); + public static native boolean BASS_ChannelPlay(int handle, boolean restart); + public static native boolean BASS_ChannelStop(int handle); + public static native boolean BASS_ChannelPause(int handle); + public static native boolean BASS_ChannelSetAttribute(int handle, int attrib, float value); + public static native boolean BASS_ChannelGetAttribute(int handle, int attrib, FloatByReference value); + public static native boolean BASS_ChannelSlideAttribute(int handle, int attrib, float value, int time); + public static native boolean BASS_ChannelIsSliding(int handle, int attrib); + public static native boolean BASS_ChannelSetAttributeEx(int handle, int attrib, Pointer value, int size); + public static native int BASS_ChannelGetAttributeEx(int handle, int attrib, Pointer value, int size); + public static native boolean BASS_ChannelSet3DAttributes(int handle, int mode, float min, float max, int iangle, int oangle, float outvol); + public static native boolean BASS_ChannelGet3DAttributes(int handle, IntByReference mode, FloatByReference min, FloatByReference max, IntByReference iangle, IntByReference oangle, FloatByReference outvol); + public static native boolean BASS_ChannelSet3DPosition(int handle, BASS_3DVECTOR pos, BASS_3DVECTOR orient, BASS_3DVECTOR vel); + public static native boolean BASS_ChannelGet3DPosition(int handle, BASS_3DVECTOR pos, BASS_3DVECTOR orient, BASS_3DVECTOR vel); + public static native long BASS_ChannelGetLength(int handle, int mode); + public static native boolean BASS_ChannelSetPosition(int handle, long pos, int mode); + public static native long BASS_ChannelGetPosition(int handle, int mode); + public static native int BASS_ChannelGetLevel(int handle); + public static native boolean BASS_ChannelGetLevelEx(int handle, float[] levels, float length, int flags); + public static native int BASS_ChannelGetData(int handle, Pointer buffer, int length); + public static native int BASS_ChannelSetSync(int handle, int type, long param, SYNCPROC proc, Pointer user); + public static native boolean BASS_ChannelRemoveSync(int handle, int sync); + public static native int BASS_ChannelSetDSP(int handle, IDSPPROC proc, Pointer user, int priority); + public static native boolean BASS_ChannelRemoveDSP(int handle, int dsp); + public static native boolean BASS_ChannelSetLink(int handle, int chan); + public static native boolean BASS_ChannelRemoveLink(int handle, int chan); + public static native int BASS_ChannelSetFX(int handle, int type, int priority); + public static native boolean BASS_ChannelRemoveFX(int handle, int fx); + public static native boolean BASS_ChannelFree(int handle); + public static native boolean BASS_ChannelStart(int handle); + + public static native boolean BASS_FXSetParameters(int handle, Structure params); + public static native boolean BASS_FXGetParameters(int handle, Structure params); + public static native boolean BASS_FXReset(int handle); + public static native boolean BASS_FXSetPriority(int handle, int priority); + + + @BA.ShortName("BassUtils") + public static class Utils { + public static int LOBYTE(int n) { return n&0xff; } + public static int HIBYTE(int n) { return (n>>8)&0xff; } + public static int LOWORD(int n) { return n&0xffff; } + public static int HIWORD(int n) { return (n>>16)&0xffff; } + public static int MAKEWORD(int a, int b) { return (a&0xff)|((b&0xff)<<8); } + public static int MAKELONG(int a, int b) { return (a&0xffff)|(b<<16); } + } + + + static { + String path = BassLibrary.ExtractLibraryFromJar(BassLibrary.lib_bass); + if (path!=null && path.length()>0) Native.register(BASS.class, path); + } + +} diff --git a/src/com/un4seen/bass/BASSALAC.java b/src/com/un4seen/bass/BASSALAC.java new file mode 100644 index 0000000..111c4c1 --- /dev/null +++ b/src/com/un4seen/bass/BASSALAC.java @@ -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); + } +} diff --git a/src/com/un4seen/bass/BASSDSD.java b/src/com/un4seen/bass/BASSDSD.java new file mode 100644 index 0000000..f16292a --- /dev/null +++ b/src/com/un4seen/bass/BASSDSD.java @@ -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 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); } +} diff --git a/src/com/un4seen/bass/BASSFLAC.java b/src/com/un4seen/bass/BASSFLAC.java new file mode 100644 index 0000000..983c7b4 --- /dev/null +++ b/src/com/un4seen/bass/BASSFLAC.java @@ -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 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 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 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 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 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); } +} diff --git a/src/com/un4seen/bass/BASSHLS.java b/src/com/un4seen/bass/BASSHLS.java new file mode 100644 index 0000000..ab72e1b --- /dev/null +++ b/src/com/un4seen/bass/BASSHLS.java @@ -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); + } + +} diff --git a/src/com/un4seen/bass/BASSMIDI.java b/src/com/un4seen/bass/BASSMIDI.java new file mode 100644 index 0000000..a6eb511 --- /dev/null +++ b/src/com/un4seen/bass/BASSMIDI.java @@ -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 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 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 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 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 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 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 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); } + +} diff --git a/src/com/un4seen/bass/BASSOPUS.java b/src/com/un4seen/bass/BASSOPUS.java new file mode 100644 index 0000000..190b53b --- /dev/null +++ b/src/com/un4seen/bass/BASSOPUS.java @@ -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); } + +} diff --git a/src/com/un4seen/bass/BASSWEBM.java b/src/com/un4seen/bass/BASSWEBM.java new file mode 100644 index 0000000..48dbeca --- /dev/null +++ b/src/com/un4seen/bass/BASSWEBM.java @@ -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); } + +} diff --git a/src/com/un4seen/bass/BASSWV.java b/src/com/un4seen/bass/BASSWV.java new file mode 100644 index 0000000..e7ac3d2 --- /dev/null +++ b/src/com/un4seen/bass/BASSWV.java @@ -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); } + +} diff --git a/src/com/un4seen/bass/BASS_AAC.java b/src/com/un4seen/bass/BASS_AAC.java new file mode 100644 index 0000000..a492b46 --- /dev/null +++ b/src/com/un4seen/bass/BASS_AAC.java @@ -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); + } +} diff --git a/src/com/un4seen/bass/BASS_APE.java b/src/com/un4seen/bass/BASS_APE.java new file mode 100644 index 0000000..3ad5229 --- /dev/null +++ b/src/com/un4seen/bass/BASS_APE.java @@ -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); + } +} diff --git a/src/com/un4seen/bass/BASS_FX.java b/src/com/un4seen/bass/BASS_FX.java new file mode 100644 index 0000000..4194841 --- /dev/null +++ b/src/com/un4seen/bass/BASS_FX.java @@ -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 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 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 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 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.. 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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.. 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 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 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 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); + } +} diff --git a/src/com/un4seen/bass/BASS_MPC.java b/src/com/un4seen/bass/BASS_MPC.java new file mode 100644 index 0000000..ad3dd3b --- /dev/null +++ b/src/com/un4seen/bass/BASS_MPC.java @@ -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); + } + +} diff --git a/src/com/un4seen/bass/BASS_TTA.java b/src/com/un4seen/bass/BASS_TTA.java new file mode 100644 index 0000000..45747b7 --- /dev/null +++ b/src/com/un4seen/bass/BASS_TTA.java @@ -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); + } + +} diff --git a/src/com/un4seen/bass/BASSenc.java b/src/com/un4seen/bass/BASSenc.java new file mode 100644 index 0000000..3fd02b8 --- /dev/null +++ b/src/com/un4seen/bass/BASSenc.java @@ -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); + } +} diff --git a/src/com/un4seen/bass/BASSenc_FLAC.java b/src/com/un4seen/bass/BASSenc_FLAC.java new file mode 100644 index 0000000..4f9bbd9 --- /dev/null +++ b/src/com/un4seen/bass/BASSenc_FLAC.java @@ -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); } + +} diff --git a/src/com/un4seen/bass/BASSenc_MP3.java b/src/com/un4seen/bass/BASSenc_MP3.java new file mode 100644 index 0000000..38f0f52 --- /dev/null +++ b/src/com/un4seen/bass/BASSenc_MP3.java @@ -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); + +} diff --git a/src/com/un4seen/bass/BASSenc_OGG.java b/src/com/un4seen/bass/BASSenc_OGG.java new file mode 100644 index 0000000..88b779a --- /dev/null +++ b/src/com/un4seen/bass/BASSenc_OGG.java @@ -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); + + +} diff --git a/src/com/un4seen/bass/BASSenc_OPUS.java b/src/com/un4seen/bass/BASSenc_OPUS.java new file mode 100644 index 0000000..08e23b1 --- /dev/null +++ b/src/com/un4seen/bass/BASSenc_OPUS.java @@ -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); } + +} diff --git a/src/com/un4seen/bass/BASSmix.java b/src/com/un4seen/bass/BASSmix.java new file mode 100644 index 0000000..68282e5 --- /dev/null +++ b/src/com/un4seen/bass/BASSmix.java @@ -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 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); + + +} diff --git a/src/com/un4seen/bass/BassLibrary.java b/src/com/un4seen/bass/BassLibrary.java new file mode 100644 index 0000000..ba64f7b --- /dev/null +++ b/src/com/un4seen/bass/BassLibrary.java @@ -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; + } + + } + + +} diff --git a/src/internetradio/RadioURLs.java b/src/internetradio/RadioURLs.java new file mode 100644 index 0000000..82176ac --- /dev/null +++ b/src/internetradio/RadioURLs.java @@ -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/"; + + +} diff --git a/src/jbass/AudioCombiner.java b/src/jbass/AudioCombiner.java new file mode 100644 index 0000000..8099922 --- /dev/null +++ b/src/jbass/AudioCombiner.java @@ -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;ii0); + + + } 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; ii0 ? 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; + } +} diff --git a/src/jbass/Bass_DeviceInfo.java b/src/jbass/Bass_DeviceInfo.java new file mode 100644 index 0000000..1cb3303 --- /dev/null +++ b/src/jbass/Bass_DeviceInfo.java @@ -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 ""; + } + } +} diff --git a/src/jbass/Bass_Info.java b/src/jbass/Bass_Info.java new file mode 100644 index 0000000..bcd27c4 --- /dev/null +++ b/src/jbass/Bass_Info.java @@ -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; + } +} diff --git a/src/jbass/Bass_RecordInfo.java b/src/jbass/Bass_RecordInfo.java new file mode 100644 index 0000000..deef263 --- /dev/null +++ b/src/jbass/Bass_RecordInfo.java @@ -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; + } +} diff --git a/src/jbass/HandleMP3Writer.java b/src/jbass/HandleMP3Writer.java new file mode 100644 index 0000000..a3eb1bb --- /dev/null +++ b/src/jbass/HandleMP3Writer.java @@ -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); + } +} diff --git a/src/jbass/HandleMp3WriterEvent.java b/src/jbass/HandleMp3WriterEvent.java new file mode 100644 index 0000000..3938f01 --- /dev/null +++ b/src/jbass/HandleMp3WriterEvent.java @@ -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); +} diff --git a/src/jbass/HandleWavWriter.java b/src/jbass/HandleWavWriter.java new file mode 100644 index 0000000..b983955 --- /dev/null +++ b/src/jbass/HandleWavWriter.java @@ -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}); + } +} diff --git a/src/jbass/HandleWriter.java b/src/jbass/HandleWriter.java new file mode 100644 index 0000000..6d11966 --- /dev/null +++ b/src/jbass/HandleWriter.java @@ -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() ; +} diff --git a/src/jbass/JBass.java b/src/jbass/JBass.java new file mode 100644 index 0000000..b472257 --- /dev/null +++ b/src/jbass/JBass.java @@ -0,0 +1,2731 @@ +package jbass; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.nio.ByteBuffer; +//import java.util.ArrayList; +//import java.util.List; +import java.nio.ByteOrder; +import java.nio.ShortBuffer; +import java.text.DecimalFormat; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +import com.sun.jna.Memory; +import com.sun.jna.Platform; +import com.sun.jna.Pointer; +import com.sun.jna.ptr.FloatByReference; +import com.sun.jna.ptr.LongByReference; +import com.un4seen.bass.BASS; +import com.un4seen.bass.BASS.*; +import com.un4seen.bass.BASS_FX; +import com.un4seen.bass.BASSenc; +import com.un4seen.bass.BASSenc_MP3; +import com.un4seen.bass.BASSmix; + + +import anywheresoftware.b4a.*; +import anywheresoftware.b4a.keywords.Bit; +import anywheresoftware.b4a.objects.collections.List; +import anywheresoftware.b4a.objects.collections.Map; +import anywheresoftware.b4a.objects.streams.File.InputStreamWrapper; +import anywheresoftware.b4a.randomaccessfile.RandomAccessFile; + + + +@BA.Author("Rudy Darmawan") +@BA.Version(2.29f) +@BA.ShortName("JBass") +@BA.Events(values={ + "errorhappened(ID as int,functionname as string, message as string)", + "streamstart(ID as int)", + "streamstop(IlkD as int)", + "streamrunning(ID as int)", + "streamidle(ID as int)", + "streambufferstatus(ID as int, position as int, remaining as int, capacity as int)", + "playstart(ID as int, namafile as string)", + "playstop(ID as int, namafile as string)", + "playfail(ID as int, namafile as string)", + "playpause(ID as int, namafile as string)", + "playvu(ID as int, result as short)", + "recordstart(ID as int)", + "recordstop(ID as int)", + "recordfail(ID as int)", + "decodedpcm(ID as int,bb() as byte)", + "log(ID as int, msg as string)", + "startudpreceiver(success as boolean,DevID as int, ListenPort as int, msg as string)", + "stopudpreceiver(DevID as int,ListenPort as int, msg as string)", + "recordbuffer(ID as int, bb() as byte)", + "recordvu(ID as int, result as short)"}) + + + +@BA.Permissions(values= { + "android.permission.RECORD_AUDIO" +}) +@BA.DependsOn(values= {"jna-5.6.0.jar"}) + +@SuppressWarnings("all") +public class JBass { + + private final int playback_samplingrate = 48000; + // revisi 03042024, besarin buffer untuk AAS Output Unit + private static final int buffersize=64000; // 64K; + private int current_play_handle=0; + private int current_record_handle = 0; + + private int PLAYER_ID = 0; + private int RECORDER_ID =0; + + private int[] EqualizerHandle={0,0,0,0,0,0,0,0,0,0}; + //private float[] EqualizerBW={0.5f,0.5f,0.5f,2.0f,2.0f,4.0f,4.0f,4.0f,8.0f,8.0f}; + private float[] EqualizerBW={8.0f,8.0f,8.0f,8.0f,8.0f,8.0f,8.0f,8.0f,8.0f,8.0f}; + private float[] EqualizerFrequency = {32.0f,64.0f,125.0f,250.0f,500.0f,1000.0f,2000.0f,4000.0f,8000.0f,16000.0f}; + private float[] EqualizerGain = {0,0,0,0,0,0,0,0,0,0}; + private BASS bass; + private BASS_FX bassfx ; // TESTED OK !! + private BASSenc bassenc ; // TESTED OK !! + private BASSenc_MP3 bassenc_mp3 ; // TESTED OK !! + private BASSmix bassmix ; // TESTED OK !! + + private BA bax; + + private String eventname=""; + private boolean recordcontinue=false; + private AtomicInteger buffer1size = new AtomicInteger(0); + + // untuk status buffer1 + ByteBuffer Buffer1; +// private final AtomicInteger Buffer1_Capacity = new AtomicInteger(0); +// private final AtomicInteger Buffer1_Remaining = new AtomicInteger(0); +// private final AtomicInteger Buffer1_Position = new AtomicInteger(0); + + private AtomicLong streamprocnano = new AtomicLong(0); + + + + + private boolean need_event_playstart = false; + private boolean need_event_playstop = false; + private boolean need_event_playpause = false; + private boolean need_event_playfail = false; + private boolean need_event_playvu = false; + private boolean need_event_recordstart =false; + private boolean need_event_recordstop = false; + private boolean need_event_recordfail = false; + private boolean need_event_log = false; + private boolean need_event_recordbuffer = false; + private boolean need_event_recordvu = false; + private boolean need_event_streamstart = false; + private boolean need_event_streamstop = false; + private boolean need_event_streamidle = false; + private boolean need_event_streamrunning = false; + private boolean need_event_errorhappened=false; + private boolean need_event_startUDPReceiver=false; + private boolean need_event_stopUDPReceiver=false; + private boolean need_event_streambufferstatus = false; + + private String streamstatus = ""; + private Map UDPReceiverMap = new Map(); // map contain all UDPReceiverData objects + private Object callerobject; + + LongByReference recordnanotime = new LongByReference(); // untuk ukur interval antara RECORDPROC ataupun Recording Thread dapat 1000 bytes + LongByReference downloadnanotime = new LongByReference(); // untuk ukur interval antara DOWNLOADPROC + LongByReference streamprocnanotime = new LongByReference(); // untuk ukur interval antara STREAMPROC + + private DecimalFormat df = new DecimalFormat("0.00"); + + public void Initialize(BA xx, Object caller, String event){ + callerobject = caller; + bax = xx; + eventname = event; + if (bax instanceof BA) { + if (callerobject !=null) { + if (!eventname.isEmpty()) { + need_event_streamstart = bax.subExists(eventname+"_streamstart"); + need_event_streamstop = bax.subExists(eventname+"_streamstop"); + need_event_streamidle = bax.subExists(eventname+"_streamidle"); + need_event_streamrunning = bax.subExists(eventname+"_streamrunning"); + need_event_streambufferstatus = bax.subExists(eventname+"_streambufferstatus"); + + need_event_playstart = bax.subExists(eventname+"_playstart"); + need_event_playstop = bax.subExists(eventname+"_playstop"); + need_event_playpause = bax.subExists(eventname+"_playpause"); + need_event_playfail = bax.subExists(eventname+"_playfail"); + need_event_playvu =bax.subExists(eventname+"_playvu"); + + need_event_recordstart = bax.subExists(eventname+"_recordstart"); + need_event_recordstop = bax.subExists(eventname+"_recordstop"); + need_event_recordfail = bax.subExists(eventname+"_recordfail"); + need_event_recordbuffer = bax.subExists(eventname+"_recordbuffer"); + need_event_recordvu = bax.subExists(eventname+"_recordvu"); + + + need_event_log = bax.subExists(eventname+"_log"); + need_event_errorhappened=bax.subExists(eventname+"_errorhappened"); + need_event_startUDPReceiver = bax.subExists(eventname+"_startudpreceiver"); + need_event_stopUDPReceiver=bax.subExists(eventname+"_stopudpreceiver"); + + + } + } + } + + + + + streamstatus = ""; + bass = new BASS(); + bassfx = new BASS_FX(); // TESTED OK !! + bassmix = new BASSmix(); // TESTED OK !! + bassenc = new BASSenc(); // TESTED OK !! + bassenc_mp3 = new BASSenc_MP3(); // TESTED OK !! + + +// BA.Log("BASS version="+Bit.ToHexString(bass.BASS_GetVersion())); +// BA.Log("BASS_FX version="+Bit.ToHexString(bassfx.BASS_FX_GetVersion())); +// BA.Log("BASSENC version="+Bit.ToHexString(bassenc.BASS_Encode_GetVersion())); +// BA.Log("BASSMIX version="+Bit.ToHexString(bassmix.BASS_Mixer_GetVersion())); +// BA.Log("BASSENC MP3 version="+Bit.ToHexString(bassenc_mp3.BASS_Encode_MP3_GetVersion())); +// if (loadplugin("bass_aac")!=0) { +// BA.Log("Loading plugin AAC success"); +// } + //if (loadplugin("basshls")!=0) BA.Log("Loading plugin HLS success"); + + UDPReceiverMap.Initialize(); + } + + + + private int loadplugin(String pluginname){ + String plugname = System.mapLibraryName(pluginname); + int plughandle = bass.BASS_PluginLoad(plugname, 0); + if (plughandle!=0) { + + BASS_PLUGININFO pluginfo = bass.BASS_PluginGetInfo(plughandle); + if (pluginfo!=null) { + BA.Log("PluginGetInfo return not-null, now reading it"); + pluginfo.read(); + + BA.Log(pluginname+" version : "+Bit.ToHexString(pluginfo.version)); + BA.Log(pluginname+" NumberOfFormat :"+pluginfo.formatc); +// for(int ii=0;ii1.0f) vuvalue[0] = 1.0f; // kalau lebih dari 1.0, max 1.0 + short maxvalue = (short)(vuvalue[0] * 32767); + raise_recordvu(ID,maxvalue); + } else { + raise_errorhappened(ID,"GetVULevel::ChannelGetLevelEx"); + raise_recordvu(ID,(short)0); + } + + } + + // dipakai di BASS_StreamCreate sebagai sumber data yang akan di play + /* + * BASS.STREAMPROC mystreamproc = new BASS.STREAMPROC() { + * + * @Override public int STREAMPROC(int handle, Pointer bufferx, int length, + * Pointer user) { // bytes dari sumber data, ditulis ke bufferx, supaya bunyi + * // Object user , jika dipakai, dapat berfungsi sebagai sumber data + * //BA.Log("STREAMPROC called, interval = "+calculate_interval( + * streamprocnanotime)+", bufferlength = "+length); if (Buffer1==null) { + * raise_streamstop(PLAYER_ID); return bassconstant.BASS_STREAMPROC_END; // + * kalau sumber data sudah null, selesai } if (current_play_handle==0) { + * raise_streamstop(PLAYER_ID); return bassconstant.BASS_STREAMPROC_END; // + * kalau handle sudah 0, selesai } + * + * // synchronized(buffer1size) { // // int written = Buffer1.position(); // // + * if (written>0) { // int max_can_read = length > written ? written : length; + * // byte[] BX = new byte[max_can_read]; // // Buffer1.flip(); // + * Buffer1.get(BX); // Buffer1.compact(); // // + * buffer1size.set(Buffer1.position()); // buffer1size.notify(); // + * bufferx.write(0, BX, 0, max_can_read); // + * //BA.Log("STREAMPROC write "+max_can_read+" bytes to Pointer"); // + * raise_streamrunning(PLAYER_ID); // return max_can_read; // return jumlah byte + * yang ditulis ke bufferx // } else { // raise_streamidle(PLAYER_ID); // return + * 0; // } // // } + * + * synchronized(Buffer1) { int written = Buffer1.position(); if (written>0) { + * int max_can_read = length > written ? written : length; byte[] BX = new + * byte[max_can_read]; + * + * Buffer1.flip(); Buffer1.get(BX); Buffer1.compact(); Buffer1.notify(); + * bufferx.write(0, BX, 0, max_can_read); + * + * written = Buffer1.position(); long nano = System.nanoTime(); long prev = + * streamprocnano.getAndSet(nano); + * BA.Log("STREAMPROC write "+max_can_read+" bytes to Pointer, position = " + * +written+" interval = "+ df.format((nano-prev)/1000000)+" ms"); + * raise_streamrunning(PLAYER_ID); return max_can_read; } else { + * raise_streamidle(PLAYER_ID); return 0; } } + * + * + * } }; + */ + + + /** + * Getting bass version + * Convert int to Hex value to get readable version. + */ + public int Getversion(){ + + int result = bass.BASS_GetVersion(); + return result; + } + + + + @SuppressWarnings("unused") + private boolean PrepareBassEqualizer(int ch_handle){ + if (ch_handle==0) { + for (int ii=0;ii<10;ii++) { + if (EqualizerHandle[ii]!=0) { + EqualizerHandle[ii]=0; + } + } + return false; + } + + int ID = 0; + if (ch_handle==current_record_handle) + ID = RECORDER_ID; + else + ID = PLAYER_ID; + + if (bass.BASS_FXReset(ch_handle)==false) { + if (ch_handle==current_record_handle) { + raise_errorhappened(ID,"PrepareBassEqualizer::FXReset"); + } else { + raise_errorhappened(ID,"PrepareBassEqualizer::FXReset"); + } + + } + for (int ii=0;ii<10;ii++) { + EqualizerHandle[ii] = bass.BASS_ChannelSetFX(ch_handle, bassconstant.BASS_FX_DX8_PARAMEQ, 1); + if (EqualizerHandle[ii]!=0) { + BASS_DX8_PARAMEQ eqx = new BASS_DX8_PARAMEQ(); + eqx.fBandwidth = this.EqualizerBW[ii]; + eqx.fCenter = this.EqualizerFrequency[ii]; + eqx.fGain = this.EqualizerGain[ii]; + + if (bass.BASS_FXSetParameters(EqualizerHandle[ii], eqx)==false) { + raise_errorhappened(ID,"PrepareBassEqualizer::FXSetParameter"); + } + } else { + raise_errorhappened(ID,"PrepareBassEqualizer::ChannelSetFX"); + } + } + return true; + } + + + /** + * Update Equalizer gain. + * Band 0 = 125 Hz; Band 1 = 250Hz; Band 2 = 500Hz; Band 3 = 1Khz; + * Band 4 = 2Khz; Band 5 = 4Khz; Band 6=8Khz; + * @param band (0 - 6) + * @param gain (-15 to 15) + * @return true if Equalizer updated + */ + public boolean UpdateEqualizer(int band, int gain) { + if (EqualizerHandle[band]==0) return false; + if (gain<-15) gain = -15; + if (gain>15) gain=15; + + + EqualizerGain[band]=(float)gain; + BASS_DX8_PARAMEQ eqx = new BASS_DX8_PARAMEQ(); + eqx.fBandwidth = this.EqualizerBW[band]; + eqx.fCenter = this.EqualizerFrequency[band]; + eqx.fGain = this.EqualizerGain[band]; + if (bass.BASS_FXSetParameters(EqualizerHandle[band], eqx)) { + return true; + } else { + raise_errorhappened(PLAYER_ID,"UpdateEqualizer::FXSetParameters"); + return false; + } + + } + + /** + * Get current equalizer gain for specific band. + * @param band, valid value = 0 ~ 9 + * @return gain in float value + */ + public float GetEqualizerGain(int band) { + return EqualizerGain[band]; + } + + /** + * Set Initial Equalizer Gain + * @param band, valid = 0 ~ 9 + * @param value, valid = -15 ~ +15 + */ + public void SetEqualizerGain(int band,float value) { + EqualizerGain[band]=value; + } + + /** + * Get Equalizer Filter Bandwidth + * @param band, valid value= 0 ~ 9 + * @return bandwidth in float value + */ + public float GetEqualizerBW(int band) { + return EqualizerBW[band]; + } + + /** + * Get Equalizer Center Frequency + * @param band,valid value = 0 ~ 9 + * @return frequency in string + */ + public String GetEqualizerCenter(int band) { + String result=""; + switch(band) { + case 0 : + result="32 Hz"; + break; + case 1 : + result="64 Hz"; + break; + case 2 : + result="125 Hz"; + break; + case 3 : + result="250 Hz"; + break; + case 4 : + result="500 Hz"; + break; + case 5 : + result="1 Khz"; + break; + case 6 : + result="2 Khz"; + break; + case 7 : + result = "4 Khz"; + break; + case 8 : + result="8 Khz"; + break; + case 9 : + result = "16 Khz"; + break; + default : + result="0"; + break; + } + return result; + } + + + /** + * Get Name, Driver, and flags for playback device. + * Flags = 1 --> enabled, Flags = 2 --> Default device, Flags = 4 --> Initalized + * @param device first device start from 1. + * @return BASS_DEVICEINFO If driver is null then no device. + */ + public Bass_DeviceInfo bass_getdeviceinfo(int device){ + BASS_DEVICEINFO result = new BASS_DEVICEINFO(); + if (bass.BASS_GetDeviceInfo(device,result)) { + result.read(); + + return new Bass_DeviceInfo(device,result); + } else { + return null; + } + + } + + /** + * Get All Playback Devices in a list. + * @return List containing BASS_DEVICEINFO structures. + */ + public List bass_getdeviceinfos(){ + List result = new List(); + result.Initialize(); + int xx = 0; + while(true) { + Bass_DeviceInfo tmp = bass_getdeviceinfo(xx); + if (tmp==null) break; // udah abis + if (tmp.IsEnabled()) { + result.Add(tmp); + } + xx+=1; + + } + return result; + } + + /** + * Init Bass for playback usage. Always call this before use. + * @param Device : start from 1 + * @param freq : enter desired sampling frequency + * @param flags : enter desired flags. + * Use BASS_CONSTANT object to help + * If want to use complicated setting, use OR operation to those values + * Flags : + * 1 = BASS_DEVICE_8BITS + * 2 = BASS_DEVICE_MONO + * 4 = BASS_DEVICE_3D + * 8 = BASS_DEVICE_16BITS + * 0x100 = BASS_DEVICE_LATENCY + * 0x400 = BASS_DEVICE_CPSPEAKERS + * 0x800 = BASS_DEVICE_SPEAKERS + * 0x1000 = BASS_DEVICE_NOSPEAKER + * 0x2000 = BASS_DEVICE_DMIX + * 0x4000 = BASS_DEVICE_FREQ + * 0x8000 = BASS_DEVICE_STEREO + * @return true if initialized. + */ + public boolean bass_init(int device, int freq, int flags){ + + + Bass_DeviceInfo idev = bass_getdeviceinfo(device); + if (idev instanceof Bass_DeviceInfo) { + if (idev.IsEnabled()) { + if (freq < 1) freq = playback_samplingrate; + + if (bass.BASS_SetDevice(device)) { + // bisa setdevice, maka free-in dulu + bass.BASS_Free(); + } + + // sampe sini , udah free atau belum init + if (bass.BASS_Init(device, freq, flags)) { + PLAYER_ID = device; + raise_log(PLAYER_ID,"bass_init success, PLAYER+ID = "+device); + return true; + } else { + raise_errorhappened(device,"Bass_Init"); + } + + } else { + raise_log(device,"Unable to bass_init id="+device+", device is disabled"); + } + } else { + raise_errorhappened(device, "Bass_GetDeviceInfo id="+device); + } + + return false; + } + + /** + * Start Playback Output. BASS will not playback if not calling this function. + * @return true if output started. + */ + public boolean bass_start_output(){ + setDeviceID(PLAYER_ID); + if (bass.BASS_Start()) { + return true; + } else raise_errorhappened(PLAYER_ID,"Start_Output::Start"); + return false; + } + + /** + * Stop Playback Output. + * @return true if output stopped. + */ + public boolean bass_stop_output(){ + setDeviceID(PLAYER_ID); + if (bass.BASS_Stop()) { + return true; + } else raise_errorhappened(PLAYER_ID,"Stop_Output::Stop"); + + return false; + } + + /** + * Pause Playback Output + * @return true if output paused. + */ + public boolean bass_pause_output(){ + setDeviceID(PLAYER_ID); + if (bass.BASS_Pause()) { + return true; + } else raise_errorhappened(PLAYER_ID,"Pause_Output::Pause"); + return false; + } + + /** + * Unload Bass Driver. Call this at end of program. + * @return true if driver unloaded. + */ + public boolean bass_free(){ + if (bass.BASS_SetDevice(PLAYER_ID)) { + // bisa set device + if (bass.BASS_Free()) { + return true; + } else { + raise_errorhappened(PLAYER_ID,"Bass_Free"); + return false; + } + } else { + // gak ada device dengan PLAYER_ID, atau PLAYER_ID belum init + // sama aja Free + return true; + } + + } + + /** + * Get/Set current thread's DeviceID. DeviceID start from 1 + * @return deviceID, or -1 if failed + */ + public int getDeviceID(){ + return PLAYER_ID; + } + + public void setDeviceID(int device){ + if (!bass.BASS_SetDevice(device)) { + int errcode = bass.BASS_ErrorGetCode(); + if (errcode == bassconstant.BASS_ERROR_DEVICE) { + // gak ada device dengan ID ini + raise_log(device,"SetDeviceID "+device+", failed, No Device with ID="+device); + } else if (errcode==bassconstant.BASS_ERROR_INIT){ + // belum di-init + if (bass_init(device,playback_samplingrate,0)) { + raise_log(device,"SetDeviceID::Init ID="+device+" is succesful"); + PLAYER_ID = device; + } else { + errcode = bass.BASS_ErrorGetCode(); + if (errcode==bassconstant.BASS_ERROR_ALREADY) { + raise_log(device,"SetDeviceID::Init ID="+device+" is succesful"); + PLAYER_ID = device; + } else { + raise_errorhappened(device, "SetDeviceID::Init"); + } + } + } + } else { + PLAYER_ID = device; + } + } + + /** + * Get last error code. + * @return int of error code. + */ + public int getErrorCode(){ + return bass.BASS_ErrorGetCode(); + } + + /** + * Get/Set Volume. Volume value 0.0 - 1.0 + */ + public float getVolume(){ + float result = -1.0f; + setDeviceID(PLAYER_ID); + result = bass.BASS_GetVolume(); + if (result==-1) { + raise_errorhappened(PLAYER_ID,"GetVolume::GetVolume"); + } + + return result; + } + public void setVolume(float vol){ + setDeviceID(PLAYER_ID); + if (!bass.BASS_SetVolume(vol)) { + raise_errorhappened(PLAYER_ID,"SetVolume::SetVolume"); + } + + } + + + + /** + * Get / Set current Stream Volume, or -1 if not playback + * @return 0 - 1.0, or -1 if not playback + */ + public float getStreamChannelVolume(){ + float resultf = -1.0f; + if (current_play_handle!=0){ + FloatByReference result = new FloatByReference(); + + if (bass.BASS_ChannelGetAttribute(current_play_handle, bassconstant.BASS_ATTRIB_VOL, result)) { + resultf = result.getValue(); + } else raise_errorhappened(PLAYER_ID,"GetStreamChannelVolume::ChannelGetAttribute"); + } + return resultf; + } + + /** + * Set current Stream Volume + * @param vol values from 0 - 1.0 + */ + public void setStreamChannelVolume(float vol){ + if (current_play_handle!=0){ + if (!bass.BASS_ChannelSetAttribute(current_play_handle, bassconstant.BASS_ATTRIB_VOL, vol)) { + raise_errorhappened(PLAYER_ID,"SetStreamChannelVolume::ChannelSetAttribute"); + } + } + } + + /** + * Get / Set Record Channel Volume, or -1 if not recrding + * @return 0.0 - 1.0, or -1 if not recording + */ + public float getRecordChannelVolume() { + float resultf = -1.0f; + if (current_record_handle!=0) { + FloatByReference result = new FloatByReference(); + + if (bass.BASS_ChannelGetAttribute(current_record_handle, bassconstant.BASS_ATTRIB_VOL, result)) { + resultf= result.getValue(); + } raise_errorhappened(RECORDER_ID,"GetRecordChannelVolume::ChannelGetAttribute"); + } + + return resultf; + } + + /** + * Get / Set Record Channel Volume + * @param vol values from 0.0 - 1.0 + */ + public void setRecordChannelVolume(float vol) { + if (current_record_handle!=0) { + if (!bass.BASS_ChannelSetAttribute(current_record_handle, bassconstant.BASS_ATTRIB_VOL, vol)) { + raise_errorhappened(RECORDER_ID,"SetRecordChannelVolume::ChannelSetAttribute"); + } + } + } + + public int GetCurrentRecordHandle() { + return current_record_handle; + } + + /** + * Playback a file, opened with bass_loadfile + * @param handle from bass_loadfile + * @param restart true if want to restart from beginning of file + * @return true if file playbacked + */ + public boolean bass_play(int handle, boolean restart){ + if (handle==0) { + raise_log(PLAYER_ID, "bass_play failed because handle = 0 "); + return false; + } + + + boolean setresult = false; + int ID = 0; + if (handle==current_record_handle) { + ID = RECORDER_ID; + } else { + ID = PLAYER_ID; + } + + if (handle==current_record_handle) { + setresult = bass.BASS_RecordSetDevice(ID); + } else { + setresult = bass.BASS_SetDevice(ID); + } + + if (setresult) { + if (bass.BASS_ChannelPlay(handle, restart)) { + return true; + } else { + raise_errorhappened(ID,"Play::ChannelPlay"); + } + } else { + + if (handle==current_record_handle) { + raise_errorhappened(ID,"Play::RecordSetDevice"); + } else { + raise_errorhappened(ID,"Play::SetDevice"); + } + + } + return false; + } + + /** + * Pause a file playback + * @param handle from bass_loadfile + * @return true if file paused + */ + public boolean bass_pause(int handle){ + if (handle==0) return false; + boolean setresult = false; + int ID = 0; + if (handle==current_record_handle) { + ID = RECORDER_ID; + setresult = bass.BASS_RecordSetDevice(ID); + }else { + ID = PLAYER_ID; + setresult = bass.BASS_SetDevice(ID); + } + + + if (setresult) { + if (bass.BASS_ChannelPause(handle)) { + return true; + } else { + raise_errorhappened(ID,"Pause::ChannelPause"); + + } + } else { + if (handle==current_record_handle) { + raise_errorhappened(RECORDER_ID,"Pause::RecordSetDevice"); + } else { + raise_errorhappened(PLAYER_ID,"Pause::SetDevice"); + } + + } + + return false; + } + + /** + * Find out if playback is still going + * @param handle from bass_loadfile + * @return 0 if stopped, others if continue + */ + public boolean bass_isplaying(int handle){ + if (handle==0) return false; + boolean setresult = false; + if (handle==current_record_handle) { + setresult = bass.BASS_RecordSetDevice(RECORDER_ID); + } else { + setresult = bass.BASS_SetDevice(PLAYER_ID); + } + if (setresult) { + if (bass.BASS_ChannelIsActive(handle)!=bassconstant.BASS_ACTIVE_PLAYING) return false; else return true; + } else { + if (handle==current_record_handle) { + raise_errorhappened(RECORDER_ID,"IsPlaying::RecordSetDevice"); + } else { + raise_errorhappened(PLAYER_ID,"IsPlaying::SetDevice"); + } + + } + + return false; + } + + /** + * Free file allocation for playback + * @param handle from loadfile + * @return true if success. + */ + public boolean bass_freefile(int handle){ + + if (handle==0) return false; + if (Buffer1!=null) { + Buffer1.clear(); + } + + + int ID = 0; + if (handle==current_record_handle) { + ID = RECORDER_ID; + } else { + ID = PLAYER_ID; + } + + try{ + + + raise_streamstop(ID); + current_play_handle=0; + bass.BASS_SetDevice(ID); + return bass.BASS_StreamFree(handle); + } + catch(Exception ex){ + raise_log(ID,"FreeFile exception : "+ex.toString()); + return false; + } + + } + + public AudioFileInformation GetAudioFileInformation(String path) { + AudioFileInformation result = new AudioFileInformation(); + result.setFilePath(path); + File ff = new File(path); + result.setIsFound(ff.exists()); + int devid = 0; + BASS_DEVICEINFO info = new BASS_DEVICEINFO(); + + + if (bass.BASS_GetDeviceInfo(devid, info)) { + info.read(); + boolean initresult = false; + if ((info.flags & bassconstant.BASS_DEVICE_INIT)!=0) { + // already inited + initresult = true; + } else { + + + initresult = bass.BASS_Init(devid, playback_samplingrate, 0); + } + + if (initresult) { + if (ff.exists()) { + int hh = 0; + hh = bass.BASS_StreamCreateFile(false,path, 0, 0, 0); + if (hh!=0) { + long sz = bass.BASS_ChannelGetLength(hh, bassconstant.BASS_POS_BYTE); + if (sz!=-1) { + result.setSizeInBytes(sz); + double dur = bass.BASS_ChannelBytes2Seconds(hh, sz); + if (dur>0) { + result.setLengthInSeconds(dur); + result.setIsValid(true); + BA.Log("Success getting MessageInformation for "+path); + } else BA.Log("Failed to ChannelBytes2Second from "+path+", Msg="+bass.GetBassErrorString()); + } else BA.Log("Failed to ChannelGetLength from "+path+", Msg="+bass.GetBassErrorString()); + } else BA.Log("Failed to open "+path+", Msg="+bass.GetBassErrorString()); + } else BA.Log("Path "+path+" is not found"); + } else BA.Log("Failed to init Device "+devid+", Msg="+bass.GetBassErrorString()); + + + } else BA.Log("Failed at GetDeviceInfo id="+devid+", Msge="+bass.GetBassErrorString()); + return result; + } + + + /** + * Open UDP Listening at listenport, using device DevID as output, on specific SamplingRate + * will raise event startUDPReceiver as result + * will raise event recordbuffer to get Bytes stream from UDP + * @param listenport UDP Listen Port + * @param maxUDPSize default is 1000 bytes + * @param DevID DeviceID to link, 0=no audio, >1 for real device + * @param SamplingRate for audio in Hertz + * @param channels 1=mono, 2=stereo + * @param flags for Bass Init, use 0 for default + */ + public boolean bass_start_UDPReceiver(int listenport, int maxUDPSize, int DevID, int SamplingRate, int channels, int flags) { + String msg=""; + Bass_DeviceInfo idev = bass_getdeviceinfo(DevID); + if (idev==null) { + msg="Device "+DevID+ " is not available"; + raise_startUDPReceiver(false, DevID, listenport, msg); + if (BA.debugMode) BA.Log(msg); + return false; + } + + if (!idev.IsEnabled()) { + // device is disabled + msg="Device "+DevID+" is disabled"; + raise_startUDPReceiver(false, DevID, listenport, msg); + if (BA.debugMode) BA.Log(msg); + return false; + } + if (!idev.IsInited()) { + // need to initialize + + if (bass.BASS_Init(DevID, SamplingRate, flags)==false) { + msg="Failed to Initialize DevID="+DevID; + raise_startUDPReceiver(false, DevID, listenport, msg); + if (BA.debugMode) BA.Log(msg); + return false; + } + } + PLAYER_ID = DevID; + bass.BASS_SetDevice(DevID); + + BASS_INFO basinfo = new BASS_INFO(); + if (bass.BASS_GetInfo(basinfo)) { + basinfo.read(); + if (BA.debugMode) { + BA.Log("BASS INFO"); + BA.Log("Hardware Memory : "+basinfo.hwsize); + BA.Log("Hardware Free : "+basinfo.hwfree); + BA.Log("Free Sample Slot : "+basinfo.freesam); + BA.Log("Supported Samplingrate min="+basinfo.minrate+ " max="+basinfo.maxrate); + BA.Log("Current Samplingrate = "+basinfo.freq); + BA.Log("Minimum Buffer length (ms) = "+basinfo.minbuf); + BA.Log("Average Delay (ms) = "+basinfo.latency); + BA.Log(""); + } + } else { + if (BA.debugMode) BA.Log("Failed to get BASS_INFO at device="+DevID); + } + + + if (BA.debugMode) BA.Log("Device "+DevID+" initialized with Sampling="+SamplingRate); + bass_stop_UDPReceiver(listenport,true); // close previous connection if exist + + UDPReceiverData newdata = new UDPReceiverData(bax,bass,listenport, DevID,eventname); + newdata.setMaxBufLength(maxUDPSize); + if (newdata.OpenStream(SamplingRate,channels,0)==0) { + msg="Failed to Open Stream"; + raise_startUDPReceiver(false,DevID,listenport,msg); + if (BA.debugMode) BA.Log(msg); + return false; + } + + if (newdata.OpenUDP()) { + UDPReceiverMap.Put(listenport, newdata); + msg = "UDP Listening Started at "+newdata.ServerIP()+":"+listenport; + raise_startUDPReceiver(true, DevID, listenport, msg); + if (BA.debugMode) BA.Log(msg); + return true; + + } else { + msg = "Failed to Initialize UDP Listening at "+listenport; + raise_startUDPReceiver(false, DevID, listenport, msg); + if (BA.debugMode) BA.Log(msg); + newdata.CloseStream(); + newdata.CloseUDP(); + newdata = null; + return false; + } + } + + /** + * Check if UDP Receiver on listenport number , is playing or not + * @param listenport : UDP Port used + * @return True if still have byte buffer, or False if already empty + */ + public boolean bass_UDPReceiverIsPlaying(int listenport) { + if (UDPReceiverMap == null) return false; + if (UDPReceiverMap.IsInitialized()==false) return false; + if (UDPReceiverMap.getSize()==0) return false; + + String msg=""; + if (UDPReceiverMap.ContainsKey(listenport)) { + Object xx = UDPReceiverMap.Get(listenport); + if (xx instanceof UDPReceiverData) { + UDPReceiverData urd = (UDPReceiverData) xx; + return urd.ChannelIsActive(); + } else { + return false; + } + } else { + msg = "No UDP Listening at port "+listenport; + if (BA.debugMode) BA.Log(msg); + return false; + } + } + + /** + * Close UDPReceiver unit + * @param listenport : UDP Port used + * @param waituntilempty : if true, will hold until all bytes is played. If false, will stop immediately + */ + public void bass_stop_UDPReceiver(int listenport, boolean waituntilempty) { + if (UDPReceiverMap == null) return; + if (UDPReceiverMap.IsInitialized()==false) return; + if (UDPReceiverMap.getSize()==0) return; + + String msg = ""; + if (UDPReceiverMap.ContainsKey(listenport)) { + Object xx = UDPReceiverMap.Get(listenport); + if (xx instanceof UDPReceiverData) { + UDPReceiverData old = (UDPReceiverData) xx; + old.CloseUDP(); + while (old.ChannelIsActive()) { + try { + Thread.sleep(20); + } catch (InterruptedException e) { + break; + } + } + old.CloseStream(); + msg = "UDP Listening at port "+old.getListenPort()+" closed"; + if (BA.debugMode) BA.Log(msg); + raise_stopUDPReceiver(old.getDevID(), old.getListenPort(),msg ); + } else { + msg = "Object at ListenPort "+listenport+ " is not UDPReceiverData"; + if (BA.debugMode) BA.Log(msg); + raise_stopUDPReceiver(-1,listenport,msg); + } + UDPReceiverMap.Remove(listenport); + xx = null; + if (BA.debugMode) BA.Log(listenport+" is removed from UDPReceiverMap"); + } else { + msg = "No UDP Listening at port "+listenport; + if (BA.debugMode) BA.Log(msg); + raise_stopUDPReceiver(-1,listenport,msg); + } + } + +// /** +// * Open a file and play it. Doesn't return until it finished. MP3 / WAV file is possible +// * will raise playstart, playstop and playfail events +// * @param file is path and filename +// * @param flags playback flags +// * +// */ +// public void bass_playandwait(String file, int flags){ +// if (bass_openfile(file,flags)==0) { +// raise_playbackfail(BASS_ID,"file="+file); +// } else{ +// //PrepareBassEqualizer(current_play_handle); +// if (bass_start_output()) { +// if (bass_play(current_play_handle,true)) { +// while(true) { +// try { +// Thread.sleep(1000); +// GetVULevel(current_play_handle,0.02f); +// if (bass_isplaying(current_play_handle)==false) break; +// }catch (InterruptedException e) { +// break; +// } +// } +// bass_stopcurrentplayback(); +// bass_stop_output(); +// raise_playbackstop(BASS_ID,"file="+file); +// } else { +// raise_playbackfail(BASS_ID,"file="+file); +// } +// } else { +// raise_playbackfail(BASS_ID,"file"+file); +// } +// +// } +// } + + /** + * Get PCM bytes from any song, and resample it + * @param filenya , filename of song + * @param newsamplingrate , new sampling rate + * @param ismono, if true, then downmix to mono + * @return PCM bytes + */ +// public byte[] bass_getpcmbytes(String filenya, int newsamplingrate, boolean ismono) { +// int DevID = 0; // no sound, for decoding +// String msg = ""; +// BASS_DEVICEINFO idev = bass_getdeviceinfo(DevID); +// if (idev==null) { +// msg="Device "+DevID+ " is not available"; +// if (BA.debugMode) BA.Log(msg); +// return null; +// } +// if ((idev.flags & bass.BASS_DEVICE_ENABLED)==0) { +// // device is disabled +// msg="Device "+DevID+" is disabled"; +// if (BA.debugMode) BA.Log(msg); +// return null; +// } +// if ((idev.flags & bass.BASS_DEVICE_INIT)==0) { +// // need to initialize +// +// if (bass.BASS_Init(DevID, 44100, 0)==false) { +// msg="Failed to Initialize DevID="+DevID; +// if (BA.debugMode) BA.Log(msg); +// return null; +// } +// } +// bass.BASS_SetDevice(DevID); +// +// int mixflag = bass.BASS_STREAM_DECODE | bass.BASS_MIXER_END; +// int mixhandle = bassmix.BASS_Mixer_StreamCreate(newsamplingrate, 1, mixflag); +// if (mixhandle==0) { +// msg = "Failed to init BASSMIX, Errorcode = "+ bass.BASS_ErrorGetCode(); +// if (BA.debugMode) BA.Log(msg); +// return null; +// } +// +// int flag = bass.BASS_STREAM_DECODE; +// if (ismono) flag |= bass.BASS_SAMPLE_MONO; +// int handle = bass.BASS_StreamCreateFile(filenya, 0, 0, flag); +// if (handle==0) { +// msg = "Error opening "+filenya+" , Errorcode="+bass.BASS_ErrorGetCode(); +// if (BA.debugMode) BA.Log(msg); +// return null; +// } +// +// if (!bassmix.BASS_Mixer_StreamAddChannel(mixhandle, handle, bass.BASS_STREAM_AUTOFREE)) { +// msg = "Error adding Mixer Channel, Errorcode = "+bass.BASS_ErrorGetCode(); +// if (BA.debugMode) BA.Log(msg); +// return null; +// } +// +// bass.BASS_ChannelPlay(mixhandle, false); +// +// +// +// long size = bass.BASS_ChannelGetLength(handle, bass.BASS_POS_BYTE); +// if (BA.debugMode) BA.Log("Original Stream Size : "+size); +// double seconds = bass.BASS_ChannelBytes2Seconds(handle, size); +// if (BA.debugMode) BA.Log("Audio length in seconds : "+seconds); +// int mixsize = (int) bass.BASS_ChannelSeconds2Bytes(mixhandle, seconds); +// if (BA.debugMode) BA.Log("New Stream Size with New SamplingRate="+newsamplingrate+" is "+mixsize); +// +// +// //Pointer buf = new Memory(mixsize); +// ByteBuffer buf = ByteBuffer.allocate(mixsize); +// int ret = bass.BASS_ChannelGetData(mixhandle, buf, mixsize); +// if (ret==-1) { +// if (BA.debugMode) BA.Log("Error ChannelGetData, code = "+bass.BASS_ErrorGetCode()); +// return null; +// } +// +// //byte[] result = buf.getByteArray(0, mixsize); +// byte[] result = buf.array(); +// +// bassmix.BASS_Mixer_ChannelRemove(handle); +// bass.BASS_StreamFree(handle); +// bass.BASS_StreamFree(mixhandle); +// +// if (BA.debugMode) BA.Log("Stream released"); +// return result; +// +// } +// + + + /** + * Get current playback handle. 0 = idle, others=is playing + * @return current playback handle. + */ + public int GetCurrentPlayHandle(){ + return current_play_handle; + } + + /** + * Open a File , and play it continuously + * To stop , call bass_stopcurrentplayback + * @param file : filename of song + * @param flags : playback flag, default = 0 + * @return true if playback started + */ + public boolean bass_playfile_and_loop(String file, int flags) { + bass_stopcurrentplayback(); // current_play_handle sudah di-nol-kan di sini + bass_openfile(file,flags); // bass sudah init di sini, current_play_handle sudah update di sini + if (current_play_handle!=0) { + // handle is valid + if (bass_start_output()) { + // audio is started + bass.BASS_SetVolume(1.0f); // max sound + if (bass_play(current_play_handle,true)) { + // bass is playing + bass.BASS_ChannelSetAttribute(current_play_handle, bassconstant.BASS_ATTRIB_VOL, 1.0f); // max sound + raise_playbackstart(PLAYER_ID, "file="+file); + Thread tx = new Thread() { + public void run() { + while(true) { + + while(true) { + try { + Thread.sleep(100); + if (bass_isplaying(current_play_handle)==false) break; // di sini sudah cek current_play_handle = 0 atau tidak + + int result = (short) bass.BASS_ChannelGetLevel(current_play_handle); + if (result!= -1) { + raise_playbackvu(PLAYER_ID, (short)result); // ambil kiri aja + } + + } catch (InterruptedException e) { + break; + } + } + + + if (bass_play(current_play_handle,true)==false) break;// ada masalah playback ulang, atau current_play_handle sudah 0 + + } + bass_stop_output(); // matikan output + bass_stopcurrentplayback(); // current_play_handle sudah di-nol-kan di sini + + raise_playbackstop(PLAYER_ID,"file="+file); + } + }; + tx.start(); + return true; + + } else { + raise_errorhappened(PLAYER_ID,"playfile_and_loop::bass_play"); + raise_playbackfail(PLAYER_ID,"file="+file); + bass_stopcurrentplayback(); // current_play_handle sudah di-nol-kan di sini + return false; + } + } else { + raise_errorhappened(PLAYER_ID,"playfile_and_loop::bass_start_output"); + raise_playbackfail(PLAYER_ID,"file="+file); + bass_stopcurrentplayback(); + return false; + } + + } else { + // current_play_handle masih nol + raise_errorhappened(PLAYER_ID,"playfile_and_loop::bass_openfile"); + raise_playbackfail(PLAYER_ID, "file="+file); + return false; + } + } + + /** + * Open a file, and play directly if succesful. + * Combination between bass_openfile and bass_play. + * Will raise playstart, playstop, and playfail events + * @param filename : filename of song + * @param flags : playback flag, default=0 + * @return true if playback started + */ + public boolean bass_playfile(final String filename, int flags) { + final Bass_DeviceInfo idev = bass_getdeviceinfo(PLAYER_ID); + if (idev instanceof Bass_DeviceInfo) { + if (idev.IsEnabled()) { + if (idev.IsInited()) { + if (current_play_handle!=0) bass_stopcurrentplayback(); // kalau ada playback, stopin di sini + final int handleresult = bass.BASS_StreamCreateFile(false, filename, 0, 0, flags); + if (handleresult!=0) { + if (bass.BASS_Start()) { + + bass.BASS_SetVolume(1.0f); + bass.BASS_ChannelSetAttribute(handleresult, bassconstant.BASS_ATTRIB_VOL, 1.0f); + if (bass.BASS_ChannelPlay(handleresult, true)) { + final String playbackstatusinfo = "File = "+filename+", Device="+idev.Name; + raise_playbackstart(PLAYER_ID, playbackstatusinfo ); + current_play_handle = handleresult; + + Thread tx = new Thread() { + private int _level; + private int playstatus = -1; + public void run() { + while(true) { + try { + Thread.sleep(50); + } catch (InterruptedException e) { + break; + } + //if (current_play_handle==0) break; + int ps = bass.BASS_ChannelIsActive(handleresult); + if (ps==bassconstant.BASS_ACTIVE_PLAYING) { + if (playstatus!=ps) { + // ada perubahan status, sekarang jadi PLAYING + playstatus = ps; + raise_playbackstart(PLAYER_ID, playbackstatusinfo); + } + _level = bass.BASS_ChannelGetLevel(handleresult); + if (_level == -1) break; // possible cause : handle invalid, notplaying , atau ended + raise_playbackvu(PLAYER_ID, (short)_level); + } else if (ps == bassconstant.BASS_ACTIVE_STOPPED) { + if (playstatus != ps) { + // ada perubahan status, sekarang jadi STOPPED + playstatus = ps; + raise_playbackvu(PLAYER_ID,(short)0); + } + + break; // keluar dari while + } else if (ps == bassconstant.BASS_ACTIVE_PAUSED) { + if (playstatus != ps) { + // ada perubahan status, sekarang jadi PAUSED + playstatus = ps; + raise_playbackpause(PLAYER_ID, playbackstatusinfo); + raise_playbackvu(PLAYER_ID,(short)0); + } + // kalau pause, tidak keluar dari while + } else if (ps == bassconstant.BASS_ACTIVE_PAUSED_DEVICE) { + if (playstatus != ps) { + playstatus = ps; + raise_log(PLAYER_ID, "Playback device paused for "+playbackstatusinfo); + } + break; // keluar dari while + } else if (ps == bassconstant.BASS_ACTIVE_STALLED) { + if (playstatus != ps) { + playstatus = ps; + raise_log(PLAYER_ID, "Playback stalled for "+playbackstatusinfo); + } + // tidak keluar dari while + } + + } + + if (handleresult!=0) bass_stopplayback(handleresult); + //bass.BASS_Stop(); + raise_playbackstop(PLAYER_ID, playbackstatusinfo); + } + }; + tx.start(); + return true; + } + } + + + } else raise_errorhappened(PLAYER_ID,"Bass_StreamCreateFile file="+filename); + } else raise_log(PLAYER_ID,"Playback Device id="+PLAYER_ID+" is not Initialized"); + } else raise_log(PLAYER_ID,"Playback Device id ="+PLAYER_ID+" is disabled"); + } else raise_log(PLAYER_ID,"Playback Device id="+PLAYER_ID+" is not available"); + + // gagal semua + raise_playbackfail(PLAYER_ID, "File = "+filename); + return false; + +// setDeviceID(PLAYER_ID); +// bass_stopcurrentplayback(); // current_play_handle sudah di-nol-kan di sini +// bass_openfile(file,flags); // bass sudah init di sini, current_play_handle sudah update di sini +// if (current_play_handle!=0) { +// //PrepareBassEqualizer(current_play_handle); +// if (bass_start_output()) { +// if (bass_play(current_play_handle,true)) { +// bass.BASS_SetVolume(1.0f); +// bass.BASS_ChannelSetAttribute(current_play_handle, bassconstant.BASS_ATTRIB_VOL, 1.0f); // max sound +// raise_playbackstart(PLAYER_ID,"file="+file); +// Thread tx = new Thread(){ +// public void run(){ +// while(true) { +// try { +// Thread.sleep(100); +// if (bass_isplaying(current_play_handle)==false) break; // cek apakah current_play_handle masih aktif, dan masih playback +// int result = (short) bass.BASS_ChannelGetLevel(current_play_handle); +// if (result!= -1) { +// raise_playbackvu(PLAYER_ID, (short)result); // ambil kiri aja +// } +// } catch (InterruptedException e) { +// break; // if interrupt exception happened, exit while +// } +// } +// bass_stop_output(); +// bass_stopcurrentplayback(); // current_play_handle sudah di-nol-kan di sini +// raise_playbackstop(PLAYER_ID,"file="+file); +// +// } +// }; +// tx.start(); +// return true; +// } else { +// raise_errorhappened(PLAYER_ID,"playfile::bass_play"); +// raise_playbackfail(PLAYER_ID,"file="+file); +// bass_stopcurrentplayback(); // current_play_handle sudah di-nol-kan di sini; +// return false; +// } +// } else { +// raise_errorhappened(PLAYER_ID,"playfile::bass_start_output"); +// raise_playbackfail(PLAYER_ID,"file="+file); +// return false; +// } +// +// } else { +// raise_errorhappened(PLAYER_ID,"playfile::bass_openfile"); +// raise_playbackfail(PLAYER_ID,"file="+file); +// return false; +// } + } + + /** + * Stop specific playback handle + * @param handle playback handle to stop + */ + public void bass_stopplayback(int handle) { + if (handle!=0) { + if (!bass.BASS_ChannelStop(handle)) { + raise_errorhappened(PLAYER_ID,"StopPlayback::ChannelStop"); + } + if (!bass.BASS_FXReset(handle)) { + raise_errorhappened(PLAYER_ID,"StopPlayback::FXReset"); + } + if (!bass.BASS_StreamFree(handle)) { + raise_errorhappened(PLAYER_ID,"StopPlayback::StreamFree"); + } + } + } + + /** + * Force stop current playback if exist + */ + public void bass_stopcurrentplayback(){ + if (current_play_handle!=0) { + if (!bass.BASS_ChannelStop(current_play_handle)) { + raise_errorhappened(PLAYER_ID,"StopCurrentPlayback::ChannelStop"); + } + if (!bass.BASS_FXReset(current_play_handle)) { + raise_errorhappened(PLAYER_ID,"StopCurrentPlayback::FXReset"); + } + if (!bass.BASS_StreamFree(current_play_handle)) { + raise_errorhappened(PLAYER_ID,"StopCurrentPlayback::StreamFree"); + } + current_play_handle=0; + } + if (Buffer1!=null) { + Buffer1 = null; + } + + } + + /** + * Get current song's length in seconds + * @return song length in seconds + */ + public double bass_GetCurrentSongLength(){ + if (current_play_handle==0) { + return 0; + } + + long byteslength = bass.BASS_ChannelGetLength(current_play_handle, bassconstant.BASS_POS_BYTE); + if (byteslength==-1) { + raise_errorhappened(PLAYER_ID,"GetCurrentSongLength::ChannelGetLength"); + return 0; + } + + double seconds = bass.BASS_ChannelBytes2Seconds(current_play_handle, byteslength); + if (seconds>0) + return seconds; + else { + raise_errorhappened(PLAYER_ID,"GetCurrentSongLength::ChannelBytes2Seconds"); + return 0; + } + } + + public String GetSongLength(String filenya, int flags) { + int cc = 0; + cc = bass.BASS_StreamCreateFile(false,filenya, 0, 0, flags); + + String result="00:00"; + if (cc!=0) { + long byteslength = bass.BASS_ChannelGetLength(cc, bassconstant.BASS_POS_BYTE); + if (byteslength!=-1) { + double seconds = bass.BASS_ChannelBytes2Seconds(cc, byteslength); + if (seconds>0) { + int rounded = (int) seconds; + int mm = (int)(rounded / 60); + int ss = (int)(rounded % 60); + StringBuilder str = new StringBuilder(); + if (mm<10) { + str.append("0").append(mm); + } else { + str.append(mm); + } + str.append(":"); + if (ss<10) { + str.append("0").append(ss); + } else { + str.append(ss); + } + result = str.toString(); + } else raise_errorhappened(PLAYER_ID,"GetSongLength::ChannelBytes2Seconds"); + } else raise_errorhappened(PLAYER_ID,"GetSongLength::ChannelGetLength"); + } else raise_errorhappened(PLAYER_ID,"GetSongLength::StreamCreateFile"); + return result; + } + + /** + * Open a file. MP3 / WAV File is possible + * @param filename is path and filename + * @param flags playback flags + * @return 0 if fail, others if success. Return value may positive or negative. + */ + public int bass_openfile(String filename, int flags) { + int result = 0; + + if (bass_init(PLAYER_ID,playback_samplingrate,0)) { + result = bass.BASS_StreamCreateFile(false,filename, 0, 0, flags); + if (result != 0) { + current_play_handle = result; + } else raise_errorhappened(PLAYER_ID,"OpenFile::StreamCreateFile"); + } else { + raise_errorhappened(PLAYER_ID,"OpenFile::Init"); + } + + return result; + } + + STREAMPROC mystreamproc = new STREAMPROC() { + @Override + public int STREAMPROC(int handle, Pointer bufferx, int length, Pointer user) { + if (Buffer1!=null) { + synchronized(Buffer1) { + + if (Buffer1.position()>0) { + // ada yang bisa dibaca + int max_can_read = length > Buffer1.position() ? Buffer1.position() : length; + byte[] BX = new byte[max_can_read]; + + Buffer1.flip(); + Buffer1.get(BX); + Buffer1.compact(); + Buffer1.notify(); + bufferx.write(0, BX, 0, max_can_read); + + + raise_streambufferstatus(PLAYER_ID); + long nano = System.nanoTime(); + long prev = streamprocnano.getAndSet(nano); + //BA.Log("STREAMPROC write "+max_can_read+" bytes to Pointer, position = "+position+" interval = "+ df.format((nano-prev)/1000000)+" ms"); + + raise_streamrunning(PLAYER_ID); + return max_can_read; + } + raise_streambufferstatus(PLAYER_ID); + raise_streamidle(PLAYER_ID); + return 0; + } + } + raise_streamstop(PLAYER_ID); + return bassconstant.BASS_STREAMPROC_END; + } + }; + + + + + /** + * Open a general usage stream channel. Use function pushdata to input PCM data to bass + * will raise event streamstart, streamstop, streamidle, streamrunning, and streambufferstatus events + * @param freq : enter desired frequency + * @param channels : 1=mono, 2=stereo + * @param flags : enter desired flags + * @param config_buffer : buffer length in ms, valid value is 10 ms to 5000 ms, default to 1000 ms + * @return stream handle (int) + */ + public int bass_openstream(int freq, int channels, int flags, int config_buffer){ + bass_stopcurrentplayback(); // current_play_handle sudah nol di sini + Buffer1 = ByteBuffer.allocate(buffersize); + + + if (bass_init(PLAYER_ID,freq, 0)) { + if (config_buffer<10) config_buffer = 1000; + if (config_buffer>5000) config_buffer = 1000; + bass.BASS_SetConfig(bassconstant.BASS_CONFIG_BUFFER, config_buffer); + + //BA.Log("setting playback buffer to 5000 ms"); + current_play_handle=bass.BASS_StreamCreate(freq, channels, flags, mystreamproc, null); + streamprocnano.set(System.nanoTime()); + if (current_play_handle==0) { + raise_errorhappened(PLAYER_ID,"OpenStream::StreamCreate"); + raise_streamstop(PLAYER_ID); + } else { + + if (!bass.BASS_ChannelPlay(current_play_handle, false)) { + raise_errorhappened(PLAYER_ID,"OpenStream::ChannelPlay"); + } + DSP_for_playbackvu("OpenStream", PLAYER_ID, current_play_handle); + raise_streamstart(PLAYER_ID); + streamprocnanotime.setValue(System.nanoTime()); + } + } + return current_play_handle; + } + + /** + * Open a general usage stream channel. Use function bass_streamputdata to input PCM data to bass + * @param freq : enter desired frequency + * @param channels : 1=mono, 2=stereo + * @param flags : enter desired flags + * @return stream handle (int) + */ + public int bass_openstreampush(int freq,int channels, int flags) { + + + bass_stopcurrentplayback(); + Buffer1 = ByteBuffer.allocate(buffersize); + + + current_play_handle = bass.BASS_StreamCreate(freq, channels, flags, bassconstant.STREAMPROC_PUSH, null); + if (current_play_handle!=0) { + if (bass.BASS_ChannelPlay(current_play_handle, true)) { + DSP_for_playbackvu("OpenStreamPush", PLAYER_ID, current_play_handle); + raise_streamstart(PLAYER_ID); + } else { + raise_errorhappened(PLAYER_ID,"OpenStreamPush::ChannelPlay"); + raise_streamstop(PLAYER_ID); + } + } else { + raise_errorhappened(PLAYER_ID,"OpenStreamPush::StreamCreate"); + raise_streamstop(PLAYER_ID); + } + + return current_play_handle; + } + + private void DSP_for_playbackvu(String functionname, int ID, int handle) { + if (handle!=0) { + BA.submitRunnable(new Runnable() { + + @Override + public void run() { + while(true) { + try { + Thread.sleep(100); + + } catch (InterruptedException e) { + break; + } + int val = BASS.BASS_ChannelGetLevel(handle); + if (val==-1) { + raise_errorhappened(ID,"DSP_for_playbackvu::BASS_ChannelGetLevel"); + break; + } + + short leftval = (short)(val & 0xFFFF); + short vuval = (short)((double)(leftval * 100)/Short.MAX_VALUE); + raise_playbackvu(ID, vuval); + } + + } + + }, null, 0); + } + + } + + + + FILECLOSEPROC fc = new FILECLOSEPROC() { + + @Override + public void FILECLOSEPROC(Pointer user) { + BA.Log("FILECLOSEPROC is called"); + raise_streamstop(PLAYER_ID); + } + }; + + FILELENPROC fl = new FILELENPROC() { + + @Override + public long FILELENPROC(Pointer user) { + BA.Log("FILELENPROC is called"); + return 0; // 0 = size unknown + //Returning 0 for a buffered file stream makes BASS stream the file in blocks + //and is equivalent to using the BASS_STREAM_BLOCK flag in the BASS_StreamCreateFileUser call. + } + + }; + + FILEREADPROC fr = new FILEREADPROC() { + + @Override + public int FILEREADPROC(Pointer buffer, int length, Pointer user) { + BA.Log("FILEREADPROC is called"); + // Catatan : return 0 = selesai. Akan lanjut ke FILECLOSEPROC + // kalau belum ada data streaming masuk (UDP / TCP) harus ditahan terus di sini + + if (Buffer1 == null) return 0; // kalau buffer1 sudah null, artinya selesai + synchronized(buffer1size) { + if (Buffer1.position()<1) { + // kalau tidak ada data, coba ditunggu 30 detik + raise_streamidle(PLAYER_ID); + try { + buffer1size.wait(30*1000); + } catch (InterruptedException e) { + // tidak ada data setelah 30 detik, ataupun ada interrupt exception + return 0; // selesai + } + + // check lagi setelah wait selesai, atau buffer sudah notify + if (Buffer1.position()<1) return 0; // + } + + Buffer1.flip(); // pindah mode baca + int readsize = Buffer1.remaining(); // berapa jumlah data bisa dibaca + if (readsize > length) readsize = length; // kalau jumlah yang bisa dibaca, lebih dari length, maka crop ke length + + byte[] readbuf = new byte[readsize]; // alokasi bytes untuk baca + Buffer1.get(readbuf); // baca dari Buffer1 + Buffer1.compact(); // siapkan buffer1 untuk penulisan berikutnya + + buffer1size.set(Buffer1.position()); // update buffer1size + buffer.write(0, readbuf, 0, readsize); // tulis bytes ke Pointer buffer + raise_streamrunning(PLAYER_ID); + return readsize; + + } + + + + + } + + }; + + FILESEEKPROC fs = new FILESEEKPROC() { + + @Override + public boolean FILESEEKPROC(long offset, Pointer user) { + BA.Log("FILESEEKPROC is called"); + return false; // false = tidak support Seek + } + + }; + + + + /** + * Open MP1/MP2/MP3/OGG/WAV stream channel. Use function bufferpush to fill buffer + * @param devid : deviceID to receive MP3 stream + * @param flags : enter desired flags + * @return stream handle (int) + */ + public int bass_openstreamMP3(int devid, int flags) { + + if (current_play_handle!=0) bass_stopcurrentplayback(); + Buffer1 = ByteBuffer.allocate(buffersize); + + + if (bass_init(devid, playback_samplingrate, 0)) { + BASS_FILEPROCS myfileproc = new BASS_FILEPROCS(fc, fl, fr, fs); + //myfileproc.write(); // sudah di write di constructor + current_play_handle = bass.BASS_StreamCreateFileUser(bassconstant.STREAMFILE_BUFFER, flags,myfileproc, null); + if (BA.debugMode) BA.Log("MP3 Stream handle="+current_play_handle); + if (current_play_handle==0) { + raise_errorhappened(devid,"openstreamMP3::StreamCreateFileUser"); + raise_streamstop(devid); + + } else { + bass_start_output(); + bass_play(current_play_handle,true); + raise_streamstart(devid); + } + } + return current_play_handle; + } + + + + /** + * Open an URL for Streaming + * @param url : valid URL from internet + * @param flags : enter desired flags + * @return stream handle (int) + */ + public int bass_openURL(String url, int flags){ + + + if (current_play_handle!=0) bass_stopcurrentplayback(); + + if (bass_init(PLAYER_ID,playback_samplingrate,0)) { + raise_log(PLAYER_ID,"Opening URL : "+url); + + current_play_handle=bass.BASS_StreamCreateURL(url, 0, (bassconstant.BASS_STREAM_BLOCK | bassconstant.BASS_STREAM_AUTOFREE), mydownproc,null); + if (current_play_handle==0) { + raise_streamstop(PLAYER_ID); + raise_errorhappened(PLAYER_ID,"openURL::StreamCreateURL"); + } else { + downloadnanotime.setValue(System.nanoTime()); + if (!bass.BASS_ChannelPlay(current_play_handle, true)) { + raise_errorhappened(PLAYER_ID, "OpenURL::ChannelPlay"); + raise_streamstop(PLAYER_ID); + } else raise_streamstart(PLAYER_ID); + } + } + + return current_play_handle; + } + + + + /** + * Write PCM data to playback output + * @param handle : from openstream handle + * @param data : data array + * @param length : 0=data array length + * @return datalength written + */ + public int bass_streamputdata(int handle, byte[] data, int length){ + if (handle!=current_play_handle) return 0; + setDeviceID(PLAYER_ID); + if (length<1) return 0; + if (length>data.length) length = data.length; + Pointer pdata = new Memory(length); + pdata.write(0, data, 0, length); + int result = bass.BASS_StreamPutData(handle, pdata, length); + if (result==-1) { + raise_errorhappened(PLAYER_ID,"StreamPutData"); + } + return result; + } + + +// /** +// * Write non-PCM data to playback output +// * @param handle : from openstream handle +// * @param data : data array +// * @param length : 0=data array length +// * @return datalength written +// */ +// public int bass_streamputfiledata(int handle, byte[] data, int length){ +// if (handle!=current_play_handle) return 0; +// setDeviceID(PLAYER_ID); +// if (length<1) return 0; +// if (length>data.length) length = data.length; +// Pointer pdata = new Memory(length); +// pdata.write(0, data, 0, length); +// int result = bass.BASS_StreamPutFileData(handle, pdata, length); +// if (result==-1) { +// raise_errorhappened(PLAYER_ID,"StreamPutFileData"); +// } +// return result; +// } + + /** + * Get Record Device Information + * @param device : ID start from 0 + * @return BASS_DEVICEINFO structure + */ + public Bass_DeviceInfo bass_recordgetdeviceinfo(int device){ + BASS_DEVICEINFO result = new BASS_DEVICEINFO(); + if (bass.BASS_RecordGetDeviceInfo(device, result)) { + result.read(); + return new Bass_DeviceInfo(device,result); + } else return null; + + } + + /** + * Get all Recording devices detected in system + * @return List of BASS_DEVICEINFO structure + */ + public List bass_recordgetdeviceinfos(){ + List result = new List(); + result.Initialize(); + int xx = 0; + while(true) { + Bass_DeviceInfo tmp = bass_recordgetdeviceinfo(xx); + if (tmp==null) break; // udah abis + if (tmp.IsEnabled()) { + result.Add(tmp); + } + xx+=1; + } + + return result; + } + + /** + * Init record device before usage + * @param dev : ID start from 1 + * @return true if recording device inited + */ + public boolean bass_recordinit(int dev){ + RECORDER_ID=dev; + if (!bass.BASS_SetConfig(bassconstant.BASS_CONFIG_REC_BUFFER, 2000)) // value = 1000 - 5000 + { + raise_errorhappened(RECORDER_ID,"RecordInit::SetConfig"); + } + if (bass.BASS_RecordInit(RECORDER_ID)) { + return true; + } else { + raise_errorhappened(RECORDER_ID,"RecordInit::RecordInit"); + return false; + } + + } + + /** + * Get/Set current thread's record device ID + * @param val : ID start from 1 + */ + public void setRecordDeviceID(int val){ + + if (!bass.BASS_RecordSetDevice(RECORDER_ID)) { + int errcode = bass.BASS_ErrorGetCode(); + if (errcode==bassconstant.BASS_ERROR_DEVICE) { + raise_log(val,"SetRecordDeviceID failed, Device "+val+" is not exist"); + } else if (errcode == bassconstant.BASS_ERROR_INIT) { + if (bass.BASS_RecordInit(val)) { + raise_log(val,"RecordInit Device "+val+" is succesful"); + RECORDER_ID = val; + } else { + raise_errorhappened(val,"SetRecordDeviceID::RecordInit"); + } + } + + } + } + + public int getRecordDeviceID(){ + return RECORDER_ID; + } + + /** + * Get BASS_RECORDINFO. + * @return BASS_RECORDINFO + */ + public BASS_RECORDINFO bass_recordgetinfo(){ + BASS_RECORDINFO xx = new BASS_RECORDINFO(); + if (bass.BASS_RecordGetInfo(xx)==false) { + raise_errorhappened(RECORDER_ID,"bass_recordgetinfo"); + } + xx.read(); + return xx; + } + + /** + * Get BASS_GetInfo status + * @return BASS_INFO + */ + public BASS_INFO bass_GetInfo(){ + BASS_INFO xx = new BASS_INFO(); + if (bass.BASS_GetInfo(xx)==false) { + raise_errorhappened(PLAYER_ID,"bass_GetInfo"); + } + xx.read(); + return xx; + } + + /** + * Get input name + * @param input : start from 0 + * @return Input name as string or empty string if failed + */ + public String bass_recordgetinputname(int input){ + if (bass.BASS_RecordSetDevice(RECORDER_ID)) { + String name = bass.BASS_RecordGetInputName(input); + if (name==null) { + raise_errorhappened(RECORDER_ID,"RecordGetInputName::RecordGetInputName"); + } else if (name=="null") { + raise_errorhappened(RECORDER_ID,"RecordGetInputName::RecordGetInputName"); + } else { + return name; + } + } else raise_errorhappened(RECORDER_ID,"RecordGetInputName::RecordSetDevice"); + return ""; + } + + /** + * Set current input for recording + * @param input : start from 0 + * @param flags : enter desired flags + * BASS_INPUT_OFF Disable the input. This flag cannot be used when the device supports only one input at a time. + * BASS_INPUT_ON Enable the input. If the device only allows one input at a time, then any previously enabled input will be disabled by this + * @param volume : enter volume from 0.0 to 1.0 + * @return true if record input set. + */ + public boolean bass_recordsetinput(int input, int flags, float volume){ + if (bass.BASS_RecordSetDevice(RECORDER_ID)) { + if (bass.BASS_RecordSetInput(input, flags, volume)) { + return true; + } else raise_errorhappened(RECORDER_ID,"RecordSetInput::RecordSetInput"); + } else raise_errorhappened(RECORDER_ID,"RecordSetInput::RecordSetDevice"); + + return false; + } + + /** + * Get current input for recording + * @param input : 0 = first......, -1 = master + * @param volume : enter volume from 0.0 to 1.0 + * @return true if input get + */ + public boolean bass_recordgetinput(int input, RecordGetInputResult resultnya){ + if (bass.BASS_RecordSetDevice(RECORDER_ID)) { + //Pointer pp = new Memory(4); + FloatByReference pp = new FloatByReference(); + int result = bass.BASS_RecordGetInput(input, pp); + if (result!= -1) { + + switch(result) { + case bassconstant.BASS_INPUT_OFF : + // input is disabled + if (resultnya!=null) { + resultnya.IsOn = false; + resultnya.InputName = "Input Disabled"; + resultnya.Volume = pp.getValue(); //pp.getFloat(0); + } + break; + case bassconstant.BASS_INPUT_TYPE_DIGITAL : + // input is Digital input source, for example, a DAT or audio CD. + if (resultnya!=null) { + resultnya.IsOn = true; + resultnya.InputName = "Digital input source"; + resultnya.Volume = pp.getValue();//pp.getFloat(0); + } + break; + case bassconstant.BASS_INPUT_TYPE_LINE : + // input is Line-in. On some devices, "Line-in" may be combined with other analog sources into a single BASS_INPUT_TYPE_ANALOG input. + if (resultnya!=null) { + resultnya.IsOn = true; + resultnya.InputName = "Line-In"; + resultnya.Volume = pp.getValue();//pp.getFloat(0); + } + + break; + case bassconstant.BASS_INPUT_TYPE_MIC : + // input is Microphone + if (resultnya!=null) { + resultnya.IsOn = true; + resultnya.InputName = "Microphone"; + resultnya.Volume = pp.getValue();//pp.getFloat(0); + } + break; + case bassconstant.BASS_INPUT_TYPE_SYNTH : + // input is Internal MIDI synthesizer + if (resultnya!=null) { + resultnya.IsOn = true; + resultnya.InputName = "Internal MIDI synthesizer"; + resultnya.Volume = pp.getValue();//pp.getFloat(0); + } + break; + case bassconstant.BASS_INPUT_TYPE_CD : + // input is Analog audio CD + if (resultnya!=null) { + resultnya.IsOn = true; + resultnya.InputName = "Analog audio CD"; + resultnya.Volume = pp.getValue();//pp.getFloat(0); + } + break; + case bassconstant.BASS_INPUT_TYPE_PHONE : + // input is Telephone + if (resultnya!=null) { + resultnya.IsOn = true; + resultnya.InputName = "Telephone"; + resultnya.Volume = pp.getValue();//pp.getFloat(0); + } + break; + case bassconstant.BASS_INPUT_TYPE_SPEAKER : + // input is PC speaker + if (resultnya!=null) { + resultnya.IsOn = true; + resultnya.InputName = "PC speaker"; + resultnya.Volume = pp.getValue();//pp.getFloat(0); + } + break; + case bassconstant.BASS_INPUT_TYPE_WAVE : + // input is The device's WAVE/PCM output + if (resultnya!=null) { + resultnya.IsOn = true; + resultnya.InputName = "WAVE/PCM output"; + resultnya.Volume = pp.getValue();//pp.getFloat(0); + } + break; + case bassconstant.BASS_INPUT_TYPE_AUX : + // Auxiliary. Like "Line-in", "Aux" may be combined with other analog sources into a single BASS_INPUT_TYPE_ANALOG input on some devices + if (resultnya!=null) { + resultnya.IsOn = true; + resultnya.InputName = "Auxiliary"; + resultnya.Volume = pp.getValue();//pp.getFloat(0); + } + break; + case bassconstant.BASS_INPUT_TYPE_ANALOG : + // input is Analog, typically a mix of all analog sources + if (resultnya!=null) { + resultnya.IsOn = true; + resultnya.InputName = "Analog Mix"; + resultnya.Volume = pp.getValue();//pp.getFloat(0); + } + break; + case bassconstant.BASS_INPUT_TYPE_UNDEF : + // input is Anything that is not covered by the other types. + if (resultnya!=null) { + resultnya.IsOn = true; + resultnya.InputName = "Input Undefined"; + resultnya.Volume = pp.getValue();//pp.getFloat(0); + } + break; + default : + // input is Unknown + if (resultnya!=null) { + resultnya.IsOn = false; + resultnya.InputName = "Unknown Result"; + resultnya.Volume = pp.getValue();//pp.getFloat(0); + } + break; + } + + return true; + } else raise_errorhappened(RECORDER_ID,"RecordGetInput::RecordGetInput"); + } else raise_errorhappened(RECORDER_ID,"RecordGetInput::RecordSetDevice"); + + return false; + } + + private HandleWavWriter wavwriter; + private RECORDPROC recproc = new RECORDPROC() { + + @Override + public boolean RECORDPROC(int handle, Pointer buffer, int length, Pointer user) { + if (current_record_handle==0) return false; + if (!recordcontinue) return false; + + byte[] buf = new byte[length]; + buffer.read(0, buf, 0, length); + raise_recordbuffer(RECORDER_ID,buf); + short vu = CalculateVU(buf); + raise_recordvu(RECORDER_ID,vu); + return true; + } + }; + + private RECORDPROC recproc1 = new RECORDPROC() { + + @Override + public boolean RECORDPROC(int handle, Pointer buffer, int length, Pointer user) { + if (current_record_handle==0) { + BA.Log("RecordProc1 called, but current_record_handle=0"); + return false; + } + if (!recordcontinue) { + BA.Log("RecordProc1 called, but recordcontinue=false"); + return false; + } + //BA.Log("RecordProc1 called, length = "+length); + byte[] xx = new byte[length]; + buffer.read(0, xx, 0, length); + ByteBuffer buf = ByteBuffer.wrap(xx); + while(buf.hasRemaining()) { + int read = buf.remaining()>1000 ? 1000 : buf.remaining(); + byte[] b = new byte[read]; + buf.get(b); + raise_recordbuffer(RECORDER_ID,b); + } + + short vu = CalculateVU(buf); + //BA.Log("RecordProc1 vu = "+vu); + raise_recordvu(RECORDER_ID,vu); + + return true; + } + + }; + + private short CalculateVU(ByteBuffer bb) { + ShortBuffer sb = bb.order(ByteOrder.LITTLE_ENDIAN).asShortBuffer(); + sb.rewind(); + short tmp = 0; + short biggest = 0; + while (sb.hasRemaining()) { + tmp = sb.get(); + if (tmp > biggest) + biggest = tmp; + } + return biggest; + } + + + private short CalculateVU(byte[] buffer) { + ByteBuffer bb = ByteBuffer.wrap(buffer); + return CalculateVU(bb); + } + + /** + * Start Recording directly to file + * Using this function, will automatically initialize record device and write WAV file + * @param recdevice : record device, start from 1 + * @param freq : sampling rate + * @param channels : 1 = mono, 2 = stereo + * @param flags = record flag + * @param targetfilename : valid writable filepath + * @return 0 = failed, other = valid record handle + */ + public int bass_recordstart_filename(int recdevice, int freq, int channels, int flags, String targetfilename) { + current_record_handle = 0; + recordcontinue = false; + + setRecordDeviceID(recdevice); // set and init if necessary + BASS_RECORDINFO inf = new BASS_RECORDINFO(); + if (!bass.BASS_RecordGetInfo(inf)) { + raise_errorhappened(recdevice,"RecordStartFilename::RecordGetInfo"); + } else { + inf.read(); // baca isi + StringBuilder str = new StringBuilder(); + str.append("Recorder has "+inf.inputs+" inputs \r\n"); + str.append("Best samplingrate at "+inf.freq+" \r\n"); + raise_log(recdevice,str.toString()); + str = null; + } + + current_record_handle=bass.BASS_RecordStart(freq, channels, flags, recproc, null); + if (current_record_handle==0) { + raise_recordfail(recdevice); + raise_errorhappened(recdevice, "RecordStartFilename::RecordStart"); + bass_recordstop(); + return 0; + } + + wavwriter = new HandleWavWriter(); + wavwriter.Initialize(null, ""); + if (!wavwriter.CreateFile_from_Handle(targetfilename, current_record_handle)) { + raise_log(recdevice,"Failed to CreateFile_from_Handle for "+targetfilename); + raise_recordfail(recdevice); + bass_recordstop(); + return 0; + } + + recordcontinue = true; + raise_recordstart(recdevice); + return current_record_handle; + } + + + + + /** + * Start Recording fro record device + * will raise event recordbuffer + * @param recdevice Record Device, start from 0 + * @param freq sampling rate + * @param channels 1=mono, 2=stereo + * @param flags enter desired flags + * @param recordlength record length in ms, from 10 ms to 5000 ms, default 1000 ms + * @return + */ + public int bass_recordstart1(int recdevice, int freq, int channels, int flags, int recordlength) { + current_record_handle=0; + recordcontinue=false; + + setRecordDeviceID(recdevice); // set and init if necessary + BASS_RECORDINFO inf = new BASS_RECORDINFO(); + if (!bass.BASS_RecordGetInfo(inf)) { + raise_errorhappened(recdevice,"RecordStart::RecordGetInfo"); + } else { + inf.read(); // baca isi + StringBuilder str = new StringBuilder(); + str.append("Recorder has "+inf.inputs+" inputs \r\n"); + str.append("Best samplingrate at "+inf.freq+" \r\n"); + raise_log(recdevice,str.toString()); + str = null; + } + if (recordlength<10) recordlength = 1000; + if (recordlength>5000) recordlength = 1000; + BA.Log("Record Buffer set to max "+recordlength+" ms"); + bass.BASS_SetConfig(bassconstant.BASS_CONFIG_REC_BUFFER, recordlength); // value = 10 - 5000 + BA.Log("About to start recording"); + current_record_handle=bass.BASS_RecordStart(freq, channels, flags, recproc1, null); + if (current_record_handle!=0) { + BA.Log("Recordhandle created"); + raise_recordstart(recdevice); + recordcontinue = true; + } else { + BA.Log("Recordhandle not created"); + raise_recordfail(recdevice); + raise_errorhappened(recdevice, "RecordStart1::RecordStart"); + bass_recordstop(); + } + + return current_record_handle; + } + + + /** + * Start recording on these parameters + * @param recdevice : Record Device + * @param freq : enter desired sampling rate + * @param channels : 1=mono, 2=stereo + * @param flags : enter desired flags + * @return 0=fail to record, others = success + */ + public int bass_recordstart(int recdevice, int freq, int channels, int flags){ + + current_record_handle=0; + recordcontinue=false; + + setRecordDeviceID(recdevice); // set and init if necessary + BASS_RECORDINFO inf = new BASS_RECORDINFO(); + if (!bass.BASS_RecordGetInfo(inf)) { + raise_errorhappened(recdevice,"RecordStart::RecordGetInfo"); + } else { + inf.read(); // baca isi + StringBuilder str = new StringBuilder(); + str.append("Recorder has "+inf.inputs+" inputs \r\n"); + str.append("Best samplingrate at "+inf.freq+" \r\n"); + raise_log(recdevice,str.toString()); + str = null; + } + + //flags = Utils.MAKELONG(flags, 10); // 10 = myrecproc akan dipanggil setiap 10 ms ---> tidak bagus !! interval loncat loncat gak jelas + //current_record_handle=bass.BASS_RecordStart(freq, channels, flags, myrecproc, null); --> kalau mau jumlah data konstan, tidak pakai recordproc, di-polling pakai thread aja !! + // set pakai ATTRIB_GRANULE gak fungsi !! + + bass.BASS_SetConfig(bassconstant.BASS_CONFIG_REC_BUFFER, 2000); // value = 1000 - 5000, default 2000 + + current_record_handle=bass.BASS_RecordStart(freq, channels, flags, null, null); + if (current_record_handle==0) { + raise_recordfail(recdevice); + return 0; + } + + //recordcontinue = true; // nanti buat evaluasi di recordproc. Karena di-polling, maka disable di sini, enable di thread + //raise_recordstart(recdevice); + //recordnanotime = System.nanoTime(); + + + Thread tx = new Thread() { + public void run() { + int readsize=0; + + recordnanotime.setValue(System.nanoTime()); + recordcontinue = true; + raise_recordstart(recdevice); + while(true) { + try { + Thread.sleep(2); + } catch (InterruptedException e) { + raise_errorhappened(RECORDER_ID,"Recordstart:thread:InterruptedException"); + break; + } + if (!recordcontinue) break; + + if (current_record_handle==0) + break; // record handle 0 + else + readsize = bass.BASS_ChannelGetData(current_record_handle, null, bassconstant.BASS_DATA_AVAILABLE); + + if (readsize<1000) continue; + + Pointer buf = new Memory(1000); // allocate buffer 1000 bytes + + if (current_record_handle==0) + break; + else + readsize = bass.BASS_ChannelGetData(current_record_handle,buf,1000); + + if (readsize<0) break; // somehow recording stopped + + byte[] xx = buf.getByteArray(0, readsize); + + //BA.Log("Record Thread length="+readsize+" bytes, Interval="+calculate_interval(recordnanotime)+" ms"); + + raise_recordbuffer(recdevice, xx); // raise event + GetVULevel(current_record_handle, 0.02f); // raise VU + + } + raise_recordstop(recdevice); + } + }; + tx.start(); + + return current_record_handle; + } + + + + /** + * Stop Current Recording Process + */ + public void bass_recordstop(){ + recordcontinue=false; + + if (current_record_handle!=0) { + if (!bass.BASS_ChannelStop(current_record_handle)) { //stop the recording and free its resources (Tidak usah StreamFree lagi) + raise_errorhappened(RECORDER_ID,"RecordStop::ChannelStop"); + } + current_record_handle=0; + } + if (!bass.BASS_RecordFree()) { + raise_errorhappened(RECORDER_ID,"RecordStop::RecordFree"); + } + + raise_recordstop(RECORDER_ID); // kalau pakai RECORDPROC. Kalau pakai Thread, disable aja + } + + + // recordproc default dipanggil tiap 100 ms + // mendingan pakai polling , supaya jumlah data diambil lebih konstan +// RECORDPROC myrecproc = new RECORDPROC() { +// +// @Override +// public boolean RECORDPROC(int handle, Pointer buffer, int length, Object user) { +// long xx = System.nanoTime() - recordnanotime; // interval dalam nano second +// recordnanotime = System.nanoTime(); +// double interval = (xx / 1000000.0); // ubah jadi ms +// interval = ((long)(interval * 100)) / 100.0; // pembulatan 2 desimal +// +// BA.Log("Recordproc handle="+handle+" length="+length+", Interval = "+interval+" ms"); +// if (handle==0) return false; /// false = berhenti +// return recordcontinue; // true = lanjut, false = berhenti +// } +// +// }; + + + private String calculate_interval(LongByReference value) { + long xx = System.nanoTime() - value.getValue(); // ambil beda ns dengan system + value.setValue(System.nanoTime());; //update sekarang + double yy = xx / 1000000.0; // ubah jadi ms + yy = (int)(yy * 100) / 100.0; // pembulatan 2 desimal + return String.valueOf(yy); + + } + + + + + public static void main(String args[]) { + + } +} diff --git a/src/jbass/RecordGetInputResult.java b/src/jbass/RecordGetInputResult.java new file mode 100644 index 0000000..5528d95 --- /dev/null +++ b/src/jbass/RecordGetInputResult.java @@ -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; +} diff --git a/src/jbass/UDPReceiverData.java b/src/jbass/UDPReceiverData.java new file mode 100644 index 0000000..9bbba3d --- /dev/null +++ b/src/jbass/UDPReceiverData.java @@ -0,0 +1,351 @@ +package jbass; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.SocketException; + +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; + + +@BA.ShortName("UDPReceiverData") + +/** + * Class untuk UDP receiving + * @author rdkartono + * + */ +public class UDPReceiverData { + private int listenport; + private int devid; + private DatagramSocket udp; + private boolean udpready; + private InetAddress myaddress; + private int basshandle; + private boolean debugmode = false; + private int maxbuflen = 1000; + private String eventname=""; + private BA bax; + private boolean need_event_recordbuffer=false; + private boolean need_event_recordvu = false; + private final String event_recordbuffer = "_recordbuffer"; + private final String event_recordvu = "_recordvu"; + private Thread udpmonitor; + private Thread vumonitor; + + + /** + * Initialize UDPReceiverData + * @param ListenPort UDP Port + * @param DevID Bass Device ID + */ + UDPReceiverData(BA xx,BASS bass2,int ListenPort, int DevID,String event) { + listenport = ListenPort; + devid = DevID; + udpready=false; + eventname = event; + + bax = xx; + if (bax!=null) { + if (!eventname.isEmpty()) { + need_event_recordbuffer = bax.subExists(eventname+event_recordbuffer); + need_event_recordvu = bax.subExists(eventname+event_recordvu); + } + } + } + + public boolean ChannelIsActive() { + if (basshandle==0) return false; + if (BASS.BASS_ChannelIsActive(basshandle)==bassconstant.BASS_ACTIVE_PLAYING) { + return true; + } else { + return false; + } + } + + /** + * Open Stream Channel on DevID + * @param freq Sampling Rate + * @param channels 1=mono, 2=stereo + * @param flags StreamCreate flags + * @return Streamhandle in integer + */ + public int OpenStream(int freq, int channels, int flags) { + if (!BASS.BASS_SetDevice(devid) && debugmode) { + if (BA.debugMode) BA.Log("BassSetDevice dev="+devid+" failed"); + } + if (!BASS.BASS_Start() && debugmode) { + if (BA.debugMode) BA.Log("BassStart dev="+devid+" failed"); + } + + + basshandle=BASS.BASS_StreamCreate(freq, channels, flags, null, null); + if (debugmode) { + if (basshandle!=0) + if (BA.debugMode) BA.Log("StreamCreate handle="+basshandle); + else + { + + if (BA.debugMode) BA.Log("StreamCreate failed, errorcode="+BASS.GetBassErrorString()); + } + } + if (basshandle!=0) { + BASS.BASS_ChannelSetAttribute(basshandle, bassconstant.BASS_ATTRIB_NOBUFFER, 1.0f); + BASS.BASS_ChannelSetAttribute(basshandle, bassconstant.BASS_ATTRIB_VOL, 1.0f); + if (BASS.BASS_ChannelPlay(basshandle, true)) { + if (debugmode) { + if (BA.debugMode) BA.Log("ChannelPlay on handle "+basshandle+" success"); + } + } else { + if (debugmode) { + + if (BA.debugMode) BA.Log("ChannelPlay on handle "+basshandle+" failed, errorcode = "+BASS.GetBassErrorString()); + } + BASS.BASS_StreamFree(basshandle); + if (debugmode) if (BA.debugMode) BA.Log("Automatic closing handle "+basshandle); + basshandle=0; + } + } + return basshandle; + } + + + /** + * Close Stream Channel on DevID + */ + public void CloseStream() { + if (basshandle==0) return; + BASS.BASS_SetDevice(devid); + BASS.BASS_ChannelStop(basshandle); + BASS.BASS_StreamFree(basshandle); + BASS.BASS_Stop(); + if (debugmode) { + if (BA.debugMode) BA.Log("Stream Handle="+basshandle+" Closed"); + } + basshandle=0; + } + + /** + * Open UDP Listening + * @return true if UDP Listener opened + */ + public boolean OpenUDP() { + try { + udp = new DatagramSocket(this.listenport); + + udp.setSoTimeout(10); // receive timeout 10ms + myaddress = udp.getLocalAddress(); + udpready = true; + if (debugmode) { + if (BA.debugMode) BA.Log("Open UDP at "+listenport+" success"); + } + + monitorUDP(); + monitorVU(); + Runtime.getRuntime().addShutdownHook(new Thread() { + public void run() { + CloseUDP(); + CloseStream(); + udpready = false; + } + }); + } catch (SocketException e) { + udpready = false; + if (debugmode) { + if (BA.debugMode) BA.Log("Open UDP Failed, Msg:"+e); + } + } + return udpready; + } + + private void monitorVU() { + vumonitor = new Thread() { + public void run() { + while(udpready) { + try { + sleep(100); + if (basshandle == 0) break; + + final int vu = BASS.BASS_ChannelGetLevel(basshandle); + if (vu >= 0) raise_recordvu(vu); + } catch (InterruptedException e) { + if (debugmode) { + if (BA.debugMode) + BA.Log("MonitorVU InterruptedException, Caused by " + e.getCause()); + } + } + } + } + }; + vumonitor.start(); + } + + /** + * UDP Monitoring and receiving + */ + private void monitorUDP() { + udpmonitor = new Thread() { + public void run() { + if (debugmode) if (BA.debugMode) BA.Log("UDP Monitoring for port "+listenport+" started"); + int written = 0; + + while (udpready) { + try { + sleep(1); + DatagramPacket xx = checkreceive(); + if (xx==null) continue; + // check if datagrampacket is valid + int lengthnya = xx.getLength(); + if (lengthnya==0) continue; + + Pointer datanya = new Memory(lengthnya); + datanya.write(0, xx.getData(), 0, lengthnya); + + if (basshandle==0) continue; // stream handle not ready + written = BASS.BASS_StreamPutData(basshandle, datanya, lengthnya); // push to bass + raise_recordbuffer(xx.getData()); + //final int vu = bass.BASS_ChannelGetLevel(basshandle); + //if (vu>=0) raise_recordvu(vu); + if (debugmode) { + if (written==-1) { + if (BA.debugMode) BA.Log("Failed to StreamPutData, errorcode = "+BASS.GetBassErrorString()); + } else { + if (BA.debugMode) BA.Log("Pushed "+lengthnya+" bytes to Handle="+basshandle); + } + } + } catch (InterruptedException e) { + if (BA.debugMode) BA.Log("MonitorUDP InterruptedException, Caused by "+e.getCause()); + udpready = false; + if (debugmode) { + if (BA.debugMode) BA.Log("MonitorUDP crashed, Msg:"+e); + } + } + + } + if (debugmode) if (BA.debugMode) BA.Log("UDP Monitoring for port "+listenport+" stopped"); + } + }; + udpmonitor.start(); + + } + + private void raise_recordbuffer(byte[] bb) { + if (bb == null) return; + if (bb.length==0) return; + + if (need_event_recordbuffer) { + bax.raiseEventFromDifferentThread(UDPReceiverData.this, null, 0, eventname+event_recordbuffer, false, new Object[] {devid,bb}); + } + } + + + private void raise_recordvu(int value) { + if (value<0) value = 0; + short leftvu = (short)(value & 0xFFFF); + + if (need_event_recordvu) { + bax.raiseEventFromDifferentThread(UDPReceiverData.this, null, 0, eventname+event_recordvu, false, new Object[] {leftvu}); + } + } + + /** + * Set Maximum UDP Buffer size + * @param value default=1000 bytes + */ + public void setMaxBufLength(int value) { + if (value<1) value=1000; + maxbuflen = value; + } + + private DatagramPacket checkreceive() { + byte[] buf = new byte[maxbuflen]; + DatagramPacket result = new DatagramPacket(buf,maxbuflen); + try { + udp.receive(result); + return result; + } catch (IOException e) { + return null; + } + + } + + /** + * Check if UDP socket ready + * @return true if ready + */ + public boolean UDPisReady() { + if (!udpready) return false; + if (udp==null) return false; + return (!udp.isClosed()); + } + + /** + * Check Server IP + * @return IP Address in string + */ + public String ServerIP() { + if (UDPisReady()) { + if (myaddress!=null) { + return myaddress.getHostAddress(); + } else return ""; + } else return ""; + + } + + /** + * Get UDP Listening Port + * @return UDP Port + */ + public int getListenPort() { + return this.listenport; + } + + /** + * Get Bass DeviceID used + * @return BASS DeviceID + */ + public int getDevID() { + return this.devid; + } + + /** + * Get UDP Socket being used + * @return UDP Socket for transmit receive + */ + public DatagramSocket getUDPSocket() { + return udp; + } + + /** + * Get Bass Stream Handle linked + * @return Stream Handle + */ + public int BassHandle() { + return this.basshandle; + } + + /** + * Close UDP connection + */ + public void CloseUDP() { + if (UDPisReady()) udp.close(); + udpready = false; + if (debugmode) { + if (BA.debugMode) BA.Log("UDP Listening at Port "+listenport+" closed"); + } + } + + /** + * Set DebugMode + * @param value if True then will give System.Output.Println + */ + public void SetDebugMode(boolean value) { + debugmode = value; + } +} diff --git a/src/jbass/commoncodes.java b/src/jbass/commoncodes.java new file mode 100644 index 0000000..76ec635 --- /dev/null +++ b/src/jbass/commoncodes.java @@ -0,0 +1,34 @@ +package jbass; + +public class commoncodes { + private final static long KB_threshold = 1024; + private final static long MB_threshold = KB_threshold * 1024; + private final static long GB_threshold = MB_threshold * 1024; + private final static long TB_threshold = GB_threshold * 1024; + + + static public String ByteCounter_to_String(long value) { + if (value < 0) { + return "0 B"; + } + if (value < KB_threshold) { + return value+" B"; + } else if (value < MB_threshold) { + return pembulatan_2_desimal(value / KB_threshold)+" KB"; + } else if (value < GB_threshold) { + return pembulatan_2_desimal(value / MB_threshold)+" MB"; + } else if (value < TB_threshold) { + return pembulatan_2_desimal(value / GB_threshold)+" GB"; + } else { + return pembulatan_2_desimal(value / TB_threshold)+" TB"; + } + } + + static private double pembulatan_2_desimal(double value) { + value = value * 100; + long xx = Math.round(value); + return (xx / 100.0); + + + } +} diff --git a/src/jbass/recordstreamjob.java b/src/jbass/recordstreamjob.java new file mode 100644 index 0000000..6682663 --- /dev/null +++ b/src/jbass/recordstreamjob.java @@ -0,0 +1,113 @@ +package jbass; + + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.net.SocketException; + +import java.util.stream.Stream; + + + +public class recordstreamjob { + private InetSocketAddress[] targetsock; + + private DatagramSocket _udpsocket = null; + + private SocketAddress localbind; + private recordstreamjobEvent _event; + private boolean targetipvalid = false; + private boolean targetportvalid = false; + private boolean socketcreated = false; + + + + + + public recordstreamjob(String[] targetip, int targetport, String localip, recordstreamjobEvent event) { + + this._event = event; + // check if target ip and port are valid + this.targetipvalid = check_targetip(targetip); + this.targetportvalid = targetport>0 && targetport<65536; + // konversi jadi array of InetAddress + if (targetipvalid) { + targetsock = Stream.of(targetip).map(x -> InetSocketAddress.createUnresolved(x, targetport)).toArray(InetSocketAddress[]::new); + } + // buat localbind kalau perlu + if (localip!=null && localip.length()>0) { + localbind = InetSocketAddress.createUnresolved(localip, 0); + } + try { + _udpsocket = new DatagramSocket(localbind); + socketcreated = true; + } catch (SocketException e) { + raise_log("Error creating UDP socket: " + e.getMessage()); + + } + } + + public boolean SendByte(byte[] bb) { + if (_udpsocket!=null) { + if (bb!=null && bb.length>0) { + if (targetipvalid) { + if (targetportvalid) { + Stream.of(targetsock).forEach(x ->{ + DatagramPacket packet = new DatagramPacket(bb,bb.length,x); + try { + _udpsocket.send(packet); + raise_log("Byte sent to target IP: " + x.getHostString() + ", port: " + x.getPort()+" Size: "+bb.length); + } catch (IOException e) { + raise_log("Error sending byte to target IP: " + e.getMessage()); + } + }); + return true; + } else raise_log("SendByte failed, target port is invalid"); + } else raise_log("SendByte failed, target IP is invalid"); + } else raise_log("SendByte failed, byte array is empty"); + } else raise_log("SendByte failed, UDP socket not created"); + return false; + } + + public void Close() { + if (_udpsocket != null) { + _udpsocket.close(); + _udpsocket = null; + } + } + + private boolean check_targetip(String[] tip) { + if (tip!=null) { + if (tip.length>0) { + return Stream.of(tip).allMatch(x -> x!=null && x.length()>0); + } + } + return false; + } + + + + public boolean getSocketCreated() { + return socketcreated; + } + + public boolean getTargetIPValid() { + return targetipvalid; + } + + public boolean getTargetPortValid() { + return targetportvalid; + } + + private void raise_log(String msg) { + if (_event != null) { + _event.log(msg); + } + } + + +} diff --git a/src/jbass/recordstreamjobEvent.java b/src/jbass/recordstreamjobEvent.java new file mode 100644 index 0000000..09c56a8 --- /dev/null +++ b/src/jbass/recordstreamjobEvent.java @@ -0,0 +1,5 @@ +package jbass; + +public interface recordstreamjobEvent { + public void log(String msg); +} diff --git a/src/linux-aarch64/libbass.so b/src/linux-aarch64/libbass.so new file mode 100644 index 0000000..ab2a517 Binary files /dev/null and b/src/linux-aarch64/libbass.so differ diff --git a/src/linux-aarch64/libbass_aac.so b/src/linux-aarch64/libbass_aac.so new file mode 100644 index 0000000..722d9eb Binary files /dev/null and b/src/linux-aarch64/libbass_aac.so differ diff --git a/src/linux-aarch64/libbass_ac3.so b/src/linux-aarch64/libbass_ac3.so new file mode 100644 index 0000000..f515198 Binary files /dev/null and b/src/linux-aarch64/libbass_ac3.so differ diff --git a/src/linux-aarch64/libbass_fx.so b/src/linux-aarch64/libbass_fx.so new file mode 100644 index 0000000..45faa93 Binary files /dev/null and b/src/linux-aarch64/libbass_fx.so differ diff --git a/src/linux-aarch64/libbass_mpc.so b/src/linux-aarch64/libbass_mpc.so new file mode 100644 index 0000000..82a22af Binary files /dev/null and b/src/linux-aarch64/libbass_mpc.so differ diff --git a/src/linux-aarch64/libbass_spx.so b/src/linux-aarch64/libbass_spx.so new file mode 100644 index 0000000..d2af193 Binary files /dev/null and b/src/linux-aarch64/libbass_spx.so differ diff --git a/src/linux-aarch64/libbass_tta.so b/src/linux-aarch64/libbass_tta.so new file mode 100644 index 0000000..3468f2c Binary files /dev/null and b/src/linux-aarch64/libbass_tta.so differ diff --git a/src/linux-aarch64/libbassalac.so b/src/linux-aarch64/libbassalac.so new file mode 100644 index 0000000..b5383ee Binary files /dev/null and b/src/linux-aarch64/libbassalac.so differ diff --git a/src/linux-aarch64/libbassape.so b/src/linux-aarch64/libbassape.so new file mode 100644 index 0000000..951e3c1 Binary files /dev/null and b/src/linux-aarch64/libbassape.so differ diff --git a/src/linux-aarch64/libbasscd.so b/src/linux-aarch64/libbasscd.so new file mode 100644 index 0000000..9a25dff Binary files /dev/null and b/src/linux-aarch64/libbasscd.so differ diff --git a/src/linux-aarch64/libbassdsd.so b/src/linux-aarch64/libbassdsd.so new file mode 100644 index 0000000..aa07439 Binary files /dev/null and b/src/linux-aarch64/libbassdsd.so differ diff --git a/src/linux-aarch64/libbassenc.so b/src/linux-aarch64/libbassenc.so new file mode 100644 index 0000000..82fb7be Binary files /dev/null and b/src/linux-aarch64/libbassenc.so differ diff --git a/src/linux-aarch64/libbassenc_flac.so b/src/linux-aarch64/libbassenc_flac.so new file mode 100644 index 0000000..200dfcf Binary files /dev/null and b/src/linux-aarch64/libbassenc_flac.so differ diff --git a/src/linux-aarch64/libbassenc_mp3.so b/src/linux-aarch64/libbassenc_mp3.so new file mode 100644 index 0000000..ee291af Binary files /dev/null and b/src/linux-aarch64/libbassenc_mp3.so differ diff --git a/src/linux-aarch64/libbassenc_ogg.so b/src/linux-aarch64/libbassenc_ogg.so new file mode 100644 index 0000000..8864b02 Binary files /dev/null and b/src/linux-aarch64/libbassenc_ogg.so differ diff --git a/src/linux-aarch64/libbassenc_opus.so b/src/linux-aarch64/libbassenc_opus.so new file mode 100644 index 0000000..d54296a Binary files /dev/null and b/src/linux-aarch64/libbassenc_opus.so differ diff --git a/src/linux-aarch64/libbassflac.so b/src/linux-aarch64/libbassflac.so new file mode 100644 index 0000000..b891ff2 Binary files /dev/null and b/src/linux-aarch64/libbassflac.so differ diff --git a/src/linux-aarch64/libbasshls.so b/src/linux-aarch64/libbasshls.so new file mode 100644 index 0000000..e9373b7 Binary files /dev/null and b/src/linux-aarch64/libbasshls.so differ diff --git a/src/linux-aarch64/libbassmidi.so b/src/linux-aarch64/libbassmidi.so new file mode 100644 index 0000000..062b5d7 Binary files /dev/null and b/src/linux-aarch64/libbassmidi.so differ diff --git a/src/linux-aarch64/libbassmix.so b/src/linux-aarch64/libbassmix.so new file mode 100644 index 0000000..af5a600 Binary files /dev/null and b/src/linux-aarch64/libbassmix.so differ diff --git a/src/linux-aarch64/libbassopus.so b/src/linux-aarch64/libbassopus.so new file mode 100644 index 0000000..437eb69 Binary files /dev/null and b/src/linux-aarch64/libbassopus.so differ diff --git a/src/linux-aarch64/libbasswebm.so b/src/linux-aarch64/libbasswebm.so new file mode 100644 index 0000000..8d61038 Binary files /dev/null and b/src/linux-aarch64/libbasswebm.so differ diff --git a/src/linux-aarch64/libbasswv.so b/src/linux-aarch64/libbasswv.so new file mode 100644 index 0000000..c834ff3 Binary files /dev/null and b/src/linux-aarch64/libbasswv.so differ diff --git a/src/linux-aarch64/libtags.so b/src/linux-aarch64/libtags.so new file mode 100644 index 0000000..1d6ee16 Binary files /dev/null and b/src/linux-aarch64/libtags.so differ diff --git a/src/linux-arm/libbass.so b/src/linux-arm/libbass.so new file mode 100644 index 0000000..ec90dc0 Binary files /dev/null and b/src/linux-arm/libbass.so differ diff --git a/src/linux-arm/libbass_aac.so b/src/linux-arm/libbass_aac.so new file mode 100644 index 0000000..69215c2 Binary files /dev/null and b/src/linux-arm/libbass_aac.so differ diff --git a/src/linux-arm/libbass_ac3.so b/src/linux-arm/libbass_ac3.so new file mode 100644 index 0000000..3fa6d96 Binary files /dev/null and b/src/linux-arm/libbass_ac3.so differ diff --git a/src/linux-arm/libbass_ape.so b/src/linux-arm/libbass_ape.so new file mode 100644 index 0000000..8356645 Binary files /dev/null and b/src/linux-arm/libbass_ape.so differ diff --git a/src/linux-arm/libbass_fx.so b/src/linux-arm/libbass_fx.so new file mode 100644 index 0000000..0a78d8f Binary files /dev/null and b/src/linux-arm/libbass_fx.so differ diff --git a/src/linux-arm/libbass_mpc.so b/src/linux-arm/libbass_mpc.so new file mode 100644 index 0000000..c2b3a9e Binary files /dev/null and b/src/linux-arm/libbass_mpc.so differ diff --git a/src/linux-arm/libbass_spx.so b/src/linux-arm/libbass_spx.so new file mode 100644 index 0000000..d45bde9 Binary files /dev/null and b/src/linux-arm/libbass_spx.so differ diff --git a/src/linux-arm/libbass_tta.so b/src/linux-arm/libbass_tta.so new file mode 100644 index 0000000..c482e2f Binary files /dev/null and b/src/linux-arm/libbass_tta.so differ diff --git a/src/linux-arm/libbassalac.so b/src/linux-arm/libbassalac.so new file mode 100644 index 0000000..4b89a52 Binary files /dev/null and b/src/linux-arm/libbassalac.so differ diff --git a/src/linux-arm/libbassape.so b/src/linux-arm/libbassape.so new file mode 100644 index 0000000..f355e36 Binary files /dev/null and b/src/linux-arm/libbassape.so differ diff --git a/src/linux-arm/libbasscd.so b/src/linux-arm/libbasscd.so new file mode 100644 index 0000000..255076a Binary files /dev/null and b/src/linux-arm/libbasscd.so differ diff --git a/src/linux-arm/libbassdsd.so b/src/linux-arm/libbassdsd.so new file mode 100644 index 0000000..ef1e21f Binary files /dev/null and b/src/linux-arm/libbassdsd.so differ diff --git a/src/linux-arm/libbassenc.so b/src/linux-arm/libbassenc.so new file mode 100644 index 0000000..20016ee Binary files /dev/null and b/src/linux-arm/libbassenc.so differ diff --git a/src/linux-arm/libbassenc_flac.so b/src/linux-arm/libbassenc_flac.so new file mode 100644 index 0000000..37d4d06 Binary files /dev/null and b/src/linux-arm/libbassenc_flac.so differ diff --git a/src/linux-arm/libbassenc_mp3.so b/src/linux-arm/libbassenc_mp3.so new file mode 100644 index 0000000..9d7e7f8 Binary files /dev/null and b/src/linux-arm/libbassenc_mp3.so differ diff --git a/src/linux-arm/libbassenc_ogg.so b/src/linux-arm/libbassenc_ogg.so new file mode 100644 index 0000000..97b4205 Binary files /dev/null and b/src/linux-arm/libbassenc_ogg.so differ diff --git a/src/linux-arm/libbassenc_opus.so b/src/linux-arm/libbassenc_opus.so new file mode 100644 index 0000000..bd33c54 Binary files /dev/null and b/src/linux-arm/libbassenc_opus.so differ diff --git a/src/linux-arm/libbassflac.so b/src/linux-arm/libbassflac.so new file mode 100644 index 0000000..1cd479f Binary files /dev/null and b/src/linux-arm/libbassflac.so differ diff --git a/src/linux-arm/libbasshls.so b/src/linux-arm/libbasshls.so new file mode 100644 index 0000000..6a17525 Binary files /dev/null and b/src/linux-arm/libbasshls.so differ diff --git a/src/linux-arm/libbassmidi.so b/src/linux-arm/libbassmidi.so new file mode 100644 index 0000000..42f3566 Binary files /dev/null and b/src/linux-arm/libbassmidi.so differ diff --git a/src/linux-arm/libbassmix.so b/src/linux-arm/libbassmix.so new file mode 100644 index 0000000..23d9c67 Binary files /dev/null and b/src/linux-arm/libbassmix.so differ diff --git a/src/linux-arm/libbassopus.so b/src/linux-arm/libbassopus.so new file mode 100644 index 0000000..a27c54d Binary files /dev/null and b/src/linux-arm/libbassopus.so differ diff --git a/src/linux-arm/libbasswebm.so b/src/linux-arm/libbasswebm.so new file mode 100644 index 0000000..1267dcd Binary files /dev/null and b/src/linux-arm/libbasswebm.so differ diff --git a/src/linux-arm/libbasswv.so b/src/linux-arm/libbasswv.so new file mode 100644 index 0000000..d0d8cdc Binary files /dev/null and b/src/linux-arm/libbasswv.so differ diff --git a/src/linux-arm/libtags.so b/src/linux-arm/libtags.so new file mode 100644 index 0000000..f2721f8 Binary files /dev/null and b/src/linux-arm/libtags.so differ diff --git a/src/linux-x86-64/libbass.so b/src/linux-x86-64/libbass.so new file mode 100644 index 0000000..3317ed4 Binary files /dev/null and b/src/linux-x86-64/libbass.so differ diff --git a/src/linux-x86-64/libbass_aac.so b/src/linux-x86-64/libbass_aac.so new file mode 100644 index 0000000..eaa273a Binary files /dev/null and b/src/linux-x86-64/libbass_aac.so differ diff --git a/src/linux-x86-64/libbass_ac3.so b/src/linux-x86-64/libbass_ac3.so new file mode 100644 index 0000000..c8b384e Binary files /dev/null and b/src/linux-x86-64/libbass_ac3.so differ diff --git a/src/linux-x86-64/libbass_ape.so b/src/linux-x86-64/libbass_ape.so new file mode 100644 index 0000000..920360d Binary files /dev/null and b/src/linux-x86-64/libbass_ape.so differ diff --git a/src/linux-x86-64/libbass_fx.so b/src/linux-x86-64/libbass_fx.so new file mode 100644 index 0000000..bb23051 Binary files /dev/null and b/src/linux-x86-64/libbass_fx.so differ diff --git a/src/linux-x86-64/libbass_mpc.so b/src/linux-x86-64/libbass_mpc.so new file mode 100644 index 0000000..a7418d5 Binary files /dev/null and b/src/linux-x86-64/libbass_mpc.so differ diff --git a/src/linux-x86-64/libbass_spx.so b/src/linux-x86-64/libbass_spx.so new file mode 100644 index 0000000..b309b35 Binary files /dev/null and b/src/linux-x86-64/libbass_spx.so differ diff --git a/src/linux-x86-64/libbass_tta.so b/src/linux-x86-64/libbass_tta.so new file mode 100644 index 0000000..465461c Binary files /dev/null and b/src/linux-x86-64/libbass_tta.so differ diff --git a/src/linux-x86-64/libbassalac.so b/src/linux-x86-64/libbassalac.so new file mode 100644 index 0000000..e876e5a Binary files /dev/null and b/src/linux-x86-64/libbassalac.so differ diff --git a/src/linux-x86-64/libbassape.so b/src/linux-x86-64/libbassape.so new file mode 100644 index 0000000..8f5a54e Binary files /dev/null and b/src/linux-x86-64/libbassape.so differ diff --git a/src/linux-x86-64/libbasscd.so b/src/linux-x86-64/libbasscd.so new file mode 100644 index 0000000..99cd7d9 Binary files /dev/null and b/src/linux-x86-64/libbasscd.so differ diff --git a/src/linux-x86-64/libbassdsd.so b/src/linux-x86-64/libbassdsd.so new file mode 100644 index 0000000..665391a Binary files /dev/null and b/src/linux-x86-64/libbassdsd.so differ diff --git a/src/linux-x86-64/libbassenc.so b/src/linux-x86-64/libbassenc.so new file mode 100644 index 0000000..bb7240b Binary files /dev/null and b/src/linux-x86-64/libbassenc.so differ diff --git a/src/linux-x86-64/libbassenc_flac.so b/src/linux-x86-64/libbassenc_flac.so new file mode 100644 index 0000000..1d3a380 Binary files /dev/null and b/src/linux-x86-64/libbassenc_flac.so differ diff --git a/src/linux-x86-64/libbassenc_mp3.so b/src/linux-x86-64/libbassenc_mp3.so new file mode 100644 index 0000000..fc1c14d Binary files /dev/null and b/src/linux-x86-64/libbassenc_mp3.so differ diff --git a/src/linux-x86-64/libbassenc_ogg.so b/src/linux-x86-64/libbassenc_ogg.so new file mode 100644 index 0000000..87c6d50 Binary files /dev/null and b/src/linux-x86-64/libbassenc_ogg.so differ diff --git a/src/linux-x86-64/libbassenc_opus.so b/src/linux-x86-64/libbassenc_opus.so new file mode 100644 index 0000000..86bc44c Binary files /dev/null and b/src/linux-x86-64/libbassenc_opus.so differ diff --git a/src/linux-x86-64/libbassflac.so b/src/linux-x86-64/libbassflac.so new file mode 100644 index 0000000..ed8be23 Binary files /dev/null and b/src/linux-x86-64/libbassflac.so differ diff --git a/src/linux-x86-64/libbasshls.so b/src/linux-x86-64/libbasshls.so new file mode 100644 index 0000000..f1c091d Binary files /dev/null and b/src/linux-x86-64/libbasshls.so differ diff --git a/src/linux-x86-64/libbassmidi.so b/src/linux-x86-64/libbassmidi.so new file mode 100644 index 0000000..fc78b20 Binary files /dev/null and b/src/linux-x86-64/libbassmidi.so differ diff --git a/src/linux-x86-64/libbassmix.so b/src/linux-x86-64/libbassmix.so new file mode 100644 index 0000000..b90214f Binary files /dev/null and b/src/linux-x86-64/libbassmix.so differ diff --git a/src/linux-x86-64/libbassopus.so b/src/linux-x86-64/libbassopus.so new file mode 100644 index 0000000..b97ac64 Binary files /dev/null and b/src/linux-x86-64/libbassopus.so differ diff --git a/src/linux-x86-64/libbasswebm.so b/src/linux-x86-64/libbasswebm.so new file mode 100644 index 0000000..43ca6a7 Binary files /dev/null and b/src/linux-x86-64/libbasswebm.so differ diff --git a/src/linux-x86-64/libbasswv.so b/src/linux-x86-64/libbasswv.so new file mode 100644 index 0000000..0a9e556 Binary files /dev/null and b/src/linux-x86-64/libbasswv.so differ diff --git a/src/linux-x86-64/libtags.so b/src/linux-x86-64/libtags.so new file mode 100644 index 0000000..92fc27c Binary files /dev/null and b/src/linux-x86-64/libtags.so differ diff --git a/src/linux-x86/libbass.so b/src/linux-x86/libbass.so new file mode 100644 index 0000000..74a204d Binary files /dev/null and b/src/linux-x86/libbass.so differ diff --git a/src/linux-x86/libbass_aac.so b/src/linux-x86/libbass_aac.so new file mode 100644 index 0000000..d286e70 Binary files /dev/null and b/src/linux-x86/libbass_aac.so differ diff --git a/src/linux-x86/libbass_ac3.so b/src/linux-x86/libbass_ac3.so new file mode 100644 index 0000000..0cd6d23 Binary files /dev/null and b/src/linux-x86/libbass_ac3.so differ diff --git a/src/linux-x86/libbass_ape.so b/src/linux-x86/libbass_ape.so new file mode 100644 index 0000000..4c7a61e Binary files /dev/null and b/src/linux-x86/libbass_ape.so differ diff --git a/src/linux-x86/libbass_fx.so b/src/linux-x86/libbass_fx.so new file mode 100644 index 0000000..e325328 Binary files /dev/null and b/src/linux-x86/libbass_fx.so differ diff --git a/src/linux-x86/libbass_mpc.so b/src/linux-x86/libbass_mpc.so new file mode 100644 index 0000000..0060463 Binary files /dev/null and b/src/linux-x86/libbass_mpc.so differ diff --git a/src/linux-x86/libbass_spx.so b/src/linux-x86/libbass_spx.so new file mode 100644 index 0000000..8b9740d Binary files /dev/null and b/src/linux-x86/libbass_spx.so differ diff --git a/src/linux-x86/libbass_tta.so b/src/linux-x86/libbass_tta.so new file mode 100644 index 0000000..e80bdcc Binary files /dev/null and b/src/linux-x86/libbass_tta.so differ diff --git a/src/linux-x86/libbassalac.so b/src/linux-x86/libbassalac.so new file mode 100644 index 0000000..fc2b2cb Binary files /dev/null and b/src/linux-x86/libbassalac.so differ diff --git a/src/linux-x86/libbassape.so b/src/linux-x86/libbassape.so new file mode 100644 index 0000000..3802ad2 Binary files /dev/null and b/src/linux-x86/libbassape.so differ diff --git a/src/linux-x86/libbasscd.so b/src/linux-x86/libbasscd.so new file mode 100644 index 0000000..8a415bf Binary files /dev/null and b/src/linux-x86/libbasscd.so differ diff --git a/src/linux-x86/libbassdsd.so b/src/linux-x86/libbassdsd.so new file mode 100644 index 0000000..faa2fab Binary files /dev/null and b/src/linux-x86/libbassdsd.so differ diff --git a/src/linux-x86/libbassenc.so b/src/linux-x86/libbassenc.so new file mode 100644 index 0000000..874faee Binary files /dev/null and b/src/linux-x86/libbassenc.so differ diff --git a/src/linux-x86/libbassenc_flac.so b/src/linux-x86/libbassenc_flac.so new file mode 100644 index 0000000..83dcfaf Binary files /dev/null and b/src/linux-x86/libbassenc_flac.so differ diff --git a/src/linux-x86/libbassenc_mp3.so b/src/linux-x86/libbassenc_mp3.so new file mode 100644 index 0000000..b780292 Binary files /dev/null and b/src/linux-x86/libbassenc_mp3.so differ diff --git a/src/linux-x86/libbassenc_ogg.so b/src/linux-x86/libbassenc_ogg.so new file mode 100644 index 0000000..7e8a082 Binary files /dev/null and b/src/linux-x86/libbassenc_ogg.so differ diff --git a/src/linux-x86/libbassenc_opus.so b/src/linux-x86/libbassenc_opus.so new file mode 100644 index 0000000..2f47bfa Binary files /dev/null and b/src/linux-x86/libbassenc_opus.so differ diff --git a/src/linux-x86/libbassflac.so b/src/linux-x86/libbassflac.so new file mode 100644 index 0000000..1ab4e07 Binary files /dev/null and b/src/linux-x86/libbassflac.so differ diff --git a/src/linux-x86/libbasshls.so b/src/linux-x86/libbasshls.so new file mode 100644 index 0000000..b133522 Binary files /dev/null and b/src/linux-x86/libbasshls.so differ diff --git a/src/linux-x86/libbassmidi.so b/src/linux-x86/libbassmidi.so new file mode 100644 index 0000000..5d96ccd Binary files /dev/null and b/src/linux-x86/libbassmidi.so differ diff --git a/src/linux-x86/libbassmix.so b/src/linux-x86/libbassmix.so new file mode 100644 index 0000000..81c8ecb Binary files /dev/null and b/src/linux-x86/libbassmix.so differ diff --git a/src/linux-x86/libbassopus.so b/src/linux-x86/libbassopus.so new file mode 100644 index 0000000..5322e3a Binary files /dev/null and b/src/linux-x86/libbassopus.so differ diff --git a/src/linux-x86/libbasswebm.so b/src/linux-x86/libbasswebm.so new file mode 100644 index 0000000..d87e057 Binary files /dev/null and b/src/linux-x86/libbasswebm.so differ diff --git a/src/linux-x86/libbasswv.so b/src/linux-x86/libbasswv.so new file mode 100644 index 0000000..6f2ac28 Binary files /dev/null and b/src/linux-x86/libbasswv.so differ diff --git a/src/linux-x86/libtags.so b/src/linux-x86/libtags.so new file mode 100644 index 0000000..edf234b Binary files /dev/null and b/src/linux-x86/libtags.so differ diff --git a/src/nr164/NR164Clients.java b/src/nr164/NR164Clients.java new file mode 100644 index 0000000..9d7e652 --- /dev/null +++ b/src/nr164/NR164Clients.java @@ -0,0 +1,582 @@ +package nr164; + +import java.io.File; +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.SocketException; +import java.nio.ByteBuffer; + +import com.sun.jna.Memory; +import com.sun.jna.Pointer; +import com.un4seen.bass.BASS; +import com.un4seen.bass.BASSenc; +import com.un4seen.bass.BASSenc.ENCODENOTIFYPROC; +import com.un4seen.bass.BASSenc.ENCODEPROC; + +import com.un4seen.bass.BASS.STREAMPROC; +import com.un4seen.bass.BASS.bassconstant; + +import anywheresoftware.b4a.BA; +import anywheresoftware.b4a.keywords.DateTime; + +@BA.ShortName("NR164Clients") +public class NR164Clients { + private int receivedbytes = 0; + private int writtenbytes = 0; + private NR164ClientsEvent eventobject; + private String mapid=""; + private final String _sourceip; + private final int _listenport; + private final int sizefactor = 32 * 1024; // 32KB + private ByteBuffer buf = ByteBuffer.allocate(4*sizefactor); // 128KB + private ByteBuffer encodedbuf = ByteBuffer.allocate(4*sizefactor); // 128KB + private BASS bass; + private BASSenc bassenc; + private boolean isstreaming = false; + private long starttick = -1; // for duration calculation since the first bytes arrived + private boolean _streamingmonitor = false; // for streamingmonitor thread, to exit + + private boolean usethedata = true; // for difference beetween closing (use the data) and discard ( not use the data) + // by default, use the data + private final boolean _ispriority; // for priority paging detection + + private final Object Me; + + private byte _internalrelay = 0; + private byte[] _externalrelay = new byte[8]; + private String _replyipaddress = ""; + private int _replycommandport = 0; + private DatagramSocket udpsock = null; + private String _recordingfilename=""; + + + + /** + * Get Recording Filename created by NR164Clients + */ + public String getRecordingFileName() { + return _recordingfilename; + } + + /** + * Can be use to save IP address for replying command + */ + public String getReplyIPAddress() { + return _replyipaddress; + } + + /* + * Can be use to save IP address for replying command + */ + public void setReplyIPAddress(String value) { + _replyipaddress = value; + } + + /* + * Can be use to save UDP Port for replying command + */ + public int getReplyCommandPort() { + return _replycommandport; + } + + /* + * Can be use to save UDP Port for replying command + */ + public void setReplyCommandPort(int value) { + _replycommandport = value; + } + + /* + * Can be use to save Internal Relay request + */ + public byte getInternalRelay() { + return _internalrelay; + } + + /* + * Can be use to save Internal Relay request + */ + public void setInternalRelay(byte value) { + _internalrelay = value; + } + + /* + * Can be use to save External Relays request + */ + public byte[] getExternalRelay() { + return _externalrelay; + } + + /* + * Can be use to save External Relays request + */ + public void setExternalRelay(byte[] value) { + if (value != null) { + _externalrelay[0] = value.length>0 ? value[0] : 0; + _externalrelay[1] = value.length>1 ? value[1] : 0; + _externalrelay[2] = value.length>2 ? value[2] : 0; + _externalrelay[3] = value.length>3 ? value[3] : 0; + _externalrelay[4] = value.length>4 ? value[4] : 0; + _externalrelay[5] = value.length>5 ? value[5] : 0; + _externalrelay[6] = value.length>6 ? value[6] : 0; + _externalrelay[7] = value.length>7 ? value[7] : 0; + + } + } + + /* + * Check if this is a priority paging request + */ + public boolean getIsPriority() { + return _ispriority; + } + +// /* +// * Check if this is a priority paging request +// */ +// public void setIsPriority(boolean value) { +// _ispriority = value; +// } + + /** + * Get assigned ID for this NR164Clients + * ID is in form of ip:port + * @return ID in string + */ + public String getID() { + return mapid; + } + + /** + * Check if NR164Clients still streaming + * @return true if yes + */ + public boolean getStillStreaming() { + return isstreaming; + } + + /** + * Check how many bytes has been received by NR164Clients + * @return value in integer + */ + public int getReceivedBytesFromNetwork() { + return receivedbytes; + } + + /** + * Check how many miliseconds since it has received the first bytes from network + * @return value in tick long + */ + public long getRunningDuration() { + return (starttick >0 ) ? -1 : (DateTime.getNow()-starttick); + } + + /** + * Check how many bytes has been written to disk + * @return value in integer + */ + public int getSavedBytesToDisk() { + return writtenbytes; + } + + @BA.Hide + /** + * Create NR164Client object + * @param ip : source IP who sent streaming + * @param port : Listening Port to receive data + * @param obj : caller object + * @param samplingrate : sampling rate + * @param channel : 1 = mono, 2 = stereo + * @param _ispriorityclient : true = live paging, false = queued paging + */ + public NR164Clients(final String ip, final int port, NR164ClientsEvent obj, int samplingrate, int channel, boolean _ispriorityclient) { + eventobject = obj; + _sourceip = ip; + _listenport = port; + _ispriority = _ispriorityclient; + Me = this; + if (_sourceip instanceof String) { + if (!_sourceip.isEmpty()) { + if (_listenport>0) { + if (_listenport<65535) { + mapid = _sourceip+":"+_listenport; + bass = new BASS(); + if (bass.BASS_GetVersion()!=0) { + bassenc = new BASSenc(); + if (bassenc.BASS_Encode_GetVersion()!=0) { + try { + DatagramSocket _sock = new DatagramSocket(_listenport); + udpsock = _sock; + Runtime.getRuntime().addShutdownHook(new ShutdownThread()); + + //raise_log("NR164Clients about to start StreamJob at samplingrate="+samplingrate+", channel="+channel); + Thread tx = new Thread(new StreamJob(samplingrate, channel)); + tx.start(); + + Thread zx = new Thread(new StreamingMonitor()); + zx.start(); + + } catch (SocketException e) { + raise_log("Unable to Listen at Port "+_listenport); + } + } else raise_log("BASSENC version=0"); + } else raise_log("BASS version=0"); + + + } else raise_log("Invalid Source Port="+_listenport); + } else raise_log("Invalid Source Port="+_listenport); + } else raise_log("Empty SOurce IP"); + } else raise_log("Source IP is null"); + + } + + private class ShutdownThread extends Thread { + + public void run() { + if (udpsock instanceof DatagramSocket) { + System.out.println("Shutdown UDP at Port "+udpsock.getLocalPort()); + udpsock.close(); + } + udpsock = null; + } + }; + + /** + * Closing NR164Clients Streaming + */ + public void Close() { + isstreaming = false; + _streamingmonitor = false; + usethedata = true; + if (udpsock instanceof DatagramSocket) { + + udpsock.close(); + } + udpsock = null; + } + + @BA.Hide + /** + * Get Encoded Buffer + * @return ByteBuffer + */ + public ByteBuffer GetEncodedBuffer() { + return encodedbuf; + } + + + /** + * Close NR164clients streaming and discard the data + */ + public void Discard() { + isstreaming = false; + _streamingmonitor = false; + usethedata = false; + if (udpsock instanceof DatagramSocket) { + raise_log("Closing UDP at Port "+udpsock.getLocalPort()+" because of Discard Request"); + udpsock.close(); + } + udpsock = null; + + } + + private class StreamingMonitor implements Runnable{ + + private final int waittime = 10 * 1000; // 10 second + + @Override + public void run() { + _streamingmonitor = true; + raise_udpstarted(); + while(_streamingmonitor) { + if (udpsock instanceof DatagramSocket) { + byte[] bb = new byte[1500]; + DatagramPacket pkg = new DatagramPacket(bb, bb.length); + try { + udpsock.setSoTimeout(waittime); // max wait time + udpsock.receive(pkg); // tahan di sini + + // sampai sini sudah bisa + PushBytes(pkg.getData(), pkg.getLength()); + raise_udpbytes(pkg.getData(), pkg.getLength()); + } catch (IOException e) { + raise_udperror(e.getMessage()); + break; + } + } else break; + } + _streamingmonitor = false; + raise_udpstopped(); + } + + + void PushBytes(byte[] bb, int length) { + if (length<1) return; + if (bb == null) return; + if (!(buf instanceof ByteBuffer)) return; + synchronized(buf) { + if (buf.remaining()0) { + // ada space buat nulis + synchronized(buf) { + if (buf.position()>0) { + // dah pernah ditulisin + buf.flip(); // pindah ke baca + int availablebytes = buf.remaining(); // cek ada berapa + if (availablebytes>length) availablebytes = length; // lebih limit gak + byte[] bb = new byte[availablebytes]; + buf.get(bb); // baca buffer ke bb + buf.compact(); // balik ke tulis + if (buffer instanceof Pointer) { + buffer.write(0, bb, 0, availablebytes); + result = availablebytes; + } else { + + raise_log("STREAMPROC write fail, buffer is not Pointer"); + } + + } + } + } + return result; + } + + }; + + // variable encnotifyproc untuk Encode_SetNotify + ENCODENOTIFYPROC encnotifyproc = new ENCODENOTIFYPROC() { + + @Override + public void ENCODENOTIFYPROC(int handle, int status, Pointer user) { + if ((status & bassenc.BASS_ENCODE_NOTIFY_FREE)>0) { + raise_finishrecording(_recordingfilename); // encoder selesai, file recording selesai, file siap dipakai + } + + } + + }; + + ENCODEPROC encproc = new ENCODEPROC() { + + @Override + public void ENCODEPROC(int handle, int channel, Pointer buffer, int length, Pointer user) { + writtenbytes+=length; // cuma buat ambil berapa bytes sudah ketulis + raise_statistic(); + + if (encodedbuf instanceof ByteBuffer) { + // ambil byte nya + byte[] bb = new byte[length]; + buffer.read(0, bb, 0, length); + + if (encodedbuf.remaining()< length) { + // digedein + int newsize = encodedbuf.capacity() + Math.max(sizefactor, length); + + ByteBuffer newbuf = ByteBuffer.allocate(newsize); + newbuf.put(encodedbuf); + encodedbuf = newbuf; + + } + + encodedbuf.put(bb); + + } + + } + + }; + + public StreamJob(int _samplingrate, int _channel) { + samplingrate = _samplingrate; + channel = _channel; + + } + + + + + + + + @Override + public void run() { + + //raise_log("Bass_Init device 0 samplingrate="+samplingrate); + bass.BASS_Init(0, samplingrate,0); + + handle = bass.BASS_StreamCreate(samplingrate, channel, bassconstant.BASS_STREAM_DECODE, proc, null); + if (handle!=0) { + //raise_log("Bass_StreamCreate success with samplingrate="+samplingrate+", channel="+channel); + raise_streamcreated(handle); + isstreaming = true; + _recordingfilename = make_filename(); + int enchandle = bassenc.BASS_Encode_Start(handle, _recordingfilename, BASSenc.BASS_ENCODE_PCM , encproc, null); + if (enchandle!=0) { + if (!bassenc.BASS_Encode_SetNotify(enchandle, encnotifyproc, null)) { + raise_log("StreamJob Encode_SetNotify fail, error="+bass.GetBassErrorString()); + } + + raise_startrecording(_recordingfilename); + int readc = 0; // untuk variable tampungan ChannelGetData + while(isstreaming) { + // di sini channelgetdata cuma buat umpan ke BassEnc enchandle + try { + Thread.sleep(20); + } catch (InterruptedException e) { + raise_log("InterruptedException StreamJob, Msg : "+e.getMessage()); + break; + } + + Pointer pf = new Memory(8000); + readc = bass.BASS_ChannelGetData(handle, pf, 8000); + if (readc==-1) { + // ada error + raise_log("StreamJob ChannelGetData fail, error="+bass.GetBassErrorString()); + break; // selesai , karena ada error + } + } + if (!bassenc.BASS_Encode_StopEx(enchandle, true)) raise_log("StreamJob Encode_StopEx fail, error="+bass.GetBassErrorString()); + + } else raise_log("StreamJob Encode_Start fail, error="+bass.GetBassErrorString()); + + if (!bass.BASS_StreamFree(handle)) raise_log("StreamJob StreamFree fail, error="+bass.GetBassErrorString()); + } else raise_log("StreamJob StreamCreate fail, error="+bass.GetBassErrorString()); + isstreaming = false; + if (usethedata) { + raise_streamclosed(); + } else { + if (_recordingfilename instanceof String) { + if (!_recordingfilename.isEmpty()) { + File ff = new File(_recordingfilename); + if (ff.exists()) { + ff.delete(); + raise_log("File "+_recordingfilename+" is discarded"); + } + } + } + + + } + } + + public String make_filename() { + StringBuilder str = new StringBuilder(); + str.append(NR164Master.bufferingpath); + str.append(File.separator); + str.append(mapid); + str.append("_"); + str.append(String.format("%02d", DateTime.GetDayOfMonth(starttick))); + str.append(String.format("%02d", DateTime.GetMonth(starttick))); + str.append(DateTime.GetYear(starttick)); + str.append("_"); + str.append(String.format("%02d", DateTime.GetHour(starttick))); + str.append(String.format("%02d", DateTime.GetMinute(starttick))); + str.append(String.format("%02d", DateTime.GetSecond(starttick))); + str.append(".wav"); + + return str.toString(); + } + + }; + + + private void raise_log(String msg) { + if (eventobject instanceof NR164ClientsEvent) { + eventobject.log((NR164Clients)Me, msg); + } + } + + private void raise_streamcreated(int handle) { + if (eventobject instanceof NR164ClientsEvent) { + eventobject.streamcreated((NR164Clients)Me, handle); + } + } + + private void raise_streamclosed() { + if (eventobject instanceof NR164ClientsEvent) { + eventobject.streamclosed((NR164Clients)Me); + } + } + + private void raise_startrecording(String namafile) { + if (eventobject instanceof NR164ClientsEvent) { + eventobject.startrecording((NR164Clients)Me, namafile); + } + } + + private void raise_finishrecording(String namafile) { + if (eventobject instanceof NR164ClientsEvent) { + eventobject.finishrecording((NR164Clients)Me, namafile); + } + } + + private void raise_udpbytes(byte[] bb, int length) { + if (eventobject instanceof NR164ClientsEvent) { + eventobject.udpbytes((NR164Clients)Me, bb, length); + } + } + + private void raise_udpstarted() { + if (eventobject instanceof NR164ClientsEvent) { + eventobject.udpstarted((NR164Clients)Me); + } + } + + private void raise_udpstopped() { + if (eventobject instanceof NR164ClientsEvent) { + eventobject.udpstopped((NR164Clients)Me); + } + } + + private void raise_udperror(String msg) { + if (eventobject instanceof NR164ClientsEvent) { + eventobject.udperror((NR164Clients)Me, msg); + } + } + + private void raise_statistic() { + if (eventobject instanceof NR164ClientsEvent) { + eventobject.statistic((NR164Clients)Me, receivedbytes, writtenbytes); + } + } +} diff --git a/src/nr164/NR164ClientsEvent.java b/src/nr164/NR164ClientsEvent.java new file mode 100644 index 0000000..01a1d42 --- /dev/null +++ b/src/nr164/NR164ClientsEvent.java @@ -0,0 +1,14 @@ +package nr164; + +public interface NR164ClientsEvent { + void log(NR164Clients client, String msg); + void streamcreated(NR164Clients client, int handle); + void streamclosed(NR164Clients client); + void startrecording(NR164Clients client, String namafile); + void finishrecording(NR164Clients client, String namafile); + void udpstarted(NR164Clients client); + void udpstopped(NR164Clients client); + void udperror(NR164Clients client, String msg); + void udpbytes(NR164Clients client, byte[] bb, int length); + void statistic(NR164Clients client, int receivedcount, int writtencount); +} diff --git a/src/nr164/NR164Data.java b/src/nr164/NR164Data.java new file mode 100644 index 0000000..ed1858b --- /dev/null +++ b/src/nr164/NR164Data.java @@ -0,0 +1,57 @@ +package nr164; + +import java.net.DatagramPacket; + +import anywheresoftware.b4a.BA; +import anywheresoftware.b4a.keywords.Bit; + +@BA.ShortName("NR164Data") +public class NR164Data { + public final int port; + public final int length; + public final String ipaddress; + public final boolean isvalid; + public final byte[] bytenya; + public NR164Data() { + isvalid = false; + port = 0; + ipaddress = ""; + length = 0; + bytenya = null; + } + public NR164Data(DatagramPacket pkg) { + if (pkg instanceof DatagramPacket) { + port = pkg.getPort(); + ipaddress = pkg.getAddress().getHostAddress(); + length = pkg.getLength(); + isvalid = true; + bytenya = new byte[length]; + + byte[] xx = pkg.getData(); + for(int ii=0;ii0) { + StringBuilder str = new StringBuilder(); + str.append("Data from "+ipaddress+":"+port+" = "); + for(int ii=0;ii0) str.append(" "); + str.append(Bit.ToHexString(Bit.And(bytenya[ii], 0xFF))); + } + System.out.println(str.toString()); + } + } + + } + + +} diff --git a/src/nr164/NR164Master.java b/src/nr164/NR164Master.java new file mode 100644 index 0000000..04e6719 --- /dev/null +++ b/src/nr164/NR164Master.java @@ -0,0 +1,730 @@ +package nr164; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.SocketException; +import java.util.LinkedList; +import java.util.Queue; + +import anywheresoftware.b4a.BA; +import anywheresoftware.b4a.objects.collections.List; +import anywheresoftware.b4a.objects.collections.Map; +import anywheresoftware.b4a.objects.streams.File; +import anywheresoftware.b4a.objects.streams.File.OutputStreamWrapper; +import jbass.Bass_DeviceInfo; + +import com.sun.jna.*; +import com.un4seen.bass.BASS; +import com.un4seen.bass.BASS.BASS_DEVICEINFO; +@BA.ShortName("NR164Master") +@BA.Events(values= { + "log(msg as string)", + "udpstatus(mapid as string, statusbyte as byte)", + "udpbytes(mapid as string, bb() as byte, length as int)", + "statistic(mapid as string, bytesreceived as int, byteswritten as int)", + "startplayback(client as NR164Clients, pathnya as string)", + "finishplayback(client as NR164Clients , pathnya as string, interrupted as boolean)", + "newudpcommand(value as NR164Data)", + +}) +public class NR164Master implements NR164ClientsEvent, NR164PlayerEvent{ + private BA bax; + private Object caller; + private String event; + private boolean need_log_event = false; + private boolean need_udpbytes_event = false; + private boolean need_statistic_event = false; + private boolean need_udpstatus_event =false; + private boolean need_startplayback_event = false; + private boolean need_finishplayback_event = false; + private boolean need_newudpcommand_event = false; + + + private Map clients; + private int _samplingrate=24000; + private int _channel = 1; + private int _playbackdev = 1; // by default, pake default playback device = 1 + private BASS bass; + private boolean _bassinited = false; + private Map _playbackdevlist; + + @BA.Hide + public static String bufferingpath; + + private Queue playbackque = new LinkedList(); + private NR164Player currentplayback = null; + private queuemonitoring quemonitor; + private Object Me; + private int _volout = 10; + + + DatagramSocket udpsocket; + + public NR164Master() { + bufferingpath = System.getProperty("user.dir"); + Me = this; + + if (Platform.isLinux()) { + try { + if (File.Exists("/dev/shm", "")) { + // do write test + OutputStreamWrapper ous = File.OpenOutput("/dev/shm", "test.txt", false); + if (ous.IsInitialized()) { + byte[] testbyte = "123456".getBytes(); + ous.WriteBytes(testbyte, 0, testbyte.length); + ous.Close(); + + // sampai sini bisa, berarti tidak ada exception + bufferingpath = "/dev/shm"; + } + } + } catch (IOException e) { + BA.Log("Unable to check File.Exist to /dev/shm"); + } + + } + + BA.Log("Buffering path set to "+bufferingpath); + } + + /** + * Initialize NR164Master + * @param callerobject : caller object + * @param eventname : event name + * @param samplingrate : sampling rate for file result + * @param channel : 1 = mono, 2 = stereo + */ + public void Initialize(BA ba, Object callerobject, String eventname, int samplingrate, int channel) { + bax = ba; + caller = callerobject; + event = eventname; + if (bax instanceof BA) { + if (callerobject!=null) { + if (event instanceof String) { + if (!event.isEmpty()) { + need_log_event = bax.subExists(event+"_log"); + need_udpbytes_event = bax.subExists(event+"_udpbytes"); + need_statistic_event = bax.subExists(event+"_statistic"); + need_udpstatus_event = bax.subExists(event+"_udpstatus"); + need_startplayback_event = bax.subExists(event+"_startplayback"); + need_finishplayback_event = bax.subExists(event+"_finishplayback"); + need_newudpcommand_event = bax.subExists(event+"_newudpcommand"); + + } + } + } + } + + _samplingrate = (samplingrate > 0 ) ? samplingrate : 24000; + _channel = (channel > 0 ) ? channel : 1; + //raise_log("NR164Master Initialize with samplingrate="+_samplingrate+", channel="+_channel); + bass = new BASS(); + if (bass.BASS_GetVersion()!=0) { + raise_log("Bass Loaded"); + _bassinited = true; + _playbackdevlist = GetAllPlaybackDevices(); + + } else raise_log("Unable to load Bass"); + + clients = new Map(); + clients.Initialize(); + + queuemonitoring qm = new queuemonitoring(); + Thread tx = new Thread(qm); + tx.start(); + quemonitor = qm; + + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + System.out.println("ShutdownHook stopstreaminglistener"); + List cl = RegisteredClients(); + if (cl.getSize()>0) { + for (int ii=0;ii20) value =20; + _volout = value; + if (currentplayback instanceof NR164Player) { + currentplayback.ChangeVolumeOutput(value); + } + } + + /** + * Open UDP for Command Listener + * @param port : listening port + * @return true if can be opened + */ + public boolean UDP_Command_Listener(int port, int maxsize) { + try { + DatagramSocket newudp = new DatagramSocket(port); + Runtime.getRuntime().addShutdownHook(new ShutdownUDP()); + UdpThread tx = new UdpThread(maxsize); + tx.start(); + udpsocket = newudp; + return true; + } catch (SocketException e) { + raise_log("Unable to open UDP Command Listener, Msg : "+e.getMessage()); + } + return false; + } + + public boolean UDP_Send_Data(String ip, int port, byte[] bb, int length) { + if (udpsocket instanceof DatagramSocket) { + try { + InetAddress inet = InetAddress.getByName(ip); + DatagramPacket pkg = new DatagramPacket(bb, length, inet,port); + udpsocket.send(pkg); + } catch (IOException e) { + raise_log("Unable to UDP Send Data to "+ip+":"+port+", Msg "+e.getMessage()); + } + + } + return false; + } + + private class ShutdownUDP extends Thread{ + public void run() { + if (udpsocket instanceof DatagramSocket) { + System.out.println("Close UDP Command at Port "+udpsocket.getLocalPort()); + udpsocket.close(); + } + udpsocket = null; + } + }; + + private class UdpThread extends Thread{ + private final int _maxsize; + public UdpThread(int maxsize) { + _maxsize = maxsize > 0 ? maxsize : 1000; + } + + public void run() { + while(true) { + if (udpsocket instanceof DatagramSocket) { + byte[] bb = new byte[_maxsize]; + DatagramPacket pkg = new DatagramPacket(bb, _maxsize); + try { + udpsocket.receive(pkg); // berhenti di sini + NR164Data nn = new NR164Data(pkg); + if (nn.isvalid) raise_newudpcommand(nn); + } catch (IOException e) { + System.out.println("Error UDP Receive, Msg : "+e.getMessage()); + break; + } + + } else break; + + } + System.out.println("Not receiving UDP Command anymore"); + } + } + + /** + * Add a NR164Clients that it's data is valid to be received + * This client is queued paging operation + * @param ip : client IP address + * @param port : client port + * @return NR164Client object , or null if failed + */ + public NR164Clients AddClient(String ip, int port) { + if (clients instanceof Map) { + if (clients.IsInitialized()) { + //raise_log("AddClient about to create NR164Clients with samplingrate="+_samplingrate+", channel="+_channel); + NR164Clients newclient = new NR164Clients(ip, port, this, _samplingrate, _channel, false); + String id = newclient.getID(); + + if (clients.ContainsKey(id)) { + Object old = clients.Remove(id); + if (old instanceof NR164Clients) { + NR164Clients oldclient = (NR164Clients) old; + oldclient.Discard(); + } + } + + clients.Put(id, newclient); + return newclient; + } + } + return null; + } + + /** + * Add a NR164Clients that its data is valid to be received + * This client is live paging operation + * @param ip : client IP address + * @param port : client Port + * @return NR164Clients object, or null if failed + */ + public NR164Clients AddPriorityClient(String ip, int port) { + if (clients instanceof Map) { + if (clients.IsInitialized()) { + //raise_log("AddPriority about to create NR164Clients with samplingrate="+_samplingrate+", channel="+_channel); + NR164Clients newclient = new NR164Clients(ip, port, this, _samplingrate, _channel, true); + String id = newclient.getID(); + + if (clients.ContainsKey(id)) { + Object old = clients.Remove(id); + if (old instanceof NR164Clients) { + NR164Clients oldclient = (NR164Clients) old; + oldclient.Discard(); + } + } + + clients.Put(id, newclient); + return newclient; + } + } + return null; + } + + /** + * Finish Streaming, and use the data received + * @param key in form of ip:port + * @return finished NR164Clients object, or null if not available + */ + public NR164Clients FinishClient_usingKey(String key) { + NR164Clients result = null; + if (clients instanceof Map) { + if (clients.IsInitialized()) { + if (clients.ContainsKey(key)) { + Object xx = clients.Get(key); + if (xx instanceof NR164Clients) { + NR164Clients client = (NR164Clients) xx; + client.Close(); + + result = client; + } + clients.Remove(key); + + } + + } + } + return result; + } + + /** + * Finish Streaming, but not using the data received + * @param key in form of ip:port + * @return discarded NR164Clients object, or null if not available + */ + public NR164Clients DiscardClient_usingKey(String key) { + NR164Clients result = null; + if (clients instanceof Map) { + if (clients.IsInitialized()) { + if (clients.ContainsKey(key)) { + Object xx = clients.Get(key); + if (xx instanceof NR164Clients) { + NR164Clients client = (NR164Clients) xx; + client.Discard(); + result = client; + } + clients.Remove(key); + } + + } + } + return result; + } + + /** + * Get List of Registered Clients + * List contains string in format ip:port + * @return List of string + */ + public List RegisteredClients() { + List result = new List(); + result.Initialize(); + if (clients instanceof Map) { + if (clients.IsInitialized()) { + int size = clients.getSize(); + if (size>0) { + for(int ii=0;ii0) { + Object xx = clients.Get(key); + if (xx instanceof NR164Clients) { + return (NR164Clients) xx; + } + } + } + } + return null; + } + + /** + * Get all Playback Devices in Map of Bass_DeviceInfo + * @return Map of Bass_DeviceInfo + */ + private Map GetAllPlaybackDevices() { + Map result = new Map(); + result.Initialize(); + if (_bassinited) { + int ii = 0; + boolean stillgotsomething = true; + do { + BASS_DEVICEINFO dev = new BASS_DEVICEINFO(); + if (bass.BASS_GetDeviceInfo(ii, dev)) { + Bass_DeviceInfo _dev = new Bass_DeviceInfo(ii,dev); + if (_dev.isvalid) { + result.Put(ii, _dev); + } else stillgotsomething = false; + } else stillgotsomething = false; + ii+=1; + } while (stillgotsomething); + } + return result; + } + + /** + * Set Playback Device used for NR164 Master + * @param index start from 1. If invalid, will revert to 1 + * @return true if this device found and can be used + */ + public boolean SetPlaybackDeviceID(int index) { + if (index<1) index = 1; + + if (_playbackdevlist instanceof Map) { + if (_playbackdevlist.IsInitialized()) { + if (_playbackdevlist.ContainsKey(index)) { + Bass_DeviceInfo dev = (Bass_DeviceInfo) _playbackdevlist.Get(index); + if (dev.IsEnabled()) { + _playbackdev = index; + raise_log("PlaybackDevice set to "+_playbackdev); + return true; + } + } + } + } + _playbackdev = 1; + raise_log("PlaybackDevice revert to "+_playbackdev); + return false; + } + + /////////// raise event to B4X ///////////////////////////////////////// + + private void raise_udpstatus(String mapid, byte statusbyte) { + if (need_udpstatus_event) bax.raiseEventFromDifferentThread(caller, null, 0, event+"_udpstatus", false, new Object[] {mapid, statusbyte}); + } + + private void raise_udpbytes(String mapid, byte[] bb, int length) { + if (need_udpbytes_event) bax.raiseEventFromDifferentThread(caller, null, 0, event+"_udpbytes", false, new Object[] {mapid, bb, length}); + } + + private void raise_log(String msg) { + if (need_log_event) bax.raiseEventFromDifferentThread(caller, null, 0, event+"_log", false, new Object[] {msg}); + } + + private void raise_statistic(String mapid, int bytesreceived, int byteswritten) { + if (need_statistic_event) bax.raiseEventFromDifferentThread(caller, null, 0, event+"_statistic", false, new Object[] {mapid, bytesreceived, byteswritten}); + } + + private void raise_startplayback(NR164Clients _client, String pathnya) { + if (need_startplayback_event) bax.raiseEventFromDifferentThread(caller, null, 0, event+"_startplayback", false, new Object[] {_client, pathnya}); + } + + private void raise_finishplayback(NR164Clients _client, String pathnya, boolean interrupted) { + if (need_finishplayback_event) bax.raiseEventFromDifferentThread(caller, null, 0, event+"_finishplayback", false, new Object[] {_client, pathnya, interrupted}); + } + + private void raise_newudpcommand(NR164Data value) { + if (need_newudpcommand_event) bax.raiseEventFromDifferentThread(caller, null, 0, event+"_newudpcommand", false, new Object[] {value}); + } + + + + ///////// event dari NR164Clients ///////////////////////// + + @Override + @BA.Hide + public void log(NR164Clients client, String msg) { + raise_log("Log from "+client.getID()+" : "+msg); + + } + + @Override + @BA.Hide + public void streamcreated(NR164Clients client, int handle) { + raise_log("Stream Created for "+client.getID()+", handle="+handle+", Priority = "+client.getIsPriority()); + if (client.getIsPriority()) { + // kalau priority streaming + if (currentplayback instanceof NR164Player) { + // setopin yang sekarang + synchronized(currentplayback) { + raise_log("Current Playback ID="+currentplayback.getID()+" will be stop, because new Priority Paging arrived"); + currentplayback.StopPlayback(); + currentplayback.setInterrupted(true); // playback nya interrupted + // tunggu di sini sampe currentplayback nya selesai + + try { + currentplayback.wait(2000); + } catch (InterruptedException e) { + raise_log("InterruptedException on currentplayback wait, Msg = "+e.getMessage()); + } + + // sampai sini currentplayback selesai ditunggu + + if (!currentplayback.IsPriorityPlayback()) { + // kalau currentplayback berasal dari paging antrian, masukin queue untuk diplay lagi + if (playbackque instanceof Queue) { + synchronized(playbackque) { + playbackque.add(currentplayback); + + } + } + } else { + // kalau current playback berasal dari priroty paging, potong saja + // do nothing + } + } + + // sampe sini sudah selesai + currentplayback = null; + } + + NR164Player newplayer = new NR164Player((Bass_DeviceInfo)_playbackdevlist.Get(_playbackdev), client, (NR164PlayerEvent) Me); + if (newplayer.getIsInited()) { + //raise_log("NR164Player about to StartPlayback with samplingrate="+_samplingrate); + newplayer.StartPlayback(_samplingrate); + raise_log("Current Priority Playback Started for "+newplayer.getID()); + currentplayback = newplayer; + + currentplayback.ChangeVolumeOutput(_volout); + } + } + } + + @Override + @BA.Hide + public void streamclosed(NR164Clients client) { + raise_log("Stream Closed for "+client.getID()); + + } + + @Override + @BA.Hide + public void startrecording(NR164Clients client, String namafile) { + raise_log("Start Recording for "+client.getID()+", Filename="+namafile); + + } + @Override + @BA.Hide + public void finishrecording(NR164Clients client, String namafile) { + raise_log("Finished Recording for "+client.getID()+", Filename="+namafile); + if (client.getIsPriority()==false) { + // yang priority sudah dihandle oleh streamcreated + // di sini khusus yang gak priority, yang dimasukkan ke queue + NR164Player newplayer = new NR164Player((Bass_DeviceInfo)_playbackdevlist.Get(_playbackdev), client, (NR164PlayerEvent) Me); + if (newplayer.getIsInited()) { + if (playbackque instanceof Queue) { + synchronized(playbackque) { + newplayer.setInterrupted(false); + playbackque.add(newplayer); + } + } + } + } + } + + @Override + @BA.Hide + public void udpbytes(NR164Clients client, byte[] bb, int length) { + raise_udpbytes(client.getID(), bb, length); + + } + + @Override + @BA.Hide + public void udpstarted(NR164Clients client) { + //raise_log("UDP Started for "+client.getID()); + raise_udpstatus(client.getID(), (byte) 1); + } + + @Override + @BA.Hide + public void udpstopped(NR164Clients client) { + //raise_log("UDP Stopped for "+client.getID()); + raise_udpstatus(client.getID(), (byte)0); + } + + @Override + @BA.Hide + public void udperror(NR164Clients client, String msg) { + //raise_log("UDP Error for "+client.getID()+", Msg : "+msg); + raise_udpstatus(client.getID(), (byte)-1); + } + + @Override + @BA.Hide + public void statistic(NR164Clients client, int receivedcount, int writtencount) { + raise_statistic(client.getID(), receivedcount, writtencount); + } + + ////////////////////////////////////////////////////////////// + + + /////////// Event dari NR164Player /////////////////////////// + + + @Override + @BA.Hide + public void playerstart(NR164Clients _client) { + if (_client instanceof NR164Clients) { + raise_startplayback(_client, _client.getRecordingFileName()); + } + } + + @Override + @BA.Hide + public void playerfinish(NR164Clients _client) { + boolean interrupted = false; + if (currentplayback instanceof NR164Player) { + synchronized(currentplayback) { + interrupted = currentplayback.getInterrupted(); + // notify kalau ada yang wait + + currentplayback.notifyAll(); + } + } + + if (_client instanceof NR164Clients) { + raise_finishplayback(_client, _client.getRecordingFileName(), interrupted); + } + + }; + + @Override + @BA.Hide + public void playerlog(NR164Clients _client, String msg) { + if (_client instanceof NR164Clients) { + raise_log("PlayerLog ID="+_client.getID()+", Message = "+msg); + } + } + + ///////////////////////////////////////////////////////////// + + ////////// Runnable class ///////////////////////// + private class queuemonitoring implements Runnable{ + private boolean isrunning = false; + @Override + public void run() { + isrunning = true; + while(isrunning) { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + break; + } + + if (!isrunning) break; + + if (currentplayback instanceof NR164Player) { + synchronized(currentplayback) { + if (currentplayback.getIsPlaying()) continue; // masih playback + + // dah gak playback, null-in aja + currentplayback.StopPlayback(); + currentplayback = null; + } + + + } + + // sampai sini, harusnya currentplayback sudah selesai, sudah null + if (playbackque instanceof Queue) { + synchronized(playbackque) { + NR164Player pl = playbackque.poll(); + if (pl instanceof NR164Player) { + // valid + if (pl.getIsInited()) { + //raise_log("NR164Player about to StartPlayback with samplingrate="+_samplingrate); + pl.StartPlayback(_samplingrate); + pl.setInterrupted(false); // ketika start playback, reset interrupted flag nya jadi false + currentplayback = pl; + currentplayback.ChangeVolumeOutput(_volout); + } + + + } + } + } else break; // ternyata dah gak valid, selesai aja + } + isrunning =false; + } + + + public void Stop() { + isrunning = false; + } + } + + + + + +} diff --git a/src/nr164/NR164Player.java b/src/nr164/NR164Player.java new file mode 100644 index 0000000..67b1aaf --- /dev/null +++ b/src/nr164/NR164Player.java @@ -0,0 +1,268 @@ +package nr164; + +import java.nio.ByteBuffer; + +import com.sun.jna.Pointer; +import com.un4seen.bass.BASS; +import com.un4seen.bass.BASS.STREAMPROC; +import com.un4seen.bass.BASS.bassconstant; + +import jbass.Bass_DeviceInfo; + +public class NR164Player { + + private final Bass_DeviceInfo _playdev; + private final NR164Clients _client; + private boolean isinited = false; + + private final NR164PlayerEvent eventobject; + private boolean _isplaying =false; + private boolean _isinterrupted = false; // for checking if playback is interrupted or not + private BASS bass; + + /** + * Get / Set Playback Interrupted status + */ + public boolean getInterrupted() { + return _isinterrupted; + } + + public void setInterrupted(boolean value) { + _isinterrupted = value; + } + + /** + * Initialize NR164Player for Streaming + * @param playbackdev : playback device + * @param client : NR164Clients containing data + */ + public NR164Player(Bass_DeviceInfo playbackdev, NR164Clients client, NR164PlayerEvent object) { + _playdev = playbackdev; + _client = client; + eventobject = object; + if (_playdev instanceof Bass_DeviceInfo) { + if (_playdev.isvalid) { + if (_playdev.IsEnabled()) { + if (_client instanceof NR164Clients) { + bass = new BASS(); + if (bass.BASS_GetVersion()!=0) { + isinited = true; + } + } + } + } + } + } + + + + public boolean getIsInited() { + return isinited; + } + + public boolean getIsPlaying() { + return _isplaying; + } + + public void StopPlayback() { + _isplaying = false; + + } + + public String getID() { + if (isinited) return _client.getID(); else return ""; + } + + /** + * Check if this playback is from NR164Clients with priority flags or not + * @return true if priority + */ + public boolean IsPriorityPlayback() { + if (isinited) { + return _client.getIsPriority(); + } + return false; + } + + public void StartPlayback(int samplingrate) { + playbackrun pp = new playbackrun(samplingrate); + Thread tx = new Thread(pp); + tx.start(); + + } + + /** + * Set Volume Output + * @param value : 0 - 20 + */ + public void ChangeVolumeOutput(int value) { + if (isinited) { + if (value<0) value = 0; + if (value>20) value = 20; + float volume = value / 20.0f; + bass.BASS_SetVolume(volume); + } + } + + private class playbackrun implements Runnable{ + private int handle = 0; + int channelstate = 0; + private final int _freq; + + public playbackrun(int samplingrate) { + _isplaying = false; + _freq = samplingrate; + } + + + @Override + public void run() { + if (isinited) { + + if (!bass.BASS_Init(_playdev.index, _freq, 0)) { + int errcode = bass.BASS_ErrorGetCode(); + if (errcode != bassconstant.BASS_ERROR_ALREADY) { + if (eventobject instanceof NR164PlayerEvent) eventobject.playerlog(_client, "NR164Player Unable to Init, error = "+bass.GetBassErrorString(errcode)); + if (eventobject instanceof NR164PlayerEvent) eventobject.playerfinish(_client); + + raise_playerlog("NR164Player for "+_client.getID()+" unable to BASS_init"); + return; + } + } + + openhandle(); + + if (handle==0) { + if (eventobject instanceof NR164PlayerEvent) eventobject.playerlog(_client, "NR164Player Unable to OpenHandle, error = "+bass.GetBassErrorString()); + if (eventobject instanceof NR164PlayerEvent) eventobject.playerfinish(_client); + + raise_playerlog("NR164Player for "+_client.getID()+" handle = 0"); + return; + } + + // sampe sini, bisa buka handle + + + if (!bass.BASS_Start()) { + if (eventobject instanceof NR164PlayerEvent) eventobject.playerlog(_client, "NR164Player Unable to Start Output, error ="+bass.GetBassErrorString()); + if (eventobject instanceof NR164PlayerEvent) eventobject.playerfinish(_client); + + raise_playerlog("NR164Player for "+_client.getID()+" unable to BASS_Start"); + return; + } + + // sampe sini output bisa buka + if (bass.BASS_ChannelPlay(handle, true)) { + + _isplaying =true; + if (eventobject instanceof NR164PlayerEvent) eventobject.playerstart(_client); + + while(true) { + + try { + Thread.sleep(20); + } catch (InterruptedException e) { + break; + } + + if (_isplaying==false) break; + channelstate = bass.BASS_ChannelIsActive(handle); + if (channelstate == bassconstant.BASS_ACTIVE_STALLED) { + if (_client instanceof NR164Clients) { + if (_client.getStillStreaming()) { + + continue; // kalau stalled, continue aja + } else { + + break; // stalled tapi gak streaming, berhenti aja + } + } else { + + break; + } + + } + if (channelstate != bassconstant.BASS_ACTIVE_PLAYING) { + break; // seperti pengganti 3 status di bawah ini + } + + //if (channelstate == bassconstant.BASS_ACTIVE_STOPPED) break; // sudah selesai + //if (channelstate == bassconstant.BASS_ACTIVE_PAUSED_DEVICE) break; // playback device hilang + } + _isplaying = false; + + + } else { + raise_playerlog("NR164Player Unable to ChannelPlay, error = "+bass.GetBassErrorString()); + } + + + + if (!bass.BASS_Stop()) { + raise_playerlog("NR164Player Unable to Stop Output, error = "+bass.GetBassErrorString()); + } + + if (!bass.BASS_StreamFree(handle)) { + raise_playerlog("NR164Player Unable to StreamFree handle = "+handle+", error = "+bass.GetBassErrorString()); + } + + if (!bass.BASS_Free()) { + raise_playerlog("NR164Player Unable to Free Output, error = "+bass.GetBassErrorString()); + } + + raise_playerfinish(); + + } + + } + + private void openhandle() { + if (isinited) { + if (_client.getIsPriority()) { + // buka handle untuk playback dari live streaming + handle = bass.BASS_StreamCreate(_freq, 1, 0, proc, null); + } else { + // buka handle untuk playback dari file + handle = bass.BASS_StreamCreateFile(false, _client.getRecordingFileName(), 0, 0, 0); + } + } + + } + + STREAMPROC proc = new STREAMPROC() { + + @Override + public int STREAMPROC(int handle, Pointer buffer, int length, Pointer user) { + int result = 0; + if (_client instanceof NR164Clients) { + ByteBuffer buf = _client.GetEncodedBuffer(); + if (buf instanceof ByteBuffer) { + result = Math.min(buf.position(), length); // cek mana yang lebih dikit + if (result>0) { + byte[] mm = new byte[result]; + buf.flip(); + buf.get(mm); + buf.compact(); + + buffer.write(0, mm, 0, result); + + } + + } + } + return result; + } + + }; + + + private void raise_playerlog(String msg) { + if (eventobject instanceof NR164PlayerEvent) eventobject.playerlog(_client, msg); + } + + private void raise_playerfinish() { + if (eventobject instanceof NR164PlayerEvent) eventobject.playerfinish(_client); + } + + }; +} diff --git a/src/nr164/NR164PlayerEvent.java b/src/nr164/NR164PlayerEvent.java new file mode 100644 index 0000000..5de7463 --- /dev/null +++ b/src/nr164/NR164PlayerEvent.java @@ -0,0 +1,7 @@ +package nr164; + +public interface NR164PlayerEvent { + void playerstart(NR164Clients mapid); + void playerfinish(NR164Clients mapid); + void playerlog(NR164Clients mapid, String msg); +} diff --git a/src/qzaip/fileinformation.java b/src/qzaip/fileinformation.java new file mode 100644 index 0000000..5ca4b4a --- /dev/null +++ b/src/qzaip/fileinformation.java @@ -0,0 +1,272 @@ +package qzaip; + +import java.nio.ByteBuffer; + +import com.sun.jna.Memory; +import com.sun.jna.Platform; +import com.sun.jna.Pointer; +import com.un4seen.bass.BASS; +import com.un4seen.bass.BASS.BASS_DEVICEINFO; +import com.un4seen.bass.BASS.bassconstant; +import com.un4seen.bass.BASSmix; + +import anywheresoftware.b4a.BA; + +@BA.ShortName("AudioFileInformation") +@BA.Events(values= { + "log(msg as string)" +}) + +@SuppressWarnings("all") +public class fileinformation { + + private BA ba; + private String event = ""; + + private String currentfile = ""; + private int currenthandle = 0; + private long currentsize = 0; + private double currentduration = 0; + private long mixersize = 0; + + private BASS bass ; + private BASSmix bassmix ; + private int samplingrate = 44100; + private boolean inited = false; + private int mixerhandle = 0; + + private boolean need_log_event = false; + private Object Me; + + /** + * initialize file information + * @param eventname : eventname + * @param filepath : valid audio filepath + * @param Samplingrate : new samplingrate. set 0 = 44100hz + */ + public void Initialize(BA bax, String eventname, String filepath, int Samplingrate) { + Me = this; + this.ba = bax; + this.event = eventname; + this.currentfile = filepath; + if (Samplingrate!=0) this.samplingrate = Samplingrate; + if (ba!=null) { + if (!event.isEmpty()) { + need_log_event = bax.subExists(eventname+"_log"); + } + } + + bass = new BASS(); + bassmix = new BASSmix(); + if (bass.BASS_GetVersion()!=0) { + if (bassmix.BASS_Mixer_GetVersion()!=0) { + inited = true; + } + } + + } + + /** + * Get selected audio filepath + * @return audio file path from initialization + */ + public String getSelectedFileName() { + return currentfile; + } + + /** + * Get selected Sampling rate + * @return samplingrate from initialization + */ + public int getSelectedSamplingRate() { + return samplingrate; + } + + /** + * Check if this class is initialized + * @return true if initialized + */ + public boolean getIsInitialized() { + return inited; + } + + /** + * Check if audio file is opened + * @return true if opened + */ + public boolean getFileisOpened() { + if (currenthandle==0) return false; else return true; + } + + /** + * Check if Mixer is opened + * @return true if opened + */ + public boolean getMixerisOpened() { + if (mixerhandle==0) return false; else return true; + } + + /** + * Get size in original file + * @return 0 if file closed + */ + public long getActualFileSize() { + return currentsize; + } + + /** + * Get audio duration in seconds + * @return 0 if file closed + */ + public double getActualDuration() { + return currentduration; + } + + /** + * get size in resampled + * @return 0 if file closed + */ + public long getResampledSize() { + return mixersize; + } + + /** + * Open file + * @return true if success + */ + public boolean Open() { + currentsize = 0; + currentduration = 0; + mixersize = 0; + if (!initbass()) return false; + int openflag = bassconstant.BASS_STREAM_DECODE; + currenthandle = BASS.BASS_StreamCreateFile(false,currentfile, 0, 0, openflag); + + if (currenthandle==0) { + bass_log("Open","BASS_StreamCreateFile"); + return false; + } + + int addflag = bassconstant.BASS_STREAM_AUTOFREE; + if (!bassmix.BASS_Mixer_StreamAddChannel(mixerhandle, currenthandle, addflag)) { + bass_log("Open","BASS_Mixer_StreamAddChannel"); + return false; + } + + long size1 = BASS.BASS_ChannelGetLength(currenthandle, bassconstant.BASS_POS_BYTE); + if (size1<0) { + bass_log("Open","BASS_ChannelGetLength"); + return false; + } else currentsize = size1; + + double dur1 = BASS.BASS_ChannelBytes2Seconds(currenthandle, size1); + if (dur1<0) { + bass_log("Open","BASS_ChannelBytes2Seconds"); + return false; + } else currentduration = dur1; + + long size2 = BASS.BASS_ChannelSeconds2Bytes(mixerhandle, dur1); + if (size2<0) { + bass_log("Open","BASS_ChannelSeconds2Bytes"); + return false; + } else mixersize = size2; + + return true; + } + + /** + * Close current file and mixer + */ + public void Close() { + if (currenthandle!=0) { + BASS.BASS_StreamFree(currenthandle); + currenthandle = 0; + } + if (mixerhandle!=0) { + BASS.BASS_StreamFree(mixerhandle); + mixerhandle = 0; + } + } + + /** + * Get bytes array of resampled PCM + * @return null if failed + */ + public byte[] getResampledBytes() { + if (currenthandle==0) return null; + if (mixerhandle==0) return null; + if (mixersize<0) return null; + + Pointer pp = new Memory(mixersize); + + int readsize = BASS.BASS_ChannelGetData(mixerhandle, pp, (int)mixersize); + if (readsize<0) { + pp = null; + return null; + } + + + return pp.getByteArray(0, readsize); + } + + + private boolean initbass() { + if (BASS.BASS_GetVersion()==0) { + raise_log("BASS_GetVersion return 0"); + return false; + } + if (bassmix.BASS_Mixer_GetVersion()==0) { + raise_log("BASS_Mixer_GetVersion return 0"); + return false; + } + + BASS_DEVICEINFO devinfo = new BASS_DEVICEINFO(); + if (!BASS.BASS_GetDeviceInfo(0, devinfo)) { + bass_log("initbass","GetDeviceInfo"); + return false; + } + + devinfo.read(); + if ((devinfo.flags & bassconstant.BASS_DEVICE_ENABLED)==0) { + raise_log("Device 0 is disabled"); + return false; + } + + if ((devinfo.flags & bassconstant.BASS_DEVICE_INIT)==0) { + // need to be inited + int flaginit = bassconstant.BASS_DEVICE_MONO; + if (!BASS.BASS_Init(0, 44100, flaginit)) { + bass_log("initbass","BASS_Init:0"); + return false; + } + } else { + // already inited, just set it + if (!BASS.BASS_SetDevice(0)) { + bass_log("initbass","SetDevice:0"); + return false; + } + } + + int mixflag = bassconstant.BASS_STREAM_DECODE ; + mixerhandle = bassmix.BASS_Mixer_StreamCreate(samplingrate, 1, mixflag); + if (mixerhandle==0) { + bass_log("initbass","BASS_Mixer_StreamCreate"); + return false; + } + + return true; + } + + private void bass_log(String functionname, String basscommand) { + int errcode = BASS.BASS_ErrorGetCode(); + if (errcode==0) return; + String msg = "Bass Error on function="+functionname+", command="+basscommand+", code="+BASS.GetBassErrorString(errcode); + raise_log(msg); + } + + private void raise_log(String msg) { + if (need_log_event) { + ba.raiseEventFromDifferentThread(Me, null, 0, event+"_log", false, new Object[] {msg}); + } + } +} diff --git a/src/qzaip/qza_receiver.java b/src/qzaip/qza_receiver.java new file mode 100644 index 0000000..77471d2 --- /dev/null +++ b/src/qzaip/qza_receiver.java @@ -0,0 +1,833 @@ +package qzaip; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.ShortBuffer; +import java.util.ArrayList; + +import com.sun.jna.Memory; +import com.sun.jna.Pointer; +import com.un4seen.bass.BASS; +import com.un4seen.bass.BASS.BASS_CHANNELINFO; +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.SYNCPROC; +import com.un4seen.bass.BASS.bassconstant; +import com.un4seen.bass.BASSmix; + +import anywheresoftware.b4a.BA; +import anywheresoftware.b4a.keywords.DateTime; +import anywheresoftware.b4a.objects.collections.Map; +import jbass.Bass_ChannelInfo; +import jbass.Bass_DeviceInfo; + + + +/** + * QZA IP Receiver + * @author rdkartono + */ +@BA.ShortName("QZAIPRECEIVER") +@BA.Events(values= { + "log(msg as string)", + "playbackstarted(handle as int, filename as string)", + "playbackstopped(handle as int, filename as string)", + "pcmbytes(handle as int, databytes() as byte)", + "vulevel(handle as int, value as int)", + "handleready(handle as int, streamname as string)" +}) +public class qza_receiver { + private Object Me; + private BASS bass; + private BASSmix bassmix; + + private BA ba; + private String eventname; + private int deviceid; + private int samplingrate; + private int channel; + private int mixerhandle; + + private boolean initialized = false; + + private boolean need_log_event = false; + private boolean need_playbackstarted_event = false; + private boolean need_playbackstopped_event = false; + private boolean need_pcmbytes_event = false; + private boolean need_vulevel_event = false; + private boolean need_handleready_event = false; + + + /** + * QzaBuffer, based on ByteBuffer, but with simplified interface + * @author rdkartono + */ + @BA.ShortName("QzaBuffer") + public class QzaBuffer { + private ByteBuffer bb; + private final int growfactor = 1024*1024; // 1MB + private int write_available = 0; + private int read_available = 0; + private final String name; + private final long creationtick; + private int handle; + + + /** + * Initialize QzaBuffer + * @param size : Size of created QzaBuffer, in bytes + */ + public QzaBuffer(int size, String name) { + this.name = name; + this.creationtick = DateTime.getNow(); + bb = ByteBuffer.allocate(size); + write_available = bb.limit(); + read_available = 0; + } + + /** + * name + * @return name in string + */ + public String getName() { + return name; + } + + /** + * Get Stream handle + * @return 0 if not assigned to stream handle + */ + public int getHandle() { + return handle; + } + + private void setHandle(int value) { + handle = value; + } + + + + /** + * Get Duration since QzaBuffer creation + * Can be use to determine unclosed streaming + * @param scale : + * "second" --> will return how many seconds since creation (default) + * "minute" --> will return how many minutes since creation + * "hour" --> will return how many hours since creation + * "day" --> will return how many days since creation + * @return value + */ + public int GetDuration(String scale) { + long delta = DateTime.getNow() - creationtick; + switch(scale) { + case "minute" : + return (int)(delta / DateTime.TicksPerMinute); + case "hour" : + return (int)(delta / DateTime.TicksPerHour); + case "day" : + return (int)(delta / DateTime.TicksPerDay); + default : + return (int) (delta / DateTime.TicksPerSecond); + + } + } + + /** + * Write array of bytes to QzaBuffer + * @param bx : array of bytes + */ + public void write(byte[] bx) { + if (bx instanceof byte[]) { + if (bx.length>0) { + if (write_available < bx.length) grow(); + bb.put(bx); + } + } + read_available = bb.position(); + write_available = bb.remaining(); + + } + + /** + * Write array of bytes to QzaBuffer + * @param bx : array of bytes + * @param length : length of bytes to write + */ + public void write(byte[] bx, int length) { + if (bx instanceof byte[]) { + if (bx.length>0) { + if (length < 1) length = bx.length; + if (length > bx.length) length = bx.length; + if (write_available < length) grow(); + bb.put(bx,0,length); + } + } + read_available = bb.position(); + write_available = bb.remaining(); // remaining = limit - position + } + + /** + * How much bytes can be read from + * @return number of bytes can be read + */ + public int Read_Available() { + return read_available; + } + + /** + * How much bytes can be written into + * @return number of bytes can be written + */ + public int Write_Available() { + return write_available; + } + + /** + * Read + * @param count : number of bytes to be read + * @return byte array + * null if no data avaiable, or + * as much as read_available if not enough, or + * as much as count if enough + */ + public byte[] read(int count) { + if (count<1) count = 0; + if (count > read_available) count = read_available; + if (count>0) { + byte[] result = new byte[count]; + bb.flip(); // change to read mode + bb.get(result); + bb.compact(); // change to write mode + + read_available = bb.position(); + write_available = bb.remaining(); + return result; + } else { + return null; + } + + + } + + /** + * Grow current ByteBuffer by growfactor (1 MB) + */ + private void grow() { + ByteBuffer bx = ByteBuffer.allocate(bb.capacity()+growfactor); + bx.put(bb); + bb = bx; + write_available = bb.remaining(); + } + } + + /** + * B4X Map to put QzaBuffer objects + */ + private Map buffermap = new Map(); + + /** + * Initialize BASS + * @param eventname : eventname + */ + public void Initialize(BA ba, String eventname) { + Me = this; + this.ba = ba; + this.eventname = eventname; + buffermap.Initialize(); + + + this.need_log_event = ba.subExists(eventname+"_log"); + this.need_playbackstarted_event = ba.subExists(eventname+"_playbackstarted"); + this.need_playbackstopped_event = ba.subExists(eventname+"_playbackstopped"); + this.need_pcmbytes_event = ba.subExists(eventname+"_pcmbytes"); + this.need_handleready_event = ba.subExists(eventname+"_handleready"); + + bass = new BASS(); + bassmix = new BASSmix(); + + initialized = (bass.BASS_GetVersion()>0 ? true : false) & (bassmix.BASS_Mixer_GetVersion()>0 ? true : false); + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + raise_log("ShutdownHook Called, calling Free()"); + Free(); + } + }); + } + + /** + * Check if BASS is initialized + * @return true if initialized + */ + public boolean IsInitialized() { + return initialized; + } + + /** + * Release BASS from memory + * Should be called only when finish using it + * Also will be called automatically on program unload using ShutdownHook + */ + public void Free() { + if (bass!=null) bass.BASS_Free(); + } + + /** + * Get current Device ID + * @return 0 = no sound, 1... = real audio device + */ + public int getDeviceID() { + return deviceid; + } + + /** + * Set current Device ID + * @param value : 0 = no sound, 1... = real audio device + */ + public void setDeviceID(int value) { + if (value != deviceid) { + if (initialized) { + if (bass.BASS_SetDevice(value)) { + deviceid = value; + } else raise_log_BASS_error("BASS_SetDevice"); + } else raise_log_bass_not_initialized("setDeviceID"); + } + } + + /** + * Get array of available playback devices + * @return array of Bass_DeviceInfo + */ + public Bass_DeviceInfo[] GetDeviceInfos() { + ArrayList result = new ArrayList(); + if (initialized) { + int ii=1; + while(true) { + BASS_DEVICEINFO bdi = new BASS_DEVICEINFO(); + if (bass.BASS_GetDeviceInfo(ii, bdi)) { + Bass_DeviceInfo bd = new Bass_DeviceInfo(ii, bdi); + result.add(bd); + ii++; + } else break; + } + } else raise_log_bass_not_initialized("GetDeviceInfos"); + + return (Bass_DeviceInfo[]) result.toArray(); + } + + + + /** + * Get PCM Bytes from a file + * @param filename : filename + * @return byte array, or null if failed + */ + public byte[] GetPCMBytesFromFile(String filename) { + byte[] result = null; + if (initialized) { + bass.BASS_Init(0, this.samplingrate>0 ? this.samplingrate : 48000, 0); + int handle = bass.BASS_StreamCreateFile(false, filename, 0, 0, bassconstant.BASS_STREAM_DECODE); + if (handle!=0) { + long count = bass.BASS_ChannelGetLength(handle, bassconstant.BASS_POS_BYTE); + if (count>0) { + Pointer buf = new Memory(count); + int readcount = bass.BASS_ChannelGetData(handle, buf, (int)count); + if (readcount>0) { + result = buf.getByteArray(0, readcount); + } else raise_log_BASS_error("BASS_ChannelGetData"); + } else raise_log_BASS_error("BASS_ChannelGetLength"); + bass.BASS_StreamFree(handle); // dont care the result + } else raise_log_BASS_error("BASS_StreamCreateFile"); + } else raise_log_bass_not_initialized("GetPCMBytesFromFile"); + return result; + } + + /** + * Open an audio device + * already call StartOutput from inside + * will raise event vulevel(handle as int, value as int) + * @param deviceid : 0 = no audio, 1.. = next hardware audio device + * @param samplingrate : samplingrate + * @param channel : 1 = mono, 2 = stereo, default to mono + * @return true if device can be opened. if need audio output, then call StartOutput function + */ + public boolean OpenDevice(int deviceid, int samplingrate, int channel) { + this.deviceid = deviceid; + this.samplingrate = samplingrate; + this.channel = channel; + if (initialized) { + int flags = this.channel==2 ? bassconstant.BASS_DEVICE_STEREO : bassconstant.BASS_DEVICE_MONO; + flags |= bassconstant.BASS_DEVICE_FREQ; + flags |= bassconstant.BASS_DEVICE_REINIT; + + + if (bass.BASS_Init(this.deviceid, this.samplingrate, flags)) { + mixerhandle = bassmix.BASS_Mixer_StreamCreate(samplingrate, channel, bassmix.BASS_MIXER_QUEUE | bassmix.BASS_MIXER_NONSTOP); + if (mixerhandle!=0) { + if (bass.BASS_ChannelPlay(mixerhandle, true)) { + + int devfail_sync_handle = bass.BASS_ChannelSetSync(mixerhandle, bassconstant.BASS_SYNC_DEV_FAIL, 0, (synchandle, mixhandle, data, user)->{ + raise_log("Playback Device Failure detected"); + }, null); + + if (devfail_sync_handle==0) raise_log_BASS_error("BASS_ChannelSetSync BASS_SYNC_DEV_FAIL"); + + int dsphandle = bass.BASS_ChannelSetDSP(mixerhandle, (dsp_hadle, mixer_handle, buffer, length, user)->{ + // dapetin vu level + ShortBuffer sb = buffer.getByteBuffer(0, length).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer(); + Short current = 0; + Short highest = 0; + while(sb.hasRemaining()) { + current = sb.get(); + if (current>highest) highest = current; + } + raise_vulevel(mixerhandle, (int)((highest.doubleValue() / Short.MAX_VALUE)*100)); + }, null, 0); + if (dsphandle==0) raise_log_BASS_error("BASS_ChannelSetDSP mixerhadle"); + + return true; + } else raise_log_BASS_error("BASS_ChannelPlay mixerhandle"); + } else raise_log_BASS_error("BASS_Mixer_StreamCreate"); + } else raise_log_BASS_error("BASS_Init"); + } else raise_log_bass_not_initialized("OpenDevice"); + return false; + } + + /** + * Start / Resume Audio Output + * @return true if Output can be started + */ + public boolean StartOutput() { + if (initialized) { + if (bass.BASS_Start()) { + return true; + } else raise_log_BASS_error("BASS_Start"); + } else raise_log_bass_not_initialized("StartOutput"); + return false; + } + + /** + * Stop Audio Output and pausing all streams in it (not free-ing) + * @return true if output can be paused + */ + public boolean PauseOutput() { + if (initialized) { + if (bass.BASS_Pause()) { + return true; + } else raise_log_BASS_error("BASS_Pause"); + } else raise_log_bass_not_initialized("PauseOutput"); + return false; + } + + /** + * Stop Audio Output and stopping all streams in it (not free-ing) + * @return true if output can be stopped + */ + public boolean StopOutput() { + if (initialized) { + if (bass.BASS_Stop()) { + return true; + } else raise_log_BASS_error("BASS_Stop"); + + } else raise_log_bass_not_initialized("StopOutput"); + return false; + } + + /** + * Set Volume + * @param value : 0 - 100 + */ + public void setVolume(int value) { + if (value<0) value = 0; + if (value>100) value = 100; + if (initialized) { + if (bass.BASS_SetVolume(value/100.0f)) { + + } else raise_log_BASS_error("BASS_SetVolume"); + } else raise_log_bass_not_initialized("setVolume"); + } + + /** + * Get Volume + * @return -1 if failed, max = 100 + */ + public int getVolume() { + if (initialized) { + float result = bass.BASS_GetVolume(); + if (result != -1) { + return (int)(result*100); + } else raise_log_BASS_error("BASS_GetVolume"); + } else raise_log_bass_not_initialized("getVolume"); + return -1; + } + + + /** + * Open a file in Decode Mode + * @param filename filename + * @return filehandle, 0 if failed, else if success + */ + public int OpenFile(String filename) { + if (initialized) { + int handle = bass.BASS_StreamCreateFile(false, filename, 0, 0, bassconstant.BASS_STREAM_DECODE); + if (handle!=0) { + return handle; + } else raise_log("BASS_StreamCreateFile failed for "+filename+", reason="+bass.GetBassErrorString()); + } else raise_log_bass_not_initialized("OpenFile"); + return 0; + } + + /** + * Open a streaming in decode mode + * will create a QzaBuffer object, put in Map with key = streamname + * after that, will return true + * Then using new thread, will wait until Bass handle created, and will raise event handleready(handle, streamname) + * use the handle result with Play / Stop / Pause + * @param streamname streaming name, something to identify + * @return true if QzaBuffer can be created + */ + public boolean OpenStreaming(String streamname) { + if (initialized) { + QzaBuffer bb = new QzaBuffer(1024*1024, streamname); + Object prevobj = buffermap.Put(streamname, bb); + if (prevobj instanceof QzaBuffer) { + QzaBuffer prevbuf = (QzaBuffer) prevobj; + if (prevbuf.getHandle()!=0) { + StreamFree(prevbuf.getHandle()); + } + prevbuf = null; + prevobj = null; // buang aja yang lama kalau ada kembar + } + + FILECLOSEPROC fcp = (user)->{ + // do nothing + }; + FILELENPROC flp = (user)->{ + return 0; // stream block + }; + FILEREADPROC frp = (buffer, length, user)->{ + if (user instanceof Pointer) { + String buffername = ((Pointer) user).getString(0); + Object obj = buffermap.Get(buffername); + if (obj instanceof QzaBuffer) { + QzaBuffer bx = (QzaBuffer) obj; + byte[] readresult = bx.read(length); + if (readresult instanceof byte[]) { + if (readresult.length>0) { + // masukin data di sini + buffer.write(0,readresult,0,readresult.length); + raise_log("FILREADPROC ["+user+"] write "+readresult.length+" bytes"); + return readresult.length; + } else raise_log("FILEREADPROC readresult ["+user+"] length invalid"); + } else raise_log("FILEREADPROC readresult ["+user+"] is not byte array"); + } else raise_log("FILEREADPROC buffermap.Get ["+user+"] is not QzaBuffer"); + } else raise_log("FILREADPROC user is not String"); + // ada masalah + return 0; + + + + }; + FILESEEKPROC fsp = (offset, user)->{ + return false; // can not seek + }; + BASS_FILEPROCS fp = new BASS_FILEPROCS(fcp, flp, frp, fsp); + + ba.submitRunnable(new Runnable() { + + @Override + public void run() { + // tunggu sampe bisa baca 8192 bytes + do { + try { + Thread.sleep(10); + } catch (InterruptedException e) { + break; // break from do-while + } + + } while (bb.Read_Available()<8192); + + // baru coba create handle + + Pointer user = new Memory(streamname.length()); + user.setString(0, streamname); + int handle = bass.BASS_StreamCreateFileUser(bassconstant.STREAMFILE_BUFFER, bassconstant.BASS_STREAM_DECODE, fp,user); + if (handle!=0) { + bb.setHandle(handle); + raise_handleready(handle, streamname); + } else raise_log("BASS_StreamCreateFileUser failed for "+streamname+", reason="+bass.GetBassErrorString()); + } + + }, null, 0); + + return true; // OpenStreaming success + } else raise_log_bass_not_initialized("OpenStream"); + return false; // OpenStreaming failed + } + + /** + * Push byte array to buffer with specific streamname + * @param streamname : stream name , some unique string to remember + * @param bb : byte array + * @param length : how many bytes to write + * @return how much write available space available after this + */ + public int StreamingPushBytes(String streamname, byte[] bb, int length) { + Object mm = buffermap.Get(streamname); + if (mm instanceof QzaBuffer) { + QzaBuffer qb = (QzaBuffer) mm; + qb.write(bb, length); + return qb.Write_Available(); + } else return -1; + } + + /** + * Get Opened Streams + * @return array of streamname, or null if not available + */ + public String[] GetOpenedStreaming() { + if (buffermap.getSize()>0) { + String[] result = new String[buffermap.getSize()]; + for(int i=0;i0) { + for(int i=0;i0) { + chimeuphandle = bass.BASS_StreamCreateFile(false, chimeup, 0, 0, bassconstant.BASS_STREAM_DECODE); + } else chimeuphandle = 0; + if (chimedown != null && chimedown.length()>0) { + chimedownhandle = bass.BASS_StreamCreateFile(false, chimedown, 0, 0, bassconstant.BASS_STREAM_DECODE); + } else chimedownhandle = 0; + + String streamname = getStreamNameByHandle(handle); + + if (mixerhandle!=0) { + + bass.BASS_ChannelLock(mixerhandle, true); // lock mixer + if (chimeuphandle!=0) bassmix.BASS_Mixer_StreamAddChannel(mixerhandle, chimeuphandle, bassconstant.BASS_STREAM_AUTOFREE); + boolean handle_added = bassmix.BASS_Mixer_StreamAddChannel(mixerhandle, handle, bassconstant.BASS_STREAM_AUTOFREE); + if (chimedownhandle!=0) bassmix.BASS_Mixer_StreamAddChannel(mixerhandle, chimedownhandle, bassconstant.BASS_STREAM_AUTOFREE); + bass.BASS_ChannelLock(mixerhandle, false); // unlock mixer + + if (handle_added) { + //playback monitoring + ba.submitRunnable(new Runnable() { + + @Override + public void run() { + int dsphandle = bass.BASS_ChannelSetDSP(mixerhandle, (dsp_handle, mixer_handle, buffer, length, user)->{ + raise_pcmbytes(mixer_handle, buffer.getByteArray(0, length)); + }, null, 0); + if (dsphandle==0) raise_log_BASS_error("BASS_ChannelSetDSP mixerhandle"); + + raise_playbackstarted(handle, streamname); + + SYNCPROC sync = (synchandle, streamhandle, data, user)->{ + if (dsphandle!=0) { + if (bass.BASS_ChannelRemoveDSP(mixerhandle, dsphandle)==false) { + raise_log_BASS_error("BASS_ChannelRemoveDSP mixerhandle"); + } + } + raise_playbackstopped(handle, streamname); + + }; + + if (chimedownhandle!=0) { + // ada chimedown, monitor playbackstop di chimedown + bassmix.BASS_Mixer_ChannelSetSync(chimedownhandle, bassconstant.BASS_SYNC_END, 0, sync, null); + } else { + // gak ada chimedown, monitor playbackstop di handle + bassmix.BASS_Mixer_ChannelSetSync(handle, bassconstant.BASS_SYNC_END, 0, sync, null); + } + + } + + }, null, 0); + + + } + return handle_added; + } else raise_log("Unable to Play, mixerhandle = 0"); + } else raise_log_invalid_handle("Play"); + } else raise_log_bass_not_initialized("Play"); + return false; + } + + /** + * Stop playback of a file, by its handle + * @param handle playback handle, obtained from OpenFile or event handleready + * @return true if success + */ + public boolean Stop(int handle) { + if (initialized) { + if (handle!=0) { + if (bass.BASS_ChannelStop(handle)) { + return true; + } else raise_log_BASS_error("BASS_ChannelStop"); + } else raise_log_invalid_handle("Stop"); + } else raise_log_bass_not_initialized("Stop"); + return false; + } + + /** + * Pause playback of a file, by its handle + * @param handle playback handle, obtained from OpenFile or event handleready + * @return true if success + */ + public boolean Pause(int handle) { + if (initialized) { + if (handle!=0) { + if (bass.BASS_ChannelPause(handle)) { + return true; + } else raise_log_BASS_error("BASS_ChannelPause"); + } else raise_log_invalid_handle("Pause"); + } else raise_log_bass_not_initialized("Pause"); + return false; + } + + /** + * Free a stream by its handle + * @param handle : obtain by OpenFile or event handleready + * @return + */ + public boolean StreamFree(int handle) { + if (initialized) { + if (handle!=0) { + if (bass.BASS_StreamFree(handle)) { + return true; + } else raise_log_BASS_error("BASS_StreamFree"); + } else raise_log_invalid_handle("StreamFree"); + } else raise_log_bass_not_initialized("StreamFree"); + return false; + } + + + + /** + * Raise Log + * @param msg : message string + */ + private void raise_log(String msg) { + if (need_log_event) ba.raiseEventFromDifferentThread(Me, null, 0, eventname+"_log", false, new Object[] {msg}); + } + + /** + * Raise log with template [functionname] failed, BASS not initialzed + * @param functionname : function name in string + */ + private void raise_log_bass_not_initialized(String functionname) { + raise_log(functionname+" failed, BASS not initialized"); + } + + /** + * Raise Log with template [functionname] failed, reason=[BASS Error String] + * @param functionname : function name in string + */ + private void raise_log_BASS_error(String functionname) { + raise_log(functionname+" failed, reason="+bass.GetBassErrorString()); + } + + /** + * Raise Log with template [functionname] failed, invalid handle + * @param functionname : function name + */ + private void raise_log_invalid_handle(String functionname) { + raise_log(functionname+" failed, invalid handle"); + } + + + + private void raise_playbackstopped(int handle, String filename) { + if (need_playbackstopped_event) ba.raiseEventFromDifferentThread(Me, null, 0, eventname+"_playbackstopped", false, new Object[] {handle, filename}); + } + + private void raise_playbackstarted(int handle, String filename) { + if (need_playbackstarted_event) ba.raiseEventFromDifferentThread(Me, null, 0, eventname+"_playbackstarted",false, new Object[] {handle, filename}); + } + + private void raise_pcmbytes(int handle, byte[] databytes) { + if (need_pcmbytes_event) ba.raiseEventFromDifferentThread(Me, null, 0, eventname+"_pcmbytes", false, new Object[] {handle, databytes}); + } + + private void raise_vulevel(int handle, int value) { + if (need_vulevel_event) ba.raiseEventFromDifferentThread(Me, null, 0, eventname+"_vulevel", false, new Object[] {handle, value}); + } + + private void raise_handleready(int handle, String streamname) { + if (need_handleready_event) ba.raiseEventFromDifferentThread(Me, null, 0, eventname+"_handleready", false, new Object[] {handle, streamname}); + } + + +} diff --git a/src/qzaip/qzaip.java b/src/qzaip/qzaip.java new file mode 100644 index 0000000..65b0bbd --- /dev/null +++ b/src/qzaip/qzaip.java @@ -0,0 +1,750 @@ +package qzaip; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.net.SocketException; +import java.net.UnknownHostException; + +import anywheresoftware.b4a.BA; +import anywheresoftware.b4a.keywords.Bit; +import anywheresoftware.b4a.keywords.DateTime; +import anywheresoftware.b4a.objects.collections.List; + +@BA.ShortName("QZAIP") +@BA.Events(values= { + "log(msg as string)", + "dataarrived(bb() as byte)", + "offline(timetick as long)", + "online(timetick as long)", + "status(stat as qzastatus)", + "txrxstatus(tx as long, txfail as long, rx as long, rxfail as long)" +}) + +public class qzaip { + private final byte cmdheader = (byte)0xAA; + private final byte cmdfooter = (byte)0x55; + + + private BA ba; + private String event = ""; + private boolean isinited = false; + private SocketAddress targetsockaddress = null; + private InetAddress targetinetaddress = null; + private String thisIP = ""; + private int thisPort = 0; + private int onlinecounter = 0; + + private boolean need_log_event = false; + private boolean need_dataarrived_event = false; + private boolean need_online_event = false; + private boolean need_offline_event = false; + private boolean need_status_event =false; + private boolean need_txrxstatus_event = false; + private qzastatus laststatus; + private Object Me; + private long txcount = 0; + private long rxcount = 0; + private long txfail = 0; + private long rxfail = 0; + private boolean isrunning = false; + + private DatagramSocket mysock; + private String description = ""; + + /** + * Initialize QZAIP class + * @param eventname : event prefix string + * @param localport : local port for UDP listening, set 0 for automatic + * @param targetIP : QZAIP target IP Address + * @param targetPort : QZAIP target UDP Port + */ + public void Initialize(BA bax, String eventname, String targetIP, int targetPort) { + Me = this; + this.isinited = false; + this.ba = bax; + this.event = eventname; + if (ba!=null) { + if (!event.isEmpty()) { + this.need_log_event = bax.subExists(eventname+"_log"); + this.need_dataarrived_event = bax.subExists(eventname+"_dataarrived"); + this.need_online_event = bax.subExists(eventname+"_online"); + this.need_offline_event = bax.subExists(eventname+"_offline"); + this.need_status_event = bax.subExists(eventname+"_status"); + this.need_txrxstatus_event = bax.subExists(eventname+"_txrxstatus"); + } + } + + + try { + this.targetinetaddress = InetAddress.getByName(targetIP); + this.targetsockaddress = new InetSocketAddress(targetinetaddress, targetPort); + + } catch(IllegalArgumentException | SecurityException | UnknownHostException ex) { + String msg = "Create targetsockaddress error, Msg:"+ex.getMessage()+", Caused:"+ex.getCause(); + raise_log(msg); + this.targetinetaddress = null; + this.targetsockaddress =null; + + } + + + + if (targetsockaddress!=null) { + try { + + mysock = new DatagramSocket(); + + } catch (SocketException e) { + raise_log("Create DatagramSocket error, Msg:"+e.getMessage()+", Caused:"+e.getCause()); + mysock = null; + return; + } + + if (mysock!=null) { + thisIP = targetIP; + thisPort = targetPort; + isinited = true; + raise_log(thisIP+" is using localport "+mysock.getLocalPort()); + Thread tx = new Thread() { + public void run() { + isrunning = true; + + while(true) { + try { + Thread.sleep(1); + } catch (InterruptedException e1) { + raise_log("Receiving Sleep Interrupted, Msg : "+e1.getMessage()+", Caused : "+e1.getCause()); + break; + } + + DatagramPacket pkg = new DatagramPacket(new byte[64],64); + try { + if (mysock!=null) { + //mysock.setSoTimeout(2000); // set receiving timeout 2 seconds + mysock.receive(pkg); + if (pkg!=null) { + if (rxcount < Long.MAX_VALUE) rxcount+=1; else rxcount = 0; + process_data(pkg); + } + + } else break; + + } catch (IOException e) { + if (rxfail < Long.MAX_VALUE) rxfail+=1; else rxfail = 0; + raise_log("Receiving Error, Msg : "+e.getMessage()+", Caused : "+e.getCause()); + } finally { + raise_txrxstatus(); + } + + } + isrunning = false; + } + }; + tx.start(); + } + } + } + + @BA.Hide + public InetAddress getInetAddress() { + return targetinetaddress; + } + + public void setDeviceDescription(String value) { + description = value.trim(); + } + + public String getDeviceDescription() { + return description; + } + + + public void CloseCommunication() { + if (mysock!=null) { + mysock.disconnect(); + mysock.close(); + mysock = null; + } + } + + public boolean getUDPisRunning() { + return isrunning; + } + + public boolean getUDPisReady() { + if (mysock==null) return false; + return isinited; + } + + public qzastatus getCurrentStatus() { + return laststatus; + } + + /** + * Get QZAIP target IP address + * @return IP Address in string + */ + public String getTarget_IPAddress() { + return thisIP; + } + + /** + * Get QZAIP target UDP Port + * @return UDP Port + */ + public int getTarget_Port() { + return thisPort; + } + + /** + * Get Ethernet IP Address used in this class + * @return IP address in string + */ + public String getLocal_IPAddress() { + if (mysock==null) return ""; + if (!isinited) return ""; + return mysock.getLocalAddress().getHostAddress(); + } + + /** + * Get Ethernet UDP Port used in this class + * @return UDP Port + */ + public int getLocal_Port() { + if (mysock==null) return 0; + if (!isinited) return 0; + return mysock.getLocalPort(); + } + + /** + * Check if this class is initalized, especially UDP Socket + * @return true if initialized correctly + */ + public boolean getIsInitialized() { + return isinited; + } + + /** + * Check if QZAIP is online + * @return true if online + */ + public boolean getIsOnline() { + if (onlinecounter>0) return true; else return false; + } + + private void raise_log(String msg) { + if (need_log_event) { + ba.raiseEventFromDifferentThread(Me, null, 0, event+"_log", false, new Object[] {msg}); + + } + } + + private void raise_data_arrived(byte[] bb) { + if (need_dataarrived_event) { + ba.raiseEventFromDifferentThread(Me, null, 0, event+"_dataarrived", false, new Object[] {bb}); + + } + } + + private void raise_offline() { + if (need_offline_event) { + ba.raiseEventFromDifferentThread(Me, null, 0, event+"_offline", false, new Object[] {DateTime.getNow()}); + + } + } + + private void raise_online() { + if (need_online_event) { + ba.raiseEventFromDifferentThread(Me, null, 0, event+"_online", false, new Object[] {DateTime.getNow()}); + + } + } + private void raise_status(qzastatus value) { + if (value==null) return; + laststatus = value; + if (need_status_event) { + ba.raiseEventFromDifferentThread(Me, null, 0, event+"_status", false, new Object[] {value}); + + } + } + + private void raise_txrxstatus() { + if (need_txrxstatus_event) { + ba.raiseEventFromDifferentThread(Me, null, 0, event+"_txrxstatus", false, new Object[] {txcount, txfail, rxcount, rxfail}); + + } + } + + /** + * Tick internal counter, to verify online / offline status + */ + public void Tick() { + if (onlinecounter>0) { + onlinecounter-=1; + if (onlinecounter==0) raise_offline(); + } + } + + /** + * Get Status (command 99) + * will raise event "status" + * @return true if sent + */ + public boolean GetStatus() { + byte[] cmd = new byte[] {cmdheader, 99, cmdfooter}; + return SendCommand(cmd); + } + + /** + * Close Evac Mode + * @return true if sent + */ + public boolean CloseEvacMode() { + byte[] cmd = new byte[] {cmdheader, 16, cmdfooter}; + if (BA.debugMode) printcommand("CloseEvacMode",cmd); + return SendCommand( cmd); + } + + /** + * Open Evac Mode + * @param SourceIP : For Multicast streaming, give Multicast Address. For Unicast Streaming, give Sender address + * @param SourcePort : Port + * @return true if sent + */ + public boolean OpenEvacMode(String SourceIP, int SourcePort) { + byte[] MIP = GetIP(SourceIP); + byte[] MPORT = GetPort(SourcePort); + byte[] cmd = new byte[] { + cmdheader, 15, + MIP[0], MIP[1], MIP[2],MIP[3], + MPORT[1], MPORT[0], + cmdfooter + }; + if (BA.debugMode) printcommand("OpenEvacMode",cmd); + return SendCommand(cmd); + } + + /** + * Close Unicast Sending + * @return true if sent + */ + public boolean CloseUnicastSending() { + byte[] cmd = new byte[] {cmdheader, 18, cmdfooter}; + if (BA.debugMode) printcommand("CloseUnicastSending",cmd); + return SendCommand(cmd); + } + + /** + * Open Unicast Sending + * @param TargetIP : Address of receiver + * @param TargetPort : port of receiver + * @return true if sent + */ + public boolean OpenUnicastSending(String TargetIP, int TargetPort) { + byte[] UIP = GetIP(TargetIP); + byte[] UPORT = GetPort(TargetPort); + byte[] cmd = new byte[] { + cmdheader, 17, + UIP[0],UIP[1],UIP[2],UIP[3], + UPORT[1],UPORT[0], + cmdfooter + }; + + if (BA.debugMode) printcommand("OpenUnicastSending",cmd); + return SendCommand(cmd); + } + + /** + * Close BGM Mode + * @return true if sent + */ + public boolean CloseBGMMode() { + byte[] cmd = new byte[] {cmdheader, 22, cmdfooter}; + if (BA.debugMode) printcommand("CloseBGMMode",cmd); + return SendCommand(cmd); + } + + /** + * Close Paging Mode + * @return true if sent + */ + public boolean ClosePagingMode() { + byte[] cmd = new byte[] {cmdheader, 20, cmdfooter}; + if (BA.debugMode) printcommand("ClosePagingMode", cmd); + return SendCommand(cmd); + } + + /** + * Open Timer Mode with 2 Relays Status, exclusive for Raspberry Pi Receivers + * @param MulticastIP : for Multicast streaming, give Multicast address. For Unicast streaming, give Sender address. + * @param MulticastPort : Port + * @param InternalRelay : Internal Relay only support 4 relay, so value in binary is : xxxx.R4R3R2R1 + * @param ExternalRelay : External Relay support up to 64 relays, so External Relay Byte List contain integer index of relays (1 - 64) + * @return true if timer mode is opened + */ + public boolean OpenTimerMode_withZones(String MulticastIP, int MulticastPort, byte InternalRelay, List ExternalRelay) { + byte[] MIP = GetIP(MulticastIP); + byte[] MPORT = GetPort(MulticastPort); + byte R1 = (byte)(InternalRelay & 0x0F); + byte[] ExR = Relay_List_to_Bytes(ExternalRelay); + byte[] cmd = new byte[] { + cmdheader, 25, + MIP[0],MIP[1],MIP[2],MIP[3], + MPORT[1],MPORT[0], + R1, + ExR[0],ExR[1],ExR[2],ExR[3],ExR[4],ExR[5],ExR[6],ExR[7], + cmdfooter + }; + + if (BA.debugMode) printcommand("OpenTimerMode_withZones",cmd); + return SendCommand(cmd); + } + + /** + * Open BGM Mode with 2 Relays Status, exclusive for Raspberry Pi Receivers + * @param SourceIP : For Multicast streaming, give Multicast address. For unicast streaming, give Sender address + * @param SourcePort : UDP Port of Source Streaming + * @param InternalRelay : Internal Relay only support 4 relay, so value in binary is : xxxx.R4R3R2R1 + * @param ExternalRelay : External Relay support up to 64 relays, so External Relay Byte List contain integer index of relays (1 - 64) + * @return true if BGMMode opened + */ + public boolean OpenBGMMode_withZones(String SourceIP, int SourcePort, byte InternalRelay, List ExternalRelay) { + byte[] MIP = GetIP(SourceIP); + byte[] MPORT = GetPort(SourcePort); + byte R1 = (byte)(InternalRelay & 0x0F); + byte[] ExR = Relay_List_to_Bytes(ExternalRelay); + byte[] cmd = new byte[] { + cmdheader, 24, + MIP[0],MIP[1],MIP[2],MIP[3], + MPORT[1],MPORT[0], + R1, + ExR[0],ExR[1],ExR[2],ExR[3],ExR[4],ExR[5],ExR[6],ExR[7], + cmdfooter + }; + + if (BA.debugMode) printcommand("OpenBGMMode_withZones",cmd); + return SendCommand(cmd); + } + + /** + * Open Paging Mode with 2 Relays Status, exclusive for Raspberry Pi Receivers + * @param SourceIP : for Multicast Streaming, give Multicast Address. For Unicast streaming, give Unicast address + * @param SourcePort : UDP Port of Source Streaming + * @param InternalRelay : Internal Relay only support 4 relay, so value in binary is : xxxx.R4R3R2R1 + * @param ExternalRelay : External Relay support up to 64 relays, so External Relay Byte List contain integer index of relays (1 - 64) + * @return true if paging mode is opened + */ + public boolean OpenPagingMode_withZones(String SourceIP, int SourcePort, byte InternalRelay, List ExternalRelay) { + byte[] MIP = GetIP(SourceIP); + byte[] MPORT = GetPort(SourcePort); + byte R1 = (byte)(InternalRelay & 0x0F); + byte[] ExR = Relay_List_to_Bytes(ExternalRelay); + byte[] cmd = new byte[] { + cmdheader, 23, + MIP[0],MIP[1],MIP[2],MIP[3], + MPORT[1],MPORT[0], + R1, + ExR[0],ExR[1],ExR[2],ExR[3],ExR[4],ExR[5],ExR[6],ExR[7], + cmdfooter + }; + + if (BA.debugMode) printcommand("OpenPagingMode_withZones",cmd); + return SendCommand(cmd); + } + + /** + * Open Paging Mode + * @param SourceIP : For Multicast Streaming, give multicast address. For Unicast sending, give Sender address + * @param SourcePort : UDP Port of Stream Source + * @return true if sent + */ + public boolean OpenPagingMode(String SourceIP, int SourcePort) { + byte[] MIP = GetIP(SourceIP); + byte[] MPORT = GetPort(SourcePort); + byte[] cmd = new byte[] { + cmdheader, 19, + MIP[0],MIP[1],MIP[2],MIP[3], + MPORT[1],MPORT[0], + cmdfooter + }; + + if (BA.debugMode) printcommand("OpenPagingMode",cmd); + return SendCommand(cmd); + } + + /** + * Change Volume Output + * @param value : valid value 0 ~ 20 + * @return true if sent + */ + public boolean SetVolumeOutput(byte value) { + if (value<0) value = 0; + if (value > 20) value = 20; + byte[] cmd = new byte[] {cmdheader, 14, value, cmdfooter}; + if (BA.debugMode) printcommand("SetVolumeOutput",cmd); + return SendCommand(cmd); + } + + /** + * Change Volume Input + * @param value : valid value 0 ~ 15 + * @return true if sent + */ + public boolean SetVolumeInput(byte value) { + if (value<0) value = 0; + if (value>15) value = 15; + byte[] cmd = new byte[] {cmdheader, 13, value, cmdfooter}; + if (BA.debugMode) printcommand("SetVolumeInput",cmd); + return SendCommand(cmd); + } + + /** + * Close Timer Receiving + * @return true if sent + */ + public boolean CloseTimerMode() { + byte[] cmd = new byte[] {cmdheader, 12, cmdfooter}; + if (BA.debugMode) printcommand("CloseTimerMode",cmd); + return SendCommand(cmd); + } + + /** + * Open Timer Receiving + * @param SourceIP : For Multicast Streaming, give Multicast address. For Unicast Streaming, give Sender address + * @param SourcePort : Port + * @return true if sent + */ + public boolean OpenTimerMode(String SourceIP, int SourcePort) { + byte[] MIP = GetIP(SourceIP); + byte[] MPORT = GetPort(SourcePort); + byte[] cmd = new byte[] { + cmdheader, 11, + MIP[0],MIP[1],MIP[2],MIP[3], + MPORT[1],MPORT[0], + cmdfooter + }; + + if (BA.debugMode) printcommand("OpenTimerMode",cmd); + return SendCommand(cmd); + } + + /** + * Open External Audio Input Mode with 2 Relays Status, exclusive for Raspberry Pi Receivers + * @param SourceIP : for Multicast Streaming, give Multicast Address. For Unicast streaming, give Unicast address + * @param SourcePort : UDP Port of Source Streaming + * @param InternalRelay : Internal Relay only support 4 relay, so value in binary is : xxxx.R4R3R2R1 + * @param ExternalRelay : External Relay support up to 64 relays, so External Relay Byte List contain integer index of relays (1 - 64) + * @return true if paging mode is opened + */ + public boolean OpenExternalAudioInputMode_withZones(String SourceIP, int SourcePort, byte InternalRelay, List ExternalRelay) { + byte[] MIP = GetIP(SourceIP); + byte[] MPORT = GetPort(SourcePort); + byte R1 = (byte)(InternalRelay & 0x0F); + byte[] ExR = Relay_List_to_Bytes(ExternalRelay); + byte[] cmd = new byte[] { + cmdheader, 30, + MIP[0],MIP[1],MIP[2],MIP[3], + MPORT[1],MPORT[0], + R1, + ExR[0],ExR[1],ExR[2],ExR[3],ExR[4],ExR[5],ExR[6],ExR[7], + cmdfooter + }; + + if (BA.debugMode) printcommand("OpenExternalAudioInputMode_withZones",cmd); + return SendCommand(cmd); + } + + /** + * Open External Audio Input + * @param SourceIP : For Multicast Straming, give Multicast Address. For Unicast Streaming, give Sender address + * @param SourcePort : Port + * @return true if sent + */ + public boolean OpenExternalAudioInputMode(String SourceIP, int SourcePort) { + byte[] MIP = GetIP(SourceIP); + byte[] MPORT = GetPort(SourcePort); + byte[] cmd = new byte[] { + cmdheader, 28, + MIP[0],MIP[1],MIP[2],MIP[3], + MPORT[1],MPORT[0], + cmdfooter + }; + + if (BA.debugMode) printcommand("OpenExternalAudioInputMode",cmd); + return SendCommand(cmd); + } + + /** + * Close External Audio Input Receiving + * @return true if sent + */ + public boolean CloseExternalAudioInputMode() { + byte[] cmd = new byte[] {cmdheader, 29, cmdfooter}; + if (BA.debugMode) printcommand("CloseExternalAudioInputMode",cmd); + return SendCommand(cmd); + } + + /** + * Open BGM Mode + * @param MulticastIP : For Multicast Mode, give multicast address. For Unicast mode, give Sender address + * @param MulticastPort : Port + * @return true if sent + */ + public boolean OpenBGMMode(String SourceIP, int SourcePort) { + byte[] MIP = GetIP(SourceIP); + byte[] MPORT = GetPort(SourcePort); + byte[] cmd = new byte[] { + cmdheader, 21, + MIP[0],MIP[1],MIP[2],MIP[3], + MPORT[1],MPORT[0], + cmdfooter + }; + + if (BA.debugMode) printcommand("OpenBGMMode",cmd); + return SendCommand(cmd); + } + + private byte[] Relay_List_to_Bytes(List value) { + byte[] result = new byte[8]; + if (value!=null) { + if (value.IsInitialized()) { + int size = value.getSize(); + if (size>0) { + for(int ii=0;ii64) continue; + if (vv < 9) { + vv -= 1; + result[0] |= (1<>8); // MSB + return result; + } + + + private boolean SendCommand(byte[] bb) { + if (mysock==null) { + raise_log("SendCommand failed, UDP not ready"); + return false; + } + if (!isinited) { + raise_log("SendCommand failed, UDP not ready"); + return false; + } + if (targetsockaddress==null) { + raise_log("SendCommand failed, targetsockaddress is null"); + return false; + } + + // prepare for sending + DatagramPacket pkg = new DatagramPacket(bb,bb.length, targetsockaddress); + + boolean success = false; + // sending packet + try { + mysock.send(pkg); + if (txcount < Long.MAX_VALUE) txcount+=1; else txcount = 0; + success = true; + } catch (IOException e1) { + raise_log("SendCommand to "+targetinetaddress.getHostAddress()+" exception. Msg : "+e1.getMessage()+", Caused : "+e1.getCause()); + if (txfail < Long.MAX_VALUE) txfail+=1; else txfail = 0; + } + + raise_txrxstatus(); + return success; + } + + private void process_data(DatagramPacket pp) { + if (pp==null) return; + byte[] datanya = pp.getData(); + if (datanya==null) return; + if (datanya.length<1) return; + if (pp.getPort()<1) return; + if (pp.getAddress()==null) return; + + + // update onlinecounter + if (onlinecounter==0) { + // previously was offline + raise_online(); + } + onlinecounter = 30; + + // process incoming data + raise_data_arrived(datanya); + BA.Log("process data dari "+pp.getAddress().getHostAddress()+":"+pp.getPort()+", length="+pp.getLength()); + raise_status(new qzastatus(pp)); + } + + private String printcommand(String functionname, byte[] bb) { + if (bb==null) return ""; + if (bb.length==0) return ""; + StringBuilder str = new StringBuilder(); + str.append(functionname+" for "+thisIP+" : "); + for(byte xx:bb) { + str.append(Bit.ToHexString(xx & 0xFF)).append(" "); + } + return str.toString(); + } + +} diff --git a/src/qzaip/qzasender.java b/src/qzaip/qzasender.java new file mode 100644 index 0000000..21ebbbf --- /dev/null +++ b/src/qzaip/qzasender.java @@ -0,0 +1,124 @@ +package qzaip; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.SocketAddress; +import java.net.SocketException; + + +/** + * qzasender is a singleton UDP sender + * @author rdkartono + * + */ +public class qzasender { + private static qzasender singleinstance = null; + private DatagramSocket udp; + private boolean udpready = false; + + + private qzasender() { + + try { + udp = new DatagramSocket(); + udpready = true; + } catch (SocketException e) { + udpready = false; + } + } + + static qzasender getInstance() { + if (singleinstance==null) singleinstance = new qzasender(); + return singleinstance; + } + + boolean isready() { + if (udp==null) { + udpready = false; + } + return udpready; + } + + + + + void CloseUDP() { + udpready = false; + if (udp!=null) { + udp.close(); + udp = null; + } + } + + String getLocalIP() { + if (udp==null) return ""; + return udp.getLocalAddress().getHostAddress(); + } + + int getLocalPort() { + if (udp==null) return 0; + return udp.getLocalPort(); + } + + boolean SendData(qzasenderevent sender, SocketAddress target, byte[] bb) { + if (udp==null) { + udpready = false; + return false; + } + if (!udpready) return false; + + Thread tx = new Thread() { + public void run() { + // prepare for sending + DatagramPacket pkg = new DatagramPacket(bb,bb.length, target); + + // sending UDP packet + try { + if (udp!=null) udp.send(pkg); + if (sender!=null) sender.data_sent(target); + + } catch(IOException e) { + String msg = "N/A"; + String cause = "N/A"; + if (e!=null) { + msg = e.getMessage(); + } + if (e.getCause()!=null) { + cause = e.getCause().getMessage(); + } + if (sender!=null) sender.send_failed(target, msg, cause); + } + + // prepare for receiving + pkg = new DatagramPacket(new byte[64],64); + pkg.setData(new byte[64]); + + + try { + udp.setSoTimeout(2000); + udp.receive(pkg); + + if (sender!=null) { + sender.data_received(target); + sender.replydata(target,pkg); + } + + } catch (IOException e) { + String msg = "N/A"; + String cause = "N/A"; + if (e !=null) { + msg = e.getMessage(); + } + if (e.getCause()!=null) { + cause = e.getCause().getMessage(); + } + if (sender!=null) sender.receive_failed(target, msg, cause); + } + } + }; + tx.start(); + + return true; + } +} diff --git a/src/qzaip/qzasenderevent.java b/src/qzaip/qzasenderevent.java new file mode 100644 index 0000000..a7ad9b1 --- /dev/null +++ b/src/qzaip/qzasenderevent.java @@ -0,0 +1,12 @@ +package qzaip; + +import java.net.DatagramPacket; +import java.net.SocketAddress; + +public interface qzasenderevent { + void data_sent(SocketAddress dest); + void data_received(SocketAddress dest); + void receive_failed(SocketAddress dest, String msg, String cause); + void send_failed(SocketAddress dest, String msg , String cause); + void replydata(SocketAddress dest, DatagramPacket value); +} diff --git a/src/qzaip/qzastatus.java b/src/qzaip/qzastatus.java new file mode 100644 index 0000000..22a29ae --- /dev/null +++ b/src/qzaip/qzastatus.java @@ -0,0 +1,364 @@ +package qzaip; + +import java.net.DatagramPacket; + +import anywheresoftware.b4a.BA; +import anywheresoftware.b4a.keywords.Bit; +import anywheresoftware.b4a.objects.collections.List; + +@BA.ShortName("qzastatus") +public class qzastatus { + private final byte cmdheader = (byte)0xAA; + private final byte cmdfooter = (byte)0x55; + private final byte cmd99 = 99; + + private byte devtype; + private String receivefrom_ip; + private int receivefrom_port; + private String streamto_ip; + private int streamto_port; + private byte volin; + private byte volout; + private byte internalR1; + private byte internalR2; + private int rxbufsize; + + private String MAC; + private byte[] externalrelay; + private boolean hasexternalrelay = false; + private boolean hasMAC = false; + private boolean hasvalue = false; + private String senderip = ""; + private int senderport = 0; + + public qzastatus() { + hasMAC = false; + hasexternalrelay = false; + hasvalue = false; + senderip = ""; + senderport = 0; + devtype = 0; + receivefrom_ip = ""; + receivefrom_port = 0; + streamto_ip = ""; + streamto_port = 0; + volin = 0; + volout = 0; + internalR1 =0; + internalR2 = 0; + rxbufsize = 0; + MAC = ""; + externalrelay = new byte[8]; + } + + public qzastatus(DatagramPacket pp) { + this(); + senderip = pp.getAddress().getHostAddress(); + senderport = pp.getPort(); + byte[] bb = pp.getData(); + if (bb==null) return; + if (bb.length<22) return; + if (bb[0]!=cmdheader) return; + if (bb[1]!=cmd99) return; + if (bb[21]!=cmdfooter) return; + this.devtype = bb[2]; + this.receivefrom_ip = Byte_to_IP(bb[3],bb[4],bb[5],bb[6]); + BA.Log("receivingfrom_ip="+receivefrom_ip); + this.receivefrom_port = Byte_to_Short(bb[8],bb[7]); + BA.Log("receivingfrom_port="+receivefrom_port); + this.streamto_ip = Byte_to_IP(bb[9],bb[10],bb[11],bb[12]); + this.streamto_port = Byte_to_Short(bb[14],bb[13]); + + this.volin = bb[15]; + this.volout = bb[16]; + this.internalR1 = bb[17]; + this.internalR2 = bb[18]; + this.rxbufsize = Byte_to_Short(bb[20],bb[19]); + + hasvalue = true; + + if (bb.length>27) { + // ada MAC nya + this.MAC = Byte_to_MAC(bb[22],bb[23],bb[24],bb[25],bb[26],bb[27]); + hasMAC = true; + } + if (bb.length>35) { + // ada external relay nya , 8 byte + for(int ii=0;ii<8;ii++) { + this.externalrelay[ii] = bb[28+ii]; + } + if (devtype==5) hasexternalrelay = true; + } + } + + /** + * Check if contain MAC Address information + * @return true if contain MAC address + */ + public boolean getHas_MAC_Address() { + return hasMAC; + } + + public String getStatusFrom_IPAddress() { + return senderip; + } + + public int getStatusFrom_Port() { + return senderport; + } + + /** + * Check if it has External Relays + * @return true if have external relays + */ + public boolean getHas_ExternalRelays() { + return hasexternalrelay; + } + + /** + * Check if it has Values + * @return true if has values + */ + public boolean getHas_Values() { + return hasvalue; + } + + /** + * Device Type : + * 1 = Instreamer + * 2 = Extreamer + * 3 = Annuncicom + * 4 = Evac Unit + * 5 = Receiver Unit + * @return 1 - 5 + */ + public int getDeviceType() { + return this.devtype & 0xFF; + } + + /** + * Check Receiving From status + * @return empty string if IDLE + */ + public String getReceivingFrom() { + if (receivefrom_port<1) + return ""; + else if (receivefrom_ip=="0.0.0.0") + return ""; + else + return receivefrom_ip+":"+receivefrom_port; + } + + /** + * Get Receiving From IP Address + * @return 0.0.0.0 on IDLE + */ + public String getReceivingFrom_IPAddress() { + return receivefrom_ip; + } + + /** + * Get Receiving From UDP Port + * @return 0 on IDLE + */ + public int getReceivingFrom_Port() { + return receivefrom_port; + } + + /** + * Get Streaming To IP Address + * @return 0.0.0.0 on IDLE + */ + public String getStreamingTo_IPAddress() { + + return streamto_ip; + } + + /** + * Get Streaming To UDP Port + * @return 0 on IDLE + */ + public int getStreamingTo_Port() { + return streamto_port; + } + + /** + * Get Streaming To status + * @return empty string on IDLE + */ + public String getStreamingTo() { + if (streamto_port<1) + return ""; + else if (streamto_ip=="0.0.0.0") + return ""; + else return streamto_ip+":"+streamto_port; + } + + /** + * Get Volume Input value + * @return Volume Input in integer + */ + public int getVolume_Input() { + return volin & 0xFF; + } + + /** + * Get Volume Output value + * @return Volume Output in integer + */ + public int getVolume_Output() { + return volout & 0xFF; + } + + /** + * Get RX Buffer Size + * @return RX Buffer in integer + */ + public int getRXBuffer() { + return rxbufsize & 0xFFFF; + } + + /** + * Get MAC address + * @return empty string if MAC is not available + */ + public String getMAC_Address() { + return MAC; + } + + /** + * Check Internal Relay 1 status + * @return true if ON + */ + public boolean getInternal_R1_ON() { + switch(devtype) { + case 1 : // instreamer, dont have R1 + case 2 : // extreamer , dont have R1 + case 4 : // EVAC unit, dont have R1 + return false; + case 3 : // annuncicom, have R1 + if (internalR1==0) return false; else return true; + case 5 : // Receiver Unit, have R1 , at bit 0 + if ((internalR1 & 0x1)==0) return false; else return true; + default : + return false; + } + } + + /** + * Check Internal Relay 2 status + * @return true if ON + */ + public boolean getInternal_R2_ON() { + switch(devtype) { + case 3 : // annuncicom , have R2 + if (internalR2==0) return false; else return true; + case 5 : // receiver unit, have R2 , at bit 1 + if ((internalR1 & 0x2)==0) return false; else return true; + default : + return false; + } + } + + /** + * Only Receiver Unit (Device Type = 5) has internal R3 and R4 + * @return true if have internal R3 and R4 + */ + public boolean getHas_Internal_R3R4 () { + if (devtype==5) return true; else return false; + } + + /** + * Check Internal Relay 3 status + * @return true if ON + */ + public boolean getInternal_R3_ON() { + if (getHas_Internal_R3R4()) { + // only receiver unit have internal R3 + if ((internalR1 & 0x4)==0) return false; else return true; + } else return false; + } + + /** + * Check Internal Relay 4 status + * @return true if ON + */ + public boolean getInternal_R4_ON() { + if (getHas_Internal_R3R4()) { + // only receiver unit have internal R4 + if ((internalR1 & 0x8)==0) return false; else return true; + } else return false; + } + + /** + * Check if there are external relays ON + * @return List of relays number that is ON, or empty list if all relays are OFF + */ + public List GetExternalRelay_ON() { + List result = new List(); + result.Initialize(); + if (hasexternalrelay) { + for(int ii=0;ii<8;ii++) { + for(int jj=0;jj<8;jj++) { + int val = (ii * 8) + jj; + val += 1; // relay value is 1 - 64 + if ((externalrelay[ii] & (1<> 8) & 0xFF; + int B3 = (value >> 16) & 0xFF; + int B4 = (value >> 24) & 0xFF; + raf.writeByte(B1); + raf.writeByte(B2); + raf.writeByte(B3); + raf.writeByte(B4); + } + + private void RafWriteShort_LE(RandomAccessFile raf, int value) throws IOException { + if (raf==null) return; + int B1 = value & 0xFF; + int B2 = (value >> 8) & 0xFF; + raf.writeByte(B1); + raf.writeByte(B2); + } + + 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_writewavcomplete_event(boolean success) { + if (need_writewavcomplete_event) ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_writewavcomplete", false, new Object[] {success, DateTime.getNow()}); + } +} diff --git a/src/qzaipv2/FilePlaybackInformation.java b/src/qzaipv2/FilePlaybackInformation.java new file mode 100644 index 0000000..9b007f4 --- /dev/null +++ b/src/qzaipv2/FilePlaybackInformation.java @@ -0,0 +1,73 @@ +package qzaipv2; + +import anywheresoftware.b4a.BA; +import anywheresoftware.b4a.keywords.DateTime; + +import java.io.File; +@BA.ShortName("FilePlaybackInformation") +public class FilePlaybackInformation { + final String filename; + private final File xx; + private double duration = 0; + private long startplaybacktick=-1; + + /** + * Create FilePlaybackInformation class + * @param filename complete path of file + */ + public FilePlaybackInformation(String filename) { + this.filename = filename; + this.xx = (filename!=null && filename.length()>0) ? new File(filename) : null; + } + + /** + * Check if filename is exists + * @return true if exists + */ + public boolean IsExists() { + if (xx!=null) { + if (xx.isFile()) { + return true; + } + } + return false; + } + + /** + * Check filesize + * @return 0 if not exists + */ + public long FileSize() { + if (IsExists()) { + return xx.length(); + } + return 0; + } + + @BA.Hide + public void setDuration(double duration) { + this.duration = duration; + } + + @BA.Hide + public void SetStartPlaybackTickToNow() { + startplaybacktick = DateTime.getNow(); + } + + @BA.Hide + public int GetPlaybackDurationToNow() { + if (startplaybacktick>0) { + return (int)((DateTime.getNow()-startplaybacktick)/1000); + } + return 0; + } + + /** + * Set duration of playback for filename, in seconds + * @return 0 if invalid + */ + public double getDuration() { + return this.duration; + } + +} diff --git a/src/qzaipv2/HandleInformation.java b/src/qzaipv2/HandleInformation.java new file mode 100644 index 0000000..8249741 --- /dev/null +++ b/src/qzaipv2/HandleInformation.java @@ -0,0 +1,67 @@ +package qzaipv2; + +import java.util.concurrent.atomic.AtomicBoolean; + +import org.apache.commons.io.input.buffer.CircularByteBuffer; + +import com.sun.jna.Pointer; +import com.sun.jna.Structure; +import com.sun.jna.Structure.FieldOrder; + +import anywheresoftware.b4a.BA; +@BA.ShortName("HandleInformation") + +@FieldOrder(value = { + "handle","HandleOpenMode","pcmsize", + "wavsavesize","wavfilename","Status", + "openedfile","UDPListenPort","SendUDP", + "streamingbuffer","socket_opened" + }) +public class HandleInformation extends Structure { + public int handle=-1; + public String HandleOpenMode; + public int pcmsize=0; + public int wavsavesize = 0; + public String wavfilename; + public String Status; + public FilePlaybackInformation openedfile; + public int UDPListenPort=0; + public UDPInformation SendUDP; + public CircularByteBuffer streamingbuffer; + public AtomicBoolean socket_opened; + + //TODO autowrite dan autoread mesti di override + @BA.Hide + @Override + public void autoWrite() { + super.autoWrite(); + } + + @BA.Hide + @Override + public void autoRead() { + super.autoRead(); + } + + + /** + * Initialize HandleInformation + */ + public HandleInformation() { + + } + + /** + * Initialize HandleInformation + * @param handle + */ + public HandleInformation(int handle) { + this.handle = handle; + } + + public HandleInformation(Pointer p) { + super(p); + } + + +} diff --git a/src/qzaipv2/UDPInformation.java b/src/qzaipv2/UDPInformation.java new file mode 100644 index 0000000..30ffe24 --- /dev/null +++ b/src/qzaipv2/UDPInformation.java @@ -0,0 +1,162 @@ +package qzaipv2; + +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.util.List; + +import com.sun.jna.Structure; +import com.sun.jna.Structure.FieldOrder; + +import anywheresoftware.b4a.BA; + +import java.util.ArrayList; +import java.io.IOException; +import java.net.DatagramPacket; + +@FieldOrder(value = {"localport","localip"}) +public class UDPInformation extends Structure{ + private DatagramSocket sock; + public int localport; + public String localip; + private InetSocketAddress[] target; + + public UDPInformation() { + + } + + //TODO autoWrite dan autoRead mesti di override + @BA.Hide + public void autoWrite() { + super.autoWrite(); + } + + @BA.Hide + @Override + public void autoRead() { + super.autoRead(); + } + + /** + * Create new UDP Information + * @param localport Local Port for UDP sender + * @param localip Local IP Address for UDP Sender + * @throws SocketException + * @throws UnknownHostException + */ + public UDPInformation(int localport, String localip) throws SocketException, UnknownHostException { + this(); + this.localip = localip; + this.localport = localport; + sock = new DatagramSocket(localport, InetAddress.getByName(localip)); + } + + public UDPInformation(InetSocketAddress value) throws SocketException { + this(); + sock = new DatagramSocket(value); + this.localip = value.getHostString(); + this.localport = value.getPort(); + } + + /** + * If UDP Socket ready AND SendTarget available, return true + * @return return false if incomplete + */ + public boolean socketready() { + if (sock!=null) { + if (target!=null && target.length>0) { + return true; + } + } + return false; + } + + /** + * Close Socket + */ + public void close() { + if (sock!=null) { + sock.close(); + sock = null; + } + if (target!=null) { + target = null; + } + } + + /** + * Set Send Target + * @param targetip list of IP Address for target + * @param targetport target port + * @return how many destinations registered, or 0 if failed + */ + public int SetSendTarget(String[] targetip, int targetport) { + target = null; + if (targetip!=null && targetip.length>0) { + List ll = new ArrayList(); + + for(String tip : targetip) { + try { + InetAddress tipx = InetAddress.getByName(tip); + InetSocketAddress sock = new InetSocketAddress(tipx, targetport); + ll.add(sock); + } catch (UnknownHostException e) { + System.out.println("Unable to get InetAddress for "+tip); + } + + } + target = ll.toArray(new InetSocketAddress[0]); + System.out.println("SendTarget : "+target.length+" destinations"); + return target.length; + } + return 0; + } + + public int SetSendTarget(InetSocketAddress[] remote) { + target = null; + if (remote!=null && remote.length>0) { + target = remote; + return target.length; + } + return 0; + } + + /** + * Send these data to SendTarget + * @param data data to send + * @param bytespersend how many data per packet, max 1500 bytes + * @return bytes sent + */ + public int Send(byte[] data, int bytespersend) { + int counter = 0; + if (socketready()) { + if (target!=null && target.length>0) { + if (data!=null && data.length>0) { + if (bytespersend<1) bytespersend = 1500; + if (bytespersend > 1500) bytespersend = 1500; + ByteBuffer bb = ByteBuffer.wrap(data); + while(bb.hasRemaining()) { + byte[] xx = new byte[bb.remaining() > bytespersend ? bytespersend : bb.remaining()]; + bb.get(xx); + + for(InetSocketAddress sa : target) { + DatagramPacket pkg = new DatagramPacket(xx, xx.length, sa); + try { + sock.send(pkg); + } catch (IOException e) { + System.out.println("UDPInformation send failed on "+sa.getHostString()+":"+sa.getPort()+", exception = "+e.getMessage()); + } + } + + counter+=xx.length; + } + } else System.out.println("UDPInformation failed, data is invalid"); + } else System.out.println("UDPInformation failed, SendTarget is invalid"); + } else System.out.println("UDPInformation Send failed, Socket not ready"); + return counter; + } + +} \ No newline at end of file diff --git a/src/qzaipv2/qzaipv2.java b/src/qzaipv2/qzaipv2.java new file mode 100644 index 0000000..f791a7c --- /dev/null +++ b/src/qzaipv2/qzaipv2.java @@ -0,0 +1,1226 @@ +package qzaipv2; + +import java.util.List; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.ShortBuffer; +import java.util.ArrayList; +import java.util.Arrays; + +import com.sun.jna.Memory; +import com.sun.jna.Pointer; +import com.sun.jna.Structure; +import com.sun.jna.ptr.FloatByReference; +import com.un4seen.bass.BASS; +import com.un4seen.bass.BASS.*; +import com.un4seen.bass.BASSenc; +import com.un4seen.bass.BASSenc_MP3; +import com.un4seen.bass.BASSmix; + +import anywheresoftware.b4a.BA; +import anywheresoftware.b4a.keywords.Bit; +import anywheresoftware.b4a.keywords.DateTime; +import jbass.Bass_DeviceInfo; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.HashMap; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.io.IOException; +import java.net.DatagramPacket; + +import org.apache.commons.io.input.buffer.CircularByteBuffer; +import java.net.InetSocketAddress; +import java.net.MulticastSocket; + +@BA.ShortName("qzaipv2") +@BA.Events(values= { + "log(msg as string)", + "handlebuffer(handle as int, bb() as byte)", + "handlevu(handle as int, value as short)", + "savetowavprogress(handle as int, filename as string, pcmsize as long)", + "udpreceive(localnetwork as string, remotenetwork as string, bb() as byte, length as int)", + "openudpreceiving(success as boolean, localnetwork as string, handle as int)", + "sendhandleprogress(handle as int, status as string, totalsent as int)", + "starthandleprogress(handle as int, status as string)", + "openmulticastreceiving(success as boolean, localnetwork as string, target as string, handle as int)", + "multicastreceive(localnetwork as string, remotenetwork as string, bb() as byte, length as int)", + "combinetowav(success as boolean, targetfilename as string, sourcefilename() as string)", + "combinetomp3(success as boolean, targetfilename as string, sourcefilename() as string)" +}) +public class qzaipv2 { + + private BA ba; + private String event; + private Object Me; + private boolean inited = false; + private boolean need_log_event = false; + private boolean need_handlebuffer_event = false; + private boolean need_handlevu_event = false; + private boolean need_savetowavprogress_event = false; + private boolean need_udpreceive_event = false; + private boolean need_openudpreceiving_event = false; + private boolean need_sendhandleprogress_event = false; + private boolean need_starthandleprogress_event =false; + private boolean need_openmulticastreceiving_event =false; + private boolean need_multicastreceive_event = false; + private boolean need_combinetowav_event = false; + private boolean need_combinetomp3_event = false; + + private int playback_devid = -1; + private int recorder_devid = -1; + + private Map handlemap = new HashMap(); + private qzaipv2event javaevent; + + public qzaipv2() { + Runtime.getRuntime().addShutdownHook(new Thread(()->{ + raise_log("ShutdownHook for qzaipv2 started"); + if (handlemap!=null && handlemap.size()>0) { + handlemap.forEach((handle, hi)->{ + BASS.BASS_ChannelFree(handle); + }); + handlemap.clear(); + } + if (playback_devid!=-1) { + BASS.BASS_Free(); + } + if (recorder_devid!=-1) { + BASS.BASS_RecordFree(); + } + })); + } + + /** + * Initialize qzaipv2 + * @param eventname + */ + public void Initialize(BA ba, String eventname) { + Me = this; + this.ba = ba; + this.event = eventname; + handlemap.clear(); + check_events(); + check_bass(); + + } + + @BA.Hide + public void SetJavaEvent(qzaipv2event event) { + javaevent = event; + } + + @BA.Hide + public void check_bass() { + inited = false; + + boolean bass_ok = false; + if (BASS.BASS_GetVersion()!=0) { + System.out.println("Bass Version = "+Bit.ToHexString(BASS.BASS_GetVersion())); + bass_ok = true; + } + + boolean bassmix_ok = false; + if (BASSmix.BASS_Mixer_GetVersion()!=0) { + System.out.println("BASSMixer Version = "+Bit.ToHexString(BASSmix.BASS_Mixer_GetVersion())); + bassmix_ok = true; + } + + boolean bassencode_ok = false; + if (BASSenc.BASS_Encode_GetVersion()!=0) { + System.out.println("BASSenc Version = "+Bit.ToHexString(BASSenc.BASS_Encode_GetVersion())); + bassencode_ok = true; + } + + boolean bassencodemp3_ok = false; + if (BASSenc_MP3.BASS_Encode_MP3_GetVersion()!=0) { + System.out.println("BASSenc_MP3 Version = "+Bit.ToHexString(BASSenc_MP3.BASS_Encode_MP3_GetVersion())); + bassencodemp3_ok = true; + + } + + inited = bass_ok && bassmix_ok && bassencode_ok && bassencodemp3_ok; + } + + /** + * Check if qzaipv2 is initialized + * @return true if inited + */ + public boolean IsInitialized() { + return inited; + } + + /** + * Get all playback devices + * @return array of Bass_DeviceInfo + */ + public Bass_DeviceInfo[] GetDeviceInfos() { + if (inited) { + List result = new ArrayList(); + int devid = 0; + while(true) { + BASS_DEVICEINFO dev = new BASS_DEVICEINFO(); + if (BASS.BASS_GetDeviceInfo(devid, dev)) { + + Bass_DeviceInfo xx = new Bass_DeviceInfo(devid, dev); + result.add(xx); + devid+=1; + } else break; + } + return result.toArray(new Bass_DeviceInfo[0]); + } else raise_log("GetDeviceInfos failed, BASS not loaded"); + return new Bass_DeviceInfo[0]; + } + + /** + * Get all recorder devices + * @return array of Bass_DeviceInfo + */ + public Bass_DeviceInfo[] GetRecordDeviceInfos() { + if (inited) { + List result = new ArrayList(); + int devid = 0; + while(true) { + BASS_DEVICEINFO dev = new BASS_DEVICEINFO(); + if (BASS.BASS_RecordGetDeviceInfo(devid, dev)) { + Bass_DeviceInfo xx = new Bass_DeviceInfo(devid, dev); + result.add(xx); + devid+=1; + } else break; + } + return result.toArray(new Bass_DeviceInfo[0]); + } else raise_log("GetRecordDeviceInfos failed, BASS not loaded"); + return new Bass_DeviceInfo[0]; + } + + /** + * Initialize Playback Output Device + * Output will be initialized with 48Khz Samplingrate, Mono, 16 bit + * @param deviceid 0 = no sound , 1 = first device, 2 = second device.. + * @return true if can be initalized + */ + public boolean Init_BASS_Output(int deviceid) { + if (inited) { + int flags = bassconstant.BASS_DEVICE_REINIT | bassconstant.BASS_DEVICE_MONO | bassconstant.BASS_DEVICE_16BITS; + + if (BASS.BASS_Init(deviceid, 48000, flags)) { + raise_log("BASS_Output inited at deviceid = "+deviceid); + playback_devid = deviceid; + return true; + } else raise_bass_error("Init_BASS_Output","BASS_Init"); + } else raise_log("Init_BASS_Output failed, BASS not loaded"); + return false; + } + + /** + * Initialize Recording Input device + * @param deviceid 0 = first device, 1 = second device .... + * @return true if can be initialized + */ + public boolean Init_BASS_Input(int deviceid) { + if (inited) { + if (BASS.BASS_RecordInit(deviceid)) { + raise_log("BASS_Input inited at deviceid = "+deviceid); + recorder_devid = deviceid; + return true; + } else { + int errcode = BASS.BASS_ErrorGetCode(); + if (errcode==bassconstant.BASS_ERROR_ALREADY) { + raise_log("BASS_Input deviceid = "+deviceid+" already initialized"); + recorder_devid = deviceid; + return true; + } else raise_bass_error("Init_BASS_Input","BASS_RecordInit"); + } + } else raise_log("Init_BASS_Input failed, BASS not loaded"); + return false; + } + + /** + * Open Recorder from Input + * Recording will be paused at first, start with function StartHandle + * Recording will be mono 16 bit + * @param samplingrate record samplingrate + * @return record handle, or 0 if failed + */ + public int OpenRecorder(final int samplingrate) { + if (recorder_devid>0) { + BASS.BASS_RecordSetDevice(recorder_devid); + int flag = bassconstant.BASS_RECORD_PAUSE; + HandleInformation hi = new HandleInformation(); + int handle = BASS.BASS_RecordStart(samplingrate, 1, flag, null, null); + + if (handle!=0) { + hi.handle = handle; + hi.HandleOpenMode = "RECORDER"; + hi.Status = "Opened"; + hi.write(); // write to native + handlemap.put(handle, hi); + return handle; + } else raise_bass_error("OpenRecorder","BASS_RecordStart"); + } raise_log("Call Init_BASS_Input first"); + return -1; + } + + /** + * Save current handle to WAV file + * @param handle valid handle + * @param targetfilename valid WAV filename + * @return encoder handle, or 0 if failed + */ + public int SavetoWAVFile(final int handle, final String targetfilename) { + final HandleInformation hi = handlemap.get(handle); + if (hi!=null) { + int flag = BASSenc.BASS_ENCODE_PCM | BASSenc.BASS_ENCODE_AUTOFREE; + + int encoderhandle = BASSenc.BASS_Encode_Start(handle, targetfilename, flag, new BASSenc.ENCODEPROC() { + + @Override + public void ENCODEPROC(int handle, int channel, Pointer buffer, int length, Pointer user) { + hi.wavsavesize+=length; + raise_savetowavprogress(handle, targetfilename, buffer.getByteArray(0, length), hi.wavsavesize); + } + }, null); + if (encoderhandle!=0) { + return encoderhandle; + } else raise_bass_error("RecordStart_and_toFile","BASS_Encode_Start"); + } + return 0; + } + + + + + /** + * Start Playback or Record + * @param starthandle handle to start + * @return true if can be started + */ + public boolean StartHandle(int starthandle) { + HandleInformation hi = handlemap.get(starthandle); + if (hi!=null) { + if (BASS.BASS_ChannelStart(starthandle)) { + hi.Status = "Started"; + raise_starthandleprogress(starthandle, "Started"); + hi.write(); // panggil ini , supaya write ke native memory + + int dsphandle = BASS.BASS_ChannelSetDSP(starthandle, starthandle_dspproc, hi.getPointer(), 1); + + if (dsphandle==0) raise_bass_error("StartHandle","BASS_ChannelSetDSP"); + + int synchandle1 = BASS.BASS_ChannelSetSync(starthandle, bassconstant.BASS_SYNC_DEV_FAIL, 0,starthandle_sync_dev_fail , hi.getPointer()); + if (synchandle1==0) raise_bass_error("StartHandle","BASS_ChannelSetSync BASS_SYNC_DEV_FAIL" ); + + int synchandle2 = BASS.BASS_ChannelSetSync(starthandle, bassconstant.BASS_SYNC_END, 0, starthandle_sync_end, hi.getPointer()); + if (synchandle2==0) raise_bass_error("StartHandle","BASS_ChannelSetSync BASS_SYNC_END"); + + int synchandle3 = BASS.BASS_ChannelSetSync(starthandle, bassconstant.BASS_SYNC_FREE, 0, starthandle_sync_free, hi.getPointer()); + if (synchandle3==0) raise_bass_error("StartHandle","BASS_ChannelSetSync BASS_SYNC_FREE"); + + boolean all_started = (dsphandle!=0) && (synchandle1!=0) && (synchandle2!=0) && (synchandle3!=0); + + if (all_started) { + if (hi.openedfile!=null) { + hi.openedfile.SetStartPlaybackTickToNow(); + } + } + + return all_started; + } else raise_bass_error("StartHandle","BASS_ChannelStart"); + } else raise_log("StartHandle failed, invalid handle"); + return false; + } + + /** + * Get HandleInformation object from a handle + * @param handle handle to get + * @return null if not exists + */ + public HandleInformation GetHandleInformation(int handle) { + return handlemap.get(handle); + } + + /** + * Stop Playback or Record + * Will also freeing resource. + * Use PauseHandle if you want to resume + * @param stophandle handle to stop + * @return true if can be stopped + */ + public boolean StopHandle(int stophandle) { + HandleInformation hi = handlemap.get(stophandle); + if (hi!=null) { + if (BASS.BASS_ChannelFree(stophandle)) { + handlemap.remove(stophandle); + return true; + } else raise_bass_error("StopHandle","BASS_ChannelFree"); + }else raise_log("StopHandle failed, invalid handle"); + return false; + + } + + /** + * Pause Playback or Record + * @param pausehandle handle to pause + * @return true if can be paused + */ + public boolean PauseHandle(int pausehandle){ + HandleInformation hi = handlemap.get(pausehandle); + if (hi!=null) { + if (BASS.BASS_ChannelPause(pausehandle)) { + hi.Status = "Paused"; + return true; + } else raise_bass_error("PauseHandle","BASS_ChannelPause"); + } else raise_log("PauseHandle failed, invalid handle"); + return false; + } + + /** + * Set Master Playback Volume + * @param value 0 - 100 + * @return true if can be set + */ + public boolean SetMasterPlaybackVolume(int value) { + if (value<0) value = 0; + if (value>100) value =100; + if (BASS.BASS_SetVolume(value / 100.0f)) { + return true; + } else raise_bass_error("SetMasterPlaybackVolume","BASS_SetVolume"); + return false; + } + + /** + * Get Master Playback Volume + * @return 0 - 100 + */ + public int GetMasterPlaybackVolume() { + float fv = BASS.BASS_GetVolume(); + if (fv>=0.0f) { + return (int)(fv*100); + } else raise_bass_error("GetMasterPlaybackVolume","BASS_GetVolume"); + return 0; + + } + + /** + * Set Volume for handle + * @param handle handle to change + * @param value 0 - 100 + * @return true if volume can be set + */ + public boolean SetHandleVolume(int handle, int value) { + if (value < 0) value = 0; + if (value > 100) value = 100; + float fv = value / 100.0f ; + HandleInformation hi = handlemap.get(handle); + if (hi!=null) { + if (BASS.BASS_ChannelSetAttribute(handle, bassconstant.BASS_ATTRIB_VOLDSP, fv)) { + return true; + } else raise_bass_error("SetHandleVolume","BASS_ChannelSetAttribute"); + } else raise_log("SetHandleVolume failed, handle invalid"); + return false; + } + + /** + * Get Volume for handle + * @param handle handle to get + * @return 0 - 100 + */ + public int GetHandleVolume(int handle) { + FloatByReference fv = new FloatByReference(); + HandleInformation hi = handlemap.get(handle); + if (hi!=null) { + if (BASS.BASS_ChannelGetAttribute(handle, bassconstant.BASS_ATTRIB_VOLDSP , fv)) { + return (int) (fv.getValue()*100); + } else raise_bass_error("GetHandleVolume","BASS_ChannelGetAttribute"); + + } else raise_log("GetHandleVolume failed, handle invalid"); + return 0; + } + + /** + * Open File + * Will be played / decoded as mono + * @param filename file to open + * @return file handle, or 0 if failed + */ + public int OpenFile(String filename) { + if (playback_devid>0) { + BASS.BASS_SetDevice(playback_devid); + int flags = bassconstant.BASS_SAMPLE_MONO; + int handle = BASS.BASS_StreamCreateFile(false, filename, 0, 0, flags); + if (handle!=0) { + HandleInformation hi = new HandleInformation(handle); + hi.HandleOpenMode = "FILE"; + hi.openedfile = new FilePlaybackInformation(filename); + long handle_length = BASS.BASS_ChannelGetLength(handle, bassconstant.BASS_POS_BYTE); + if (handle_length>0) { + double duration = BASS.BASS_ChannelBytes2Seconds(handle, handle_length); + if (duration>0) hi.openedfile.setDuration(duration); + } + + + hi.Status = "Opened"; + hi.write(); // update to native + handlemap.put(handle, hi); + + return handle; + } else raise_bass_error("OpenFile","BASS_StreamCreateFile"); + } else raise_log("OpenFile failed, call Init_BASS_Output first"); + return 0; + } + + public void OpenMulticastReceiving(final int listenport, String localip, String MulticastAddress, int targetport) { + if (!valid_ip(localip)) localip = "0.0.0.0"; + final InetSocketAddress local = CreateSocketAddress(localip, listenport); + if (local==null) { + raise_openmulticastreceiving(false, localip+":"+listenport,MulticastAddress+":"+targetport,0 ); + raise_log("OpenMulticastReceiving failed, localip or listenport invalid"); + return; + } + final InetSocketAddress target = CreateSocketAddress(MulticastAddress, targetport); + if (target==null) { + raise_openmulticastreceiving(false,localip+":"+listenport, MulticastAddress+":"+targetport, 0); + raise_log("OpenMulticastReceiving failed, MulticastAddress or targetport invalid"); + return; + } + + final HandleInformation hi = new HandleInformation(); + hi.streamingbuffer = new CircularByteBuffer(32 * 1024); + hi.socket_opened = new AtomicBoolean(false); + hi.HandleOpenMode = "MULTICAST_RECEIVE"; + hi.write(); // update to native + + //final CircularByteBuffer cbb = new CircularByteBuffer(32 * 1024); + //AtomicBoolean socket_opened = new AtomicBoolean(false); + + // UDP Thread + new Thread(()->{ + try (MulticastSocket socket = new MulticastSocket(local)) { + socket.joinGroup(target, null); + hi.socket_opened.set(true); + hi.write(); // update to native + //socket_opened.set(true); + raise_log("OpenMulticastReceiving opened at "+local.getHostString()+":"+local.getPort()+" and joining "+target.getHostString()+":"+target.getPort()); + while(true) { + if (socket.isClosed()) break; + DatagramPacket pkg = new DatagramPacket(new byte[1500],1500); + socket.receive(pkg); + String from = pkg.getAddress().getHostAddress()+":"+pkg.getPort(); + byte[] bb = Arrays.copyOf(pkg.getData(),pkg.getLength()); + synchronized(hi.streamingbuffer) { + hi.streamingbuffer.add(bb,0,bb.length); + } + raise_multicastreceive(local.getHostString()+":"+local.getPort(),from, bb, bb.length); + } + } catch (IOException |IllegalArgumentException | SecurityException e) { + raise_log("OpenMulticastReceiving failed, exception = "+e.getMessage()); + } finally { + raise_log("MulticastSocket "+local.getHostString()+":"+local.getPort()+" is closed"); + hi.socket_opened.set(false); + hi.write(); // update to native + //socket_opened.set(false); + } + + }).start(); + + // BASS thread + new Thread(()->{ + final int flags = bassconstant.BASS_SAMPLE_MONO; + + int handle = BASS.BASS_StreamCreateFileUser(bassconstant.STREAMFILE_NOBUFFER, flags, new BASS_FILEPROCS( + //FILECLOSEPROC + fc, + //FILELENPROC + fl, + //FILEREADPROC + fr, + //FILESEEKPROC + fs + ), hi.getPointer()); + raise_openmulticastreceiving(handle!=0, local.getHostString()+":"+local.getPort(),target.getHostString()+":"+target.getPort(), handle ); + if (handle==0) { + raise_bass_error("OpenMulticastReceiving","BASS_StreamCreateFileUser"); + } else { + hi.handle = handle; + + hi.Status = "Opened"; + hi.UDPListenPort = listenport; + hi.write(); // update to native + handlemap.put(handle, hi); + } + }).start(); + } + + /** + * Open UDP Receiving + * will raise event openudpreceiving(success as boolean, listenport as int, handle as int) + * @param listenport Listen Port + * @param localip local network address . If invalid, default to 0.0.0.0 + */ + public void OpenUDPReceiving(final int listenport, String localip) { + if (!valid_ip(localip)) localip = "0.0.0.0"; + final InetSocketAddress local = CreateSocketAddress(localip, listenport); + if (local==null) { + raise_openudpreceiving(false, localip+":"+listenport,0); + return; + } + final CircularByteBuffer cbb = new CircularByteBuffer(32 * 1024); + AtomicBoolean socket_opened = new AtomicBoolean(false); + + // UDP Thread + new Thread(()->{ + try(DatagramSocket socket = new DatagramSocket(local)) { + raise_log("UDP is listening at "+local.getHostString()+":"+local.getPort()); + socket_opened.set(true); + + while(true) { + if (socket.isClosed()) break; + DatagramPacket pkg = new DatagramPacket(new byte[1500], 1500); + socket.receive(pkg); + String from = pkg.getAddress().getHostAddress()+":"+pkg.getPort(); + byte[] bb = Arrays.copyOf(pkg.getData(),pkg.getLength()); + synchronized(cbb) { + cbb.add(bb,0,bb.length); + } + raise_udpreceive(local.getHostString()+":"+local.getPort(),from, bb, bb.length); + } + } catch (IOException e) { + raise_log("OpenUDPReceiving UDP Thread failed, Exception = "+e.getMessage()); + } finally { + raise_log("Socket "+local.getHostString()+":"+local.getPort()+" is closed"); + socket_opened.set(false); + + } + }).start(); + + // BASS thread + new Thread(()->{ + final int flags = bassconstant.BASS_SAMPLE_MONO; + + + int handle = BASS.BASS_StreamCreateFileUser(bassconstant.STREAMFILE_NOBUFFER, flags, new BASS_FILEPROCS( + //FILECLOSEPROC + fc, + //FILELENPROC + (user)->{ + int available = cbb.getCurrentNumberOfBytes(); + + raise_log("FILELENPROC called, returning "+available); + return available; + }, + //FILEREADPROC + (buffer, length, user)->{ + if (socket_opened.get()) { + synchronized(cbb) { + if (!cbb.hasBytes()) + try { + raise_log("FILEREADPROC wait at CircularByteBuffer"); + cbb.wait(); + } catch (InterruptedException e) { + raise_log("CircularByteBuffer wait exception = "+e.getMessage()); + + } + int cbbsize = cbb.getCurrentNumberOfBytes(); + if (cbbsize>0) { + int readsize = cbbsize <= length ? cbbsize : length; + byte[] bb = new byte[readsize]; + cbb.read(bb, 0, readsize); + buffer.write(0, bb, 0, readsize); + raise_log("FILEREADPROC called, returned "+readsize); + return readsize; + } + } + } + return 0; + }, + //FILESEEKPROC + fs + ), null); + raise_openudpreceiving(handle!=0, local.getHostString()+":"+local.getPort(), handle); + if (handle==0) { + raise_bass_error("OpenUDPReceiving","BASS_StreamCreateFileUser"); + } else { + HandleInformation hi = new HandleInformation(handle); + hi.HandleOpenMode = "UDP_RECEIVE"; + hi.Status = "Opened"; + hi.UDPListenPort = listenport; + hi.streamingbuffer = cbb; + hi.write(); // update to native + handlemap.put(handle, hi); + } + }).start(); + + } + + + /** + * Send handle using UDP to specified target ip and target port + * Data being send is PCM format + * will raise event sendhandleprogress(handle, status,totalsent); + * @param handle handle to send + * @param localport if want to use specific UDP local port. If 0, use random local port + * @param localip if want to use specific network adapter, give the ip here. if null, will use random network adapter + * @param targetport target port to send + * @param targetip list of target ip address + * @param maxbytesperpackaget maximum bytes per package, if invalid, default to 1500 bytes + * @return true if operation can be executed + */ + public boolean SendHandle_PCMToUDP(int handle, int localport, String localip, int targetport, String[] targetip, int maxbytesperpackaget) { + HandleInformation hi = handlemap.get(handle); + if (hi!=null) { + if (valid_port(localport)) { + if (valid_port(targetport)) { + if (targetip!=null && targetip.length>0) { + for(String tip : targetip) { + if (!valid_ip(tip)) { + raise_log("SendHandle_PCMToUDP failed, invalid targetip = "+tip); + return false; + } + } + + // sampe sini bisa semua + hi.SendUDP = Setup_UDPInformation(localport, localip, targetport, targetip); + + if (hi.SendUDP!=null) { + + new Thread(()->{ + raise_log("SendHandle_PCMToUDP started"); + int available; + int totalsent = 0; + raise_sendhandleprogress(handle, "started",totalsent); + + while(true) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + break; + } + + // cek ada data atau tidak + available = BASS.BASS_ChannelGetData(handle, null, bassconstant.BASS_DATA_AVAILABLE); + if (available>0) { + // ada data + Pointer pof = new Memory(available); + available = BASS.BASS_ChannelGetData(handle, pof, available); + if (available>0) { + // real data di Pointer pof + int sent = hi.SendUDP.Send(pof.getByteArray(0, available), maxbytesperpackaget); + if (sent>0) { + totalsent+=sent; + raise_sendhandleprogress(handle, "progress",totalsent); + } + } else if(available==-1) { + // ada error + raise_bass_error("SendHandle_PCMToUDP","BASS_ChannelGetData"); + break; + } + } else if (available==-1) { + // ada error + raise_bass_error("SendHandle_PCMToUDP","BASS_ChannelGetData"); + break; + } else continue; + + + } + raise_log("SendHandle_PCMToUDP finished"); + raise_sendhandleprogress(handle, "finished",totalsent); + + }).start(); + + return true; + } else raise_log("SendHandle_PCMToUDP failed, UDPInformation not created"); + } else raise_log("SendHandle_PCMToUDP failed, targetip array must valid"); + } else raise_log("SendHandle_PCMToUDP failed, targetport must between 1 - 65535"); + } else raise_log("SendHandle_PCMToUDP failed, localport must between 1 - 65535"); + } else raise_log("SendHandle_PCMToUDP failed, handle invalid"); + return false; + } + + /** + * Combine sourcefiles into new WAV file + * target file will always 16 bit mono + * will raise event combinetowav(success as boolean, targetfilename as string, sourcefiles() as string) + * @param targetfilename target filename + * @param targetsamplingrate target samplingrate + * @param sourcefiles array of source filenames + */ + public void CombineToWav(final String targetfilename, final int targetsamplingrate, final String[] sourcefiles) { + if (valid_string(targetfilename)) { + if (targetsamplingrate>0) { + if (sourcefiles!=null && sourcefiles.length>0) { + // opening handles for sourcefiles + boolean all_sources_opened = true; + List handles = new ArrayList<>(); + final int openflag = bassconstant.BASS_STREAM_DECODE | bassconstant.BASS_SAMPLE_MONO | bassconstant.BASS_STREAM_AUTOFREE; + for(String fn : sourcefiles) { + final int fh = BASS.BASS_StreamCreateFile(false, fn, 0, 0, openflag); + if (fh!=0) { + handles.add(fh); + } else { + all_sources_opened = false; + raise_log("CombineToWav failed on opening sourcefile = "+fn+", exception = "+BASS.GetBassErrorString()); + } + } + + if (all_sources_opened) { + // opening mixer + final int flags = bassconstant.BASS_STREAM_DECODE | BASSmix.BASS_MIXER_END; + final int mixerhandle = BASSmix.BASS_Mixer_StreamCreate(targetsamplingrate, 1, flags); + AtomicBoolean all_sources_added = new AtomicBoolean(true); + if (mixerhandle!=0) { + handles.forEach((fh)->{ + if (!BASSmix.BASS_Mixer_StreamAddChannel(mixerhandle, fh, bassconstant.BASS_STREAM_AUTOFREE)) { + all_sources_added.set(false); + raise_bass_error("CombineToWav","BASS_Mixer_StreamAddChannel"); + } + }); + + if (all_sources_added.get()) { + raise_log("Adding sourcefiles to mixer completed, now start combining files to WAV"); + final int encflag = BASSenc.BASS_ENCODE_PCM; + final int enchandle = BASSenc.BASS_Encode_Start(mixerhandle, targetfilename, encflag, null, null); + if (enchandle!=0) { + new Thread(()->{ + long total = 0; + int counter =0; + long tick = DateTime.getNow(); + raise_log("CombineToWav started to combine"); + while(true) { + try { + Thread.sleep(10); + Pointer pp = new Memory(32*1000); + counter = BASS.BASS_ChannelGetData(mixerhandle, pp, 4000); + if (counter<1) + break; // selesai + else + total+=counter; + } catch (InterruptedException e) { + break; + } + + } + int ttc = (int)((DateTime.getNow() - tick)/1000); + raise_log("CombineToWav "+targetfilename+" finished, size = "+total+" bytes, time to convert = "+ttc+" seconds"); + raise_combinetowav(true, targetfilename, sourcefiles); + BASS.BASS_ChannelFree(mixerhandle); + }).start(); + + return; + } else raise_bass_error("CombineToWav","BASS_Encode_Start"); + } else raise_log("CombineToWav canceled, because some source unable to add to mixer"); + raise_log("Free-ing mixer"); + BASS.BASS_ChannelFree(mixerhandle); + } else raise_bass_error("CombineToWav","BASS_Mixer_StreamCreate"); + } else raise_log("CombineToWav canceled because invalid sourcefile(s)"); + // Free-ing opened sourcefiles + raise_log("Free-ing all opened sourcefiles"); + handles.forEach((fh)->{ + BASS.BASS_ChannelFree(fh); + }); + } else raise_log("CombineToWav failed, sourcefiles is not available"); + } else raise_log("CombineToWav failed, targetsamplingrate is invalid"); + } else raise_log("CombineToWav failed, targetfilename is not available"); + + raise_combinetowav(false, targetfilename, sourcefiles); + } + + /** + * Combine sourcefiles into new MP3 file + * target file will always 16 bit mono + * will raise event combinetowav(success as boolean, targetfilename as string, sourcefiles() as string) + * @param targetfilename target filename + * @param targetsamplingrate target samplingrate + * @param sourcefiles array of source filenames + */ + public void CombineToMP3(final String targetfilename, final int targetsamplingrate, final String[] sourcefiles) { + if (valid_string(targetfilename)) { + if (targetsamplingrate>0) { + if (sourcefiles!=null && sourcefiles.length>0) { + // opening handles for sourcefiles + boolean all_sources_opened = true; + List handles = new ArrayList<>(); + final int openflag = bassconstant.BASS_STREAM_DECODE | bassconstant.BASS_SAMPLE_MONO | bassconstant.BASS_STREAM_AUTOFREE; + for(String fn : sourcefiles) { + final int fh = BASS.BASS_StreamCreateFile(false, fn, 0, 0, openflag); + if (fh!=0) { + handles.add(fh); + } else { + all_sources_opened = false; + raise_log("CombineToMP3 failed on opening sourcefile = "+fn+", exception = "+BASS.GetBassErrorString()); + } + } + + if (all_sources_opened) { + // opening mixer + final int flags = bassconstant.BASS_STREAM_DECODE | BASSmix.BASS_MIXER_END; + final int mixerhandle = BASSmix.BASS_Mixer_StreamCreate(targetsamplingrate, 1, flags); + AtomicBoolean all_sources_added = new AtomicBoolean(true); + if (mixerhandle!=0) { + handles.forEach((fh)->{ + if (!BASSmix.BASS_Mixer_StreamAddChannel(mixerhandle, fh, bassconstant.BASS_STREAM_AUTOFREE)) { + all_sources_added.set(false); + raise_bass_error("CombineToMP3","BASS_Mixer_StreamAddChannel"); + } + }); + + if (all_sources_added.get()) { + raise_log("Adding sourcefiles to mixer completed, now start combining files to MP3"); + final int encflag = 0; + final String encoptions = null; + final int enchandle = BASSenc_MP3.BASS_Encode_MP3_StartFile(mixerhandle, encoptions, encflag, targetfilename); + if (enchandle!=0) { + new Thread(()->{ + long total = 0; + int counter =0; + long tick = DateTime.getNow(); + raise_log("CombineToMP3 started to combine"); + while(true) { + try { + Thread.sleep(10); + Pointer pp = new Memory(32*1000); + counter = BASS.BASS_ChannelGetData(mixerhandle, pp, 4000); + if (counter<1) + break; // selesai + else + total+=counter; + } catch (InterruptedException e) { + break; + } + + } + int ttc = (int)((DateTime.getNow() - tick)/1000); + raise_log("CombineToMP3 "+targetfilename+" finished, size = "+total+" bytes, time to convert = "+ttc+" seconds"); + raise_combinetowav(true, targetfilename, sourcefiles); + BASS.BASS_ChannelFree(mixerhandle); + }).start(); + + return; + } else raise_bass_error("CombineToMP3","BASS_Encode_Start"); + } else raise_log("CombineToMP3 canceled, because some source unable to add to mixer"); + raise_log("Free-ing mixer"); + BASS.BASS_ChannelFree(mixerhandle); + } else raise_bass_error("CombineToMP3","BASS_Mixer_StreamCreate"); + } else raise_log("CombineToMP3 canceled because invalid sourcefile(s)"); + // Free-ing opened sourcefiles + raise_log("Free-ing all opened sourcefiles"); + handles.forEach((fh)->{ + BASS.BASS_ChannelFree(fh); + }); + } else raise_log("CombineToMP3 failed, sourcefiles is not available"); + } else raise_log("CombineToMP3 failed, targetsamplingrate is invalid"); + } else raise_log("CombineToMP3 failed, targetfilename is not available"); + + raise_combinetomp3(false, targetfilename, sourcefiles); + } + + private UDPInformation Setup_UDPInformation(int localport, String localip, int targetport, String[] targetip) { + InetSocketAddress localinet; + InetSocketAddress[] remoteinet; + if (!valid_string(localip)) localip = "0.0.0.0"; + + try{ + localinet = new InetSocketAddress(localip, localport); + } catch(IllegalArgumentException | SecurityException e) { + raise_log("Setup_UDPInformation failed on localport, exception = "+e.getMessage()); + return null; + } + + remoteinet = new InetSocketAddress[targetip.length]; + for(int ii=0;ii0) { + if (value<65536) { + return true; + } + } + return false; + } + + private boolean valid_ip(String value) { + if (valid_string(value)) { + try { + InetAddress inet = InetAddress.getByName(value); + return !inet.isLoopbackAddress(); + } catch(UnknownHostException | SecurityException e) { + + } + } + return false; + } + + private InetSocketAddress CreateSocketAddress(String ip, int port) { + if (valid_ip(ip)) { + try { + InetSocketAddress xx = new InetSocketAddress(ip, port); + return xx; + } catch(IllegalArgumentException | SecurityException e) { + + } + } + return null; + } + + private short CalculateVU(ByteBuffer source) { + ShortBuffer xx = source.order(ByteOrder.LITTLE_ENDIAN).asShortBuffer(); + short highest = 0; + short current = 0; + while(xx.hasRemaining()) { + current = xx.get(); + if (current > highest) { + highest = current; + } + } + return highest; + } + + + + private void check_events() { + if (ba!=null) { + if (valid_string(event)) { + need_log_event = ba.subExists(event+"_log"); + need_handlebuffer_event = ba.subExists(event+"_handlebuffer"); + need_handlevu_event = ba.subExists(event+"_handlevu"); + need_savetowavprogress_event = ba.subExists(event+"_savetowavprogress"); + need_udpreceive_event = ba.subExists(event+"_udpreceive"); + need_openudpreceiving_event = ba.subExists(event+"_openudpreceiving"); + need_sendhandleprogress_event =ba.subExists(event+"_sendhandleprogress"); + need_starthandleprogress_event =ba.subExists(event+"_starthandleprogress"); + need_openmulticastreceiving_event = ba.subExists(event+"_openmulticastreceiving"); + need_multicastreceive_event = ba.subExists(event+"_multicastreceive"); + need_combinetowav_event = ba.subExists(event+"_combinetowav"); + need_combinetomp3_event = ba.subExists(event+"_combinetomp3"); + } + } + } + + private void raise_combinetomp3(boolean success, String targetfilename, String[] sourcefiles) { + if (need_combinetomp3_event) + ba.raiseEventFromDifferentThread(Me, null, 0, event+"_combinetomp3", false, new Object[] {success, targetfilename, sourcefiles}); + } + + private void raise_combinetowav(boolean success, String targetfilename ,String[] sourcefiles) { + if (need_combinetowav_event) + ba.raiseEventFromDifferentThread(Me, null, 0, event+"_combinetowav", false, new Object[] {success, targetfilename, sourcefiles}); + } + + private void raise_multicastreceive(String localnetwork, String remotenetwork, byte[] bb, int length) { + if (need_multicastreceive_event) + ba.raiseEventFromDifferentThread(Me, null, 0, event+"_multicastreceive", false, new Object[] {localnetwork, remotenetwork, bb, length}); + + if (javaevent!=null) + javaevent.multicastreceive(localnetwork, remotenetwork, bb, length); + } + + private void raise_openmulticastreceiving(boolean success, String local, String target, int handle) { + if (need_openmulticastreceiving_event) + ba.raiseEventFromDifferentThread(Me, null, 0, event+"_openmulticastreceiving", false, new Object[] {success, local, target, handle}); + + if (javaevent!=null) + javaevent.openmulticastreceiving(success, local, target, handle); + } + + private void raise_starthandleprogress(int handle, String status) { + if (need_starthandleprogress_event) + ba.raiseEventFromDifferentThread(Me, null, 0, event+"_starthandleprogress", false, new Object[] {handle, status}); + + if (javaevent!=null) + javaevent.starthandleprogress(handle, status); + } + + private void raise_sendhandleprogress(int handle, String status, int totalsent) { + if (need_sendhandleprogress_event) + ba.raiseEventFromDifferentThread(Me, null, 0, event+"_sendhandleprogress", false, new Object[] {handle, status, totalsent}); + + if (javaevent!=null) + javaevent.sendhandleprogress(handle, status, totalsent); + } + + private void raise_bass_error(String FunctionName, String BassFunctionName) { + StringBuilder msg = new StringBuilder(); + msg.append("Function = "+FunctionName+"\n"); + msg.append("Bass Function = "+BassFunctionName+"\n"); + msg.append("Error = "+BASS.GetBassErrorString()); + raise_log(msg.toString()); + } + + private void raise_log(String msg) { + if (need_log_event) + ba.raiseEventFromDifferentThread(Me, null, 0, event+"_log", false, new Object[] {msg}); + + if (javaevent!=null) + javaevent.log(msg); + } + + private void raise_handlebuffer(int handle, byte[] bb) { + if (need_handlebuffer_event) + ba.raiseEventFromDifferentThread(Me, null, 0, event+"_handlebuffer", false, new Object[] {handle,bb}); + + if (javaevent!=null) + javaevent.handlebuffer(handle, bb); + } + + private void raise_handlevu(int handle, short value) { + if (need_handlevu_event) + ba.raiseEventFromDifferentThread(Me, null, 0, event+"_handlevu", false, new Object[] {handle, value}); + + if (javaevent!=null) + javaevent.handlevu(handle, value); + } + + private void raise_savetowavprogress(int handle, String filename, byte[] pcmbytes, long pcmsize) { + if (need_savetowavprogress_event) + ba.raiseEventFromDifferentThread(Me, null, 0, event+"_savetowavprogress", false, new Object[] {handle, filename, pcmbytes, pcmsize}); + + if (javaevent!=null) + javaevent.savetowavprogress(handle, filename, pcmsize); + } + + private void raise_udpreceive(String localnetwork, String remotenetwork, byte[] bb, int length) { + if (need_udpreceive_event) + ba.raiseEventFromDifferentThread(Me, null, 0, event+"_udpreceive", false, new Object[] {localnetwork, remotenetwork, bb, length}); + + if (javaevent!=null) + javaevent.udpreceive(localnetwork, remotenetwork, bb, length); + } + + private void raise_openudpreceiving(boolean success, String localnetwork, int handle) { + if (need_openudpreceiving_event) + ba.raiseEventFromDifferentThread(Me, null, 0, event+"_openudpreceiving", false, new Object[] {success, localnetwork, handle}); + + if (javaevent!=null) + javaevent.openudpreceiving(success, localnetwork, handle); + } + + private boolean valid_string(String xx) { + if (xx!=null) { + if (xx instanceof String) { + if (xx.length()>0) { + return true; + } + } + } + return false; + } + + private void Update_HandleInformation_Status(Pointer user, String status) { + if (user!=null) { + if (valid_string(status)) { + HandleInformation xx = Structure.newInstance(HandleInformation.class, user); + xx.read(); + xx.Status = status; + xx.write(); + } + } + + } + + + // dibuat terpisah begini , supaya tidak kena JNA Exception Callback Object has been garbage collected + IDSPPROC starthandle_dspproc = (dsp_handle, channel_handle, buffer, length, user)->{ + // channel_handle = starthandle + byte[] buf = buffer.getByteArray(0, length); + raise_handlebuffer(channel_handle, buf); + raise_handlevu(channel_handle, CalculateVU(ByteBuffer.wrap(buf))); + + HandleInformation xx = Structure.newInstance(HandleInformation.class, user); + xx.read(); // panggil ini dari native memory + xx.pcmsize += length; + xx.write(); //update lagi native memory + xx = null; + }; + + SYNCPROC starthandle_sync_dev_fail = (sync_handle, channel_handle, data, user)->{ + // channel_handle = starthandle + raise_starthandleprogress(channel_handle, "Device Fail"); + raise_log("Device Failure on StartHandle = "+channel_handle); + Update_HandleInformation_Status(user, "Device Failure"); + }; + + SYNCPROC starthandle_sync_free = (sync_handle, channel_handle, data, user)->{ + // channel_handle = starthandle + raise_starthandleprogress(channel_handle, "Finished"); + Update_HandleInformation_Status(user,"Finished"); + }; + + SYNCPROC starthandle_sync_end = (sync_handle, channel_handle, data, user)->{ + // channel_handle = starthandle + raise_starthandleprogress(channel_handle, "End Position"); + Update_HandleInformation_Status(user,"End Position"); + }; + + FILESEEKPROC fs = (offset, user)->{ + HandleInformation hix = Structure.newInstance(HandleInformation.class, user); + hix.read(); + raise_log("FILESEEKPROC called for handle="+hix.handle+", returned false"); + return false; + }; + + FILECLOSEPROC fc = (user)->{ + HandleInformation hix = Structure.newInstance(HandleInformation.class, user); + hix.read(); + + raise_log("FILECLOSEPROC called for handle="+hix.handle); + + }; + + FILELENPROC fl = (user)->{ + HandleInformation hix = Structure.newInstance(HandleInformation.class, user); + hix.read(); + + int available = hix.streamingbuffer.getCurrentNumberOfBytes(); + + raise_log("FILELENPROC called for handle="+hix.handle+", returning "+available); + return available; + }; + + FILEREADPROC fr = (buffer, length, user)->{ + HandleInformation hix = Structure.newInstance(HandleInformation.class, user); + hix.read(); // read from native memory + + if (hix.socket_opened.get()) { + synchronized(hix.streamingbuffer) { + if (!hix.streamingbuffer.hasBytes()) + try { + raise_log("FILEREADPROC handle = "+hix.handle+" wait at CircularByteBuffer"); + hix.streamingbuffer.wait(); + } catch (InterruptedException e) { + raise_log("CircularByteBuffer wait exception = "+e.getMessage()); + + } + int cbbsize = hix.streamingbuffer.getCurrentNumberOfBytes(); + if (cbbsize>0) { + int readsize = cbbsize <= length ? cbbsize : length; + byte[] bb = new byte[readsize]; + hix.streamingbuffer.read(bb, 0, readsize); + buffer.write(0, bb, 0, readsize); + raise_log("FILEREADPROC called for handle ="+hix.handle+", returned "+readsize); + return readsize; + } + } + } + return 0; + }; +} diff --git a/src/qzaipv2/qzaipv2event.java b/src/qzaipv2/qzaipv2event.java new file mode 100644 index 0000000..cfe1932 --- /dev/null +++ b/src/qzaipv2/qzaipv2event.java @@ -0,0 +1,14 @@ +package qzaipv2; + +public interface qzaipv2event { + void log(String msg); + void handlebuffer(int handle, byte[] bb); + void handlevu(int handle , short value ); + void savetowavprogress(int handle , String filename , long pcmsize); + void sendhandleprogress(int handle , String status , int totalsent ); + void starthandleprogress(int handle , String status ); + void openudpreceiving(boolean success , String localnetwork , int handle ); + void openmulticastreceiving(boolean success , String localnetwork ,String target ,int handle); + void udpreceive(String localnetwork , String remotenetwork , byte[] bb, int length ); + void multicastreceive(String localnetwork , String remotenetwork ,byte[] bb, int length ); +} diff --git a/src/tester/tester.java b/src/tester/tester.java new file mode 100644 index 0000000..a12def8 --- /dev/null +++ b/src/tester/tester.java @@ -0,0 +1,147 @@ +package tester; + +import java.io.IOException; + +import jbass.Bass_DeviceInfo; +import qzaipv2.qzaipv2; +import qzaipv2.qzaipv2event; + +public class tester{ + + static qzaipv2 qza; + public static void main(String[] args) { + qza = new qzaipv2(); + qza.SetJavaEvent(new qzaipv2event() { + + @Override + public void log(String msg) { + System.out.println(msg); + + } + + @Override + public void handlebuffer(int handle, byte[] bb) { + // TODO Auto-generated method stub + + } + + @Override + public void handlevu(int handle, short value) { + // TODO Auto-generated method stub + + } + + @Override + public void savetowavprogress(int handle, String filename, long pcmsize) { + // TODO Auto-generated method stub + + } + + @Override + public void sendhandleprogress(int handle, String status, int totalsent) { + // TODO Auto-generated method stub + + } + + @Override + public void starthandleprogress(int handle, String status) { + System.out.println("Handle = "+handle+", Status = "+status); + + } + + @Override + public void openudpreceiving(boolean success, String localnetwork, int handle) { + // TODO Auto-generated method stub + + } + + @Override + public void openmulticastreceiving(boolean success, String localnetwork, String target, int handle) { + // TODO Auto-generated method stub + + } + + @Override + public void udpreceive(String localnetwork, String remotenetwork, byte[] bb, int length) { + // TODO Auto-generated method stub + + } + + @Override + public void multicastreceive(String localnetwork, String remotenetwork, byte[] bb, int length) { + // TODO Auto-generated method stub + + } + + }); + System.out.println("Tester qzaipv2"); + String currentfolder = System.getProperty("user.dir"); + System.out.println("Current Folder = "+currentfolder); + + qza.check_bass(); + System.out.println("BASS inited = "+qza.IsInitialized()); + + Bass_DeviceInfo[] player = qza.GetDeviceInfos(); + if (player.length>0) { + for(Bass_DeviceInfo xx : player) { + System.out.println("Found player id="+xx.index+" Name = "+xx.Name); + } + } + Bass_DeviceInfo[] recorder = qza.GetRecordDeviceInfos(); + if (recorder.length>0) { + for(Bass_DeviceInfo xx : recorder) { + System.out.println("Found recorder id="+xx.index+" Name = "+xx.Name); + } + } + + // test playback + if (qza.Init_BASS_Output(1)) { +// Test UDP Receiving + qza.OpenUDPReceiving(5000,null); + try { + System.in.read(); + } catch (IOException e) { + System.out.println("Readkey exception = "+e.getMessage()); + } + +// Test playback audio +// String songpath = "C:\\Users\\rdkartono\\Music\\best acoustic song cover 2017.mp3"; +// int handle = qza.OpenFile(songpath); +// if (handle!=0) { +// if (qza.StartHandle(handle)) { +// System.out.println("Playing songhandle"); +// try { +// System.in.read(); +// } catch(IOException e) { +// System.out.println("Readkey exception = "+e.getMessage()); +// } +// +// if (qza.StopHandle(handle)) { +// System.out.println("Stopping songhandle"); +// } +// } +// } + } + + // test recording +// if (qza.Init_BASS_Input(1)) { +// final int rechandle = qza.RecordStart(44100); +// if (rechandle!=0) { +// System.out.println("Record Handle created"); +// if (qza.StartHandle(rechandle)) System.out.println("Record started"); +// int wavhandle = qza.SavetoWAVFile(rechandle, "trial1.wav"); +// if (wavhandle!=0) System.out.println("Recording to trial1.wav"); +// try { +// Thread.sleep(10 * 1000); +// } catch (InterruptedException e) { +// +// } +// if (qza.StopHandle(rechandle)) System.out.println("Record finished"); +// } +// } + + System.out.println("Tester finish"); + } + + +} diff --git a/src/visualizer/FrequencyVisualizer.java b/src/visualizer/FrequencyVisualizer.java new file mode 100644 index 0000000..585fb24 --- /dev/null +++ b/src/visualizer/FrequencyVisualizer.java @@ -0,0 +1,468 @@ +package visualizer; + +import com.sun.jna.Memory; +import com.sun.jna.Pointer; +import com.un4seen.bass.BASS; +import com.un4seen.bass.BASS.BASS_CHANNELINFO; +import com.un4seen.bass.BASS.bassconstant; + +import android.graphics.Paint.Align; +import android.graphics.Rect; +import android.graphics.Typeface; +import anywheresoftware.b4a.BA; +import anywheresoftware.b4a.keywords.constants.Colors; +import anywheresoftware.b4a.objects.ImageViewWrapper; +import anywheresoftware.b4a.objects.drawable.CanvasWrapper; +import jbass.Bass_ChannelInfo; + +@BA.ShortName("FrequencyVisualizer") +@BA.Events(values= { + "log(sourcehandle as int, msg as string)", + "visualizationstatus(sourcehandle as int, isvizualizing as boolean)", + "dominantfreqdb(sourcehandle as int, freq as int, db as float, dba as float)", +}) +public class FrequencyVisualizer { + /** + * Frequency Bands + */ + //public final float[] freqs = {25, 31, 39, 50, 62, 79, 99, 125, 157, 198, 250, 315, 397, 500, 630, 794, 1000, 1300, 1600, 2000, 2500, 3200, 4000, 5000, 6300, 8000, 10000, 13000, 16000}; + //public final float[] freqs = {100, 5000, 16000}; + public final float[] freqs = {20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000}; + + /** + * Number of frequency bands + */ + public final int bands = freqs.length; + + private BA bax = null; + private Object caller = null; + private String event = ""; + private boolean inited = false; + private boolean need_log_event = false; + private boolean need_visualizationstatus_event = false; + private boolean need_dominantfreqdb_event = false; + + private BASS bass = null; + private CanvasWrapper _canvas = null; + private ImageViewWrapper _imageview = null; + private int _handle = 0; + private float _dBPullUp = 90.0f; + private boolean _isvisualizing = false; + private Bass_ChannelInfo bci = null; + private boolean _showscale = false; + private int _fontsize = 10; + + public void Initialize(BA ba, Object callerobject, String eventname) { + bax = ba; + caller = callerobject; + event = eventname; + if (bax instanceof BA) { + if (caller != null) { + if (event instanceof String) { + if (!event.isEmpty()) { + need_log_event = bax.subExists(event+"_log"); + need_visualizationstatus_event = bax.subExists(event+"_visualizationstatus"); + need_dominantfreqdb_event = bax.subExists(event+"_dominantfreqdb"); + bass = new BASS(); + if (bass.BASS_GetVersion()!=0) { + inited = true; + } + } + } + } + } + } + + /** + * Check if Frequency Visualizer is initialized + * @return true if initialized + */ + public boolean IsInitialized() { + return inited; + } + + /** + * Get / Set Canvas to draw + * @param canvas : canvas object + */ + public void setCanvas(final CanvasWrapper canvas) { + _canvas = canvas; + } + + public CanvasWrapper getCanvas() { + return _canvas; + } + + /** + * Get / Set ImageView that holding the Canvas + * @param imageview : imageview object + */ + public void setImageView(final ImageViewWrapper imageview) { + _imageview = imageview; + } + + public ImageViewWrapper getImageView() { + return _imageview; + } + + private void setHandle(int value) { + _handle = value; + } + + /** + * Get handle assigned to this Frequency Visualizer + * @return handle number + */ + public int getHandle() { + return _handle; + } + + /** + * Get / Set dB Pull Up + * dB A = dB + dB Pull Up + * @param value : dB pull up value + */ + public void setdBPullUp(float value) { + _dBPullUp = value; + } + + public float getdBPullUp() { + return _dBPullUp; + } + + /** + * Show / hide scale on canvas + * @param value : if true will show scale + */ + public void setShowDBScale(boolean value) { + _showscale = value; + } + + public boolean getShowDBScale() { + return _showscale; + } + + /** + * Start Visualize + * @param handle: channel handle + * @param imageview : imageview object + * @param canvas : canvas object + * @return true if success + */ + public boolean StartVisualize(int handle, ImageViewWrapper imageview, CanvasWrapper canvas) { + StopVisualize(); // stop previous visualization if exist + if (IsInitialized()) { + if (handle != 0) { + if (imageview instanceof ImageViewWrapper) { + if (imageview.IsInitialized()) { + if (imageview.getVisible()) { + if (canvas instanceof CanvasWrapper) { + BASS_CHANNELINFO chi = new BASS_CHANNELINFO(); + if (bass.BASS_ChannelGetInfo(handle, chi)) { + bci = new Bass_ChannelInfo(chi); + if (bci.isvalid) { + setCanvas(canvas); + setImageView(imageview); + setHandle(handle); + Thread tx = new Thread(new visualrun(1024)); // use fft size 1024 + tx.start(); + return true; + } + } + } else raise_log("StartVisualize failed, canvas is not object"); + } else raise_log("StartVisualize failed, imageview is not visible"); + } else raise_log("StartVisualize failed, imageview is not initialized"); + } else raise_log("StartVisualize failed, imageview is not object"); + } else raise_log("StartVisualize failed, handle is zero"); + } else raise_log("StartVisualize failed, FrequencyVisualizer is not initialized"); + return false; + } + + private class visualrun implements Runnable{ + // fft size + private final int fftsize; + // float size, selalu setengah fftsize + private final int floatsize ; + // flag di bass + private int getdataflag; + + private float[] amplitudes = new float[bands]; + private float[] dBresult = new float[bands]; + private int dominant_freq = 0; + private float dominant_freq_dB = 0; + + public visualrun(int fft_size) { + + switch(fft_size) { + case 1024 : + getdataflag = bassconstant.BASS_DATA_FFT1024; + break; + case 16384 : + getdataflag = bassconstant.BASS_DATA_FFT16384; + break; + case 2048 : + getdataflag = bassconstant.BASS_DATA_FFT2048; + break; + case 32768 : + getdataflag = bassconstant.BASS_DATA_FFT32768; + break; + case 4096 : + getdataflag = bassconstant.BASS_DATA_FFT4096; + break; + case 512 : + getdataflag = bassconstant.BASS_DATA_FFT512; + break; + case 8192 : + getdataflag = bassconstant.BASS_DATA_FFT8192; + break; + default : + getdataflag = bassconstant.BASS_DATA_FFT256; + fft_size = 256; // kalau sampai angkanya ngaco + break; + } + + fftsize = fft_size; + floatsize = fftsize / 2; + } + @Override + public void run() { + clear_canvas(); + update_imageview(); + raise_dominantfreqdb(0,Float.NEGATIVE_INFINITY,0); + + raise_visualizationstatus(true); + _isvisualizing = true; + int readsize = 0; // untuk result bass_channelgetdata + while (_isvisualizing) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + _isvisualizing = false; + raise_log("InterruptedException on FrequencyVisualizer handle="+_handle); + continue; + } + + Pointer buf = new Memory(floatsize * 4); // dikali 4 karena 1 float = 4 bytes + readsize = bass.BASS_ChannelGetData(_handle, buf, getdataflag); + if (readsize == -1) { + int errcode = bass.BASS_ErrorGetCode(); + if (errcode != 0) { + raise_log("ChannelGetData fail, Msg : "+bass.GetBassErrorString()); + _isvisualizing = false; + } + continue; + } + + if (readsize>0) { + // ada FFT result + float[] fftresult = buf.getFloatArray(0, readsize/4); + calculate(fftresult); + draw_fft(); + } + } + raise_dominantfreqdb(0,Float.NEGATIVE_INFINITY,0); + raise_visualizationstatus(false); + clear_canvas(); + update_imageview(); + } + + private void draw_fft() { + if (_canvas instanceof CanvasWrapper) { + if (_imageview instanceof ImageViewWrapper) { + if (_imageview.IsInitialized()) { + if (_imageview.getVisible()) { + final int imageview_height = _imageview.getHeight(); + final int imageview_width = _imageview.getWidth(); + + final float widest_legend = (int)_canvas.MeasureStringWidth("999 dBA", Typeface.DEFAULT, _fontsize); // tulisan legend paling panjang, buat ngubah nilai left + final float tallest_legend = (int)_canvas.MeasureStringHeight("20K", Typeface.DEFAULT, _fontsize); + + float width = imageview_width-2; + float height = imageview_height-2; + + if (_showscale) { + width -= widest_legend ; // width adalah imageview width, dikurangi lebarnya tulisan legend sumbu Y, dikurangi 2 buat spasi + height -= tallest_legend ; // height adalah imageview height, dikurangi tingginya tulisan legend sumbu X, dikurangi 2 buat spasi + } + + final float bar_width = (width / bands); // 1 pixel kiri, 1 pixel kanan + final float cell_height = (height / (int)_dBPullUp); // untuk convert height ke dB cell . Full scale nya adalah dBPullUp. 1 pixel atas, 1 pixel bawah + + float left = 0; // mulai kiri dari 0 + float top = 0; // mulai dari atas 0 + + _canvas.DrawColor(Colors.Transparent); // bersihin + + if (_showscale) { + // bikin legenda + + //sumbu Y = amplitudo dalam dB + // koordinat tulisan itu bottom left + + top = tallest_legend; + left = 0; + int legendY = (int)_dBPullUp; // ini full scale nya (maksudnya dalam dB) + final int legendY_max = (int)_dBPullUp / 10; // dari full scale, cuma dibagi jadi 10 bagian saja di sumbu Y, biar gampang + for(int ii=0; ii<= legendY_max; ii++) { + String legendstring = String.format("%3d dBA", legendY); + _canvas.DrawTextRotated(bax, legendstring, left, top, Typeface.DEFAULT, _fontsize, Colors.Black, Align.LEFT, 0); + + legendY-=10; // tulisan legend turunin 10 (maksudnya dalam dB) + top += (cell_height*10); // turun ke bawah 10 pixel + } + // gambar garis sumbu Y + _canvas.DrawLine(widest_legend, 0, widest_legend, top, Colors.Black, 1); + + left = widest_legend + 2; + top = imageview_height; + // gambar garis sumbu X + _canvas.DrawLine(left, height-2, imageview_width, height-2, Colors.Black, 1); + + // sumbu X = frequency + for(int ii=0;ii< bands;ii++) { + String legendstring = String.format("%5d", (int)freqs[ii]); + _canvas.DrawTextRotated(bax, legendstring, left, top, Typeface.DEFAULT, _fontsize, Colors.Black, Align.LEFT,0); + left+= bar_width+2; + } + + + + // update nilai left + left = widest_legend+3; // kananin dari sumbu garis Y + height = height-3; // naikin di atas sumbu garis X + } + + + + // bikin spectrum bar + + float dBA[] = new float[bands]; + for(int ii=0;ii0) { + int b0 = 0; + float _biggestdB = Float.NEGATIVE_INFINITY; + int _dominantfreq = 0; + float sampling = bci.frequency; + + + for(int x=0;x fftdatalength-1) { + b1 = fftdatalength-1; + } + for( ; b0 < b1 ; b0++) + if (amplitudes[x]_biggestdB) { + _biggestdB = dBresult[x]; + _dominantfreq = (int)freqs[x]; + } + + + } + + + + boolean need_update_dominantfreqdb = false; + if (_biggestdB != dominant_freq_dB) { + dominant_freq_dB = _biggestdB; + need_update_dominantfreqdb = true; + } + + if (_dominantfreq != dominant_freq) { + need_update_dominantfreqdb = true; + dominant_freq = _dominantfreq; + } + + if (need_update_dominantfreqdb) { + float dBA = dominant_freq_dB + _dBPullUp; + if (dBA < 0) dBA = 0; + raise_dominantfreqdb(dominant_freq, dominant_freq_dB, dBA); + } + } + } + } + + private void clear_canvas() { + if (_canvas instanceof CanvasWrapper) { + _canvas.DrawColor(Colors.Transparent); + } + } + + private void update_imageview() { + if (_imageview instanceof ImageViewWrapper) { + if (_imageview.IsInitialized()) { + _imageview.Invalidate(); + } + } + } + } + + /** + * Stop Visualize + */ + public void StopVisualize() { + _isvisualizing = false; + } + + /** + * Get / Set Font size for X - Y scales + * @return font size + */ + public int getFontSize() { + return _fontsize; + } + + public void setFontSize(int value) { + _fontsize = value; + } + + + /** + * Check if FrequencyVisualizer is still visualizing + * @return true if still visualizing + */ + public boolean getIsVisualizing() { + return _isvisualizing; + } + + private void raise_log(String msg) { + if (need_log_event) bax.raiseEventFromDifferentThread(caller, null, 0, event+"_log", false, new Object[] {_handle, msg}); + } + + private void raise_visualizationstatus(boolean value) { + if (need_visualizationstatus_event) bax.raiseEventFromDifferentThread(caller, null, 0, event+"_visualizationstatus", false, new Object[] {_handle, value}); + } + + private void raise_dominantfreqdb(int freq, float dB, float dBA) { + if (need_dominantfreqdb_event) bax.raiseEventFromDifferentThread(caller, null, 0, event+"_dominantfreqdb", false, new Object[] {_handle, freq, dB, dBA}); + } +} diff --git a/src/visualizer/SpectrumBand.java b/src/visualizer/SpectrumBand.java new file mode 100644 index 0000000..322715e --- /dev/null +++ b/src/visualizer/SpectrumBand.java @@ -0,0 +1,117 @@ +package visualizer; + +import anywheresoftware.b4a.BA; + +//@BA.ShortName("SpectrumBand") +public class SpectrumBand { + + /** + * Frequency Bands + */ + //public final int[] freqs = {25, 31, 39, 50, 62, 79, 99, 125, 157, 198, 250, 315, 397, 500, 630, 794, 1000, 1300, 1600, 2000, 2500, 3200, 4000, 5000, 6300, 8000, 10000, 13000, 16000}; + //public final int[] freqs = {100, 5000, 16000}; + public final int[] freqs = {20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000}; + + /** + * Number of frequency bands + */ + public final int bands = freqs.length; + + @BA.Hide + public float[] amplitudes; + + /** + * dB value for each frequency bands + */ + public float[] dB; + + /** + * Samplingrate of SpectrumBand + */ + public final double samplingrate; + + /** + * Raw FFT Data from BASS + */ + @BA.Hide + public final float[] FFT_Data; + + /** + * Total FFT Size + */ + @BA.Hide + public final int FFT_Size; + + /** + * The biggest dB obtained from calculation + */ + public final float biggest_dB; + /** + * Frequency band where the biggest dB found + */ + public final int dominant_freq; + + public SpectrumBand(int sampling, int fftsize, float[] fftdata) { + samplingrate = sampling; + FFT_Data = fftdata; + FFT_Size = fftsize; + amplitudes = new float[bands]; + dB = new float[bands]; + // calculate + // http://www.un4seen.com/forum/?topic=18928.msg132361#msg132361 + int b0 = 0; + float _biggestdB = Float.NEGATIVE_INFINITY; + int _dominantfreq = 0; + for(int x=1;x (FFT_Data.length-1)) b1 = FFT_Data.length-1; + while(b0_biggestdB) { + _biggestdB = dB[x]; + _dominantfreq = freqs[x]; + } + //BA.Log("x="+x+", Freq="+freqs[x]+", amplitude="+amplitudes[x]+", dB="+dB[x]); + } + + biggest_dB = _biggestdB; + dominant_freq = _dominantfreq; + } + + /** + * Return dBA value from dB + * result is biggest_dB + dBPullUp + * @param dBPullUp : calibrated value to convert dB to dBA + * @return value in dBA + */ + public float Biggest_dBA(float dBPullUp) { + float tmp = biggest_dB + dBPullUp; + return tmp < 0 ? 0 : tmp; + } + + /** + * Get dBA valud from current dB + * dBA = dB + dBPullUp + * @param dbPullUp : calibrated value to convert dB to dBA + * @return value in float + */ + public float[] Get_dBA(float dbPullUp) { + if (dB != null) { + int dBlen = dB.length; + float tmp = 0; + if (dBlen>0) { + float dBA[] = new float[dBlen]; + for(int ii=0;ii