• Android ilbc 语音对话示范(五)接收端处理【转】http://blog.csdn.net/ranxiedao/article/details/8080385


    Android ilbc 语音对话示范(五)接收端处理

    分类: Android 语音对话 483人阅读 评论(5) 收藏 举报

    此系列文章拖了N久,有好多人发邮件来询问我第五次的文章为什么没有写,其实非常抱歉,本人学生一个,暑假一直

    去公司实习,最近又忙着各种招聘找工作,没有时间好好写,现在抽空把最后一篇补上,水平有限,如过有不对的,请

    各位指正~

        前四篇文章分别介绍了 “代码结构”,“程序流程”,以及”发送方的处理”,现在就把接收方的处理流程做个介绍;

               

        如上图所示,接收方的操作有三个类:AudioDecoder(负责解码),AudioPlayer(负责播放解码后的音频),

    AudioReceiver(负责从服务器接收音频数据包),这三个类的流程在第三篇中有详细的介绍。

    1.AudioReceiver代码:

       AudioReceiver使用UDP方式从服务端接收音频数据,其过程比较简单,直接上代码:

     

    1. package xmu.swordbearer.audio.receiver;  
    2.   
    3. import java.io.IOException;  
    4. import java.net.DatagramPacket;  
    5. import java.net.DatagramSocket;  
    6. import java.net.SocketException;  
    7.   
    8. import xmu.swordbearer.audio.MyConfig;  
    9. import android.util.Log;  
    10.   
    11. public class AudioReceiver implements Runnable {  
    12.     String LOG = "NET Reciever ";  
    13.     int port = MyConfig.CLIENT_PORT;// 接收的端口  
    14.     DatagramSocket socket;  
    15.     DatagramPacket packet;  
    16.     boolean isRunning = false;  
    17.   
    18.     private byte[] packetBuf = new byte[1024];  
    19.     private int packetSize = 1024;  
    20.   
    21.     /* 
    22.      * 开始接收数据 
    23.      */  
    24.     public void startRecieving() {  
    25.         if (socket == null) {  
    26.             try {  
    27.                 socket = new DatagramSocket(port);  
    28.                 packet = new DatagramPacket(packetBuf, packetSize);  
    29.             } catch (SocketException e) {  
    30.             }  
    31.         }  
    32.         new Thread(this).start();  
    33.     }  
    34.   
    35.     /* 
    36.      * 停止接收数据 
    37.      */  
    38.     public void stopRecieving() {  
    39.         isRunning = false;  
    40.     }  
    41.   
    42.     /* 
    43.      * 释放资源 
    44.      */  
    45.     private void release() {  
    46.         if (packet != null) {  
    47.             packet = null;  
    48.         }  
    49.         if (socket != null) {  
    50.             socket.close();  
    51.             socket = null;  
    52.         }  
    53.     }  
    54.   
    55.     public void run() {  
    56.         // 在接收前,要先启动解码器  
    57.         AudioDecoder decoder = AudioDecoder.getInstance();  
    58.         decoder.startDecoding();  
    59.   
    60.         isRunning = true;  
    61.         try {  
    62.             while (isRunning) {  
    63.                 socket.receive(packet);  
    64.                 // 每接收一个UDP包,就交给解码器,等待解码  
    65.                 decoder.addData(packet.getData(), packet.getLength());  
    66.             }  
    67.   
    68.         } catch (IOException e) {  
    69.             Log.e(LOG, LOG + "RECIEVE ERROR!");  
    70.         }  
    71.         // 接收完成,停止解码器,释放资源  
    72.         decoder.stopDecoding();  
    73.         release();  
    74.         Log.e(LOG, LOG + "stop recieving");  
    75.     }  
    76.   
    77. }  

    2.AudioDecoder代码:

    解码的过程也很简单,由于接收端接收到了音频数据,然后就把数据交给解码器,所以解码的主要工作就是把接收端的数

    据取出来进行解码,如果解码正确,就将解码后的数据再转交给AudioPlayer去播放,这三个类之间是依次传递的 :

        AudioReceiver---->AudioDecoder--->AudioPlayer

    下面代码中有个List变量 private List<AudioData> dataList = null;这个就是用来存放数据的,每次解码时,dataList.remove(0),

    从最前端取出数据进行解码:

    1. package xmu.swordbearer.audio.receiver;  
    2.   
    3. import java.util.Collections;  
    4. import java.util.LinkedList;  
    5. import java.util.List;  
    6.   
    7. import xmu.swordbearer.audio.AudioCodec;  
    8. import xmu.swordbearer.audio.data.AudioData;  
    9. import android.util.Log;  
    10.   
    11. public class AudioDecoder implements Runnable {  
    12.   
    13.     String LOG = "CODEC Decoder ";  
    14.     private static AudioDecoder decoder;  
    15.   
    16.     private static final int MAX_BUFFER_SIZE = 2048;  
    17.   
    18.     private byte[] decodedData = new byte[1024];// data of decoded  
    19.     private boolean isDecoding = false;  
    20.     private List<AudioData> dataList = null;  
    21.   
    22.     public static AudioDecoder getInstance() {  
    23.         if (decoder == null) {  
    24.             decoder = new AudioDecoder();  
    25.         }  
    26.         return decoder;  
    27.     }  
    28.   
    29.     private AudioDecoder() {  
    30.         this.dataList = Collections  
    31.                 .synchronizedList(new LinkedList<AudioData>());  
    32.     }  
    33.   
    34.     /* 
    35.      * add Data to be decoded 
    36.      *  
    37.      * @ data:the data recieved from server 
    38.      *  
    39.      * @ size:data size 
    40.      */  
    41.     public void addData(byte[] data, int size) {  
    42.         AudioData adata = new AudioData();  
    43.         adata.setSize(size);  
    44.         byte[] tempData = new byte[size];  
    45.         System.arraycopy(data, 0, tempData, 0, size);  
    46.         adata.setRealData(tempData);  
    47.         dataList.add(adata);  
    48.         System.out.println(LOG + "add data once");  
    49.   
    50.     }  
    51.   
    52.     /* 
    53.      * start decode AMR data 
    54.      */  
    55.     public void startDecoding() {  
    56.         System.out.println(LOG + "start decoder");  
    57.         if (isDecoding) {  
    58.             return;  
    59.         }  
    60.         new Thread(this).start();  
    61.     }  
    62.   
    63.     public void run() {  
    64.         // start player first  
    65.         AudioPlayer player = AudioPlayer.getInstance();  
    66.         player.startPlaying();  
    67.         //  
    68.         this.isDecoding = true;  
    69.         // init ILBC parameter:30 ,20, 15  
    70.         AudioCodec.audio_codec_init(30);  
    71.   
    72.         Log.d(LOG, LOG + "initialized decoder");  
    73.         int decodeSize = 0;  
    74.         while (isDecoding) {  
    75.             while (dataList.size() > 0) {  
    76.                 AudioData encodedData = dataList.remove(0);  
    77.                 decodedData = new byte[MAX_BUFFER_SIZE];  
    78.   
    79.                 byte[] data = encodedData.getRealData();  
    80.                 //  
    81.                 decodeSize = AudioCodec.audio_decode(data, 0,  
    82.                         encodedData.getSize(), decodedData, 0);  
    83.                 if (decodeSize > 0) {  
    84.                     // add decoded audio to player  
    85.                     player.addData(decodedData, decodeSize);  
    86.                     // clear data  
    87.                     decodedData = new byte[decodedData.length];  
    88.                 }  
    89.             }  
    90.         }  
    91.         System.out.println(LOG + "stop decoder");  
    92.         // stop playback audio  
    93.         player.stopPlaying();  
    94.     }  
    95.   
    96.     public void stopDecoding() {  
    97.         this.isDecoding = false;  
    98.     }  
    99. }  

    3.AudioPlayer代码:

    播放器的工作流程其实和解码器一模一样,都是启动一个线程,然后不断从自己的 dataList中提取数据。

    不过要注意,播放器的一些参数配置非常的关键;

    播放声音时,使用了Android自带的 AudioTrack 这个类,它有这个方法:

    public int write(byte[] audioData,int offsetInBytes, int sizeInBytes)可以直接播放;

    所有播放器的代码如下:

    1. package xmu.swordbearer.audio.receiver;  
    2.   
    3. import java.util.Collections;  
    4. import java.util.LinkedList;  
    5. import java.util.List;  
    6.   
    7. import xmu.swordbearer.audio.data.AudioData;  
    8. import android.media.AudioFormat;  
    9. import android.media.AudioManager;  
    10. import android.media.AudioRecord;  
    11. import android.media.AudioTrack;  
    12. import android.util.Log;  
    13.   
    14. public class AudioPlayer implements Runnable {  
    15.     String LOG = "AudioPlayer ";  
    16.     private static AudioPlayer player;  
    17.   
    18.     private List<AudioData> dataList = null;  
    19.     private AudioData playData;  
    20.     private boolean isPlaying = false;  
    21.   
    22.     private AudioTrack audioTrack;  
    23.   
    24.     private static final int sampleRate = 8000;  
    25.     // 注意:参数配置  
    26.     private static final int channelConfig = AudioFormat.CHANNEL_IN_MONO;  
    27.     private static final int audioFormat = AudioFormat.ENCODING_PCM_16BIT;  
    28.   
    29.     private AudioPlayer() {  
    30.         dataList = Collections.synchronizedList(new LinkedList<AudioData>());  
    31.     }  
    32.   
    33.     public static AudioPlayer getInstance() {  
    34.         if (player == null) {  
    35.             player = new AudioPlayer();  
    36.         }  
    37.         return player;  
    38.     }  
    39.   
    40.     public void addData(byte[] rawData, int size) {  
    41.         AudioData decodedData = new AudioData();  
    42.         decodedData.setSize(size);  
    43.   
    44.         byte[] tempData = new byte[size];  
    45.         System.arraycopy(rawData, 0, tempData, 0, size);  
    46.         decodedData.setRealData(tempData);  
    47.         dataList.add(decodedData);  
    48.     }  
    49.   
    50.     /* 
    51.      * init Player parameters 
    52.      */  
    53.     private boolean initAudioTrack() {  
    54.         int bufferSize = AudioRecord.getMinBufferSize(sampleRate,  
    55.                 channelConfig, audioFormat);  
    56.         if (bufferSize < 0) {  
    57.             Log.e(LOG, LOG + "initialize error!");  
    58.             return false;  
    59.         }  
    60.         audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate,  
    61.                 channelConfig, audioFormat, bufferSize, AudioTrack.MODE_STREAM);  
    62.         // set volume:设置播放音量  
    63.         audioTrack.setStereoVolume(1.0f, 1.0f);  
    64.         audioTrack.play();  
    65.         return true;  
    66.     }  
    67.   
    68.     private void playFromList() {  
    69.         while (dataList.size() > 0 && isPlaying) {  
    70.             playData = dataList.remove(0);  
    71.             audioTrack.write(playData.getRealData(), 0, playData.getSize());  
    72.         }  
    73.     }  
    74.   
    75.     public void startPlaying() {  
    76.         if (isPlaying) {  
    77.             return;  
    78.         }  
    79.         new Thread(this).start();  
    80.     }  
    81.   
    82.     public void run() {  
    83.         this.isPlaying = true;  
    84.           
    85.         if (!initAudioTrack()) {  
    86.             Log.e(LOG, LOG + "initialized player error!");  
    87.             return;  
    88.         }  
    89.         while (isPlaying) {  
    90.             if (dataList.size() > 0) {  
    91.                 playFromList();  
    92.             } else {  
    93.                 try {  
    94.                     Thread.sleep(20);  
    95.                 } catch (InterruptedException e) {  
    96.                 }  
    97.             }  
    98.         }  
    99.         if (this.audioTrack != null) {  
    100.             if (this.audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {  
    101.                 this.audioTrack.stop();  
    102.                 this.audioTrack.release();  
    103.             }  
    104.         }  
    105.         Log.d(LOG, LOG + "end playing");  
    106.     }  
    107.   
    108.     public void stopPlaying() {  
    109.         this.isPlaying = false;  
    110.     }  
    111. }  

     

    4.简易服务端:

    为了方便测试,我自己用Java 写了一个UDP的服务器,其功能非常的弱,就是接收,然后转发给另一方:

    1. import java.io.IOException;  
    2. import java.net.DatagramPacket;  
    3. import java.net.DatagramSocket;  
    4. import java.net.InetAddress;  
    5. import java.net.SocketException;  
    6. import java.net.UnknownHostException;  
    7.   
    8. public class AudioServer implements Runnable {  
    9.   
    10.     DatagramSocket socket;  
    11.     DatagramPacket packet;// 从客户端接收到的UDP包  
    12.     DatagramPacket sendPkt;// 转发给另一个客户端的UDP包  
    13.   
    14.     byte[] pktBuffer = new byte[1024];  
    15.     int bufferSize = 1024;  
    16.     boolean isRunning = false;  
    17.     int myport = 5656;  
    18.   
    19.     // ///////////  
    20.     String clientIpStr = "192.168.1.104";  
    21.     InetAddress clientIp;  
    22.     int clientPort = 5757;  
    23.   
    24.     public AudioServer() {  
    25.         try {  
    26.             clientIp = InetAddress.getByName(clientIpStr);  
    27.         } catch (UnknownHostException e1) {  
    28.             e1.printStackTrace();  
    29.         }  
    30.         try {  
    31.             socket = new DatagramSocket(myport);  
    32.             packet = new DatagramPacket(pktBuffer, bufferSize);  
    33.         } catch (SocketException e) {  
    34.             e.printStackTrace();  
    35.         }  
    36.         System.out.println("服务器初始化完成");  
    37.     }  
    38.   
    39.     public void startServer() {  
    40.         this.isRunning = true;  
    41.         new Thread(this).start();  
    42.     }  
    43.   
    44.     public void run() {  
    45.         try {  
    46.             while (isRunning) {  
    47.                 socket.receive(packet);  
    48.                 sendPkt = new DatagramPacket(packet.getData(),  
    49.                         packet.getLength(), packet.getAddress(), clientPort);  
    50.                 socket.send(sendPkt);  
    51.                 try {  
    52.                     Thread.sleep(20);  
    53.                 } catch (InterruptedException e) {  
    54.                     e.printStackTrace();  
    55.                 }  
    56.             }  
    57.         } catch (IOException e) {  
    58.         }  
    59.     }  
    60.   
    61.     // main  
    62.     public static void main(String[] args) {  
    63.         new AudioServer().startServer();  
    64.     }  
    65. }  


    5.结语:

    Android使用 ILBC 进行语音通话的大致过程就讲述完了,此系列只是做一个ILBC 使用原理的介绍,距离真正的语音

    通话还有很多工作要做,缺点还是很多的:

       1. 文章中介绍的只是单方通话,如果要做成双方互相通话或者一对多的通话,就需要增加更多的流程处理,其服务端

    也要做很多工作;

       2. 实时性:本程序在局域网中使用时,实时性还是较高的,但是再广域网中,效果可能会有所下降,除此之外,本

    程序还缺少时间戳的处理,如果网络状况不理想,或者数据延迟,就会导致语音播放前后混乱;

       3. 服务器很弱:真正的流媒体服务器,需要很强的功能,来对数据进行处理,我是为了方便,就写了一个简单的,

    最近打算移植live555,用来做专门的流媒体服务器,用RTP协议对数据进行封装,这样效果应该会好很多。

     

    现在,整个工程的代码都完成了,全部源代码可以在这里下载: http://download.csdn.net/detail/ranxiedao/4923759

    BY:http://blog.csdn.net/ranxiedao

  • 相关阅读:
    常见正则总结
    word 操作教程
    word调整技巧
    关于如何自定义handler
    html 处理
    iis 导入和导出配置——iis管理
    前端学习
    动态添加js的方法
    jquery学习笔记
    php学习笔记
  • 原文地址:https://www.cnblogs.com/songtzu/p/2891982.html
Copyright © 2020-2023  润新知