移动设备的“声波通信/验证”的实现——SinVoice开源项目介绍(三)

来源:互联网 发布:湖北知行科技有限公司 编辑:程序博客网 时间:2024/04/29 16:33

 

    前两篇介绍了声波验证/通信的原理和声音播放的实现,这一篇将介绍最重要,也是最难懂的东西,就是SinVoice是如何对这些数字进行编码传输的。

    因为源代码中加入了大量的难以区分的回调函数,为了阅读方便,我进行了部分的重命名和代码的整理,大家不要感到诧异。

    首先给出项目的结构:

    


    这篇文章重点介绍是Encoder类、SinGenerator类,Buffer类。

    在前面的文章中,我们了解到SinVoiceplayer是我们直接接触和使用的类,使用SinVoicePlayer.play(text)方法就可以非常容易的播放出我们想要传输的数字的对应音频信号,然后进行解析即可。在SinVoicePlayer中,是通过调用PcmPlayer的start()方法,进行播放操作的,而在pcmPlayer中,则是又调用AudioTrace来实现最终的音频播放功能。通过对AudioTrace的一层层封装,最终实现了SinVoicePlayer的简单调用。

    既然AudioTrace是最终进行音频播放的类,那么,要进行播放的数据从哪里来的呢?

    答案就是,数据来自Encoder类、SinGenerator类和Buffer类。

    下面是Encoder的代码,代码经过了整理

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /* 
  2.  * Copyright (C) 2013 gujicheng 
  3.  *  
  4.  * Licensed under the GPL License Version 2.0; 
  5.  * you may not use this file except in compliance with the License. 
  6.  *  
  7.  * If you have any question, please contact me. 
  8.  *  
  9.  ************************************************************************* 
  10.  **                   Author information                                ** 
  11.  ************************************************************************* 
  12.  ** Email: gujicheng197@126.com                                         ** 
  13.  ** QQ   : 29600731                                                     ** 
  14.  ** Weibo: http://weibo.com/gujicheng197                                ** 
  15.  ************************************************************************* 
  16.  */  
  17. package com.libra.sinvoice;  
  18.   
  19. import java.util.List;  
  20.   
  21. import com.libra.sinvoice.Buffer.BufferData;  
  22.   
  23. /** 
  24.  *  
  25.  * @ClassName: com.libra.sinvoice.Encoder 
  26.  * @Description: 编码器 
  27.  * @author zhaokaiqiang 
  28.  * @date 2014-11-16 下午1:32:17 
  29.  *  
  30.  */  
  31. public class Encoder implements SinGenerator.SinGeneratorCallback {  
  32.   
  33.     private final static String TAG = "Encoder";  
  34.     private final static int STATE_ENCODING = 1;  
  35.     private final static int STATE_STOPED = 2;  
  36.   
  37.     // index 0, 1, 2, 3, 4, 5, 6  
  38.     // circleCount 31, 28, 25, 22, 19, 15, 10  
  39.     private final static int[] CODE_FREQUENCY = { 14221575176420042321,  
  40.             29404410 };  
  41.     private int mState;  
  42.   
  43.     private SinGenerator mSinGenerator;  
  44.   
  45.     private EncoderCallback encoderCallback;  
  46.   
  47.     public static interface EncoderCallback {  
  48.   
  49.         void freeEncodeBuffer(BufferData buffer);  
  50.   
  51.         BufferData getEncodeBuffer();  
  52.     }  
  53.   
  54.     public Encoder(EncoderCallback callback, int sampleRate, int bits,  
  55.             int bufferSize) {  
  56.         encoderCallback = callback;  
  57.         mState = STATE_STOPED;  
  58.         mSinGenerator = new SinGenerator(this, sampleRate, bits, bufferSize);  
  59.     }  
  60.   
  61.     public final static int getMaxCodeCount() {  
  62.         return CODE_FREQUENCY.length;  
  63.     }  
  64.   
  65.     public final boolean isStoped() {  
  66.         return (STATE_STOPED == mState);  
  67.     }  
  68.   
  69.     // content of input from 0 to (CODE_FREQUENCY.length-1)  
  70.     public void encode(List<Integer> codes, int duration) {  
  71.         if (STATE_STOPED == mState) {  
  72.             mState = STATE_ENCODING;  
  73.   
  74.             mSinGenerator.start();  
  75.             for (int index : codes) {  
  76.                 if (STATE_ENCODING == mState) {  
  77.                     if (index >= 0 && index < CODE_FREQUENCY.length) {  
  78.                         // 使用正弦发生器编码  
  79.                         mSinGenerator.gen(CODE_FREQUENCY[index], duration);  
  80.                     } else {  
  81.                         LogHelper.d(TAG, "code index error");  
  82.                     }  
  83.                 } else {  
  84.                     LogHelper.d(TAG, "encode force stop");  
  85.                     break;  
  86.                 }  
  87.             }  
  88.             mSinGenerator.stop();  
  89.   
  90.         }  
  91.     }  
  92.   
  93.     public void stop() {  
  94.         if (STATE_ENCODING == mState) {  
  95.             mState = STATE_STOPED;  
  96.             mSinGenerator.stop();  
  97.         }  
  98.     }  
  99.   
  100.     @Override  
  101.     public BufferData getGenBuffer() {  
  102.         if (null != encoderCallback) {  
  103.             return encoderCallback.getEncodeBuffer();  
  104.         }  
  105.         return null;  
  106.     }  
  107.   
  108.     @Override  
  109.     public void freeGenBuffer(BufferData buffer) {  
  110.         if (null != encoderCallback) {  
  111.             encoderCallback.freeEncodeBuffer(buffer);  
  112.         }  
  113.     }  
  114. }  

    关于这个类,主要是以下几点:

    1.这个类实现了SinGenerator.SinGeneratorCallback接口,这个接口其实是SinGenerator类里面的,这个接口主要完成的数据的获取与释放,在下面的代码中我将会说明

    2.数组CODE_FREQUENCY中存放的数字分别代表从0到6对应的频率,不同的数据会根据不同的频率进行编码,circleCount 31, 28, 25, 22, 19, 15, 10 是指在编码的过程中,对应频率的正弦波在一个周期内的取样数量,每个周期的取样数量x周期总数=总取样数。

    还记得之前的DEFAULT_GEN_DURATION=100吗,这个变量指的就是每一个数字对应的音频持续的时间,100代表100毫秒,也就是0.1秒。我们说过,默认的采样率使用的是44.1KHZ,这是1s内采样44100次,如果需要播放100毫秒,那么就只需要采样44100/10=4410次,因为

private final static int[] CODE_FREQUENCY = { 1422, 1575, 1764, 2004, 2321,2940, 4410 };

    假如我们想给数字0编码,那么我们知道0对应的振动频率就是1422HZ,这个也是一秒钟的,如果是100ms呢?就是142.2HZ,我们使用,142.2x31=4408.2,接近4410次,所以说,我们根据想要生成的音频的频率,就能知道每一个周期内需要采样多少次,就能算出采样的间隔。因为Encoder也只是一个包装类,真正实现编码的是SinGenerator,在这个累里面,我们就可以看到很多的加密细节。

   下面是SinGenerator的代码实现

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /* 
  2.  * Copyright (C) 2013 gujicheng 
  3.  *  
  4.  * Licensed under the GPL License Version 2.0; 
  5.  * you may not use this file except in compliance with the License. 
  6.  *  
  7.  * If you have any question, please contact me. 
  8.  *  
  9.  ************************************************************************* 
  10.  **                   Author information                                ** 
  11.  ************************************************************************* 
  12.  ** Email: gujicheng197@126.com                                         ** 
  13.  ** QQ   : 29600731                                                     ** 
  14.  ** Weibo: http://weibo.com/gujicheng197                                ** 
  15.  ************************************************************************* 
  16.  */  
  17. package com.libra.sinvoice;  
  18.   
  19. import com.libra.sinvoice.Buffer.BufferData;  
  20.   
  21. /** 
  22.  *  
  23.  * @ClassName: com.libra.sinvoice.SinGenerator 
  24.  * @Description: 正弦波发生器 
  25.  * @author zhaokaiqiang 
  26.  * @date 2014-11-15 下午2:51:34 
  27.  *  
  28.  */  
  29. public class SinGenerator {  
  30.   
  31.     private static final String TAG = "SinGenerator";  
  32.   
  33.     private static final int STATE_START = 1;  
  34.     private static final int STATE_STOP = 2;  
  35.   
  36.     // 2^8时的峰值  
  37.     public static final int BITS_8 = 128;  
  38.     // 默认为2^16时的峰值  
  39.     public static final int BITS_16 = 32768;  
  40.   
  41.     // 采样率  
  42.     public static final int SAMPLE_RATE_8 = 8000;  
  43.     public static final int SAMPLE_RATE_11 = 11250;  
  44.     public static final int SAMPLE_RATE_16 = 16000;  
  45.   
  46.     public static final int UNIT_ACCURACY_1 = 4;  
  47.     public static final int UNIT_ACCURACY_2 = 8;  
  48.   
  49.     private int mState;  
  50.     private int mSampleRate;  
  51.     private int mBits;  
  52.   
  53.     private static final int DEFAULT_BITS = BITS_8;  
  54.     private static final int DEFAULT_SAMPLE_RATE = SAMPLE_RATE_8;  
  55.     private static final int DEFAULT_BUFFER_SIZE = 1024;  
  56.   
  57.     private int mFilledSize;  
  58.     private int mBufferSize;  
  59.     private SinGeneratorCallback sinGeneratorCallback;  
  60.   
  61.     public static interface SinGeneratorCallback {  
  62.   
  63.         BufferData getGenBuffer();  
  64.   
  65.         void freeGenBuffer(BufferData buffer);  
  66.     }  
  67.   
  68.     public SinGenerator(SinGeneratorCallback callback) {  
  69.         this(callback, DEFAULT_SAMPLE_RATE, DEFAULT_BITS, DEFAULT_BUFFER_SIZE);  
  70.     }  
  71.   
  72.     public SinGenerator(SinGeneratorCallback callback, int sampleRate,  
  73.             int bits, int bufferSize) {  
  74.         sinGeneratorCallback = callback;  
  75.   
  76.         mBufferSize = bufferSize;  
  77.         mSampleRate = sampleRate;  
  78.         mBits = bits;  
  79.   
  80.         mFilledSize = 0;  
  81.         mState = STATE_STOP;  
  82.     }  
  83.   
  84.     public void stop() {  
  85.         if (STATE_START == mState) {  
  86.             mState = STATE_STOP;  
  87.         }  
  88.     }  
  89.   
  90.     public void start() {  
  91.         if (STATE_STOP == mState) {  
  92.             mState = STATE_START;  
  93.         }  
  94.     }  
  95.   
  96.     /** 
  97.      * 对数字进行编码 
  98.      *  
  99.      * @param genRate 
  100.      * @param duration 
  101.      */  
  102.     public void gen(int genRate, int duration) {  
  103.         if (STATE_START == mState) {  
  104.   
  105.             // 定值16384  
  106.             int n = mBits / 2;  
  107.             int totalCount = (duration * mSampleRate) / 1000;  
  108.             double per = (genRate / (double) mSampleRate) * 2 * Math.PI;  
  109.             double d = 0;  
  110.   
  111.             LogHelper.d(TAG, "per:" + per + "___genRate:" + genRate);  
  112.             if (null != sinGeneratorCallback) {  
  113.                 mFilledSize = 0;  
  114.                 // 获取要编码的数据  
  115.                 BufferData bufferData = sinGeneratorCallback.getGenBuffer();  
  116.                 if (null != bufferData) {  
  117.                     for (int i = 0; i < totalCount; ++i) {  
  118.   
  119.                         if (STATE_START == mState) {  
  120.   
  121.                             // 算出不同点的正弦值  
  122.                             int out = (int) (Math.sin(d) * n) + 128;  
  123.   
  124.                             // 如果填充数量超过了缓冲区的大小,就重置mFilledSize,释放bufferData  
  125.                             if (mFilledSize >= mBufferSize - 1) {  
  126.                                 // free buffer  
  127.                                 bufferData.setFilledSize(mFilledSize);  
  128.                                 sinGeneratorCallback.freeGenBuffer(bufferData);  
  129.   
  130.                                 mFilledSize = 0;  
  131.                                 bufferData = sinGeneratorCallback  
  132.                                         .getGenBuffer();  
  133.                                 if (null == bufferData) {  
  134.                                     LogHelper.d(TAG, "get null buffer");  
  135.                                     break;  
  136.                                 }  
  137.                             }  
  138.   
  139.                             // 转码为byte类型并保存,& 0xff是为了防止负数转换出现异常  
  140.                             bufferData.byteData[mFilledSize++] = (byte) (out & 0xff);  
  141.                             if (BITS_16 == mBits) {  
  142.                                 bufferData.byteData[mFilledSize++] = (byte) ((out >> 8) & 0xff);  
  143.                             }  
  144.   
  145.                             d += per;  
  146.                         } else {  
  147.                             LogHelper.d(TAG, "sin gen force stop");  
  148.                             break;  
  149.                         }  
  150.                     }  
  151.                 } else {  
  152.                     LogHelper.d(TAG, "get null buffer");  
  153.                 }  
  154.   
  155.                 if (null != bufferData) {  
  156.                     bufferData.setFilledSize(mFilledSize);  
  157.                     sinGeneratorCallback.freeGenBuffer(bufferData);  
  158.                 }  
  159.                 mFilledSize = 0;  
  160.   
  161.             }  
  162.         }  
  163.     }  
  164. }  

    最主要的方法就是gen(),我们对这个方法进行详细的解析。

    1int n = mBits / 2; 在这里定义的n,在后面的代码中参与了运算,n是指我们要创建的正弦函数的峰值,就是最高点的值,mBits的值是2^16/2=32768,在这里将峰值除以二,应该是为了识别率考虑,因为在将n直接赋值为mBits的时候,发出的声音较为尖锐,识别率降低很多,所以,这里选择了mBits/2最为峰值。

    2.int totalCount = (duration * mSampleRate) / 1000;这个是在计算要循环的次数,因为duration=100,所以采样的总次数是4410,循环执行4410次

    3.double per = (genRate / (doublemSampleRate) * 2 * Math.PI;这个per参数是用来记录在循环的过程中,每次往前步进的距离,这个是和频率相关的。我们以发出数字5为例,从Encoder类中,我们知道5对应的频率是2940HZ,如果我们要声音播放100ms,那么就需要震动294次,也就是294个正弦周期。而这294次,根据44.1KHZ的频率,也就是100ms采样4410次的频率,就可以算出在每个周期里面,需要采样4410/294=15,所以一个周期内采样数量是15次,而一个正弦周期的长度是2PI,所以,使用2PI/15=0.4186,这个值就是这里的per值。

    4.int out = (int) (Math.sin(d) * n) + 128; 在计算出per之后,在循环中使用变量d对每次的per进行了累加,然后使用前面的计算公式,就可以计算出在采样点处对应的函数值,在完成下面的操作之后,就实现了数字的编码

// 转码为byte类型并保存,& 0xff是为了防止负数转换出现异常

bufferData.byteData[mFilledSize++] = (byte) (out & 0xff);

if (BITS_16 == mBits) {

bufferData.byteData[mFilledSize++] = (byte) ((out >> 8) & 0xff);

}


    我们看到,在保存编码的时候,使用了Buffre类,这个类是对字节数据进行存储的类,用来保存编码完成之后的字节数据,下面我们简单看下这个类的代码

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /* 
  2.  * Copyright (C) 2013 gujicheng 
  3.  *  
  4.  * Licensed under the GPL License Version 2.0; 
  5.  * you may not use this file except in compliance with the License. 
  6.  *  
  7.  * If you have any question, please contact me. 
  8.  *  
  9.  ************************************************************************* 
  10.  **                   Author information                                ** 
  11.  ************************************************************************* 
  12.  ** Email: gujicheng197@126.com                                         ** 
  13.  ** QQ   : 29600731                                                     ** 
  14.  ** Weibo: http://weibo.com/gujicheng197                                ** 
  15.  ************************************************************************* 
  16.  */  
  17. package com.libra.sinvoice;  
  18.   
  19. import java.util.concurrent.BlockingQueue;  
  20. import java.util.concurrent.LinkedBlockingQueue;  
  21.   
  22. /** 
  23.  *  
  24.  * @ClassName: com.libra.sinvoice.Buffer 
  25.  * @Description: 缓冲器 
  26.  * @author zhaokaiqiang 
  27.  * @date 2014-11-15 下午1:35:46 
  28.  *  
  29.  */  
  30. public class Buffer {  
  31.   
  32.     private final static String TAG = "Buffer";  
  33.     // 生产者队列  
  34.     private BlockingQueue<BufferData> mProducerQueue;  
  35.     // 消费者队列  
  36.     private BlockingQueue<BufferData> mConsumeQueue;  
  37.     // 缓冲区数量  
  38.     private int mBufferCount;  
  39.     // 缓冲区体积  
  40.     private int mBufferSize;  
  41.   
  42.     public Buffer() {  
  43.         this(Common.DEFAULT_BUFFER_COUNT, Common.DEFAULT_BUFFER_SIZE);  
  44.     }  
  45.   
  46.     public Buffer(int bufferCount, int bufferSize) {  
  47.   
  48.         mBufferSize = bufferSize;  
  49.         mBufferCount = bufferCount;  
  50.         mProducerQueue = new LinkedBlockingQueue<BufferData>(mBufferCount);  
  51.         // we want to put the end buffer, so need to add 1  
  52.         mConsumeQueue = new LinkedBlockingQueue<BufferData>(mBufferCount + 1);  
  53.   
  54.         // 初始化生产者队列  
  55.         for (int i = 0; i < mBufferCount; ++i) {  
  56.             try {  
  57.                 mProducerQueue.put(new BufferData(mBufferSize));  
  58.             } catch (InterruptedException e) {  
  59.                 e.printStackTrace();  
  60.             }  
  61.         }  
  62.     }  
  63.   
  64.     public void reset() {  
  65.         // 将生产者的空头结点剔除  
  66.         int size = mProducerQueue.size();  
  67.         for (int i = 0; i < size; ++i) {  
  68.             BufferData data = mProducerQueue.peek();  
  69.             if (null == data || null == data.byteData) {  
  70.                 mProducerQueue.poll();  
  71.             }  
  72.         }  
  73.   
  74.         // 将消费者中的非空数据添加到生产者当中  
  75.         size = mConsumeQueue.size();  
  76.         for (int i = 0; i < size; ++i) {  
  77.             BufferData data = mConsumeQueue.poll();  
  78.             if (null != data && null != data.byteData) {  
  79.                 mProducerQueue.add(data);  
  80.             }  
  81.         }  
  82.   
  83.         LogHelper.d(TAG, "reset ProducerQueue Size:" + mProducerQueue.size()  
  84.                 + "    ConsumeQueue Size:" + mConsumeQueue.size());  
  85.     }  
  86.   
  87.     final public int getEmptyCount() {  
  88.         return mProducerQueue.size();  
  89.     }  
  90.   
  91.     final public int getFullCount() {  
  92.         return mConsumeQueue.size();  
  93.     }  
  94.   
  95.     // 获取生产者的头结点,阻塞式  
  96.     public BufferData getEmpty() {  
  97.         return getImpl(mProducerQueue);  
  98.     }  
  99.   
  100.     // 加入到生产者中  
  101.     public boolean putEmpty(BufferData data) {  
  102.         return putImpl(data, mProducerQueue);  
  103.     }  
  104.   
  105.     // 获取消费者的头结点  
  106.     public BufferData getFull() {  
  107.         return getImpl(mConsumeQueue);  
  108.     }  
  109.   
  110.     // 加入到消费者中  
  111.     public boolean putFull(BufferData data) {  
  112.         return putImpl(data, mConsumeQueue);  
  113.     }  
  114.   
  115.     // 获取队列的头结点  
  116.     private BufferData getImpl(BlockingQueue<BufferData> queue) {  
  117.         if (null != queue) {  
  118.             try {  
  119.                 return queue.take();  
  120.             } catch (InterruptedException e) {  
  121.                 e.printStackTrace();  
  122.             }  
  123.         }  
  124.         return null;  
  125.     }  
  126.   
  127.     // 将数据加入到队列中  
  128.     private boolean putImpl(BufferData data, BlockingQueue<BufferData> queue) {  
  129.         if (null != queue && null != data) {  
  130.             try {  
  131.                 queue.put(data);  
  132.                 return true;  
  133.             } catch (InterruptedException e) {  
  134.                 e.printStackTrace();  
  135.             }  
  136.         }  
  137.         return false;  
  138.     }  
  139.   
  140.     // when mData is null, means it is end of input  
  141.     public static class BufferData {  
  142.         // 数据容器  
  143.         public byte byteData[];  
  144.         // 填充体积  
  145.         private int mFilledSize;  
  146.         // 缓冲最大体积  
  147.         private int mMaxBufferSize;  
  148.         // 静态空缓冲区  
  149.         private static BufferData sEmptyBuffer = new BufferData(0);  
  150.   
  151.         public BufferData(int maxBufferSize) {  
  152.   
  153.             mMaxBufferSize = maxBufferSize;  
  154.             mFilledSize = 0;  
  155.   
  156.             if (maxBufferSize > 0) {  
  157.                 byteData = new byte[mMaxBufferSize];  
  158.             } else {  
  159.                 byteData = null;  
  160.             }  
  161.         }  
  162.   
  163.         /** 
  164.          * 获取空的缓冲区 
  165.          *  
  166.          * @return 
  167.          */  
  168.         public static BufferData getEmptyBuffer() {  
  169.             return sEmptyBuffer;  
  170.         }  
  171.   
  172.         // 重置填充数量  
  173.         final public void reset() {  
  174.             mFilledSize = 0;  
  175.         }  
  176.   
  177.         final public int getMaxBufferSize() {  
  178.             return mMaxBufferSize;  
  179.         }  
  180.   
  181.         // 设置填充数量  
  182.         final public void setFilledSize(int size) {  
  183.             mFilledSize = size;  
  184.         }  
  185.   
  186.         final public int getFilledSize() {  
  187.             return mFilledSize;  
  188.         }  
  189.     }  
  190.   
  191. }  

    Buffer使用两个队列实现了生产者消费者模型,从而保证编译好一个,播放一个,在SinVoicePlayer类里面开了两个线程,分别对两个队列里面的数据进行管理。

    

    这个项目的Demo下载地址https://github.com/ZhaoKaiQiang/SinVoiceDemo


转载请注明出处:http://blog.csdn.net/zhaokaiqiang1992

0 0