导语:你可以利用org.quietmodem.Quiet在你的安卓设备上使用扬声器传输数据,可以作为框架层或UDP/TCP栈来进行操作。
你可以利用org.quietmodem.Quiet在你的安卓设备上使用扬声器传输数据,可以作为框架层或UDP/TCP栈来进行操作。
它包含了 libquiet和quiet-lwip及其附属类的预编译库文件,并且加入了高度模拟java.net.* package的接口。它遵守3-clause BSD协议,附属包也都具有混合了MIT与BSD的许可。
Quiet包可以被armeabi-v7a, arm64-v8a, x86和x86_64系统支持,它在32位模式时需要 Android API 版本为14,而在64位模式时需要 Android API 版本为16,任意模式下只需要RECORD_AUDIO许可。
强烈推荐使用Genymotion模拟器来做测试,因为它提供了麦克风使用权,这在默认的Android Studio中是没有的,Quiet使用麦克风时Android Studio会抛出异常。
Quiet的优点
如果你是一位很有资历的研究人员,你应该还记得从前我们用拨号的猫上网,从某种角度来说,Quiet包使用的方法与其相似。当然不得不说这确实是一种复古的方法,但也有其优点:
可跨平台:任何具有扬声器、麦克风以及足够的计算能力的设备都可以通过它进行传输。
不使用配对模式:它不像蓝牙那样麻烦,可以不通过设备配对直接使用音频,这样减少了冲突并提高了用户体验。
可嵌入信息:与QR码类似,简短的数据可以被编码到流音频或音频录制中,传输后再进行解码。
这个问题取决于你选择哪种操作模式,Quiet提供了可听见的正常模式,与超声波模式。可听见模式下听起来像一阵风吹过,超声波模式的频率超过17千赫(kHz),成年人基本无法听到。如果环境不太吵,两种模式都可以在相对低的音量下使用。
Quiet在可听见模式下的速度大约是7kbps。如果两个设备通过电缆连接(使用3.5mm音频插头),Quiet可以在电缆模式下工作,速度可以达到64kbps。
台式机/笔记本:libquiet 和 quiet-lwip Javascript: quiet-js (UDP/TCP版本即将发布) iOS:即将发布
Quiet可以作为框架层使用,也可以在UDP/TCP模式下使用。对于后者,它提供了lwIP TCP栈,该栈相对于安卓提供的栈是可以完全独立执行的。
付诸于实践
必须将Android NDK安装在local.properties中的ndk.dir下,这是为了在Quiet中建立JNI包裹文件。
假设我们从MainActivity.java开始工作:
importjava.io.IOException; importorg.quietmodem.Quiet.*; FrameReceiverConfigreceiverConfig = null; FrameTransmitterConfigtransmitterConfig = null; try { transmitterConfig = newFrameTransmitterConfig( this, "audible-7k-channel-0"); receiverConfig = new FrameReceiverConfig( this, "audible-7k-channel-0"); } catch(IOException e) { // could not build configs } FrameReceiverreceiver = null; FrameTransmittertransmitter = null; try { receiver = newFrameReceiver(receiverConfig); transmitter = newFrameTransmitter(transmitterConfig); } catch(ModemException e) { // could not set up receiver/transmitter }
这建立了传输器和接收器。这里我们选择可听见的模式。我们可以这样
// set receiver toblock for at most 1 second // by defaultreceivers are nonblocking receiver.setBlocking(1,0); byte[] buf = newbyte[1024]; long recvLen = 0; try { recvLen = receiver.receive(buf); } catch(IOException e) { // read timed out }
也可以这样
String payload ="Hello, World!"; try { transmitter.send(payload.getBytes()); } catch(IOException e) { // our message might be too long or thetransmit queue full }
这就足够用来发送框架了。当我们想发送很短的数据,这些数据不需要发送器或接收器,可以简单的装在一个frame中时,我们就会使用框架模式,也就是说,框架是广播媒体。
如果我们想让两个设备进行互动,或者我们想让数据自动分组转发,我们就要用到UDP/TCP模式。
首先建立新的NetworkInterface
importjava.io.IOException; importjava.net.SocketException; importorg.quietmodem.Quiet.*; FrameReceiverConfigreceiverConfig = null; FrameTransmitterConfigtransmitterConfig = null; try { transmitterConfig = newFrameTransmitterConfig( this, "audible-7k-channel-0"); receiverConfig = new FrameReceiverConfig( this, "audible-7k-channel-0"); } catch(IOException e) { // could not build configs } NetworkInterfaceConfigconf = new NetworkInterfaceConfig( receiverConfig, transmitterConfig); NetworkInterfaceintf = null; try { intf = new NetworkInterface(conf); } catch(ModemException e) { // network interface failure }
例子中省略了NetworkInterfaceConfig()中建立Auto IP的IP地址和网络掩码,它会自动给我们的接口赋予一个地址,一旦我们将接口实例化,探测并决定该地址需要数秒的时间。
如果我们使用点对点模式,就需要寻找附近的客户端,我们可以通过广播UDP包来完成。
我们可以像下面这样来实现:
// org.quietmodem.Quiet.DatagramSocket DatagramSocket s =null; try { // bind to wildcard:3333 -- note that thisis // usingorg.quietmodem.Quiet.InetSocketAddress // not java.net.InetSocketAddress sSend = new DatagramSocket(newInetSocketAddress("0.0.0.0", 3333)); // listen on 3334 sRecv = new DatagramSocket(newInetSocketAddress("0.0.0.0", 3334)); // don't block for more than 10 seconds sRecv.setSoTimeout(10000); // get broadcast permission sSend.setBroadcast(true); } catch(SocketException e) { // exception creating DatagramSocket } byte[] send ="MARCO".getBytes(); byte[] recv = newbyte[1024]; InetSocketAddresspeer = null; while (true) { // get ready to do a broadcast to port 3334 // again, this is org.quietmodem.Quiet.DatagramPacket DatagramPacket p = new DatagramPacket(send,send.length, newInetSocketAddress("169.254.255.255", 3334)); try { sSend.send(p); } catch (IOException e) { // exception sending on DatagramSocket } DatagramPacket pRecv = newDatagramPacket(recv, recv.length); boolean received = false; try { sRecv.receive(pRecv); received = true; peer = pRecv.getSocketAddress(); // respond so that the other peer knowswe're here p.setData("POLO".getBytes()); p.setSocketAddress(peer); sSend.send(p); } catch (IOException e) { // exception receiving onDatagramSocket } if (received) { break; } // our 10-second read timeout acts as asleep // continue broadcasting until we get abite } // now use thepeer's address somehow.... // continuesending UDP or establish a TCP connection // on another port // when finished,close sSend, sRecv and intf