最近要做一個項目,利用手機的耳機口輸出紅外信號,從而把手機變成紅外遙控器,信號處理的知識基本都還給老師了,剛開始真的挺頭疼。找了不少資料研究了一下,總算有點心得,在這里做個備忘。
一、音頻信號輸出原理
音頻耳機口輸出信號的原理已經有大牛的文章,參考http://blog.csdn.net/xl19862005/article/details/8522869
再補充一點個人的理解,Android音頻輸出采樣率一般為44.1kHz,AudioTrack源碼中限制最大采樣率為48kHz,也就是說耳機音頻口輸出的音頻頻率應該在20kHz左右,基本就是Android音頻輸出頻率極限了。紅外信號載波一般是38kHz,所以單純的想通過音頻信號是達不到要求的,需要借助外部硬件,找了一下,發現某寶上有的賣的,配套的app也有,下來試了一下,效果不錯。不過既然是自己開發,需要搞清原理,反編譯看了一下,核心代碼都是native code,還是走正路,自己研究怎么實現。
網上找了一些相關資料,Linux平台下有相關的開源項目LIRC(Linux Infrared Remote Control):http://www.lirc.org/,支持各種類型的硬件,可以通過配置文件來支持各種類型的遙控設備,電視、dvd等等,是一個相對成熟的項目,目前google play中有很多紅外遙控的應用都是基於此項目開發的。不過我這邊只關注耳機口輸出紅外,LIRC中關於audio耳機口輸出原理圖(http://www.lirc.org/html/audio.html):
利用耳機的左右聲道,輸出19kHz音頻,通過左邊的電路圖,輸出38kHz。
二、實現
原理搞清楚了,接下來實現,通過AudioTrack輸出19kHz的正弦波形即可。這里沒有將LIRC整體移植到android上,一來工程量太大,二來我只是用耳機這一種,其他的用不上,主要借鑒其中的音頻輸出原理。LIRC移植到Android可以參考開源項目irdroid:http://www.irdroid.com/
核心代碼, 輸出19kHz正弦,參考國外大牛:http://stackoverflow.com/questions/2413426/playing-an-arbitrary-tone-with-android
1 public SignalProcessor(final int frequency) { 2 int buffSize = AudioTrack.getMinBufferSize(this.sampleRate, 3 AudioFormat.CHANNEL_CONFIGURATION_STEREO, 4 AudioFormat.ENCODING_PCM_16BIT) * 4; 5 6 genSignal = new byte[buffSize]; 7 genSpace = new byte[buffSize]; 8 9 for (int j = 0; j < buffSize;) { 10 double dVal = Math.sin(2 * Math.PI * ((double)j)/4.0 11 / (((double)sampleRate) / ((double)frequency))); 12 final short val = (short) ((dVal * 32767)); 13 final short val_minus = (short) -val; 14 // in 16 bit wav PCM, first byte is the low order byte 15 genSpace[j] = 0; 16 genSignal[j++] = (byte) (val & 0x00ff); 17 genSpace[j] = 0; 18 genSignal[j++] = (byte) ((val & 0xff00) >>> 8); 19 genSpace[j] = 0; 20 genSignal[j++] = (byte) (val_minus & 0x00ff); 21 genSpace[j] = 0; 22 genSignal[j++] = (byte) ((val_minus & 0xff00) >>> 8); 23 } 24 25 audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 26 this.sampleRate, AudioFormat.CHANNEL_CONFIGURATION_STEREO, 27 AudioFormat.ENCODING_PCM_16BIT, buffSize, 28 AudioTrack.MODE_STREAM); 29 audioTrack.play(); 30 } 31 32 public void play(final ArrayList<Integer> SignalSpaceList) { 33 boolean signal = true; 34 int count=0; 35 for (Integer d : SignalSpaceList) { 36 final int stop = (int) (((double) (d * sampleRate)) / 1000000.0) *4 ; 37 if (signal) 38 for (int i = 0; i < stop;) 39 { 40 if (stop - i < buffSize) 41 count= audioTrack.write(genSignal, 0, stop - i); 42 else 43 count = audioTrack.write(genSignal, 0, buffSize); 44 if(count>0) 45 i+=count; 46 } 47 else 48 for (int i = 0; i < stop;) 49 { 50 if (stop - i < buffSize) 51 count= audioTrack.write(genSpace, 0, stop - i); 52 else 53 count = audioTrack.write(genSpace, 0, buffSize); 54 if(count>0) 55 i+=count; 56 } 57 58 signal = !signal; 59 } 60 }
解釋一下:
1 public void play(final ArrayList<Integer> SignalSpaceList)
傳入信號與空閑時間的一個list,比如NEC編碼中如圖:
HEAD信號時間9ms,空閑時間4.5ms,list中傳入9000,4500
測試耳機口單聲道輸出波形:
連接外設之后,輸出方波: