安卓手把手教你學習並實現 安卓耳機口音訊轉紅外發射
安卓實現耳機口音訊轉紅外發射
前一段時間因為找工作,完了之後又有兩個專案做,一個 BLE4.0 的專案,一個紅外控制的專案,因此也好久沒寫文章了。BLE4.0 的資料網上一抓一大把,就不多說了。
雖說紅外很早就開始火了,從最早的遙控器,到紅外測距等等,但是網上關於 Android 紅外開發的相關資料幾乎沒有。那就只能硬著頭皮自己上。
手機自帶紅外有 ConsumerIrManager 類,很好用,略過。而我們今天看的是另一種紅外發送方式:音訊轉紅外。
1、相關知識介紹:
這是網上找的格力空調的開機短碼,將這些數字理解成一個 TA 自己規定的協議,9000,4500 為幀頭,560,1690 代表 1,560,560 代表 0。先不管幀頭,剩下的翻譯過來就是{1,0,1,0 , 1,0,1,0,0,1,0,1,0,1,0,1},再翻譯為16進位制,即為0xAA,0x55,這是一個開機命令。
取樣率 44100:通俗理解就是 在1s內在一條連續的正玄波上面採集 44100 個點。
載波 38KHZ: 即為我們發出的音訊訊號需要放在 38KHZ 的載波上才能傳送出去被紅外接收頭接收。
音訊轉紅外要做的就是生成 PCM(單/雙聲道)資料,即為音訊資料,按照硬體支援的 NEC 協議,指定取樣率,指定載波,使用 Android SDK 中的 audioTrack 類播放這段音訊即可。
注:多媒體基礎知識之PCM資料,這裡面有 PCM 相關的知識,包括取樣率、載波等。
注:硬體自己焊接或者淘寶:android 音訊紅外發射頭,附焊接教程
2、音訊除錯:
音訊除錯我使用的電腦軟體 cooledit,百度一下就有免費的,再加一根3.5mm 的公對公耳機線。
-
step1:使用遙控精靈搜到你的空調型號。
-
step2:公頭線一頭插耳機口,一頭插電腦音訊 mic 口,手機音量調到最大,並在電腦上開啟 cooledit 軟體。
-
step3:點選 cooledit 的錄音鍵,選擇取樣率 44100,雙聲道,16 位。開啟遙控精靈,打到你的空調按鈕面板,連續點選幾次開機鍵。
-
step4:可以看到 cooledit 軟體介面上有一些綠色的聲音波形。
上面這張圖片是我抓的遙控精靈發出的電平訊號,可以看到 9ms 的高電平和 4.5ms 的低電平,雖然看到這兒是方波,但是再往後其實 TA 也是正玄波。高電平這兒全部是標準的正玄波組成的,寬度為時間寬度 9ms。
那對於我們來說只要仿造出圖中那樣的波形,9ms高、4.5ms低………….,即可和遙控精靈一樣控制我們的空調了。
3、仿造波形:
要仿造一段 20KHZ(因為耳機口只能輸出這麼大),取樣率 44100 , 16 位雙聲道的 PCM 音訊資料,網上還是有點資料可尋的。
目前,我沒有找到輸出方波的方法,再加上經過對遙控精靈輸出波形的觀察,也是輸出的正玄波,所以就放心的輸出 Sin 正玄波吧。
必要了解 ①:
buffSize = AudioTrack.getMinBufferSize(this.sampleRate,
AudioFormat.CHANNEL_OUT_STEREO,
AudioFormat.ENCODING_PCM_16BIT) * 4;
- 1
- 2
- 3
AudioTrack:音訊播放類。
sampleRate:取樣率。
AudioFormat.CHANNEL_OUT_STEREO:雙聲道輸出,即為立體聲,但同時也增加了檔案大小。
AudioFormat.ENCODING_PCM_16BIT:16位,一個取樣點佔16位。但同時也增加了檔案大小。
必要了解 ②:
y(t) = A * sin (ωt + φ)
//ω即為角速度,在一段週期內轉過了多少角度。
//T為週期。
//f為頻率。
ω = 2π/T = 2πf
y(t) = A * sin (2πft + φ)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
正玄波重要函式,
- A: 振幅,這裡為1;
- f : 頻率,這裡為 freqOfTone;(即為19000HZ)
- t: 時間,這裡為 (i/sampleRate);(當i為44100時是不是就是1s了)
- φ: 相位,這裡為0;
表示下來就是這樣:
sample[i] = Math.sin(2 * Math.PI * i * (freqOfTone /sampleRate));
- 1
必要了解 ③:
audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
this.sampleRate, AudioFormat.CHANNEL_OUT_STEREO,
AudioFormat.ENCODING_PCM_16BIT, bytes1.length,
AudioTrack.MODE_STREAM);
- 1
- 2
- 3
- 4
STREAM_MUSIC:播放型別,有 Alerm、Notification 等。
AudioTrack.MODE_STREAM:MODE 有 STREAM 和 STATIC 兩種。STREAM 型別意味著音訊可以被連續播放,只需要一直往緩衝池寫即可。STATIC 通常用於播放遊戲音等,適合短小音訊。
bytes1.length:一次可播放音訊檔案的緩衝池大小。
核心程式碼片段:
for (final double dVal : sample) {
final short val = (short) ((dVal * 32767));
final short val_minus = (short) -val;
//左聲道
generatedSnd[idx] = (byte) (val & 0x00ff);
generatedSnd[idx+1] = (byte) ((val & 0xff00) >>> 8);
//16位雙聲道 右聲道
generatedSnd[idx+2] = (byte) (val_minus & 0x00ff);
generatedSnd[idx+3] = (byte) ((val_minus & 0xff00) >>> 8);
idx=idx+4;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
程式碼解釋:
上面第2行和第3行是將振幅縮放到最大振幅(32767是16位整數的最大值)。
上面第5、6行和第8、9行是填充PCM資料,上面有文章講了PCM資料格式,在16位wav PCM中,低位元組到高位元組:
樣本大小 | 資料格式 | 最小值 | 最大值 |
---|---|---|---|
8位PCM | int | -128 | 127 |
16位PCM | int | -32768 | 32767 |
填充完畢後,我們就有了完整的正玄波資料。(即高電平正玄波)
List<Byte> listByte = new ArrayList<>();
for (int j = 0; j < patterns.length; j++) {
int d=patterns[j];
final int points = (int) ((((double) d / 1000000.0) * sampleRate)*4);
if (j % 2 == 0) {
for (int i = 0; i < points; i++) {
listByte.add(generatedSnd[i]);
}
} else {
for (int i = 0; i < points; i++) {
listByte.add((byte) 0);
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
程式碼解釋:
patterns 即為我們要傳送的電平陣列,9000 , 4500… 那個。
看第4行,9000是 μs,9000/1000000,是將 μs 轉化為秒,再乘sampleRate 即為在9000μs 這段時間內佔有多少個取樣點。
因為9000為高電平,4500為低電平,再接下來又為高電平,然後又是低電平….所以偶數位為高電平 ,所以偶數位上這 points 個點都為高電平,到奇數位了這 points 個點都為低電平,低電平使用0表示即可。然後將所有的點拼裝到一起,組成完成的PCM資料,使用 AudioTrack 播放即可。
try {
audioTrack.play();
} catch (IllegalStateException e) {
LogUtil.e( e.getMessage());
}
audioTrack.write(listByte, 0, listByte.length);
- 1
- 2
- 3
- 4
- 5
- 6
格力空調的控制碼網上一搜一大堆,在這裡我不會開放原始碼,核心程式碼已經給出了,自己好好理解理解,分析分析,就可以自己寫出來了。
這個也是自己花了好長時間才搞定的,所以請尊重他人勞動成果,不做伸手黨。當然有不明白的可以在下面留言,歡迎交流,共同學習。
歡迎交流。