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


    在上一篇的文章中,我们介绍了声波通信/验证的原理和基本使用,这一篇,我们将就一些细节进行谈论。

    再来一张项目的结构图

移动设备的“声波通信/验证”的实现——SinVoice开源项目介绍(二)_第1张图片


    SinVoicePlayer类是我们使用的时候直接接触的类,通过调用play()方法,我们就能将需要传输的数字播放出去,下面是这个类的代码实现

[java]  view plain copy
  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: [email protected]                                         ** 
  13.  ** QQ   : 29600731                                                     ** 
  14.  ** Weibo: http://weibo.com/gujicheng197                                ** 
  15.  ************************************************************************* 
  16.  */  
  17. package com.libra.sinvoice;  
  18.   
  19. import java.util.ArrayList;  
  20. import java.util.List;  
  21.   
  22. import android.media.AudioFormat;  
  23. import android.text.TextUtils;  
  24.   
  25. import com.libra.sinvoice.Buffer.BufferData;  
  26.   
  27. /** 
  28.  *  
  29.  * @ClassName: com.libra.sinvoice.SinVoicePlayer 
  30.  * @Description: 声音播放类 
  31.  * @author zhaokaiqiang 
  32.  * @date 2014-11-15 下午12:56:57 
  33.  *  
  34.  */  
  35. public class SinVoicePlayer implements Encoder.Listener, Encoder.Callback,  
  36.         PcmPlayer.Listener, PcmPlayer.Callback {  
  37.   
  38.     private final static String TAG = "SinVoicePlayer";  
  39.   
  40.     private final static int STATE_START = 1;  
  41.     private final static int STATE_STOP = 2;  
  42.     private final static int STATE_PENDING = 3;  
  43.   
  44.     // 默认的间隔时间  
  45.     private final static int DEFAULT_GEN_DURATION = 100;  
  46.   
  47.     private String mCodeBook;  
  48.     // 用于存放使用CoodBook编码过的数字  
  49.     private List mCodes = new ArrayList();  
  50.   
  51.     private Encoder mEncoder;  
  52.     private PcmPlayer mPlayer;  
  53.     private Buffer mBuffer;  
  54.   
  55.     private int mState;  
  56.     private Listener mListener;  
  57.     private Thread mPlayThread;  
  58.     private Thread mEncodeThread;  
  59.   
  60.     public static interface Listener {  
  61.   
  62.         void onPlayStart();  
  63.   
  64.         void onPlayEnd();  
  65.     }  
  66.   
  67.     public SinVoicePlayer() {  
  68.         this(Common.DEFAULT_CODE_BOOK);  
  69.     }  
  70.   
  71.     public SinVoicePlayer(String codeBook) {  
  72.         this(codeBook, Common.DEFAULT_SAMPLE_RATE, Common.DEFAULT_BUFFER_SIZE,  
  73.                 Common.DEFAULT_BUFFER_COUNT);  
  74.     }  
  75.   
  76.     /** 
  77.      * 构造函数 
  78.      *  
  79.      * @param codeBook 
  80.      * @param sampleRate 
  81.      *            采样率 
  82.      * @param bufferSize 
  83.      *            缓冲区体积 
  84.      * @param buffCount 
  85.      *            缓冲区数量 
  86.      */  
  87.     public SinVoicePlayer(String codeBook, int sampleRate, int bufferSize,  
  88.             int buffCount) {  
  89.   
  90.         mState = STATE_STOP;  
  91.         mBuffer = new Buffer(buffCount, bufferSize);  
  92.   
  93.         mEncoder = new Encoder(this, sampleRate, SinGenerator.BITS_16,  
  94.                 bufferSize);  
  95.         mEncoder.setListener(this);  
  96.         mPlayer = new PcmPlayer(this, sampleRate, AudioFormat.CHANNEL_OUT_MONO,  
  97.                 AudioFormat.ENCODING_PCM_16BIT, bufferSize);  
  98.         mPlayer.setListener(this);  
  99.   
  100.         setCodeBook(codeBook);  
  101.     }  
  102.   
  103.     public void setListener(Listener listener) {  
  104.         mListener = listener;  
  105.     }  
  106.   
  107.     public void setCodeBook(String codeBook) {  
  108.         if (!TextUtils.isEmpty(codeBook)  
  109.                 && codeBook.length() < Encoder.getMaxCodeCount() - 1) {  
  110.             mCodeBook = codeBook;  
  111.         }  
  112.     }  
  113.   
  114.     /** 
  115.      * 将要加密的文本根据CodeBook进行编码 
  116.      *  
  117.      * @param text 
  118.      * @return 是否编码成功 
  119.      */  
  120.     private boolean convertTextToCodes(String text) {  
  121.         boolean ret = true;  
  122.   
  123.         if (!TextUtils.isEmpty(text)) {  
  124.             mCodes.clear();  
  125.             mCodes.add(Common.START_TOKEN);  
  126.             int len = text.length();  
  127.             for (int i = 0; i < len; ++i) {  
  128.                 char ch = text.charAt(i);  
  129.                 int index = mCodeBook.indexOf(ch);  
  130.                 if (index > -1) {  
  131.                     mCodes.add(index + 1);  
  132.                 } else {  
  133.                     ret = false;  
  134.                     LogHelper.d(TAG, "invalidate char:" + ch);  
  135.                     break;  
  136.                 }  
  137.             }  
  138.             if (ret) {  
  139.                 mCodes.add(Common.STOP_TOKEN);  
  140.             }  
  141.         } else {  
  142.             ret = false;  
  143.         }  
  144.   
  145.         return ret;  
  146.     }  
  147.   
  148.     public void play(final String text) {  
  149.         if (STATE_STOP == mState && null != mCodeBook  
  150.                 && convertTextToCodes(text)) {  
  151.             mState = STATE_PENDING;  
  152.   
  153.             mPlayThread = new Thread() {  
  154.                 @Override  
  155.                 public void run() {  
  156.                     mPlayer.start();  
  157.                 }  
  158.             };  
  159.             if (null != mPlayThread) {  
  160.                 mPlayThread.start();  
  161.             }  
  162.   
  163.             mEncodeThread = new Thread() {  
  164.                 @Override  
  165.                 public void run() {  
  166.                     mEncoder.encode(mCodes, DEFAULT_GEN_DURATION);  
  167.                     stopPlayer();  
  168.                     mEncoder.stop();  
  169.                     mPlayer.stop();  
  170.                 }  
  171.             };  
  172.             if (null != mEncodeThread) {  
  173.                 mEncodeThread.start();  
  174.             }  
  175.   
  176.             mState = STATE_START;  
  177.         }  
  178.     }  
  179.   
  180.     public void stop() {  
  181.         if (STATE_START == mState) {  
  182.             mState = STATE_PENDING;  
  183.             mEncoder.stop();  
  184.             if (null != mEncodeThread) {  
  185.                 try {  
  186.                     mEncodeThread.join();  
  187.                 } catch (InterruptedException e) {  
  188.                     e.printStackTrace();  
  189.                 } finally {  
  190.                     mEncodeThread = null;  
  191.                 }  
  192.             }  
  193.   
  194.         }  
  195.     }  
  196.   
  197.     private void stopPlayer() {  
  198.         if (mEncoder.isStoped()) {  
  199.             mPlayer.stop();  
  200.         }  
  201.   
  202.         // put end buffer  
  203.         mBuffer.putFull(BufferData.getEmptyBuffer());  
  204.   
  205.         if (null != mPlayThread) {  
  206.             try {  
  207.                 mPlayThread.join();  
  208.             } catch (InterruptedException e) {  
  209.                 e.printStackTrace();  
  210.             } finally {  
  211.                 mPlayThread = null;  
  212.             }  
  213.         }  
  214.   
  215.         mBuffer.reset();  
  216.         mState = STATE_STOP;  
  217.     }  
  218.   
  219.     @Override  
  220.     public void onStartEncode() {  
  221.         LogHelper.d(TAG, "onStartGen");  
  222.     }  
  223.   
  224.     @Override  
  225.     public void freeEncodeBuffer(BufferData buffer) {  
  226.         if (null != buffer) {  
  227.             mBuffer.putFull(buffer);  
  228.         }  
  229.     }  
  230.   
  231.     @Override  
  232.     public BufferData getEncodeBuffer() {  
  233.         return mBuffer.getEmpty();  
  234.     }  
  235.   
  236.     @Override  
  237.     public void onEndEncode() {  
  238.     }  
  239.   
  240.     @Override  
  241.     public BufferData getPlayBuffer() {  
  242.         return mBuffer.getFull();  
  243.     }  
  244.   
  245.     @Override  
  246.     public void freePlayData(BufferData data) {  
  247.         mBuffer.putEmpty(data);  
  248.     }  
  249.   
  250.     @Override  
  251.     public void onPlayStart() {  
  252.         if (null != mListener) {  
  253.             mListener.onPlayStart();  
  254.         }  
  255.     }  
  256.   
  257.     @Override  
  258.     public void onPlayStop() {  
  259.         if (null != mListener) {  
  260.             mListener.onPlayEnd();  
  261.         }  
  262.     }  
  263.   
  264. }  

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

    1.DEFAULT_GEN_DURATION是指的每个音频信号的持续时长,默认为0.1秒

    2.convertTextToCodes()方法是将需要编码的文本进行过滤,过滤规则就是CodeBook,如果要进行传输的数字不在CodeBook里面,程序就不会继续向下执行了

    

    虽然SinVoicePlayer类很重要,但是真正完成声音播放任务的并不是他,而是PcmPlayer类。因为源代码的一些命名很混乱很不明确,因此我修改了一些命名,如果想看原项目的同学不要感到惊讶。下面我们看一下这个类的实现。

[java]  view plain copy
  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: [email protected]                                         ** 
  13.  ** QQ   : 29600731                                                     ** 
  14.  ** Weibo: http://weibo.com/gujicheng197                                ** 
  15.  ************************************************************************* 
  16.  */  
  17. package com.libra.sinvoice;  
  18.   
  19. import android.media.AudioManager;  
  20. import android.media.AudioTrack;  
  21.   
  22. import com.libra.sinvoice.Buffer.BufferData;  
  23.   
  24. /** 
  25.  *  
  26.  * @ClassName: com.libra.sinvoice.PcmPlayer 
  27.  * @Description: PCM播放器 
  28.  * @author zhaokaiqiang 
  29.  * @date 2014-11-15 下午1:10:18 
  30.  *  
  31.  */  
  32. public class PcmPlayer {  
  33.   
  34.     private final static String TAG = "PcmPlayer";  
  35.     private final static int STATE_START = 1;  
  36.     private final static int STATE_STOP = 2;  
  37.     // 播放状态,用于控制播放或者是停止  
  38.     private int mState;  
  39.     private AudioTrack audioTrack;  
  40.     // 已经播放过的字节长度  
  41.     private long playedLen;  
  42.     private PcmListener pcmListener;  
  43.     private PcmCallback playerCallback;  
  44.   
  45.     public static interface PcmListener {  
  46.   
  47.         void onPcmPlayStart();  
  48.   
  49.         void onPcmPlayStop();  
  50.     }  
  51.   
  52.     public static interface PcmCallback {  
  53.   
  54.         BufferData getPlayBuffer();  
  55.   
  56.         void freePlayData(BufferData data);  
  57.     }  
  58.   
  59.     public PcmPlayer(PcmCallback callback, int sampleRate, int channel,  
  60.             int format, int bufferSize) {  
  61.         playerCallback = callback;  
  62.         // 初始化AudioTrack对象(音频流类型,采样率,通道,格式,缓冲区大小,模式)  
  63.         audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate,  
  64.                 channel, format, bufferSize, AudioTrack.MODE_STREAM);  
  65.         mState = STATE_STOP;  
  66.     }  
  67.   
  68.     public void setListener(PcmListener listener) {  
  69.         pcmListener = listener;  
  70.     }  
  71.   
  72.     public void start() {  
  73.   
  74.         if (STATE_STOP == mState && null != audioTrack) {  
  75.             mState = STATE_START;  
  76.             playedLen = 0;  
  77.   
  78.             if (null != playerCallback) {  
  79.   
  80.                 if (null != pcmListener) {  
  81.                     pcmListener.onPcmPlayStart();  
  82.                 }  
  83.                 while (STATE_START == mState) {  
  84.                     // 获取要播放的字节数据  
  85.                     BufferData data = playerCallback.getPlayBuffer();  
  86.                     if (null != data) {  
  87.                         if (null != data.byteData) {  
  88.                             // 设置要播放的字节数据  
  89.                             int len = audioTrack.write(data.byteData, 0,  
  90.                                     data.getFilledSize());  
  91.                             // 首次进入,播放声音  
  92.                             if (0 == playedLen) {  
  93.                                 audioTrack.play();  
  94.                             }  
  95.                             playedLen += len;  
  96.                             // 释放数据  
  97.                             playerCallback.freePlayData(data);  
  98.                         } else {  
  99.                             LogHelper.d(TAG,  
  100.                                     "it is the end of input, so need stop");  
  101.                             break;  
  102.                         }  
  103.                     } else {  
  104.                         LogHelper.d(TAG, "get null data");  
  105.                         break;  
  106.                     }  
  107.   
  108.                 }  
  109.   
  110.                 if (STATE_STOP == mState) {  
  111.                     audioTrack.pause();  
  112.                     audioTrack.flush();  
  113.                     audioTrack.stop();  
  114.                 }  
  115.                 if (null != pcmListener) {  
  116.                     pcmListener.onPcmPlayStop();  
  117.                 }  
  118.             } else {  
  119.                 throw new IllegalArgumentException("PcmCallback can't be null");  
  120.             }  
  121.         }  
  122.     }  
  123.   
  124.     public void stop() {  
  125.         if (STATE_START == mState && null != audioTrack) {  
  126.             mState = STATE_STOP;  
  127.         }  
  128.     }  
  129. }  

     关于这个类,需要注意的是以下几点:

    1.PcmPalyer是通过AudioTrack类实现单频率播放的,在初始化AudioTrack对象的时候,需要穿很多参数,我在代码里面已经注释。在SinVoicePlayer中初始化PcmPlayer对象的时候,使用的是下面的参数进行的初始化

mPlayer = new PcmPlayer(this, sampleRate, AudioFormat.CHANNEL_OUT_MONO,

AudioFormat.ENCODING_PCM_16BIT, bufferSize);

    sampleRate是采样率,默认44.1kHZ,AudioFormat.CHANNEL_OUT_MONO是使用单声道播放,还有立体声也就是双声道模式,为了保证频率的一致,使用单声道比较合适。AudioFormat.ENCODING_PCM_16BIT是指使用16位的PCM格式编码,PCM也是一种声音的编码格式。


    2.在start()方法里面的while循环是为了不断的取出要播放的字节数据,audioTrack.play()方法只会执行一次,在stop()里面把mState赋值为STATE_STOP,while循环就会退出,从而执行下面audioTrack的停止方法,结束声音的播放。


    既然最后播放声音的重担落到了AudioTrack类的身上,那么我们就没有理由不去了解一下这个类了。


    AudioTrack是一个用来播放声音的类,构造函数中需要传下面这些参数

public AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat,

            int bufferSizeInBytes, int mode)


    重点说一下第一个和最后一个参数的含义。

    AudioTrack中有MODE_STATIC和MODE_STREAM两种分类。STREAM的意思是由用户在应用程序通过write方式把数据一次一次得写到audiotrack中。这个和我们在socket中发送数据一样,应用层从某个地方获取数据,例如通过编解码得到PCM数据,然后write到audiotrack。这种方式的坏处就是总是在JAVA层和Native层交互,效率损失较大。
    STATIC的意思是一开始创建的时候,就把音频数据放到一个固定的buffer,然后直接传给audiotrack,后续就不用一次次得write了。AudioTrack会自己播放这个buffer中的数据。这种方法对于铃声等内存占用较小,延时要求较高的声音来说很适用。

    由于我们这里需要动态的写入不同的数据,因此,我们需要用MODE_STREAM模式,上面的代码中,是先write的数据,然后play(),其实正规的写法是先play(),然后通过write方法往AudioTrack里面写入字节数据即可。一开始我也疑惑呢,后来发现play方法只执行一次,而write方法会执行多次,即一边输入数据一边输出。


    在构造AudioTrack的第一个参数streamType和Android中的AudioManager有关系,涉及到手机上的音频管理策略。
Android将系统的声音分为以下几类常见的:
         STREAM_ALARM:警告声
         STREAM_MUSCI:音乐声,例如music等
         STREAM_RING:铃声
         STREAM_SYSTEM:系统声音
         STREAM_VOCIE_CALL:电话声音


    为什么要分这么多呢?例如在听music的时候接到电话,这个时候music播放肯定会停止,此时你只能听到电话,如果你调节音量的话,这个调节肯定只对电话起作用。当电话打完了,再回到music,肯定不用再调节音量了。这可以让系统将这几种声音的数据分开管理。


    这篇文章先介绍到这里,下篇文章将介绍数字编码的实现细节。


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

你可能感兴趣的:(移动设备的“声波通信/验证”的实现——SinVoice开源项目介绍(二))