###安卓实现耳机口音频转红外发射
前一段时间因为找工作,完了之后又有两个项目做,一个 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,这是一个开机命令。
注:红外遥控器原理和NEC协议,这里面有相关的知识,建议先阅读。
采样率 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;
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 + φ)
正玄波重要函数,
- A: 振幅,这里为1;
- f : 频率,这里为 freqOfTone;(即为19000HZ)
- t: 时间,这里为 (i/sampleRate);(当i为44100时是不是就是1s了)
- φ: 相位,这里为0;
表示下来就是这样:
sample[i] = Math.sin(2 * Math.PI * i * (freqOfTone /sampleRate));
必要了解 ③:
audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,this.sampleRate, AudioFormat.CHANNEL_OUT_STEREO,AudioFormat.ENCODING_PCM_16BIT, bytes1.length,AudioTrack.MODE_STREAM);
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;}
代码解释:
上面第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);}}}
代码解释:
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);
格力空调的控制码网上一搜一大堆,在这里我不会开放源代码,核心代码已经给出了,自己好好理解理解,分析分析,就可以自己写出来了。
这个也是自己花了好长时间才搞定的,所以请尊重他人劳动成果,不做伸手党。当然有不明白的可以在下面留言,欢迎交流,共同学习。
my QQ : 1003077897
my csdn:http://blog.csdn.net/u012534831
my gay:https://github.com/qht1003077897
欢迎交流。