相關(guān)
demo介紹:
本例只實(shí)現(xiàn)了發(fā)送字符串的功能(用的是方波標(biāo)記)车猬,而且界面布局很簡單穿肄,貼圖:
點(diǎn)擊【send】將會(huì)發(fā)送出一段噪音闺阱,噪音的內(nèi)容是用你所輸入的字符串編碼過來的焚辅,用的是Ascall碼轉(zhuǎn)換成高低電頻己儒,當(dāng)然在手機(jī)的耳機(jī)插口需要插入一個(gè)自己開發(fā)的板子淑玫,接收端需要安裝配套的信號接受器巾腕。
綁定button。
listener = new Listener(); play = (Button)findViewById(R.id.play); text = (EditText)findViewById(R.id.text); play.setOnClickListener(listener);
listener的內(nèi)容:
private class Listener implements OnClickListener
{
@Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
sendme = text.getText().toString();
Log.d("!!!!!!","listener");
if(sendme != ""&& sendme != null)
{
if(thread == null)
{
thread = new AudioThread();
thread.start();
}
else
thread.letRun();
}
}
}
thread的內(nèi)容:
class AudioThread extends Thread{ @Override public void run() { // TODO Auto-generated method stub while(true) { if(isrun) { Log.d("!!!!!!","runner"); audioplayer = new AudioSend(); audioplayer.play(sendme); try { sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } isrun =false; } } } public void letRun() { Log.d("!!!!!!","letrunner"); isrun = true; } }
已經(jīng)發(fā)現(xiàn)重點(diǎn)就在audioplayer這個(gè)對象絮蒿,這個(gè)是自己用audiosend類封裝的尊搬,運(yùn)用的是AudioTrack,查看api就可以發(fā)現(xiàn)這個(gè)類有個(gè)write()方法土涝,方法是重載的:write(byte[]佛寿,int,int)或者是write(short[]但壮,int冀泻,int),數(shù)組內(nèi)即為pcm消息茵肃。
PCM內(nèi)容解析
先來了解下耳機(jī)的構(gòu)造:
我們知道腔长,耳機(jī)是用來聽音樂,打電話的验残,既然是和聲音相關(guān)的捞附,那么耳機(jī)線上傳輸?shù)木褪且纛l信號,常見的音頻信號一般都是在100Hz——10KHz左右的范圍內(nèi),那么手機(jī)里面的音頻輸出系統(tǒng)(DA和音頻功放)的幅頻特性(也既帶寬)一定也是在這個(gè)范圍(這是本人的猜想鸟召,由于設(shè)備和儀器有限胆绊,沒有進(jìn)行系統(tǒng)的測試,有興趣的朋友可以用相關(guān)的測試儀器測測)欧募,那么压状,既然有帶寬,好家伙跟继,我們就可以通過努力在這個(gè)頻帶內(nèi)實(shí)現(xiàn)我們的通信信道了种冬!另外值得提的一點(diǎn)是,耳機(jī)線上傳輸?shù)囊纛l信號是交流的舔糖!
下面我們來看看市面上常見的耳機(jī)座(公頭)的引腳定義娱两,android手機(jī)上用的耳機(jī)大多都是3.5mm的四芯座,在這四個(gè)芯中金吗,分別是:地十兢、左聲道、右聲道和線控開關(guān)(MIC)
發(fā)現(xiàn)其實(shí)耳機(jī)接受的就是電流摇庙,那么那個(gè)數(shù)組中包含的應(yīng)該就是電流的強(qiáng)度旱物。那么我們只需要將所要發(fā)送的內(nèi)容轉(zhuǎn)換為電流的強(qiáng)度來標(biāo)記,并且在接受端按照相同的方式來解碼即可卫袒。
通信協(xié)議
如圖所示通信協(xié)議中每十個(gè)位標(biāo)記位發(fā)送的一個(gè)字母宵呛,開始位定義成低電流終止位定義成高電流(我們沒有定奇偶校檢位),中間的八位來定義字母的內(nèi)容(也就是字母轉(zhuǎn)換成Acall碼之后的內(nèi)容)但是每個(gè)電流如果用1和0表示的話差別太小了玛臂,單片機(jī)很可能檢測不到烤蜕,或者檢測失誤,所以我將高電流定義為-128迹冤,低電流定義為16讽营,代碼先將每個(gè)字母轉(zhuǎn)化成對應(yīng)的ascll碼,然后再將它轉(zhuǎn)換成對應(yīng)的高低電流的波峰和波谷泡徙。
發(fā)送消息
public class AudioSend { static int baudRate = 4800; static int maxRate = 48000; static int delayBit = 0; private static byte ihigh = (byte) (-128); private static byte ilow = (byte) (16); AudioTrack audioplayer; static int minSize; static byte[] getBuffer(String str) { int bytesinframe = delayBit + 10;//delay + 8bit + 一個(gè)標(biāo)識(shí)開始的位 + 一個(gè)標(biāo)識(shí)結(jié)束的位 byte[] sendme = str.getBytes(); int n = maxRate/baudRate; boolean[] bits = new boolean[sendme.length \* bytesinframe];// byte[] waveform = new byte[(sendme.length\*bytesinframe* n)]; //防止失真橱鹏,延長每個(gè)波的變化的播放時(shí)間 Arrays.fill(bits, true); //當(dāng)其不斷傳出電流的時(shí)候標(biāo)志著無信息傳送,一旦有低壓電流標(biāo)志開始傳送數(shù)據(jù) int i,m,k,j = 0; for (i=0;i<sendme.length;++i) { m=i\*bytesinframe; bits[m]=false; bits[++m]=((sendme[i]&1)==1);//位操作堪藐,也可以先轉(zhuǎn)換成數(shù)字再用 Integer.toBinaryString bits[++m]=((sendme[i]&2)==2); bits[++m]=((sendme[i]&4)==4); bits[++m]=((sendme[i]&8)==8); bits[++m]=((sendme[i]&16)==16); bits[++m]=((sendme[i]&32)==32); bits[++m]=((sendme[i]&64)==64); bits[++m]=((sendme[i]&128)==128); //加上延時(shí)的位 for(k=0;k<bytesinframe-9;k++) bits[++m]=true; } //轉(zhuǎn)換成需要的byte數(shù)組 for (i=0;i<bits.length;i++) { for (k=0;k<n;k++) { waveform[j++]=(bits[i])?((byte) (ihigh)):((byte) (ilow)); } } bits=null; return waveform; } public void play (String str) { byte[] send = getBuffer(str); minSize = AudioTrack.getMinBufferSize(48000,AudioFormat.CHANNEL_OUT_MONO ,AudioFormat.ENCODING_PCM_16BIT); audioplayer = new AudioTrack(AudioManager.STREAM_MUSIC, 48000, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, minSize, AudioTrack.MODE_STREAM); audioplayer.play(); audioplayer.write(send, 0, send.length); audioplayer.stop(); audioplayer.release(); } }
在構(gòu)造audiotrack的時(shí)候
minSize = AudioTrack.getMinBufferSize(48000,AudioFormat.CHANNEL_OUT_MONO ,AudioFormat.ENCODING_PCM_16BIT); audioplayer = new AudioTrack(AudioManager.STREAM_MUSIC, 48000, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, minSize, AudioTrack.MODE_STREAM);
第一行其實(shí)是獲取最小緩沖區(qū)大小莉兰,闡述按順序依次是采樣率,聲道礁竞,和采樣精度.采樣率就是每秒鐘要發(fā)送多少個(gè)點(diǎn)過去(其實(shí)android上只能是4800,44100,48000)糖荒,也就是告訴audiotrack對象每秒需要從我所write的數(shù)組中提取多少個(gè)點(diǎn),當(dāng)然在播放音樂的時(shí)候采樣率越高就會(huì)音質(zhì)效果越好模捂。聲道就不提了捶朵,只是音頻中實(shí)現(xiàn)聲道的方式并不是若干條不相干的音頻蜘矢,而是混在一次,比如雙通道:123456的采樣综看,播放結(jié)果是左聲道:135品腹,右聲道246.單通道的話就是123456,時(shí)間延長一倍红碑。采樣精度就是電流的強(qiáng)度的上限和下限舞吭,當(dāng)然也是越大音質(zhì)越好,目前似乎只能是8BIT和16BIT析珊,應(yīng)該也就是這個(gè)原因所以write接受的是byte和short數(shù)組吧羡鸥。
對于代碼段:
//轉(zhuǎn)換成需要的byte數(shù)組 for (i=0;i<bits.length;i++) { for (k=0;k<n;k++) { waveform[j++]=(bits[i])?((byte) (ihigh)):((byte) (ilow)); } }
目的是為了達(dá)到我們想要的采樣率而延時(shí),也就是將每個(gè)波的變化延長k個(gè)單位時(shí)間忠寻,k呢是48000/4800得來的兄春,也就是我機(jī)器每秒發(fā)送48000個(gè)電流變化值出去,但是現(xiàn)在我想只讓它發(fā)送4800個(gè)锡溯,那也就需要把每個(gè)點(diǎn)復(fù)制十份然后發(fā)送。當(dāng)然哑姚,如果你是想把采樣率定為4800的話可以直接在初始化的時(shí)候?qū)懠婪梗窃谟布O(shè)置中采樣率(其實(shí)硬件中叫波特率)就有很多選擇了可以是600,1200,2400,4800,44100等等叙量,為了可以盡可能的滿足不同硬件需求倡蝙,建議這樣寫。