聲波通信開源項SinVoice介紹二


    在上一篇的文章中,我們介紹了聲波通信/驗證的原理和基本使用,這一篇,我們將就一些細節進行談論。

    再來一張項目的結構圖


    SinVoicePlayer類是我們使用的時候直接接觸的類,通過調用play()方法,我們就能將需要傳輸的數字播放出去,下面是這個類的代碼實現

[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: [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<Integer> mCodes = new ArrayList<Integer>();  
  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 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: [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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章