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}); } }