Android 音頻口PCM通信

相關(guān)

demo下載地址
相關(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等等叙量,為了可以盡可能的滿足不同硬件需求倡蝙,建議這樣寫。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末绞佩,一起剝皮案震驚了整個(gè)濱河市寺鸥,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌品山,老刑警劉巖胆建,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異肘交,居然都是意外死亡笆载,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進(jìn)店門涯呻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來凉驻,“玉大人,你說我怎么就攤上這事复罐±缘牵” “怎么了?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵效诅,是天一觀的道長胀滚。 經(jīng)常有香客問我趟济,道長,這世上最難降的妖魔是什么蛛淋? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任咙好,我火速辦了婚禮,結(jié)果婚禮上褐荷,老公的妹妹穿的比我還像新娘勾效。我一直安慰自己,他們只是感情好叛甫,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布层宫。 她就那樣靜靜地躺著,像睡著了一般其监。 火紅的嫁衣襯著肌膚如雪萌腿。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天抖苦,我揣著相機(jī)與錄音毁菱,去河邊找鬼。 笑死锌历,一個(gè)胖子當(dāng)著我的面吹牛贮庞,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播究西,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼窗慎,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了卤材?” 一聲冷哼從身側(cè)響起遮斥,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎扇丛,沒想到半個(gè)月后术吗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡晕拆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年藐翎,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片实幕。...
    茶點(diǎn)故事閱讀 39,977評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡吝镣,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出昆庇,到底是詐尸還是另有隱情末贾,我是刑警寧澤,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布整吆,位于F島的核電站拱撵,受9級特大地震影響辉川,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜拴测,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一乓旗、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧集索,春花似錦屿愚、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至函匕,卻和暖如春娱据,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背盅惜。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工中剩, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人抒寂。 一個(gè)月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓咽安,卻偏偏與公主長得像,于是被迫代替她去往敵國和親蓬推。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評論 2 355

推薦閱讀更多精彩內(nèi)容