zoukankan      html  css  js  c++  java
  • 【Android开发经验】移动设备的“声波通信/验证”的实现——SinVoice开源项目介绍(三)

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

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

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

        首先给出项目的结构:

        


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

        在前面的文章中,我们了解到SinVoiceplayer是我们直接接触和使用的类,使用SinVoicePlayer.play(text)方法就能够非常easy的播放出我们想要传输的数字的相应音频信号。然后进行解析就可以。

    在SinVoicePlayer中,是通过调用PcmPlayer的start()方法,进行播放操作的,而在pcmPlayer中,则是又调用AudioTrace来实现终于的音频播放功能。

    通过对AudioTrace的一层层封装,终于实现了SinVoicePlayer的简单调用。

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

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

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

    /*
     * Copyright (C) 2013 gujicheng
     * 
     * Licensed under the GPL License Version 2.0;
     * you may not use this file except in compliance with the License.
     * 
     * If you have any question, please contact me.
     * 
     *************************************************************************
     **                   Author information                                **
     *************************************************************************
     ** Email: gujicheng197@126.com                                         **
     ** QQ   : 29600731                                                     **
     ** Weibo: http://weibo.com/gujicheng197                                **
     *************************************************************************
     */
    package com.libra.sinvoice;
    
    import java.util.List;
    
    import com.libra.sinvoice.Buffer.BufferData;
    
    /**
     * 
     * @ClassName: com.libra.sinvoice.Encoder
     * @Description: 编码器
     * @author zhaokaiqiang
     * @date 2014-11-16 下午1:32:17
     * 
     */
    public class Encoder implements SinGenerator.SinGeneratorCallback {
    
    	private final static String TAG = "Encoder";
    	private final static int STATE_ENCODING = 1;
    	private final static int STATE_STOPED = 2;
    
    	// index 0, 1, 2, 3, 4, 5, 6
    	// circleCount 31, 28, 25, 22, 19, 15, 10
    	private final static int[] CODE_FREQUENCY = { 1422, 1575, 1764, 2004, 2321,
    			2940, 4410 };
    	private int mState;
    
    	private SinGenerator mSinGenerator;
    
    	private EncoderCallback encoderCallback;
    
    	public static interface EncoderCallback {
    
    		void freeEncodeBuffer(BufferData buffer);
    
    		BufferData getEncodeBuffer();
    	}
    
    	public Encoder(EncoderCallback callback, int sampleRate, int bits,
    			int bufferSize) {
    		encoderCallback = callback;
    		mState = STATE_STOPED;
    		mSinGenerator = new SinGenerator(this, sampleRate, bits, bufferSize);
    	}
    
    	public final static int getMaxCodeCount() {
    		return CODE_FREQUENCY.length;
    	}
    
    	public final boolean isStoped() {
    		return (STATE_STOPED == mState);
    	}
    
    	// content of input from 0 to (CODE_FREQUENCY.length-1)
    	public void encode(List<Integer> codes, int duration) {
    		if (STATE_STOPED == mState) {
    			mState = STATE_ENCODING;
    
    			mSinGenerator.start();
    			for (int index : codes) {
    				if (STATE_ENCODING == mState) {
    					if (index >= 0 && index < CODE_FREQUENCY.length) {
    						// 使用正弦发生器编码
    						mSinGenerator.gen(CODE_FREQUENCY[index], duration);
    					} else {
    						LogHelper.d(TAG, "code index error");
    					}
    				} else {
    					LogHelper.d(TAG, "encode force stop");
    					break;
    				}
    			}
    			mSinGenerator.stop();
    
    		}
    	}
    
    	public void stop() {
    		if (STATE_ENCODING == mState) {
    			mState = STATE_STOPED;
    			mSinGenerator.stop();
    		}
    	}
    
    	@Override
    	public BufferData getGenBuffer() {
    		if (null != encoderCallback) {
    			return encoderCallback.getEncodeBuffer();
    		}
    		return null;
    	}
    
    	@Override
    	public void freeGenBuffer(BufferData buffer) {
    		if (null != encoderCallback) {
    			encoderCallback.freeEncodeBuffer(buffer);
    		}
    	}
    }
    

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

        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的代码实现

    /*
     * Copyright (C) 2013 gujicheng
     * 
     * Licensed under the GPL License Version 2.0;
     * you may not use this file except in compliance with the License.
     * 
     * If you have any question, please contact me.
     * 
     *************************************************************************
     **                   Author information                                **
     *************************************************************************
     ** Email: gujicheng197@126.com                                         **
     ** QQ   : 29600731                                                     **
     ** Weibo: http://weibo.com/gujicheng197                                **
     *************************************************************************
     */
    package com.libra.sinvoice;
    
    import com.libra.sinvoice.Buffer.BufferData;
    
    /**
     * 
     * @ClassName: com.libra.sinvoice.SinGenerator
     * @Description: 正弦波发生器
     * @author zhaokaiqiang
     * @date 2014-11-15 下午2:51:34
     * 
     */
    public class SinGenerator {
    
    	private static final String TAG = "SinGenerator";
    
    	private static final int STATE_START = 1;
    	private static final int STATE_STOP = 2;
    
    	// 2^8时的峰值
    	public static final int BITS_8 = 128;
    	// 默觉得2^16时的峰值
    	public static final int BITS_16 = 32768;
    
    	// 採样率
    	public static final int SAMPLE_RATE_8 = 8000;
    	public static final int SAMPLE_RATE_11 = 11250;
    	public static final int SAMPLE_RATE_16 = 16000;
    
    	public static final int UNIT_ACCURACY_1 = 4;
    	public static final int UNIT_ACCURACY_2 = 8;
    
    	private int mState;
    	private int mSampleRate;
    	private int mBits;
    
    	private static final int DEFAULT_BITS = BITS_8;
    	private static final int DEFAULT_SAMPLE_RATE = SAMPLE_RATE_8;
    	private static final int DEFAULT_BUFFER_SIZE = 1024;
    
    	private int mFilledSize;
    	private int mBufferSize;
    	private SinGeneratorCallback sinGeneratorCallback;
    
    	public static interface SinGeneratorCallback {
    
    		BufferData getGenBuffer();
    
    		void freeGenBuffer(BufferData buffer);
    	}
    
    	public SinGenerator(SinGeneratorCallback callback) {
    		this(callback, DEFAULT_SAMPLE_RATE, DEFAULT_BITS, DEFAULT_BUFFER_SIZE);
    	}
    
    	public SinGenerator(SinGeneratorCallback callback, int sampleRate,
    			int bits, int bufferSize) {
    		sinGeneratorCallback = callback;
    
    		mBufferSize = bufferSize;
    		mSampleRate = sampleRate;
    		mBits = bits;
    
    		mFilledSize = 0;
    		mState = STATE_STOP;
    	}
    
    	public void stop() {
    		if (STATE_START == mState) {
    			mState = STATE_STOP;
    		}
    	}
    
    	public void start() {
    		if (STATE_STOP == mState) {
    			mState = STATE_START;
    		}
    	}
    
    	/**
    	 * 对数字进行编码
    	 * 
    	 * @param genRate
    	 * @param duration
    	 */
    	public void gen(int genRate, int duration) {
    		if (STATE_START == mState) {
    
    			// 定值16384
    			int n = mBits / 2;
    			int totalCount = (duration * mSampleRate) / 1000;
    			double per = (genRate / (double) mSampleRate) * 2 * Math.PI;
    			double d = 0;
    
    			LogHelper.d(TAG, "per:" + per + "___genRate:" + genRate);
    			if (null != sinGeneratorCallback) {
    				mFilledSize = 0;
    				// 获取要编码的数据
    				BufferData bufferData = sinGeneratorCallback.getGenBuffer();
    				if (null != bufferData) {
    					for (int i = 0; i < totalCount; ++i) {
    
    						if (STATE_START == mState) {
    
    							// 算出不同点的正弦值
    							int out = (int) (Math.sin(d) * n) + 128;
    
    							// 假设填充数量超过了缓冲区的大小,就重置mFilledSize,释放bufferData
    							if (mFilledSize >= mBufferSize - 1) {
    								// free buffer
    								bufferData.setFilledSize(mFilledSize);
    								sinGeneratorCallback.freeGenBuffer(bufferData);
    
    								mFilledSize = 0;
    								bufferData = sinGeneratorCallback
    										.getGenBuffer();
    								if (null == bufferData) {
    									LogHelper.d(TAG, "get null buffer");
    									break;
    								}
    							}
    
    							// 转码为byte类型并保存。& 0xff是为了防止负数转换出现异常
    							bufferData.byteData[mFilledSize++] = (byte) (out & 0xff);
    							if (BITS_16 == mBits) {
    								bufferData.byteData[mFilledSize++] = (byte) ((out >> 8) & 0xff);
    							}
    
    							d += per;
    						} else {
    							LogHelper.d(TAG, "sin gen force stop");
    							break;
    						}
    					}
    				} else {
    					LogHelper.d(TAG, "get null buffer");
    				}
    
    				if (null != bufferData) {
    					bufferData.setFilledSize(mFilledSize);
    					sinGeneratorCallback.freeGenBuffer(bufferData);
    				}
    				mFilledSize = 0;
    
    			}
    		}
    	}
    }
    

        最基本的方法就是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 / (double) mSampleRate) * 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类,这个类是对字节数据进行存储的类。用来保存编码完毕之后的字节数据。以下我们简单看下这个类的代码

    /*
     * Copyright (C) 2013 gujicheng
     * 
     * Licensed under the GPL License Version 2.0;
     * you may not use this file except in compliance with the License.
     * 
     * If you have any question, please contact me.
     * 
     *************************************************************************
     **                   Author information                                **
     *************************************************************************
     ** Email: gujicheng197@126.com                                         **
     ** QQ   : 29600731                                                     **
     ** Weibo: http://weibo.com/gujicheng197                                **
     *************************************************************************
     */
    package com.libra.sinvoice;
    
    import java.util.concurrent.BlockingQueue;
    import java.util.concurrent.LinkedBlockingQueue;
    
    /**
     * 
     * @ClassName: com.libra.sinvoice.Buffer
     * @Description: 缓冲器
     * @author zhaokaiqiang
     * @date 2014-11-15 下午1:35:46
     * 
     */
    public class Buffer {
    
    	private final static String TAG = "Buffer";
    	// 生产者队列
    	private BlockingQueue<BufferData> mProducerQueue;
    	// 消费者队列
    	private BlockingQueue<BufferData> mConsumeQueue;
    	// 缓冲区数量
    	private int mBufferCount;
    	// 缓冲区体积
    	private int mBufferSize;
    
    	public Buffer() {
    		this(Common.DEFAULT_BUFFER_COUNT, Common.DEFAULT_BUFFER_SIZE);
    	}
    
    	public Buffer(int bufferCount, int bufferSize) {
    
    		mBufferSize = bufferSize;
    		mBufferCount = bufferCount;
    		mProducerQueue = new LinkedBlockingQueue<BufferData>(mBufferCount);
    		// we want to put the end buffer, so need to add 1
    		mConsumeQueue = new LinkedBlockingQueue<BufferData>(mBufferCount + 1);
    
    		// 初始化生产者队列
    		for (int i = 0; i < mBufferCount; ++i) {
    			try {
    				mProducerQueue.put(new BufferData(mBufferSize));
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    
    	public void reset() {
    		// 将生产者的空头结点剔除
    		int size = mProducerQueue.size();
    		for (int i = 0; i < size; ++i) {
    			BufferData data = mProducerQueue.peek();
    			if (null == data || null == data.byteData) {
    				mProducerQueue.poll();
    			}
    		}
    
    		// 将消费者中的非空数据增加到生产者其中
    		size = mConsumeQueue.size();
    		for (int i = 0; i < size; ++i) {
    			BufferData data = mConsumeQueue.poll();
    			if (null != data && null != data.byteData) {
    				mProducerQueue.add(data);
    			}
    		}
    
    		LogHelper.d(TAG, "reset ProducerQueue Size:" + mProducerQueue.size()
    				+ "    ConsumeQueue Size:" + mConsumeQueue.size());
    	}
    
    	final public int getEmptyCount() {
    		return mProducerQueue.size();
    	}
    
    	final public int getFullCount() {
    		return mConsumeQueue.size();
    	}
    
    	// 获取生产者的头结点,堵塞式
    	public BufferData getEmpty() {
    		return getImpl(mProducerQueue);
    	}
    
    	// 增加到生产者中
    	public boolean putEmpty(BufferData data) {
    		return putImpl(data, mProducerQueue);
    	}
    
    	// 获取消费者的头结点
    	public BufferData getFull() {
    		return getImpl(mConsumeQueue);
    	}
    
    	// 增加到消费者中
    	public boolean putFull(BufferData data) {
    		return putImpl(data, mConsumeQueue);
    	}
    
    	// 获取队列的头结点
    	private BufferData getImpl(BlockingQueue<BufferData> queue) {
    		if (null != queue) {
    			try {
    				return queue.take();
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    		return null;
    	}
    
    	// 将数据增加到队列中
    	private boolean putImpl(BufferData data, BlockingQueue<BufferData> queue) {
    		if (null != queue && null != data) {
    			try {
    				queue.put(data);
    				return true;
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    		return false;
    	}
    
    	// when mData is null, means it is end of input
    	public static class BufferData {
    		// 数据容器
    		public byte byteData[];
    		// 填充体积
    		private int mFilledSize;
    		// 缓冲最大体积
    		private int mMaxBufferSize;
    		// 静态空缓冲区
    		private static BufferData sEmptyBuffer = new BufferData(0);
    
    		public BufferData(int maxBufferSize) {
    
    			mMaxBufferSize = maxBufferSize;
    			mFilledSize = 0;
    
    			if (maxBufferSize > 0) {
    				byteData = new byte[mMaxBufferSize];
    			} else {
    				byteData = null;
    			}
    		}
    
    		/**
    		 * 获取空的缓冲区
    		 * 
    		 * @return
    		 */
    		public static BufferData getEmptyBuffer() {
    			return sEmptyBuffer;
    		}
    
    		// 重置填充数量
    		final public void reset() {
    			mFilledSize = 0;
    		}
    
    		final public int getMaxBufferSize() {
    			return mMaxBufferSize;
    		}
    
    		// 设置填充数量
    		final public void setFilledSize(int size) {
    			mFilledSize = size;
    		}
    
    		final public int getFilledSize() {
    			return mFilledSize;
    		}
    	}
    
    }
    

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

        

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

  • 相关阅读:
    ThinkPHP5如何引用新建的配置文件?
    MySQL与SQLServer的update left join语法区别
    如何POST一个JSON格式的数据给java接口,获得返回数据
    如何使用ThinkPHP5 ,自动生成目录?
    LeetCode347_TopK
    数据结构算法Review1_堆
    OJ_3_字符串数字相互转换
    C++Review11_指针数组和数组指针
    LeetCode15_三数之和
    LeetCode1_两数之和
  • 原文地址:https://www.cnblogs.com/mthoutai/p/7146617.html
Copyright © 2011-2022 走看看