天天看點

聲波通信開源項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

繼續閱讀