天天看点

声波通信开源项SinVoice介绍二

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

    再来一张项目的结构图

声波通信开源项SinVoice介绍二

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

[java]  view plain copy

声波通信开源项SinVoice介绍二
声波通信开源项SinVoice介绍二
  1. package com.libra.sinvoice;  
  2. import java.util.ArrayList;  
  3. import java.util.List;  
  4. import android.media.AudioFormat;  
  5. import android.text.TextUtils;  
  6. import com.libra.sinvoice.Buffer.BufferData;  
  7. public class SinVoicePlayer implements Encoder.Listener, Encoder.Callback,  
  8.         PcmPlayer.Listener, PcmPlayer.Callback {  
  9.     private final static String TAG = "SinVoicePlayer";  
  10.     private final static int STATE_START = 1;  
  11.     private final static int STATE_STOP = 2;  
  12.     private final static int STATE_PENDING = 3;  
  13.     // 默认的间隔时间  
  14.     private final static int DEFAULT_GEN_DURATION = 100;  
  15.     private String mCodeBook;  
  16.     // 用于存放使用CoodBook编码过的数字  
  17.     private List<Integer> mCodes = new ArrayList<Integer>();  
  18.     private Encoder mEncoder;  
  19.     private PcmPlayer mPlayer;  
  20.     private Buffer mBuffer;  
  21.     private int mState;  
  22.     private Listener mListener;  
  23.     private Thread mPlayThread;  
  24.     private Thread mEncodeThread;  
  25.     public static interface Listener {  
  26.         void onPlayStart();  
  27.         void onPlayEnd();  
  28.     }  
  29.     public SinVoicePlayer() {  
  30.         this(Common.DEFAULT_CODE_BOOK);  
  31.     }  
  32.     public SinVoicePlayer(String codeBook) {  
  33.         this(codeBook, Common.DEFAULT_SAMPLE_RATE, Common.DEFAULT_BUFFER_SIZE,  
  34.                 Common.DEFAULT_BUFFER_COUNT);  
  35.     }  
  36.     public SinVoicePlayer(String codeBook, int sampleRate, int bufferSize,  
  37.             int buffCount) {  
  38.         mState = STATE_STOP;  
  39.         mBuffer = new Buffer(buffCount, bufferSize);  
  40.         mEncoder = new Encoder(this, sampleRate, SinGenerator.BITS_16,  
  41.                 bufferSize);  
  42.         mEncoder.setListener(this);  
  43.         mPlayer = new PcmPlayer(this, sampleRate, AudioFormat.CHANNEL_OUT_MONO,  
  44.                 AudioFormat.ENCODING_PCM_16BIT, bufferSize);  
  45.         mPlayer.setListener(this);  
  46.         setCodeBook(codeBook);  
  47.     }  
  48.     public void setListener(Listener listener) {  
  49.         mListener = listener;  
  50.     }  
  51.     public void setCodeBook(String codeBook) {  
  52.         if (!TextUtils.isEmpty(codeBook)  
  53.                 && codeBook.length() < Encoder.getMaxCodeCount() - 1) {  
  54.             mCodeBook = codeBook;  
  55.         }  
  56.     }  
  57.     private boolean convertTextToCodes(String text) {  
  58.         boolean ret = true;  
  59.         if (!TextUtils.isEmpty(text)) {  
  60.             mCodes.clear();  
  61.             mCodes.add(Common.START_TOKEN);  
  62.             int len = text.length();  
  63.             for (int i = 0; i < len; ++i) {  
  64.                 char ch = text.charAt(i);  
  65.                 int index = mCodeBook.indexOf(ch);  
  66.                 if (index > -1) {  
  67.                     mCodes.add(index + 1);  
  68.                 } else {  
  69.                     ret = false;  
  70.                     LogHelper.d(TAG, "invalidate char:" + ch);  
  71.                     break;  
  72.                 }  
  73.             }  
  74.             if (ret) {  
  75.                 mCodes.add(Common.STOP_TOKEN);  
  76.             }  
  77.         } else {  
  78.             ret = false;  
  79.         }  
  80.         return ret;  
  81.     }  
  82.     public void play(final String text) {  
  83.         if (STATE_STOP == mState && null != mCodeBook  
  84.                 && convertTextToCodes(text)) {  
  85.             mState = STATE_PENDING;  
  86.             mPlayThread = new Thread() {  
  87.                 @Override  
  88.                 public void run() {  
  89.                     mPlayer.start();  
  90.                 }  
  91.             };  
  92.             if (null != mPlayThread) {  
  93.                 mPlayThread.start();  
  94.             }  
  95.             mEncodeThread = new Thread() {  
  96.                 @Override  
  97.                 public void run() {  
  98.                     mEncoder.encode(mCodes, DEFAULT_GEN_DURATION);  
  99.                     stopPlayer();  
  100.                     mEncoder.stop();  
  101.                     mPlayer.stop();  
  102.                 }  
  103.             };  
  104.             if (null != mEncodeThread) {  
  105.                 mEncodeThread.start();  
  106.             }  
  107.             mState = STATE_START;  
  108.         }  
  109.     }  
  110.     public void stop() {  
  111.         if (STATE_START == mState) {  
  112.             mState = STATE_PENDING;  
  113.             mEncoder.stop();  
  114.             if (null != mEncodeThread) {  
  115.                 try {  
  116.                     mEncodeThread.join();  
  117.                 } catch (InterruptedException e) {  
  118.                     e.printStackTrace();  
  119.                 } finally {  
  120.                     mEncodeThread = null;  
  121.                 }  
  122.             }  
  123.         }  
  124.     }  
  125.     private void stopPlayer() {  
  126.         if (mEncoder.isStoped()) {  
  127.             mPlayer.stop();  
  128.         }  
  129.         // put end buffer  
  130.         mBuffer.putFull(BufferData.getEmptyBuffer());  
  131.         if (null != mPlayThread) {  
  132.             try {  
  133.                 mPlayThread.join();  
  134.             } catch (InterruptedException e) {  
  135.                 e.printStackTrace();  
  136.             } finally {  
  137.                 mPlayThread = null;  
  138.             }  
  139.         }  
  140.         mBuffer.reset();  
  141.         mState = STATE_STOP;  
  142.     }  
  143.     @Override  
  144.     public void onStartEncode() {  
  145.         LogHelper.d(TAG, "onStartGen");  
  146.     }  
  147.     @Override  
  148.     public void freeEncodeBuffer(BufferData buffer) {  
  149.         if (null != buffer) {  
  150.             mBuffer.putFull(buffer);  
  151.         }  
  152.     }  
  153.     @Override  
  154.     public BufferData getEncodeBuffer() {  
  155.         return mBuffer.getEmpty();  
  156.     }  
  157.     @Override  
  158.     public void onEndEncode() {  
  159.     }  
  160.     @Override  
  161.     public BufferData getPlayBuffer() {  
  162.         return mBuffer.getFull();  
  163.     }  
  164.     @Override  
  165.     public void freePlayData(BufferData data) {  
  166.         mBuffer.putEmpty(data);  
  167.     }  
  168.     @Override  
  169.     public void onPlayStart() {  
  170.         if (null != mListener) {  
  171.             mListener.onPlayStart();  
  172.         }  
  173.     }  
  174.     @Override  
  175.     public void onPlayStop() {  
  176.         if (null != mListener) {  
  177.             mListener.onPlayEnd();  
  178.         }  
  179.     }  
  180. }  

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

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

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

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

[java]  view plain copy

声波通信开源项SinVoice介绍二
声波通信开源项SinVoice介绍二
  1. package com.libra.sinvoice;  
  2. import android.media.AudioManager;  
  3. import android.media.AudioTrack;  
  4. import com.libra.sinvoice.Buffer.BufferData;  
  5. public class PcmPlayer {  
  6.     private final static String TAG = "PcmPlayer";  
  7.     private final static int STATE_START = 1;  
  8.     private final static int STATE_STOP = 2;  
  9.     // 播放状态,用于控制播放或者是停止  
  10.     private int mState;  
  11.     private AudioTrack audioTrack;  
  12.     // 已经播放过的字节长度  
  13.     private long playedLen;  
  14.     private PcmListener pcmListener;  
  15.     private PcmCallback playerCallback;  
  16.     public static interface PcmListener {  
  17.         void onPcmPlayStart();  
  18.         void onPcmPlayStop();  
  19.     }  
  20.     public static interface PcmCallback {  
  21.         BufferData getPlayBuffer();  
  22.         void freePlayData(BufferData data);  
  23.     }  
  24.     public PcmPlayer(PcmCallback callback, int sampleRate, int channel,  
  25.             int format, int bufferSize) {  
  26.         playerCallback = callback;  
  27.         // 初始化AudioTrack对象(音频流类型,采样率,通道,格式,缓冲区大小,模式)  
  28.         audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate,  
  29.                 channel, format, bufferSize, AudioTrack.MODE_STREAM);  
  30.         mState = STATE_STOP;  
  31.     }  
  32.     public void setListener(PcmListener listener) {  
  33.         pcmListener = listener;  
  34.     }  
  35.     public void start() {  
  36.         if (STATE_STOP == mState && null != audioTrack) {  
  37.             mState = STATE_START;  
  38.             playedLen = 0;  
  39.             if (null != playerCallback) {  
  40.                 if (null != pcmListener) {  
  41.                     pcmListener.onPcmPlayStart();  
  42.                 }  
  43.                 while (STATE_START == mState) {  
  44.                     // 获取要播放的字节数据  
  45.                     BufferData data = playerCallback.getPlayBuffer();  
  46.                     if (null != data) {  
  47.                         if (null != data.byteData) {  
  48.                             // 设置要播放的字节数据  
  49.                             int len = audioTrack.write(data.byteData, 0,  
  50.                                     data.getFilledSize());  
  51.                             // 首次进入,播放声音  
  52.                             if (0 == playedLen) {  
  53.                                 audioTrack.play();  
  54.                             }  
  55.                             playedLen += len;  
  56.                             // 释放数据  
  57.                             playerCallback.freePlayData(data);  
  58.                         } else {  
  59.                             LogHelper.d(TAG,  
  60.                                     "it is the end of input, so need stop");  
  61.                             break;  
  62.                         }  
  63.                     } else {  
  64.                         LogHelper.d(TAG, "get null data");  
  65.                         break;  
  66.                     }  
  67.                 }  
  68.                 if (STATE_STOP == mState) {  
  69.                     audioTrack.pause();  
  70.                     audioTrack.flush();  
  71.                     audioTrack.stop();  
  72.                 }  
  73.                 if (null != pcmListener) {  
  74.                     pcmListener.onPcmPlayStop();  
  75.                 }  
  76.             } else {  
  77.                 throw new IllegalArgumentException("PcmCallback can't be null");  
  78.             }  
  79.         }  
  80.     }  
  81.     public void stop() {  
  82.         if (STATE_START == mState && null != audioTrack) {  
  83.             mState = STATE_STOP;  
  84.         }  
  85.     }  
  86. }  

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

    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

继续阅读