Android ilbc 語音對話示范(五)接收端處理

      介紹瞭 “代碼結構”,“程序流程”,以及”發送方的處理”,現在就把接收方的處理流程做個介紹;
          
    如上圖所示,接收方的操作有三個類:AudioDecoder(負責解碼),AudioPlayer(負責播放解碼後的音頻),
AudioReceiver(負責從服務器接收音頻數據包),這三個類的流程在第三篇中有詳細的介紹。
1.AudioReceiver代碼:
   AudioReceiver使用UDP方式從服務端接收音頻數據,其過程比較簡單,直接上代碼:
 
package xmu.swordbearer.audio.receiver; 
 
import java.io.IOException; 
import java.net.DatagramPacket; 
import java.net.DatagramSocket; 
import java.net.SocketException; 
 
import xmu.swordbearer.audio.MyConfig; 
import android.util.Log; 
 
public class AudioReceiver implements Runnable { 
    String LOG = "NET Reciever "; 
    int port = MyConfig.CLIENT_PORT;// 接收的端口 
    DatagramSocket socket; 
    DatagramPacket packet; 
    boolean isRunning = false; 
 
    private byte[] packetBuf = new byte[1024]; 
    private int packetSize = 1024; 
 
    /*
     * 開始接收數據
     */ 
    public void startRecieving() { 
        if (socket == null) { 
            try { 
                socket = new DatagramSocket(port); 
                packet = new DatagramPacket(packetBuf, packetSize); 
            } catch (SocketException e) { 
            } 
        } 
        new Thread(this).start(); 
    } 
 
    /*
     * 停止接收數據
     */ 
    public void stopRecieving() { 
        isRunning = false; 
    } 
 
    /*
     * 釋放資源
     */ 
    private void release() { 
        if (packet != null) { 
            packet = null; 
        } 
        if (socket != null) { 
            socket.close(); 
            socket = null; 
        } 
    } 
 
    public void run() { 
        // 在接收前,要先啟動解碼器 
        AudioDecoder decoder = AudioDecoder.getInstance(); 
        decoder.startDecoding(); 
 
        isRunning = true; 
        try { 
            while (isRunning) { 
                socket.receive(packet); 
                // 每接收一個UDP包,就交給解碼器,等待解碼 
                decoder.addData(packet.getData(), packet.getLength()); 
            } 
 
        } catch (IOException e) { 
            Log.e(LOG, LOG + "RECIEVE ERROR!"); 
        } 
        // 接收完成,停止解碼器,釋放資源 
        decoder.stopDecoding(); 
        release(); 
        Log.e(LOG, LOG + "stop recieving"); 
    } 
 

2.AudioDecoder代碼:
解碼的過程也很簡單,由於接收端接收到瞭音頻數據,然後就把數據交給解碼器,所以解碼的主要工作就是把接收端的數
據取出來進行解碼,如果解碼正確,就將解碼後的數據再轉交給AudioPlayer去播放,這三個類之間是依次傳遞的 :
    AudioReceiver—->AudioDecoder—>AudioPlayer
下面代碼中有個List變量 private List<AudioData> dataList = null; 這個就是用來存放數據的,每次解碼時,dataList.remove(0),
從最前端取出數據進行解碼:
package xmu.swordbearer.audio.receiver; 
 
import java.util.Collections; 
import java.util.LinkedList; 
import java.util.List; 
 
import xmu.swordbearer.audio.AudioCodec; 
import xmu.swordbearer.audio.data.AudioData; 
import android.util.Log; 
 
public class AudioDecoder implements Runnable { 
 
    String LOG = "CODEC Decoder "; 
    private static AudioDecoder decoder; 
 
    private static final int MAX_BUFFER_SIZE = 2048; 
 
    private byte[] decodedData = new byte[1024];// data of decoded 
    private boolean isDecoding = false; 
    private List<AudioData> dataList = null; 
 
    public static AudioDecoder getInstance() { 
        if (decoder == null) { 
            decoder = new AudioDecoder(); 
        } 
        return decoder; 
    } 
 
    private AudioDecoder() { 
        this.dataList = Collections 
                .synchronizedList(new LinkedList<AudioData>()); 
    } 
 
    /*
     * add Data to be decoded
     * 
     * @ data:the data recieved from server
     * 
     * @ size:data size
     */ 
    public void addData(byte[] data, int size) { 
        AudioData adata = new AudioData(); 
        adata.setSize(size); 
        byte[] tempData = new byte[size]; 
        System.arraycopy(data, 0, tempData, 0, size); 
        adata.setRealData(tempData); 
        dataList.add(adata); 
        System.out.println(LOG + "add data once"); 
 
    } 
 
    /*
     * start decode AMR data
     */ 
    public void startDecoding() { 
        System.out.println(LOG + "start decoder"); 
        if (isDecoding) { 
            return; 
        } 
        new Thread(this).start(); 
    } 
 
    public void run() { 
        // start player first 
        AudioPlayer player = AudioPlayer.getInstance(); 
        player.startPlaying(); 
        // 
        this.isDecoding = true; 
        // init ILBC parameter:30 ,20, 15 
        AudioCodec.audio_codec_init(30); 
 
        Log.d(LOG, LOG + "initialized decoder"); 
        int decodeSize = 0; 
        while (isDecoding) { 
            while (dataList.size() > 0) { 
                AudioData encodedData = dataList.remove(0); 
                decodedData = new byte[MAX_BUFFER_SIZE]; 
 
                byte[] data = encodedData.getRealData(); 
                // 
                decodeSize = AudioCodec.audio_decode(data, 0, 
                        encodedData.getSize(), decodedData, 0); 
                if (decodeSize > 0) { 
                    // add decoded audio to player 
                    player.addData(decodedData, decodeSize); 
                    // clear data 
                    decodedData = new byte[decodedData.length]; 
                } 
            } 
        } 
        System.out.println(LOG + "stop decoder"); 
        // stop playback audio 
        player.stopPlaying(); 
    } 
 
    public void stopDecoding() { 
        this.isDecoding = false; 
    } 

3.AudioPlayer代碼:
播放器的工作流程其實和解碼器一模一樣,都是啟動一個線程,然後不斷從自己的 dataList中提取數據。
不過要註意,播放器的一些參數配置非常的關鍵;
播放聲音時,使用瞭Android自帶的 AudioTrack 這個類,它有這個方法:
public int write(byte[] audioData,int offsetInBytes, int sizeInBytes) 可以直接播放;
所有播放器的代碼如下:
package xmu.swordbearer.audio.receiver; 
 
import java.util.Collections; 
import java.util.LinkedList; 
import java.util.List; 
 
import xmu.swordbearer.audio.data.AudioData; 
import android.media.AudioFormat; 
import android.media.AudioManager; 
import android.media.AudioRecord; 
import android.media.AudioTrack; 
import android.util.Log; 
 
public class AudioPlayer implements Runnable { 
    String LOG = "AudioPlayer "; 
    private static AudioPlayer player; 
 
    private List<AudioData> dataList = null; 
    private AudioData playData; 
    private boolean isPlaying = false; 
 
    private AudioTrack audioTrack; 
 
    private static final int sampleRate = 8000; 
    // 註意:參數配置 
    private static final int channelConfig = AudioFormat.CHANNEL_IN_MONO; 
    private static final int audioFormat = AudioFormat.ENCODING_PCM_16BIT; 
 
    private AudioPlayer() { 
        dataList = Collections.synchronizedList(new LinkedList<AudioData>()); 
    } 
 
    public static AudioPlayer getInstance() { 
        if (player == null) { 
            player = new AudioPlayer(); 
        } 
        return player; 
    } 
 
    public void addData(byte[] rawData, int size) { 
        AudioData decodedData = new AudioData(); 
        decodedData.setSize(size); 
 
        byte[] tempData = new byte[size]; 
        System.arraycopy(rawData, 0, tempData, 0, size); 
        decodedData.setRealData(tempData); 
        dataList.add(decodedData); 
    } 
 
    /*
     * init Player parameters
     */ 
    private boolean initAudioTrack() { 
        int bufferSize = AudioRecord.getMinBufferSize(sampleRate, 
                channelConfig, audioFormat); 
        if (bufferSize < 0) { 
            Log.e(LOG, LOG + "initialize error!"); 
            return false; 
        } 
        audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, 
                channelConfig, audioFormat, bufferSize, AudioTrack.MODE_STREAM); 
        // set volume:設置播放音量 
        audioTrack.setStereoVolume(1.0f, 1.0f); 
        audioTrack.play(); 
        return true; 
    } 
 
    private void playFromList() { 
        while (dataList.size() > 0 && isPlaying) { 
            playData = dataList.remove(0); 
            audioTrack.write(playData.getRealData(), 0, playData.getSize()); 
        } 
    } 
 
    public void startPlaying() { 
        if (isPlaying) { 
            return; 
        } 
        new Thread(this).start(); 
    } 
 
    public void run() { 
        this.isPlaying = true; 
         
        if (!initAudioTrack()) { 
            Log.e(LOG, LOG + "initialized player error!"); 
            return; 
        } 
        while (isPlaying) { 
            if (dataList.size() > 0) { 
                playFromList(); 
            } else { 
                try { 
                    Thread.sleep(20); 
                } catch (InterruptedException e) { 
                } 
            } 
        } 
        if (this.audioTrack != null) { 
            if (this.audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) { 
                this.audioTrack.stop(); 
                this.audioTrack.release(); 
            } 
        } 
        Log.d(LOG, LOG + "end playing"); 
    } 
 
    public void stopPlaying() { 
        this.isPlaying = false; 
    } 

4.簡易服務端:
為瞭方便測試,我自己用Java 寫瞭一個UDP的服務器,其功能非常的弱,就是接收,然後轉發給另一方:
import java.io.IOException; 
import java.net.DatagramPacket; 
import java.net.DatagramSocket; 
import java.net.InetAddress; 
import java.net.SocketException; 
import java.net.UnknownHostException; 
 
public class AudioServer implements Runnable { 
 
    DatagramSocket socket; 
    DatagramPacket packet;// 從客戶端接收到的UDP包 
    DatagramPacket sendPkt;// 轉發給另一個客戶端的UDP包 
 
    byte[] pktBuffer = new byte[1024]; 
    int bufferSize = 1024; 
    boolean isRunning = false; 
    int myport = 5656; 
 
    // /////////// 
    String clientIpStr = "192.168.1.104"; 
    InetAddress clientIp; 
    int clientPort = 5757; 
 
    public AudioServer() { 
        try { 
            clientIp = InetAddress.getByName(clientIpStr); 
        } catch (UnknownHostException e1) { 
            e1.printStackTrace(); 
        } 
        try { 
            socket = new DatagramSocket(myport); 
            packet = new DatagramPacket(pktBuffer, bufferSize); 
        } catch (SocketException e) { 
            e.printStackTrace(); 
        } 
        System.out.println("服務器初始化完成"); 
    } 
 
    public void startServer() { 
        this.isRunning = true; 
        new Thread(this).start(); 
    } 
 
    public void run() { 
        try { 
            while (isRunning) { 
                socket.receive(packet); 
                sendPkt = new DatagramPacket(packet.getData(), 
                        packet.getLength(), packet.getAddress(), clientPort); 
                socket.send(sendPkt); 
                try { 
                    Thread.sleep(20); 
                } catch (InterruptedException e) { 
                    e.printStackTrace(); 
                } 
            } 
        } catch (IOException e) { 
        } 
    } 
 
    // main 
    public static void main(String[] args) { 
        new AudioServer().startServer(); 
    } 

5.結語:
Android使用 ILBC 進行語音通話的大致過程就講述完瞭,此系列隻是做一個ILBC 使用原理的介紹,距離真正的語音
通話還有很多工作要做,缺點還是很多的:
   1. 文章中介紹的隻是單方通話,如果要做成雙方互相通話或者一對多的通話,就需要增加更多的流程處理,其服務端
也要做很多工作;
   2. 實時性:本程序在局域網中使用時,實時性還是較高的,但是再廣域網中,效果可能會有所下降,除此之外,本
程序還缺少時間戳的處理,如果網絡狀況不理想,或者數據延遲,就會導致語音播放前後混亂;
   3. 服務器很弱:真正的流媒體服務器,需要很強的功能,來對數據進行處理,我是為瞭方便,就寫瞭一個簡單的,
最近打算移植live555,用來做專門的流媒體服務器,用RTP協議對數據進行封裝,這樣效果應該會好很多。

 

You May Also Like