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

来源:互联网 发布:apache tiles 编辑:程序博客网 时间:2024/05/21 10:32
1350443837_9554.png 

如上图所示,接收方的操作有三个类:AudioDecoder(负责解码),AudioPlayer(负责播放解码后的音频),AudioReceiver(负责从服务器接收音频数据包),这三个类的流程在第三篇中有详细的介绍。1.AudioReceiver代码:
   AudioReceiver使用UDP方式从服务端接收音频数据,其过程比较简单,直接上代码:

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

解码的过程也很简单,由于接收端接收到了音频数据,然后就把数据交给解码器,所以解码的主要工作就是把接收端的数据取出来进行解码,如果解码正确,就将解码后的数据再转交给AudioPlayer去播放,这三个类之间是依次传递的 :
AudioReceiver---->AudioDecoder--->AudioPlayer

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

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

播放声音时,使用了Android自带的 AudioTrack 这个类,它有这个方法:
public int write(byte[] audioData,int offsetInBytes, int sizeInBytes)可以直接播放;

所有播放器的代码如下:

  1. View Code 
  2. package xmu.swordbearer.audio.receiver;

  3. import java.util.Collections;
  4. import java.util.LinkedList;
  5. import java.util.List;

  6. import xmu.swordbearer.audio.data.AudioData;
  7. import android.media.AudioFormat;
  8. import android.media.AudioManager;
  9. import android.media.AudioRecord;
  10. import android.media.AudioTrack;
  11. import android.util.Log;

  12. public class AudioPlayer implements Runnable {
  13.      String LOG = "AudioPlayer ";
  14.      private static AudioPlayer player;

  15.      private List<AudioData> dataList = null;
  16.      private AudioData playData;
  17.      private boolean isPlaying = false;

  18.      private AudioTrack audioTrack;

  19.      private static final int sampleRate = 8000;
  20.      // 注意:参数配置
  21.      private static final int channelConfig = AudioFormat.CHANNEL_IN_MONO;
  22.      private static final int audioFormat = AudioFormat.ENCODING_PCM_16BIT;

  23.      private AudioPlayer() {
  24.          dataList = Collections.synchronizedList(new LinkedList<AudioData>());
  25.      }

  26.      public static AudioPlayer getInstance() {
  27.          if (player == null) {
  28.              player = new AudioPlayer();
  29.          }
  30.          return player;
  31.      }

  32.      public void addData(byte[] rawData, int size) {
  33.          AudioData decodedData = new AudioData();
  34.          decodedData.setSize(size);

  35.          byte[] tempData = new byte[size];
  36.          System.arraycopy(rawData, 0, tempData, 0, size);
  37.          decodedData.setRealData(tempData);
  38.          dataList.add(decodedData);
  39.      }

  40.      /*
  41.       * init Player parameters
  42.       */
  43.      private boolean initAudioTrack() {
  44.          int bufferSize = AudioRecord.getMinBufferSize(sampleRate,
  45.                  channelConfig, audioFormat);
  46.          if (bufferSize < 0) {
  47.              Log.e(LOG, LOG + "initialize error!");
  48.              return false;
  49.          }
  50.          audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate,
  51.                  channelConfig, audioFormat, bufferSize, AudioTrack.MODE_STREAM);
  52.          // set volume:设置播放音量
  53.          audioTrack.setStereoVolume(1.0f, 1.0f);
  54.          audioTrack.play();
  55.          return true;
  56.      }

  57.      private void playFromList() {
  58.          while (dataList.size() > 0 && isPlaying) {
  59.              playData = dataList.remove(0);
  60.              audioTrack.write(playData.getRealData(), 0, playData.getSize());
  61.          }
  62.      }

  63.      public void startPlaying() {
  64.          if (isPlaying) {
  65.              return;
  66.          }
  67.          new Thread(this).start();
  68.      }

  69.      public void run() {
  70.          this.isPlaying = true;
  71.          
  72.          if (!initAudioTrack()) {
  73.              Log.e(LOG, LOG + "initialized player error!");
  74.              return;
  75.          }
  76.          while (isPlaying) {
  77.              if (dataList.size() > 0) {
  78.                  playFromList();
  79.              } else {
  80.                  try {
  81.                      Thread.sleep(20);
  82.                  } catch (InterruptedException e) {
  83.                  }
  84.              }
  85.          }
  86.          if (this.audioTrack != null) {
  87.              if (this.audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
  88.                  this.audioTrack.stop();
  89.                  this.audioTrack.release();
  90.              }
  91.          }
  92.          Log.d(LOG, LOG + "end playing");
  93.      }

  94.      public void stopPlaying() {
  95.          this.isPlaying = false;
  96.      }
  97. }
复制代码
4.简易服务端:

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

  1. View Code 
  2. import java.io.IOException;
  3. import java.net.DatagramPacket;
  4. import java.net.DatagramSocket;
  5. import java.net.InetAddress;
  6. import java.net.SocketException;
  7. import java.net.UnknownHostException;

  8. public class AudioServer implements Runnable {

  9.      DatagramSocket socket;
  10.      DatagramPacket packet;// 从客户端接收到的UDP包
  11.      DatagramPacket sendPkt;// 转发给另一个客户端的UDP包

  12.      byte[] pktBuffer = new byte[1024];
  13.      int bufferSize = 1024;
  14.      boolean isRunning = false;
  15.      int myport = 5656;

  16.      // ///////////
  17.      String clientIpStr = "192.168.1.104";
  18.      InetAddress clientIp;
  19.      int clientPort = 5757;

  20.      public AudioServer() {
  21.          try {
  22.              clientIp = InetAddress.getByName(clientIpStr);
  23.          } catch (UnknownHostException e1) {
  24.              e1.printStackTrace();
  25.          }
  26.          try {
  27.              socket = new DatagramSocket(myport);
  28.              packet = new DatagramPacket(pktBuffer, bufferSize);
  29.          } catch (SocketException e) {
  30.              e.printStackTrace();
  31.          }
  32.          System.out.println("服务器初始化完成");
  33.      }

  34.      public void startServer() {
  35.          this.isRunning = true;
  36.          new Thread(this).start();
  37.      }

  38.      public void run() {
  39.          try {
  40.              while (isRunning) {
  41.                  socket.receive(packet);
  42.                  sendPkt = new DatagramPacket(packet.getData(),
  43.                          packet.getLength(), packet.getAddress(), clientPort);
  44.                  socket.send(sendPkt);
  45.                  try {
  46.                      Thread.sleep(20);
  47.                  } catch (InterruptedException e) {
  48.                      e.printStackTrace();
  49.                  }
  50.              }
  51.          } catch (IOException e) {
  52.          }
  53.      }

  54.      // main
  55.      public static void main(String[] args) {
  56.          new AudioServer().startServer();
  57.      }
  58. }
复制代码
5.结语:

Android使用 ILBC 进行语音通话的大致过程就讲述完了,此系列只是做一个ILBC 使用原理的介绍,距离真正的语音通话还有很多工作要做,缺点还是很多的:

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

   2. 实时性:本程序在局域网中使用时,实时性还是较高的,但是再广域网中,效果可能会有所下降,除此之外,本程序还缺少时间戳的处理,如果网络状况不理想,或者数据延迟,就会导致语音播放前后混乱;

   3. 服务器很弱:真正的流媒体服务器,需要很强的功能,来对数据进行处理,我是为了方便,就写了一个简单的,最近打算移植live555,用来做专门的流媒体服务器,用RTP协议对数据进行封装,这样效果应该会好很多。

取自:http://www.cnblogs.com/swordbearer/archive/2012/10/17/2662164.html
原创粉丝点击