本文主要是介绍声波通信开源项SinVoice介绍三,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
前两篇介绍了声波验证/通信的原理和声音播放的实现,这一篇将介绍最重要,也是最难懂的东西,就是SinVoice是如何对这些数字进行编码传输的。
因为源代码中加入了大量的难以区分的回调函数,为了阅读方便,我进行了部分的重命名和代码的整理,大家不要感到诧异。
首先给出项目的结构:
这篇文章重点介绍是Encoder类、SinGenerator类,Buffer类。
在前面的文章中,我们了解到SinVoiceplayer是我们直接接触和使用的类,使用SinVoicePlayer.play(text)方法就可以非常容易的播放出我们想要传输的数字的对应音频信号,然后进行解析即可。在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
原文地址:http://blog.csdn.net/zhaokaiqiang1992
这篇关于声波通信开源项SinVoice介绍三的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!